1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.internal;
21  
22  import java.lang.reflect.Field;
23  import java.lang.reflect.Modifier;
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.HashMap;
27  import java.util.HashSet;
28  import java.util.List;
29  import java.util.Locale;
30  import java.util.Map;
31  import java.util.Map.Entry;
32  import java.util.Properties;
33  import java.util.Set;
34  import java.util.TreeMap;
35  import java.util.stream.Collectors;
36  import java.util.stream.Stream;
37  
38  import org.junit.Assert;
39  import org.junit.Test;
40  
41  import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
42  import com.puppycrawl.tools.checkstyle.Checker;
43  import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
44  import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
45  import com.puppycrawl.tools.checkstyle.GlobalStatefulCheck;
46  import com.puppycrawl.tools.checkstyle.ModuleFactory;
47  import com.puppycrawl.tools.checkstyle.StatelessCheck;
48  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
49  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
50  import com.puppycrawl.tools.checkstyle.api.Configuration;
51  import com.puppycrawl.tools.checkstyle.checks.imports.ImportControlCheck;
52  import com.puppycrawl.tools.checkstyle.internal.utils.CheckUtil;
53  import com.puppycrawl.tools.checkstyle.internal.utils.ConfigurationUtil;
54  import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
55  import com.puppycrawl.tools.checkstyle.internal.utils.XdocUtil;
56  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
57  import com.puppycrawl.tools.checkstyle.utils.ModuleReflectionUtil;
58  
59  public class AllChecksTest extends AbstractModuleTestSupport {
60  
61      private static final Locale[] ALL_LOCALES = {
62          Locale.GERMAN,
63          new Locale("es"),
64          new Locale("fi"),
65          Locale.FRENCH,
66          Locale.JAPANESE,
67          new Locale("pt"),
68          new Locale("tr"),
69          Locale.CHINESE,
70          Locale.ENGLISH,
71      };
72  
73      private static final Map<String, Set<String>> CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE =
74              new HashMap<>();
75      private static final Map<String, Set<String>> GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE =
76              new HashMap<>();
77  
78      static {
79          // checkstyle
80  
81          CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("NoWhitespaceBefore", Stream.of(
82                  // we use GenericWhitespace for this behavior
83                  "GENERIC_START", "GENERIC_END").collect(Collectors.toSet()));
84          CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("AbbreviationAsWordInName", Stream.of(
85                  // enum values should be uppercase, we use EnumValueNameCheck instead
86                  "ENUM_CONSTANT_DEF").collect(Collectors.toSet()));
87          CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("FinalLocalVariable", Stream.of(
88                  // we prefer all parameters be effectively final as to not damage readability
89                  // we use ParameterAssignmentCheck to enforce this
90                  "PARAMETER_DEF").collect(Collectors.toSet()));
91          // we have no need to block these specific tokens
92          CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("IllegalToken",
93                  Stream.of("LITERAL_SUPER", "LITERAL_ASSERT", "ENUM_CONSTANT_DEF",
94                          "TYPE_PARAMETERS", "TYPE_UPPER_BOUNDS", "NUM_DOUBLE", "LITERAL_SWITCH",
95                          "ANNOTATIONS", "LITERAL_SHORT", "LITERAL_PROTECTED", "FOR_CONDITION",
96                          "FOR_INIT", "LITERAL_LONG", "MINUS", "OBJBLOCK", "LITERAL_NULL",
97                          "ANNOTATION", "LITERAL_TRUE", "COMMENT_CONTENT", "LITERAL_CHAR",
98                          "PARAMETER_DEF", "POST_DEC", "ANNOTATION_FIELD_DEF", "BLOCK_COMMENT_END",
99                          "TYPE", "LITERAL_INT", "BSR", "ENUM", "ANNOTATION_MEMBER_VALUE_PAIR",
100                         "TYPECAST", "LITERAL_SYNCHRONIZED", "PLUS_ASSIGN", "DOT", "LPAREN",
101                         "LITERAL_IF", "LITERAL_CATCH", "BAND", "INTERFACE_DEF", "LOR", "BNOT",
102                         "METHOD_CALL", "AT", "ELLIPSIS", "ARRAY_INIT", "FOR_EACH_CLAUSE",
103                         "LITERAL_THROWS", "CHAR_LITERAL", "CASE_GROUP", "POST_INC", "SEMI",
104                         "LITERAL_FINALLY", "ASSIGN", "RESOURCE_SPECIFICATION", "STATIC_IMPORT",
105                         "GENERIC_START", "IMPORT", "SL", "VARIABLE_DEF", "LITERAL_DOUBLE",
106                         "RCURLY", "RESOURCE", "SR", "COMMA", "BAND_ASSIGN", "METHOD_DEF",
107                         "LITERAL_VOID", "NUM_LONG", "LITERAL_TRANSIENT", "LITERAL_THIS", "LCURLY",
108                         "MINUS_ASSIGN", "TYPE_LOWER_BOUNDS", "TYPE_ARGUMENT", "LITERAL_CLASS",
109                         "INSTANCE_INIT", "DIV", "STAR", "UNARY_MINUS", "FOR_ITERATOR", "NOT_EQUAL",
110                         "LE", "LITERAL_INTERFACE", "LITERAL_FLOAT", "LITERAL_INSTANCEOF",
111                         "BOR_ASSIGN", "LT", "SL_ASSIGN", "ELIST", "ANNOTATION_ARRAY_INIT",
112                         "MODIFIERS", "LITERAL_BREAK", "EXTENDS_CLAUSE", "TYPE_PARAMETER",
113                         "LITERAL_DEFAULT", "STATIC_INIT", "BSR_ASSIGN", "TYPE_EXTENSION_AND",
114                         "BOR", "LITERAL_PRIVATE", "LITERAL_THROW", "LITERAL_BYTE", "BXOR",
115                         "WILDCARD_TYPE", "FINAL", "PARAMETERS", "RPAREN", "SR_ASSIGN",
116                         "UNARY_PLUS", "EMPTY_STAT", "LITERAL_STATIC", "LITERAL_CONTINUE",
117                         "STAR_ASSIGN", "LAMBDA", "RBRACK", "BXOR_ASSIGN", "CTOR_CALL",
118                         "LITERAL_FALSE", "DO_WHILE", "LITERAL_PUBLIC", "LITERAL_WHILE", "PLUS",
119                         "INC", "CTOR_DEF", "GENERIC_END", "DIV_ASSIGN", "SLIST", "LNOT", "LAND",
120                         "LITERAL_ELSE", "ABSTRACT", "STRICTFP", "QUESTION", "LITERAL_NEW",
121                         "LITERAL_RETURN", "SINGLE_LINE_COMMENT", "INDEX_OP", "EXPR",
122                         "BLOCK_COMMENT_BEGIN", "PACKAGE_DEF", "IMPLEMENTS_CLAUSE", "NUM_FLOAT",
123                         "LITERAL_DO", "EOF", "GE", "RESOURCES", "MOD", "DEC", "EQUAL",
124                         "LITERAL_BOOLEAN", "CLASS_DEF", "COLON", "LITERAL_TRY", "ENUM_DEF", "GT",
125                         "NUM_INT", "ANNOTATION_DEF", "METHOD_REF", "TYPE_ARGUMENTS",
126                         "DOUBLE_COLON", "IDENT", "MOD_ASSIGN", "LITERAL_FOR", "SUPER_CTOR_CALL",
127                         "STRING_LITERAL", "ARRAY_DECLARATOR", "LITERAL_CASE").collect(
128                         Collectors.toSet()));
129         // we have no need to block specific token text
130         CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("IllegalTokenText",
131                 Stream.of("NUM_DOUBLE", "NUM_FLOAT", "NUM_INT", "NUM_LONG", "IDENT",
132                     "COMMENT_CONTENT", "STRING_LITERAL", "CHAR_LITERAL")
133                     .collect(Collectors.toSet()));
134         // we do not use this check as it is deprecated
135         CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("WriteTag",
136                 Stream.of("ENUM_CONSTANT_DEF", "METHOD_DEF", "CTOR_DEF", "ANNOTATION_FIELD_DEF")
137                         .collect(Collectors.toSet()));
138         // state of the configuration when test was made until reason found in
139         // https://github.com/checkstyle/checkstyle/issues/3730
140         CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("AnnotationLocation",
141                 Stream.of("CLASS_DEF", "CTOR_DEF", "ENUM_DEF", "INTERFACE_DEF",
142                         "METHOD_DEF", "VARIABLE_DEF").collect(Collectors.toSet()));
143         CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("NoLineWrap", Stream.of(
144                 // method declaration could be long due to "parameters/exceptions", it is ok to
145                 // be not strict there
146                 "METHOD_DEF", "CTOR_DEF",
147                 // type declaration could be long due to "extends/implements", it is ok to
148                 // be not strict there
149                 "CLASS_DEF", "ENUM_DEF", "INTERFACE_DEF")
150                 .collect(Collectors.toSet()));
151         CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("NoWhitespaceAfter", Stream.of(
152                 // whitespace after is preferred
153                 "TYPECAST", "LITERAL_SYNCHRONIZED").collect(Collectors.toSet()));
154         CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("SeparatorWrap", Stream.of(
155                 // needs context to decide what type of parentheses should be separated or not
156                 // which this check does not provide
157                 "LPAREN", "RPAREN").collect(Collectors.toSet()));
158         CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("NeedBraces", Stream.of(
159                 // we prefer no braces here as it looks unusual even though they help avoid sharing
160                 // scope of variables
161                 "LITERAL_DEFAULT", "LITERAL_CASE").collect(Collectors.toSet()));
162         CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("FinalParameters", Stream.of(
163                 // we prefer these to be effectively final as to not damage readability
164                 "FOR_EACH_CLAUSE", "LITERAL_CATCH").collect(Collectors.toSet()));
165         CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("WhitespaceAround", Stream.of(
166                 // we prefer no spaces on one side or both for these tokens
167                 "ARRAY_INIT",
168                 "ELLIPSIS",
169                 // these are covered by GenericWhitespaceCheck
170                 "WILDCARD_TYPE", "GENERIC_END", "GENERIC_START").collect(Collectors.toSet()));
171 
172         // google
173         GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("AnnotationLocation", Stream.of(
174                 // state of the configuration when test was made until reason found in
175                 // https://github.com/checkstyle/checkstyle/issues/3730
176                 "ANNOTATION_DEF", "ANNOTATION_FIELD_DEF", "ENUM_CONSTANT_DEF", "PACKAGE_DEF",
177                 "PARAMETER_DEF")
178                 .collect(Collectors.toSet()));
179         GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("AbbreviationAsWordInName", Stream.of(
180                 // enum values should be uppercase
181                 "ENUM_CONSTANT_DEF").collect(Collectors.toSet()));
182         GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("NoLineWrap", Stream.of(
183                 // method declaration could be long due to "parameters/exceptions", it is ok to
184                 // be not strict there
185                 "METHOD_DEF", "CTOR_DEF", "CLASS_DEF", "ENUM_DEF", "INTERFACE_DEF")
186                 .collect(Collectors.toSet()));
187         GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("SeparatorWrap", Stream.of(
188                 // location could be any to allow writing expressions for indexes evaluation
189                 // on new line, see https://github.com/checkstyle/checkstyle/issues/3752
190                 "RBRACK",
191                 // for some targets annotations can be used without wrapping, as described
192                 // in https://google.github.io/styleguide/javaguide.html#s4.8.5-annotations
193                 "AT",
194                 // location could be any to allow using for line separation in enum values,
195                 // see https://github.com/checkstyle/checkstyle/issues/3752
196                 "SEMI",
197                 // needs context to decide what type of parentheses should be separated or not
198                 // which this check does not provide
199                 "LPAREN", "RPAREN").collect(Collectors.toSet()));
200         GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("NeedBraces", Stream.of(
201                 // google doesn't require or prevent braces on these
202                 "LAMBDA", "LITERAL_DEFAULT", "LITERAL_CASE").collect(Collectors.toSet()));
203         GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("EmptyBlock", Stream.of(
204                 // google doesn't specifically mention empty braces at the start of a case/default
205                 "LITERAL_DEFAULT", "LITERAL_CASE",
206                 // can be empty for special cases via '6.2 Caught exceptions: not ignored'
207                 "LITERAL_CATCH",
208                 // specifically allowed via '5.2.4 Constant names'
209                 "ARRAY_INIT",
210                 // state of the configuration when test was made until
211                 // https://github.com/checkstyle/checkstyle/issues/4121
212                 "INSTANCE_INIT", "LITERAL_DO", "LITERAL_FOR", "LITERAL_SYNCHRONIZED",
213                 "LITERAL_WHILE", "STATIC_INIT").collect(Collectors.toSet()));
214         GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("WhitespaceAround", Stream.of(
215                 //  allowed via '4.8.3 Arrays'
216                 "ARRAY_INIT",
217                 //  '...' is almost same as '[]' by meaning
218                 "ELLIPSIS",
219                 // google prefers no spaces on one side or both for these tokens
220                 "GENERIC_START", "GENERIC_END", "WILDCARD_TYPE")
221                 .collect(Collectors.toSet()));
222         GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("IllegalTokenText", Stream.of(
223                 // all other java tokens and text are allowed
224                 "NUM_DOUBLE", "NUM_FLOAT", "NUM_INT", "NUM_LONG", "IDENT",
225                 "COMMENT_CONTENT", "STRING_LITERAL", "CHAR_LITERAL")
226                 .collect(Collectors.toSet()));
227         GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("OperatorWrap", Stream.of(
228                 // specifically allowed via '4.5.1 Where to break' because the following are
229                 // assignment operators and they are allowed to break before or after the symbol
230                 "DIV_ASSIGN", "BOR_ASSIGN", "SL_ASSIGN", "ASSIGN", "BSR_ASSIGN", "BAND_ASSIGN",
231                 "PLUS_ASSIGN", "MINUS_ASSIGN", "SR_ASSIGN", "STAR_ASSIGN", "BXOR_ASSIGN",
232                 "MOD_ASSIGN",
233                 // state of the configuration when test was made until
234                 // https://github.com/checkstyle/checkstyle/issues/4122
235                 "COLON", "TYPE_EXTENSION_AND").collect(Collectors.toSet()));
236         GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("NoWhitespaceBefore", Stream.of(
237                 // google uses GenericWhitespace for this behavior
238                 "GENERIC_START", "GENERIC_END").collect(Collectors.toSet()));
239     }
240 
241     @Override
242     protected String getPackageLocation() {
243         return "com/puppycrawl/tools/checkstyle/internal/allchecks";
244     }
245 
246     @Test
247     public void testAllModulesWithDefaultConfiguration() throws Exception {
248         final String inputFilePath = getPath("InputAllChecksDefaultConfig.java");
249         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
250 
251         for (Class<?> module : CheckUtil.getCheckstyleModules()) {
252             if (ModuleReflectionUtil.isRootModule(module)) {
253                 continue;
254             }
255 
256             final DefaultConfiguration moduleConfig = createModuleConfig(module);
257             final Checker checker;
258             if (module.equals(ImportControlCheck.class)) {
259                 // ImportControlCheck must have the import control configuration file to avoid
260                 // violation.
261                 moduleConfig.addAttribute("file", getPath(
262                         "InputAllChecksImportControl.xml"));
263             }
264             checker = createChecker(moduleConfig);
265             verify(checker, inputFilePath, expected);
266         }
267     }
268 
269     @Test
270     public void testDefaultTokensAreSubsetOfAcceptableTokens() throws Exception {
271         for (Class<?> check : CheckUtil.getCheckstyleChecks()) {
272             if (AbstractCheck.class.isAssignableFrom(check)) {
273                 final AbstractCheck testedCheck = (AbstractCheck) check.getDeclaredConstructor()
274                         .newInstance();
275                 final int[] defaultTokens = testedCheck.getDefaultTokens();
276                 final int[] acceptableTokens = testedCheck.getAcceptableTokens();
277 
278                 if (!isSubset(defaultTokens, acceptableTokens)) {
279                     final String errorMessage = String.format(Locale.ROOT,
280                             "%s's default tokens must be a subset"
281                             + " of acceptable tokens.", check.getName());
282                     Assert.fail(errorMessage);
283                 }
284             }
285         }
286     }
287 
288     @Test
289     public void testRequiredTokensAreSubsetOfAcceptableTokens() throws Exception {
290         for (Class<?> check : CheckUtil.getCheckstyleChecks()) {
291             if (AbstractCheck.class.isAssignableFrom(check)) {
292                 final AbstractCheck testedCheck = (AbstractCheck) check.getDeclaredConstructor()
293                         .newInstance();
294                 final int[] requiredTokens = testedCheck.getRequiredTokens();
295                 final int[] acceptableTokens = testedCheck.getAcceptableTokens();
296 
297                 if (!isSubset(requiredTokens, acceptableTokens)) {
298                     final String errorMessage = String.format(Locale.ROOT,
299                             "%s's required tokens must be a subset"
300                             + " of acceptable tokens.", check.getName());
301                     Assert.fail(errorMessage);
302                 }
303             }
304         }
305     }
306 
307     @Test
308     public void testRequiredTokensAreSubsetOfDefaultTokens() throws Exception {
309         for (Class<?> check : CheckUtil.getCheckstyleChecks()) {
310             if (AbstractCheck.class.isAssignableFrom(check)) {
311                 final AbstractCheck testedCheck = (AbstractCheck) check.getDeclaredConstructor()
312                         .newInstance();
313                 final int[] defaultTokens = testedCheck.getDefaultTokens();
314                 final int[] requiredTokens = testedCheck.getRequiredTokens();
315 
316                 if (!isSubset(requiredTokens, defaultTokens)) {
317                     final String errorMessage = String.format(Locale.ROOT,
318                             "%s's required tokens must be a subset"
319                             + " of default tokens.", check.getName());
320                     Assert.fail(errorMessage);
321                 }
322             }
323         }
324     }
325 
326     @Test
327     public void testAllModulesHaveMultiThreadAnnotation() throws Exception {
328         for (Class<?> module : CheckUtil.getCheckstyleModules()) {
329             if (ModuleReflectionUtil.isRootModule(module)
330                     || ModuleReflectionUtil.isFilterModule(module)
331                     || ModuleReflectionUtil.isFileFilterModule(module)
332                     || ModuleReflectionUtil.isTreeWalkerFilterModule(module)) {
333                 continue;
334             }
335 
336             Assert.assertTrue(
337                     "module '" + module.getSimpleName()
338                             + "' must contain a multi-thread annotation",
339                     module.isAnnotationPresent(GlobalStatefulCheck.class)
340                             || module.isAnnotationPresent(FileStatefulCheck.class)
341                             || module.isAnnotationPresent(StatelessCheck.class));
342         }
343     }
344 
345     @Test
346     public void testAllModulesAreReferencedInConfigFile() throws Exception {
347         final Set<String> modulesReferencedInConfig = CheckUtil.getConfigCheckStyleModules();
348         final Set<String> moduleNames = CheckUtil.getSimpleNames(CheckUtil.getCheckstyleModules());
349 
350         moduleNames.stream().filter(check -> !modulesReferencedInConfig.contains(check))
351             .forEach(check -> {
352                 final String errorMessage = String.format(Locale.ROOT,
353                     "%s is not referenced in checkstyle_checks.xml", check);
354                 Assert.fail(errorMessage);
355             });
356     }
357 
358     @Test
359     public void testAllCheckTokensAreReferencedInCheckstyleConfigFile() throws Exception {
360         final Configuration configuration = ConfigurationUtil
361                 .loadConfiguration("config/checkstyle_checks.xml");
362 
363         validateAllCheckTokensAreReferencedInConfigFile("checkstyle", configuration,
364                 CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE);
365     }
366 
367     @Test
368     public void testAllCheckTokensAreReferencedInGoogleConfigFile() throws Exception {
369         final Configuration configuration = ConfigurationUtil
370                 .loadConfiguration("src/main/resources/google_checks.xml");
371 
372         validateAllCheckTokensAreReferencedInConfigFile("google", configuration,
373                 GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE);
374     }
375 
376     private static void validateAllCheckTokensAreReferencedInConfigFile(String configName,
377             Configuration configuration, Map<String, Set<String>> tokensToIgnore) throws Exception {
378         final ModuleFactory moduleFactory = TestUtil.getPackageObjectFactory();
379         final Set<Configuration> configChecks = ConfigurationUtil.getChecks(configuration);
380 
381         final Map<String, Set<String>> configCheckTokens = new HashMap<>();
382         final Map<String, Set<String>> checkTokens = new HashMap<>();
383 
384         for (Configuration checkConfig : configChecks) {
385             final String checkName = checkConfig.getName();
386             final Object instance;
387 
388             try {
389                 instance = moduleFactory.createModule(checkName);
390             }
391             catch (CheckstyleException ex) {
392                 throw new CheckstyleException("Couldn't find check: " + checkName, ex);
393             }
394 
395             if (instance instanceof AbstractCheck) {
396                 final AbstractCheck check = (AbstractCheck) instance;
397 
398                 Set<String> configTokens = configCheckTokens.get(checkName);
399 
400                 if (configTokens == null) {
401                     configTokens = new HashSet<>();
402 
403                     configCheckTokens.put(checkName, configTokens);
404 
405                     // add all overridden tokens
406                     final Set<String> overrideTokens = tokensToIgnore.get(checkName);
407 
408                     if (overrideTokens != null) {
409                         configTokens.addAll(overrideTokens);
410                     }
411 
412                     configTokens.addAll(CheckUtil.getTokenNameSet(check.getRequiredTokens()));
413                     checkTokens.put(checkName,
414                             CheckUtil.getTokenNameSet(check.getAcceptableTokens()));
415                 }
416 
417                 try {
418                     configTokens.addAll(Arrays.asList(checkConfig.getAttribute("tokens").trim()
419                             .split(",\\s*")));
420                 }
421                 catch (CheckstyleException ex) {
422                     // no tokens defined, so it is using default
423                     configTokens.addAll(CheckUtil.getTokenNameSet(check.getDefaultTokens()));
424                 }
425             }
426         }
427 
428         for (Entry<String, Set<String>> entry : checkTokens.entrySet()) {
429             Assert.assertEquals("'" + entry.getKey()
430                     + "' should have all acceptable tokens from check in " + configName
431                     + " config or specify an override to ignore the specific tokens",
432                     entry.getValue(), configCheckTokens.get(entry.getKey()));
433         }
434     }
435 
436     @Test
437     public void testAllCheckstyleModulesHaveXdocDocumentation() throws Exception {
438         final Set<String> checkstyleModulesNames = CheckUtil.getSimpleNames(CheckUtil
439                 .getCheckstyleModules());
440         final Set<String> modulesNamesWhichHaveXdocs = XdocUtil.getModulesNamesWhichHaveXdoc();
441 
442         // these are documented on non-'config_' pages
443         checkstyleModulesNames.remove("TreeWalker");
444         checkstyleModulesNames.remove("Checker");
445 
446         checkstyleModulesNames.stream()
447             .filter(moduleName -> !modulesNamesWhichHaveXdocs.contains(moduleName))
448             .forEach(moduleName -> {
449                 final String missingModuleMessage = String.format(Locale.ROOT,
450                     "Module %s does not have xdoc documentation.",
451                     moduleName);
452                 Assert.fail(missingModuleMessage);
453             });
454     }
455 
456     @Test
457     public void testAllCheckstyleModulesInCheckstyleConfig() throws Exception {
458         final Set<String> configChecks = CheckUtil.getConfigCheckStyleModules();
459         final Set<String> moduleNames = CheckUtil.getSimpleNames(CheckUtil.getCheckstyleModules());
460 
461         for (String moduleName : moduleNames) {
462             Assert.assertTrue("checkstyle_checks.xml is missing module: " + moduleName,
463                     configChecks.contains(moduleName));
464         }
465     }
466 
467     @Test
468     public void testAllCheckstyleChecksHaveMessage() throws Exception {
469         for (Class<?> module : CheckUtil.getCheckstyleChecks()) {
470             final String name = module.getSimpleName();
471 
472             Assert.assertFalse(name
473                     + " should have at least one 'MSG_*' field for error messages", CheckUtil
474                     .getCheckMessages(module).isEmpty());
475         }
476     }
477 
478     @Test
479     public void testAllCheckstyleMessages() throws Exception {
480         final Map<String, List<String>> usedMessages = new TreeMap<>();
481 
482         // test validity of messages from modules
483         for (Class<?> module : CheckUtil.getCheckstyleModules()) {
484             for (Field message : CheckUtil.getCheckMessages(module)) {
485                 Assert.assertEquals(module.getSimpleName() + "." + message.getName()
486                         + " should be 'public static final'", Modifier.PUBLIC | Modifier.STATIC
487                         | Modifier.FINAL, message.getModifiers());
488 
489                 // below is required for package/private classes
490                 if (!message.isAccessible()) {
491                     message.setAccessible(true);
492                 }
493 
494                 verifyCheckstyleMessage(usedMessages, module, message);
495             }
496         }
497 
498         // test properties for messages not used by checks
499         for (Entry<String, List<String>> entry : usedMessages.entrySet()) {
500             final Properties pr = new Properties();
501             pr.load(AllChecksTest.class.getResourceAsStream(
502                     "/" + entry.getKey().replace('.', '/') + "/messages.properties"));
503 
504             for (Object key : pr.keySet()) {
505                 // hidden exception messages
506                 if ("translation.wrongLanguageCode".equals(key)) {
507                     continue;
508                 }
509 
510                 Assert.assertTrue("property '" + key + "' isn't used by any check in package '"
511                         + entry.getKey() + "'", entry.getValue().contains(key.toString()));
512             }
513         }
514     }
515 
516     private static void verifyCheckstyleMessage(Map<String, List<String>> usedMessages,
517             Class<?> module, Field message) throws Exception {
518         final String messageString = message.get(null).toString();
519         final String packageName = module.getPackage().getName();
520         List<String> packageMessages = usedMessages.get(packageName);
521 
522         if (packageMessages == null) {
523             packageMessages = new ArrayList<>();
524             usedMessages.put(packageName, packageMessages);
525         }
526 
527         packageMessages.add(messageString);
528 
529         for (Locale locale : ALL_LOCALES) {
530             String result = null;
531 
532             try {
533                 result = CheckUtil.getCheckMessage(module, locale, messageString);
534             }
535             catch (IllegalArgumentException ex) {
536                 Assert.fail(module.getSimpleName() + " with the message '" + messageString
537                         + "' in locale '" + locale.getLanguage() + "' failed with: "
538                         + ex.getClass().getSimpleName() + " - " + ex.getMessage());
539             }
540 
541             Assert.assertNotNull(
542                     module.getSimpleName() + " should have text for the message '"
543                             + messageString + "' in locale " + locale.getLanguage() + "'",
544                     result);
545             Assert.assertFalse(
546                     module.getSimpleName() + " should have non-empty text for the message '"
547                             + messageString + "' in locale '" + locale.getLanguage() + "'",
548                     result.trim().isEmpty());
549             Assert.assertFalse(
550                     module.getSimpleName() + " should have non-TODO text for the message '"
551                             + messageString + "' in locale " + locale.getLanguage() + "'",
552                     !"todo.match".equals(messageString)
553                             && result.trim().startsWith("TODO"));
554         }
555     }
556 
557     /**
558      * Checks that an array is a subset of other array.
559      * @param array to check whether it is a subset.
560      * @param arrayToCheckIn array to check in.
561      * @return {@code true} if all elements in {@code array} are in {@code arrayToCheckIn}.
562      */
563     private static boolean isSubset(int[] array, int... arrayToCheckIn) {
564         Arrays.sort(arrayToCheckIn);
565         boolean result = true;
566         for (final int element : array) {
567             if (Arrays.binarySearch(arrayToCheckIn, element) < 0) {
568                 result = false;
569                 break;
570             }
571         }
572         return result;
573     }
574 
575 }