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.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 new Locale("es"),
69 new Locale("fi"),
70 Locale.FRENCH,
71 Locale.GERMAN,
72 Locale.JAPANESE,
73 new Locale("pt"),
74 new Locale("ru"),
75 new Locale("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 private static final Set<String> INTERNAL_MODULES;
83
84 static {
85
86
87 CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("NoWhitespaceBefore", Stream.of(
88
89 "GENERIC_START", "GENERIC_END").collect(Collectors.toUnmodifiableSet()));
90 CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("AbbreviationAsWordInName", Stream.of(
91
92 "ENUM_CONSTANT_DEF").collect(Collectors.toUnmodifiableSet()));
93 CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("FinalLocalVariable", Stream.of(
94
95
96 "PARAMETER_DEF").collect(Collectors.toUnmodifiableSet()));
97
98 CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("IllegalToken",
99 Stream.of("LITERAL_SUPER", "LITERAL_ASSERT", "ENUM_CONSTANT_DEF",
100 "TYPE_PARAMETERS", "TYPE_UPPER_BOUNDS", "NUM_DOUBLE", "LITERAL_SWITCH",
101 "ANNOTATIONS", "LITERAL_SHORT", "LITERAL_PROTECTED", "FOR_CONDITION",
102 "FOR_INIT", "LITERAL_LONG", "MINUS", "OBJBLOCK", "LITERAL_NULL",
103 "ANNOTATION", "LITERAL_TRUE", "COMMENT_CONTENT", "LITERAL_CHAR",
104 "PARAMETER_DEF", "POST_DEC", "ANNOTATION_FIELD_DEF", "BLOCK_COMMENT_END",
105 "TYPE", "LITERAL_INT", "BSR", "ENUM", "ANNOTATION_MEMBER_VALUE_PAIR",
106 "TYPECAST", "LITERAL_SYNCHRONIZED", "PLUS_ASSIGN", "DOT", "LPAREN",
107 "LITERAL_IF", "LITERAL_CATCH", "BAND", "INTERFACE_DEF", "LOR", "BNOT",
108 "METHOD_CALL", "AT", "ELLIPSIS", "ARRAY_INIT", "FOR_EACH_CLAUSE",
109 "LITERAL_THROWS", "CHAR_LITERAL", "CASE_GROUP", "POST_INC", "SEMI",
110 "LITERAL_FINALLY", "ASSIGN", "RESOURCE_SPECIFICATION", "STATIC_IMPORT",
111 "GENERIC_START", "IMPORT", "SL", "VARIABLE_DEF", "LITERAL_DOUBLE",
112 "RCURLY", "RESOURCE", "SR", "COMMA", "BAND_ASSIGN", "METHOD_DEF",
113 "LITERAL_VOID", "NUM_LONG", "LITERAL_TRANSIENT", "LITERAL_THIS", "LCURLY",
114 "MINUS_ASSIGN", "TYPE_LOWER_BOUNDS", "TYPE_ARGUMENT", "LITERAL_CLASS",
115 "INSTANCE_INIT", "DIV", "STAR", "UNARY_MINUS", "FOR_ITERATOR", "NOT_EQUAL",
116 "LE", "LITERAL_INTERFACE", "LITERAL_FLOAT", "LITERAL_INSTANCEOF",
117 "BOR_ASSIGN", "LT", "SL_ASSIGN", "ELIST", "ANNOTATION_ARRAY_INIT",
118 "MODIFIERS", "LITERAL_BREAK", "EXTENDS_CLAUSE", "TYPE_PARAMETER",
119 "LITERAL_DEFAULT", "STATIC_INIT", "BSR_ASSIGN", "TYPE_EXTENSION_AND",
120 "BOR", "LITERAL_PRIVATE", "LITERAL_THROW", "LITERAL_BYTE", "BXOR",
121 "WILDCARD_TYPE", "FINAL", "PARAMETERS", "RPAREN", "SR_ASSIGN",
122 "UNARY_PLUS", "EMPTY_STAT", "LITERAL_STATIC", "LITERAL_CONTINUE",
123 "STAR_ASSIGN", "LAMBDA", "RBRACK", "BXOR_ASSIGN", "CTOR_CALL",
124 "LITERAL_FALSE", "DO_WHILE", "LITERAL_PUBLIC", "LITERAL_WHILE", "PLUS",
125 "INC", "CTOR_DEF", "GENERIC_END", "DIV_ASSIGN", "SLIST", "LNOT", "LAND",
126 "LITERAL_ELSE", "ABSTRACT", "STRICTFP", "QUESTION", "LITERAL_NEW",
127 "LITERAL_RETURN", "SINGLE_LINE_COMMENT", "INDEX_OP", "EXPR",
128 "BLOCK_COMMENT_BEGIN", "PACKAGE_DEF", "IMPLEMENTS_CLAUSE", "NUM_FLOAT",
129 "LITERAL_DO", "EOF", "GE", "RESOURCES", "MOD", "DEC", "EQUAL",
130 "LITERAL_BOOLEAN", "CLASS_DEF", "COLON", "LITERAL_TRY", "ENUM_DEF", "GT",
131 "NUM_INT", "ANNOTATION_DEF", "METHOD_REF", "TYPE_ARGUMENTS",
132 "DOUBLE_COLON", "IDENT", "MOD_ASSIGN", "LITERAL_FOR", "SUPER_CTOR_CALL",
133 "STRING_LITERAL", "ARRAY_DECLARATOR", "LITERAL_CASE",
134 "PATTERN_VARIABLE_DEF", "RECORD_DEF", "LITERAL_RECORD",
135 "RECORD_COMPONENTS", "RECORD_COMPONENT_DEF", "COMPACT_CTOR_DEF",
136 "TEXT_BLOCK_LITERAL_BEGIN", "TEXT_BLOCK_CONTENT", "TEXT_BLOCK_LITERAL_END",
137 "LITERAL_YIELD", "SWITCH_RULE")
138 .collect(Collectors.toUnmodifiableSet()));
139
140 CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("IllegalTokenText",
141 Stream.of("NUM_DOUBLE", "NUM_FLOAT", "NUM_INT", "NUM_LONG", "IDENT",
142 "COMMENT_CONTENT", "STRING_LITERAL", "CHAR_LITERAL", "TEXT_BLOCK_CONTENT")
143 .collect(Collectors.toUnmodifiableSet()));
144
145 CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("WriteTag",
146 Stream.of("ENUM_CONSTANT_DEF", "METHOD_DEF", "CTOR_DEF",
147 "ANNOTATION_FIELD_DEF", "RECORD_DEF", "COMPACT_CTOR_DEF")
148 .collect(Collectors.toUnmodifiableSet()));
149
150
151 CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("AnnotationLocation",
152 Stream.of("CLASS_DEF", "CTOR_DEF", "ENUM_DEF", "INTERFACE_DEF",
153 "METHOD_DEF", "VARIABLE_DEF",
154 "RECORD_DEF", "COMPACT_CTOR_DEF")
155 .collect(Collectors.toUnmodifiableSet()));
156 CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("NoLineWrap", Stream.of(
157
158
159 "METHOD_DEF", "CTOR_DEF", "COMPACT_CTOR_DEF",
160
161
162 "CLASS_DEF", "ENUM_DEF", "INTERFACE_DEF", "RECORD_DEF")
163 .collect(Collectors.toUnmodifiableSet()));
164 CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("NoWhitespaceAfter", Stream.of(
165
166 "TYPECAST", "LITERAL_SYNCHRONIZED").collect(Collectors.toUnmodifiableSet()));
167 CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("SeparatorWrap", Stream.of(
168
169
170 "LPAREN", "RPAREN").collect(Collectors.toUnmodifiableSet()));
171 CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("NeedBraces", Stream.of(
172
173
174 "LITERAL_DEFAULT", "LITERAL_CASE").collect(Collectors.toUnmodifiableSet()));
175 CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("FinalParameters", Stream.of(
176
177 "FOR_EACH_CLAUSE", "LITERAL_CATCH", "PATTERN_VARIABLE_DEF")
178 .collect(Collectors.toUnmodifiableSet()));
179 CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("WhitespaceAround", Stream.of(
180
181 "ARRAY_INIT",
182 "ELLIPSIS",
183
184 "WILDCARD_TYPE", "GENERIC_END", "GENERIC_START")
185 .collect(Collectors.toUnmodifiableSet()));
186
187
188 GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("AnnotationLocation", Stream.of(
189
190
191 "ANNOTATION_DEF", "ANNOTATION_FIELD_DEF", "ENUM_CONSTANT_DEF", "PACKAGE_DEF")
192 .collect(Collectors.toUnmodifiableSet()));
193 GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("AbbreviationAsWordInName", Stream.of(
194
195 "ENUM_CONSTANT_DEF").collect(Collectors.toUnmodifiableSet()));
196 GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("NoLineWrap", Stream.of(
197
198
199 "METHOD_DEF", "CTOR_DEF", "CLASS_DEF", "ENUM_DEF", "INTERFACE_DEF", "RECORD_DEF",
200 "COMPACT_CTOR_DEF")
201 .collect(Collectors.toUnmodifiableSet()));
202 GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("SeparatorWrap", Stream.of(
203
204
205 "RBRACK",
206
207
208 "AT",
209
210
211 "SEMI",
212
213
214 "LPAREN", "RPAREN").collect(Collectors.toUnmodifiableSet()));
215 GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("NeedBraces", Stream.of(
216
217 "LAMBDA", "LITERAL_DEFAULT", "LITERAL_CASE")
218 .collect(Collectors.toUnmodifiableSet()));
219 GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("EmptyBlock", Stream.of(
220
221 "LITERAL_DEFAULT", "LITERAL_CASE",
222
223 "LITERAL_CATCH",
224
225 "ARRAY_INIT",
226
227
228 "INSTANCE_INIT", "LITERAL_DO", "LITERAL_FOR", "LITERAL_SYNCHRONIZED",
229 "LITERAL_WHILE", "STATIC_INIT").collect(Collectors.toUnmodifiableSet()));
230 GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("WhitespaceAround", Stream.of(
231
232 "ARRAY_INIT",
233
234 "ELLIPSIS",
235
236 "GENERIC_START", "GENERIC_END", "WILDCARD_TYPE")
237 .collect(Collectors.toUnmodifiableSet()));
238 GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("IllegalTokenText", Stream.of(
239
240 "NUM_DOUBLE", "NUM_FLOAT", "NUM_INT", "NUM_LONG",
241
242 "IDENT",
243
244 "COMMENT_CONTENT"
245 )
246 .collect(Collectors.toUnmodifiableSet()));
247 GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("OperatorWrap", Stream.of(
248
249
250 "DIV_ASSIGN", "BOR_ASSIGN", "SL_ASSIGN", "ASSIGN", "BSR_ASSIGN", "BAND_ASSIGN",
251 "PLUS_ASSIGN", "MINUS_ASSIGN", "SR_ASSIGN", "STAR_ASSIGN", "BXOR_ASSIGN",
252 "MOD_ASSIGN",
253
254
255 "COLON").collect(Collectors.toUnmodifiableSet()));
256 GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("NoWhitespaceBefore", Stream.of(
257
258 "GENERIC_START", "GENERIC_END",
259
260
261 "ELLIPSIS").collect(Collectors.toUnmodifiableSet()));
262 INTERNAL_MODULES = Definitions.INTERNAL_MODULES.stream()
263 .map(moduleName -> {
264 final List<String> packageTokens = Splitter
265 .on(".").splitToList(moduleName);
266 return packageTokens.get(packageTokens.size() - 1);
267 })
268 .collect(Collectors.toUnmodifiableSet());
269 }
270
271 @Override
272 protected String getPackageLocation() {
273 return "com/puppycrawl/tools/checkstyle/internal/allchecks";
274 }
275
276 @Test
277 public void testAllModulesWithDefaultConfiguration() throws Exception {
278 final String inputFilePath = getPath("InputAllChecksDefaultConfig.java");
279 final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
280
281 for (Class<?> module : CheckUtil.getCheckstyleModules()) {
282 if (ModuleReflectionUtil.isRootModule(module)) {
283 continue;
284 }
285
286 final DefaultConfiguration moduleConfig = createModuleConfig(module);
287 if (module.equals(ImportControlCheck.class)) {
288
289
290 moduleConfig.addProperty("file", getPath(
291 "InputAllChecksImportControl.xml"));
292 }
293 verify(moduleConfig, inputFilePath, expected);
294 }
295 }
296
297 @Test
298 public void testDefaultTokensAreSubsetOfAcceptableTokens() throws Exception {
299 for (Class<?> check : CheckUtil.getCheckstyleChecks()) {
300 if (AbstractCheck.class.isAssignableFrom(check)) {
301 final AbstractCheck testedCheck = (AbstractCheck) TestUtil.instantiate(check);
302 final int[] defaultTokens = testedCheck.getDefaultTokens();
303 final int[] acceptableTokens = testedCheck.getAcceptableTokens();
304
305 assertWithMessage("%s's default tokens must be a subset of acceptable tokens.",
306 check.getName())
307 .that(isSubset(defaultTokens, acceptableTokens))
308 .isTrue();
309 }
310 }
311 }
312
313 @Test
314 public void testRequiredTokensAreSubsetOfAcceptableTokens() throws Exception {
315 for (Class<?> check : CheckUtil.getCheckstyleChecks()) {
316 if (AbstractCheck.class.isAssignableFrom(check)) {
317 final AbstractCheck testedCheck = (AbstractCheck) TestUtil.instantiate(check);
318 final int[] requiredTokens = testedCheck.getRequiredTokens();
319 final int[] acceptableTokens = testedCheck.getAcceptableTokens();
320
321 assertWithMessage("%s's required tokens must be a subset of acceptable tokens.",
322 check.getName())
323 .that(isSubset(requiredTokens, acceptableTokens))
324 .isTrue();
325 }
326 }
327 }
328
329 @Test
330 public void testRequiredTokensAreSubsetOfDefaultTokens() throws Exception {
331 for (Class<?> check : CheckUtil.getCheckstyleChecks()) {
332 if (AbstractCheck.class.isAssignableFrom(check)) {
333 final AbstractCheck testedCheck = (AbstractCheck) TestUtil.instantiate(check);
334 final int[] defaultTokens = testedCheck.getDefaultTokens();
335 final int[] requiredTokens = testedCheck.getRequiredTokens();
336
337 assertWithMessage("%s's required tokens must be a subset of default tokens.",
338 check.getName())
339 .that(isSubset(requiredTokens, defaultTokens))
340 .isTrue();
341 }
342 }
343 }
344
345 @Test
346 public void testAllModulesHaveMultiThreadAnnotation() throws Exception {
347 for (Class<?> module : CheckUtil.getCheckstyleModules()) {
348 if (ModuleReflectionUtil.isRootModule(module)
349 || ModuleReflectionUtil.isFilterModule(module)
350 || ModuleReflectionUtil.isFileFilterModule(module)
351 || ModuleReflectionUtil.isTreeWalkerFilterModule(module)) {
352 continue;
353 }
354
355 assertWithMessage("module '" + module.getSimpleName()
356 + "' must contain a multi-thread annotation")
357 .that(module.isAnnotationPresent(GlobalStatefulCheck.class)
358 || module.isAnnotationPresent(FileStatefulCheck.class)
359 || module.isAnnotationPresent(StatelessCheck.class))
360 .isTrue();
361 }
362 }
363
364 @Test
365 public void testAllModulesAreReferencedInConfigFile() throws Exception {
366 final Set<String> modulesReferencedInConfig = CheckUtil.getConfigCheckStyleModules();
367 final Set<String> moduleNames = CheckUtil.getSimpleNames(CheckUtil.getCheckstyleModules());
368
369 moduleNames.removeAll(INTERNAL_MODULES);
370 moduleNames.stream().filter(check -> !modulesReferencedInConfig.contains(check))
371 .forEach(check -> {
372 final String errorMessage = String.format(Locale.ROOT,
373 "%s is not referenced in checkstyle-checks.xml", check);
374 assertWithMessage(errorMessage).fail();
375 });
376 }
377
378 @Test
379 public void testAllCheckTokensAreReferencedInCheckstyleConfigFile() throws Exception {
380 final Configuration configuration = ConfigurationUtil
381 .loadConfiguration("config/checkstyle-checks.xml");
382
383 validateAllCheckTokensAreReferencedInConfigFile("checkstyle", configuration,
384 CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE, false);
385 }
386
387 @Test
388 public void testAllCheckTokensAreReferencedInGoogleConfigFile() throws Exception {
389 final Configuration configuration = ConfigurationUtil
390 .loadConfiguration("src/main/resources/google_checks.xml");
391
392 validateAllCheckTokensAreReferencedInConfigFile("google", configuration,
393 GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE, true);
394 }
395
396 private static void validateAllCheckTokensAreReferencedInConfigFile(String configName,
397 Configuration configuration, Map<String, Set<String>> tokensToIgnore,
398 boolean defaultTokensMustBeExplicit) throws Exception {
399 final ModuleFactory moduleFactory = TestUtil.getPackageObjectFactory();
400 final Set<Configuration> configChecks = ConfigurationUtil.getChecks(configuration);
401
402 final Map<String, Set<String>> configCheckTokens = new HashMap<>();
403 final Map<String, Set<String>> checkTokens = new HashMap<>();
404
405 for (Configuration checkConfig : configChecks) {
406 final String checkName = checkConfig.getName();
407 final Object instance;
408
409 try {
410 instance = moduleFactory.createModule(checkName);
411 }
412 catch (CheckstyleException exc) {
413 throw new CheckstyleException("Couldn't find check: " + checkName, exc);
414 }
415 final AbstractCheck check;
416 if (instance instanceof AbstractCheck abstractCheck
417 && !isAllTokensAcceptable(abstractCheck)) {
418 check = abstractCheck;
419 }
420 else {
421
422 continue;
423 }
424
425 Set<String> configTokens = configCheckTokens.get(checkName);
426
427 if (configTokens == null) {
428 configTokens = new HashSet<>();
429
430 configCheckTokens.put(checkName, configTokens);
431
432
433 final Set<String> overrideTokens = tokensToIgnore.get(checkName);
434
435 if (overrideTokens != null) {
436 configTokens.addAll(overrideTokens);
437 }
438
439 configTokens.addAll(CheckUtil.getTokenNameSet(check.getRequiredTokens()));
440 checkTokens.put(checkName,
441 CheckUtil.getTokenNameSet(check.getAcceptableTokens()));
442 }
443
444 try {
445 configTokens.addAll(Arrays.asList(checkConfig.getProperty("tokens").trim()
446 .split(",\\s*")));
447 }
448 catch (CheckstyleException exc) {
449
450 if (defaultTokensMustBeExplicit) {
451 validateDefaultTokens(checkConfig, check, configTokens);
452 }
453 else {
454 configTokens.addAll(CheckUtil.getTokenNameSet(check.getDefaultTokens()));
455 }
456 }
457 }
458 for (Entry<String, Set<String>> entry : checkTokens.entrySet()) {
459 final Set<String> actual = configCheckTokens.get(entry.getKey());
460 assertWithMessage("'" + entry.getKey()
461 + "' should have all acceptable tokens from check in " + configName
462 + " config or specify an override to ignore the specific tokens")
463 .that(actual)
464 .isEqualTo(entry.getValue());
465 }
466 }
467
468 private static boolean isAllTokensAcceptable(AbstractCheck check) {
469 return Arrays.equals(check.getAcceptableTokens(), TokenUtil.getAllTokenIds());
470 }
471
472 private static void validateDefaultTokens(Configuration checkConfig, AbstractCheck check,
473 Set<String> configTokens) {
474
475 final BitSet defaultTokensSet = TokenUtil.asBitSet(check.getDefaultTokens());
476 final BitSet requiredTokensSet = TokenUtil.asBitSet(check.getRequiredTokens());
477
478 if (defaultTokensSet.equals(requiredTokensSet)) {
479 configTokens.addAll(
480 CheckUtil.getTokenNameSet(check.getDefaultTokens()));
481 }
482 else {
483 assertWithMessage("All default tokens should be used in config for "
484 + checkConfig.getName()).fail();
485 }
486 }
487
488 @Test
489 public void testAllCheckstyleModulesHaveXdocDocumentation() throws Exception {
490 final Set<String> checkstyleModulesNames = CheckUtil.getSimpleNames(CheckUtil
491 .getCheckstyleModules());
492 final Set<String> modulesNamesWhichHaveXdocs = XdocUtil.getModulesNamesWhichHaveXdoc();
493
494
495 checkstyleModulesNames.remove("TreeWalker");
496 checkstyleModulesNames.remove("Checker");
497
498 checkstyleModulesNames.removeAll(INTERNAL_MODULES);
499 checkstyleModulesNames.stream()
500 .filter(moduleName -> !modulesNamesWhichHaveXdocs.contains(moduleName))
501 .forEach(moduleName -> {
502 final String missingModuleMessage = String.format(Locale.ROOT,
503 "Module %s does not have xdoc documentation.",
504 moduleName);
505 assertWithMessage(missingModuleMessage).fail();
506 });
507 }
508
509 @Test
510 public void testAllCheckstyleModulesInCheckstyleConfig() throws Exception {
511 final Set<String> configChecks = CheckUtil.getConfigCheckStyleModules();
512 final Set<String> moduleNames = CheckUtil.getSimpleNames(CheckUtil.getCheckstyleModules());
513 moduleNames.removeAll(INTERNAL_MODULES);
514 for (String moduleName : moduleNames) {
515 assertWithMessage("checkstyle-checks.xml is missing module: " + moduleName)
516 .that(configChecks)
517 .contains(moduleName);
518 }
519 }
520
521 @Test
522 public void testAllCheckstyleChecksHaveMessage() throws Exception {
523 for (Class<?> module : CheckUtil.getCheckstyleChecks()) {
524 final String name = module.getSimpleName();
525 final Set<Field> messages = CheckUtil.getCheckMessages(module, false);
526
527
528 if ("SuppressWarningsHolder".equals(name)) {
529 assertWithMessage(name + " should not have any 'MSG_*' fields for error messages")
530 .that(messages)
531 .isEmpty();
532 }
533 else {
534 assertWithMessage(
535 name + " should have at least one 'MSG_*' field for error messages")
536 .that(messages)
537 .isNotEmpty();
538 }
539 }
540 }
541
542 @Test
543 public void testAllCheckstyleMessages() throws Exception {
544 final Map<String, List<String>> usedMessages = new TreeMap<>();
545
546
547 for (Class<?> module : CheckUtil.getCheckstyleModules()) {
548 for (Field message : CheckUtil.getCheckMessages(module, true)) {
549 assertWithMessage(module.getSimpleName() + "." + message.getName()
550 + " should be 'public static final'")
551 .that(message.getModifiers())
552 .isEqualTo(Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL);
553
554
555 message.trySetAccessible();
556
557 if (!INTERNAL_MODULES.contains(module.getSimpleName())) {
558 verifyCheckstyleMessage(usedMessages, module, message);
559 }
560 }
561 }
562
563
564 for (Entry<String, List<String>> entry : usedMessages.entrySet()) {
565 final Properties pr = new Properties();
566 pr.load(AllChecksTest.class.getResourceAsStream(
567 "/" + entry.getKey().replace('.', '/') + "/messages.properties"));
568
569 for (Object key : pr.keySet()) {
570
571 if ("translation.wrongLanguageCode".equals(key)) {
572 continue;
573 }
574
575 assertWithMessage("property '" + key + "' isn't used by any check in package '"
576 + entry.getKey() + "'")
577 .that(entry.getValue())
578 .contains(key.toString());
579 }
580 }
581 }
582
583 private static void verifyCheckstyleMessage(Map<String, List<String>> usedMessages,
584 Class<?> module, Field message) throws Exception {
585 final String messageString = message.get(null).toString();
586 final String packageName = module.getPackage().getName();
587 final List<String> packageMessages =
588 usedMessages.computeIfAbsent(packageName, key -> new ArrayList<>());
589
590 packageMessages.add(messageString);
591
592 for (Locale locale : ALL_LOCALES) {
593 String result = null;
594
595 try {
596 result = CheckUtil.getCheckMessage(module, locale, messageString);
597 }
598
599 catch (Exception exc) {
600 assertWithMessage(module.getSimpleName() + " with the message '" + messageString
601 + "' in locale '" + locale.getLanguage() + "' failed with: "
602 + exc.getClass().getSimpleName() + " - " + exc.getMessage()).fail();
603 }
604
605 assertWithMessage(module.getSimpleName() + " should have text for the message '"
606 + messageString + "' in locale " + locale.getLanguage() + "'")
607 .that(result)
608 .isNotNull();
609 assertWithMessage("%s should have non-empty text for the message '%s' in locale '%s'",
610 module.getSimpleName(), messageString, locale.getLanguage())
611 .that(result.trim())
612 .isNotEmpty();
613 assertWithMessage(
614 module.getSimpleName() + " should have non-TODO text for the message '"
615 + messageString + "' in locale " + locale.getLanguage() + "'")
616 .that(!"todo.match".equals(messageString)
617 && result.trim().startsWith("TODO"))
618 .isFalse();
619 }
620 }
621
622
623
624
625
626
627
628
629 private static boolean isSubset(int[] array, int... arrayToCheckIn) {
630 Arrays.sort(arrayToCheckIn);
631 boolean result = true;
632 for (final int element : array) {
633 if (Arrays.binarySearch(arrayToCheckIn, element) < 0) {
634 result = false;
635 break;
636 }
637 }
638 return result;
639 }
640
641 }