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 import static java.lang.Integer.parseInt;
24
25 import java.beans.PropertyDescriptor;
26 import java.io.File;
27 import java.io.IOException;
28 import java.io.StringReader;
29 import java.lang.reflect.Array;
30 import java.lang.reflect.Field;
31 import java.lang.reflect.ParameterizedType;
32 import java.net.URI;
33 import java.nio.file.Files;
34 import java.nio.file.Path;
35 import java.nio.file.Paths;
36 import java.util.ArrayList;
37 import java.util.Arrays;
38 import java.util.BitSet;
39 import java.util.Collection;
40 import java.util.Collections;
41 import java.util.HashMap;
42 import java.util.HashSet;
43 import java.util.Iterator;
44 import java.util.List;
45 import java.util.Locale;
46 import java.util.Map;
47 import java.util.NoSuchElementException;
48 import java.util.Optional;
49 import java.util.Properties;
50 import java.util.Set;
51 import java.util.TreeSet;
52 import java.util.regex.Matcher;
53 import java.util.regex.Pattern;
54 import java.util.stream.Collectors;
55 import java.util.stream.IntStream;
56 import java.util.stream.Stream;
57
58 import org.apache.commons.beanutils.PropertyUtils;
59 import org.junit.jupiter.api.BeforeAll;
60 import org.junit.jupiter.api.Test;
61 import org.junit.jupiter.api.io.TempDir;
62 import org.w3c.dom.Document;
63 import org.w3c.dom.Node;
64 import org.w3c.dom.NodeList;
65 import org.xml.sax.InputSource;
66
67 import com.puppycrawl.tools.checkstyle.Checker;
68 import com.puppycrawl.tools.checkstyle.ConfigurationLoader;
69 import com.puppycrawl.tools.checkstyle.ConfigurationLoader.IgnoredModulesOptions;
70 import com.puppycrawl.tools.checkstyle.ModuleFactory;
71 import com.puppycrawl.tools.checkstyle.PropertiesExpander;
72 import com.puppycrawl.tools.checkstyle.XdocsPropertyType;
73 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
74 import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;
75 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
76 import com.puppycrawl.tools.checkstyle.api.Configuration;
77 import com.puppycrawl.tools.checkstyle.checks.javadoc.AbstractJavadocCheck;
78 import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption;
79 import com.puppycrawl.tools.checkstyle.internal.utils.CheckUtil;
80 import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
81 import com.puppycrawl.tools.checkstyle.internal.utils.XdocGenerator;
82 import com.puppycrawl.tools.checkstyle.internal.utils.XdocUtil;
83 import com.puppycrawl.tools.checkstyle.internal.utils.XmlUtil;
84 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
85
86
87
88
89
90
91
92 public class XdocsPagesTest {
93 private static final Path SITE_PATH = Path.of("src/site/site.xml");
94
95 private static final Path AVAILABLE_CHECKS_PATH = Path.of("src/site/xdoc/checks.xml");
96 private static final String LINK_TEMPLATE =
97 "(?s).*<a href=\"[^\"]+#%1$s\">([\\r\\n\\s])*%1$s([\\r\\n\\s])*</a>.*";
98
99 private static final Pattern VERSION = Pattern.compile("\\d+\\.\\d+(\\.\\d+)?");
100
101 private static final Pattern DESCRIPTION_VERSION = Pattern
102 .compile("^Since Checkstyle \\d+\\.\\d+(\\.\\d+)?");
103
104 private static final List<String> XML_FILESET_LIST = List.of(
105 "TreeWalker",
106 "name=\"Checker\"",
107 "name=\"Header\"",
108 "name=\"LineLength\"",
109 "name=\"Translation\"",
110 "name=\"SeverityMatchFilter\"",
111 "name=\"SuppressWithNearbyTextFilter\"",
112 "name=\"SuppressWithPlainTextCommentFilter\"",
113 "name=\"SuppressionFilter\"",
114 "name=\"SuppressionSingleFilter\"",
115 "name=\"SuppressWarningsFilter\"",
116 "name=\"BeforeExecutionExclusionFileFilter\"",
117 "name=\"RegexpHeader\"",
118 "name=\"RegexpOnFilename\"",
119 "name=\"RegexpSingleline\"",
120 "name=\"RegexpMultiline\"",
121 "name=\"JavadocPackage\"",
122 "name=\"NewlineAtEndOfFile\"",
123 "name=\"OrderedProperties\"",
124 "name=\"UniqueProperties\"",
125 "name=\"FileLength\"",
126 "name=\"FileTabCharacter\""
127 );
128
129 private static final Set<String> CHECK_PROPERTIES = getProperties(AbstractCheck.class);
130 private static final Set<String> JAVADOC_CHECK_PROPERTIES =
131 getProperties(AbstractJavadocCheck.class);
132 private static final Set<String> FILESET_PROPERTIES = getProperties(AbstractFileSetCheck.class);
133
134 private static final Set<String> UNDOCUMENTED_PROPERTIES = Set.of(
135 "Checker.classLoader",
136 "Checker.classloader",
137 "Checker.moduleClassLoader",
138 "Checker.moduleFactory",
139 "TreeWalker.classLoader",
140 "TreeWalker.moduleFactory",
141 "TreeWalker.cacheFile",
142 "TreeWalker.upChild",
143 "SuppressWithNearbyCommentFilter.fileContents",
144 "SuppressionCommentFilter.fileContents"
145 );
146
147 private static final Set<String> PROPERTIES_ALLOWED_GET_TYPES_FROM_METHOD = Set.of(
148
149 "SuppressWarningsHolder.aliasList",
150
151 "Header.header",
152 "RegexpHeader.header",
153
154 "RedundantModifier.jdkVersion",
155
156 "CustomImportOrder.customImportOrderRules"
157 );
158
159 private static final Set<String> SUN_MODULES = Collections.unmodifiableSet(
160 CheckUtil.getConfigSunStyleModules());
161
162
163
164 private static final Set<String> IGNORED_SUN_MODULES = Set.of(
165 "ArrayTypeStyle",
166 "AvoidNestedBlocks",
167 "AvoidStarImport",
168 "ConstantName",
169 "DesignForExtension",
170 "EmptyBlock",
171 "EmptyForIteratorPad",
172 "EmptyStatement",
173 "EqualsHashCode",
174 "FileLength",
175 "FileTabCharacter",
176 "FinalClass",
177 "FinalParameters",
178 "GenericWhitespace",
179 "HiddenField",
180 "HideUtilityClassConstructor",
181 "IllegalImport",
182 "IllegalInstantiation",
183 "InnerAssignment",
184 "InterfaceIsType",
185 "JavadocMethod",
186 "JavadocPackage",
187 "JavadocStyle",
188 "JavadocType",
189 "JavadocVariable",
190 "LeftCurly",
191 "LineLength",
192 "LocalFinalVariableName",
193 "LocalVariableName",
194 "MagicNumber",
195 "MemberName",
196 "MethodLength",
197 "MethodName",
198 "MethodParamPad",
199 "MissingJavadocMethod",
200 "MissingSwitchDefault",
201 "ModifierOrder",
202 "NeedBraces",
203 "NewlineAtEndOfFile",
204 "NoWhitespaceAfter",
205 "NoWhitespaceBefore",
206 "OperatorWrap",
207 "PackageName",
208 "ParameterName",
209 "ParameterNumber",
210 "ParenPad",
211 "RedundantImport",
212 "RedundantModifier",
213 "RegexpSingleline",
214 "RightCurly",
215 "SimplifyBooleanExpression",
216 "SimplifyBooleanReturn",
217 "StaticVariableName",
218 "TodoComment",
219 "Translation",
220 "TypecastParenPad",
221 "TypeName",
222 "UnusedImports",
223 "UpperEll",
224 "VisibilityModifier",
225 "WhitespaceAfter",
226 "WhitespaceAround"
227 );
228
229 private static final Set<String> GOOGLE_MODULES = Collections.unmodifiableSet(
230 CheckUtil.getConfigGoogleStyleModules());
231
232 private static final Set<String> NON_MODULE_XDOC = Set.of(
233 "config_system_properties.xml",
234 "sponsoring.xml",
235 "consulting.xml",
236 "index.xml",
237 "extending.xml",
238 "contributing.xml",
239 "running.xml",
240 "checks.xml",
241 "property_types.xml",
242 "google_style.xml",
243 "sun_style.xml",
244 "style_configs.xml",
245 "writingfilters.xml",
246 "writingfilefilters.xml",
247 "eclipse.xml",
248 "netbeans.xml",
249 "idea.xml",
250 "beginning_development.xml",
251 "writingchecks.xml",
252 "config.xml",
253 "report_issue.xml"
254 );
255
256 private static final String NAMES_MUST_BE_IN_ALPHABETICAL_ORDER_SITE_PATH =
257 " names must be in alphabetical order at " + SITE_PATH;
258
259 @TempDir
260 private static File temporaryFolder;
261
262
263
264
265
266
267
268
269 @BeforeAll
270 public static void generateXdocContent() throws Exception {
271 XdocGenerator.generateXdocContent(temporaryFolder);
272 }
273
274 @Test
275 public void testAllChecksPresentOnAvailableChecksPage() throws Exception {
276 final String availableChecks = Files.readString(AVAILABLE_CHECKS_PATH);
277
278 CheckUtil.getSimpleNames(CheckUtil.getCheckstyleChecks())
279 .stream()
280 .filter(checkName -> {
281 return !"JavadocMetadataScraper".equals(checkName)
282 && !"ClassAndPropertiesSettersJavadocScraper".equals(checkName);
283 })
284 .forEach(checkName -> {
285 if (!isPresent(availableChecks, checkName)) {
286 assertWithMessage(
287 checkName + " is not correctly listed on Available Checks page"
288 + " - add it to " + AVAILABLE_CHECKS_PATH).fail();
289 }
290 });
291 }
292
293 private static boolean isPresent(String availableChecks, String checkName) {
294 final String linkPattern = String.format(Locale.ROOT, LINK_TEMPLATE, checkName);
295 return availableChecks.matches(linkPattern);
296 }
297
298 @Test
299 public void testAllConfigsHaveLinkInSite() throws Exception {
300 final String siteContent = Files.readString(SITE_PATH);
301
302 for (Path path : XdocUtil.getXdocsConfigFilePaths(XdocUtil.getXdocsFilePaths())) {
303 final String expectedFile = path.toString()
304 .replace(".xml", ".html")
305 .replaceAll("\\\\", "/")
306 .replaceAll("src[\\\\/]site[\\\\/]xdoc[\\\\/]", "");
307 final boolean isConfigHtmlFile = Pattern.matches("config_[a-z]+.html", expectedFile);
308 final boolean isChecksIndexHtmlFile = "checks/index.html".equals(expectedFile);
309 final boolean isOldReleaseNotes = path.toString().contains("releasenotes_");
310 final boolean isInnerPage = "report_issue.html".equals(expectedFile);
311
312 if (!isConfigHtmlFile && !isChecksIndexHtmlFile
313 && !isOldReleaseNotes && !isInnerPage) {
314 final String expectedLink = String.format(Locale.ROOT, "href=\"%s\"", expectedFile);
315 assertWithMessage("Expected to find link to '" + expectedLink + "' in " + SITE_PATH)
316 .that(siteContent)
317 .contains(expectedLink);
318 }
319 }
320 }
321
322 @Test
323 public void testAllChecksPageInSyncWithChecksSummaries() throws Exception {
324 final Pattern endOfSentence = Pattern.compile("(.*?\\.)\\s", Pattern.DOTALL);
325 final Map<String, String> summaries = readSummaries();
326
327 for (Path path : XdocUtil.getXdocsConfigFilePaths(XdocUtil.getXdocsFilePaths())) {
328 final String fileName = path.getFileName().toString();
329 if (isNonModulePage(fileName)
330 || path.toString().contains("filefilters")
331 || path.toString().contains("filters")) {
332 continue;
333 }
334
335 final String input = Files.readString(path);
336 final Document document = XmlUtil.getRawXml(fileName, input, input);
337 final NodeList sources = document.getElementsByTagName("subsection");
338
339 for (int position = 0; position < sources.getLength(); position++) {
340 final Node section = sources.item(position);
341 final String sectionName = XmlUtil.getNameAttributeOfNode(section);
342 if (!"Description".equals(sectionName)) {
343 continue;
344 }
345
346 final String checkName = XmlUtil.getNameAttributeOfNode(section.getParentNode());
347 final Matcher matcher = endOfSentence.matcher(section.getTextContent());
348 assertWithMessage(
349 "The first sentence of the \"Description\" subsection for the check "
350 + checkName + " in the file \"" + fileName + "\" should end with a period")
351 .that(matcher.find())
352 .isTrue();
353 final String firstSentence = XmlUtil.sanitizeXml(matcher.group(1));
354 assertWithMessage("The summary for check " + checkName
355 + " in the file \"" + AVAILABLE_CHECKS_PATH + "\""
356 + " should match the first sentence of the \"Description\" subsection"
357 + " for this check in the file \"" + fileName + "\"")
358 .that(summaries.get(checkName))
359 .isEqualTo(firstSentence);
360 }
361 }
362 }
363
364 @Test
365 public void testCategoryIndexPageTableInSyncWithAllChecksPageTable() throws Exception {
366 final Map<String, String> summaries = readSummaries();
367 for (Path path : XdocUtil.getXdocsConfigFilePaths(XdocUtil.getXdocsFilePaths())) {
368 final String fileName = path.getFileName().toString();
369 if (!"index.xml".equals(fileName)
370 || path.getParent().toString().contains("filters")) {
371 continue;
372 }
373
374 final String input = Files.readString(path);
375 final Document document = XmlUtil.getRawXml(fileName, input, input);
376 final NodeList sources = document.getElementsByTagName("tr");
377
378 for (int position = 0; position < sources.getLength(); position++) {
379 final Node tableRow = sources.item(position);
380 final Iterator<Node> cells = XmlUtil
381 .findChildElementsByTag(tableRow, "td").iterator();
382 final String checkName = XmlUtil.sanitizeXml(cells.next().getTextContent());
383 final String description = XmlUtil.sanitizeXml(cells.next().getTextContent());
384 assertWithMessage("The summary for check " + checkName
385 + " in the file \"" + path + "\""
386 + " should match the summary"
387 + " for this check in the file \"" + AVAILABLE_CHECKS_PATH + "\"")
388 .that(description)
389 .isEqualTo(summaries.get(checkName));
390 }
391 }
392 }
393
394 @Test
395 public void testAlphabetOrderInNames() throws Exception {
396 final String input = Files.readString(SITE_PATH);
397 final Document document = XmlUtil.getRawXml(SITE_PATH.toString(), input, input);
398 final NodeList nodes = document.getElementsByTagName("item");
399
400 for (int nodeIndex = 0; nodeIndex < nodes.getLength(); nodeIndex++) {
401 final Node current = nodes.item(nodeIndex);
402
403 if ("Checks".equals(XmlUtil.getNameAttributeOfNode(current))) {
404 final List<String> groupNames = getNames(current);
405 final List<String> groupNamesSorted = groupNames.stream()
406 .sorted()
407 .collect(Collectors.toUnmodifiableList());
408
409 assertWithMessage("Group" + NAMES_MUST_BE_IN_ALPHABETICAL_ORDER_SITE_PATH)
410 .that(groupNames)
411 .containsExactlyElementsIn(groupNamesSorted)
412 .inOrder();
413
414 Node groupNode = current.getFirstChild();
415 int index = 0;
416 final int totalGroups = XmlUtil.getChildrenElements(current).size();
417 while (index < totalGroups) {
418 if ("item".equals(groupNode.getNodeName())) {
419 final List<String> checkNames = getNames(groupNode);
420 final List<String> checkNamesSorted = checkNames.stream()
421 .sorted()
422 .collect(Collectors.toUnmodifiableList());
423 assertWithMessage("Check" + NAMES_MUST_BE_IN_ALPHABETICAL_ORDER_SITE_PATH)
424 .that(checkNames)
425 .containsExactlyElementsIn(checkNamesSorted)
426 .inOrder();
427 index++;
428 }
429 groupNode = groupNode.getNextSibling();
430 }
431 }
432 if ("Filters".equals(XmlUtil.getNameAttributeOfNode(current))) {
433 final List<String> filterNames = getNames(current);
434 final List<String> filterNamesSorted = filterNames.stream()
435 .sorted()
436 .collect(Collectors.toUnmodifiableList());
437 assertWithMessage("Filter" + NAMES_MUST_BE_IN_ALPHABETICAL_ORDER_SITE_PATH)
438 .that(filterNames)
439 .containsExactlyElementsIn(filterNamesSorted)
440 .inOrder();
441 }
442 if ("File Filters".equals(XmlUtil.getNameAttributeOfNode(current))) {
443 final List<String> fileFilterNames = getNames(current);
444 final List<String> fileFilterNamesSorted = fileFilterNames.stream()
445 .sorted()
446 .collect(Collectors.toUnmodifiableList());
447 assertWithMessage("File Filter" + NAMES_MUST_BE_IN_ALPHABETICAL_ORDER_SITE_PATH)
448 .that(fileFilterNames)
449 .containsExactlyElementsIn(fileFilterNamesSorted)
450 .inOrder();
451 }
452 }
453 }
454
455 @Test
456 public void testAlphabetOrderAtIndexPages() throws Exception {
457 final Path allChecks = Paths.get("src/site/xdoc/checks.xml");
458 validateOrder(allChecks, "Check");
459
460 final String[] groupNames = {"annotation", "blocks", "design",
461 "coding", "header", "imports", "javadoc", "metrics",
462 "misc", "modifier", "naming", "regexp", "sizes", "whitespace"};
463 for (String name : groupNames) {
464 final Path checks = Paths.get("src/site/xdoc/checks/" + name + "/index.xml");
465 validateOrder(checks, "Check");
466 }
467 final Path filters = Paths.get("src/site/xdoc/filters/index.xml");
468 validateOrder(filters, "Filter");
469
470 final Path fileFilters = Paths.get("src/site/xdoc/filefilters/index.xml");
471 validateOrder(fileFilters, "File Filter");
472 }
473
474 public static void validateOrder(Path path, String name) throws Exception {
475 final String input = Files.readString(path);
476 final Document document = XmlUtil.getRawXml(path.toString(), input, input);
477 final NodeList nodes = document.getElementsByTagName("div");
478
479 for (int nodeIndex = 0; nodeIndex < nodes.getLength(); nodeIndex++) {
480 final Node current = nodes.item(nodeIndex);
481 final List<String> names = getNamesFromIndexPage(current);
482 final List<String> namesSorted = names.stream()
483 .sorted()
484 .collect(Collectors.toUnmodifiableList());
485
486 assertWithMessage(name + NAMES_MUST_BE_IN_ALPHABETICAL_ORDER_SITE_PATH + path)
487 .that(names)
488 .containsExactlyElementsIn(namesSorted)
489 .inOrder();
490 }
491 }
492
493 private static List<String> getNamesFromIndexPage(Node node) {
494 final List<String> result = new ArrayList<>();
495 final Set<Node> children = XmlUtil.findChildElementsByTag(node, "a");
496
497 Node current = node.getFirstChild();
498 Node treeNode = current;
499 boolean getFirstChild = false;
500 int index = 0;
501 while (current != null && index < children.size()) {
502 if ("tr".equals(current.getNodeName())) {
503 treeNode = current.getNextSibling();
504 }
505 if ("a".equals(current.getNodeName())) {
506 final String name = current.getFirstChild().getTextContent()
507 .replace(" ", "").replace("\n", "");
508 result.add(name);
509 current = treeNode;
510 getFirstChild = false;
511 index++;
512 }
513 else if (getFirstChild) {
514 current = current.getFirstChild();
515 getFirstChild = false;
516 }
517 else {
518 current = current.getNextSibling();
519 getFirstChild = true;
520 }
521 }
522 return result;
523 }
524
525 private static List<String> getNames(Node node) {
526 final Set<Node> children = XmlUtil.getChildrenElements(node);
527 final List<String> result = new ArrayList<>();
528 Node current = node.getFirstChild();
529 int index = 0;
530 while (index < children.size()) {
531 if ("item".equals(current.getNodeName())) {
532 final String name = XmlUtil.getNameAttributeOfNode(current);
533 result.add(name);
534 index++;
535 }
536 current = current.getNextSibling();
537 }
538 return result;
539 }
540
541 private static Map<String, String> readSummaries() throws Exception {
542 final String fileName = AVAILABLE_CHECKS_PATH.getFileName().toString();
543 final String input = Files.readString(AVAILABLE_CHECKS_PATH);
544 final Document document = XmlUtil.getRawXml(fileName, input, input);
545 final NodeList rows = document.getElementsByTagName("tr");
546 final Map<String, String> result = new HashMap<>();
547
548 for (int position = 0; position < rows.getLength(); position++) {
549 final Node row = rows.item(position);
550 final Iterator<Node> cells = XmlUtil.findChildElementsByTag(row, "td").iterator();
551 final String name = XmlUtil.sanitizeXml(cells.next().getTextContent());
552 final String summary = XmlUtil.sanitizeXml(cells.next().getTextContent());
553
554 result.put(name, summary);
555 }
556
557 return result;
558 }
559
560 @Test
561 public void testAllSubSections() throws Exception {
562 for (Path path : XdocUtil.getXdocsFilePaths()) {
563 final String input = Files.readString(path);
564 final String fileName = path.getFileName().toString();
565
566 final Document document = XmlUtil.getRawXml(fileName, input, input);
567 final NodeList subSections = document.getElementsByTagName("subsection");
568
569 for (int position = 0; position < subSections.getLength(); position++) {
570 final Node subSection = subSections.item(position);
571 final Node name = subSection.getAttributes().getNamedItem("name");
572
573 assertWithMessage("All sub-sections in '" + fileName + "' must have a name")
574 .that(name)
575 .isNotNull();
576
577 final Node id = subSection.getAttributes().getNamedItem("id");
578
579 assertWithMessage("All sub-sections in '" + fileName + "' must have an id")
580 .that(id)
581 .isNotNull();
582
583 final String sectionName;
584 final String nameString = name.getNodeValue();
585 final String idString = id.getNodeValue();
586 final String expectedId;
587
588 if ("google_style.xml".equals(fileName)) {
589 sectionName = "Google";
590 expectedId = (sectionName + " " + nameString).replace(' ', '_');
591 }
592 else if ("sun_style.xml".equals(fileName)) {
593 sectionName = "Sun";
594 expectedId = (sectionName + " " + nameString).replace(' ', '_');
595 }
596 else if (path.toString().contains("filters")
597 || path.toString().contains("checks")) {
598
599
600 sectionName = XmlUtil.getNameAttributeOfNode(subSection.getParentNode());
601 expectedId = nameString.replace(' ', '_');
602 }
603 else {
604 sectionName = XmlUtil.getNameAttributeOfNode(subSection.getParentNode());
605 expectedId = (sectionName + " " + nameString).replace(' ', '_');
606 }
607
608 assertWithMessage(fileName + " sub-section " + nameString + " for section "
609 + sectionName + " must match")
610 .that(idString)
611 .isEqualTo(expectedId);
612 }
613 }
614 }
615
616 @Test
617 public void testAllXmlExamples() throws Exception {
618 for (Path path : XdocUtil.getXdocsFilePaths()) {
619 final String input = Files.readString(path);
620 final String fileName = path.getFileName().toString();
621
622 final Document document = XmlUtil.getRawXml(fileName, input, input);
623 final NodeList sources = document.getElementsByTagName("source");
624
625 for (int position = 0; position < sources.getLength(); position++) {
626 final String unserializedSource = sources.item(position).getTextContent()
627 .replace("...", "").trim();
628
629 if (unserializedSource.length() > 1 && (unserializedSource.charAt(0) != '<'
630 || unserializedSource.charAt(unserializedSource.length() - 1) != '>'
631
632 || unserializedSource.contains("<!"))) {
633 continue;
634 }
635
636 final String code = buildXml(unserializedSource);
637
638 XmlUtil.getRawXml(fileName, code, unserializedSource);
639
640
641 assertWithMessage("Xml is invalid, old or has outdated structure")
642 .that(fileName.startsWith("anttask")
643 || fileName.startsWith("releasenotes")
644 || fileName.startsWith("writingjavadocchecks")
645 || isValidCheckstyleXml(fileName, code, unserializedSource))
646 .isTrue();
647 }
648 }
649 }
650
651 private static String buildXml(String unserializedSource) throws IOException {
652
653 String code = unserializedSource
654
655 .replace("target/cachefile", "target/cachefile-test");
656
657 if (!hasFileSetClass(code)) {
658 code = "<module name=\"TreeWalker\">\n" + code + "\n</module>";
659 }
660 if (!code.contains("name=\"Checker\"")) {
661 code = "<module name=\"Checker\">\n" + code + "\n</module>";
662 }
663 if (!code.startsWith("<?xml")) {
664 final String dtdPath = new File(
665 "src/main/resources/com/puppycrawl/tools/checkstyle/configuration_1_3.dtd")
666 .getCanonicalPath();
667
668 code = "<?xml version=\"1.0\"?>\n<!DOCTYPE module PUBLIC "
669 + "\"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN\" \"" + dtdPath
670 + "\">\n" + code;
671 }
672 return code;
673 }
674
675 private static boolean hasFileSetClass(String xml) {
676 boolean found = false;
677
678 for (String find : XML_FILESET_LIST) {
679 if (xml.contains(find)) {
680 found = true;
681 break;
682 }
683 }
684
685 return found;
686 }
687
688 private static boolean isValidCheckstyleXml(String fileName, String code,
689 String unserializedSource)
690 throws IOException, CheckstyleException {
691
692 if (!code.contains("com.mycompany") && !code.contains("checkstyle-packages")
693 && !code.contains("MethodLimit") && !code.contains("<suppress ")
694 && !code.contains("<suppress-xpath ")
695 && !code.contains("<import-control ")
696 && !unserializedSource.startsWith("<property ")
697 && !unserializedSource.startsWith("<taskdef ")) {
698
699 try {
700 final Properties properties = new Properties();
701
702 properties.setProperty("checkstyle.header.file",
703 new File("config/java.header").getCanonicalPath());
704 properties.setProperty("config.folder",
705 new File("config").getCanonicalPath());
706
707 final PropertiesExpander expander = new PropertiesExpander(properties);
708 final Configuration config = ConfigurationLoader.loadConfiguration(new InputSource(
709 new StringReader(code)), expander, IgnoredModulesOptions.EXECUTE);
710 final Checker checker = new Checker();
711
712 try {
713 final ClassLoader moduleClassLoader = Checker.class.getClassLoader();
714 checker.setModuleClassLoader(moduleClassLoader);
715 checker.configure(config);
716 }
717 finally {
718 checker.destroy();
719 }
720 }
721 catch (CheckstyleException ex) {
722 throw new CheckstyleException(fileName + " has invalid Checkstyle xml ("
723 + ex.getMessage() + "): " + unserializedSource, ex);
724 }
725 }
726 return true;
727 }
728
729 @Test
730 public void testAllCheckSections() throws Exception {
731 final ModuleFactory moduleFactory = TestUtil.getPackageObjectFactory();
732
733 for (Path path : XdocUtil.getXdocsConfigFilePaths(XdocUtil.getXdocsFilePaths())) {
734 final String fileName = path.getFileName().toString();
735
736 if (isNonModulePage(fileName)) {
737 continue;
738 }
739
740 final String input = Files.readString(path);
741 final Document document = XmlUtil.getRawXml(fileName, input, input);
742 final NodeList sources = document.getElementsByTagName("section");
743 String lastSectionName = null;
744
745 for (int position = 0; position < sources.getLength(); position++) {
746 final Node section = sources.item(position);
747 final String sectionName = XmlUtil.getNameAttributeOfNode(section);
748
749 if ("Content".equals(sectionName) || "Overview".equals(sectionName)) {
750 assertWithMessage(fileName + " section '" + sectionName + "' should be first")
751 .that(lastSectionName)
752 .isNull();
753 continue;
754 }
755
756 assertWithMessage(
757 fileName + " section '" + sectionName + "' shouldn't end with 'Check'")
758 .that(sectionName.endsWith("Check"))
759 .isFalse();
760 if (lastSectionName != null) {
761 assertWithMessage(fileName + " section '" + sectionName
762 + "' is out of order compared to '" + lastSectionName + "'")
763 .that(sectionName.toLowerCase(Locale.ENGLISH).compareTo(
764 lastSectionName.toLowerCase(Locale.ENGLISH)) >= 0)
765 .isTrue();
766 }
767
768 validateCheckSection(moduleFactory, fileName, sectionName, section);
769
770 lastSectionName = sectionName;
771 }
772 }
773 }
774
775 public static boolean isNonModulePage(String fileName) {
776 return NON_MODULE_XDOC.contains(fileName)
777 || fileName.startsWith("releasenotes")
778 || Pattern.matches("config_[a-z]+.xml", fileName);
779 }
780
781 @Test
782 public void testAllCheckSectionsEx() throws Exception {
783 final ModuleFactory moduleFactory = TestUtil.getPackageObjectFactory();
784
785 final Path path = Path.of(XdocUtil.DIRECTORY_PATH + "/config.xml");
786 final String fileName = path.getFileName().toString();
787
788 final String input = Files.readString(path);
789 final Document document = XmlUtil.getRawXml(fileName, input, input);
790 final NodeList sources = document.getElementsByTagName("section");
791
792 for (int position = 0; position < sources.getLength(); position++) {
793 final Node section = sources.item(position);
794 final String sectionName = XmlUtil.getNameAttributeOfNode(section);
795
796 if (!"Checker".equals(sectionName) && !"TreeWalker".equals(sectionName)) {
797 continue;
798 }
799
800 validateCheckSection(moduleFactory, fileName, sectionName, section);
801 }
802 }
803
804 private static void validateCheckSection(ModuleFactory moduleFactory, String fileName,
805 String sectionName, Node section) throws Exception {
806 final Object instance;
807
808 try {
809 instance = moduleFactory.createModule(sectionName);
810 }
811 catch (CheckstyleException ex) {
812 throw new CheckstyleException(fileName + " couldn't find class: " + sectionName, ex);
813 }
814
815 int subSectionPos = 0;
816 for (Node subSection : XmlUtil.getChildrenElements(section)) {
817 if (subSectionPos == 0 && "p".equals(subSection.getNodeName())) {
818 validateSinceDescriptionSection(fileName, sectionName, subSection);
819 continue;
820 }
821
822 final String subSectionName = XmlUtil.getNameAttributeOfNode(subSection);
823
824
825 if ("Notes".equals(subSectionName)
826 || "Rule Description".equals(subSectionName)
827 || "Metadata".equals(subSectionName)) {
828 continue;
829 }
830
831
832 if (subSectionPos == 1 && !"Properties".equals(subSectionName)) {
833 validatePropertySection(fileName, sectionName, null, instance);
834 subSectionPos++;
835 }
836 if (subSectionPos == 4 && !"Violation Messages".equals(subSectionName)) {
837 validateViolationSection(fileName, sectionName, null, instance);
838 subSectionPos++;
839 }
840
841 assertWithMessage(fileName + " section '" + sectionName + "' should be in order")
842 .that(subSectionName)
843 .isEqualTo(getSubSectionName(subSectionPos));
844
845 switch (subSectionPos) {
846 case 0:
847 validateDescriptionSection(fileName, sectionName, subSection);
848 break;
849 case 1:
850 validatePropertySection(fileName, sectionName, subSection, instance);
851 break;
852 case 3:
853 validateUsageExample(fileName, sectionName, subSection);
854 break;
855 case 4:
856 validateViolationSection(fileName, sectionName, subSection, instance);
857 break;
858 case 5:
859 validatePackageSection(fileName, sectionName, subSection, instance);
860 break;
861 case 6:
862 validateParentSection(fileName, sectionName, subSection);
863 break;
864 case 2:
865 default:
866 break;
867 }
868
869 subSectionPos++;
870 }
871
872 if ("Checker".equals(sectionName)) {
873 assertWithMessage(fileName + " section '" + sectionName
874 + "' should contain up to 'Package' sub-section")
875 .that(subSectionPos)
876 .isGreaterThan(5);
877 }
878 else {
879 assertWithMessage(fileName + " section '" + sectionName
880 + "' should contain up to 'Parent' sub-section")
881 .that(subSectionPos)
882 .isGreaterThan(6);
883 }
884 }
885
886 private static void validateSinceDescriptionSection(String fileName, String sectionName,
887 Node subSection) {
888 assertWithMessage(fileName + " section '" + sectionName
889 + "' should have a valid version at the start of the description like:\n"
890 + DESCRIPTION_VERSION.pattern())
891 .that(DESCRIPTION_VERSION.matcher(subSection.getTextContent().trim()).find())
892 .isTrue();
893 }
894
895 private static Object getSubSectionName(int subSectionPos) {
896 final String result;
897
898 switch (subSectionPos) {
899 case 0:
900 result = "Description";
901 break;
902 case 1:
903 result = "Properties";
904 break;
905 case 2:
906 result = "Examples";
907 break;
908 case 3:
909 result = "Example of Usage";
910 break;
911 case 4:
912 result = "Violation Messages";
913 break;
914 case 5:
915 result = "Package";
916 break;
917 case 6:
918 result = "Parent Module";
919 break;
920 default:
921 result = null;
922 break;
923 }
924
925 return result;
926 }
927
928 private static void validateDescriptionSection(String fileName, String sectionName,
929 Node subSection) {
930 if ("config_filters.xml".equals(fileName) && "SuppressionXpathFilter".equals(sectionName)) {
931 validateListOfSuppressionXpathFilterIncompatibleChecks(subSection);
932 }
933 }
934
935 private static void validateListOfSuppressionXpathFilterIncompatibleChecks(Node subSection) {
936 assertWithMessage(
937 "Incompatible check list should match XpathRegressionTest.INCOMPATIBLE_CHECK_NAMES")
938 .that(getListById(subSection, "SuppressionXpathFilter_IncompatibleChecks"))
939 .isEqualTo(XpathRegressionTest.INCOMPATIBLE_CHECK_NAMES);
940 final Set<String> suppressionXpathFilterJavadocChecks = getListById(subSection,
941 "SuppressionXpathFilter_JavadocChecks");
942 assertWithMessage(
943 "Javadoc check list should match XpathRegressionTest.INCOMPATIBLE_JAVADOC_CHECK_NAMES")
944 .that(suppressionXpathFilterJavadocChecks)
945 .isEqualTo(XpathRegressionTest.INCOMPATIBLE_JAVADOC_CHECK_NAMES);
946 }
947
948 private static void validatePropertySection(String fileName, String sectionName,
949 Node subSection, Object instance) throws Exception {
950 final Set<String> properties = getProperties(instance.getClass());
951 final Class<?> clss = instance.getClass();
952
953 fixCapturedProperties(sectionName, instance, clss, properties);
954
955 if (subSection != null) {
956 assertWithMessage(fileName + " section '" + sectionName
957 + "' should have no properties to show")
958 .that(properties)
959 .isNotEmpty();
960
961 final Set<Node> nodes = XmlUtil.getChildrenElements(subSection);
962 assertWithMessage(fileName + " section '" + sectionName
963 + "' subsection 'Properties' should have one child node")
964 .that(nodes)
965 .hasSize(1);
966
967 final Node div = nodes.iterator().next();
968 assertWithMessage(fileName + " section '" + sectionName
969 + "' subsection 'Properties' has unexpected child node")
970 .that(div.getNodeName())
971 .isEqualTo("div");
972 final String wrapperMessage = fileName + " section '" + sectionName
973 + "' subsection 'Properties' wrapping div for table needs the"
974 + " class 'wrapper'";
975 assertWithMessage(wrapperMessage)
976 .that(div.hasAttributes())
977 .isTrue();
978 assertWithMessage(wrapperMessage)
979 .that(div.getAttributes().getNamedItem("class").getNodeValue())
980 .isNotNull();
981 assertWithMessage(wrapperMessage)
982 .that(div.getAttributes().getNamedItem("class").getNodeValue())
983 .contains("wrapper");
984
985 final Node table = XmlUtil.getFirstChildElement(div);
986 assertWithMessage(fileName + " section '" + sectionName
987 + "' subsection 'Properties' has unexpected child node")
988 .that(table.getNodeName())
989 .isEqualTo("table");
990
991 validatePropertySectionPropertiesOrder(fileName, sectionName, table, properties);
992
993 validatePropertySectionProperties(fileName, sectionName, table, instance,
994 properties);
995 }
996
997 assertWithMessage(
998 fileName + " section '" + sectionName + "' should show properties: " + properties)
999 .that(properties)
1000 .isEmpty();
1001 }
1002
1003 private static void validatePropertySectionPropertiesOrder(String fileName, String sectionName,
1004 Node table, Set<String> properties) {
1005 final Set<Node> rows = XmlUtil.getChildrenElements(table);
1006 final List<String> orderedPropertyNames = new ArrayList<>(properties);
1007 final List<String> tablePropertyNames = new ArrayList<>();
1008
1009
1010 if (orderedPropertyNames.contains("javadocTokens")) {
1011 orderedPropertyNames.remove("javadocTokens");
1012 orderedPropertyNames.add("javadocTokens");
1013 }
1014 if (orderedPropertyNames.contains("tokens")) {
1015 orderedPropertyNames.remove("tokens");
1016 orderedPropertyNames.add("tokens");
1017 }
1018
1019 rows
1020 .stream()
1021
1022 .skip(1)
1023 .forEach(row -> {
1024 final List<Node> columns = new ArrayList<>(XmlUtil.getChildrenElements(row));
1025 assertWithMessage(fileName + " section '" + sectionName
1026 + "' should have the requested columns")
1027 .that(columns)
1028 .hasSize(5);
1029
1030 final String propertyName = columns.get(0).getTextContent();
1031 tablePropertyNames.add(propertyName);
1032 });
1033
1034 assertWithMessage(fileName + " section '" + sectionName
1035 + "' should have properties in the requested order")
1036 .that(tablePropertyNames)
1037 .isEqualTo(orderedPropertyNames);
1038 }
1039
1040 private static void fixCapturedProperties(String sectionName, Object instance, Class<?> clss,
1041 Set<String> properties) {
1042
1043 if (hasParentModule(sectionName)) {
1044 if (AbstractJavadocCheck.class.isAssignableFrom(clss)) {
1045 properties.removeAll(JAVADOC_CHECK_PROPERTIES);
1046
1047
1048 properties.add("violateExecutionOnNonTightHtml");
1049 }
1050 else if (AbstractCheck.class.isAssignableFrom(clss)) {
1051 properties.removeAll(CHECK_PROPERTIES);
1052 }
1053 }
1054 if (AbstractFileSetCheck.class.isAssignableFrom(clss)) {
1055 properties.removeAll(FILESET_PROPERTIES);
1056
1057
1058 properties.add("fileExtensions");
1059 }
1060
1061
1062 new HashSet<>(properties).stream()
1063 .filter(prop -> UNDOCUMENTED_PROPERTIES.contains(clss.getSimpleName() + "." + prop))
1064 .forEach(properties::remove);
1065
1066 if (AbstractCheck.class.isAssignableFrom(clss)) {
1067 final AbstractCheck check = (AbstractCheck) instance;
1068
1069 final int[] acceptableTokens = check.getAcceptableTokens();
1070 Arrays.sort(acceptableTokens);
1071 final int[] defaultTokens = check.getDefaultTokens();
1072 Arrays.sort(defaultTokens);
1073 final int[] requiredTokens = check.getRequiredTokens();
1074 Arrays.sort(requiredTokens);
1075
1076 if (!Arrays.equals(acceptableTokens, defaultTokens)
1077 || !Arrays.equals(acceptableTokens, requiredTokens)) {
1078 properties.add("tokens");
1079 }
1080 }
1081
1082 if (AbstractJavadocCheck.class.isAssignableFrom(clss)) {
1083 final AbstractJavadocCheck check = (AbstractJavadocCheck) instance;
1084
1085 final int[] acceptableJavadocTokens = check.getAcceptableJavadocTokens();
1086 Arrays.sort(acceptableJavadocTokens);
1087 final int[] defaultJavadocTokens = check.getDefaultJavadocTokens();
1088 Arrays.sort(defaultJavadocTokens);
1089 final int[] requiredJavadocTokens = check.getRequiredJavadocTokens();
1090 Arrays.sort(requiredJavadocTokens);
1091
1092 if (!Arrays.equals(acceptableJavadocTokens, defaultJavadocTokens)
1093 || !Arrays.equals(acceptableJavadocTokens, requiredJavadocTokens)) {
1094 properties.add("javadocTokens");
1095 }
1096 }
1097 }
1098
1099 private static void validatePropertySectionProperties(String fileName, String sectionName,
1100 Node table, Object instance, Set<String> properties) throws Exception {
1101 boolean skip = true;
1102 boolean didJavadocTokens = false;
1103 boolean didTokens = false;
1104
1105 for (Node row : XmlUtil.getChildrenElements(table)) {
1106 final List<Node> columns = new ArrayList<>(XmlUtil.getChildrenElements(row));
1107
1108 assertWithMessage(fileName + " section '" + sectionName
1109 + "' should have the requested columns")
1110 .that(columns)
1111 .hasSize(5);
1112
1113 if (skip) {
1114 assertWithMessage(fileName + " section '" + sectionName
1115 + "' should have the specific title")
1116 .that(columns.get(0).getTextContent())
1117 .isEqualTo("name");
1118 assertWithMessage(fileName + " section '" + sectionName
1119 + "' should have the specific title")
1120 .that(columns.get(1).getTextContent())
1121 .isEqualTo("description");
1122 assertWithMessage(fileName + " section '" + sectionName
1123 + "' should have the specific title")
1124 .that(columns.get(2).getTextContent())
1125 .isEqualTo("type");
1126 assertWithMessage(fileName + " section '" + sectionName
1127 + "' should have the specific title")
1128 .that(columns.get(3).getTextContent())
1129 .isEqualTo("default value");
1130 assertWithMessage(fileName + " section '" + sectionName
1131 + "' should have the specific title")
1132 .that(columns.get(4).getTextContent())
1133 .isEqualTo("since");
1134
1135 skip = false;
1136 continue;
1137 }
1138
1139 assertWithMessage(fileName + " section '" + sectionName
1140 + "' should have token properties last")
1141 .that(didTokens)
1142 .isFalse();
1143
1144 final String propertyName = columns.get(0).getTextContent();
1145 assertWithMessage(fileName + " section '" + sectionName
1146 + "' should not contain the property: " + propertyName)
1147 .that(properties.remove(propertyName))
1148 .isTrue();
1149
1150 if ("tokens".equals(propertyName)) {
1151 final AbstractCheck check = (AbstractCheck) instance;
1152 validatePropertySectionPropertyTokens(fileName, sectionName, check, columns);
1153 didTokens = true;
1154 }
1155 else if ("javadocTokens".equals(propertyName)) {
1156 final AbstractJavadocCheck check = (AbstractJavadocCheck) instance;
1157 validatePropertySectionPropertyJavadocTokens(fileName, sectionName, check, columns);
1158 didJavadocTokens = true;
1159 }
1160 else {
1161 assertWithMessage(fileName + " section '" + sectionName
1162 + "' should have javadoc token properties next to last, before tokens")
1163 .that(didJavadocTokens)
1164 .isFalse();
1165
1166 validatePropertySectionPropertyEx(fileName, sectionName, instance, columns,
1167 propertyName);
1168 }
1169
1170 assertWithMessage("%s section '%s' should have a version for %s",
1171 fileName, sectionName, propertyName)
1172 .that(columns.get(4).getTextContent().trim())
1173 .isNotEmpty();
1174 assertWithMessage("%s section '%s' should have a valid version for %s",
1175 fileName, sectionName, propertyName)
1176 .that(columns.get(4).getTextContent().trim())
1177 .matches(VERSION);
1178 }
1179 }
1180
1181 private static void validatePropertySectionPropertyEx(String fileName, String sectionName,
1182 Object instance, List<Node> columns, String propertyName) throws Exception {
1183 assertWithMessage("%s section '%s' should have a description for %s",
1184 fileName, sectionName, propertyName)
1185 .that(columns.get(1).getTextContent().trim())
1186 .isNotEmpty();
1187 assertWithMessage("%s section '%s' should have a description for %s"
1188 + " that starts with uppercase character",
1189 fileName, sectionName, propertyName)
1190 .that(Character.isUpperCase(columns.get(1).getTextContent().trim().charAt(0)))
1191 .isTrue();
1192
1193 final String actualTypeName = columns.get(2).getTextContent().replace("\n", "")
1194 .replace("\r", "").replaceAll(" +", " ").trim();
1195
1196 assertWithMessage(
1197 fileName + " section '" + sectionName + "' should have a type for " + propertyName)
1198 .that(actualTypeName)
1199 .isNotEmpty();
1200
1201 final Field field = getField(instance.getClass(), propertyName);
1202 final Class<?> fieldClass = getFieldClass(fileName, sectionName, instance, field,
1203 propertyName);
1204
1205 final String expectedTypeName = Optional.ofNullable(field)
1206 .map(nonNullField -> nonNullField.getAnnotation(XdocsPropertyType.class))
1207 .map(propertyType -> propertyType.value().getDescription())
1208 .orElse(fieldClass.getSimpleName());
1209 final String expectedValue = getModulePropertyExpectedValue(sectionName, propertyName,
1210 field, fieldClass, instance);
1211
1212 assertWithMessage(fileName + " section '" + sectionName
1213 + "' should have the type for " + propertyName)
1214 .that(actualTypeName)
1215 .isEqualTo(expectedTypeName);
1216
1217 if (expectedValue != null) {
1218 final String actualValue = columns.get(3).getTextContent().trim()
1219 .replaceAll("\\s+", " ")
1220 .replaceAll("\\s,", ",");
1221
1222 assertWithMessage(fileName + " section '" + sectionName
1223 + "' should have the value for " + propertyName)
1224 .that(actualValue)
1225 .isEqualTo(expectedValue);
1226 }
1227 }
1228
1229 private static void validatePropertySectionPropertyTokens(String fileName, String sectionName,
1230 AbstractCheck check, List<Node> columns) {
1231 assertWithMessage(fileName + " section '" + sectionName
1232 + "' should have the basic token description")
1233 .that(columns.get(1).getTextContent())
1234 .isEqualTo("tokens to check");
1235
1236 final String acceptableTokenText = columns.get(2).getTextContent().trim();
1237 String expectedAcceptableTokenText = "subset of tokens "
1238 + CheckUtil.getTokenText(check.getAcceptableTokens(),
1239 check.getRequiredTokens());
1240 if (isAllTokensAcceptable(check)) {
1241 expectedAcceptableTokenText = "set of any supported tokens";
1242 }
1243 assertWithMessage(fileName + " section '" + sectionName
1244 + "' should have all the acceptable tokens")
1245 .that(acceptableTokenText
1246 .replaceAll("\\s+", " ")
1247 .replaceAll("\\s,", ",")
1248 .replaceAll("\\s\\.", "."))
1249 .isEqualTo(expectedAcceptableTokenText);
1250 assertWithMessage(fileName + "'s acceptable token section: " + sectionName
1251 + "should have ',' & '.' at beginning of the next corresponding lines.")
1252 .that(isInvalidTokenPunctuation(acceptableTokenText))
1253 .isFalse();
1254
1255 final String defaultTokenText = columns.get(3).getTextContent().trim();
1256 final String expectedDefaultTokenText = CheckUtil.getTokenText(check.getDefaultTokens(),
1257 check.getRequiredTokens());
1258 if (expectedDefaultTokenText.isEmpty()) {
1259 assertWithMessage("Empty tokens should have 'empty' string in xdoc")
1260 .that(defaultTokenText)
1261 .isEqualTo("empty");
1262 }
1263 else {
1264 assertWithMessage(fileName + " section '" + sectionName
1265 + "' should have all the default tokens")
1266 .that(defaultTokenText
1267 .replaceAll("\\s+", " ")
1268 .replaceAll("\\s,", ",")
1269 .replaceAll("\\s\\.", "."))
1270 .isEqualTo(expectedDefaultTokenText);
1271 assertWithMessage(fileName + "'s default token section: " + sectionName
1272 + "should have ',' or '.' at beginning of the next corresponding lines.")
1273 .that(isInvalidTokenPunctuation(defaultTokenText))
1274 .isFalse();
1275 }
1276
1277 }
1278
1279 private static boolean isAllTokensAcceptable(AbstractCheck check) {
1280 return Arrays.equals(check.getAcceptableTokens(), TokenUtil.getAllTokenIds());
1281 }
1282
1283 private static void validatePropertySectionPropertyJavadocTokens(String fileName,
1284 String sectionName, AbstractJavadocCheck check, List<Node> columns) {
1285 assertWithMessage(fileName + " section '" + sectionName
1286 + "' should have the basic token javadoc description")
1287 .that(columns.get(1).getTextContent())
1288 .isEqualTo("javadoc tokens to check");
1289
1290 final String acceptableTokenText = columns.get(2).getTextContent().trim();
1291 assertWithMessage(fileName + " section '" + sectionName
1292 + "' should have all the acceptable javadoc tokens")
1293 .that(acceptableTokenText
1294 .replaceAll("\\s+", " ")
1295 .replaceAll("\\s,", ",")
1296 .replaceAll("\\s\\.", "."))
1297 .isEqualTo("subset of javadoc tokens "
1298 + CheckUtil.getJavadocTokenText(check.getAcceptableJavadocTokens(),
1299 check.getRequiredJavadocTokens()));
1300 assertWithMessage(fileName + "'s acceptable javadoc token section: " + sectionName
1301 + "should have ',' & '.' at beginning of the next corresponding lines.")
1302 .that(isInvalidTokenPunctuation(acceptableTokenText))
1303 .isFalse();
1304
1305 final String defaultTokenText = columns.get(3).getTextContent().trim();
1306 assertWithMessage(fileName + " section '" + sectionName
1307 + "' should have all the default javadoc tokens")
1308 .that(defaultTokenText
1309 .replaceAll("\\s+", " ")
1310 .replaceAll("\\s,", ",")
1311 .replaceAll("\\s\\.", "."))
1312 .isEqualTo(CheckUtil.getJavadocTokenText(check.getDefaultJavadocTokens(),
1313 check.getRequiredJavadocTokens()));
1314 assertWithMessage(fileName + "'s default javadoc token section: " + sectionName
1315 + "should have ',' & '.' at beginning of the next corresponding lines.")
1316 .that(isInvalidTokenPunctuation(defaultTokenText))
1317 .isFalse();
1318 }
1319
1320 private static boolean isInvalidTokenPunctuation(String tokenText) {
1321 return Pattern.compile("\\w,").matcher(tokenText).find()
1322 || Pattern.compile("\\w\\.").matcher(tokenText).find();
1323 }
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338 private static String getModulePropertyExpectedValue(String sectionName, String propertyName,
1339 Field field, Class<?> fieldClass, Object instance) throws Exception {
1340 String result = null;
1341
1342 if (field != null) {
1343 final Object value = field.get(instance);
1344
1345 if ("Checker".equals(sectionName) && "localeCountry".equals(propertyName)) {
1346 result = "default locale country for the Java Virtual Machine";
1347 }
1348 else if ("Checker".equals(sectionName) && "localeLanguage".equals(propertyName)) {
1349 result = "default locale language for the Java Virtual Machine";
1350 }
1351 else if ("Checker".equals(sectionName) && "charset".equals(propertyName)) {
1352 result = "UTF-8";
1353 }
1354 else if ("charset".equals(propertyName)) {
1355 result = "the charset property of the parent"
1356 + " <a href=\"https://checkstyle.org/config.html#Checker\">Checker</a> module";
1357 }
1358 else if ("PropertyCacheFile".equals(fieldClass.getSimpleName())) {
1359 result = "null (no cache file)";
1360 }
1361 else if (fieldClass == boolean.class) {
1362 result = value.toString();
1363 }
1364 else if (fieldClass == int.class) {
1365 result = value.toString();
1366 }
1367 else if (fieldClass == int[].class) {
1368 result = getIntArrayPropertyValue(value);
1369 }
1370 else if (fieldClass == double[].class) {
1371 result = Arrays.toString((double[]) value).replace("[", "").replace("]", "")
1372 .replace(".0", "");
1373 if (result.isEmpty()) {
1374 result = "{}";
1375 }
1376 }
1377 else if (fieldClass == String[].class) {
1378 result = getStringArrayPropertyValue(propertyName, value);
1379 }
1380 else if (fieldClass == URI.class || fieldClass == String.class) {
1381 if (value != null) {
1382 result = '"' + value.toString() + '"';
1383 }
1384 }
1385 else if (fieldClass == Pattern.class) {
1386 if (value != null) {
1387 result = '"' + value.toString().replace("\n", "\\n").replace("\t", "\\t")
1388 .replace("\r", "\\r").replace("\f", "\\f") + '"';
1389 }
1390 }
1391 else if (fieldClass == Pattern[].class) {
1392 result = getPatternArrayPropertyValue(value);
1393 }
1394 else if (fieldClass.isEnum()) {
1395 if (value != null) {
1396 result = value.toString().toLowerCase(Locale.ENGLISH);
1397 }
1398 }
1399 else if (fieldClass == AccessModifierOption[].class) {
1400 result = Arrays.toString((Object[]) value).replace("[", "").replace("]", "");
1401 }
1402 else {
1403 assertWithMessage("Unknown property type: " + fieldClass.getSimpleName()).fail();
1404 }
1405
1406 if (result == null) {
1407 result = "null";
1408 }
1409 }
1410
1411 return result;
1412 }
1413
1414
1415
1416
1417
1418
1419
1420 private static String getPatternArrayPropertyValue(Object fieldValue) {
1421 Object value = fieldValue;
1422 String result;
1423 if (value instanceof Collection) {
1424 final Collection<?> collection = (Collection<?>) value;
1425 final Pattern[] newArray = new Pattern[collection.size()];
1426 final Iterator<?> iterator = collection.iterator();
1427 int index = 0;
1428
1429 while (iterator.hasNext()) {
1430 final Object next = iterator.next();
1431 newArray[index] = (Pattern) next;
1432 index++;
1433 }
1434
1435 value = newArray;
1436 }
1437
1438 if (value != null && Array.getLength(value) > 0) {
1439 final String[] newArray = new String[Array.getLength(value)];
1440
1441 for (int i = 0; i < newArray.length; i++) {
1442 newArray[i] = ((Pattern) Array.get(value, i)).pattern();
1443 }
1444
1445 result = Arrays.toString(newArray).replace("[", "").replace("]", "");
1446 }
1447 else {
1448 result = "";
1449 }
1450
1451 if (result.isEmpty()) {
1452 result = "{}";
1453 }
1454 return result;
1455 }
1456
1457
1458
1459
1460
1461
1462
1463
1464 private static String getStringArrayPropertyValue(String propertyName, Object value) {
1465 String result;
1466 if (value == null) {
1467 result = "";
1468 }
1469 else {
1470 final Stream<?> valuesStream;
1471 if (value instanceof Collection) {
1472 final Collection<?> collection = (Collection<?>) value;
1473 valuesStream = collection.stream();
1474 }
1475 else {
1476 final Object[] array = (Object[]) value;
1477 valuesStream = Arrays.stream(array);
1478 }
1479 result = valuesStream
1480 .map(String.class::cast)
1481 .sorted()
1482 .collect(Collectors.joining(", "));
1483 }
1484
1485 if (result.isEmpty()) {
1486 if ("fileExtensions".equals(propertyName)) {
1487 result = "all files";
1488 }
1489 else {
1490 result = "{}";
1491 }
1492 }
1493 return result;
1494 }
1495
1496
1497
1498
1499
1500
1501
1502 private static String getIntArrayPropertyValue(Object value) {
1503 final IntStream stream;
1504 if (value instanceof Collection) {
1505 final Collection<?> collection = (Collection<?>) value;
1506 stream = collection.stream()
1507 .mapToInt(number -> (int) number);
1508 }
1509 else if (value instanceof BitSet) {
1510 stream = ((BitSet) value).stream();
1511 }
1512 else {
1513 stream = Arrays.stream((int[]) value);
1514 }
1515 String result = stream
1516 .mapToObj(TokenUtil::getTokenName)
1517 .sorted()
1518 .collect(Collectors.joining(", "));
1519 if (result.isEmpty()) {
1520 result = "{}";
1521 }
1522 return result;
1523 }
1524
1525
1526
1527
1528
1529
1530
1531
1532 private static Field getField(Class<?> fieldClass, String propertyName) {
1533 Field result = null;
1534 Class<?> currentClass = fieldClass;
1535
1536 while (!Object.class.equals(currentClass)) {
1537 try {
1538 result = currentClass.getDeclaredField(propertyName);
1539 result.trySetAccessible();
1540 break;
1541 }
1542 catch (NoSuchFieldException ignored) {
1543 currentClass = currentClass.getSuperclass();
1544 }
1545 }
1546
1547 return result;
1548 }
1549
1550 private static Class<?> getFieldClass(String fileName, String sectionName, Object instance,
1551 Field field, String propertyName) throws Exception {
1552 Class<?> result = null;
1553
1554 if (PROPERTIES_ALLOWED_GET_TYPES_FROM_METHOD.contains(sectionName + "." + propertyName)) {
1555 final PropertyDescriptor descriptor = PropertyUtils.getPropertyDescriptor(instance,
1556 propertyName);
1557 result = descriptor.getPropertyType();
1558 }
1559 if (field != null && result == null) {
1560 result = field.getType();
1561 }
1562 if (result == null) {
1563 assertWithMessage(
1564 fileName + " section '" + sectionName + "' could not find field "
1565 + propertyName)
1566 .fail();
1567 }
1568 if (field != null && (result == List.class || result == Set.class)) {
1569 final ParameterizedType type = (ParameterizedType) field.getGenericType();
1570 final Class<?> parameterClass = (Class<?>) type.getActualTypeArguments()[0];
1571
1572 if (parameterClass == Integer.class) {
1573 result = int[].class;
1574 }
1575 else if (parameterClass == String.class) {
1576 result = String[].class;
1577 }
1578 else if (parameterClass == Pattern.class) {
1579 result = Pattern[].class;
1580 }
1581 else {
1582 assertWithMessage("Unknown parameterized type: " + parameterClass.getSimpleName())
1583 .fail();
1584 }
1585 }
1586 else if (result == BitSet.class) {
1587 result = int[].class;
1588 }
1589
1590 return result;
1591 }
1592
1593 private static Set<String> getListById(Node subSection, String id) {
1594 Set<String> result = null;
1595 final Node node = XmlUtil.findChildElementById(subSection, id);
1596 if (node != null) {
1597 result = XmlUtil.getChildrenElements(node)
1598 .stream()
1599 .map(Node::getTextContent)
1600 .collect(Collectors.toUnmodifiableSet());
1601 }
1602 return result;
1603 }
1604
1605 private static void validateViolationSection(String fileName, String sectionName,
1606 Node subSection,
1607 Object instance) throws Exception {
1608 final Class<?> clss = instance.getClass();
1609 final Set<Field> fields = CheckUtil.getCheckMessages(clss, true);
1610 final Set<String> list = new TreeSet<>();
1611
1612 for (Field field : fields) {
1613
1614 field.trySetAccessible();
1615
1616 list.add(field.get(null).toString());
1617 }
1618
1619 final StringBuilder expectedText = new StringBuilder(120);
1620
1621 for (String s : list) {
1622 expectedText.append(s);
1623 expectedText.append('\n');
1624 }
1625
1626 if (expectedText.length() > 0) {
1627 expectedText.append("All messages can be customized if the default message doesn't "
1628 + "suit you.\nPlease see the documentation to learn how to.");
1629 }
1630
1631 if (subSection == null) {
1632 assertWithMessage(fileName + " section '" + sectionName
1633 + "' should have the expected error keys")
1634 .that(expectedText.toString())
1635 .isEqualTo("");
1636 }
1637 else {
1638 final String subsectionTextContent = subSection.getTextContent()
1639 .replaceAll("\n\\s+", "\n")
1640 .replaceAll("\\s+", " ")
1641 .trim();
1642 assertWithMessage(fileName + " section '" + sectionName
1643 + "' should have the expected error keys")
1644 .that(subsectionTextContent)
1645 .isEqualTo(expectedText.toString().replaceAll("\n", " ").trim());
1646
1647 for (Node node : XmlUtil.findChildElementsByTag(subSection, "a")) {
1648 final String url = node.getAttributes().getNamedItem("href").getTextContent();
1649 final String linkText = node.getTextContent().trim();
1650 final String expectedUrl;
1651
1652 if ("see the documentation".equals(linkText)) {
1653 expectedUrl = "../../config.html#Custom_messages";
1654 }
1655 else {
1656 expectedUrl = "https://github.com/search?q="
1657 + "path%3Asrc%2Fmain%2Fresources%2F"
1658 + clss.getPackage().getName().replace(".", "%2F")
1659 + "%20path%3A**%2Fmessages*.properties+repo%3Acheckstyle%2F"
1660 + "checkstyle+%22" + linkText + "%22";
1661 }
1662
1663 assertWithMessage(fileName + " section '" + sectionName
1664 + "' should have matching url for '" + linkText + "'")
1665 .that(url)
1666 .isEqualTo(expectedUrl);
1667 }
1668 }
1669 }
1670
1671 private static void validateUsageExample(String fileName, String sectionName, Node subSection) {
1672 final String text = subSection.getTextContent().replace("Checkstyle Style", "")
1673 .replace("Google Style", "").replace("Sun Style", "").trim();
1674
1675 assertWithMessage(fileName + " section '" + sectionName
1676 + "' has unknown text in 'Example of Usage': " + text)
1677 .that(text)
1678 .isEmpty();
1679
1680 boolean hasCheckstyle = false;
1681 boolean hasGoogle = false;
1682 boolean hasSun = false;
1683
1684 for (Node node : XmlUtil.findChildElementsByTag(subSection, "a")) {
1685 final String url = node.getAttributes().getNamedItem("href").getTextContent();
1686 final String linkText = node.getTextContent().trim();
1687 String expectedUrl = null;
1688
1689 if ("Checkstyle Style".equals(linkText)) {
1690 hasCheckstyle = true;
1691 expectedUrl = "https://github.com/search?q="
1692 + "path%3Aconfig%20path%3A**%2Fcheckstyle-checks.xml+"
1693 + "repo%3Acheckstyle%2Fcheckstyle+" + sectionName;
1694 }
1695 else if ("Google Style".equals(linkText)) {
1696 hasGoogle = true;
1697 expectedUrl = "https://github.com/search?q="
1698 + "path%3Asrc%2Fmain%2Fresources%20path%3A**%2Fgoogle_checks.xml+"
1699 + "repo%3Acheckstyle%2Fcheckstyle+"
1700 + sectionName;
1701
1702 assertWithMessage(fileName + " section '" + sectionName
1703 + "' should be in google_checks.xml or not reference 'Google Style'")
1704 .that(GOOGLE_MODULES)
1705 .contains(sectionName);
1706 }
1707 else if ("Sun Style".equals(linkText)) {
1708 hasSun = true;
1709 expectedUrl = "https://github.com/search?q="
1710 + "path%3Asrc%2Fmain%2Fresources%20path%3A**%2Fsun_checks.xml+"
1711 + "repo%3Acheckstyle%2Fcheckstyle+"
1712 + sectionName;
1713
1714 assertWithMessage(fileName + " section '" + sectionName
1715 + "' should be in sun_checks.xml or not reference 'Sun Style'")
1716 .that(SUN_MODULES)
1717 .contains(sectionName);
1718 }
1719
1720 assertWithMessage(fileName + " section '" + sectionName
1721 + "' should have matching url")
1722 .that(url)
1723 .isEqualTo(expectedUrl);
1724 }
1725
1726 assertWithMessage(fileName + " section '" + sectionName
1727 + "' should have a checkstyle section")
1728 .that(hasCheckstyle)
1729 .isTrue();
1730 assertWithMessage(fileName + " section '" + sectionName
1731 + "' should have a google section since it is in it's config")
1732 .that(hasGoogle || !GOOGLE_MODULES.contains(sectionName))
1733 .isTrue();
1734 assertWithMessage(fileName + " section '" + sectionName
1735 + "' should have a sun section since it is in it's config")
1736 .that(hasSun || !SUN_MODULES.contains(sectionName))
1737 .isTrue();
1738 }
1739
1740 private static void validatePackageSection(String fileName, String sectionName,
1741 Node subSection, Object instance) {
1742 assertWithMessage(fileName + " section '" + sectionName
1743 + "' should have matching package")
1744 .that(subSection.getTextContent().trim())
1745 .isEqualTo(instance.getClass().getPackage().getName());
1746 }
1747
1748 private static void validateParentSection(String fileName, String sectionName,
1749 Node subSection) {
1750 final String expected;
1751
1752 if (!"TreeWalker".equals(sectionName) && hasParentModule(sectionName)) {
1753 expected = "TreeWalker";
1754 }
1755 else {
1756 expected = "Checker";
1757 }
1758
1759 assertWithMessage(fileName + " section '" + sectionName + "' should have matching parent")
1760 .that(subSection.getTextContent().trim())
1761 .isEqualTo(expected);
1762 }
1763
1764 private static boolean hasParentModule(String sectionName) {
1765 final String search = "\"" + sectionName + "\"";
1766 boolean result = true;
1767
1768 for (String find : XML_FILESET_LIST) {
1769 if (find.contains(search)) {
1770 result = false;
1771 break;
1772 }
1773 }
1774
1775 return result;
1776 }
1777
1778 private static Set<String> getProperties(Class<?> clss) {
1779 final Set<String> result = new TreeSet<>();
1780 final PropertyDescriptor[] map = PropertyUtils.getPropertyDescriptors(clss);
1781
1782 for (PropertyDescriptor p : map) {
1783 if (p.getWriteMethod() != null) {
1784 result.add(p.getName());
1785 }
1786 }
1787
1788 return result;
1789 }
1790
1791 @Test
1792 public void testAllStyleRules() throws Exception {
1793 for (Path path : XdocUtil.getXdocsStyleFilePaths(XdocUtil.getXdocsFilePaths())) {
1794 final String fileName = path.getFileName().toString();
1795 final String styleName = fileName.substring(0, fileName.lastIndexOf('_'));
1796 final String input = Files.readString(path);
1797 final Document document = XmlUtil.getRawXml(fileName, input, input);
1798 final NodeList sources = document.getElementsByTagName("tr");
1799
1800 final Set<String> styleChecks;
1801 switch (styleName) {
1802 case "google":
1803 styleChecks = new HashSet<>(GOOGLE_MODULES);
1804 break;
1805
1806 case "sun":
1807 styleChecks = new HashSet<>(SUN_MODULES);
1808 styleChecks.removeAll(IGNORED_SUN_MODULES);
1809 break;
1810
1811 default:
1812 assertWithMessage("Missing modules list for style file '" + fileName + "'")
1813 .fail();
1814 styleChecks = null;
1815 }
1816
1817 String lastRuleName = null;
1818 String[] lastRuleNumberParts = null;
1819
1820 for (int position = 0; position < sources.getLength(); position++) {
1821 final Node row = sources.item(position);
1822 final List<Node> columns = new ArrayList<>(
1823 XmlUtil.findChildElementsByTag(row, "td"));
1824
1825 if (columns.isEmpty()) {
1826 continue;
1827 }
1828
1829 final String ruleName = columns.get(1).getTextContent().trim();
1830 lastRuleNumberParts = validateRuleNameOrder(
1831 fileName, lastRuleName, lastRuleNumberParts, ruleName);
1832
1833 if (!"--".equals(ruleName)) {
1834 validateStyleAnchors(XmlUtil.findChildElementsByTag(columns.get(0), "a"),
1835 fileName, ruleName);
1836 }
1837
1838 validateStyleModules(XmlUtil.findChildElementsByTag(columns.get(2), "a"),
1839 XmlUtil.findChildElementsByTag(columns.get(3), "a"), styleChecks, styleName,
1840 ruleName);
1841
1842 lastRuleName = ruleName;
1843 }
1844
1845
1846 styleChecks.remove("BeforeExecutionExclusionFileFilter");
1847 styleChecks.remove("SuppressionFilter");
1848 styleChecks.remove("SuppressionXpathFilter");
1849 styleChecks.remove("SuppressionXpathSingleFilter");
1850 styleChecks.remove("TreeWalker");
1851 styleChecks.remove("Checker");
1852 styleChecks.remove("SuppressWithNearbyCommentFilter");
1853 styleChecks.remove("SuppressionCommentFilter");
1854 styleChecks.remove("SuppressWarningsFilter");
1855 styleChecks.remove("SuppressWarningsHolder");
1856 styleChecks.remove("SuppressWithNearbyTextFilter");
1857
1858 assertWithMessage(
1859 fileName + " requires the following check(s) to appear: " + styleChecks)
1860 .that(styleChecks)
1861 .isEmpty();
1862 }
1863 }
1864
1865 private static String[] validateRuleNameOrder(String fileName, String lastRuleName,
1866 String[] lastRuleNumberParts, String ruleName) {
1867 final String[] ruleNumberParts = ruleName.split(" ", 2)[0].split("\\.");
1868
1869 if (lastRuleName != null) {
1870 final int ruleNumberPartsAmount = ruleNumberParts.length;
1871 final int lastRuleNumberPartsAmount = lastRuleNumberParts.length;
1872 final String outOfOrderReason = fileName + " rule '" + ruleName
1873 + "' is out of order compared to '" + lastRuleName + "'";
1874 boolean lastRuleNumberPartWasEqual = false;
1875 int partIndex;
1876 for (partIndex = 0; partIndex < ruleNumberPartsAmount; partIndex++) {
1877 if (lastRuleNumberPartsAmount <= partIndex) {
1878
1879
1880 break;
1881 }
1882
1883 final String ruleNumberPart = ruleNumberParts[partIndex];
1884 final String lastRuleNumberPart = lastRuleNumberParts[partIndex];
1885 final boolean ruleNumberPartsAreNumeric = IntStream.concat(
1886 ruleNumberPart.chars(),
1887 lastRuleNumberPart.chars()
1888 ).allMatch(Character::isDigit);
1889
1890 if (ruleNumberPartsAreNumeric) {
1891 final int numericRuleNumberPart = parseInt(ruleNumberPart);
1892 final int numericLastRuleNumberPart = parseInt(lastRuleNumberPart);
1893 assertWithMessage(outOfOrderReason)
1894 .that(numericRuleNumberPart)
1895 .isAtLeast(numericLastRuleNumberPart);
1896 }
1897 else {
1898 assertWithMessage(outOfOrderReason)
1899 .that(ruleNumberPart.compareToIgnoreCase(lastRuleNumberPart))
1900 .isAtLeast(0);
1901 }
1902 lastRuleNumberPartWasEqual = ruleNumberPart.equalsIgnoreCase(lastRuleNumberPart);
1903 if (!lastRuleNumberPartWasEqual) {
1904
1905
1906 break;
1907 }
1908 }
1909 if (ruleNumberPartsAmount == partIndex && lastRuleNumberPartWasEqual) {
1910 if (lastRuleNumberPartsAmount == partIndex) {
1911 assertWithMessage(fileName + " rule '" + ruleName + "' and rule '"
1912 + lastRuleName + "' have the same rule number").fail();
1913 }
1914 else {
1915 assertWithMessage(outOfOrderReason).fail();
1916 }
1917 }
1918 }
1919
1920 return ruleNumberParts;
1921 }
1922
1923 private static void validateStyleAnchors(Set<Node> anchors, String fileName, String ruleName) {
1924 assertWithMessage(fileName + " rule '" + ruleName + "' must have two row anchors")
1925 .that(anchors)
1926 .hasSize(2);
1927
1928 final int space = ruleName.indexOf(' ');
1929 assertWithMessage(fileName + " rule '" + ruleName
1930 + "' must have have a space between the rule's number and the rule's name")
1931 .that(space)
1932 .isNotEqualTo(-1);
1933
1934 final String ruleNumber = ruleName.substring(0, space);
1935
1936 int position = 1;
1937
1938 for (Node anchor : anchors) {
1939 final String actualUrl;
1940 final String expectedUrl;
1941
1942 if (position == 1) {
1943 actualUrl = XmlUtil.getNameAttributeOfNode(anchor);
1944 expectedUrl = ruleNumber;
1945 }
1946 else {
1947 actualUrl = anchor.getAttributes().getNamedItem("href").getTextContent();
1948 expectedUrl = "#" + ruleNumber;
1949 }
1950
1951 assertWithMessage(fileName + " rule '" + ruleName + "' anchor "
1952 + position + " should have matching name/url")
1953 .that(actualUrl)
1954 .isEqualTo(expectedUrl);
1955
1956 position++;
1957 }
1958 }
1959
1960 private static void validateStyleModules(Set<Node> checks, Set<Node> configs,
1961 Set<String> styleChecks, String styleName, String ruleName) {
1962 final Iterator<Node> itrChecks = checks.iterator();
1963 final Iterator<Node> itrConfigs = configs.iterator();
1964 final boolean isGoogleDocumentation = "google".equals(styleName);
1965
1966 if (isGoogleDocumentation) {
1967 validateChapterWiseTesting(itrChecks, itrConfigs, styleChecks, styleName, ruleName);
1968 }
1969 else {
1970 validateModuleWiseTesting(itrChecks, itrConfigs, styleChecks, styleName, ruleName);
1971 }
1972
1973 assertWithMessage(styleName + "_style.xml rule '" + ruleName + "' has too many configs")
1974 .that(itrConfigs.hasNext())
1975 .isFalse();
1976 }
1977
1978 private static void validateModuleWiseTesting(Iterator<Node> itrChecks,
1979 Iterator<Node> itrConfigs, Set<String> styleChecks, String styleName, String ruleName) {
1980 while (itrChecks.hasNext()) {
1981 final Node module = itrChecks.next();
1982 final String moduleName = module.getTextContent().trim();
1983 final String href = module.getAttributes().getNamedItem("href").getTextContent();
1984 final boolean moduleIsCheck = href.startsWith("checks/");
1985
1986 if (!moduleIsCheck) {
1987 continue;
1988 }
1989
1990 assertWithMessage(styleName + "_style.xml rule '" + ruleName + "' module '" + moduleName
1991 + "' shouldn't end with 'Check'")
1992 .that(moduleName.endsWith("Check"))
1993 .isFalse();
1994
1995 styleChecks.remove(moduleName);
1996
1997 for (String configName : new String[] {"config", "test"}) {
1998 Node config = null;
1999
2000 try {
2001 config = itrConfigs.next();
2002 }
2003 catch (NoSuchElementException ignore) {
2004 assertWithMessage(styleName + "_style.xml rule '" + ruleName + "' module '"
2005 + moduleName + "' is missing the config link: " + configName).fail();
2006 }
2007
2008 assertWithMessage(styleName + "_style.xml rule '" + ruleName + "' module '"
2009 + moduleName + "' has mismatched config/test links")
2010 .that(config.getTextContent().trim())
2011 .isEqualTo(configName);
2012
2013 final String configUrl = config.getAttributes().getNamedItem("href")
2014 .getTextContent();
2015
2016 if ("config".equals(configName)) {
2017 final String expectedUrl = "https://github.com/search?q="
2018 + "path%3Asrc%2Fmain%2Fresources%20path%3A**%2F" + styleName
2019 + "_checks.xml+repo%3Acheckstyle%2Fcheckstyle+" + moduleName;
2020
2021 assertWithMessage(styleName + "_style.xml rule '" + ruleName + "' module '"
2022 + moduleName + "' should have matching " + configName + " url")
2023 .that(configUrl)
2024 .isEqualTo(expectedUrl);
2025 }
2026 else if ("test".equals(configName)) {
2027 assertWithMessage(styleName + "_style.xml rule '" + ruleName + "' module '"
2028 + moduleName + "' should have matching " + configName + " url")
2029 .that(configUrl)
2030 .startsWith("https://github.com/checkstyle/checkstyle/"
2031 + "blob/master/src/it/java/com/" + styleName
2032 + "/checkstyle/test/");
2033 assertWithMessage(styleName + "_style.xml rule '" + ruleName + "' module '"
2034 + moduleName + "' should have matching " + configName + " url")
2035 .that(configUrl)
2036 .endsWith("/" + moduleName + "Test.java");
2037
2038 assertWithMessage(styleName + "_style.xml rule '" + ruleName + "' module '"
2039 + moduleName + "' should have a test that exists")
2040 .that(new File(configUrl.substring(53).replace('/',
2041 File.separatorChar)).exists())
2042 .isTrue();
2043 }
2044 }
2045 }
2046 }
2047
2048 private static void validateChapterWiseTesting(Iterator<Node> itrChecks,
2049 Iterator<Node> itrSample, Set<String> styleChecks, String styleName, String ruleName) {
2050 boolean hasChecks = false;
2051 final Set<String> usedModules = new HashSet<>();
2052
2053 while (itrChecks.hasNext()) {
2054 final Node module = itrChecks.next();
2055 final String moduleName = module.getTextContent().trim();
2056 final String href = module.getAttributes().getNamedItem("href").getTextContent();
2057 final boolean moduleIsCheck = href.startsWith("checks/");
2058
2059 final String partialConfigUrl = "https://github.com/search?q="
2060 + "path%3Asrc%2Fmain%2Fresources%20path%3A**%2F" + styleName;
2061
2062 if (!moduleIsCheck) {
2063 if (href.startsWith(partialConfigUrl)) {
2064 assertWithMessage("google_style.xml rule '" + ruleName + "' module '"
2065 + moduleName + "' has too many config links").fail();
2066 }
2067 continue;
2068 }
2069
2070 hasChecks = true;
2071
2072 assertWithMessage("The module '" + moduleName + "' in the rule '" + ruleName
2073 + "' of the style guide '" + styleName
2074 + "_style.xml' should not appear more than once in the section.")
2075 .that(usedModules)
2076 .doesNotContain(moduleName);
2077
2078 usedModules.add(moduleName);
2079
2080 assertWithMessage(styleName + "_style.xml rule '" + ruleName + "' module '"
2081 + moduleName + "' shouldn't end with 'Check'")
2082 .that(moduleName.endsWith("Check"))
2083 .isFalse();
2084
2085 styleChecks.remove(moduleName);
2086
2087 if (itrChecks.hasNext()) {
2088 final Node config = itrChecks.next();
2089
2090 final String configUrl = config.getAttributes()
2091 .getNamedItem("href").getTextContent();
2092
2093 final String expectedUrl =
2094 partialConfigUrl + "_checks.xml+repo%3Acheckstyle%2Fcheckstyle+" + moduleName;
2095
2096 assertWithMessage(
2097 "google_style.xml rule '" + ruleName + "' module '" + moduleName
2098 + "' should have matching config url")
2099 .that(configUrl)
2100 .isEqualTo(expectedUrl);
2101 }
2102 else {
2103 assertWithMessage(styleName + "_style.xml rule '" + ruleName + "' module '"
2104 + moduleName + "' is missing the config link").fail();
2105 }
2106 }
2107
2108 if (itrSample.hasNext()) {
2109 assertWithMessage(styleName + "_style.xml rule '" + ruleName + "' should have checks"
2110 + " if it has sample links")
2111 .that(hasChecks)
2112 .isTrue();
2113
2114 final Node sample = itrSample.next();
2115 final String inputFolderUrl = sample.getAttributes().getNamedItem("href")
2116 .getTextContent();
2117 final String extractedChapterNumber = getExtractedChapterNumber(ruleName);
2118 final String extractedSectionNumber = getExtractedSectionNumber(ruleName);
2119
2120 assertWithMessage("google_style.xml rule '" + ruleName + "' rule '"
2121 + "' should have matching sample url")
2122 .that(inputFolderUrl)
2123 .startsWith("https://github.com/checkstyle/checkstyle/"
2124 + "tree/master/src/it/resources/com/google/checkstyle/test/");
2125
2126 assertWithMessage("google_style.xml rule '" + ruleName
2127 + "' should have matching sample url")
2128 .that(inputFolderUrl)
2129 .containsMatch(
2130 "/chapter" + extractedChapterNumber
2131 + "\\D[^/]+/rule" + extractedSectionNumber + "\\D");
2132
2133 assertWithMessage("google_style.xml rule '" + ruleName
2134 + "' should have a inputs test folder that exists")
2135 .that(new File(inputFolderUrl.substring(53).replace('/',
2136 File.separatorChar)).exists())
2137 .isTrue();
2138
2139 assertWithMessage(styleName + "_style.xml rule '" + ruleName
2140 + "' has too many samples link")
2141 .that(itrSample.hasNext())
2142 .isFalse();
2143 }
2144 else {
2145 assertWithMessage(styleName + "_style.xml rule '" + ruleName + "' is missing"
2146 + " sample link")
2147 .that(hasChecks)
2148 .isFalse();
2149 }
2150 }
2151
2152 private static String getExtractedChapterNumber(String ruleName) {
2153 final Pattern pattern = Pattern.compile("^\\d+");
2154 final Matcher matcher = pattern.matcher(ruleName);
2155 matcher.find();
2156 return matcher.group();
2157 }
2158
2159 private static String getExtractedSectionNumber(String ruleName) {
2160 final Pattern pattern = Pattern.compile("^\\d+(\\.\\d+)*");
2161 final Matcher matcher = pattern.matcher(ruleName);
2162 matcher.find();
2163 return matcher.group().replaceAll("\\.", "");
2164 }
2165
2166 @Test
2167 public void testAllExampleMacrosHaveParagraphWithIdBeforeThem() throws Exception {
2168 for (Path path : XdocUtil.getXdocsTemplatesFilePaths()) {
2169 final String fileName = path.getFileName().toString();
2170 final String input = Files.readString(path);
2171 final Document document = XmlUtil.getRawXml(fileName, input, input);
2172 final NodeList sources = document.getElementsByTagName("macro");
2173
2174 for (int position = 0; position < sources.getLength(); position++) {
2175 final Node macro = sources.item(position);
2176 final String macroName = macro.getAttributes()
2177 .getNamedItem("name").getTextContent();
2178
2179 if (!"example".equals(macroName)) {
2180 continue;
2181 }
2182
2183 final Node precedingParagraph = getPrecedingParagraph(macro);
2184 assertWithMessage(fileName
2185 + ": paragraph before example macro should have an id attribute")
2186 .that(precedingParagraph.hasAttributes())
2187 .isTrue();
2188
2189 final Node idAttribute = precedingParagraph.getAttributes().getNamedItem("id");
2190 assertWithMessage(fileName
2191 + ": paragraph before example macro should have an id attribute")
2192 .that(idAttribute)
2193 .isNotNull();
2194
2195 validatePrecedingParagraphId(macro, fileName, idAttribute);
2196 }
2197 }
2198 }
2199
2200 private static void validatePrecedingParagraphId(
2201 Node macro, String fileName, Node idAttribute) {
2202 String exampleName = "";
2203 String exampleType = "";
2204 final NodeList params = macro.getChildNodes();
2205 for (int paramPosition = 0; paramPosition < params.getLength(); paramPosition++) {
2206 final Node item = params.item(paramPosition);
2207
2208 if (!"param".equals(item.getNodeName())) {
2209 continue;
2210 }
2211
2212 final String paramName = item.getAttributes()
2213 .getNamedItem("name").getTextContent();
2214 final String paramValue = item.getAttributes()
2215 .getNamedItem("value").getTextContent();
2216 if ("path".equals(paramName)) {
2217 exampleName = paramValue.substring(paramValue.lastIndexOf('/') + 1,
2218 paramValue.lastIndexOf('.'));
2219 }
2220 else if ("type".equals(paramName)) {
2221 exampleType = paramValue;
2222 }
2223 }
2224
2225 final String id = idAttribute.getTextContent();
2226 final String expectedId = String.format(Locale.ROOT, "%s-%s", exampleName,
2227 exampleType);
2228 if (expectedId.startsWith("package-info")) {
2229 assertWithMessage(fileName
2230 + ": paragraph before example macro should have the expected id value")
2231 .that(id)
2232 .endsWith(expectedId);
2233 }
2234 else {
2235 assertWithMessage(fileName
2236 + ": paragraph before example macro should have the expected id value")
2237 .that(id)
2238 .isEqualTo(expectedId);
2239 }
2240 }
2241
2242 private static Node getPrecedingParagraph(Node macro) {
2243 Node precedingNode = macro.getPreviousSibling();
2244 while (!"p".equals(precedingNode.getNodeName())) {
2245 precedingNode = precedingNode.getPreviousSibling();
2246 }
2247 return precedingNode;
2248 }
2249 }