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