From 8216c3517f604fc505c677f57c52450eaf97d5c4 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Sat, 6 Jul 2019 15:53:46 +0200 Subject: [PATCH] [doc] Error in changelog 6.16.0 due to not properly closed rule tag Fixes #1896 --- docs/pages/release_notes.md | 1 + pmd-doc/pom.xml | 13 ++ .../sourceforge/pmd/docs/RuleTagChecker.java | 139 ++++++++++++++++++ .../pmd/docs/RuleTagCheckerTest.java | 29 ++++ .../pages/pmd/rules/java/bestpractices.md | 4 + .../docs/pages/ruletag-examples.md | 12 ++ 6 files changed, 198 insertions(+) create mode 100644 pmd-doc/src/main/java/net/sourceforge/pmd/docs/RuleTagChecker.java create mode 100644 pmd-doc/src/test/java/net/sourceforge/pmd/docs/RuleTagCheckerTest.java create mode 100644 pmd-doc/src/test/resources/ruletagchecker/docs/pages/pmd/rules/java/bestpractices.md create mode 100644 pmd-doc/src/test/resources/ruletagchecker/docs/pages/ruletag-examples.md diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index ad683c568e..b7ff7a206d 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -17,6 +17,7 @@ This is a {{ site.pmd.release_type }} release. ### Fixed Issues * doc + * [#1896](https://github.com/pmd/pmd/issues/1896): \[doc] Error in changelog 6.16.0 due to not properly closed rule tag * [#1906](https://github.com/pmd/pmd/issues/1906): \[doc] Broken link for adding own CPD languages ### API Changes diff --git a/pmd-doc/pom.xml b/pmd-doc/pom.xml index ab62832e72..eb26c4c980 100644 --- a/pmd-doc/pom.xml +++ b/pmd-doc/pom.xml @@ -51,6 +51,19 @@ + + check-rule-tags + + java + + verify + + net.sourceforge.pmd.docs.RuleTagChecker + + ${project.basedir} + + + diff --git a/pmd-doc/src/main/java/net/sourceforge/pmd/docs/RuleTagChecker.java b/pmd-doc/src/main/java/net/sourceforge/pmd/docs/RuleTagChecker.java new file mode 100644 index 0000000000..55603bace4 --- /dev/null +++ b/pmd-doc/src/main/java/net/sourceforge/pmd/docs/RuleTagChecker.java @@ -0,0 +1,139 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.docs; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class RuleTagChecker { + private static final Logger LOG = Logger.getLogger(DeadLinksChecker.class.getName()); + + private static final Pattern RULE_TAG = Pattern.compile("\\{%\\s*rule\\s+\"(.*?)\"\\s*"); + private static final Pattern RULE_REFERENCE = Pattern.compile("(\\w+)\\/(\\w+)\\/(\\w+)"); + + private final Path pagesDirectory; + private final List issues = new ArrayList<>(); + private final Map> rulesCache = new HashMap<>(); + + public RuleTagChecker(Path rootDirectory) { + final Path pagesDirectory = rootDirectory.resolve("docs/pages"); + + if (!Files.isDirectory(pagesDirectory)) { + LOG.severe("can't check rule tags, didn't find \"docs/pages\" directory at: " + pagesDirectory); + System.exit(1); + } + + this.pagesDirectory = pagesDirectory; + } + + public List check() throws IOException { + Files.walkFileTree(pagesDirectory, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + checkFile(file); + return super.visitFile(file, attrs); + } + }); + return issues; + } + + private void checkFile(Path file) throws IOException { + if (file == null || !file.getFileName().toString().toLowerCase(Locale.ROOT).endsWith(".md")) { + return; + } + + LOG.finer("Checking " + file); + int lineNo = 0; + for (String line : Files.readAllLines(file, StandardCharsets.UTF_8)) { + lineNo++; + Matcher ruleTagMatcher = RULE_TAG.matcher(line); + while (ruleTagMatcher.find()) { + String ruleReference = ruleTagMatcher.group(1); + int pos = ruleTagMatcher.end(); + if (line.charAt(pos) != '%' || line.charAt(pos + 1) != '}') { + addIssue(file, lineNo, "Rule tag for " + ruleReference + " is not closed properly"); + } else if (!ruleReferenceTargetExists(ruleReference)) { + addIssue(file, lineNo, "Rule " + ruleReference + " is not found"); + } + } + } + } + + private boolean ruleReferenceTargetExists(String ruleReference) { + Matcher ruleRefMatcher = RULE_REFERENCE.matcher(ruleReference); + if (ruleRefMatcher.matches()) { + String language = ruleRefMatcher.group(1); + String category = ruleRefMatcher.group(2); + String rule = ruleRefMatcher.group(3); + + Path ruleDocPage = pagesDirectory.resolve("pmd/rules/" + language + "/" + category.toLowerCase(Locale.ROOT) + ".md"); + Set rules = getRules(ruleDocPage); + return rules.contains(rule); + } + return false; + } + + private Set getRules(Path ruleDocPage) { + Set result = rulesCache.get(ruleDocPage); + + if (result == null) { + result = new HashSet<>(); + try { + for (String line : Files.readAllLines(ruleDocPage, StandardCharsets.UTF_8)) { + if (line.startsWith("## ")) { + result.add(line.substring(3)); + } + rulesCache.put(ruleDocPage, result); + } + } catch (NoSuchFileException e) { + LOG.warning("File " + ruleDocPage + " not found."); + } catch (IOException e) { + LOG.log(Level.SEVERE, "Unable to read rules from " + ruleDocPage, e); + } + } + + return result; + } + + private void addIssue(Path file, int lineNo, String message) { + issues.add(String.format("%s:%2d: %s", pagesDirectory.relativize(file).toString(), lineNo, message)); + } + + public static void main(String[] args) throws IOException { + if (args.length != 1) { + System.err.println("Wrong arguments!"); + System.err.println(); + System.err.println("java " + RuleTagChecker.class.getSimpleName() + " "); + System.exit(1); + } + final Path rootDirectory = Paths.get(args[0]).resolve("..").toRealPath(); + + RuleTagChecker ruleTagChecker = new RuleTagChecker(rootDirectory); + List issues = ruleTagChecker.check(); + + if (!issues.isEmpty()) { + issues.forEach(System.err::println); + throw new AssertionError("Wrong rule tags detected"); + } + } +} diff --git a/pmd-doc/src/test/java/net/sourceforge/pmd/docs/RuleTagCheckerTest.java b/pmd-doc/src/test/java/net/sourceforge/pmd/docs/RuleTagCheckerTest.java new file mode 100644 index 0000000000..3b9a6fc777 --- /dev/null +++ b/pmd-doc/src/test/java/net/sourceforge/pmd/docs/RuleTagCheckerTest.java @@ -0,0 +1,29 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + + +package net.sourceforge.pmd.docs; + +import java.nio.file.FileSystems; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; + +public class RuleTagCheckerTest { + + @Test + public void testAllChecks() throws Exception { + RuleTagChecker checker = new RuleTagChecker(FileSystems.getDefault().getPath("src/test/resources/ruletagchecker")); + List issues = checker.check(); + + Assert.assertEquals(3, issues.size()); + Assert.assertEquals("ruletag-examples.md: 8: Rule tag for java/bestpractices/AvoidPrintStackTrace is not closed properly", + issues.get(0)); + Assert.assertEquals("ruletag-examples.md:10: Rule java/notexistingcategory/AvoidPrintStackTrace is not found", + issues.get(1)); + Assert.assertEquals("ruletag-examples.md:12: Rule java/bestpractices/NotExistingRule is not found", + issues.get(2)); + } +} diff --git a/pmd-doc/src/test/resources/ruletagchecker/docs/pages/pmd/rules/java/bestpractices.md b/pmd-doc/src/test/resources/ruletagchecker/docs/pages/pmd/rules/java/bestpractices.md new file mode 100644 index 0000000000..7de208cf28 --- /dev/null +++ b/pmd-doc/src/test/resources/ruletagchecker/docs/pages/pmd/rules/java/bestpractices.md @@ -0,0 +1,4 @@ + +## AvoidPrintStackTrace + +Sample rule doc for tests. diff --git a/pmd-doc/src/test/resources/ruletagchecker/docs/pages/ruletag-examples.md b/pmd-doc/src/test/resources/ruletagchecker/docs/pages/ruletag-examples.md new file mode 100644 index 0000000000..89bdeb6a01 --- /dev/null +++ b/pmd-doc/src/test/resources/ruletagchecker/docs/pages/ruletag-examples.md @@ -0,0 +1,12 @@ +--- +title: Sample Page with rule tags +permalink: rule_tag_samples.html +--- + +This is a link to the rule AvoidPrintStackTrace: {% rule "java/bestpractices/AvoidPrintStackTrace" %}. + +This is the same link, but the rule tag is not closed properly: {% rule "java/bestpractices/AvoidPrintStackTrace" %). + +Now this is link to a rule inside a category, which doesn't exist: {% rule "java/notexistingcategory/AvoidPrintStackTrace" %}. + +This is link to a rule, which doesn't exist: {% rule "java/bestpractices/NotExistingRule" %}.