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 java.lang.Integer.parseInt;
23 import static java.nio.charset.StandardCharsets.UTF_8;
24 import static org.hamcrest.CoreMatchers.describedAs;
25 import static org.hamcrest.CoreMatchers.is;
26
27 import java.beans.PropertyDescriptor;
28 import java.io.File;
29 import java.io.IOException;
30 import java.io.StringReader;
31 import java.lang.reflect.Array;
32 import java.lang.reflect.Field;
33 import java.lang.reflect.ParameterizedType;
34 import java.net.URI;
35 import java.nio.file.Files;
36 import java.nio.file.Path;
37 import java.nio.file.Paths;
38 import java.util.ArrayList;
39 import java.util.Arrays;
40 import java.util.BitSet;
41 import java.util.Collection;
42 import java.util.Collections;
43 import java.util.HashSet;
44 import java.util.Iterator;
45 import java.util.List;
46 import java.util.Locale;
47 import java.util.NoSuchElementException;
48 import java.util.Properties;
49 import java.util.Set;
50 import java.util.TreeSet;
51 import java.util.regex.Pattern;
52 import java.util.stream.IntStream;
53
54 import org.apache.commons.beanutils.PropertyUtils;
55 import org.junit.Assert;
56 import org.junit.Test;
57 import org.w3c.dom.Document;
58 import org.w3c.dom.Node;
59 import org.w3c.dom.NodeList;
60 import org.xml.sax.InputSource;
61
62 import com.puppycrawl.tools.checkstyle.Checker;
63 import com.puppycrawl.tools.checkstyle.ConfigurationLoader;
64 import com.puppycrawl.tools.checkstyle.ModuleFactory;
65 import com.puppycrawl.tools.checkstyle.PropertiesExpander;
66 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
67 import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;
68 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
69 import com.puppycrawl.tools.checkstyle.api.Configuration;
70 import com.puppycrawl.tools.checkstyle.api.Scope;
71 import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
72 import com.puppycrawl.tools.checkstyle.checks.LineSeparatorOption;
73 import com.puppycrawl.tools.checkstyle.checks.annotation.AnnotationUseStyleCheck.ClosingParens;
74 import com.puppycrawl.tools.checkstyle.checks.annotation.AnnotationUseStyleCheck.ElementStyle;
75 import com.puppycrawl.tools.checkstyle.checks.annotation.AnnotationUseStyleCheck.TrailingArrayComma;
76 import com.puppycrawl.tools.checkstyle.checks.blocks.BlockOption;
77 import com.puppycrawl.tools.checkstyle.checks.blocks.LeftCurlyOption;
78 import com.puppycrawl.tools.checkstyle.checks.blocks.RightCurlyOption;
79 import com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderOption;
80 import com.puppycrawl.tools.checkstyle.checks.javadoc.AbstractJavadocCheck;
81 import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifier;
82 import com.puppycrawl.tools.checkstyle.checks.whitespace.PadOption;
83 import com.puppycrawl.tools.checkstyle.checks.whitespace.WrapOption;
84 import com.puppycrawl.tools.checkstyle.internal.utils.CheckUtil;
85 import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
86 import com.puppycrawl.tools.checkstyle.internal.utils.XdocUtil;
87 import com.puppycrawl.tools.checkstyle.internal.utils.XmlUtil;
88 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
89
90 public class XdocsPagesTest {
91
92 private static final Path AVAILABLE_CHECKS_PATH = Paths.get("src/xdocs/checks.xml");
93 private static final String LINK_TEMPLATE =
94 "(?s).*<a href=\"config_\\w+\\.html#%1$s\">(\\s)*%1$s</a>.*";
95
96 private static final Pattern VERSION = Pattern.compile("\\d+\\.\\d+(\\.\\d+)?");
97
98 private static final Pattern DESCRIPTION_VERSION = Pattern
99 .compile("^Since Checkstyle \\d+\\.\\d+(\\.\\d+)?");
100
101 private static final List<String> XML_FILESET_LIST = Arrays.asList(
102 "TreeWalker",
103 "name=\"Checker\"",
104 "name=\"Header\"",
105 "name=\"Translation\"",
106 "name=\"SeverityMatchFilter\"",
107 "name=\"SuppressWithPlainTextCommentFilter\"",
108 "name=\"SuppressionFilter\"",
109 "name=\"SuppressWarningsFilter\"",
110 "name=\"BeforeExecutionExclusionFileFilter\"",
111 "name=\"RegexpHeader\"",
112 "name=\"RegexpOnFilename\"",
113 "name=\"RegexpSingleline\"",
114 "name=\"RegexpMultiline\"",
115 "name=\"JavadocPackage\"",
116 "name=\"NewlineAtEndOfFile\"",
117 "name=\"UniqueProperties\"",
118 "name=\"FileLength\"",
119 "name=\"FileTabCharacter\""
120 );
121
122 private static final Set<String> CHECK_PROPERTIES = getProperties(AbstractCheck.class);
123 private static final Set<String> JAVADOC_CHECK_PROPERTIES =
124 getProperties(AbstractJavadocCheck.class);
125 private static final Set<String> FILESET_PROPERTIES = getProperties(AbstractFileSetCheck.class);
126
127 private static final List<String> UNDOCUMENTED_PROPERTIES = Arrays.asList(
128 "Checker.classLoader",
129 "Checker.classloader",
130 "Checker.moduleClassLoader",
131 "Checker.moduleFactory",
132 "TreeWalker.classLoader",
133 "TreeWalker.moduleFactory",
134 "TreeWalker.cacheFile",
135 "TreeWalker.upChild",
136 "SuppressWithNearbyCommentFilter.fileContents",
137 "SuppressionCommentFilter.fileContents"
138 );
139
140 private static final List<String> PROPERTIES_ALLOWED_GET_TYPES_FROM_METHOD = Arrays.asList(
141
142 "SuppressWarningsHolder.aliasList",
143
144 "Header.header",
145 "RegexpHeader.header"
146 );
147
148 private static final Set<String> SUN_MODULES = Collections.unmodifiableSet(
149 new HashSet<>(CheckUtil.getConfigSunStyleModules()));
150
151
152
153 private static final List<String> IGNORED_SUN_MODULES = Arrays.asList(
154 "ArrayTypeStyle",
155 "AvoidNestedBlocks",
156 "AvoidStarImport",
157 "ConstantName",
158 "DesignForExtension",
159 "EmptyBlock",
160 "EmptyForIteratorPad",
161 "EmptyStatement",
162 "EqualsHashCode",
163 "FileLength",
164 "FileTabCharacter",
165 "FinalClass",
166 "FinalParameters",
167 "GenericWhitespace",
168 "HiddenField",
169 "HideUtilityClassConstructor",
170 "IllegalImport",
171 "IllegalInstantiation",
172 "InnerAssignment",
173 "InterfaceIsType",
174 "JavadocMethod",
175 "JavadocPackage",
176 "JavadocStyle",
177 "JavadocType",
178 "JavadocVariable",
179 "LeftCurly",
180 "LineLength",
181 "LocalFinalVariableName",
182 "LocalVariableName",
183 "MagicNumber",
184 "MemberName",
185 "MethodLength",
186 "MethodName",
187 "MethodParamPad",
188 "MissingSwitchDefault",
189 "ModifierOrder",
190 "NeedBraces",
191 "NewlineAtEndOfFile",
192 "NoWhitespaceAfter",
193 "NoWhitespaceBefore",
194 "OperatorWrap",
195 "PackageName",
196 "ParameterName",
197 "ParameterNumber",
198 "ParenPad",
199 "RedundantImport",
200 "RedundantModifier",
201 "RegexpSingleline",
202 "RightCurly",
203 "SimplifyBooleanExpression",
204 "SimplifyBooleanReturn",
205 "StaticVariableName",
206 "TodoComment",
207 "Translation",
208 "TypecastParenPad",
209 "TypeName",
210 "UnusedImports",
211 "UpperEll",
212 "VisibilityModifier",
213 "WhitespaceAfter",
214 "WhitespaceAround"
215 );
216 private static final Set<String> GOOGLE_MODULES = Collections.unmodifiableSet(
217 new HashSet<>(CheckUtil.getConfigGoogleStyleModules()));
218
219 @Test
220 public void testAllChecksPresentOnAvailableChecksPage() throws Exception {
221 final String availableChecks = new String(Files.readAllBytes(AVAILABLE_CHECKS_PATH), UTF_8);
222
223 CheckUtil.getSimpleNames(CheckUtil.getCheckstyleChecks())
224 .forEach(checkName -> {
225 if (!isPresent(availableChecks, checkName)) {
226 Assert.fail(checkName + " is not correctly listed on Available Checks page"
227 + " - add it to " + AVAILABLE_CHECKS_PATH);
228 }
229 });
230 }
231
232 private static boolean isPresent(String availableChecks, String checkName) {
233 final String linkPattern = String.format(Locale.ROOT, LINK_TEMPLATE, checkName);
234 return availableChecks.matches(linkPattern);
235 }
236
237 @Test
238 public void testAllSubSections() throws Exception {
239 for (Path path : XdocUtil.getXdocsFilePaths()) {
240 final String input = new String(Files.readAllBytes(path), UTF_8);
241 final String fileName = path.getFileName().toString();
242
243 final Document document = XmlUtil.getRawXml(fileName, input, input);
244 final NodeList subSections = document.getElementsByTagName("subsection");
245
246 for (int position = 0; position < subSections.getLength(); position++) {
247 final Node subSection = subSections.item(position);
248 final Node name = subSection.getAttributes().getNamedItem("name");
249
250 Assert.assertNotNull("All sub-sections in '" + fileName + "' must have a name",
251 name);
252
253 final Node id = subSection.getAttributes().getNamedItem("id");
254
255 Assert.assertNotNull("All sub-sections in '" + fileName + "' must have an id", id);
256
257 final String sectionName;
258
259 if ("google_style.xml".equals(fileName)) {
260 sectionName = "Google";
261 }
262 else if ("sun_style.xml".equals(fileName)) {
263 sectionName = "Sun";
264 }
265 else {
266 sectionName = subSection.getParentNode().getAttributes()
267 .getNamedItem("name").getTextContent();
268 }
269
270 final String nameString = name.getNodeValue();
271 final String idString = id.getNodeValue();
272
273 Assert.assertEquals(fileName + " sub-section " + nameString + " for section "
274 + sectionName + " must match",
275 (sectionName + " " + nameString).replace(' ', '_'), idString);
276 }
277 }
278 }
279
280 @Test
281 public void testAllXmlExamples() throws Exception {
282 for (Path path : XdocUtil.getXdocsFilePaths()) {
283 final String input = new String(Files.readAllBytes(path), UTF_8);
284 final String fileName = path.getFileName().toString();
285
286 final Document document = XmlUtil.getRawXml(fileName, input, input);
287 final NodeList sources = document.getElementsByTagName("source");
288
289 for (int position = 0; position < sources.getLength(); position++) {
290 final String unserializedSource = sources.item(position).getTextContent()
291 .replace("...", "").trim();
292
293 if (unserializedSource.charAt(0) != '<'
294 || unserializedSource.charAt(unserializedSource.length() - 1) != '>'
295
296 || unserializedSource.contains("<!")) {
297 continue;
298 }
299
300 final String code = buildXml(unserializedSource);
301
302 XmlUtil.getRawXml(fileName, code, unserializedSource);
303
304
305 Assert.assertTrue("Xml is invalid, old or has outdated structure",
306 fileName.startsWith("anttask")
307 || fileName.startsWith("releasenotes")
308 || isValidCheckstyleXml(fileName, code, unserializedSource));
309 }
310 }
311 }
312
313 private static String buildXml(String unserializedSource) throws IOException {
314
315 String code = unserializedSource
316
317 .replace("target/cachefile", "target/cachefile-test");
318
319 if (!hasFileSetClass(code)) {
320 code = "<module name=\"TreeWalker\">\n" + code + "\n</module>";
321 }
322 if (!code.contains("name=\"Checker\"")) {
323 code = "<module name=\"Checker\">\n" + code + "\n</module>";
324 }
325 if (!code.startsWith("<?xml")) {
326 final String dtdPath = new File(
327 "src/main/resources/com/puppycrawl/tools/checkstyle/configuration_1_3.dtd")
328 .getCanonicalPath();
329
330 code = "<?xml version=\"1.0\"?>\n<!DOCTYPE module PUBLIC "
331 + "\"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN\" \"" + dtdPath
332 + "\">\n" + code;
333 }
334 return code;
335 }
336
337 private static boolean hasFileSetClass(String xml) {
338 boolean found = false;
339
340 for (String find : XML_FILESET_LIST) {
341 if (xml.contains(find)) {
342 found = true;
343 break;
344 }
345 }
346
347 return found;
348 }
349
350 private static boolean isValidCheckstyleXml(String fileName, String code,
351 String unserializedSource)
352 throws IOException, CheckstyleException {
353
354 if (!code.contains("com.mycompany") && !code.contains("checkstyle-packages")
355 && !code.contains("MethodLimit") && !code.contains("<suppress ")
356 && !code.contains("<suppress-xpath ")
357 && !code.contains("<import-control ")
358 && !unserializedSource.startsWith("<property ")
359 && !unserializedSource.startsWith("<taskdef ")) {
360
361 try {
362 final Properties properties = new Properties();
363
364 properties.setProperty("checkstyle.header.file",
365 new File("config/java.header").getCanonicalPath());
366
367 final PropertiesExpander expander = new PropertiesExpander(properties);
368 final Configuration config = ConfigurationLoader.loadConfiguration(new InputSource(
369 new StringReader(code)), expander, false);
370 final Checker checker = new Checker();
371
372 try {
373 final ClassLoader moduleClassLoader = Checker.class.getClassLoader();
374 checker.setModuleClassLoader(moduleClassLoader);
375 checker.configure(config);
376 }
377 finally {
378 checker.destroy();
379 }
380 }
381 catch (CheckstyleException ex) {
382 throw new CheckstyleException(fileName + " has invalid Checkstyle xml ("
383 + ex.getMessage() + "): " + unserializedSource, ex);
384 }
385 }
386 return true;
387 }
388
389 @Test
390 public void testAllCheckSections() throws Exception {
391 final ModuleFactory moduleFactory = TestUtil.getPackageObjectFactory();
392
393 for (Path path : XdocUtil.getXdocsConfigFilePaths(XdocUtil.getXdocsFilePaths())) {
394 final String fileName = path.getFileName().toString();
395
396 if ("config_reporting.xml".equals(fileName)) {
397 continue;
398 }
399
400 final String input = new String(Files.readAllBytes(path), UTF_8);
401 final Document document = XmlUtil.getRawXml(fileName, input, input);
402 final NodeList sources = document.getElementsByTagName("section");
403 String lastSectionName = null;
404
405 for (int position = 0; position < sources.getLength(); position++) {
406 final Node section = sources.item(position);
407 final String sectionName = section.getAttributes().getNamedItem("name")
408 .getNodeValue();
409
410 if ("Content".equals(sectionName) || "Overview".equals(sectionName)) {
411 Assert.assertNull(fileName + " section '" + sectionName + "' should be first",
412 lastSectionName);
413 continue;
414 }
415
416 Assert.assertTrue(fileName + " section '" + sectionName
417 + "' shouldn't end with 'Check'", !sectionName.endsWith("Check"));
418 if (lastSectionName != null) {
419 Assert.assertTrue(
420 fileName + " section '" + sectionName
421 + "' is out of order compared to '" + lastSectionName + "'",
422 sectionName.toLowerCase(Locale.ENGLISH).compareTo(
423 lastSectionName.toLowerCase(Locale.ENGLISH)) >= 0);
424 }
425
426 validateCheckSection(moduleFactory, fileName, sectionName, section);
427
428 lastSectionName = sectionName;
429 }
430 }
431 }
432
433
434
435
436
437 @Test
438 public void testAllCheckSectionsEx() throws Exception {
439 final ModuleFactory moduleFactory = TestUtil.getPackageObjectFactory();
440
441 final Path path = Paths.get(XdocUtil.DIRECTORY_PATH + "/config.xml");
442 final String fileName = path.getFileName().toString();
443
444 final String input = new String(Files.readAllBytes(path), UTF_8);
445 final Document document = XmlUtil.getRawXml(fileName, input, input);
446 final NodeList sources = document.getElementsByTagName("section");
447
448 for (int position = 0; position < sources.getLength(); position++) {
449 final Node section = sources.item(position);
450 final String sectionName = section.getAttributes().getNamedItem("name")
451 .getNodeValue();
452
453 if (!"Checker".equals(sectionName) && !"TreeWalker".equals(sectionName)) {
454 continue;
455 }
456
457 validateCheckSection(moduleFactory, fileName, sectionName, section);
458 }
459 }
460
461 private static void validateCheckSection(ModuleFactory moduleFactory, String fileName,
462 String sectionName, Node section) throws Exception {
463 final Object instance;
464
465 try {
466 instance = moduleFactory.createModule(sectionName);
467 }
468 catch (CheckstyleException ex) {
469 throw new CheckstyleException(fileName + " couldn't find class: " + sectionName, ex);
470 }
471
472 int subSectionPos = 0;
473 for (Node subSection : XmlUtil.getChildrenElements(section)) {
474 if (subSectionPos == 0 && "p".equals(subSection.getNodeName())) {
475 validateSinceDescriptionSection(fileName, sectionName, subSection);
476 continue;
477 }
478
479 final String subSectionName = subSection.getAttributes().getNamedItem("name")
480 .getNodeValue();
481
482
483 if ("Notes".equals(subSectionName)
484 || "Rule Description".equals(subSectionName)
485 || "Metadata".equals(subSectionName)) {
486 continue;
487 }
488
489
490 if (subSectionPos == 1 && !"Properties".equals(subSectionName)) {
491 validatePropertySection(fileName, sectionName, null, instance);
492 subSectionPos++;
493 }
494 if (subSectionPos == 4 && !"Error Messages".equals(subSectionName)) {
495 validateErrorSection(fileName, sectionName, null, instance);
496 subSectionPos++;
497 }
498
499 Assert.assertEquals(fileName + " section '" + sectionName
500 + "' should be in order", getSubSectionName(subSectionPos),
501 subSectionName);
502
503 switch (subSectionPos) {
504 case 0:
505 break;
506 case 1:
507 validatePropertySection(fileName, sectionName, subSection, instance);
508 break;
509 case 2:
510 break;
511 case 3:
512 validateUsageExample(fileName, sectionName, subSection);
513 break;
514 case 4:
515 validateErrorSection(fileName, sectionName, subSection, instance);
516 break;
517 case 5:
518 validatePackageSection(fileName, sectionName, subSection, instance);
519 break;
520 case 6:
521 validateParentSection(fileName, sectionName, subSection);
522 break;
523 default:
524 break;
525 }
526
527 subSectionPos++;
528 }
529
530 if ("Checker".equals(sectionName)) {
531 Assert.assertTrue(fileName + " section '" + sectionName
532 + "' should contain up to 'Package' sub-section", subSectionPos >= 6);
533 }
534 else {
535 Assert.assertTrue(fileName + " section '" + sectionName
536 + "' should contain up to 'Parent' sub-section", subSectionPos >= 7);
537 }
538 }
539
540 private static void validateSinceDescriptionSection(String fileName, String sectionName,
541 Node subSection) {
542 Assert.assertTrue(fileName + " section '" + sectionName
543 + "' should have a valid version at the start of the description like:\n"
544 + DESCRIPTION_VERSION.pattern(),
545 DESCRIPTION_VERSION.matcher(subSection.getTextContent().trim()).find());
546 }
547
548 private static Object getSubSectionName(int subSectionPos) {
549 final String result;
550
551 switch (subSectionPos) {
552 case 0:
553 result = "Description";
554 break;
555 case 1:
556 result = "Properties";
557 break;
558 case 2:
559 result = "Examples";
560 break;
561 case 3:
562 result = "Example of Usage";
563 break;
564 case 4:
565 result = "Error Messages";
566 break;
567 case 5:
568 result = "Package";
569 break;
570 case 6:
571 result = "Parent Module";
572 break;
573 default:
574 result = null;
575 break;
576 }
577
578 return result;
579 }
580
581 private static void validatePropertySection(String fileName, String sectionName,
582 Node subSection, Object instance) throws Exception {
583 final Set<String> properties = getProperties(instance.getClass());
584 final Class<?> clss = instance.getClass();
585
586 fixCapturedProperties(sectionName, instance, clss, properties);
587
588 if (subSection != null) {
589 Assert.assertTrue(fileName + " section '" + sectionName
590 + "' should have no properties to show", !properties.isEmpty());
591
592 validatePropertySectionProperties(fileName, sectionName, subSection, instance,
593 properties);
594 }
595
596 Assert.assertTrue(fileName + " section '" + sectionName + "' should show properties: "
597 + properties, properties.isEmpty());
598 }
599
600 private static void fixCapturedProperties(String sectionName, Object instance, Class<?> clss,
601 Set<String> properties) {
602
603 if (hasParentModule(sectionName)) {
604 if (AbstractJavadocCheck.class.isAssignableFrom(clss)) {
605 properties.removeAll(JAVADOC_CHECK_PROPERTIES);
606
607
608 properties.add("violateExecutionOnNonTightHtml");
609 }
610 else if (AbstractCheck.class.isAssignableFrom(clss)) {
611 properties.removeAll(CHECK_PROPERTIES);
612 }
613 }
614 if (AbstractFileSetCheck.class.isAssignableFrom(clss)) {
615 properties.removeAll(FILESET_PROPERTIES);
616
617
618 properties.add("fileExtensions");
619 }
620
621
622 new HashSet<>(properties).stream()
623 .filter(prop -> UNDOCUMENTED_PROPERTIES.contains(clss.getSimpleName() + "." + prop))
624 .forEach(properties::remove);
625
626 if (AbstractCheck.class.isAssignableFrom(clss)) {
627 final AbstractCheck check = (AbstractCheck) instance;
628
629 final int[] acceptableTokens = check.getAcceptableTokens();
630 Arrays.sort(acceptableTokens);
631 final int[] defaultTokens = check.getDefaultTokens();
632 Arrays.sort(defaultTokens);
633 final int[] requiredTokens = check.getRequiredTokens();
634 Arrays.sort(requiredTokens);
635
636 if (!Arrays.equals(acceptableTokens, defaultTokens)
637 || !Arrays.equals(acceptableTokens, requiredTokens)) {
638 properties.add("tokens");
639 }
640 }
641
642 if (AbstractJavadocCheck.class.isAssignableFrom(clss)) {
643 final AbstractJavadocCheck check = (AbstractJavadocCheck) instance;
644
645 final int[] acceptableJavadocTokens = check.getAcceptableJavadocTokens();
646 Arrays.sort(acceptableJavadocTokens);
647 final int[] defaultJavadocTokens = check.getDefaultJavadocTokens();
648 Arrays.sort(defaultJavadocTokens);
649 final int[] requiredJavadocTokens = check.getRequiredJavadocTokens();
650 Arrays.sort(requiredJavadocTokens);
651
652 if (!Arrays.equals(acceptableJavadocTokens, defaultJavadocTokens)
653 || !Arrays.equals(acceptableJavadocTokens, requiredJavadocTokens)) {
654 properties.add("javadocTokens");
655 }
656 }
657 }
658
659 private static void validatePropertySectionProperties(String fileName, String sectionName,
660 Node subSection, Object instance, Set<String> properties) throws Exception {
661 boolean skip = true;
662 boolean didJavadocTokens = false;
663 boolean didTokens = false;
664
665 for (Node row : XmlUtil.getChildrenElements(XmlUtil.getFirstChildElement(subSection))) {
666 final List<Node> columns = new ArrayList<>(XmlUtil.getChildrenElements(row));
667
668 Assert.assertEquals(fileName + " section '" + sectionName
669 + "' should have the requested columns", 5, columns.size());
670
671 if (skip) {
672 Assert.assertEquals(fileName + " section '" + sectionName
673 + "' should have the specific title", "name", columns.get(0)
674 .getTextContent());
675 Assert.assertEquals(fileName + " section '" + sectionName
676 + "' should have the specific title", "description", columns.get(1)
677 .getTextContent());
678 Assert.assertEquals(fileName + " section '" + sectionName
679 + "' should have the specific title", "type", columns.get(2)
680 .getTextContent());
681 Assert.assertEquals(fileName + " section '" + sectionName
682 + "' should have the specific title", "default value", columns.get(3)
683 .getTextContent());
684 Assert.assertEquals(fileName + " section '" + sectionName
685 + "' should have the specific title", "since", columns.get(4)
686 .getTextContent());
687
688 skip = false;
689 continue;
690 }
691
692 Assert.assertFalse(fileName + " section '" + sectionName
693 + "' should have token properties last", didTokens);
694
695 final String propertyName = columns.get(0).getTextContent();
696 Assert.assertTrue(fileName + " section '" + sectionName
697 + "' should not contain the property: " + propertyName,
698 properties.remove(propertyName));
699
700 if ("tokens".equals(propertyName)) {
701 final AbstractCheck check = (AbstractCheck) instance;
702 validatePropertySectionPropertyTokens(fileName, sectionName, check, columns);
703 didTokens = true;
704 }
705 else if ("javadocTokens".equals(propertyName)) {
706 final AbstractJavadocCheck check = (AbstractJavadocCheck) instance;
707 validatePropertySectionPropertyJavadocTokens(fileName, sectionName, check, columns);
708 didJavadocTokens = true;
709 }
710 else {
711 Assert.assertFalse(fileName + " section '" + sectionName
712 + "' should have javadoc token properties next to last, before tokens",
713 didJavadocTokens);
714
715 validatePropertySectionPropertyEx(fileName, sectionName, instance, columns,
716 propertyName);
717 }
718
719 Assert.assertFalse(fileName + " section '" + sectionName
720 + "' should have a version for " + propertyName, columns.get(4)
721 .getTextContent().trim().isEmpty());
722 Assert.assertTrue(fileName + " section '" + sectionName
723 + "' should have a valid version for " + propertyName,
724 VERSION.matcher(columns.get(4).getTextContent().trim()).matches());
725 }
726 }
727
728 private static void validatePropertySectionPropertyEx(String fileName, String sectionName,
729 Object instance, List<Node> columns, String propertyName) throws Exception {
730 Assert.assertFalse(fileName + " section '" + sectionName
731 + "' should have a description for " + propertyName, columns.get(1)
732 .getTextContent().trim().isEmpty());
733
734 final String actualTypeName = columns.get(2).getTextContent().replace("\n", "")
735 .replace("\r", "").replaceAll(" +", " ").trim();
736
737 Assert.assertFalse(fileName + " section '" + sectionName + "' should have a type for "
738 + propertyName, actualTypeName.isEmpty());
739
740 final Field field = getField(instance.getClass(), propertyName);
741 final Class<?> fieldClss = getFieldClass(fileName, sectionName, instance, field,
742 propertyName);
743
744 final String expectedTypeName = getModulePropertyExpectedTypeName(sectionName, fieldClss,
745 instance, propertyName);
746 final String expectedValue = getModulePropertyExpectedValue(sectionName, propertyName,
747 field, fieldClss, instance);
748
749 Assert.assertEquals(fileName + " section '" + sectionName
750 + "' should have the type for " + propertyName, expectedTypeName,
751 actualTypeName);
752
753 if (expectedValue != null) {
754 final String actualValue = columns.get(3).getTextContent().replace("\n", "")
755 .replace("\r", "").replaceAll(" +", " ").trim();
756
757 Assert.assertEquals(fileName + " section '" + sectionName
758 + "' should have the value for " + propertyName, expectedValue,
759 actualValue);
760 }
761 }
762
763 private static void validatePropertySectionPropertyTokens(String fileName, String sectionName,
764 AbstractCheck check, List<Node> columns) {
765 Assert.assertEquals(fileName + " section '" + sectionName
766 + "' should have the basic token description", "tokens to check", columns.get(1)
767 .getTextContent());
768 Assert.assertEquals(
769 fileName + " section '" + sectionName + "' should have all the acceptable tokens",
770 "subset of tokens "
771 + CheckUtil.getTokenText(check.getAcceptableTokens(),
772 check.getRequiredTokens()), columns.get(2).getTextContent()
773 .replaceAll("\\s+", " ").trim());
774 Assert.assertEquals(fileName + " section '" + sectionName
775 + "' should have all the default tokens",
776 CheckUtil.getTokenText(check.getDefaultTokens(), check.getRequiredTokens()),
777 columns.get(3).getTextContent().replaceAll("\\s+", " ").trim());
778 }
779
780 private static void validatePropertySectionPropertyJavadocTokens(String fileName,
781 String sectionName, AbstractJavadocCheck check, List<Node> columns) {
782 Assert.assertEquals(fileName + " section '" + sectionName
783 + "' should have the basic token javadoc description", "javadoc tokens to check",
784 columns.get(1).getTextContent());
785 Assert.assertEquals(
786 fileName + " section '" + sectionName
787 + "' should have all the acceptable javadoc tokens",
788 "subset of javadoc tokens "
789 + CheckUtil.getJavadocTokenText(check.getAcceptableJavadocTokens(),
790 check.getRequiredJavadocTokens()), columns.get(2).getTextContent()
791 .replaceAll("\\s+", " ").trim());
792 Assert.assertEquals(
793 fileName + " section '" + sectionName
794 + "' should have all the default javadoc tokens",
795 CheckUtil.getJavadocTokenText(check.getDefaultJavadocTokens(),
796 check.getRequiredJavadocTokens()), columns.get(3).getTextContent()
797 .replaceAll("\\s+", " ").trim());
798 }
799
800
801
802
803
804
805
806
807
808
809 private static String getModulePropertyExpectedTypeName(String sectionName, Class<?> fieldClass,
810 Object instance, String propertyName) {
811 final String instanceName = instance.getClass().getSimpleName();
812 String result = null;
813
814 if (("SuppressionCommentFilter".equals(sectionName)
815 || "SuppressWithNearbyCommentFilter".equals(sectionName)
816 || "SuppressWithPlainTextCommentFilter".equals(sectionName))
817 && ("checkFormat".equals(propertyName)
818 || "messageFormat".equals(propertyName)
819 || "influenceFormat".equals(propertyName))
820 || ("RegexpMultiline".equals(sectionName)
821 || "RegexpSingleline".equals(sectionName)
822 || "RegexpSinglelineJava".equals(sectionName))
823 && "format".equals(propertyName)) {
824
825 result = "Regular Expression";
826 }
827 else if ("CustomImportOrder".equals(sectionName)
828 && "customImportOrderRules".equals(propertyName)) {
829
830 result = "String";
831 }
832 else if (fieldClass == boolean.class) {
833 result = "Boolean";
834 }
835 else if (fieldClass == int.class) {
836 result = "Integer";
837 }
838 else if (fieldClass == int[].class) {
839 if (isPropertyTokenType(sectionName, propertyName)) {
840 result = "subset of tokens TokenTypes";
841 }
842 else {
843 result = "Integer Set";
844 }
845 }
846 else if (fieldClass == double[].class) {
847 result = "Number Set";
848 }
849 else if (fieldClass == String.class) {
850 result = "String";
851
852 if ("Checker".equals(sectionName) && "localeCountry".equals(propertyName)) {
853 result += " (either the empty string or an uppercase ISO 3166 2-letter code)";
854 }
855 else if ("Checker".equals(sectionName) && "localeLanguage".equals(propertyName)) {
856 result += " (either the empty string or a lowercase ISO 639 code)";
857 }
858 }
859 else if (fieldClass == String[].class) {
860 if (propertyName.endsWith("Tokens") || propertyName.endsWith("Token")
861 || "AtclauseOrderCheck".equals(instanceName) && "target".equals(propertyName)
862 || "MultipleStringLiteralsCheck".equals(instanceName)
863 && "ignoreOccurrenceContext".equals(propertyName)) {
864 result = "subset of tokens TokenTypes";
865 }
866 else {
867 result = "String Set";
868 }
869 }
870 else if (fieldClass == URI.class) {
871 result = "URI";
872 }
873 else if (fieldClass == Pattern.class) {
874 result = "Regular Expression";
875 }
876 else if (fieldClass == Pattern[].class) {
877 result = "Regular Expressions";
878 }
879 else if (fieldClass == SeverityLevel.class) {
880 result = "Severity";
881 }
882 else if (fieldClass == Scope.class) {
883 result = "Scope";
884 }
885 else if (fieldClass == ElementStyle.class) {
886 result = "Element Style";
887 }
888 else if (fieldClass == ClosingParens.class) {
889 result = "Closing Parens";
890 }
891 else if (fieldClass == TrailingArrayComma.class) {
892 result = "Trailing Comma";
893 }
894 else if (fieldClass == PadOption.class) {
895 result = "Pad Policy";
896 }
897 else if (fieldClass == WrapOption.class) {
898 result = "Wrap Operator Policy";
899 }
900 else if (fieldClass == BlockOption.class) {
901 result = "Block Policy";
902 }
903 else if (fieldClass == LeftCurlyOption.class) {
904 result = "Left Curly Brace Policy";
905 }
906 else if (fieldClass == RightCurlyOption.class) {
907 result = "Right Curly Brace Policy";
908 }
909 else if (fieldClass == LineSeparatorOption.class) {
910 result = "Line Separator Policy";
911 }
912 else if (fieldClass == ImportOrderOption.class) {
913 result = "Import Order Policy";
914 }
915 else if (fieldClass == AccessModifier[].class) {
916 result = "Access Modifier Set";
917 }
918 else if ("PropertyCacheFile".equals(fieldClass.getSimpleName())) {
919 result = "File";
920 }
921 else {
922 Assert.fail("Unknown property type: " + fieldClass.getSimpleName());
923 }
924
925 if ("SuppressWarningsHolder".equals(instanceName)) {
926 result = result + " in a format of comma separated attribute=value entries. The "
927 + "attribute is the fully qualified name of the Check and value is its alias.";
928 }
929
930 return result;
931 }
932
933
934
935
936
937
938
939
940
941
942
943 private static String getModulePropertyExpectedValue(String sectionName, String propertyName,
944 Field field, Class<?> fieldClass, Object instance) throws Exception {
945 String result = null;
946
947 if (field != null) {
948 Object value = field.get(instance);
949
950
951 if ("Checker".equals(sectionName) && "localeCountry".equals(propertyName)) {
952 result = "default locale country for the Java Virtual Machine";
953 }
954 else if ("Checker".equals(sectionName) && "localeLanguage".equals(propertyName)) {
955 result = "default locale language for the Java Virtual Machine";
956 }
957 else if ("Checker".equals(sectionName) && "charset".equals(propertyName)) {
958 result = "System property \"file.encoding\"";
959 }
960 else if ("charset".equals(propertyName)) {
961 result = "the charset property of the parent Checker module";
962 }
963 else if ("PropertyCacheFile".equals(fieldClass.getSimpleName())) {
964 result = "null (no cache file)";
965 }
966 else if (fieldClass == boolean.class) {
967 result = value.toString();
968 }
969 else if (fieldClass == int.class) {
970 if (value.equals(Integer.MAX_VALUE)) {
971 result = "java.lang.Integer.MAX_VALUE";
972 }
973 else {
974 result = value.toString();
975 }
976 }
977 else if (fieldClass == int[].class) {
978 if (value instanceof Collection) {
979 final Collection<?> collection = (Collection<?>) value;
980 final int[] newArray = new int[collection.size()];
981 final Iterator<?> iterator = collection.iterator();
982 int index = 0;
983
984 while (iterator.hasNext()) {
985 newArray[index] = (Integer) iterator.next();
986 index++;
987 }
988
989 value = newArray;
990 }
991
992 if (isPropertyTokenType(sectionName, propertyName)) {
993 result = "";
994 boolean first = true;
995
996 if (value instanceof BitSet) {
997 final BitSet list = (BitSet) value;
998 final StringBuilder sb = new StringBuilder(20);
999
1000 for (int i = 0; i < list.size(); i++) {
1001 if (list.get(i)) {
1002 if (first) {
1003 first = false;
1004 }
1005 else {
1006 sb.append(", ");
1007 }
1008
1009 sb.append(TokenUtil.getTokenName(i));
1010 }
1011 }
1012
1013 result = sb.toString();
1014 }
1015 else if (value != null) {
1016 final StringBuilder sb = new StringBuilder(20);
1017
1018 for (int i = 0; i < Array.getLength(value); i++) {
1019 if (first) {
1020 first = false;
1021 }
1022 else {
1023 sb.append(", ");
1024 }
1025
1026 sb.append(TokenUtil.getTokenName((int) Array.get(value, i)));
1027 }
1028
1029 result = sb.toString();
1030 }
1031 }
1032 else {
1033 result = Arrays.toString((int[]) value).replace("[", "").replace("]", "");
1034
1035 if (result.isEmpty()) {
1036 result = "{}";
1037 }
1038 }
1039 }
1040 else if (fieldClass == double[].class) {
1041 result = Arrays.toString((double[]) value).replace("[", "").replace("]", "")
1042 .replace(".0", "");
1043 if (result.isEmpty()) {
1044 result = "{}";
1045 }
1046 }
1047 else if (fieldClass == String[].class) {
1048 if (value instanceof Collection) {
1049 final Collection<?> collection = (Collection<?>) value;
1050 final String[] newArray = new String[collection.size()];
1051 final Iterator<?> iterator = collection.iterator();
1052 int index = 0;
1053
1054 while (iterator.hasNext()) {
1055 final Object next = iterator.next();
1056 newArray[index] = (String) next;
1057 index++;
1058 }
1059
1060 value = newArray;
1061 }
1062
1063 if (value != null && Array.getLength(value) > 0) {
1064 if (Array.get(value, 0) instanceof Number) {
1065 final String[] newArray = new String[Array.getLength(value)];
1066
1067 for (int i = 0; i < newArray.length; i++) {
1068 newArray[i] = TokenUtil.getTokenName(((Number) Array.get(value, i))
1069 .intValue());
1070 }
1071
1072 value = newArray;
1073 }
1074
1075 result = Arrays.toString((Object[]) value).replace("[", "")
1076 .replace("]", "");
1077 }
1078 else {
1079 result = "";
1080 }
1081
1082 if (result.isEmpty()) {
1083 if ("fileExtensions".equals(propertyName)) {
1084 result = "all files";
1085 }
1086 else {
1087 result = "{}";
1088 }
1089 }
1090 }
1091 else if (fieldClass == URI.class || fieldClass == String.class) {
1092 if (value != null) {
1093 result = '"' + value.toString() + '"';
1094 }
1095 }
1096 else if (fieldClass == Pattern.class) {
1097 if (value != null) {
1098 result = '"' + value.toString().replace("\n", "\\n").replace("\t", "\\t")
1099 .replace("\r", "\\r").replace("\f", "\\f") + '"';
1100
1101 if ("\"^$\"".equals(result)) {
1102 result += " (empty)";
1103 }
1104 }
1105 }
1106 else if (fieldClass == Pattern[].class) {
1107 if (value instanceof Collection) {
1108 final Collection<?> collection = (Collection<?>) value;
1109 final Pattern[] newArray = new Pattern[collection.size()];
1110 final Iterator<?> iterator = collection.iterator();
1111 int index = 0;
1112
1113 while (iterator.hasNext()) {
1114 final Object next = iterator.next();
1115 newArray[index] = (Pattern) next;
1116 index++;
1117 }
1118
1119 value = newArray;
1120 }
1121
1122 if (value != null && Array.getLength(value) > 0) {
1123 final String[] newArray = new String[Array.getLength(value)];
1124
1125 for (int i = 0; i < newArray.length; i++) {
1126 newArray[i] = ((Pattern) Array.get(value, i)).pattern();
1127 }
1128
1129 result = Arrays.toString(newArray).replace("[", "")
1130 .replace("]", "");
1131 }
1132 else {
1133 result = "";
1134 }
1135
1136 if (result.isEmpty()) {
1137 result = "{}";
1138 }
1139 }
1140 else if (fieldClass.isEnum()) {
1141 if (value != null) {
1142 result = value.toString().toLowerCase(Locale.ENGLISH);
1143 }
1144 }
1145 else if (fieldClass == AccessModifier[].class) {
1146 result = Arrays.toString((Object[]) value).replace("[", "").replace("]", "");
1147 }
1148 else {
1149 Assert.fail("Unknown property type: " + fieldClass.getSimpleName());
1150 }
1151
1152 if (result == null) {
1153 result = "null";
1154 }
1155 }
1156
1157 return result;
1158 }
1159
1160
1161
1162
1163
1164
1165
1166
1167 private static boolean isPropertyTokenType(String sectionName, String propertyName) {
1168 return "AtclauseOrder".equals(sectionName) && "target".equals(propertyName)
1169 || "IllegalType".equals(sectionName) && "memberModifiers".equals(propertyName)
1170 || "MagicNumber".equals(sectionName)
1171 && "constantWaiverParentToken".equals(propertyName)
1172 || "MultipleStringLiterals".equals(sectionName)
1173 && "ignoreOccurrenceContext".equals(propertyName)
1174 || "DescendantToken".equals(sectionName) && "limitedTokens".equals(propertyName);
1175 }
1176
1177 private static Field getField(Class<?> clss, String propertyName) {
1178 Field result = null;
1179
1180 if (clss != null) {
1181 try {
1182 result = clss.getDeclaredField(propertyName);
1183 result.setAccessible(true);
1184 }
1185 catch (NoSuchFieldException ignored) {
1186 result = getField(clss.getSuperclass(), propertyName);
1187 }
1188 }
1189
1190 return result;
1191 }
1192
1193 private static Class<?> getFieldClass(String fileName, String sectionName, Object instance,
1194 Field field, String propertyName) throws Exception {
1195 Class<?> result = null;
1196
1197 if (field != null) {
1198 result = field.getType();
1199 }
1200 if (result == null) {
1201 Assert.assertTrue(
1202 fileName + " section '" + sectionName + "' could not find field "
1203 + propertyName,
1204 PROPERTIES_ALLOWED_GET_TYPES_FROM_METHOD.contains(sectionName + "."
1205 + propertyName));
1206
1207 final PropertyDescriptor descriptor = PropertyUtils.getPropertyDescriptor(instance,
1208 propertyName);
1209 result = descriptor.getPropertyType();
1210 }
1211 if (result == List.class || result == Set.class) {
1212 final ParameterizedType type = (ParameterizedType) field.getGenericType();
1213 final Class<?> parameterClass = (Class<?>) type.getActualTypeArguments()[0];
1214
1215 if (parameterClass == Integer.class) {
1216 result = int[].class;
1217 }
1218 else if (parameterClass == String.class) {
1219 result = String[].class;
1220 }
1221 else if (parameterClass == Pattern.class) {
1222 result = Pattern[].class;
1223 }
1224 else {
1225 Assert.fail("Unknown parameterized type: " + parameterClass.getSimpleName());
1226 }
1227 }
1228 else if (result == BitSet.class) {
1229 result = int[].class;
1230 }
1231
1232 return result;
1233 }
1234
1235 private static void validateErrorSection(String fileName, String sectionName, Node subSection,
1236 Object instance) throws Exception {
1237 final Class<?> clss = instance.getClass();
1238 final Set<Field> fields = CheckUtil.getCheckMessages(clss);
1239 final Set<String> list = new TreeSet<>();
1240
1241 for (Field field : fields) {
1242
1243 if (!field.isAccessible()) {
1244 field.setAccessible(true);
1245 }
1246
1247 list.add(field.get(null).toString());
1248 }
1249
1250 final StringBuilder expectedText = new StringBuilder(120);
1251
1252 for (String s : list) {
1253 expectedText.append(s);
1254 expectedText.append('\n');
1255 }
1256
1257 if (expectedText.length() > 0) {
1258 expectedText.append("All messages can be customized if the default message doesn't "
1259 + "suit you.\nPlease see the documentation to learn how to.");
1260 }
1261
1262 if (subSection == null) {
1263 Assert.assertEquals(fileName + " section '" + sectionName
1264 + "' should have the expected error keys", "", expectedText.toString());
1265 }
1266 else {
1267 Assert.assertEquals(fileName + " section '" + sectionName
1268 + "' should have the expected error keys", expectedText.toString().trim(),
1269 subSection.getTextContent().replaceAll("\n\\s+", "\n").trim());
1270
1271 for (Node node : XmlUtil.findChildElementsByTag(subSection, "a")) {
1272 final String url = node.getAttributes().getNamedItem("href").getTextContent();
1273 final String linkText = node.getTextContent().trim();
1274 final String expectedUrl;
1275
1276 if ("see the documentation".equals(linkText)) {
1277 expectedUrl = "config.html#Custom_messages";
1278 }
1279 else {
1280 expectedUrl = "https://github.com/search?q="
1281 + "path%3Asrc%2Fmain%2Fresources%2F"
1282 + clss.getPackage().getName().replace(".", "%2F")
1283 + "+filename%3Amessages*.properties+repo%3Acheckstyle%2Fcheckstyle+%22"
1284 + linkText + "%22";
1285 }
1286
1287 Assert.assertEquals(fileName + " section '" + sectionName
1288 + "' should have matching url for '" + linkText + "'", expectedUrl, url);
1289 }
1290 }
1291 }
1292
1293 private static void validateUsageExample(String fileName, String sectionName, Node subSection) {
1294 final String text = subSection.getTextContent().replace("Checkstyle Style", "")
1295 .replace("Google Style", "").replace("Sun Style", "").trim();
1296
1297 Assert.assertTrue(fileName + " section '" + sectionName
1298 + "' has unknown text in 'Example of Usage': " + text, text.isEmpty());
1299
1300 boolean hasCheckstyle = false;
1301 boolean hasGoogle = false;
1302 boolean hasSun = false;
1303
1304 for (Node node : XmlUtil.findChildElementsByTag(subSection, "a")) {
1305 final String url = node.getAttributes().getNamedItem("href").getTextContent();
1306 final String linkText = node.getTextContent().trim();
1307 String expectedUrl = null;
1308
1309 if ("Checkstyle Style".equals(linkText)) {
1310 hasCheckstyle = true;
1311 expectedUrl = "https://github.com/search?q="
1312 + "path%3Aconfig+filename%3Acheckstyle_checks.xml+"
1313 + "repo%3Acheckstyle%2Fcheckstyle+" + sectionName;
1314 }
1315 else if ("Google Style".equals(linkText)) {
1316 hasGoogle = true;
1317 expectedUrl = "https://github.com/search?q="
1318 + "path%3Asrc%2Fmain%2Fresources+filename%3Agoogle_checks.xml+"
1319 + "repo%3Acheckstyle%2Fcheckstyle+"
1320 + sectionName;
1321
1322 Assert.assertTrue(fileName + " section '" + sectionName
1323 + "' should be in google_checks.xml or not reference 'Google Style'",
1324 GOOGLE_MODULES.contains(sectionName));
1325 }
1326 else if ("Sun Style".equals(linkText)) {
1327 hasSun = true;
1328 expectedUrl = "https://github.com/search?q="
1329 + "path%3Asrc%2Fmain%2Fresources+filename%3Asun_checks.xml+"
1330 + "repo%3Acheckstyle%2Fcheckstyle+"
1331 + sectionName;
1332
1333 Assert.assertTrue(fileName + " section '" + sectionName
1334 + "' should be in sun_checks.xml or not reference 'Sun Style'",
1335 SUN_MODULES.contains(sectionName));
1336 }
1337
1338 Assert.assertEquals(fileName + " section '" + sectionName
1339 + "' should have matching url", expectedUrl, url);
1340 }
1341
1342 Assert.assertTrue(fileName + " section '" + sectionName
1343 + "' should have a checkstyle section", hasCheckstyle);
1344 Assert.assertTrue(fileName + " section '" + sectionName
1345 + "' should have a google section since it is in it's config", hasGoogle
1346 || !GOOGLE_MODULES.contains(sectionName));
1347 Assert.assertTrue(fileName + " section '" + sectionName
1348 + "' should have a sun section since it is in it's config",
1349 hasSun || !SUN_MODULES.contains(sectionName));
1350 }
1351
1352 private static void validatePackageSection(String fileName, String sectionName,
1353 Node subSection, Object instance) {
1354 Assert.assertEquals(fileName + " section '" + sectionName
1355 + "' should have matching package", instance.getClass().getPackage().getName(),
1356 subSection.getTextContent().trim());
1357 }
1358
1359 private static void validateParentSection(String fileName, String sectionName,
1360 Node subSection) {
1361 final String expected;
1362
1363 if (!"TreeWalker".equals(sectionName) && hasParentModule(sectionName)) {
1364 expected = "TreeWalker";
1365 }
1366 else {
1367 expected = "Checker";
1368 }
1369
1370 Assert.assertEquals(
1371 fileName + " section '" + sectionName + "' should have matching parent",
1372 expected, subSection
1373 .getTextContent().trim());
1374 }
1375
1376 private static boolean hasParentModule(String sectionName) {
1377 final String search = "\"" + sectionName + "\"";
1378 boolean result = true;
1379
1380 for (String find : XML_FILESET_LIST) {
1381 if (find.contains(search)) {
1382 result = false;
1383 break;
1384 }
1385 }
1386
1387 return result;
1388 }
1389
1390 private static Set<String> getProperties(Class<?> clss) {
1391 final Set<String> result = new TreeSet<>();
1392 final PropertyDescriptor[] map = PropertyUtils.getPropertyDescriptors(clss);
1393
1394 for (PropertyDescriptor p : map) {
1395 if (p.getWriteMethod() != null) {
1396 result.add(p.getName());
1397 }
1398 }
1399
1400 return result;
1401 }
1402
1403 @Test
1404 public void testAllStyleRules() throws Exception {
1405 for (Path path : XdocUtil.getXdocsStyleFilePaths(XdocUtil.getXdocsFilePaths())) {
1406 final String fileName = path.getFileName().toString();
1407 final String styleName = fileName.substring(0, fileName.lastIndexOf('_'));
1408 final String input = new String(Files.readAllBytes(path), UTF_8);
1409 final Document document = XmlUtil.getRawXml(fileName, input, input);
1410 final NodeList sources = document.getElementsByTagName("tr");
1411
1412 final Set<String> styleChecks;
1413 switch (styleName) {
1414 case "google":
1415 styleChecks = new HashSet<>(GOOGLE_MODULES);
1416 break;
1417
1418 case "sun":
1419 styleChecks = new HashSet<>(SUN_MODULES);
1420 styleChecks.removeAll(IGNORED_SUN_MODULES);
1421 break;
1422
1423 default:
1424 Assert.fail("Missing modules list for style file '" + fileName + "'");
1425 styleChecks = null;
1426 }
1427
1428 String lastRuleName = null;
1429 String[] lastRuleNumberParts = null;
1430
1431 for (int position = 0; position < sources.getLength(); position++) {
1432 final Node row = sources.item(position);
1433 final List<Node> columns = new ArrayList<>(
1434 XmlUtil.findChildElementsByTag(row, "td"));
1435
1436 if (columns.isEmpty()) {
1437 continue;
1438 }
1439
1440 final String ruleName = columns.get(1).getTextContent().trim();
1441 lastRuleNumberParts = validateRuleNameOrder(
1442 fileName, lastRuleName, lastRuleNumberParts, ruleName);
1443
1444 if (!"--".equals(ruleName)) {
1445 validateStyleAnchors(XmlUtil.findChildElementsByTag(columns.get(0), "a"),
1446 fileName, ruleName);
1447 }
1448
1449 validateStyleModules(XmlUtil.findChildElementsByTag(columns.get(2), "a"),
1450 XmlUtil.findChildElementsByTag(columns.get(3), "a"), styleChecks, styleName,
1451 ruleName);
1452
1453 lastRuleName = ruleName;
1454 }
1455
1456
1457 styleChecks.remove("BeforeExecutionExclusionFileFilter");
1458 styleChecks.remove("TreeWalker");
1459 styleChecks.remove("Checker");
1460
1461 Assert.assertTrue(fileName + " requires the following check(s) to appear: "
1462 + styleChecks, styleChecks.isEmpty());
1463 }
1464 }
1465
1466 private static String[] validateRuleNameOrder(String fileName, String lastRuleName,
1467 String[] lastRuleNumberParts, String ruleName) {
1468 final String[] ruleNumberParts = ruleName.split(" ", 2)[0].split("\\.");
1469
1470 if (lastRuleName != null) {
1471 final int ruleNumberPartsAmount = ruleNumberParts.length;
1472 final int lastRuleNumberPartsAmount = lastRuleNumberParts.length;
1473 final String outOfOrderReason = fileName + " rule '" + ruleName
1474 + "' is out of order compared to '" + lastRuleName + "'";
1475 boolean lastRuleNumberPartWasEqual = false;
1476 int partIndex;
1477 for (partIndex = 0; partIndex < ruleNumberPartsAmount; partIndex++) {
1478 if (lastRuleNumberPartsAmount <= partIndex) {
1479
1480
1481 break;
1482 }
1483
1484 final String ruleNumberPart = ruleNumberParts[partIndex];
1485 final String lastRuleNumberPart = lastRuleNumberParts[partIndex];
1486 final boolean ruleNumberPartsAreNumeric = IntStream.concat(
1487 ruleNumberPart.chars(),
1488 lastRuleNumberPart.chars()
1489 ).allMatch(Character::isDigit);
1490
1491 if (ruleNumberPartsAreNumeric) {
1492 final int numericRuleNumberPart = parseInt(ruleNumberPart);
1493 final int numericLastRuleNumberPart = parseInt(lastRuleNumberPart);
1494 Assert.assertThat(
1495 outOfOrderReason,
1496 numericRuleNumberPart < numericLastRuleNumberPart,
1497 describedAs("'%0' should not be less than '%1'",
1498 is(false),
1499 numericRuleNumberPart, numericLastRuleNumberPart));
1500 }
1501 else {
1502 Assert.assertThat(
1503 outOfOrderReason,
1504 ruleNumberPart.compareToIgnoreCase(lastRuleNumberPart) < 0,
1505 describedAs("'%0' should not be less than '%1'",
1506 is(false),
1507 ruleNumberPart, lastRuleNumberPart));
1508 }
1509 lastRuleNumberPartWasEqual = ruleNumberPart.equalsIgnoreCase(lastRuleNumberPart);
1510 if (!lastRuleNumberPartWasEqual) {
1511
1512
1513 break;
1514 }
1515 }
1516 if (ruleNumberPartsAmount == partIndex && lastRuleNumberPartWasEqual) {
1517 if (lastRuleNumberPartsAmount == partIndex) {
1518 Assert.fail(fileName + " rule '" + ruleName + "' and rule '"
1519 + lastRuleName + "' have the same rule number");
1520 }
1521 else {
1522 Assert.fail(outOfOrderReason);
1523 }
1524 }
1525 }
1526
1527 return ruleNumberParts;
1528 }
1529
1530 private static void validateStyleAnchors(Set<Node> anchors, String fileName, String ruleName) {
1531 Assert.assertEquals(fileName + " rule '" + ruleName + "' must have two row anchors", 2,
1532 anchors.size());
1533
1534 final int space = ruleName.indexOf(' ');
1535 Assert.assertTrue(fileName + " rule '" + ruleName
1536 + "' must have have a space between the rule's number and the rule's name",
1537 space != -1);
1538
1539 final String ruleNumber = ruleName.substring(0, space);
1540
1541 int position = 1;
1542
1543 for (Node anchor : anchors) {
1544 final String actualUrl;
1545 final String expectedUrl;
1546
1547 if (position == 1) {
1548 actualUrl = anchor.getAttributes().getNamedItem("name").getTextContent();
1549 expectedUrl = ruleNumber;
1550 }
1551 else {
1552 actualUrl = anchor.getAttributes().getNamedItem("href").getTextContent();
1553 expectedUrl = "#" + ruleNumber;
1554 }
1555
1556 Assert.assertEquals(fileName + " rule '" + ruleName + "' anchor " + position
1557 + " should have matching name/url", expectedUrl, actualUrl);
1558
1559 position++;
1560 }
1561 }
1562
1563 private static void validateStyleModules(Set<Node> checks, Set<Node> configs,
1564 Set<String> styleChecks, String styleName, String ruleName) {
1565 final Iterator<Node> itrChecks = checks.iterator();
1566 final Iterator<Node> itrConfigs = configs.iterator();
1567
1568 while (itrChecks.hasNext()) {
1569 final Node module = itrChecks.next();
1570 final String moduleName = module.getTextContent().trim();
1571
1572 if (!module.getAttributes().getNamedItem("href").getTextContent()
1573 .startsWith("config_")) {
1574 continue;
1575 }
1576
1577 Assert.assertTrue(styleName + "_style.xml rule '" + ruleName + "' module '" + moduleName
1578 + "' shouldn't end with 'Check'", !moduleName.endsWith("Check"));
1579
1580 styleChecks.remove(moduleName);
1581
1582 for (String configName : new String[] {"config", "test"}) {
1583 Node config = null;
1584
1585 try {
1586 config = itrConfigs.next();
1587 }
1588 catch (NoSuchElementException ignore) {
1589 Assert.fail(styleName + "_style.xml rule '" + ruleName + "' module '"
1590 + moduleName + "' is missing the config link: " + configName);
1591 }
1592
1593 Assert.assertEquals(styleName + "_style.xml rule '" + ruleName + "' module '"
1594 + moduleName + "' has mismatched config/test links", configName,
1595 config.getTextContent().trim());
1596
1597 final String configUrl = config.getAttributes().getNamedItem("href")
1598 .getTextContent();
1599
1600 if ("config".equals(configName)) {
1601 final String expectedUrl = "https://github.com/search?q="
1602 + "path%3Asrc%2Fmain%2Fresources+filename%3A" + styleName
1603 + "_checks.xml+repo%3Acheckstyle%2Fcheckstyle+" + moduleName;
1604
1605 Assert.assertEquals(styleName + "_style.xml rule '" + ruleName + "' module '"
1606 + moduleName + "' should have matching " + configName + " url",
1607 expectedUrl, configUrl);
1608 }
1609 else if ("test".equals(configName)) {
1610 Assert.assertTrue(styleName + "_style.xml rule '" + ruleName + "' module '"
1611 + moduleName + "' should have matching " + configName + " url",
1612 configUrl.startsWith("https://github.com/checkstyle/checkstyle/"
1613 + "blob/master/src/it/java/com/" + styleName
1614 + "/checkstyle/test/"));
1615 Assert.assertTrue(styleName + "_style.xml rule '" + ruleName + "' module '"
1616 + moduleName + "' should have matching " + configName + " url",
1617 configUrl.endsWith("/" + moduleName + "Test.java"));
1618
1619 Assert.assertTrue(styleName + "_style.xml rule '" + ruleName + "' module '"
1620 + moduleName + "' should have a test that exists",
1621 new File(configUrl.substring(53)
1622 .replace('/', File.separatorChar)).exists());
1623 }
1624 }
1625 }
1626
1627 Assert.assertFalse(styleName + "_style.xml rule '" + ruleName + "' has too many configs",
1628 itrConfigs.hasNext());
1629 }
1630
1631 }