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.io.File;
25  import java.lang.reflect.Field;
26  import java.lang.reflect.Modifier;
27  import java.util.ArrayList;
28  import java.util.Arrays;
29  import java.util.BitSet;
30  import java.util.HashMap;
31  import java.util.HashSet;
32  import java.util.List;
33  import java.util.Locale;
34  import java.util.Map;
35  import java.util.Map.Entry;
36  import java.util.Properties;
37  import java.util.Set;
38  import java.util.TreeMap;
39  import java.util.regex.Pattern;
40  import java.util.stream.Collectors;
41  import java.util.stream.Stream;
42  
43  import org.junit.jupiter.api.Test;
44  
45  import com.google.common.base.Splitter;
46  import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
47  import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
48  import com.puppycrawl.tools.checkstyle.Definitions;
49  import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
50  import com.puppycrawl.tools.checkstyle.GlobalStatefulCheck;
51  import com.puppycrawl.tools.checkstyle.JavaParser;
52  import com.puppycrawl.tools.checkstyle.ModuleFactory;
53  import com.puppycrawl.tools.checkstyle.StatelessCheck;
54  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
55  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
56  import com.puppycrawl.tools.checkstyle.api.Configuration;
57  import com.puppycrawl.tools.checkstyle.api.DetailAST;
58  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
59  import com.puppycrawl.tools.checkstyle.checks.imports.ImportControlCheck;
60  import com.puppycrawl.tools.checkstyle.internal.utils.CheckUtil;
61  import com.puppycrawl.tools.checkstyle.internal.utils.ConfigurationUtil;
62  import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
63  import com.puppycrawl.tools.checkstyle.internal.utils.XdocUtil;
64  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
65  import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
66  import com.puppycrawl.tools.checkstyle.utils.ModuleReflectionUtil;
67  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
68  
69  public class AllChecksTest extends AbstractModuleTestSupport {
70  
71      private static final Locale[] ALL_LOCALES = {
72          Locale.CHINESE,
73          Locale.ENGLISH,
74          Locale.of("es"),
75          Locale.of("fi"),
76          Locale.FRENCH,
77          Locale.GERMAN,
78          Locale.JAPANESE,
79          Locale.of("pt"),
80          Locale.of("ru"),
81          Locale.of("tr"),
82      };
83  
84      private static final Pattern SETTER_PATTERN = Pattern.compile("^set[A-Z].*");
85  
86      private static final Map<String, Set<String>> CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE =
87              new HashMap<>();
88      private static final Map<String, Set<String>> GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE =
89              new HashMap<>();
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         // only CLASS_DEF: require @since on public *Check types (see checkstyle-checks.xml)
154         CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("WriteTag",
155                 Stream.of("INTERFACE_DEF", "ENUM_DEF", "ANNOTATION_DEF", "METHOD_DEF",
156                     "CTOR_DEF", "ENUM_CONSTANT_DEF", "ANNOTATION_FIELD_DEF", "RECORD_DEF",
157                     "COMPACT_CTOR_DEF")
158                     .collect(Collectors.toUnmodifiableSet()));
159         CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("NoLineWrap", Stream.of(
160                 // method/constructor declaration could be long due to "parameters/exceptions", it
161                 // is ok to be not strict there
162                 "METHOD_DEF", "CTOR_DEF", "COMPACT_CTOR_DEF",
163                 // type declaration could be long due to "extends/implements", it is ok to
164                 // be not strict there
165                 "CLASS_DEF", "ENUM_DEF", "INTERFACE_DEF", "RECORD_DEF")
166                 .collect(Collectors.toUnmodifiableSet()));
167         CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("NoWhitespaceAfter", Stream.of(
168                 // whitespace after is preferred
169                 "TYPECAST", "LITERAL_SYNCHRONIZED").collect(Collectors.toUnmodifiableSet()));
170         CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("SeparatorWrap", Stream.of(
171                 // needs context to decide what type of parentheses should be separated or not
172                 // which this check does not provide
173                 "LPAREN", "RPAREN").collect(Collectors.toUnmodifiableSet()));
174         CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("NeedBraces", Stream.of(
175                 // we prefer no braces here as it looks unusual even though they help avoid sharing
176                 // scope of variables
177                 "LITERAL_DEFAULT", "LITERAL_CASE").collect(Collectors.toUnmodifiableSet()));
178         CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("FinalParameters", Stream.of(
179                 // we prefer these to be effectively final as to not damage readability
180                 "FOR_EACH_CLAUSE", "LITERAL_CATCH", "PATTERN_VARIABLE_DEF")
181             .collect(Collectors.toUnmodifiableSet()));
182         CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("WhitespaceAround", Stream.of(
183                 // we prefer no spaces on one side or both for these tokens
184                 "ARRAY_INIT",
185                 "ELLIPSIS",
186                 // these are covered by GenericWhitespaceCheck
187                 "WILDCARD_TYPE", "GENERIC_END", "GENERIC_START")
188             .collect(Collectors.toUnmodifiableSet()));
189 
190         // google
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                 // module import declarations are not part of the Google style config token set
200                 "MODULE_IMPORT",
201                 // module declarations are not part of the Google style config token set
202                 "MODULE_DEF")
203                 .collect(Collectors.toUnmodifiableSet()));
204         GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("SeparatorWrap", Stream.of(
205                 // location could be any to allow writing expressions for indexes evaluation
206                 // on new line, see https://github.com/checkstyle/checkstyle/issues/3752
207                 "RBRACK",
208                 // for some targets annotations can be used without wrapping, as described
209                 // in https://google.github.io/styleguide/javaguide.html#s4.8.5-annotations
210                 "AT",
211                 // location could be any to allow using for line separation in enum values,
212                 // see https://github.com/checkstyle/checkstyle/issues/3752
213                 "SEMI",
214                 // needs context to decide what type of parentheses should be separated or not
215                 // which this check does not provide
216                 "LPAREN", "RPAREN").collect(Collectors.toUnmodifiableSet()));
217         GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("NeedBraces", Stream.of(
218                 // google doesn't require or prevent braces on these
219                 "LAMBDA", "LITERAL_DEFAULT", "LITERAL_CASE")
220             .collect(Collectors.toUnmodifiableSet()));
221         GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("EmptyBlock", Stream.of(
222                 // google doesn't specifically mention empty braces at the start of a case/default
223                 "LITERAL_DEFAULT", "LITERAL_CASE",
224                 // can be empty for special cases via '6.2 Caught exceptions: not ignored'
225                 "LITERAL_CATCH",
226                 // specifically allowed via '5.2.4 Constant names'
227                 "ARRAY_INIT",
228                 // state of the configuration when test was made until
229                 // https://github.com/checkstyle/checkstyle/issues/4121
230                 "INSTANCE_INIT", "LITERAL_DO", "LITERAL_FOR", "LITERAL_SYNCHRONIZED",
231                 "LITERAL_WHILE", "STATIC_INIT").collect(Collectors.toUnmodifiableSet()));
232         GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("EmptyLineSeparator", Stream.of(
233                 // module import declarations are not part of the Google style config token set
234                 "MODULE_IMPORT").collect(Collectors.toUnmodifiableSet()));
235         GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("WhitespaceAround", Stream.of(
236                 //  allowed via '4.8.3 Arrays'
237                 "ARRAY_INIT",
238                 //  '...' is almost same as '[]' by meaning
239                 "ELLIPSIS",
240                 // google prefers no spaces on one side or both for these tokens
241                 "GENERIC_START", "GENERIC_END", "WILDCARD_TYPE")
242                 .collect(Collectors.toUnmodifiableSet()));
243         GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("WhitespaceAfter", Stream.of(
244                 // these tokens are already validated by WhitespaceAround, having them
245                 // in both checks causes duplicate violations
246                 "LITERAL_IF", "LITERAL_ELSE", "LITERAL_RETURN", "LITERAL_WHILE",
247                 "LITERAL_DO", "LITERAL_FOR", "LITERAL_FINALLY", "DO_WHILE",
248                 "LITERAL_SWITCH", "LITERAL_SYNCHRONIZED", "LITERAL_TRY", "LITERAL_CATCH",
249                 "LAMBDA", "LITERAL_WHEN")
250                 .collect(Collectors.toUnmodifiableSet()));
251         GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("WhitespaceBeforeEmptyBody", Stream.of(
252                 // these tokens are already validated by WhitespaceAround, having them
253                 // in both checks causes duplicate violations
254                 "CTOR_DEF", "COMPACT_CTOR_DEF", "LITERAL_DO", "LITERAL_IF", "LITERAL_ELSE",
255                 "LITERAL_TRY", "LITERAL_CATCH", "LITERAL_FINALLY", "LITERAL_SYNCHRONIZED",
256                 "LITERAL_SWITCH", "LAMBDA")
257                 .collect(Collectors.toUnmodifiableSet()));
258         GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("IllegalTokenText", Stream.of(
259                 // numerical types should not be included
260                 "NUM_DOUBLE", "NUM_FLOAT", "NUM_INT", "NUM_LONG",
261                 // identifiers are covered by other checks
262                 "IDENT",
263                 // comments should be skipped as nobody write in octal or unicode code style
264                 "COMMENT_CONTENT"
265                 )
266                 .collect(Collectors.toUnmodifiableSet()));
267         GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("OperatorWrap", Stream.of(
268                 // specifically allowed via '4.5.1 Where to break' because the following are
269                 // assignment operators and they are allowed to break before or after the symbol
270                 "DIV_ASSIGN", "BOR_ASSIGN", "SL_ASSIGN", "ASSIGN", "BSR_ASSIGN", "BAND_ASSIGN",
271                 "PLUS_ASSIGN", "MINUS_ASSIGN", "SR_ASSIGN", "STAR_ASSIGN", "BXOR_ASSIGN",
272                 "MOD_ASSIGN",
273                 // COLON token ignored in check config, explained in
274                 // https://github.com/checkstyle/checkstyle/issues/4122
275                 "COLON").collect(Collectors.toUnmodifiableSet()));
276         GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("NoWhitespaceBefore", Stream.of(
277                 // google uses GenericWhitespace for this behavior
278                 "GENERIC_START", "GENERIC_END",
279                 // whitespace is necessary between a type annotation and ellipsis
280                 // according '4.6.2 Horizontal whitespace point 9'
281                 "ELLIPSIS").collect(Collectors.toUnmodifiableSet()));
282         INTERNAL_MODULES = Definitions.INTERNAL_MODULES.stream()
283                 .map(moduleName -> {
284                     final List<String> packageTokens = Splitter
285                             .on(".").splitToList(moduleName);
286                     return packageTokens.getLast();
287                 })
288                 .collect(Collectors.toUnmodifiableSet());
289     }
290 
291     @Override
292     public String getPackageLocation() {
293         return "com/puppycrawl/tools/checkstyle/internal/allchecks";
294     }
295 
296     @Test
297     public void testAllModulesWithDefaultConfiguration() throws Exception {
298         final String inputFilePath = getPath("InputAllChecksDefaultConfig.java");
299         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
300 
301         for (Class<?> module : CheckUtil.getCheckstyleModules()) {
302             if (ModuleReflectionUtil.isRootModule(module)) {
303                 continue;
304             }
305 
306             final DefaultConfiguration moduleConfig = createModuleConfig(module);
307             if (module.equals(ImportControlCheck.class)) {
308                 // ImportControlCheck must have the import control configuration file to avoid
309                 // violation
310                 moduleConfig.addProperty("file", getPath(
311                         "InputAllChecksImportControl.xml"));
312             }
313             verify(moduleConfig, inputFilePath, expected);
314         }
315     }
316 
317     @Test
318     public void testDefaultTokensAreSubsetOfAcceptableTokens() throws Exception {
319         for (Class<?> check : CheckUtil.getCheckstyleChecks()) {
320             if (AbstractCheck.class.isAssignableFrom(check)) {
321                 final AbstractCheck testedCheck = (AbstractCheck) TestUtil.instantiate(check);
322                 final int[] defaultTokens = testedCheck.getDefaultTokens();
323                 final int[] acceptableTokens = testedCheck.getAcceptableTokens();
324 
325                 assertWithMessage("%s's default tokens must be a subset of acceptable tokens.",
326                             check.getName())
327                         .that(isSubset(defaultTokens, acceptableTokens))
328                         .isTrue();
329             }
330         }
331     }
332 
333     @Test
334     public void testRequiredTokensAreSubsetOfAcceptableTokens() throws Exception {
335         for (Class<?> check : CheckUtil.getCheckstyleChecks()) {
336             if (AbstractCheck.class.isAssignableFrom(check)) {
337                 final AbstractCheck testedCheck = (AbstractCheck) TestUtil.instantiate(check);
338                 final int[] requiredTokens = testedCheck.getRequiredTokens();
339                 final int[] acceptableTokens = testedCheck.getAcceptableTokens();
340 
341                 assertWithMessage("%s's required tokens must be a subset of acceptable tokens.",
342                             check.getName())
343                         .that(isSubset(requiredTokens, acceptableTokens))
344                         .isTrue();
345             }
346         }
347     }
348 
349     @Test
350     public void testRequiredTokensAreSubsetOfDefaultTokens() throws Exception {
351         for (Class<?> check : CheckUtil.getCheckstyleChecks()) {
352             if (AbstractCheck.class.isAssignableFrom(check)) {
353                 final AbstractCheck testedCheck = (AbstractCheck) TestUtil.instantiate(check);
354                 final int[] defaultTokens = testedCheck.getDefaultTokens();
355                 final int[] requiredTokens = testedCheck.getRequiredTokens();
356 
357                 assertWithMessage("%s's required tokens must be a subset of default tokens.",
358                             check.getName())
359                         .that(isSubset(requiredTokens, defaultTokens))
360                         .isTrue();
361             }
362         }
363     }
364 
365     @Test
366     public void testAllModulesHaveMultiThreadAnnotation() throws Exception {
367         for (Class<?> module : CheckUtil.getCheckstyleModules()) {
368             if (ModuleReflectionUtil.isRootModule(module)
369                     || ModuleReflectionUtil.isFilterModule(module)
370                     || ModuleReflectionUtil.isFileFilterModule(module)
371                     || ModuleReflectionUtil.isTreeWalkerFilterModule(module)) {
372                 continue;
373             }
374 
375             assertWithMessage("module '%s' must contain a multi-thread annotation",
376                 module.getSimpleName())
377                             .that(module.isAnnotationPresent(GlobalStatefulCheck.class)
378                                     || module.isAnnotationPresent(FileStatefulCheck.class)
379                                     || module.isAnnotationPresent(StatelessCheck.class))
380                             .isTrue();
381         }
382     }
383 
384     @Test
385     public void testAllModulesAreReferencedInConfigFile() throws Exception {
386         final Set<String> modulesReferencedInConfig = CheckUtil.getConfigCheckStyleModules();
387         final Set<String> moduleNames = CheckUtil.getSimpleNames(CheckUtil.getCheckstyleModules());
388 
389         moduleNames.removeAll(INTERNAL_MODULES);
390         moduleNames.stream().filter(check -> !modulesReferencedInConfig.contains(check))
391             .forEach(check -> {
392                 final String errorMessage = String.format(Locale.ROOT,
393                     "%s is not referenced in checkstyle-checks.xml", check);
394                 assertWithMessage(errorMessage).fail();
395             });
396     }
397 
398     @Test
399     public void testAllCheckTokensAreReferencedInCheckstyleConfigFile() throws Exception {
400         final Configuration configuration = ConfigurationUtil
401                 .loadConfiguration("config/checkstyle-checks.xml");
402 
403         validateAllCheckTokensAreReferencedInConfigFile("checkstyle", configuration,
404                 CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE, false);
405     }
406 
407     @Test
408     public void testAllCheckTokensAreReferencedInGoogleConfigFile() throws Exception {
409         final Configuration configuration = ConfigurationUtil
410                 .loadConfiguration("src/main/resources/google_checks.xml");
411 
412         validateAllCheckTokensAreReferencedInConfigFile("google", configuration,
413                 GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE, true);
414     }
415 
416     private static void validateAllCheckTokensAreReferencedInConfigFile(String configName,
417             Configuration configuration, Map<String, Set<String>> tokensToIgnore,
418             boolean defaultTokensMustBeExplicit) throws Exception {
419         final ModuleFactory moduleFactory = TestUtil.getPackageObjectFactory();
420         final Set<Configuration> configChecks = ConfigurationUtil.getChecks(configuration);
421 
422         final Map<String, Set<String>> configCheckTokens = new HashMap<>();
423         final Map<String, Set<String>> checkTokens = new HashMap<>();
424 
425         for (Configuration checkConfig : configChecks) {
426             final String checkName = checkConfig.getName();
427             final Object instance;
428 
429             try {
430                 instance = moduleFactory.createModule(checkName);
431             }
432             catch (CheckstyleException exc) {
433                 throw new CheckstyleException("Couldn't find check: " + checkName, exc);
434             }
435             final AbstractCheck check;
436             if (instance instanceof AbstractCheck abstractCheck
437                     && !isAllTokensAcceptable(abstractCheck)) {
438                 check = abstractCheck;
439             }
440             else {
441                 // we can not have in our config test for all tokens
442                 continue;
443             }
444 
445             Set<String> configTokens = configCheckTokens.get(checkName);
446 
447             if (configTokens == null) {
448                 configTokens = new HashSet<>();
449 
450                 configCheckTokens.put(checkName, configTokens);
451 
452                 // add all overridden tokens
453                 final Set<String> overrideTokens = tokensToIgnore.get(checkName);
454 
455                 if (overrideTokens != null) {
456                     configTokens.addAll(overrideTokens);
457                 }
458 
459                 configTokens.addAll(CheckUtil.getTokenNameSet(check.getRequiredTokens()));
460                 checkTokens.put(checkName,
461                         CheckUtil.getTokenNameSet(check.getAcceptableTokens()));
462             }
463 
464             try {
465                 configTokens.addAll(Arrays.asList(checkConfig.getProperty("tokens").trim()
466                         .split(",\\s*")));
467             }
468             catch (CheckstyleException exc) {
469                 // no tokens defined, so it is using default
470                 if (defaultTokensMustBeExplicit) {
471                     validateDefaultTokens(checkConfig, check, configTokens);
472                 }
473                 else {
474                     configTokens.addAll(CheckUtil.getTokenNameSet(check.getDefaultTokens()));
475                 }
476             }
477         }
478         for (Entry<String, Set<String>> entry : checkTokens.entrySet()) {
479             final Set<String> actual = configCheckTokens.get(entry.getKey());
480             assertWithMessage(
481                 "'%s' should have all acceptable tokens "
482                     + "from check in %s config "
483                     + "or specify an override to ignore the specific tokens",
484                 entry.getKey(), configName)
485                 .that(actual)
486                 .isEqualTo(entry.getValue());
487         }
488     }
489 
490     private static boolean isAllTokensAcceptable(AbstractCheck check) {
491         return Arrays.equals(check.getAcceptableTokens(), TokenUtil.getAllTokenIds());
492     }
493 
494     private static void validateDefaultTokens(Configuration checkConfig, AbstractCheck check,
495                                               Set<String> configTokens) {
496 
497         final BitSet defaultTokensSet = TokenUtil.asBitSet(check.getDefaultTokens());
498         final BitSet requiredTokensSet = TokenUtil.asBitSet(check.getRequiredTokens());
499 
500         if (defaultTokensSet.equals(requiredTokensSet)) {
501             configTokens.addAll(
502                     CheckUtil.getTokenNameSet(check.getDefaultTokens()));
503         }
504         else {
505             assertWithMessage("All default tokens should be used in config for %s",
506                 checkConfig.getName()).fail();
507         }
508     }
509 
510     @Test
511     public void testAllCheckstyleModulesHaveXdocDocumentation() throws Exception {
512         final Set<String> checkstyleModulesNames = CheckUtil.getSimpleNames(CheckUtil
513                 .getCheckstyleModules());
514         final Set<String> modulesNamesWhichHaveXdocs = XdocUtil.getModulesNamesWhichHaveXdoc();
515 
516         // these are documented on non-'config_' pages
517         checkstyleModulesNames.remove("TreeWalker");
518         checkstyleModulesNames.remove("Checker");
519         // temporarily hosted in test folder
520         checkstyleModulesNames.removeAll(INTERNAL_MODULES);
521         checkstyleModulesNames.stream()
522             .filter(moduleName -> !modulesNamesWhichHaveXdocs.contains(moduleName))
523             .forEach(moduleName -> {
524                 final String missingModuleMessage = String.format(Locale.ROOT,
525                     "Module %s does not have xdoc documentation.",
526                     moduleName);
527                 assertWithMessage(missingModuleMessage).fail();
528             });
529     }
530 
531     @Test
532     public void testAllCheckstyleModulesInCheckstyleConfig() throws Exception {
533         final Set<String> configChecks = CheckUtil.getConfigCheckStyleModules();
534         final Set<String> moduleNames = CheckUtil.getSimpleNames(CheckUtil.getCheckstyleModules());
535         moduleNames.removeAll(INTERNAL_MODULES);
536         for (String moduleName : moduleNames) {
537             assertWithMessage("checkstyle-checks.xml is missing module: %s", moduleName)
538                     .that(configChecks)
539                     .contains(moduleName);
540         }
541     }
542 
543     @Test
544     public void testAllCheckstyleChecksHaveMessage() throws Exception {
545         for (Class<?> module : CheckUtil.getCheckstyleChecks()) {
546             final String name = module.getSimpleName();
547             final Set<Field> messages = CheckUtil.getCheckMessagesWithoutDeepScan(module);
548 
549             // No messages in just module
550             if ("SuppressWarningsHolder".equals(name)) {
551                 assertWithMessage("%s should not have any 'MSG_*' fields for error messages", name)
552                         .that(messages)
553                         .isEmpty();
554             }
555             else {
556                 assertWithMessage(
557                         "%s should have at least one 'MSG_*' field for error messages", name)
558                                 .that(messages)
559                                 .isNotEmpty();
560             }
561         }
562     }
563 
564     @Test
565     public void testAllCheckstyleMessages() throws Exception {
566         final Map<String, List<String>> usedMessages = new TreeMap<>();
567 
568         // test validity of messages from modules
569         for (Class<?> module : CheckUtil.getCheckstyleModules()) {
570             for (Field message : CheckUtil.getCheckMessagesWithDeepScan(module)) {
571                 assertWithMessage("%s.%s should be 'public static final'", module.getSimpleName(),
572                     message.getName())
573                     .that(message.getModifiers())
574                     .isEqualTo(Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL);
575 
576                 // below is required for package/private classes
577                 message.trySetAccessible();
578 
579                 if (!INTERNAL_MODULES.contains(module.getSimpleName())) {
580                     verifyCheckstyleMessage(usedMessages, module, message);
581                 }
582             }
583         }
584 
585         // test properties for messages not used by checks
586         for (Entry<String, List<String>> entry : usedMessages.entrySet()) {
587             final Properties pr = new Properties();
588             pr.load(AllChecksTest.class.getResourceAsStream(
589                     "/" + entry.getKey().replace('.', '/') + "/messages.properties"));
590 
591             for (Object key : pr.keySet()) {
592                 // hidden exception messages
593                 if ("translation.wrongLanguageCode".equals(key)) {
594                     continue;
595                 }
596 
597                 assertWithMessage("property '%s' isn't used by any check in package '%s'", key,
598                     entry.getKey())
599                         .that(entry.getValue())
600                         .contains(key.toString());
601             }
602         }
603     }
604 
605     @Test
606     public void testAllChecksOrFiltersSettersHaveSinceTag() throws Exception {
607 
608         for (Class<?> module : CheckUtil.getCheckstyleModules()) {
609 
610             if (Modifier.isAbstract(module.getModifiers())) {
611                 continue;
612             }
613 
614             final String className = module.getSimpleName();
615             if (className.endsWith("Check") || className.endsWith("Filter")) {
616 
617                 final File file = new File("src/main/java/"
618                         + module.getName().replace('.', '/') + ".java");
619                 final DetailAST ast = JavaParser.parseFile(file, JavaParser.Options.WITH_COMMENTS);
620                 final DetailAST classDef = ast.findFirstToken(TokenTypes.CLASS_DEF);
621                 final DetailAST objectBlock = classDef.findFirstToken(TokenTypes.OBJBLOCK);
622                 DetailAST node = objectBlock.getFirstChild();
623 
624                 while (node != null) {
625                     if (node.getType() == TokenTypes.METHOD_DEF && isSetter(node)) {
626                         final DetailAST javadocBlockBegin = getPrecedingJavadocComment(node);
627                         if (javadocBlockBegin != null) {
628                             final String javadocContent =
629                                     JavadocUtil.getJavadocCommentContent(javadocBlockBegin);
630                             assertWithMessage("Setter '%s' in '%s' must have a @since tag",
631                                     node.findFirstToken(TokenTypes.IDENT).getText(), className)
632                                     .that(javadocContent.contains("@since"))
633                                     .isTrue();
634                         }
635                     }
636                     node = node.getNextSibling();
637                 }
638             }
639         }
640     }
641 
642     private static boolean isSetter(DetailAST ast) {
643 
644         final boolean isSetterMethod;
645 
646         final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
647 
648         boolean hasOverride = false;
649 
650         // Skip @Override methods
651         DetailAST annotation = modifiers.findFirstToken(TokenTypes.ANNOTATION);
652         while (annotation != null && annotation.getType() == TokenTypes.ANNOTATION) {
653             final DetailAST ident = annotation.findFirstToken(TokenTypes.IDENT);
654             if (ident != null && "Override".equals(ident.getText())) {
655                 hasOverride = true;
656             }
657             annotation = annotation.getNextSibling();
658         }
659 
660         if (hasOverride) {
661             isSetterMethod = false;
662         }
663         else if (modifiers.findFirstToken(TokenTypes.LITERAL_PUBLIC) == null) {
664             isSetterMethod = false;
665         }
666         else {
667             boolean isSlistNotNull = false;
668             final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
669             final String name = type.getNextSibling().getText();
670             final boolean matchesSetterFormat = SETTER_PATTERN.matcher(name).matches();
671             final boolean voidReturnType = type.findFirstToken(TokenTypes.LITERAL_VOID) != null;
672 
673             final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
674             final boolean singleParam = params.getChildCount(TokenTypes.PARAMETER_DEF) == 1;
675 
676             if (matchesSetterFormat && voidReturnType && singleParam) {
677                 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST);
678                 isSlistNotNull = slist != null;
679             }
680             isSetterMethod = isSlistNotNull;
681         }
682 
683         return isSetterMethod;
684     }
685 
686     private static DetailAST getPrecedingJavadocComment(final DetailAST methodDef) {
687         final DetailAST modifiers = methodDef.findFirstToken(TokenTypes.MODIFIERS);
688         DetailAST blockComment = modifiers.findFirstToken(TokenTypes.BLOCK_COMMENT_BEGIN);
689         DetailAST result = null;
690 
691         if (blockComment == null) {
692             final DetailAST annotation = modifiers.findFirstToken(TokenTypes.ANNOTATION);
693             if (annotation != null) {
694                 blockComment = annotation.findFirstToken(TokenTypes.BLOCK_COMMENT_BEGIN);
695             }
696         }
697 
698         if (blockComment != null && JavadocUtil.isJavadocComment(blockComment)) {
699             result = blockComment;
700         }
701         return result;
702     }
703 
704     private static void verifyCheckstyleMessage(Map<String, List<String>> usedMessages,
705             Class<?> module, Field message) throws Exception {
706         final String messageString = message.get(null).toString();
707         final String packageName = module.getPackage().getName();
708         final List<String> packageMessages =
709                 usedMessages.computeIfAbsent(packageName, key -> new ArrayList<>());
710 
711         packageMessages.add(messageString);
712 
713         for (Locale locale : ALL_LOCALES) {
714             String result = null;
715 
716             try {
717                 result = CheckUtil.getCheckMessage(module, locale, messageString);
718             }
719             // -@cs[IllegalCatch] There is no other way to deliver filename that was used
720             catch (Exception exc) {
721                 assertWithMessage("%s with the message '%s' in locale '%s' failed with: %s - %s",
722                         module.getSimpleName(), messageString, locale.getLanguage(),
723                         exc.getClass().getSimpleName(), exc.getMessage()).fail();
724             }
725 
726             assertWithMessage("%s should have text for the message '%s' in locale %s'",
727                     module.getSimpleName(), messageString, locale.getLanguage())
728                 .that(result)
729                 .isNotNull();
730             assertWithMessage("%s should have non-empty text for the message '%s' in locale '%s'",
731                             module.getSimpleName(), messageString, locale.getLanguage())
732                     .that(result.trim())
733                     .isNotEmpty();
734             assertWithMessage(
735                     "%s should have non-TODO text for the message '%s' in locale %s'",
736                     module.getSimpleName(), messageString, locale.getLanguage())
737                                     .that(!"todo.match".equals(messageString)
738                                             && result.trim().startsWith("TODO"))
739                                     .isFalse();
740         }
741     }
742 
743     /**
744      * Checks that an array is a subset of other array.
745      *
746      * @param array to check whether it is a subset.
747      * @param arrayToCheckIn array to check in.
748      * @return {@code true} if all elements in {@code array} are in {@code arrayToCheckIn}.
749      */
750     private static boolean isSubset(int[] array, int... arrayToCheckIn) {
751         Arrays.sort(arrayToCheckIn);
752         boolean result = true;
753         for (final int element : array) {
754             if (Arrays.binarySearch(arrayToCheckIn, element) < 0) {
755                 result = false;
756                 break;
757             }
758         }
759         return result;
760     }
761 
762 }