Merge pull request #4804 from adangel:issue-4309-xpath-cleanups
[core] XPath cleanups #4804
This commit is contained in:
@ -165,7 +165,6 @@ with a backslash when needed.
|
||||
```xml
|
||||
<rule name="MyXpathRule" ...>
|
||||
<properties>
|
||||
<property name="version" value="2.0" />
|
||||
<property name="intProp" type="List[Integer]" value="1,2,5" description="An IntegerMultiProperty." />
|
||||
<property name="reportedIdentifiers" type="List[String]" value="foo,bar"
|
||||
description="A StringMultiProperty." />
|
||||
|
@ -104,7 +104,7 @@ instead of mentioning the `ref` attribute, it mentions the `class` attribute,
|
||||
with the implementation class of your rule.
|
||||
|
||||
* **For Java rules:** this is the concrete class extending AbstractRule (transitively)
|
||||
* **For XPath rules:** this is `net.sourceforge.pmd.lang.rule.XPathRule`.
|
||||
* **For XPath rules:** this is `net.sourceforge.pmd.lang.rule.xpath.XPathRule`.
|
||||
* **For XPath rules analyzing XML-based languages:** this is `net.sourceforge.pmd.lang.xml.rule.DomXPathRule`.
|
||||
See [XPath rules in XML](pmd_languages_xml.html#xpath-rules-in-xml) for more info.
|
||||
|
||||
@ -128,7 +128,7 @@ Example for XPath rule:
|
||||
<rule name="MyXPathRule"
|
||||
language="java"
|
||||
message="Violation!"
|
||||
class="net.sourceforge.pmd.lang.rule.XPathRule">
|
||||
class="net.sourceforge.pmd.lang.rule.xpath.XPathRule">
|
||||
<description>
|
||||
Description
|
||||
</description>
|
||||
|
@ -27,7 +27,8 @@ with opt-in support for XPath 2.0.
|
||||
See [the Saxonica documentation](https://www.saxonica.com/html/documentation/expressions/xpath31new.html)
|
||||
for an introduction to new features in XPath 3.1.
|
||||
|
||||
The property `version` of {% jdoc core::lang.rule.XPathRule %} is deprecated and will be removed.
|
||||
The property `version` of {% jdoc core::lang.rule.XPathRule %} is deprecated and
|
||||
has been removed with PMD 7.
|
||||
|
||||
|
||||
## DOM representation of ASTs
|
||||
|
@ -127,7 +127,7 @@ copy-paste into your ruleset XML. The resulting element looks like so:
|
||||
<rule name="DontCallBossShort"
|
||||
language="java"
|
||||
message="Boss wants to talk to you."
|
||||
class="net.sourceforge.pmd.lang.rule.XPathRule">
|
||||
class="net.sourceforge.pmd.lang.rule.xpath.XPathRule">
|
||||
<description>
|
||||
TODO
|
||||
</description>
|
||||
|
@ -109,9 +109,11 @@ XPath 2.0 is available in PMD 6 already and can be used right away. PMD 7 will u
|
||||
won't support XPath 1.0 anymore. The difference between XPath 2.0 and XPath 3.1 is not big, so your XPath 2.0
|
||||
can be expected to work in PMD 7 without any further changes. So the migration path is to simply migrate to XPath 2.0.
|
||||
|
||||
After you have migrated your XPath rules to XPath 2.0, remove the "version" property, since that will be removed
|
||||
with PMD 7. PMD 7 by default uses XPath 3.1.
|
||||
See below [XPath](#xpath-migrating-from-10-to-20) for details.
|
||||
After you have migrated your XPath rules to XPath 2.0, remove the "version" property, since that has been removed
|
||||
with PMD 7. PMD 7 by default uses XPath 3.1. See below [XPath](#xpath-migrating-from-10-to-20) for details.
|
||||
|
||||
Then change the `class` attribute of your rule to `net.sourceforge.pmd.lang.rule.xpath.XPathRule` - because the
|
||||
class {%jdoc core::lang.rule.xpath.XPathRule %} has been moved into subpackage {% jdoc_package core::lang.rule.xpath %}.
|
||||
|
||||
There are some general changes for AST nodes regarding the `@Image` attribute.
|
||||
See below [General AST Changes to avoid @Image](#general-ast-changes-to-avoid-image).
|
||||
|
@ -175,6 +175,7 @@ The rules have been moved into categories with PMD 6.
|
||||
* [#3903](https://github.com/pmd/pmd/issues/3903): \[core] Consolidate `n.s.pmd.reporting` package
|
||||
* [#3917](https://github.com/pmd/pmd/issues/3917): \[core] Consolidate `n.s.pmd.lang.rule` package
|
||||
* [#4065](https://github.com/pmd/pmd/issues/4065): \[core] Rename TokenMgrError to LexException, Tokenizer to CpdLexer
|
||||
* [#4309](https://github.com/pmd/pmd/issues/4309): \[core] Cleanups in XPath area
|
||||
* [#4312](https://github.com/pmd/pmd/issues/4312): \[core] Remove unnecessary property `color` and system property `pmd.color` in `TextColorRenderer`
|
||||
* [#4313](https://github.com/pmd/pmd/issues/4313): \[core] Remove support for <lang>-<ruleset> hyphen notation for ruleset references
|
||||
* [#4314](https://github.com/pmd/pmd/issues/4314): \[core] Remove ruleset compatibility filter (RuleSetFactoryCompatibility) and CLI option `--no-ruleset-compatibility`
|
||||
@ -235,6 +236,11 @@ The rules have been moved into categories with PMD 6.
|
||||
See [General AST Changes to avoid @Image]({{ baseurl }}pmd_userdocs_migrating_to_pmd7.html#general-ast-changes-to-avoid-image)
|
||||
in the migration guide for details.
|
||||
|
||||
**XPath Rules**
|
||||
* The property `version` was already deprecated and has finally been removed. Please don't define the version
|
||||
property anymore in your custom XPath rules. By default, the latest XPath version will be used, which
|
||||
is XPath 3.1.
|
||||
|
||||
**Moved classes/consolidated packages**
|
||||
|
||||
* pmd-core
|
||||
@ -252,6 +258,7 @@ in the migration guide for details.
|
||||
* {%jdoc core::reporting.RuleViolation %}
|
||||
* {%jdoc core::reporting.ViolationSuppressor %}
|
||||
* {%jdoc core::reporting.ParametricRuleViolation %} (moved from `net.sourcceforge.pmd.lang.rule`)
|
||||
* {%jdoc core::lang.rule.xpath.XPathRule %} has been moved into subpackage {% jdoc_package core::lang.rule.xpath %}.
|
||||
|
||||
**Internalized classes**
|
||||
|
||||
@ -746,6 +753,7 @@ See also [Detailed Release Notes for PMD 7]({{ baseurl }}pmd_release_notes_pmd7.
|
||||
* [#4204](https://github.com/pmd/pmd/issues/4204): \[core] Provide a CpdAnalysis class as a programmatic entry point into CPD
|
||||
* [#4301](https://github.com/pmd/pmd/issues/4301): \[core] Remove deprecated property concrete classes
|
||||
* [#4302](https://github.com/pmd/pmd/issues/4302): \[core] Migrate Property Framework API to Java 8
|
||||
* [#4309](https://github.com/pmd/pmd/issues/4309): \[core] Cleanups in XPath area
|
||||
* [#4312](https://github.com/pmd/pmd/issues/4312): \[core] Remove unnecessary property `color` and system property `pmd.color` in `TextColorRenderer`
|
||||
* [#4313](https://github.com/pmd/pmd/issues/4313): \[core] Remove support for <lang>-<ruleset> hyphen notation for ruleset references
|
||||
* [#4314](https://github.com/pmd/pmd/issues/4314): \[core] Remove ruleset compatibility filter (RuleSetFactoryCompatibility) and CLI option `--no-ruleset-compatibility`
|
||||
|
@ -106,7 +106,7 @@ private class TestRunAs {
|
||||
since="6.13.0"
|
||||
language="apex"
|
||||
message="Apex test methods should have @isTest annotation."
|
||||
class="net.sourceforge.pmd.lang.rule.XPathRule"
|
||||
class="net.sourceforge.pmd.lang.rule.xpath.XPathRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_bestpractices.html#apexunittestmethodshouldhaveistestannotation">
|
||||
<description>
|
||||
Apex test methods should have `@isTest` annotation instead of the `testMethod` keyword,
|
||||
@ -116,7 +116,6 @@ annotation for test classes and methods.
|
||||
</description>
|
||||
<priority>3</priority>
|
||||
<properties>
|
||||
<property name="version" value="2.0"/>
|
||||
<property name="xpath">
|
||||
<value>
|
||||
<![CDATA[
|
||||
@ -231,7 +230,7 @@ trigger Accounts on Account (before insert, before update, before delete, after
|
||||
since="6.18.0"
|
||||
language="apex"
|
||||
message="Calls to System.debug should specify a logging level."
|
||||
class="net.sourceforge.pmd.lang.rule.XPathRule"
|
||||
class="net.sourceforge.pmd.lang.rule.xpath.XPathRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_bestpractices.html#debugsshoulduselogginglevel">
|
||||
<description>
|
||||
The first parameter of System.debug, when using the signature with two parameters, is a LoggingLevel enum.
|
||||
|
@ -37,7 +37,7 @@ public class fooClass { } // This will be reported unless you change the regex
|
||||
language="apex"
|
||||
since="5.6.0"
|
||||
message="Avoid using 'if...else' statements without curly braces"
|
||||
class="net.sourceforge.pmd.lang.rule.XPathRule"
|
||||
class="net.sourceforge.pmd.lang.rule.xpath.XPathRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_codestyle.html#ifelsestmtsmustusebraces">
|
||||
<description>
|
||||
Avoid using if..else statements without using surrounding braces. If the code formatting
|
||||
@ -74,7 +74,7 @@ else
|
||||
language="apex"
|
||||
since="5.6.0"
|
||||
message="Avoid using if statements without curly braces"
|
||||
class="net.sourceforge.pmd.lang.rule.XPathRule"
|
||||
class="net.sourceforge.pmd.lang.rule.xpath.XPathRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_codestyle.html#ifstmtsmustusebraces">
|
||||
<description>
|
||||
Avoid using if statements without using braces to surround the code block. If the code
|
||||
@ -156,7 +156,7 @@ public class Foo {
|
||||
language="apex"
|
||||
since="5.6.0"
|
||||
message="Avoid using 'for' statements without curly braces"
|
||||
class="net.sourceforge.pmd.lang.rule.XPathRule"
|
||||
class="net.sourceforge.pmd.lang.rule.xpath.XPathRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_codestyle.html#forloopsmustusebraces">
|
||||
<description>
|
||||
Avoid using 'for' statements without using surrounding braces. If the code formatting or
|
||||
@ -270,7 +270,7 @@ public class Foo {
|
||||
language="apex"
|
||||
since="6.7.0"
|
||||
message="Use one statement for each line, it enhances code readability."
|
||||
class="net.sourceforge.pmd.lang.rule.XPathRule"
|
||||
class="net.sourceforge.pmd.lang.rule.xpath.XPathRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_codestyle.html#onedeclarationperline">
|
||||
<description>
|
||||
Apex allows the use of several variables declaration of the same type on one line. However, it
|
||||
@ -338,7 +338,7 @@ public class Foo {
|
||||
language="apex"
|
||||
since="5.6.0"
|
||||
message="Avoid using 'while' statements without curly braces"
|
||||
class="net.sourceforge.pmd.lang.rule.XPathRule"
|
||||
class="net.sourceforge.pmd.lang.rule.xpath.XPathRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_codestyle.html#whileloopsmustusebraces">
|
||||
<description>
|
||||
Avoid using 'while' statements without using braces to surround the code block. If the code
|
||||
|
@ -54,7 +54,7 @@ public class Foo {
|
||||
language="apex"
|
||||
since="6.0.0"
|
||||
message="Avoid directly accessing Trigger.old and Trigger.new"
|
||||
class="net.sourceforge.pmd.lang.rule.XPathRule"
|
||||
class="net.sourceforge.pmd.lang.rule.xpath.XPathRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_errorprone.html#avoiddirectaccesstriggermap">
|
||||
<description>
|
||||
Avoid directly accessing Trigger.old and Trigger.new as it can lead to a bug. Triggers should be bulkified and iterate through the map to handle the actions for each item separately.
|
||||
@ -138,7 +138,7 @@ public without sharing class Foo {
|
||||
language="apex"
|
||||
since="6.0.0"
|
||||
message="Avoid empty catch blocks"
|
||||
class="net.sourceforge.pmd.lang.rule.XPathRule"
|
||||
class="net.sourceforge.pmd.lang.rule.xpath.XPathRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_errorprone.html#emptycatchblock">
|
||||
<description>
|
||||
Empty Catch Block finds instances where an exception is caught, but nothing is done.
|
||||
@ -177,7 +177,7 @@ public void doSomething() {
|
||||
language="apex"
|
||||
since="6.0.0"
|
||||
message="Avoid empty 'if' statements"
|
||||
class="net.sourceforge.pmd.lang.rule.XPathRule"
|
||||
class="net.sourceforge.pmd.lang.rule.xpath.XPathRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_errorprone.html#emptyifstmt">
|
||||
<description>
|
||||
Empty If Statement finds instances where a condition is checked but nothing is done about it.
|
||||
@ -210,7 +210,7 @@ public class Foo {
|
||||
language="apex"
|
||||
since="6.0.0"
|
||||
message="Avoid empty block statements."
|
||||
class="net.sourceforge.pmd.lang.rule.XPathRule"
|
||||
class="net.sourceforge.pmd.lang.rule.xpath.XPathRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_errorprone.html#emptystatementblock">
|
||||
<description>
|
||||
Empty block statements serve no purpose and should be removed.
|
||||
@ -247,7 +247,7 @@ public class Foo {
|
||||
language="apex"
|
||||
since="6.0.0"
|
||||
message="Avoid empty try or finally blocks"
|
||||
class="net.sourceforge.pmd.lang.rule.XPathRule"
|
||||
class="net.sourceforge.pmd.lang.rule.xpath.XPathRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_errorprone.html#emptytryorfinallyblock">
|
||||
<description>
|
||||
Avoid empty try or finally blocks - what's the point?
|
||||
@ -291,7 +291,7 @@ public class Foo {
|
||||
language="apex"
|
||||
since="6.0.0"
|
||||
message="Avoid empty 'while' statements"
|
||||
class="net.sourceforge.pmd.lang.rule.XPathRule"
|
||||
class="net.sourceforge.pmd.lang.rule.xpath.XPathRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_errorprone.html#emptywhilestmt">
|
||||
<description>
|
||||
Empty While Statement finds all instances where a while statement does nothing.
|
||||
@ -445,7 +445,7 @@ public class Foo { // perfect, both methods provided
|
||||
language="apex"
|
||||
since="6.22.0"
|
||||
message="Test methods must be in test classes"
|
||||
class="net.sourceforge.pmd.lang.rule.XPathRule"
|
||||
class="net.sourceforge.pmd.lang.rule.xpath.XPathRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_errorprone.html#testmethodsmustbeintestclasses">
|
||||
<description>
|
||||
Test methods marked as a testMethod or annotated with @IsTest,
|
||||
|
@ -13,7 +13,7 @@ Rules that flag suboptimal code.
|
||||
language="apex"
|
||||
since="6.36.0"
|
||||
message="Avoid debug statements since they impact on performance"
|
||||
class="net.sourceforge.pmd.lang.rule.XPathRule"
|
||||
class="net.sourceforge.pmd.lang.rule.xpath.XPathRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_performance.html#avoiddebugstatements">
|
||||
<description>
|
||||
Debug statements contribute to longer transactions and consume Apex CPU time even when debug logs are not being captured.
|
||||
@ -24,7 +24,6 @@ For other valid use cases that the statement is in fact valid make use of the `@
|
||||
</description>
|
||||
<priority>3</priority>
|
||||
<properties>
|
||||
<property name="version" value="2.0"/>
|
||||
<property name="xpath">
|
||||
<value>
|
||||
<![CDATA[
|
||||
@ -141,7 +140,7 @@ public class Something {
|
||||
language="apex"
|
||||
since="6.40.0"
|
||||
message="DescribeSObjectResult could be being loaded eagerly with all child relationships."
|
||||
class="net.sourceforge.pmd.lang.rule.XPathRule"
|
||||
class="net.sourceforge.pmd.lang.rule.xpath.XPathRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_performance.html#eagerlyloadeddescribesobjectresult">
|
||||
<description>
|
||||
This rule finds `DescribeSObjectResult`s which could have been loaded eagerly via `SObjectType.getDescribe()`.
|
||||
@ -172,7 +171,6 @@ Properties:
|
||||
<priority>3</priority>
|
||||
<properties>
|
||||
<property name="noDefault" type="Boolean" value="false" description="Do not allow SObjectDescribeOptions.DEFAULT option to ensure consistent results no matter where getDescribe is called"/>
|
||||
<property name="version" value="2.0"/>
|
||||
<property name="xpath">
|
||||
<value>
|
||||
<![CDATA[
|
||||
|
@ -10,7 +10,7 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import net.sourceforge.pmd.lang.apex.ast.ApexParserTestBase;
|
||||
import net.sourceforge.pmd.lang.document.FileId;
|
||||
import net.sourceforge.pmd.lang.rule.XPathRule;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.XPathRule;
|
||||
import net.sourceforge.pmd.reporting.Report;
|
||||
|
||||
/**
|
||||
|
@ -7,7 +7,7 @@
|
||||
</description>
|
||||
|
||||
<rule name="ReportAllRootNodes" language="dummy" since="1.0" message="Violation from ReportAllRootNodes"
|
||||
class="net.sourceforge.pmd.lang.rule.XPathRule"
|
||||
class="net.sourceforge.pmd.lang.rule.xpath.XPathRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/rules/test/TestRuleset3.xml#Ruleset3Rule1">
|
||||
<description>Just for test</description>
|
||||
<priority>3</priority>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<description/>
|
||||
<rule name="ExceptionThrowingRule"
|
||||
language="java"
|
||||
class="net.sourceforge.pmd.lang.rule.XPathRule">
|
||||
class="net.sourceforge.pmd.lang.rule.xpath.XPathRule">
|
||||
<description>Use this rule to produce a processing error.</description>
|
||||
<priority>3</priority>
|
||||
<properties>
|
||||
|
@ -0,0 +1,8 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.rule;
|
||||
|
||||
public class XPathRule extends net.sourceforge.pmd.lang.rule.xpath.XPathRule {
|
||||
}
|
@ -0,0 +1,172 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
// This class has been taken from 7.0.0-SNAPSHOT
|
||||
// Changes: not final anymore to allow a subclass in the old package
|
||||
|
||||
package net.sourceforge.pmd.lang.rule.xpath;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.exception.ContextedRuntimeException;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.sourceforge.pmd.Rule;
|
||||
import net.sourceforge.pmd.RuleContext;
|
||||
import net.sourceforge.pmd.lang.LanguageProcessor;
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
import net.sourceforge.pmd.lang.rule.AbstractRule;
|
||||
import net.sourceforge.pmd.lang.rule.RuleTargetSelector;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.internal.DeprecatedAttrLogger;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.internal.SaxonXPathRuleQuery;
|
||||
import net.sourceforge.pmd.properties.PropertyDescriptor;
|
||||
import net.sourceforge.pmd.properties.PropertyFactory;
|
||||
|
||||
|
||||
/**
|
||||
* Rule that tries to match an XPath expression against a DOM view of an AST.
|
||||
*/
|
||||
public /*final*/ class XPathRule extends AbstractRule {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(XPathRule.class);
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #XPathRule(XPathVersion, String)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static final PropertyDescriptor<String> XPATH_DESCRIPTOR =
|
||||
PropertyFactory.stringProperty("xpath")
|
||||
.desc("XPath expression")
|
||||
.defaultValue("")
|
||||
.build();
|
||||
|
||||
/**
|
||||
* This is initialized only once when calling {@link #apply(Node, RuleContext)} or {@link #getTargetSelector()}.
|
||||
*/
|
||||
private SaxonXPathRuleQuery xpathRuleQuery;
|
||||
|
||||
|
||||
// this is shared with rules forked by deepCopy, used by the XPathRuleQuery
|
||||
private DeprecatedAttrLogger attrLogger = DeprecatedAttrLogger.create(this);
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated This is now only used by the ruleset loader. When
|
||||
* we have syntactic sugar for XPath rules in the XML, we won't
|
||||
* need this anymore.
|
||||
*/
|
||||
@Deprecated
|
||||
public XPathRule() {
|
||||
definePropertyDescriptor(XPATH_DESCRIPTOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a new XPath rule with the given version + expression
|
||||
*
|
||||
* @param version Version of the XPath language
|
||||
* @param expression XPath expression
|
||||
*
|
||||
* @throws NullPointerException If any of the arguments is null
|
||||
*/
|
||||
public XPathRule(XPathVersion version, String expression) {
|
||||
this();
|
||||
|
||||
Objects.requireNonNull(version, "XPath version is null");
|
||||
Objects.requireNonNull(expression, "XPath expression is null");
|
||||
|
||||
setProperty(XPathRule.XPATH_DESCRIPTOR, expression);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Rule deepCopy() {
|
||||
XPathRule rule = (XPathRule) super.deepCopy();
|
||||
rule.attrLogger = this.attrLogger;
|
||||
return rule;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the XPath expression that implements this rule.
|
||||
*/
|
||||
public String getXPathExpression() {
|
||||
return getProperty(XPATH_DESCRIPTOR);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void apply(Node target, RuleContext ctx) {
|
||||
SaxonXPathRuleQuery query = getQueryMaybeInitialize();
|
||||
|
||||
List<Node> nodesWithViolation;
|
||||
try {
|
||||
nodesWithViolation = query.evaluate(target);
|
||||
} catch (PmdXPathException e) {
|
||||
throw addExceptionContext(e);
|
||||
}
|
||||
|
||||
for (Node nodeWithViolation : nodesWithViolation) {
|
||||
addViolation(ctx, nodeWithViolation, nodeWithViolation.getImage());
|
||||
}
|
||||
}
|
||||
|
||||
private ContextedRuntimeException addExceptionContext(PmdXPathException e) {
|
||||
return e.addRuleName(getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(LanguageProcessor languageProcessor) {
|
||||
String xpath = getXPathExpression();
|
||||
XPathVersion version = XPathVersion.DEFAULT;
|
||||
|
||||
try {
|
||||
xpathRuleQuery = new SaxonXPathRuleQuery(xpath,
|
||||
version,
|
||||
getPropertiesByPropertyDescriptor(),
|
||||
languageProcessor.services().getXPathHandler(),
|
||||
attrLogger);
|
||||
} catch (PmdXPathException e) {
|
||||
throw addExceptionContext(e);
|
||||
}
|
||||
}
|
||||
|
||||
private SaxonXPathRuleQuery getQueryMaybeInitialize() throws PmdXPathException {
|
||||
if (xpathRuleQuery == null) {
|
||||
throw new IllegalStateException("Not initialized");
|
||||
}
|
||||
return xpathRuleQuery;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected @NonNull RuleTargetSelector buildTargetSelector() {
|
||||
|
||||
List<String> visits = getQueryMaybeInitialize().getRuleChainVisits();
|
||||
|
||||
logXPathRuleChainUsage(!visits.isEmpty());
|
||||
|
||||
return visits.isEmpty() ? RuleTargetSelector.forRootOnly()
|
||||
: RuleTargetSelector.forXPathNames(visits);
|
||||
}
|
||||
|
||||
|
||||
private void logXPathRuleChainUsage(boolean usesRuleChain) {
|
||||
LOG.debug("{} rule chain for XPath rule: {} ({})",
|
||||
usesRuleChain ? "Using" : "no",
|
||||
getName(),
|
||||
getRuleSetName());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String dysfunctionReason() {
|
||||
if (StringUtils.isBlank(getXPathExpression())) {
|
||||
return "Missing XPath expression";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -27,6 +27,7 @@ import net.sourceforge.pmd.lang.LanguageVersion;
|
||||
import net.sourceforge.pmd.lang.document.FileId;
|
||||
import net.sourceforge.pmd.lang.document.TextFile;
|
||||
import net.sourceforge.pmd.lang.rule.internal.RuleSetReference;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.XPathRule;
|
||||
|
||||
/**
|
||||
* This class represents a collection of rules along with some optional filter
|
||||
|
@ -2,12 +2,9 @@
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.rule;
|
||||
package net.sourceforge.pmd.lang.rule.xpath;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@ -16,11 +13,11 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.sourceforge.pmd.annotation.DeprecatedUntil700;
|
||||
import net.sourceforge.pmd.lang.LanguageProcessor;
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.PmdXPathException;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.XPathVersion;
|
||||
import net.sourceforge.pmd.lang.rule.AbstractRule;
|
||||
import net.sourceforge.pmd.lang.rule.Rule;
|
||||
import net.sourceforge.pmd.lang.rule.RuleTargetSelector;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.internal.DeprecatedAttrLogger;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.internal.SaxonXPathRuleQuery;
|
||||
import net.sourceforge.pmd.properties.PropertyDescriptor;
|
||||
@ -35,8 +32,6 @@ public final class XPathRule extends AbstractRule {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(XPathRule.class);
|
||||
|
||||
// TODO move to XPath subpackage
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #XPathRule(XPathVersion, String)}
|
||||
*/
|
||||
@ -47,17 +42,6 @@ public final class XPathRule extends AbstractRule {
|
||||
.defaultValue("")
|
||||
.build();
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #XPathRule(XPathVersion, String)}
|
||||
*/
|
||||
@Deprecated
|
||||
@DeprecatedUntil700
|
||||
public static final PropertyDescriptor<XPathVersion> VERSION_DESCRIPTOR =
|
||||
PropertyFactory.enumProperty("version", getXPathVersions())
|
||||
.desc("XPath specification version")
|
||||
.defaultValue(XPathVersion.DEFAULT)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* This is initialized only once when calling {@link #apply(Node, RuleContext)} or {@link #getTargetSelector()}.
|
||||
*/
|
||||
@ -76,7 +60,6 @@ public final class XPathRule extends AbstractRule {
|
||||
@Deprecated
|
||||
public XPathRule() {
|
||||
definePropertyDescriptor(XPATH_DESCRIPTOR);
|
||||
definePropertyDescriptor(VERSION_DESCRIPTOR);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -94,7 +77,6 @@ public final class XPathRule extends AbstractRule {
|
||||
Objects.requireNonNull(expression, "XPath expression is null");
|
||||
|
||||
setProperty(XPathRule.XPATH_DESCRIPTOR, expression);
|
||||
setProperty(XPathRule.VERSION_DESCRIPTOR, XPathVersion.ofId(version.getXmlName()));
|
||||
}
|
||||
|
||||
|
||||
@ -105,14 +87,6 @@ public final class XPathRule extends AbstractRule {
|
||||
return rule;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the version for this rule. Returns null if this is not
|
||||
* set or invalid.
|
||||
*/
|
||||
public XPathVersion getVersion() {
|
||||
return getProperty(VERSION_DESCRIPTOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the XPath expression that implements this rule.
|
||||
*/
|
||||
@ -144,11 +118,7 @@ public final class XPathRule extends AbstractRule {
|
||||
@Override
|
||||
public void initialize(LanguageProcessor languageProcessor) {
|
||||
String xpath = getXPathExpression();
|
||||
XPathVersion version = getVersion();
|
||||
|
||||
if (version == null) {
|
||||
throw new IllegalStateException("Invalid XPath version, should have been caught by Rule::dysfunctionReason");
|
||||
}
|
||||
XPathVersion version = XPathVersion.DEFAULT;
|
||||
|
||||
try {
|
||||
xpathRuleQuery = new SaxonXPathRuleQuery(xpath,
|
||||
@ -182,9 +152,8 @@ public final class XPathRule extends AbstractRule {
|
||||
|
||||
|
||||
private void logXPathRuleChainUsage(boolean usesRuleChain) {
|
||||
LOG.debug("{} rule chain for XPath {} rule: {} ({})",
|
||||
LOG.debug("{} rule chain for XPath rule: {} ({})",
|
||||
usesRuleChain ? "Using" : "no",
|
||||
getProperty(XPathRule.VERSION_DESCRIPTOR),
|
||||
getName(),
|
||||
getRuleSetName());
|
||||
}
|
||||
@ -192,19 +161,9 @@ public final class XPathRule extends AbstractRule {
|
||||
|
||||
@Override
|
||||
public String dysfunctionReason() {
|
||||
if (getVersion() == null) {
|
||||
return "Invalid XPath version '" + getProperty(VERSION_DESCRIPTOR) + "'";
|
||||
} else if (StringUtils.isBlank(getXPathExpression())) {
|
||||
if (StringUtils.isBlank(getXPathExpression())) {
|
||||
return "Missing XPath expression";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Map<String, XPathVersion> getXPathVersions() {
|
||||
Map<String, XPathVersion> tmp = new HashMap<>();
|
||||
for (XPathVersion v : XPathVersion.values()) {
|
||||
tmp.put(v.getXmlName(), v);
|
||||
}
|
||||
return Collections.unmodifiableMap(tmp);
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.rule.xpath.impl;
|
||||
|
||||
import net.sourceforge.pmd.lang.Language;
|
||||
|
||||
import net.sf.saxon.lib.ExtensionFunctionDefinition;
|
||||
import net.sf.saxon.om.StructuredQName;
|
||||
|
||||
|
||||
/**
|
||||
* Base impl for an XPath function definition.
|
||||
* This uses Saxon API.
|
||||
*
|
||||
* @since 7.0.0
|
||||
*/
|
||||
public abstract class AbstractXPathFunctionDef extends ExtensionFunctionDefinition {
|
||||
|
||||
private static final String PMD_URI_PREFIX = "http://pmd.sourceforge.net/";
|
||||
private final StructuredQName qname;
|
||||
|
||||
private AbstractXPathFunctionDef(String localName, String namespacePrefix, String uri) {
|
||||
this.qname = new StructuredQName(namespacePrefix, uri, localName);
|
||||
}
|
||||
|
||||
protected AbstractXPathFunctionDef(String localName) {
|
||||
this(localName, "pmd", PMD_URI_PREFIX + "pmd-core");
|
||||
}
|
||||
|
||||
protected AbstractXPathFunctionDef(String localName, Language language) {
|
||||
this(localName, "pmd-" + language.getId(), PMD_URI_PREFIX + "pmd-" + language.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public final StructuredQName getFunctionQName() {
|
||||
return qname;
|
||||
}
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.rule.xpath.impl;
|
||||
|
||||
import javax.xml.namespace.QName;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import net.sourceforge.pmd.lang.Language;
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
|
||||
/**
|
||||
* Base impl for an XPath function definition.
|
||||
*
|
||||
* @since 7.0.0
|
||||
*/
|
||||
public abstract class XPathFunctionDefinition {
|
||||
|
||||
private static final String PMD_URI_PREFIX = "http://pmd.sourceforge.net/";
|
||||
private final QName qname;
|
||||
|
||||
private XPathFunctionDefinition(String localName, String namespacePrefix, String uri) {
|
||||
this.qname = new QName(uri, localName, namespacePrefix);
|
||||
}
|
||||
|
||||
protected XPathFunctionDefinition(String localName) {
|
||||
this(localName, "pmd", PMD_URI_PREFIX + "pmd-core");
|
||||
}
|
||||
|
||||
protected XPathFunctionDefinition(String localName, Language language) {
|
||||
this(localName, "pmd-" + language.getId(), PMD_URI_PREFIX + "pmd-" + language.getId());
|
||||
}
|
||||
|
||||
public final QName getQName() {
|
||||
return qname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the types of the function arguments. By default, an empty array is returned, indicating
|
||||
* that the function takes no arguments.
|
||||
*/
|
||||
public Type[] getArgumentTypes() {
|
||||
return new Type[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the return type of the function.
|
||||
*/
|
||||
public abstract Type getResultType();
|
||||
|
||||
/**
|
||||
* If the function depends on the context item, then
|
||||
* this method should return {@code true}.
|
||||
*
|
||||
* <p>Note: Only if this is true, the contextNode parameter will be present in the
|
||||
* {@link FunctionCall#call(Node, Object[])} method.
|
||||
*/
|
||||
public boolean dependsOnContext() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a call on this function. This method is called, when a function call
|
||||
* is found in the XPath expression.
|
||||
*/
|
||||
public abstract FunctionCall makeCallExpression();
|
||||
|
||||
/**
|
||||
* Supported types of a custom XPath function. These can be used as {@link #getResultType() result types}
|
||||
* or {@link #getArgumentTypes() argument types}.
|
||||
*/
|
||||
public enum Type {
|
||||
/** Represents {@link String}. */
|
||||
SINGLE_STRING,
|
||||
/** Represents {@link Boolean}. */
|
||||
SINGLE_BOOLEAN,
|
||||
/** Represents {@link Integer}. */
|
||||
SINGLE_INTEGER,
|
||||
/** Represents any node. Usually used as an argument type. */
|
||||
SINGLE_ELEMENT,
|
||||
/** Represents a {@link java.util.List} of {@link String}, potentially empty. */
|
||||
STRING_SEQUENCE,
|
||||
/** Represents a {@link java.util.Optional} {@link String}. */
|
||||
OPTIONAL_STRING,
|
||||
/** Represents a {@link java.util.Optional} {@link Double}. */
|
||||
OPTIONAL_DECIMAL,
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the actual implementation of a custom XPath function.
|
||||
*/
|
||||
public interface FunctionCall {
|
||||
/**
|
||||
* This method is called at runtime to evaluate the XPath function expression.
|
||||
*
|
||||
* @param contextNode the context node or {@code null}, if this function doesn't depend on the context.
|
||||
* See {@link XPathFunctionDefinition#dependsOnContext()}.
|
||||
* @param arguments The arguments converted as the corresponding java types.
|
||||
* See {@link XPathFunctionDefinition#getArgumentTypes()}.
|
||||
* @return The result of the function. This should be the corresponding java type of
|
||||
* {@link XPathFunctionDefinition#getResultType()}.
|
||||
* @throws XPathFunctionException when any problem during evaluation occurs, like invalid arguments.
|
||||
*/
|
||||
Object call(@Nullable Node contextNode, Object[] arguments) throws XPathFunctionException;
|
||||
|
||||
/**
|
||||
* This is called once before the function is evaluated. It can be used to optimize the
|
||||
* implementation by doing expensive operations only once and cache the result.
|
||||
* This is useful, if the argument of the function is of type {@link String} and is provided
|
||||
* as a String literal in the XPath expression.
|
||||
*
|
||||
* <p>This is an optional step. The default implementation does nothing.
|
||||
*
|
||||
* @param arguments The arguments converted as the corresponding java types.
|
||||
* See {@link XPathFunctionDefinition#getArgumentTypes()}.
|
||||
* Note: This array might contain {@code null} elements, if the values are
|
||||
* not known yet because they are dynamic. Only literal values are available.
|
||||
* @throws XPathFunctionException when any problem during initialization occurs, like invalid arguments.
|
||||
*/
|
||||
default void staticInit(Object[] arguments) throws XPathFunctionException {
|
||||
// default implementation does nothing
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.rule.xpath.impl;
|
||||
|
||||
/**
|
||||
* Indicates a problem during the execution of a custom
|
||||
* XPath function.
|
||||
*/
|
||||
public class XPathFunctionException extends Exception {
|
||||
public XPathFunctionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public XPathFunctionException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -11,8 +11,6 @@ import java.util.Set;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.internal.DefaultXPathFunctions;
|
||||
import net.sourceforge.pmd.util.CollectionUtil;
|
||||
|
||||
import net.sf.saxon.lib.ExtensionFunctionDefinition;
|
||||
|
||||
|
||||
/**
|
||||
* Interface for performing Language specific XPath handling, such as
|
||||
@ -24,7 +22,7 @@ public interface XPathHandler {
|
||||
* Returns the set of extension functions for this language module.
|
||||
* These are the additional functions available in XPath queries.
|
||||
*/
|
||||
Set<ExtensionFunctionDefinition> getRegisteredExtensionFunctions();
|
||||
Set<XPathFunctionDefinition> getRegisteredExtensionFunctions();
|
||||
|
||||
|
||||
static XPathHandler noFunctionDefinitions() {
|
||||
@ -34,8 +32,8 @@ public interface XPathHandler {
|
||||
/**
|
||||
* Returns a default XPath handler.
|
||||
*/
|
||||
static XPathHandler getHandlerForFunctionDefs(ExtensionFunctionDefinition first, ExtensionFunctionDefinition... defs) {
|
||||
Set<ExtensionFunctionDefinition> set = new HashSet<>(CollectionUtil.setOf(first, defs));
|
||||
static XPathHandler getHandlerForFunctionDefs(XPathFunctionDefinition first, XPathFunctionDefinition... defs) {
|
||||
Set<XPathFunctionDefinition> set = new HashSet<>(CollectionUtil.setOf(first, defs));
|
||||
set.addAll(DefaultXPathFunctions.getDefaultFunctions());
|
||||
|
||||
return () -> Collections.unmodifiableSet(set);
|
||||
|
@ -7,23 +7,15 @@ package net.sourceforge.pmd.lang.rule.xpath.internal;
|
||||
import java.util.function.ToIntFunction;
|
||||
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.impl.AbstractXPathFunctionDef;
|
||||
|
||||
import net.sf.saxon.expr.XPathContext;
|
||||
import net.sf.saxon.lib.ExtensionFunctionCall;
|
||||
import net.sf.saxon.om.Sequence;
|
||||
import net.sf.saxon.pattern.NodeKindTest;
|
||||
import net.sf.saxon.trans.XPathException;
|
||||
import net.sf.saxon.type.Type;
|
||||
import net.sf.saxon.value.Int64Value;
|
||||
import net.sf.saxon.value.SequenceType;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.impl.XPathFunctionDefinition;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.impl.XPathFunctionException;
|
||||
|
||||
/**
|
||||
* A function that returns the current file name.
|
||||
*
|
||||
* @author Clément Fournier
|
||||
*/
|
||||
public final class CoordinateXPathFunction extends AbstractXPathFunctionDef {
|
||||
public final class CoordinateXPathFunction extends XPathFunctionDefinition {
|
||||
|
||||
public static final CoordinateXPathFunction START_LINE =
|
||||
new CoordinateXPathFunction("startLine", Node::getBeginLine);
|
||||
@ -34,9 +26,7 @@ public final class CoordinateXPathFunction extends AbstractXPathFunctionDef {
|
||||
public static final CoordinateXPathFunction END_COLUMN =
|
||||
new CoordinateXPathFunction("endColumn", Node::getEndColumn);
|
||||
|
||||
private static final SequenceType[] A_SINGLE_ELEMENT = {
|
||||
NodeKindTest.makeNodeKindTest(Type.ELEMENT).one(),
|
||||
};
|
||||
private static final Type[] A_SINGLE_ELEMENT = { Type.SINGLE_ELEMENT };
|
||||
public static final String PMD_NODE_USER_DATA = "pmd.node";
|
||||
private final ToIntFunction<Node> getter;
|
||||
|
||||
@ -46,32 +36,26 @@ public final class CoordinateXPathFunction extends AbstractXPathFunctionDef {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SequenceType[] getArgumentTypes() {
|
||||
public Type[] getArgumentTypes() {
|
||||
return A_SINGLE_ELEMENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SequenceType getResultType(SequenceType[] suppliedArgumentTypes) {
|
||||
return SequenceType.SINGLE_INTEGER;
|
||||
public Type getResultType() {
|
||||
return Type.SINGLE_INTEGER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtensionFunctionCall makeCallExpression() {
|
||||
return new ExtensionFunctionCall() {
|
||||
|
||||
@Override
|
||||
public Sequence call(XPathContext context, Sequence[] arguments) throws XPathException {
|
||||
Node node = XPathElementToNodeHelper.itemToNode(arguments[0]);
|
||||
if (node == null) {
|
||||
throw new XPathException(
|
||||
"Cannot call function '" + getFunctionQName().getLocalPart()
|
||||
+ "' on argument " + arguments[0]
|
||||
);
|
||||
}
|
||||
return Int64Value.makeIntegerValue(getter.applyAsInt(node));
|
||||
public FunctionCall makeCallExpression() {
|
||||
return (contextNode, arguments) -> {
|
||||
Node node = XPathElementToNodeHelper.itemToNode(arguments[0]);
|
||||
if (node == null) {
|
||||
throw new XPathFunctionException(
|
||||
"Cannot call function '" + getQName().getLocalPart()
|
||||
+ "' on argument " + arguments[0]
|
||||
);
|
||||
}
|
||||
return getter.applyAsInt(node);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -7,16 +7,15 @@ package net.sourceforge.pmd.lang.rule.xpath.internal;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import net.sourceforge.pmd.lang.rule.xpath.impl.XPathFunctionDefinition;
|
||||
import net.sourceforge.pmd.util.CollectionUtil;
|
||||
|
||||
import net.sf.saxon.lib.ExtensionFunctionDefinition;
|
||||
|
||||
/**
|
||||
* Default XPath functions provided by pmd-core.
|
||||
*/
|
||||
public final class DefaultXPathFunctions {
|
||||
|
||||
private static final Set<ExtensionFunctionDefinition> DEFAULTS =
|
||||
private static final Set<XPathFunctionDefinition> DEFAULTS =
|
||||
CollectionUtil.immutableSetOf(
|
||||
FileNameXPathFunction.INSTANCE,
|
||||
CoordinateXPathFunction.START_LINE,
|
||||
@ -29,7 +28,7 @@ public final class DefaultXPathFunctions {
|
||||
// utility class
|
||||
}
|
||||
|
||||
public static Set<ExtensionFunctionDefinition> getDefaultFunctions() {
|
||||
public static Set<XPathFunctionDefinition> getDefaultFunctions() {
|
||||
return DEFAULTS;
|
||||
}
|
||||
}
|
||||
|
@ -11,8 +11,8 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.sourceforge.pmd.lang.rule.Rule;
|
||||
import net.sourceforge.pmd.lang.rule.XPathRule;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.Attribute;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.XPathRule;
|
||||
|
||||
/**
|
||||
* Records usages of deprecated attributes in XPath rules. This needs
|
||||
|
@ -6,23 +6,16 @@ package net.sourceforge.pmd.lang.rule.xpath.internal;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
import net.sourceforge.pmd.lang.ast.RootNode;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.impl.AbstractXPathFunctionDef;
|
||||
|
||||
import net.sf.saxon.expr.XPathContext;
|
||||
import net.sf.saxon.lib.ExtensionFunctionCall;
|
||||
import net.sf.saxon.om.Sequence;
|
||||
import net.sf.saxon.trans.XPathException;
|
||||
import net.sf.saxon.value.SequenceType;
|
||||
import net.sf.saxon.value.StringValue;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.impl.XPathFunctionDefinition;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.impl.XPathFunctionException;
|
||||
|
||||
/**
|
||||
* A function that returns the current file name.
|
||||
*
|
||||
* @author Clément Fournier
|
||||
*/
|
||||
public final class FileNameXPathFunction extends AbstractXPathFunctionDef {
|
||||
public final class FileNameXPathFunction extends XPathFunctionDefinition {
|
||||
|
||||
public static final FileNameXPathFunction INSTANCE = new FileNameXPathFunction();
|
||||
|
||||
@ -31,36 +24,31 @@ public final class FileNameXPathFunction extends AbstractXPathFunctionDef {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SequenceType[] getArgumentTypes() {
|
||||
return new SequenceType[0];
|
||||
public Type getResultType() {
|
||||
return Type.SINGLE_STRING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SequenceType getResultType(SequenceType[] suppliedArgumentTypes) {
|
||||
return SequenceType.STRING_SEQUENCE;
|
||||
public boolean dependsOnContext() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtensionFunctionCall makeCallExpression() {
|
||||
return new ExtensionFunctionCall() {
|
||||
|
||||
@Override
|
||||
public Sequence call(XPathContext context, Sequence[] arguments) throws XPathException {
|
||||
Node node = XPathElementToNodeHelper.itemToNode(context.getContextItem());
|
||||
if (node == null) {
|
||||
throw new XPathException(
|
||||
"Cannot call function '" + getFunctionQName().getLocalPart()
|
||||
+ "' with context item " + context.getContextItem()
|
||||
);
|
||||
}
|
||||
RootNode root = node.getRoot();
|
||||
Objects.requireNonNull(root, "No root node in tree?");
|
||||
|
||||
String fileName = root.getTextDocument().getFileId().getFileName();
|
||||
Objects.requireNonNull(fileName, "File name was not set");
|
||||
|
||||
return new StringValue(fileName);
|
||||
public FunctionCall makeCallExpression() {
|
||||
return (node, arguments) -> {
|
||||
if (node == null) {
|
||||
throw new XPathFunctionException(
|
||||
"Cannot call function '" + getQName().getLocalPart()
|
||||
+ "' without context item"
|
||||
);
|
||||
}
|
||||
RootNode root = node.getRoot();
|
||||
Objects.requireNonNull(root, "No root node in tree?");
|
||||
|
||||
String fileName = root.getTextDocument().getFileId().getFileName();
|
||||
Objects.requireNonNull(fileName, "File name was not set");
|
||||
|
||||
return fileName;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,172 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.rule.xpath.internal;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.xml.namespace.QName;
|
||||
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.impl.XPathFunctionDefinition;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.impl.XPathFunctionException;
|
||||
|
||||
import net.sf.saxon.expr.Expression;
|
||||
import net.sf.saxon.expr.StaticContext;
|
||||
import net.sf.saxon.expr.StringLiteral;
|
||||
import net.sf.saxon.expr.XPathContext;
|
||||
import net.sf.saxon.lib.ExtensionFunctionCall;
|
||||
import net.sf.saxon.lib.ExtensionFunctionDefinition;
|
||||
import net.sf.saxon.om.EmptyAtomicSequence;
|
||||
import net.sf.saxon.om.Sequence;
|
||||
import net.sf.saxon.om.StructuredQName;
|
||||
import net.sf.saxon.pattern.NodeKindTest;
|
||||
import net.sf.saxon.trans.XPathException;
|
||||
import net.sf.saxon.value.BigDecimalValue;
|
||||
import net.sf.saxon.value.BooleanValue;
|
||||
import net.sf.saxon.value.EmptySequence;
|
||||
import net.sf.saxon.value.Int64Value;
|
||||
import net.sf.saxon.value.SequenceExtent;
|
||||
import net.sf.saxon.value.SequenceType;
|
||||
import net.sf.saxon.value.StringValue;
|
||||
|
||||
/**
|
||||
* Converts PMD's {@link XPathFunctionDefinition} into Saxon's {@link ExtensionFunctionDefinition}.
|
||||
*/
|
||||
public class SaxonExtensionFunctionDefinitionAdapter extends ExtensionFunctionDefinition {
|
||||
private static final SequenceType SINGLE_ELEMENT_SEQUENCE_TYPE = NodeKindTest.ELEMENT.one();
|
||||
|
||||
private final XPathFunctionDefinition definition;
|
||||
|
||||
public SaxonExtensionFunctionDefinitionAdapter(XPathFunctionDefinition definition) {
|
||||
this.definition = definition;
|
||||
}
|
||||
|
||||
private SequenceType convertToSequenceType(XPathFunctionDefinition.Type type) {
|
||||
switch (type) {
|
||||
case SINGLE_STRING: return SequenceType.SINGLE_STRING;
|
||||
case SINGLE_BOOLEAN: return SequenceType.SINGLE_BOOLEAN;
|
||||
case SINGLE_ELEMENT: return SINGLE_ELEMENT_SEQUENCE_TYPE;
|
||||
case SINGLE_INTEGER: return SequenceType.SINGLE_INTEGER;
|
||||
case STRING_SEQUENCE: return SequenceType.STRING_SEQUENCE;
|
||||
case OPTIONAL_STRING: return SequenceType.OPTIONAL_STRING;
|
||||
case OPTIONAL_DECIMAL: return SequenceType.OPTIONAL_DECIMAL;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Type " + type + " is not supported");
|
||||
}
|
||||
}
|
||||
|
||||
private SequenceType[] convertToSequenceTypes(XPathFunctionDefinition.Type[] types) {
|
||||
SequenceType[] result = new SequenceType[types.length];
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
result[i] = convertToSequenceType(types[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StructuredQName getFunctionQName() {
|
||||
QName qName = definition.getQName();
|
||||
return new StructuredQName(qName.getPrefix(), qName.getNamespaceURI(), qName.getLocalPart());
|
||||
}
|
||||
|
||||
@Override
|
||||
public SequenceType[] getArgumentTypes() {
|
||||
return convertToSequenceTypes(definition.getArgumentTypes());
|
||||
}
|
||||
|
||||
@Override
|
||||
public SequenceType getResultType(SequenceType[] suppliedArgumentTypes) {
|
||||
return convertToSequenceType(definition.getResultType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dependsOnFocus() {
|
||||
return definition.dependsOnContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtensionFunctionCall makeCallExpression() {
|
||||
XPathFunctionDefinition.FunctionCall call = definition.makeCallExpression();
|
||||
return new ExtensionFunctionCall() {
|
||||
@Override
|
||||
public Expression rewrite(StaticContext context, Expression[] arguments) throws XPathException {
|
||||
Object[] convertedArguments = new Object[definition.getArgumentTypes().length];
|
||||
for (int i = 0; i < convertedArguments.length; i++) {
|
||||
if (arguments[i] instanceof StringLiteral) {
|
||||
convertedArguments[i] = ((StringLiteral) arguments[i]).getStringValue();
|
||||
}
|
||||
}
|
||||
try {
|
||||
call.staticInit(convertedArguments);
|
||||
} catch (XPathFunctionException e) {
|
||||
XPathException xPathException = new XPathException(e);
|
||||
xPathException.setIsStaticError(true);
|
||||
throw xPathException;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Sequence call(XPathContext context, Sequence[] arguments) throws XPathException {
|
||||
Node contextNode = null;
|
||||
if (definition.dependsOnContext()) {
|
||||
contextNode = XPathElementToNodeHelper.itemToNode(context.getContextItem());
|
||||
}
|
||||
Object[] convertedArguments = new Object[definition.getArgumentTypes().length];
|
||||
for (int i = 0; i < convertedArguments.length; i++) {
|
||||
switch (definition.getArgumentTypes()[i]) {
|
||||
case SINGLE_STRING:
|
||||
convertedArguments[i] = arguments[i].head().getStringValue();
|
||||
break;
|
||||
case SINGLE_ELEMENT:
|
||||
convertedArguments[i] = arguments[i].head();
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Don't know how to convert argument type " + definition.getArgumentTypes()[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Object result = null;
|
||||
try {
|
||||
result = call.call(contextNode, convertedArguments);
|
||||
} catch (XPathFunctionException e) {
|
||||
throw new XPathException(e);
|
||||
}
|
||||
Sequence convertedResult = null;
|
||||
switch (definition.getResultType()) {
|
||||
case SINGLE_BOOLEAN:
|
||||
convertedResult = BooleanValue.get((Boolean) result);
|
||||
break;
|
||||
case SINGLE_INTEGER:
|
||||
convertedResult = Int64Value.makeIntegerValue((Integer) result);
|
||||
break;
|
||||
case SINGLE_STRING:
|
||||
convertedResult = new StringValue((String) result);
|
||||
break;
|
||||
case OPTIONAL_STRING:
|
||||
convertedResult = result instanceof Optional && ((Optional<String>) result).isPresent()
|
||||
? new StringValue(((Optional<String>) result).get())
|
||||
: EmptyAtomicSequence.INSTANCE;
|
||||
break;
|
||||
case STRING_SEQUENCE:
|
||||
convertedResult = result instanceof List
|
||||
? new SequenceExtent(((List<String>) result).stream().map(StringValue::new).collect(Collectors.toList()))
|
||||
: EmptySequence.getInstance();
|
||||
break;
|
||||
case OPTIONAL_DECIMAL:
|
||||
convertedResult = result instanceof Optional && ((Optional<Double>) result).isPresent()
|
||||
? new BigDecimalValue(((Optional<Double>) result).get())
|
||||
: EmptySequence.getInstance();
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Don't know how to convert result type " + definition.getResultType());
|
||||
}
|
||||
return convertedResult;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -17,10 +17,10 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
import net.sourceforge.pmd.lang.ast.RootNode;
|
||||
import net.sourceforge.pmd.lang.rule.XPathRule;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.PmdXPathException;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.PmdXPathException.Phase;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.XPathVersion;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.impl.XPathFunctionDefinition;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.impl.XPathHandler;
|
||||
import net.sourceforge.pmd.properties.PropertyDescriptor;
|
||||
import net.sourceforge.pmd.util.DataMap;
|
||||
@ -194,12 +194,13 @@ public class SaxonXPathRuleQuery {
|
||||
|
||||
for (final PropertyDescriptor<?> propertyDescriptor : properties.keySet()) {
|
||||
final String name = propertyDescriptor.name();
|
||||
if (!"xpath".equals(name) && !XPathRule.VERSION_DESCRIPTOR.name().equals(name)) {
|
||||
if (!"xpath".equals(name)) {
|
||||
staticCtx.declareProperty(propertyDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
for (ExtensionFunctionDefinition fun : xPathHandler.getRegisteredExtensionFunctions()) {
|
||||
for (XPathFunctionDefinition xpathFun : xPathHandler.getRegisteredExtensionFunctions()) {
|
||||
ExtensionFunctionDefinition fun = new SaxonExtensionFunctionDefinitionAdapter(xpathFun);
|
||||
StructuredQName qname = fun.getFunctionQName();
|
||||
staticCtx.declareNamespace(qname.getPrefix(), qname.getURI());
|
||||
this.configuration.registerExtensionFunction(fun);
|
||||
|
@ -241,7 +241,7 @@ class RuleSetFactoryTest extends RulesetFactoryTestBase {
|
||||
"<?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\">"
|
||||
+ " <rule name=\"NewName\" message=\"m\" class=\"net.sourceforge.pmd.lang.rule.xpath.XPathRule\" language=\"dummy\">"
|
||||
+ " <description>d</description>\n" + " <priority>2</priority>\n" + " </rule>"
|
||||
+ "</ruleset>");
|
||||
assertEquals(1, rs.getRules().size());
|
||||
@ -269,7 +269,7 @@ class RuleSetFactoryTest extends RulesetFactoryTestBase {
|
||||
"<?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\">"
|
||||
+ " <rule name=\"NewName\" message=\"m\" class=\"net.sourceforge.pmd.lang.rule.xpath.XPathRule\" language=\"dummy\">"
|
||||
+ " <description>d</description>\n"
|
||||
+ " <priority>2</priority>\n"
|
||||
+ " </rule>"
|
||||
|
@ -20,6 +20,7 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import net.sourceforge.pmd.lang.rule.RuleSet.RuleSetBuilder;
|
||||
import net.sourceforge.pmd.lang.rule.internal.RuleSetReference;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.XPathRule;
|
||||
import net.sourceforge.pmd.util.internal.xml.SchemaConstants;
|
||||
|
||||
/**
|
||||
|
@ -19,6 +19,7 @@ import net.sourceforge.pmd.lang.ast.DummyNode;
|
||||
import net.sourceforge.pmd.lang.ast.DummyNode.DummyRootNode;
|
||||
import net.sourceforge.pmd.lang.ast.DummyNodeWithDeprecatedAttribute;
|
||||
import net.sourceforge.pmd.lang.document.TextRegion;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.XPathRule;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.XPathVersion;
|
||||
import net.sourceforge.pmd.reporting.Report;
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user