From 69a3914a77d61d9407ae73cfd8f81941f9473667 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Sat, 10 Oct 2020 17:02:13 +0200 Subject: [PATCH] [doc] Renamed/Moved rules are missing in documentation (Fixes #2823) Include deprecated rule references conditionally when loading rulesets via RuleSetFactory --- docs/pages/release_notes.md | 2 + .../net/sourceforge/pmd/RuleSetFactory.java | 50 +++++++++++-------- .../sourceforge/pmd/RulesetsFactoryUtils.java | 26 ++++++++++ .../sourceforge/pmd/RuleSetFactoryTest.java | 24 +++++++++ .../pmd/docs/GenerateRuleDocsCmd.java | 4 +- .../pmd/docs/RuleDocGeneratorTest.java | 3 +- 6 files changed, 85 insertions(+), 24 deletions(-) diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index 73216f30e8..2f1510ec13 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -25,6 +25,8 @@ This is a {{ site.pmd.release_type }} release. * java-errorprone * [#2157](https://github.com/pmd/pmd/issues/2157): \[java] Improve DoNotCallSystemExit: permit call in main(), flag System.halt +* miscellaneous + * [#2823](https://github.com/pmd/pmd/issues/2823): \[doc] Renamed/Moved rules are missing in documentation ### API Changes diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/RuleSetFactory.java b/pmd-core/src/main/java/net/sourceforge/pmd/RuleSetFactory.java index 87ca747d0f..3fef741d9a 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/RuleSetFactory.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/RuleSetFactory.java @@ -45,9 +45,10 @@ import net.sourceforge.pmd.util.ResourceLoader; /** * RuleSetFactory is responsible for creating RuleSet instances from XML * content. By default Rules will be loaded using the {@link RulePriority#LOW} priority, - * with Rule deprecation warnings off. - * By default, the ruleset compatibility filter is active, too. - * See {@link RuleSetFactoryCompatibility}. + * with Rule deprecation warnings off; + * the ruleset compatibility filter is active, too (see {@link RuleSetFactoryCompatibility}); + * if the ruleset contains rule references (e.g. for renamed or moved rules), these + * are ignored by default. */ public class RuleSetFactory { @@ -61,6 +62,7 @@ public class RuleSetFactory { private final RulePriority minimumPriority; private final boolean warnDeprecated; private final RuleSetFactoryCompatibility compatibilityFilter; + private final boolean includeDeprecatedRuleReferences; private final Map parsedRulesets = new HashMap<>(); @@ -89,9 +91,15 @@ public class RuleSetFactory { @Deprecated // to be hidden with PMD 7.0.0. public RuleSetFactory(final ResourceLoader resourceLoader, final RulePriority minimumPriority, final boolean warnDeprecated, final boolean enableCompatibility) { + this(resourceLoader, minimumPriority, warnDeprecated, enableCompatibility, false); + } + + RuleSetFactory(final ResourceLoader resourceLoader, final RulePriority minimumPriority, + final boolean warnDeprecated, final boolean enableCompatibility, boolean includeDeprecatedRuleReferences) { this.resourceLoader = resourceLoader; this.minimumPriority = minimumPriority; this.warnDeprecated = warnDeprecated; + this.includeDeprecatedRuleReferences = includeDeprecatedRuleReferences; if (enableCompatibility) { this.compatibilityFilter = new RuleSetFactoryCompatibility(); @@ -133,27 +141,25 @@ public class RuleSetFactory { */ public Iterator getRegisteredRuleSets() throws RuleSetNotFoundException { String rulesetsProperties = null; - try { - List ruleSetReferenceIds = new ArrayList<>(); - for (Language language : LanguageRegistry.findWithRuleSupport()) { - Properties props = new Properties(); - rulesetsProperties = "category/" + language.getTerseName() + "/categories.properties"; - try (InputStream inputStream = resourceLoader.loadClassPathResourceAsStreamOrThrow(rulesetsProperties)) { - props.load(inputStream); - String rulesetFilenames = props.getProperty("rulesets.filenames"); - if (rulesetFilenames != null) { - ruleSetReferenceIds.addAll(RuleSetReferenceId.parse(rulesetFilenames)); - } - } catch (RuleSetNotFoundException e) { - LOG.warning("The language " + language.getTerseName() + " provides no " + rulesetsProperties + "."); + List ruleSetReferenceIds = new ArrayList<>(); + for (Language language : LanguageRegistry.findWithRuleSupport()) { + Properties props = new Properties(); + rulesetsProperties = "category/" + language.getTerseName() + "/categories.properties"; + try (InputStream inputStream = resourceLoader.loadClassPathResourceAsStreamOrThrow(rulesetsProperties)) { + props.load(inputStream); + String rulesetFilenames = props.getProperty("rulesets.filenames"); + if (rulesetFilenames != null) { + ruleSetReferenceIds.addAll(RuleSetReferenceId.parse(rulesetFilenames)); } + } catch (RuleSetNotFoundException e) { + LOG.warning("The language " + language.getTerseName() + " provides no " + rulesetsProperties + "."); + } catch (IOException ioe) { + throw new RuntimeException("Couldn't find " + rulesetsProperties + + "; please ensure that the directory is on the classpath. The current classpath is: " + + System.getProperty("java.class.path")); } - return createRuleSets(ruleSetReferenceIds).getRuleSetsIterator(); - } catch (IOException ioe) { - throw new RuntimeException("Couldn't find " + rulesetsProperties - + "; please ensure that the directory is on the classpath. The current classpath is: " - + System.getProperty("java.class.path")); } + return createRuleSets(ruleSetReferenceIds).getRuleSetsIterator(); } /** @@ -225,7 +231,7 @@ public class RuleSetFactory { * if unable to find a resource. */ public RuleSet createRuleSet(RuleSetReferenceId ruleSetReferenceId) throws RuleSetNotFoundException { - return createRuleSet(ruleSetReferenceId, false); + return createRuleSet(ruleSetReferenceId, includeDeprecatedRuleReferences); } private RuleSet createRuleSet(RuleSetReferenceId ruleSetReferenceId, boolean withDeprecatedRuleReferences) diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/RulesetsFactoryUtils.java b/pmd-core/src/main/java/net/sourceforge/pmd/RulesetsFactoryUtils.java index e68c6e44ba..69a1ff066c 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/RulesetsFactoryUtils.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/RulesetsFactoryUtils.java @@ -174,6 +174,32 @@ public final class RulesetsFactoryUtils { return new RuleSetFactory(new ResourceLoader(), minimumPriority, warnDeprecated, enableCompatibility); } + /** + * Returns a ruleset factory which uses the classloader for PMD + * classes to resolve resource references. + * + * @param minimumPriority Minimum priority for rules to be included + * @param warnDeprecated If true, print warnings when deprecated rules are included + * @param enableCompatibility If true, rule references to moved rules are mapped to their + * new location if they are known + * @param includeDeprecatedRuleReferences If true, deprecated rule references are retained. Usually, these + * references are ignored, since they indicate renamed/moved rules, and the referenced + * rule is often included in the same ruleset. Enabling this might result in + * duplicated rules. + * + * @return A ruleset factory + * + * @see #createFactory(PMDConfiguration) + */ + public static RuleSetFactory createFactory(RulePriority minimumPriority, + boolean warnDeprecated, + boolean enableCompatibility, + boolean includeDeprecatedRuleReferences) { + + return new RuleSetFactory(new ResourceLoader(), minimumPriority, warnDeprecated, enableCompatibility, + includeDeprecatedRuleReferences); + } + /** * If in debug modus, print the names of the rules. * diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/RuleSetFactoryTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/RuleSetFactoryTest.java index c7ffd3fe81..1732c1f732 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/RuleSetFactoryTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/RuleSetFactoryTest.java @@ -245,6 +245,30 @@ public class RuleSetFactoryTest { assertTrue(logging.getLog().isEmpty()); } + /** + * This is an example of a category (built-in) ruleset, which contains a rule, that has been renamed. + * This means: a rule definition for "NewName" and a rule reference "OldName", that is deprecated + * and exists for backwards compatibility. + * + *

When loading this ruleset at a whole for generating the documentation, we should still + * include the deprecated rule reference, so that we can create a nice documentation. + * + * @throws Exception + */ + @Test + public void testRuleSetWithDeprecatedRenamedRuleForDoc() throws Exception { + RuleSetFactory rsf = RulesetsFactoryUtils.createFactory(RulePriority.LOW, false, false, true); + RuleSet rs = rsf.createRuleSet(createRuleSetReferenceId("\n" + "\n" + + " ruleset desc\n" + + " " + + " " + + " d\n" + " 2\n" + " " + + "")); + assertEquals(2, rs.getRules().size()); + assertNotNull(rs.getRuleByName("NewName")); + assertNotNull(rs.getRuleByName("OldName")); + } + /** * This is an example of a custom user ruleset, that references a rule, that has been renamed. * The user should get a deprecation warning. diff --git a/pmd-doc/src/main/java/net/sourceforge/pmd/docs/GenerateRuleDocsCmd.java b/pmd-doc/src/main/java/net/sourceforge/pmd/docs/GenerateRuleDocsCmd.java index 75c56f7f49..2256a21d18 100644 --- a/pmd-doc/src/main/java/net/sourceforge/pmd/docs/GenerateRuleDocsCmd.java +++ b/pmd-doc/src/main/java/net/sourceforge/pmd/docs/GenerateRuleDocsCmd.java @@ -19,6 +19,7 @@ import java.util.regex.Pattern; import org.apache.commons.io.FilenameUtils; +import net.sourceforge.pmd.RulePriority; import net.sourceforge.pmd.RuleSet; import net.sourceforge.pmd.RuleSetFactory; import net.sourceforge.pmd.RuleSetNotFoundException; @@ -39,7 +40,8 @@ public final class GenerateRuleDocsCmd { Path output = FileSystems.getDefault().getPath(args[0]).resolve("..").toAbsolutePath().normalize(); System.out.println("Generating docs into " + output); - RuleSetFactory ruleSetFactory = RulesetsFactoryUtils.defaultFactory(); + // important: use a RuleSetFactory that includes all rules, e.g. deprecated rule references + RuleSetFactory ruleSetFactory = RulesetsFactoryUtils.createFactory(RulePriority.LOW, false, false, true); Iterator registeredRuleSets = ruleSetFactory.getRegisteredRuleSets(); List additionalRulesets = findAdditionalRulesets(output); diff --git a/pmd-doc/src/test/java/net/sourceforge/pmd/docs/RuleDocGeneratorTest.java b/pmd-doc/src/test/java/net/sourceforge/pmd/docs/RuleDocGeneratorTest.java index 00a84abf6e..5e7561cdf3 100644 --- a/pmd-doc/src/test/java/net/sourceforge/pmd/docs/RuleDocGeneratorTest.java +++ b/pmd-doc/src/test/java/net/sourceforge/pmd/docs/RuleDocGeneratorTest.java @@ -21,6 +21,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; +import net.sourceforge.pmd.RulePriority; import net.sourceforge.pmd.RuleSet; import net.sourceforge.pmd.RuleSetFactory; import net.sourceforge.pmd.RuleSetNotFoundException; @@ -61,7 +62,7 @@ public class RuleDocGeneratorTest { public void testSingleRuleset() throws RuleSetNotFoundException, IOException { RuleDocGenerator generator = new RuleDocGenerator(writer, root); - RuleSetFactory rsf = RulesetsFactoryUtils.defaultFactory(); + RuleSetFactory rsf = RulesetsFactoryUtils.createFactory(RulePriority.LOW, false, false, true); RuleSet ruleset = rsf.createRuleSet("rulesets/ruledoctest/sample.xml"); generator.generate(Arrays.asList(ruleset).iterator(),