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