forked from phoedos/pmd
Merge branch 'xml-new-xpath-rule' into pmd7-merge-xml-rule
This commit is contained in:
@ -12,7 +12,7 @@ GEM
|
||||
concurrent-ruby (1.1.9)
|
||||
cork (0.3.0)
|
||||
colored2 (~> 3.1)
|
||||
danger (8.4.3)
|
||||
danger (8.4.5)
|
||||
claide (~> 1.0)
|
||||
claide-plugins (>= 0.9.2)
|
||||
colored2 (~> 3.1)
|
||||
@ -26,7 +26,7 @@ GEM
|
||||
octokit (~> 4.7)
|
||||
terminal-table (>= 1, < 4)
|
||||
differ (0.1.2)
|
||||
et-orbi (1.2.6)
|
||||
et-orbi (1.2.7)
|
||||
tzinfo
|
||||
faraday (1.10.0)
|
||||
faraday-em_http (~> 1.0)
|
||||
@ -62,7 +62,7 @@ GEM
|
||||
rexml
|
||||
kramdown-parser-gfm (1.1.0)
|
||||
kramdown (~> 2.0)
|
||||
liquid (5.1.0)
|
||||
liquid (5.2.0)
|
||||
logger-colors (1.0.0)
|
||||
mini_portile2 (2.8.0)
|
||||
multipart-post (2.1.1)
|
||||
|
@ -1,7 +1,7 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activesupport (6.0.4.6)
|
||||
activesupport (6.0.4.7)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
@ -14,8 +14,7 @@ GEM
|
||||
execjs
|
||||
coffee-script-source (1.11.1)
|
||||
colorator (1.1.0)
|
||||
commonmarker (0.17.13)
|
||||
ruby-enum (~> 0.5)
|
||||
commonmarker (0.23.4)
|
||||
concurrent-ruby (1.1.9)
|
||||
dnsruby (1.61.9)
|
||||
simpleidn (~> 0.1)
|
||||
@ -52,12 +51,12 @@ GEM
|
||||
ffi (1.15.5)
|
||||
forwardable-extended (2.6.0)
|
||||
gemoji (3.0.1)
|
||||
github-pages (223)
|
||||
github-pages (225)
|
||||
github-pages-health-check (= 1.17.9)
|
||||
jekyll (= 3.9.0)
|
||||
jekyll-avatar (= 0.7.0)
|
||||
jekyll-coffeescript (= 1.1.1)
|
||||
jekyll-commonmark-ghpages (= 0.1.6)
|
||||
jekyll-commonmark-ghpages (= 0.2.0)
|
||||
jekyll-default-layout (= 0.1.4)
|
||||
jekyll-feed (= 0.15.1)
|
||||
jekyll-gist (= 1.5.0)
|
||||
@ -71,7 +70,7 @@ GEM
|
||||
jekyll-relative-links (= 0.6.1)
|
||||
jekyll-remote-theme (= 0.4.3)
|
||||
jekyll-sass-converter (= 1.5.2)
|
||||
jekyll-seo-tag (= 2.7.1)
|
||||
jekyll-seo-tag (= 2.8.0)
|
||||
jekyll-sitemap (= 1.4.0)
|
||||
jekyll-swiss (= 1.0.0)
|
||||
jekyll-theme-architect (= 0.2.0)
|
||||
@ -127,12 +126,12 @@ GEM
|
||||
jekyll-coffeescript (1.1.1)
|
||||
coffee-script (~> 2.2)
|
||||
coffee-script-source (~> 1.11.1)
|
||||
jekyll-commonmark (1.3.1)
|
||||
commonmarker (~> 0.14)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-commonmark-ghpages (0.1.6)
|
||||
commonmarker (~> 0.17.6)
|
||||
jekyll-commonmark (~> 1.2)
|
||||
jekyll-commonmark (1.4.0)
|
||||
commonmarker (~> 0.22)
|
||||
jekyll-commonmark-ghpages (0.2.0)
|
||||
commonmarker (~> 0.23.4)
|
||||
jekyll (~> 3.9.0)
|
||||
jekyll-commonmark (~> 1.4.0)
|
||||
rouge (>= 2.0, < 4.0)
|
||||
jekyll-default-layout (0.1.4)
|
||||
jekyll (~> 3.0)
|
||||
@ -164,7 +163,7 @@ GEM
|
||||
rubyzip (>= 1.3.0, < 3.0)
|
||||
jekyll-sass-converter (1.5.2)
|
||||
sass (~> 3.4)
|
||||
jekyll-seo-tag (2.7.1)
|
||||
jekyll-seo-tag (2.8.0)
|
||||
jekyll (>= 3.8, < 5.0)
|
||||
jekyll-sitemap (1.4.0)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
@ -248,8 +247,6 @@ GEM
|
||||
ffi (~> 1.0)
|
||||
rexml (3.2.5)
|
||||
rouge (3.26.0)
|
||||
ruby-enum (0.9.0)
|
||||
i18n
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
safe_yaml (1.0.5)
|
||||
|
@ -367,6 +367,9 @@ entries:
|
||||
- title: Visualforce
|
||||
url: /pmd_languages_visualforce.html
|
||||
output: web, pdf
|
||||
- title: XML and XML dialects
|
||||
url: /pmd_languages_xml.html
|
||||
output: web, pdf
|
||||
- title: Developer Documentation
|
||||
output: web, pdf
|
||||
folderitems:
|
||||
|
@ -7,24 +7,93 @@ aliases:
|
||||
type: "xs:string"
|
||||
description: "The qualified name of a Java class, possibly with pairs of brackets to indicate an array type.
|
||||
Can also be a primitive type name."
|
||||
- &node_param
|
||||
name: element
|
||||
type: "xs:element"
|
||||
description: "Any element node"
|
||||
- &needs_typenode "The context node must be a {% jdoc jast::TypeNode %}"
|
||||
- &coord_fun_note |
|
||||
The function is not context-dependent, but takes a node as its first parameter.
|
||||
The function is only available in XPath 2.0.
|
||||
- &needs_node_ctx "The requires the context node to be an element"
|
||||
|
||||
langs:
|
||||
- name: "Any language"
|
||||
- name: "All languages"
|
||||
ns: "pmd"
|
||||
header: "Functions available to all languages are in the namespace `pmd`."
|
||||
funs:
|
||||
- name: fileName
|
||||
returnType: "xs:string"
|
||||
shortDescription: "Returns the current filename"
|
||||
description: "Returns the current simple filename without path but including the extension.
|
||||
This can be used to write rules that check filename naming conventions.
|
||||
|
||||
<p>This function is available since PMD 6.38.0.</p>"
|
||||
notes: "The function can be called on any node."
|
||||
shortDescription: "Returns the simple name of the current file"
|
||||
description: |
|
||||
Returns the current simple file name, without path but including the extension.
|
||||
This can be used to write rules that check file naming conventions.
|
||||
|
||||
since: 6.38.0
|
||||
notes: *needs_node_ctx
|
||||
examples:
|
||||
- code: "//b[pmd:fileName() = 'Foo.xml']"
|
||||
outcome: "Matches any `<b>` tags in files called `Foo.xml`."
|
||||
|
||||
- name: startLine
|
||||
returnType: "xs:int"
|
||||
parameters:
|
||||
- *node_param
|
||||
shortDescription: "Returns the start line of the given node"
|
||||
description: |
|
||||
Returns the line where the node starts in the source file.
|
||||
Line numbers are 1-based.
|
||||
|
||||
since: 6.44.0
|
||||
notes: *coord_fun_note
|
||||
examples:
|
||||
- code: "//b[pmd:startLine(.) > 5]"
|
||||
outcome: "Matches any `<b>` node which starts after the fifth line."
|
||||
|
||||
- name: endLine
|
||||
returnType: "xs:int"
|
||||
parameters:
|
||||
- *node_param
|
||||
shortDescription: "Returns the end line of the given node"
|
||||
description: |
|
||||
Returns the line where the node ends in the source file.
|
||||
Line numbers are 1-based.
|
||||
|
||||
since: 6.44.0
|
||||
notes: *coord_fun_note
|
||||
examples:
|
||||
- code: "//b[pmd:endLine(.) == pmd:startLine(.)]"
|
||||
outcome: "Matches any `<b>` node which doesn't span more than one line."
|
||||
|
||||
- name: startColumn
|
||||
returnType: "xs:int"
|
||||
parameters:
|
||||
- *node_param
|
||||
shortDescription: "Returns the start column of the given node (inclusive)"
|
||||
description: |
|
||||
Returns the column number where the node starts in the source file.
|
||||
Column numbers are 1-based. The start column is inclusive.
|
||||
|
||||
since: 6.44.0
|
||||
notes: *coord_fun_note
|
||||
examples:
|
||||
- code: "//b[pmd:startColumn(.) = 1]"
|
||||
outcome: "Matches any `<b>` node which starts on the first column of a line"
|
||||
|
||||
- name: endColumn
|
||||
returnType: "xs:int"
|
||||
parameters:
|
||||
- *node_param
|
||||
shortDescription: "Returns the end column of the given node (exclusive)"
|
||||
description: |
|
||||
Returns the column number where the node ends in the source file.
|
||||
Column numbers are 1-based. The end column is exclusive.
|
||||
|
||||
since: 6.44.0
|
||||
notes: *coord_fun_note
|
||||
examples:
|
||||
- code: "//b[pmd:startLine(.) = pmd:endLine(.) and pmd:endColumn(.) - pmd:startColumn(.) = 1]"
|
||||
outcome: "Matches any `<b>` node which spans exactly one character"
|
||||
|
||||
- name: "Java"
|
||||
ns: "pmd-java"
|
||||
|
@ -4,7 +4,11 @@
|
||||
|
||||
### {{ lang.name }}
|
||||
|
||||
{% if lang.header %}
|
||||
{{ lang.header | render_markdown }}
|
||||
{% else %}
|
||||
{{ lang.name }} functions are in the namespace `{{ lang.ns }}`.
|
||||
{% endif %}
|
||||
|
||||
<div class="table-responsive">
|
||||
<table width="100%">
|
||||
@ -50,6 +54,10 @@
|
||||
|
||||
<dl>
|
||||
<dd>{{ fun.description | render_markdown }}</dd>
|
||||
{% if fun.since %}
|
||||
<dt>Since</dt>
|
||||
<dd>PMD {{ fun.since }}</dd>
|
||||
{% endif %}
|
||||
<dt>Remarks</dt>
|
||||
<dd>{{ fun.notes | render_markdown }}</dd>
|
||||
|
||||
|
@ -29,6 +29,8 @@ body {
|
||||
margin-top: 60px;
|
||||
margin-left: -15px;
|
||||
margin-right: 15px;
|
||||
height: 80%;
|
||||
overflow: auto;
|
||||
}
|
||||
.container {
|
||||
margin-left: 15px;
|
||||
@ -49,6 +51,8 @@ body {
|
||||
margin-top: 60px;
|
||||
margin-left: -15px;
|
||||
margin-right: 15px;
|
||||
height: 80%;
|
||||
overflow: auto;
|
||||
}
|
||||
.container {
|
||||
margin-left: 15px;
|
||||
|
75
docs/pages/pmd/languages/xml.md
Normal file
75
docs/pages/pmd/languages/xml.md
Normal file
@ -0,0 +1,75 @@
|
||||
---
|
||||
title: Processing XML files
|
||||
permalink: pmd_languages_xml.html
|
||||
last_updated: March 2022 (6.44.0)
|
||||
---
|
||||
|
||||
## The XML language module
|
||||
|
||||
PMD has an XML language module which exposes the [DOM](https://de.wikipedia.org/wiki/Document_Object_Model)
|
||||
of an XML document as an AST. Different flavours of XML are represented by separate
|
||||
language instances, which all use the same parser under the hood. The following
|
||||
table lists the languages currently provided by the `pmd-xml` maven module.
|
||||
|
||||
| Language ID | Description |
|
||||
|-------------|-----------------------------------|
|
||||
| xml | Generic XML language |
|
||||
| pom | Maven Project Object Model (POM) |
|
||||
| wsdl | Web Services Description Language |
|
||||
| xsl | Extensible Stylesheet Language |
|
||||
|
||||
Each of those languages has a separate rule index, and may provide domain-specific
|
||||
[XPath functions](pmd_userdocs_extending_writing_xpath_rules.html#pmd-extension-functions).
|
||||
At their core they use the same parsing facilities though.
|
||||
|
||||
### File attribution
|
||||
|
||||
Any file ending with `.xml` is associated with the `xml` language. Other XML flavours
|
||||
use more specific extensions, like `.xsl`.
|
||||
|
||||
Some XML-based file formats do not conventionally use a `.xml` extension. To associate
|
||||
these files with the XML language, you need to use the `--force-language xml` command-line
|
||||
arguments, for instance:
|
||||
```
|
||||
$ ./run.sh pmd -d /home/me/src/xml-file.ext -f text -R ruleset.xml --force-language xml
|
||||
```
|
||||
Please refer to [PMD CLI reference](pmd_userdocs_cli_reference.html#analyze-other-xml-formats)
|
||||
for more examples.
|
||||
|
||||
|
||||
### XPath rules in XML
|
||||
|
||||
While other languages use {% jdoc core::lang.rule.XPathRule %} to create XPath rules,
|
||||
the use of this class is not recommended for XML languages. Instead, since 6.44.0, you
|
||||
are advised to use {% jdoc xml::lang.xml.rule.DomXPathRule %}. This rule class interprets
|
||||
XPath queries exactly as regular XPath, while `XPathRule` works on a wrapper for the
|
||||
DOM which is inconsistent with the XPath spec. Since `DomXPathRule` conforms to the
|
||||
XPath spec, you can
|
||||
- test XML queries in any stock XPath testing tool, or use resources like StackOverflow
|
||||
to help you write XPath queries.
|
||||
- match XML comments and processing instructions
|
||||
- use standard XPath functions like `text()` or `fn:string`
|
||||
|
||||
{% include note.html content="The Rule Designer only works with `XPathRule`, and the tree it prints is inconsistent with the DOM representation used by `DomXPathRule`. You can use an online free XPath testing tool to test your query instead." %}
|
||||
|
||||
Here's an example declaration of a `DomXPathRule`:
|
||||
```xml
|
||||
<rule name="MyXPathRule"
|
||||
language="xml"
|
||||
message="A message"
|
||||
class="net.sourceforge.pmd.lang.xml.rule.DomXPathRule">
|
||||
|
||||
<properties>
|
||||
<property name="xpath">
|
||||
<value><![CDATA[
|
||||
/a/b/c[@attr = "5"]
|
||||
]]></value>
|
||||
</property>
|
||||
<!-- Note: the property "version" is unsupported. -->
|
||||
</properties>
|
||||
</rule>
|
||||
```
|
||||
The most important change is the `class` attribute, which doesn't point to `XPathRule`
|
||||
but to `DomXPathRule`. Please see the Javadoc for {% jdoc xml::lang.xml.rule.DomXPathRule %}
|
||||
for more info about the differences with `XPathRule`.
|
||||
|
@ -60,6 +60,8 @@ The CLI itself remains compatible, if you run PMD via command-line, no action is
|
||||
* [#3773](https://github.com/pmd/pmd/pull/3773): \[apex] EagerlyLoadedDescribeSObjectResult false positives with SObjectField.getDescribe()
|
||||
* core
|
||||
* [#3299](https://github.com/pmd/pmd/issues/3299): \[core] Deprecate system properties of PMDCommandLineInterface
|
||||
* doc
|
||||
* [#3812](https://github.com/pmd/pmd/issues/3812): \[doc] Documentation website table of contents broken on pages with many subheadings
|
||||
|
||||
### API Changes
|
||||
|
||||
@ -96,6 +98,7 @@ The CLI itself remains compatible, if you run PMD via command-line, no action is
|
||||
### External Contributions
|
||||
|
||||
* [#3773](https://github.com/pmd/pmd/pull/3773): \[apex] EagerlyLoadedDescribeSObjectResult false positives with SObjectField.getDescribe() - [@filiprafalowicz](https://github.com/filiprafalowicz)
|
||||
* [#3836](https://github.com/pmd/pmd/pull/3836): \[doc] Make TOC scrollable when too many subheadings - [@JerritEic](https://github.com/JerritEic)
|
||||
|
||||
{% endtocmaker %}
|
||||
|
||||
|
@ -15,15 +15,15 @@ import net.sourceforge.pmd.lang.ast.impl.DummyTreeUtil;
|
||||
|
||||
public class RuleContextTest {
|
||||
|
||||
public static Report getReport(Rule rule, BiConsumer<Rule, RuleContext> sideEffects) throws Exception {
|
||||
public static Report getReport(Rule rule, BiConsumer<Rule, RuleContext> sideEffects) {
|
||||
return Report.buildReport(listener -> sideEffects.accept(rule, RuleContext.create(listener, rule)));
|
||||
}
|
||||
|
||||
public static Report getReportForRuleApply(Rule rule, Node node) throws Exception {
|
||||
public static Report getReportForRuleApply(Rule rule, Node node) {
|
||||
return getReport(rule, (r, ctx) -> r.apply(node, ctx));
|
||||
}
|
||||
|
||||
public static Report getReportForRuleSetApply(RuleSet ruleset, RootNode node) throws Exception {
|
||||
public static Report getReportForRuleSetApply(RuleSet ruleset, RootNode node) {
|
||||
return Report.buildReport(listener -> new RuleSets(ruleset).apply(node, listener));
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
package net.sourceforge.pmd.lang.rule;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
@ -14,6 +15,7 @@ import org.junit.contrib.java.lang.system.SystemErrRule;
|
||||
|
||||
import net.sourceforge.pmd.Report;
|
||||
import net.sourceforge.pmd.RuleContextTest;
|
||||
import net.sourceforge.pmd.lang.DummyLanguageModule;
|
||||
import net.sourceforge.pmd.lang.LanguageRegistry;
|
||||
import net.sourceforge.pmd.lang.ast.DummyNode;
|
||||
import net.sourceforge.pmd.lang.ast.DummyNodeWithDeprecatedAttribute;
|
||||
@ -91,11 +93,73 @@ public class XPathRuleTest {
|
||||
return xpr;
|
||||
}
|
||||
|
||||
public DummyNode newNode() {
|
||||
|
||||
public XPathRule makeXPath(String xpathExpr) {
|
||||
XPathRule xpr = new XPathRule(XPathVersion.XPATH_2_0, xpathExpr);
|
||||
xpr.setLanguage(LanguageRegistry.getLanguage(DummyLanguageModule.NAME));
|
||||
xpr.setName("name");
|
||||
xpr.setMessage("gotcha");
|
||||
return xpr;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileNameInXpath() {
|
||||
Report report = executeRule(makeXPath("//*[pmd:fileName() = 'Foo.cls']"),
|
||||
newRoot("src/Foo.cls"));
|
||||
|
||||
assertThat(report.getViolations(), hasSize(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBeginLine() {
|
||||
Report report = executeRule(makeXPath("//*[pmd:startLine(.)=1]"),
|
||||
newRoot("src/Foo.cls"));
|
||||
|
||||
assertThat(report.getViolations(), hasSize(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBeginCol() {
|
||||
Report report = executeRule(makeXPath("//*[pmd:startColumn(.)=1]"),
|
||||
newRoot("src/Foo.cls"));
|
||||
|
||||
assertThat(report.getViolations(), hasSize(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEndLine() {
|
||||
Report report = executeRule(makeXPath("//*[pmd:endLine(.)=1]"),
|
||||
newRoot("src/Foo.cls"));
|
||||
|
||||
assertThat(report.getViolations(), hasSize(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEndColumn() {
|
||||
Report report = executeRule(makeXPath("//*[pmd:endColumn(.)>1]"),
|
||||
newRoot("src/Foo.cls"));
|
||||
|
||||
assertThat(report.getViolations(), hasSize(1));
|
||||
}
|
||||
|
||||
public Report executeRule(net.sourceforge.pmd.Rule rule, DummyNode node) {
|
||||
return RuleContextTest.getReportForRuleApply(rule, node);
|
||||
}
|
||||
|
||||
|
||||
public DummyRoot newNode() {
|
||||
DummyRoot root = new DummyRoot();
|
||||
DummyNode dummy = new DummyNodeWithDeprecatedAttribute(2);
|
||||
dummy.setCoords(1, 1, 1, 2);
|
||||
root.addChild(dummy, 0);
|
||||
return root;
|
||||
}
|
||||
|
||||
public DummyRoot newRoot(String fileName) {
|
||||
DummyRoot dummy = new DummyRoot().withFileName(fileName);
|
||||
dummy.setCoords(1, 1, 1, 2);
|
||||
return dummy;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -91,7 +91,7 @@ public final class XmlParserImpl {
|
||||
|
||||
private final AstInfo<RootXmlNode> astInfo;
|
||||
|
||||
RootXmlNode(XmlParserImpl parser, Node domNode, ParserTask task) {
|
||||
RootXmlNode(XmlParserImpl parser, Document domNode, ParserTask task) {
|
||||
super(parser, domNode);
|
||||
this.astInfo = new AstInfo<>(task, this);
|
||||
}
|
||||
@ -100,6 +100,16 @@ public final class XmlParserImpl {
|
||||
public AstInfo<RootXmlNode> getAstInfo() {
|
||||
return astInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlNode wrap(Node domNode) {
|
||||
return super.wrap(domNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document getNode() {
|
||||
return (Document) super.getNode();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,163 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.xml.rule;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.sourceforge.pmd.RuleContext;
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
import net.sourceforge.pmd.lang.rule.AbstractRule;
|
||||
import net.sourceforge.pmd.lang.rule.XPathRule;
|
||||
import net.sourceforge.pmd.lang.xml.ast.XmlParser.RootXmlNode;
|
||||
import net.sourceforge.pmd.properties.PropertyDescriptor;
|
||||
import net.sourceforge.pmd.properties.PropertyFactory;
|
||||
|
||||
/**
|
||||
* XPath rule that executes an expression on the DOM directly, and not
|
||||
* on the PMD AST wrapper. The XPath expressions adheres to the XPath
|
||||
* (2.0) spec, so they can be tested in any existing XPath testing tools
|
||||
* instead of just the PMD designer (google "xpath test"). Usage of this
|
||||
* class is strongly recommended over the standard {@link XPathRule}, which
|
||||
* is mostly useful in other PMD languages.
|
||||
*
|
||||
* <h3>Differences with {@link XPathRule}</h3>
|
||||
*
|
||||
* This rule and {@link XPathRule} do not accept exactly the same queries,
|
||||
* because {@link XPathRule} implements the XPath spec in an ad-hoc way.
|
||||
* The main differences are:
|
||||
* <ul>
|
||||
* <li>{@link XPathRule} uses <i>elements</i> to represent text nodes.
|
||||
* This is contrary to the XPath spec, in which element and text nodes
|
||||
* are different kinds of nodes. To replace the query {@code //elt/text[@Image="abc"]},
|
||||
* use the XPath function {@code text()}, eg {@code //elt[text()="abc"]}.
|
||||
* <li>{@link XPathRule} adds additional attributes to each element
|
||||
* (eg {@code @BeginLine} and {@code @Image}). These attributes are not
|
||||
* XML attributes, so they are not accessible using DomXPathRule rule.
|
||||
* Instead, use the XPath functions {@code pmd:startLine(node)}, {@code pmd:endLine(node)} and related.
|
||||
* For instance, replace {@code //elt[@EndLine - @BeginLine > 10]} with
|
||||
* {@code elt[pmd:endLine(.) - pmd:startLine(.) > 10]}.
|
||||
* <li>{@link XPathRule} uses an element called {@code "document"} as the
|
||||
* root node of every XML AST. This node does not have the correct node kind,
|
||||
* as it's an element, not a document. To replace {@code /document/RootNode},
|
||||
* use just {@code /RootNode}.
|
||||
* <li>{@link XPathRule} ignores comments and processing instructions
|
||||
* (eg FXML's {@code <?import javafx.Node ?>}).
|
||||
* This rule makes them accessible with the regular XPath syntax.
|
||||
* The following finds all comments in the file:
|
||||
* <pre>{@code
|
||||
* //comment()
|
||||
* }</pre>
|
||||
* The following finds only top-level comments starting with "prefix":
|
||||
* <pre>{@code
|
||||
* /comment()[fn:starts-with(fn:string(.), "prefix")]
|
||||
* }</pre>
|
||||
* Note the use of {@code fn:string}.
|
||||
*
|
||||
* As an example of matching processing instructions, the following
|
||||
* fetches all {@code <?import ... ?>} processing instructions.
|
||||
* <pre>{@code
|
||||
* /processing-instruction('import')
|
||||
* }</pre>
|
||||
* The string value of the instruction can be found with {@code fn:string}.
|
||||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Additionally, this rule only supports XPath 2.0, with no option
|
||||
* for configuration. This will be bumped to XPath 3.1 in PMD 7.
|
||||
*
|
||||
* <h4>Namespace-sensitivity</h4>
|
||||
*
|
||||
* <p>Another important difference is that this rule is namespace-sensitive.
|
||||
* If the tested XML documents use a schema ({@code xmlns} attribute on the root),
|
||||
* you should set the property {@code defaultNsUri} on the rule with
|
||||
* the value of the {@code xmlns} attribute. Otherwise node tests won't
|
||||
* match unless you use a wildcard URI prefix ({@code *:nodeName}).
|
||||
*
|
||||
* <p>For instance for the document
|
||||
* <pre>{@code
|
||||
* <foo xmlns="http://company.com/aschema">
|
||||
* </foo>
|
||||
* }</pre>
|
||||
* the XPath query {@code //foo} will not match anything, while {@code //*:foo}
|
||||
* will. If you set the property {@code defaultNsUri} to {@code "http://company.com/aschema"},
|
||||
* then {@code //foo} will be expanded to {@code //Q{http://company.com/aschema}foo},
|
||||
* and match the {@code foo} node. The behaviour is equivalent in the following
|
||||
* document:
|
||||
* <pre>{@code
|
||||
* <my:foo xmlns:my='http://company.com/aschema'>
|
||||
* </my:foo>
|
||||
* }</pre>
|
||||
*
|
||||
* <p>However, for the document
|
||||
* <pre>{@code
|
||||
* <foo>
|
||||
* </foo>
|
||||
* }</pre>
|
||||
* the XPath queries {@code //foo} and {@code //*:foo} both match, because
|
||||
* {@code //foo} is expanded to {@code //Q{}foo} (local name foo, empty URI),
|
||||
* and the document has no default namespace (= the empty default namespace).
|
||||
*
|
||||
* <p>Note that explicitly specifying URIs with {@code Q{...}localName}
|
||||
* as in this documentation is XPath 3.1 syntax and will only be available
|
||||
* in PMD 7.
|
||||
*
|
||||
* @since PMD 6.44.0
|
||||
* @author Clément Fournier
|
||||
*/
|
||||
public class DomXPathRule extends AbstractRule {
|
||||
|
||||
SaxonDomXPathQuery query;
|
||||
|
||||
private static final PropertyDescriptor<String> XPATH_EXPR
|
||||
= PropertyFactory.stringProperty("xpath")
|
||||
.desc("An XPath 2.0 expression that will be evaluated against the root DOM")
|
||||
.defaultValue("") // no default value
|
||||
.build();
|
||||
|
||||
private static final PropertyDescriptor<String> DEFAULT_NS_URI
|
||||
= PropertyFactory.stringProperty("defaultNsUri")
|
||||
.desc("A URI for the default namespace of node tests in the XPath expression."
|
||||
+ "This is provided to match documents based on their declared schema.")
|
||||
.defaultValue("")
|
||||
.build();
|
||||
|
||||
|
||||
public DomXPathRule() {
|
||||
definePropertyDescriptor(XPATH_EXPR);
|
||||
definePropertyDescriptor(DEFAULT_NS_URI);
|
||||
}
|
||||
|
||||
|
||||
public DomXPathRule(String xpath) {
|
||||
this(xpath, "");
|
||||
}
|
||||
|
||||
public DomXPathRule(String xpath, String defaultNsUri) {
|
||||
this();
|
||||
setProperty(XPATH_EXPR, xpath);
|
||||
setProperty(DEFAULT_NS_URI, defaultNsUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(List<? extends Node> nodes, RuleContext ctx) {
|
||||
for (Node n : nodes) {
|
||||
RootXmlNode root = (RootXmlNode) n;
|
||||
SaxonDomXPathQuery query = getXPathQuery();
|
||||
for (Node foundNode : query.evaluate(root, this)) {
|
||||
ctx.addViolation(foundNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private SaxonDomXPathQuery getXPathQuery() {
|
||||
if (query == null) {
|
||||
query = new SaxonDomXPathQuery(getProperty(XPATH_EXPR),
|
||||
getProperty(DEFAULT_NS_URI),
|
||||
getPropertyDescriptors());
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,191 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.xml.rule;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.commons.lang3.exception.ContextedRuntimeException;
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.SaxonXPathRuleQuery;
|
||||
import net.sourceforge.pmd.lang.xml.ast.XmlNode;
|
||||
import net.sourceforge.pmd.lang.xml.ast.XmlParser.RootXmlNode;
|
||||
import net.sourceforge.pmd.lang.xpath.Initializer;
|
||||
import net.sourceforge.pmd.properties.PropertyDescriptor;
|
||||
import net.sourceforge.pmd.properties.PropertySource;
|
||||
import net.sourceforge.pmd.util.DataMap;
|
||||
import net.sourceforge.pmd.util.DataMap.DataKey;
|
||||
import net.sourceforge.pmd.util.DataMap.SimpleDataKey;
|
||||
|
||||
import net.sf.saxon.Configuration;
|
||||
import net.sf.saxon.dom.DocumentWrapper;
|
||||
import net.sf.saxon.dom.NodeWrapper;
|
||||
import net.sf.saxon.om.Item;
|
||||
import net.sf.saxon.om.NamePool;
|
||||
import net.sf.saxon.om.NamespaceConstant;
|
||||
import net.sf.saxon.om.ValueRepresentation;
|
||||
import net.sf.saxon.sxpath.IndependentContext;
|
||||
import net.sf.saxon.sxpath.XPathDynamicContext;
|
||||
import net.sf.saxon.sxpath.XPathEvaluator;
|
||||
import net.sf.saxon.sxpath.XPathExpression;
|
||||
import net.sf.saxon.sxpath.XPathStaticContext;
|
||||
import net.sf.saxon.sxpath.XPathVariable;
|
||||
import net.sf.saxon.trans.XPathException;
|
||||
|
||||
final class SaxonDomXPathQuery {
|
||||
|
||||
private static final NamePool NAME_POOL = new NamePool();
|
||||
|
||||
private static final SimpleDataKey<DocumentWrapper> SAXON_DOM_WRAPPER
|
||||
= DataMap.simpleDataKey("pmd.saxon.dom.wrapper");
|
||||
|
||||
/** The XPath expression as a string. */
|
||||
private final String xpath;
|
||||
/** The executable XPath expression. */
|
||||
private final XPathExpressionWithProperties xpathExpression;
|
||||
|
||||
|
||||
private final Configuration configuration;
|
||||
|
||||
SaxonDomXPathQuery(String xpath, String defaultNsUri, List<PropertyDescriptor<?>> properties) {
|
||||
this.xpath = xpath;
|
||||
configuration = new Configuration();
|
||||
configuration.setNamePool(NAME_POOL);
|
||||
xpathExpression = makeXPathExpression(this.xpath, defaultNsUri, properties);
|
||||
}
|
||||
|
||||
private XPathExpressionWithProperties makeXPathExpression(String xpath, String defaultUri, List<PropertyDescriptor<?>> properties) {
|
||||
final IndependentContext xpathStaticContext = new IndependentContext(configuration);
|
||||
xpathStaticContext.declareNamespace("fn", NamespaceConstant.FN);
|
||||
xpathStaticContext.setDefaultElementNamespace(defaultUri);
|
||||
|
||||
|
||||
// Register PMD functions
|
||||
Initializer.initialize(xpathStaticContext);
|
||||
|
||||
Map<PropertyDescriptor<?>, XPathVariable> xpathVariables = declareXPathVariables(properties, xpathStaticContext);
|
||||
|
||||
try {
|
||||
final XPathEvaluator xpathEvaluator = new XPathEvaluator(configuration);
|
||||
xpathEvaluator.setStaticContext(xpathStaticContext);
|
||||
XPathExpression expression = xpathEvaluator.createExpression(xpath);
|
||||
return new XPathExpressionWithProperties(
|
||||
expression,
|
||||
xpathVariables
|
||||
);
|
||||
} catch (final XPathException e) {
|
||||
throw new ContextedRuntimeException(e)
|
||||
.addContextValue("XPath", xpath);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<PropertyDescriptor<?>, XPathVariable> declareXPathVariables(List<PropertyDescriptor<?>> accessibleProperties, XPathStaticContext xpathStaticContext) {
|
||||
Map<PropertyDescriptor<?>, XPathVariable> xpathVariables = new HashMap<>();
|
||||
for (final PropertyDescriptor<?> propertyDescriptor : accessibleProperties) {
|
||||
final String name = propertyDescriptor.name();
|
||||
if (!isExcludedProperty(name)) {
|
||||
final XPathVariable xpathVariable = xpathStaticContext.declareVariable(null, name);
|
||||
xpathVariables.put(propertyDescriptor, xpathVariable);
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableMap(xpathVariables);
|
||||
}
|
||||
|
||||
private boolean isExcludedProperty(String name) {
|
||||
return "xpath".equals(name)
|
||||
|| "defaultNsUri".equals(name)
|
||||
|| "violationSuppressRegex".equals(name)
|
||||
|| "violationSuppressXPath".equals(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return xpath;
|
||||
}
|
||||
|
||||
public List<Node> evaluate(RootXmlNode root, PropertySource propertyValues) {
|
||||
DocumentWrapper wrapper = getSaxonDomWrapper(root);
|
||||
|
||||
try {
|
||||
List<Node> result = new ArrayList<>();
|
||||
for (Item item : this.xpathExpression.evaluate(wrapper, propertyValues)) {
|
||||
if (item instanceof NodeWrapper) {
|
||||
NodeWrapper nodeInfo = (NodeWrapper) item;
|
||||
Object domNode = nodeInfo.getUnderlyingNode();
|
||||
if (domNode instanceof org.w3c.dom.Node) {
|
||||
XmlNode wrapped = root.wrap((org.w3c.dom.Node) domNode);
|
||||
result.add(wrapped);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} catch (XPathException e) {
|
||||
throw new ContextedRuntimeException(e)
|
||||
.addContextValue("XPath", xpath);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private DocumentWrapper getSaxonDomWrapper(RootXmlNode node) {
|
||||
DataMap<DataKey<?, ?>> userMap = node.getUserMap();
|
||||
if (userMap.isSet(SAXON_DOM_WRAPPER)) {
|
||||
return userMap.get(SAXON_DOM_WRAPPER);
|
||||
}
|
||||
Document domRoot = node.getNode();
|
||||
DocumentWrapper wrapper = new DocumentWrapper(
|
||||
domRoot, domRoot.getBaseURI(), configuration
|
||||
);
|
||||
userMap.set(SAXON_DOM_WRAPPER, wrapper);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
static final class XPathExpressionWithProperties {
|
||||
|
||||
final XPathExpression expr;
|
||||
final Map<PropertyDescriptor<?>, XPathVariable> xpathVariables;
|
||||
|
||||
XPathExpressionWithProperties(XPathExpression expr, Map<PropertyDescriptor<?>, XPathVariable> xpathVariables) {
|
||||
this.expr = expr;
|
||||
this.xpathVariables = xpathVariables;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<Item> evaluate(final DocumentWrapper elementNode, PropertySource properties) throws XPathException {
|
||||
XPathDynamicContext dynamicContext = createDynamicContext(elementNode, properties);
|
||||
return (List<Item>) expr.evaluate(dynamicContext);
|
||||
}
|
||||
|
||||
private XPathDynamicContext createDynamicContext(final DocumentWrapper elementNode, PropertySource properties) {
|
||||
final XPathDynamicContext dynamicContext = expr.createDynamicContext(elementNode);
|
||||
|
||||
// Set variable values on the dynamic context
|
||||
for (final Entry<PropertyDescriptor<?>, XPathVariable> entry : xpathVariables.entrySet()) {
|
||||
ValueRepresentation saxonValue = getSaxonValue(properties, entry);
|
||||
XPathVariable variable = entry.getValue();
|
||||
try {
|
||||
dynamicContext.setVariable(variable, saxonValue);
|
||||
} catch (XPathException e) {
|
||||
throw new ContextedRuntimeException(e)
|
||||
.addContextValue("Variable", variable);
|
||||
}
|
||||
}
|
||||
return dynamicContext;
|
||||
}
|
||||
|
||||
private static ValueRepresentation getSaxonValue(PropertySource properties, Entry<PropertyDescriptor<?>, XPathVariable> entry) {
|
||||
Object value = properties.getProperty(entry.getKey());
|
||||
Objects.requireNonNull(value, "null property value for " + entry.getKey());
|
||||
return SaxonXPathRuleQuery.getRepresentation(entry.getKey(), value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -9,18 +9,33 @@ import static net.sourceforge.pmd.lang.ast.test.TestUtilsKt.assertSize;
|
||||
import org.junit.Test;
|
||||
|
||||
import net.sourceforge.pmd.Report;
|
||||
import net.sourceforge.pmd.Rule;
|
||||
import net.sourceforge.pmd.lang.LanguageRegistry;
|
||||
import net.sourceforge.pmd.lang.rule.XPathRule;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.XPathVersion;
|
||||
import net.sourceforge.pmd.lang.xml.XmlLanguageModule;
|
||||
import net.sourceforge.pmd.lang.xml.XmlParsingHelper;
|
||||
|
||||
public class XmlXPathRuleTest {
|
||||
|
||||
private static final String A_URI = "http://soap.sforce.com/2006/04/metadata";
|
||||
private static final String FXML_IMPORTS = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||
+ "\n"
|
||||
+ "<!--suppress JavaFxDefaultTag -->\n"
|
||||
+ "\n"
|
||||
+ "<?import javafx.scene.layout.AnchorPane?>\n"
|
||||
+ "<?import javafx.scene.layout.BorderPane?>\n"
|
||||
+ "<?import javafx.scene.control.Tooltip?>\n"
|
||||
+ "<?import javafx.scene.control.Label?>\n"
|
||||
+ "<?import org.kordamp.ikonli.javafx.FontIcon?>\n"
|
||||
+ "<AnchorPane prefHeight=\"750.0\" prefWidth=\"1200.0\" stylesheets=\"@../css/designer.css\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n"
|
||||
+ "</AnchorPane>";
|
||||
final XmlParsingHelper xml = XmlParsingHelper.XML;
|
||||
|
||||
private XPathRule makeXPath(String expression) {
|
||||
XPathRule rule = new XPathRule(XPathVersion.XPATH_2_0, expression);
|
||||
private Rule makeXPath(String expression) {
|
||||
return makeXPath(expression, "");
|
||||
}
|
||||
|
||||
private Rule makeXPath(String expression, String nsUri) {
|
||||
DomXPathRule rule = new DomXPathRule(expression, nsUri);
|
||||
rule.setLanguage(LanguageRegistry.getLanguage(XmlLanguageModule.NAME));
|
||||
rule.setMessage("XPath Rule Failed");
|
||||
return rule;
|
||||
@ -36,4 +51,168 @@ public class XmlXPathRuleTest {
|
||||
assertSize(report, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTextFunctionInXpath() {
|
||||
// https://github.com/pmd/pmd/issues/915
|
||||
Report report = xml.executeRule(makeXPath("//app[text()[1]='app2']"),
|
||||
"<a><app>app2</app></a>");
|
||||
|
||||
assertSize(report, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRootNodeWildcardUri() {
|
||||
// https://github.com/pmd/pmd/issues/3413#issuecomment-1072614398
|
||||
Report report = xml.executeRule(makeXPath("/*:Flow"),
|
||||
"<Flow xmlns=\"http://soap.sforce.com/2006/04/metadata\">\n"
|
||||
+ "</Flow>");
|
||||
|
||||
assertSize(report, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoNamespaceRoot() {
|
||||
Report report = xml.executeRule(makeXPath("/Flow"),
|
||||
"<Flow>\n"
|
||||
+ "</Flow>");
|
||||
|
||||
assertSize(report, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNamespaceDescendantWrongDefaultUri() {
|
||||
Report report = xml.executeRule(makeXPath("//a"),
|
||||
"<Flow xmlns='" + A_URI + "'><a/></Flow>");
|
||||
|
||||
assertSize(report, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNamespaceDescendantOkUri() {
|
||||
Report report = xml.executeRule(makeXPath("//a", A_URI),
|
||||
"<Flow xmlns='" + A_URI + "'><a/></Flow>");
|
||||
|
||||
assertSize(report, 1);
|
||||
|
||||
report = xml.executeRule(makeXPath("//*:a"),
|
||||
"<Flow xmlns='" + A_URI + "'><a/></Flow>");
|
||||
|
||||
assertSize(report, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNamespaceDescendantWildcardUri() {
|
||||
Report report = xml.executeRule(makeXPath("//*:a"),
|
||||
"<Flow xmlns='" + A_URI + "'><a/></Flow>");
|
||||
|
||||
assertSize(report, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNamespacePrefixDescendantWildcardUri() {
|
||||
Report report = xml.executeRule(makeXPath("//*:Flow"),
|
||||
"<my:Flow xmlns:my='" + A_URI + "'><a/></my:Flow>");
|
||||
|
||||
assertSize(report, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNamespacePrefixDescendantOkUri() {
|
||||
Report report = xml.executeRule(makeXPath("//Flow", A_URI),
|
||||
"<my:Flow xmlns:my='" + A_URI + "'><a/></my:Flow>");
|
||||
|
||||
assertSize(report, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNamespacePrefixDescendantWrongUri() {
|
||||
Report report = xml.executeRule(makeXPath("//Flow", "wrongURI"),
|
||||
"<my:Flow xmlns:my='" + A_URI + "'><a/></my:Flow>");
|
||||
|
||||
assertSize(report, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRootExpr() {
|
||||
Report report = xml.executeRule(makeXPath("/"),
|
||||
"<Flow><a/></Flow>");
|
||||
|
||||
assertSize(report, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessingInstructions() {
|
||||
Report report = xml.executeRule(makeXPath("/child::processing-instruction()", "http://javafx.com/javafx/8"),
|
||||
FXML_IMPORTS);
|
||||
|
||||
assertSize(report, 5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessingInstructionsNamed() {
|
||||
Report report = xml.executeRule(makeXPath("/child::processing-instruction('import')"),
|
||||
FXML_IMPORTS);
|
||||
|
||||
assertSize(report, 5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessingInstructionXML() {
|
||||
// <?xml ?> does not create a PI
|
||||
Report report = xml.executeRule(makeXPath("/child::processing-instruction('xml')", "http://javafx.com/javafx/8"),
|
||||
FXML_IMPORTS);
|
||||
|
||||
assertSize(report, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComments() {
|
||||
Report report = xml.executeRule(makeXPath("/child::comment()[fn:starts-with(fn:string(.), 'suppress')]"),
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||
+ "<!--suppress JavaFxDefaultTag -->\n"
|
||||
+ "<AnchorPane prefHeight=\"750.0\" prefWidth=\"1200.0\" stylesheets=\"@../css/designer.css\" xmlns=\"http://javafx.com/javafx/8\" xmlns:fx=\"http://javafx.com/fxml/1\">\n"
|
||||
+ "</AnchorPane>");
|
||||
|
||||
assertSize(report, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testXmlNsFunctions() {
|
||||
// https://github.com/pmd/pmd/issues/2766
|
||||
Report report = xml.executeRule(
|
||||
makeXPath("/manifest[namespace-uri-for-prefix('android', .) = 'http://schemas.android.com/apk/res/android']"),
|
||||
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
|
||||
+ "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
|
||||
+ " package=\"com.a.b\">\n"
|
||||
+ "\n"
|
||||
+ " <application\n"
|
||||
+ " android:allowBackup=\"true\"\n"
|
||||
+ " android:icon=\"@mipmap/ic_launcher\"\n"
|
||||
+ " android:label=\"@string/app_name\"\n"
|
||||
+ " android:roundIcon=\"@mipmap/ic_launcher_round\"\n"
|
||||
+ " android:supportsRtl=\"true\"\n"
|
||||
+ " android:theme=\"@style/AppTheme\">\n"
|
||||
+ " <activity android:name=\".MainActivity\">\n"
|
||||
+ " <intent-filter>\n"
|
||||
+ " <action android:name=\"android.intent.action.MAIN\" />\n"
|
||||
+ "\n"
|
||||
+ " <category android:name=\"android.intent.category.LAUNCHER\" />\n"
|
||||
+ " </intent-filter>\n"
|
||||
+ " </activity>\n"
|
||||
+ " </application>\n"
|
||||
+ "\n"
|
||||
+ "</manifest>");
|
||||
|
||||
assertSize(report, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationFuns() {
|
||||
Rule rule = makeXPath("//Flow[pmd:startLine(.) != pmd:endLine(.)]");
|
||||
Report report = xml.executeRule(rule, "<Flow><a/></Flow>");
|
||||
assertSize(report, 0);
|
||||
report = xml.executeRule(rule, "<Flow>\n<a/>\n</Flow>");
|
||||
assertSize(report, 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user