[doc] Renamed/Moved rules are missing in documentation (Fixes #2823)

Include deprecated rule references conditionally
when loading rulesets via RuleSetFactory
This commit is contained in:
Andreas Dangel
2020-10-10 17:02:13 +02:00
parent 92bab9b595
commit 69a3914a77
6 changed files with 85 additions and 24 deletions

View File

@ -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

View File

@ -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<RuleSetReferenceId, RuleSet> 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<RuleSet> getRegisteredRuleSets() throws RuleSetNotFoundException {
String rulesetsProperties = null;
try {
List<RuleSetReferenceId> 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<RuleSetReferenceId> 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)

View File

@ -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.
*

View File

@ -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.
*
* <p>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("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<ruleset name=\"test\">\n"
+ " <description>ruleset desc</description>\n"
+ " <rule deprecated=\"true\" ref=\"NewName\" name=\"OldName\"/>"
+ " <rule name=\"NewName\" message=\"m\" class=\"net.sourceforge.pmd.lang.rule.XPathRule\" language=\"dummy\">"
+ " <description>d</description>\n" + " <priority>2</priority>\n" + " </rule>"
+ "</ruleset>"));
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.

View File

@ -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<RuleSet> registeredRuleSets = ruleSetFactory.getRegisteredRuleSets();
List<String> additionalRulesets = findAdditionalRulesets(output);

View File

@ -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(),