1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
95
96 CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("NoWhitespaceBefore", Stream.of(
97
98 "GENERIC_START", "GENERIC_END").collect(Collectors.toUnmodifiableSet()));
99 CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("AbbreviationAsWordInName", Stream.of(
100
101 "ENUM_CONSTANT_DEF").collect(Collectors.toUnmodifiableSet()));
102 CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("FinalLocalVariable", Stream.of(
103
104
105 "PARAMETER_DEF").collect(Collectors.toUnmodifiableSet()));
106
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
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
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
161
162 "METHOD_DEF", "CTOR_DEF", "COMPACT_CTOR_DEF",
163
164
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
169 "TYPECAST", "LITERAL_SYNCHRONIZED").collect(Collectors.toUnmodifiableSet()));
170 CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("SeparatorWrap", Stream.of(
171
172
173 "LPAREN", "RPAREN").collect(Collectors.toUnmodifiableSet()));
174 CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("NeedBraces", Stream.of(
175
176
177 "LITERAL_DEFAULT", "LITERAL_CASE").collect(Collectors.toUnmodifiableSet()));
178 CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("FinalParameters", Stream.of(
179
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
184 "ARRAY_INIT",
185 "ELLIPSIS",
186
187 "WILDCARD_TYPE", "GENERIC_END", "GENERIC_START")
188 .collect(Collectors.toUnmodifiableSet()));
189
190
191 GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("AbbreviationAsWordInName", Stream.of(
192
193 "ENUM_CONSTANT_DEF").collect(Collectors.toUnmodifiableSet()));
194 GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("NoLineWrap", Stream.of(
195
196
197 "METHOD_DEF", "CTOR_DEF", "CLASS_DEF", "ENUM_DEF", "INTERFACE_DEF", "RECORD_DEF",
198 "COMPACT_CTOR_DEF",
199
200 "MODULE_IMPORT",
201
202 "MODULE_DEF")
203 .collect(Collectors.toUnmodifiableSet()));
204 GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("SeparatorWrap", Stream.of(
205
206
207 "RBRACK",
208
209
210 "AT",
211
212
213 "SEMI",
214
215
216 "LPAREN", "RPAREN").collect(Collectors.toUnmodifiableSet()));
217 GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("NeedBraces", Stream.of(
218
219 "LAMBDA", "LITERAL_DEFAULT", "LITERAL_CASE")
220 .collect(Collectors.toUnmodifiableSet()));
221 GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("EmptyBlock", Stream.of(
222
223 "LITERAL_DEFAULT", "LITERAL_CASE",
224
225 "LITERAL_CATCH",
226
227 "ARRAY_INIT",
228
229
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
234 "MODULE_IMPORT").collect(Collectors.toUnmodifiableSet()));
235 GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("WhitespaceAround", Stream.of(
236
237 "ARRAY_INIT",
238
239 "ELLIPSIS",
240
241 "GENERIC_START", "GENERIC_END", "WILDCARD_TYPE")
242 .collect(Collectors.toUnmodifiableSet()));
243 GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("WhitespaceAfter", Stream.of(
244
245
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
253
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
260 "NUM_DOUBLE", "NUM_FLOAT", "NUM_INT", "NUM_LONG",
261
262 "IDENT",
263
264 "COMMENT_CONTENT"
265 )
266 .collect(Collectors.toUnmodifiableSet()));
267 GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("OperatorWrap", Stream.of(
268
269
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
274
275 "COLON").collect(Collectors.toUnmodifiableSet()));
276 GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("NoWhitespaceBefore", Stream.of(
277
278 "GENERIC_START", "GENERIC_END",
279
280
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
309
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
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
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
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
517 checkstyleModulesNames.remove("TreeWalker");
518 checkstyleModulesNames.remove("Checker");
519
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
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
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
577 message.trySetAccessible();
578
579 if (!INTERNAL_MODULES.contains(module.getSimpleName())) {
580 verifyCheckstyleMessage(usedMessages, module, message);
581 }
582 }
583 }
584
585
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
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
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
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
745
746
747
748
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 }