diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 0bd4d2f99f..5c752659ea 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -2,7 +2,7 @@
name: Bug report
about: Create a report to help us improve
title: ''
-labels: bug
+labels: 'a:bug'
assignees: ''
---
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index 5efb987e38..ecbd0ebc84 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -2,7 +2,7 @@
name: Feature request
about: Suggest an idea for this project
title: ''
-labels: enhancement
+labels: 'an:enhancement'
assignees: ''
---
diff --git a/.github/ISSUE_TEMPLATE/new_rule.md b/.github/ISSUE_TEMPLATE/new_rule.md
index ec48bd982c..4e79d37f11 100644
--- a/.github/ISSUE_TEMPLATE/new_rule.md
+++ b/.github/ISSUE_TEMPLATE/new_rule.md
@@ -2,7 +2,7 @@
name: New Rule
about: You have an idea for a new rule? Great!
title: ''
-labels: new-rule
+labels: 'a:new-rule'
assignees: ''
---
diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md
index 9389f8c3ba..5af12acfa1 100644
--- a/.github/ISSUE_TEMPLATE/question.md
+++ b/.github/ISSUE_TEMPLATE/question.md
@@ -2,7 +2,7 @@
name: Question
about: Feel free to ask any question about PMD and its usage
title: ''
-labels: question
+labels: 'a:question'
assignees: ''
---
diff --git a/.github/ISSUE_TEMPLATE/rule_violation.md b/.github/ISSUE_TEMPLATE/rule_violation.md
index 92404bd43b..dd2c61ab0d 100644
--- a/.github/ISSUE_TEMPLATE/rule_violation.md
+++ b/.github/ISSUE_TEMPLATE/rule_violation.md
@@ -2,7 +2,7 @@
name: Rule violation
about: Let us know about a false positive/false negative
title: ''
-labels: bug
+labels: 'a:bug'
assignees: ''
---
diff --git a/.travis.yml b/.travis.yml
index bcfbfccb5c..2276f63309 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -49,7 +49,7 @@ jobs:
env: BUILD=publish
before_install:
- - bash .travis/before_install.sh "11.0.6+10"
+ - bash .travis/before_install.sh "11.0.7+10"
- source ${HOME}/java.env
install: true
before_script: true
diff --git a/docs/_data/sidebars/pmd_sidebar.yml b/docs/_data/sidebars/pmd_sidebar.yml
index b2c7740c6c..0d6145cd6c 100644
--- a/docs/_data/sidebars/pmd_sidebar.yml
+++ b/docs/_data/sidebars/pmd_sidebar.yml
@@ -430,4 +430,7 @@ entries:
- title: Merging pull requests
url: /pmd_projectdocs_committers_merging_pull_requests.html
output: web, pdf
+ - title: Main Landing page
+ url: /pmd_projectdocs_committers_main_landing_page.html
+ output: web, pdf
diff --git a/docs/pages/pmd/projectdocs/committers/main_landing_page.md b/docs/pages/pmd/projectdocs/committers/main_landing_page.md
new file mode 100644
index 0000000000..a0f2515068
--- /dev/null
+++ b/docs/pages/pmd/projectdocs/committers/main_landing_page.md
@@ -0,0 +1,85 @@
+---
+title: Main Landing Page
+permalink: pmd_projectdocs_committers_main_landing_page.html
+last_updated: March 2020
+author: Andreas Dangel
+---
+
+The main homepage of PMD is hosted by Github Pages.
+
+The repository is .
+
+It uses [Jekyll](https://jekyllrb.com/) to generate the static html pages. Jekyll is
+executed by github for every push to the repository. Please note, that it takes some time
+until Jekyll has been executed and due to caching, the homepage is not updated immediately.
+It usually takes 15 minutes.
+
+
+## Contents
+
+* Main page - aka "Landing page":
+ * Layout: [_layouts/default.html](https://github.com/pmd/pmd.github.io/blob/master/_layouts/default.html).
+ It includes all the sub section, which can be found in the includes directory [_includes/](https://github.com/pmd/pmd.github.io/tree/master/_includes)
+ * The latest PMD version is configured in `_config.yml` and the variables `site.pmd.latestVersion` are used
+ e.g. in [_includes/home.html](https://github.com/pmd/pmd.github.io/blob/master/_includes/home.html).
+* Blog - aka "News":
+ * This is a section on main page. It shows the 5 latest news. See [_includes/news.html](https://github.com/pmd/pmd.github.io/blob/master/_includes/news.html).
+ * There is also a sub page "news" which lists all news.
+ * Layout: [_layouts/news.html](https://github.com/pmd/pmd.github.io/blob/master/_layouts/news.html)
+ * Page (which is pretty empty): [news.html](https://github.com/pmd/pmd.github.io/blob/master/news.html)
+* Documentation for the latest release:
+ * The PMD documentation of the latest release is simply copied as static html into the folder [latest/](https://github.com/pmd/pmd.github.io/tree/master/latest).
+ This makes the latest release documentation available under the stable URL
+ . This URL is also used for the [sitemap.xml](https://github.com/pmd/pmd.github.io/blob/master/sitemap.xml).
+* Documentation for previous releases are still being kept under the folders `pmd-/`.
+
+
+## Building the page locally
+
+Since the repository contains the documentation for many old PMD releases, it is quite big. When executing
+Jekyll to generate the site, it copies all the files to the folder `_site/` - and this can take a while.
+
+In order to speed things up locally, consider to add `pmd-*` to the exclude patterns in `_config.yml`. See
+also the comments in this file.
+
+Then it is a matter of simply executing `bundle exec jekyll serve`. This will generate the site and host
+it on localhost, so you can test the page at .
+
+
+## Updates during a release
+
+When creating a new PMD release, some content of the main page need to be updated as well.
+This done as part of the [Release process](pmd_projectdocs_committers_releasing.html), but is
+summarized here as well:
+
+* The versions (e.g. `pmd.latestVersion`) needs to be updated in `_config.yml`
+ * This is needed to generate the correct links and texts for the latest version on landing page
+* The new PMD documentation needs to be copied to `/pmd-/`
+* Then this folder needs to copied to `/latest/`, actually replacing the old version.
+* A new blog post with release notes is added: `/_posts/YYYY-mm-dd-PMD-.md`
+* The sitemap `sitemap.xml` is regenerated
+
+Some of these steps are automated through `do-release.sh` (like blog post), some are manual steps
+(updating the version in _config.yml) and other steps are done on the travis-ci-build (like
+copying the new documentation).
+
+## Adding a new blog post
+
+Adding a new blog post is as easy as:
+
+* Creating a new file in the folder "_posts": `/_posts/YYYY-mm-dd-.md`
+* The file name needs to fit this pattern. The date of the blog post is taken from the file name. The ""
+ is used for the url.
+* The file is a markdown file starting with a frontmatter for jekyll. Just use this template for the new file:
+
+```
+---
+layout: post
+title: Title
+---
+
+Here comes the text
+```
+
+Once you commit and push it, Github will run Jekyll and update the page. The Jekyll templates take care that
+the new post is recognized and added to the news section and also on the news subpage.
diff --git a/docs/pages/pmd/projectdocs/committers/releasing.md b/docs/pages/pmd/projectdocs/committers/releasing.md
index 16a64bc112..0951ac7e16 100644
--- a/docs/pages/pmd/projectdocs/committers/releasing.md
+++ b/docs/pages/pmd/projectdocs/committers/releasing.md
@@ -1,5 +1,5 @@
---
-title: Releasing
+title: Release process
permalink: pmd_projectdocs_committers_releasing.html
author: Romain Pelisse , Andreas Dangel
---
diff --git a/docs/pages/pmd/userdocs/incremental_analysis.md b/docs/pages/pmd/userdocs/incremental_analysis.md
index 701fa19da3..5ba251e901 100644
--- a/docs/pages/pmd/userdocs/incremental_analysis.md
+++ b/docs/pages/pmd/userdocs/incremental_analysis.md
@@ -24,7 +24,7 @@ untouched, files with violations will be listed with full detail. Therefore, its
Incremental analysis is enabled automatically once a location to store the cache has been defined.
From command-line that is done through the [`-cache`](pmd_userdocs_cli_reference.html#cache) argument, but support for the feature is
available for tools integrating PMD such as [Ant](pmd_userdocs_tools_ant.html),
-[Maven](pmd_userdocs_tools_maven.html), and Gradle.
+[Maven](pmd_userdocs_tools_maven.html), and [Gradle](pmd_userdocs_tools_gradle.html).
### Disabling incremental analysis
@@ -32,3 +32,83 @@ available for tools integrating PMD such as [Ant](pmd_userdocs_tools_ant.html),
By default, PMD will suggest to use an analysis cache by logging a warning.
If you'd like to disable this warning, or ignore the analysis cache for a
few runs, you can use the [`-no-cache`](pmd_userdocs_cli_reference.html#no-cache) switch.
+
+
+### FAQ
+
+#### When is the cache invalidated?
+
+On the following reasons, the complete cache file is considered invalid:
+
+* The PMD version differs. Since each PMD version might have fixed some false-positives or false-negatives for rules,
+ a cache file created with a different version is considered invalid. The version comparison is exact.
+* The used ruleset has been changed. If the ruleset is changed in any way (e.g. adding/removing rules, changing
+ rule properties, ...), the cache is considered invalid.
+* The [`auxclasspath`](pmd_userdocs_cli_reference.html#auxclasspath) changed. The auxclasspath is used during
+ type resolution. A changed auxclasspath can result for rules, that use type resolution, in different
+ violations. Usually, if the auxclasspath is correct and type resolution works, the rules report less false-positives.
+ To make sure, the correct violations are reported, the cache is considered invalid, if the auxclasspath has changed.
+* The execution classpath has been changed. On the execution classpath not only the PMD classes are located, but also
+ the implementation of e.g. custom rules. If any jar file/class file on the execution classpath is changed, then
+ the cache is considered invalid as well.
+
+#### What is stored in the cache file?
+
+The cache file consists of a header and a body. The header stores the information which is used to decided
+whether the whole cache file is valid or not (see above). The following information is stored:
+
+* PMD Version
+* Ruleset checksum
+* Auxclasspath checksum
+* Execution classpath checksum
+
+The body contains an entry for every file that has been analyzed. For every file, the following information
+is stored:
+
+* The full (absolute) pathname of the file
+* The checksum of the file itself
+* 0 or more rule violations with all the info (line number, etc.)
+
+You can think of the cache as a Map where the filepath is used as the key
+and the violations found in previous runs are the value.
+
+The cache is in the end just a file with serialized data (binary). The implementation is
+{% jdoc core::cache.FileAnalysisCache %}.
+
+#### How does PMD detect whether a file has been changed?
+
+When analyzing a file, PMD records the checksum of the file content and stores this
+together with the violations in the cache file. When running PMD with the cache file,
+PMD looks up the file in the cache and compares the checksums.
+If the checksums match, then the file is not even parsed, the rules
+are not executed and the violations for this file are entirely used from the cache.
+If the checksum doesn't match, then the cached violations are discarded (if there are any)
+and the file is fully processed: the file is parsed and all the rules are run for it.
+After we are done, the cache is updated with the new violations.
+
+#### Can I reuse a cache created on branch A for analyzing my project on branch B?
+
+This is possible. As long as the same PMD version and same ruleset is used on both branches.
+Also note, that if the branch uses a different dependencies, the auxclasspath is different on both
+classes, which invalidates the cache completely. If you project uses e.g. Maven for dependency
+management and your branch uses different dependencies (either different version or completely different
+artifacts), then the auxclasspath is changed.
+
+If files have been renamed on the branch, these files will be analyzed again since PMD uses
+the file names to assign existing rule violations from the cache. Also, if the full path name
+of the file changes, because the other branch is checked out at a different location, then all
+the cached files don't match.
+
+Apart from these restrictions, PMD will only analyze files that changed between runs.
+If your previous run was on branch A and then you run on branch B using the same cache file,
+it will only look at files that are different between the 2 branches.
+
+#### Can I reuse a cache file across different machines?
+
+This is only possible, if the other machine uses the exact same path names. That means that
+your project needs to be checked out into the same directory structure.
+
+Additionally, all the other restrictions apply (same PMD version, same ruleset, same auxclasspath,
+same execution classpath).
+
+See also issue [#2063 [core] Support sharing incremental analysis cache file across different machines](https://github.com/pmd/pmd/issues/2063).
diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md
index fe9fbfe609..794293b75c 100644
--- a/docs/pages/release_notes.md
+++ b/docs/pages/release_notes.md
@@ -30,17 +30,35 @@ not change the result of your rules*, if it does, please report a bug at https:/
Note that XPath 1.0 support, the default XPath version, is deprecated since PMD 6.22.0.
**We highly recommend that you upgrade your rules to XPath 2.0**. Please refer to the [migration guide](https://pmd.github.io/latest/pmd_userdocs_extending_writing_xpath_rules.html#migrating-from-10-to-20).
+#### New Rules
+* The new Apex rule {% rule "apex/codestyle/FieldDeclarationsShouldBeAtStart" %} (`apex-codestyle`)
+ helps to ensure that field declarations are always at the beginning of a class.
+
+* The new Apex rule {% rule "apex/bestpractices/UnusedLocalVariable" %} (`apex-bestpractices`) detects unused
+ local variables.
### Fixed Issues
-* apex
- * [#2210](https://github.com/pmd/pmd/issues/2210): \[apex] ApexCRUDViolation: Support WITH SECURITY_ENFORCED
+* apex-design
* [#2358](https://github.com/pmd/pmd/issues/2358): \[apex] Invalid Apex in Cognitive Complexity tests
+* apex-security
+ * [#2210](https://github.com/pmd/pmd/issues/2210): \[apex] ApexCRUDViolation: Support WITH SECURITY_ENFORCED
+ * [#2399](https://github.com/pmd/pmd/issues/2399): \[apex] ApexCRUDViolation: false positive with security enforced with line break
+* core
+ * [#2355](https://github.com/pmd/pmd/issues/2355): \[doc] Improve documentation about incremental analysis
+ * [#2356](https://github.com/pmd/pmd/issues/2356): \[doc] Add missing doc about pmd.github.io
* java
* [#2378](https://github.com/pmd/pmd/issues/2378): \[java] AbstractJUnitRule has bad performance on large code bases
+* java-codestyle
+ * [#1164](https://github.com/pmd/pmd/issues/1164): \[java] ClassNamingConventions suggests to add Util for class containing only static constants
+ * [#1723](https://github.com/pmd/pmd/issues/1723): \[java] UseDiamondOperator false-positive inside lambda
* java-design
* [#2390](https://github.com/pmd/pmd/issues/2390): \[java] AbstractClassWithoutAnyMethod: missing violation for nested classes
+* java-errorprone
+ * [#2402](https://github.com/pmd/pmd/issues/2402): \[java] CloseResource possible false positive with Primitive Streams
+* java-multithreading
+ * [#2313](https://github.com/pmd/pmd/issues/2313): \[java] Documenation for DoNotUseThreads is outdated
### API Changes
@@ -51,6 +69,11 @@ Note that XPath 1.0 support, the default XPath version, is deprecated since PMD
Those APIs are not intended to be used by clients, and will be hidden or removed with PMD 7.0.0.
You can identify them with the `@InternalApi` annotation. You'll also get a deprecation warning.
+* {% jdoc core::lang.rule.xpath.AbstractXPathRuleQuery %}
+* {% jdoc core::lang.rule.xpath.JaxenXPathRuleQuery %}
+* {% jdoc core::lang.rule.xpath.SaxonXPathRuleQuery %}
+* {% jdoc core::lang.rule.xpath.XPathRuleQuery %}
+
##### In ASTs
As part of the changes we'd like to do to AST classes for 7.0.0, we would like to
@@ -106,6 +129,11 @@ implementations, and their corresponding Parser if it exists (in the same packag
* {% jdoc !!core::lang.TokenManager#setFileName(java.lang.String) %}
* {% jdoc !!core::lang.ast.AbstractTokenManager#setFileName(java.lang.String) %}
* {% jdoc !!core::lang.ast.AbstractTokenManager#getFileName(java.lang.String) %}
+* {% jdoc !!core::cpd.token.AntlrToken#getType() %} - use `getKind()` instead.
+* {% jdoc core::lang.rule.ImmutableLanguage %}
+* {% jdoc core::lang.rule.MockRule %}
+* {% jdoc !!java::lang.java.ast.ASTRecordDeclaration#getComponentList() %}
+* Multiple fields, constructors and methods in {% jdoc core::lang.rule.XPathRule %}. See javadoc for details.
### External Contributions
@@ -113,6 +141,13 @@ implementations, and their corresponding Parser if it exists (in the same packag
* [#2314](https://github.com/pmd/pmd/pull/2314): \[doc] maven integration - Add version to plugin - [Pham Hai Trung](https://github.com/gpbp)
* [#2353](https://github.com/pmd/pmd/pull/2353): \[plsql] xmlforest with optional AS - [Piotr Szymanski](https://github.com/szyman23)
* [#2383](https://github.com/pmd/pmd/pull/2383): \[apex] Fix invalid apex in documentation - [Gwilym Kuiper](https://github.com/gwilymatgearset)
+* [#2395](https://github.com/pmd/pmd/pull/2395): \[apex] New Rule: Unused local variables - [Gwilym Kuiper](https://github.com/gwilymatgearset)
+* [#2396](https://github.com/pmd/pmd/pull/2396): \[apex] New rule: field declarations should be at start - [Gwilym Kuiper](https://github.com/gwilymatgearset)
+* [#2397](https://github.com/pmd/pmd/pull/2397): \[apex] fixed WITH SECURITY_ENFORCED regex to recognise line break characters - [Kieran Black](https://github.com/kieranlblack)
+* [#2401](https://github.com/pmd/pmd/pull/2401): \[doc] Update DoNotUseThreads rule documentation - [Saikat Sengupta](https://github.com/s4ik4t)
+* [#2403](https://github.com/pmd/pmd/pull/2403): \[java] #2402 fix false-positives on Primitive Streams - [Bernd Farka](https://github.com/BerndFarkaDyna)
+* [#2409](https://github.com/pmd/pmd/pull/2409): \[java] ClassNamingConventions suggests to add Util for class containing only static constants, fixes #1164 - [Binu R J](https://github.com/binu-r)
+* [#2411](https://github.com/pmd/pmd/pull/2411): \[java] Fix UseAssertEqualsInsteadOfAssertTrue Example - [Moritz Scheve](https://github.com/Blightbuster)
{% endtocmaker %}
diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexHandler.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexHandler.java
index 561ec95e63..00bfdff2d9 100644
--- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexHandler.java
+++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexHandler.java
@@ -12,7 +12,6 @@ import net.sourceforge.pmd.lang.AbstractLanguageVersionHandler;
import net.sourceforge.pmd.lang.Parser;
import net.sourceforge.pmd.lang.ParserOptions;
import net.sourceforge.pmd.lang.VisitorStarter;
-import net.sourceforge.pmd.lang.XPathHandler;
import net.sourceforge.pmd.lang.apex.ast.ASTMethod;
import net.sourceforge.pmd.lang.apex.ast.ASTUserClassOrInterface;
import net.sourceforge.pmd.lang.apex.ast.ApexNode;
@@ -22,7 +21,6 @@ import net.sourceforge.pmd.lang.apex.metrics.api.ApexClassMetricKey;
import net.sourceforge.pmd.lang.apex.metrics.api.ApexOperationMetricKey;
import net.sourceforge.pmd.lang.apex.multifile.ApexMultifileVisitorFacade;
import net.sourceforge.pmd.lang.apex.rule.ApexRuleViolationFactory;
-import net.sourceforge.pmd.lang.ast.xpath.DefaultASTXPathHandler;
import net.sourceforge.pmd.lang.metrics.LanguageMetricsProvider;
import net.sourceforge.pmd.lang.metrics.internal.AbstractLanguageMetricsProvider;
import net.sourceforge.pmd.lang.rule.RuleViolationFactory;
@@ -38,12 +36,6 @@ public class ApexHandler extends AbstractLanguageVersionHandler {
return rootNode -> new ApexMultifileVisitorFacade().initializeWith((ApexNode>) rootNode);
}
-
- @Override
- public XPathHandler getXPathHandler() {
- return new DefaultASTXPathHandler();
- }
-
@Override
public RuleViolationFactory getRuleViolationFactory() {
return ApexRuleViolationFactory.INSTANCE;
diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/UnusedLocalVariableRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/UnusedLocalVariableRule.java
new file mode 100644
index 0000000000..b898dd6999
--- /dev/null
+++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/UnusedLocalVariableRule.java
@@ -0,0 +1,38 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.apex.rule.bestpractices;
+
+import java.util.List;
+
+import net.sourceforge.pmd.lang.apex.ast.ASTBlockStatement;
+import net.sourceforge.pmd.lang.apex.ast.ASTVariableDeclaration;
+import net.sourceforge.pmd.lang.apex.ast.ASTVariableExpression;
+import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule;
+
+public class UnusedLocalVariableRule extends AbstractApexRule {
+ public UnusedLocalVariableRule() {
+ addRuleChainVisit(ASTVariableDeclaration.class);
+ }
+
+ @Override
+ public Object visit(ASTVariableDeclaration node, Object data) {
+ String variableName = node.getImage();
+
+ ASTBlockStatement variableContext = node.getFirstParentOfType(ASTBlockStatement.class);
+ List potentialUsages = variableContext.findDescendantsOfType(ASTVariableExpression.class);
+
+ for (ASTVariableExpression usage : potentialUsages) {
+ if (usage.getParent() == node) {
+ continue;
+ }
+ if (usage.getImage().equals(variableName)) {
+ return data;
+ }
+ }
+
+ addViolation(data, node, variableName);
+ return data;
+ }
+}
diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/FieldDeclarationsShouldBeAtStartRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/FieldDeclarationsShouldBeAtStartRule.java
new file mode 100644
index 0000000000..0c3c056306
--- /dev/null
+++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/FieldDeclarationsShouldBeAtStartRule.java
@@ -0,0 +1,74 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.apex.rule.codestyle;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import net.sourceforge.pmd.lang.apex.ast.ASTBlockStatement;
+import net.sourceforge.pmd.lang.apex.ast.ASTField;
+import net.sourceforge.pmd.lang.apex.ast.ASTMethod;
+import net.sourceforge.pmd.lang.apex.ast.ASTProperty;
+import net.sourceforge.pmd.lang.apex.ast.ASTUserClass;
+import net.sourceforge.pmd.lang.apex.ast.ApexNode;
+import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule;
+
+public class FieldDeclarationsShouldBeAtStartRule extends AbstractApexRule {
+ private static final Comparator> NODE_BY_SOURCE_LOCATION_COMPARATOR =
+ Comparator
+ .>comparingInt(ApexNode::getBeginLine)
+ .thenComparing(ApexNode::getBeginColumn);
+ public static final String STATIC_INITIALIZER_METHOD_NAME = "";
+
+ public FieldDeclarationsShouldBeAtStartRule() {
+ addRuleChainVisit(ASTUserClass.class);
+ }
+
+ @Override
+ public Object visit(ASTUserClass node, Object data) {
+ // Unfortunately the parser re-orders the AST to put field declarations before method declarations
+ // so we have to rely on line numbers / positions to work out where the first non-field declaration starts
+ // so we can check if the fields are in acceptable places.
+ List fields = node.findChildrenOfType(ASTField.class);
+
+ List> nonFieldDeclarations = new ArrayList<>();
+
+ nonFieldDeclarations.addAll(getMethodNodes(node));
+ nonFieldDeclarations.addAll(node.findChildrenOfType(ASTUserClass.class));
+ nonFieldDeclarations.addAll(node.findChildrenOfType(ASTProperty.class));
+ nonFieldDeclarations.addAll(node.findChildrenOfType(ASTBlockStatement.class));
+
+ Optional> firstNonFieldDeclaration = nonFieldDeclarations.stream()
+ .filter(ApexNode::hasRealLoc)
+ .min(NODE_BY_SOURCE_LOCATION_COMPARATOR);
+
+ if (!firstNonFieldDeclaration.isPresent()) {
+ // there is nothing except field declaration, so that has to come first
+ return data;
+ }
+
+ for (ASTField field : fields) {
+ if (NODE_BY_SOURCE_LOCATION_COMPARATOR.compare(field, firstNonFieldDeclaration.get()) > 0) {
+ addViolation(data, field, field.getName());
+ }
+ }
+
+ return data;
+ }
+
+ private List> getMethodNodes(ASTUserClass node) {
+ // The method represents static initializer blocks, of which there can be many. The
+ // method doesn't contain location information, however the containing ASTBlockStatements do,
+ // so we fetch them for that method only.
+ return node.findChildrenOfType(ASTMethod.class).stream()
+ .flatMap(method -> method.getImage().equals(STATIC_INITIALIZER_METHOD_NAME)
+ ? method.findChildrenOfType(ASTBlockStatement.class).stream() : Stream.of(method))
+ .collect(Collectors.toList());
+ }
+}
diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexCRUDViolationRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexCRUDViolationRule.java
index f1571cd01c..7616100ee3 100644
--- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexCRUDViolationRule.java
+++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexCRUDViolationRule.java
@@ -85,7 +85,7 @@ public class ApexCRUDViolationRule extends AbstractApexRule {
private static final String[] RESERVED_KEYS_FLS = new String[] { "Schema", S_OBJECT_TYPE, };
- private static final Pattern WITH_SECURITY_ENFORCED = Pattern.compile("(?i).*[^']\\s*WITH\\s+SECURITY_ENFORCED\\s*[^']*");
+ private static final Pattern WITH_SECURITY_ENFORCED = Pattern.compile("(?is).*[^']\\s*WITH\\s+SECURITY_ENFORCED\\s*[^']*");
private final Map varToTypeMapping = new HashMap<>();
private final ListMultimap typeToDMLOperationMapping = ArrayListMultimap.create();
diff --git a/pmd-apex/src/main/resources/category/apex/bestpractices.xml b/pmd-apex/src/main/resources/category/apex/bestpractices.xml
index 9d2913a771..f91fa5095a 100644
--- a/pmd-apex/src/main/resources/category/apex/bestpractices.xml
+++ b/pmd-apex/src/main/resources/category/apex/bestpractices.xml
@@ -208,4 +208,25 @@ public class Foo {
+
+
+Detects when a local variable is declared and/or assigned but not used.
+
+
+
+
+
+
diff --git a/pmd-apex/src/main/resources/category/apex/codestyle.xml b/pmd-apex/src/main/resources/category/apex/codestyle.xml
index e76d6ebb8a..0c0ea51a4a 100644
--- a/pmd-apex/src/main/resources/category/apex/codestyle.xml
+++ b/pmd-apex/src/main/resources/category/apex/codestyle.xml
@@ -104,6 +104,30 @@ if (foo) { // preferred approach
+
+
+ Field declarations should appear before method declarations within a class.
+
+ 3
+
+
+
+
+
-
diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/UnusedLocalVariableTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/UnusedLocalVariableTest.java
new file mode 100644
index 0000000000..cef1960afc
--- /dev/null
+++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/UnusedLocalVariableTest.java
@@ -0,0 +1,11 @@
+/*
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.apex.rule.bestpractices;
+
+import net.sourceforge.pmd.testframework.PmdRuleTst;
+
+public class UnusedLocalVariableTest extends PmdRuleTst {
+ // no additional unit tests
+}
diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/FieldDeclarationsShouldBeAtStartTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/FieldDeclarationsShouldBeAtStartTest.java
new file mode 100644
index 0000000000..c22140d52d
--- /dev/null
+++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/FieldDeclarationsShouldBeAtStartTest.java
@@ -0,0 +1,11 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.apex.rule.codestyle;
+
+import net.sourceforge.pmd.testframework.PmdRuleTst;
+
+public class FieldDeclarationsShouldBeAtStartTest extends PmdRuleTst {
+ // no additional unit tests
+}
diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/bestpractices/xml/UnusedLocalVariable.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/bestpractices/xml/UnusedLocalVariable.xml
new file mode 100644
index 0000000000..5e5e7ed803
--- /dev/null
+++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/bestpractices/xml/UnusedLocalVariable.xml
@@ -0,0 +1,82 @@
+
+
+
+ Unused variables should result in errors
+ 2
+ 3,7
+
+ Variable 'foo' defined but not used
+ Variable 'foo' defined but not used
+
+
+
+
+
+
+
+ Used variables should not result in errors
+ 0
+
+
+
+
+
+
+ Shadowing a field
+ 1
+ 5
+
+
+
\ No newline at end of file
diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/FieldDeclarationsShouldBeAtStart.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/FieldDeclarationsShouldBeAtStart.xml
new file mode 100644
index 0000000000..f8f1b95503
--- /dev/null
+++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/FieldDeclarationsShouldBeAtStart.xml
@@ -0,0 +1,207 @@
+
+
+
+
+ Does not warn if there are no methods
+ 0
+
+
+
+
+
+
+ Does warn if a field is after a method
+ 1
+ 4
+
+ Field declaration for 'thisIsNotOkay' should be before method declarations in its class
+
+
+
+
+
+
+
+ Warns if field is after constructor
+ 1
+ 6
+
+ Field declaration for 'someField' should be before method declarations in its class
+
+
+
+
+
+
+
+ Warns only for fields after the first method declaration
+ 1
+ 8
+
+ Field declaration for 'thisFieldIsNotOkay' should be before method declarations in its class
+
+
+
+
+
+
+
+ Warns for fields defined on the same line after a method
+ 1
+ 2
+
+ Field declaration for 'thisFieldIsNotOkay' should be before method declarations in its class
+
+
+
+
+
+
+
+ Does not warn for fields defined on the same line before a method
+ 0
+
+
+
+
+
+
+ Allows nested classes to have fields
+ 0
+
+
+
+
+
+
+ Allows nested classes to have fields
+ 1
+ 9
+
+
+
+
+
+
+ Fields should go before inner classes too
+ 1
+ 4
+
+
+
+
+
+
+ Fields should go before properties too
+ 1
+ 4
+
+
+
+
+
+
+ Fields should go before block statements
+ 1
+ 6
+
+
+
+
+
+
+ Fields should go before static block statements
+ 1
+ 6
+
+
+
+
+
\ No newline at end of file
diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/security/xml/ApexCRUDViolation.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/security/xml/ApexCRUDViolation.xml
index 6df26b5834..3d0293ebcb 100644
--- a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/security/xml/ApexCRUDViolation.xml
+++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/security/xml/ApexCRUDViolation.xml
@@ -275,6 +275,19 @@ public class Foo {
} ]]>
+
+ Accepts Closure SECURITY ENFORCED Line Break
+ 0
+
+
+
Accepts Closure SECURITY ENFORCED in a List
0
@@ -287,6 +300,19 @@ public class Foo {
} ]]>
+
+ Accepts Closure SECURITY ENFORCED in a List Line Break
+ 0
+ m() {
+ List c = [SELECT Name FROM Contact
+ WITH SECURITY_ENFORCED LIMIT 1];
+ return c;
+ }
+} ]]>
+
+
Accepts Closure SECURITY ENFORCED with Case Insensitivity
0
@@ -299,6 +325,19 @@ public class Foo {
} ]]>
+
+ Accepts Closure SECURITY ENFORCED with Case Insensitivity Line Break
+ 0
+
+
+
Accepts Closure SECURITY ENFORCED Not Secured
1
@@ -323,6 +362,19 @@ public class Foo {
} ]]>
+
+ Accepts Closure SECURITY ENFORCED Secured Line Break
+ 0
+
+
+
Proper accessibility CRUD,FLS
0
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/RuleSetFactory.java b/pmd-core/src/main/java/net/sourceforge/pmd/RuleSetFactory.java
index a9229d480f..4603f6f727 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/RuleSetFactory.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/RuleSetFactory.java
@@ -305,7 +305,7 @@ public class RuleSetFactory {
public RuleSet createSingleRuleRuleSet(final Rule rule) { // TODO make static?
final long checksum;
if (rule instanceof XPathRule) {
- checksum = rule.getProperty(XPathRule.XPATH_DESCRIPTOR).hashCode();
+ checksum = ((XPathRule) rule).getXPathExpression().hashCode();
} else {
// TODO : Is this good enough? all properties' values + rule name
checksum = rule.getPropertiesByPropertyDescriptor().values().hashCode() * 31 + rule.getName().hashCode();
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/token/AntlrToken.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/token/AntlrToken.java
index 476577e325..f28544d8d4 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/token/AntlrToken.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/token/AntlrToken.java
@@ -64,10 +64,18 @@ public class AntlrToken implements GenericToken {
return token.getCharPositionInLine() + token.getStopIndex() - token.getStartIndex();
}
- public int getType() {
+ public int getKind() {
return token.getType();
}
+ /**
+ * @deprecated use {@link #getKind()} instead.
+ */
+ @Deprecated
+ public int getType() {
+ return getKind();
+ }
+
public boolean isHidden() {
return token.getChannel() == Lexer.HIDDEN;
}
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/token/AntlrTokenFilter.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/token/AntlrTokenFilter.java
index c76332d69e..bb29f4d120 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/token/AntlrTokenFilter.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/token/AntlrTokenFilter.java
@@ -25,6 +25,6 @@ public class AntlrTokenFilter extends BaseTokenFilter {
@Override
protected boolean shouldStopProcessing(final AntlrToken currentToken) {
- return currentToken.getType() == EOF;
+ return currentToken.getKind() == EOF;
}
}
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/AbstractLanguageVersionHandler.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/AbstractLanguageVersionHandler.java
index 9fb7fb820f..634e00fe24 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/AbstractLanguageVersionHandler.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/AbstractLanguageVersionHandler.java
@@ -18,6 +18,7 @@ import net.sourceforge.pmd.util.designerbindings.DesignerBindings;
*/
public abstract class AbstractLanguageVersionHandler implements LanguageVersionHandler {
+
@Override
public DataFlowHandler getDataFlowHandler() {
return DataFlowHandler.DUMMY;
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/XPathHandler.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/XPathHandler.java
index 95728185d7..2895550346 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/XPathHandler.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/XPathHandler.java
@@ -7,6 +7,7 @@ package net.sourceforge.pmd.lang;
import org.jaxen.Navigator;
import net.sourceforge.pmd.annotation.InternalApi;
+import net.sourceforge.pmd.lang.ast.xpath.DefaultASTXPathHandler;
import net.sourceforge.pmd.lang.xpath.Initializer;
import net.sf.saxon.sxpath.IndependentContext;
@@ -19,22 +20,7 @@ import net.sf.saxon.sxpath.IndependentContext;
@Deprecated
public interface XPathHandler {
- XPathHandler DUMMY = new XPathHandler() {
- @Override
- public void initialize() {
- // empty handler - does nothing
- }
-
- @Override
- public void initialize(IndependentContext context) {
- // empty handler - does nothing
- }
-
- @Override
- public Navigator getNavigator() {
- return null;
- }
- };
+ XPathHandler DUMMY = new DefaultASTXPathHandler();
/**
* Initialize. This is intended to be called by {@link Initializer} to
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/AbstractNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/AbstractNode.java
index e7f7b0fdef..c14ede1d12 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/AbstractNode.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/AbstractNode.java
@@ -357,7 +357,7 @@ public abstract class AbstractNode implements Node {
}
@Override
- public List findDescendantsOfType(final Class targetType) {
+ public List findDescendantsOfType(final Class extends T> targetType) {
final List list = new ArrayList<>();
findDescendantsOfType(this, targetType, list, false);
return list;
@@ -381,7 +381,7 @@ public abstract class AbstractNode implements Node {
findDescendantsOfType(this, targetType, results, crossBoundaries);
}
- private static void findDescendantsOfType(final Node node, final Class targetType, final List results,
+ private static void findDescendantsOfType(final Node node, final Class extends T> targetType, final List results,
final boolean crossFindBoundaries) {
for (Node child : node.children()) {
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Node.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Node.java
index 1b8172ec54..46b9ae08f3 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Node.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Node.java
@@ -284,7 +284,7 @@ public interface Node {
* @return List of all children of type targetType. Returns an empty list if
* none found.
*/
- List findDescendantsOfType(Class targetType);
+ List findDescendantsOfType(Class extends T> targetType);
/**
* Traverses down the tree to find all the descendant instances of type
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/AbstractASTXPathHandler.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/AbstractASTXPathHandler.java
index f4c1e8ddbe..eb470c94bd 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/AbstractASTXPathHandler.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/AbstractASTXPathHandler.java
@@ -25,4 +25,14 @@ public abstract class AbstractASTXPathHandler implements XPathHandler {
public void initialize(IndependentContext context, Language language, Class> functionsClass) {
context.declareNamespace("pmd-" + language.getTerseName(), "java:" + functionsClass.getName());
}
+
+ @Override
+ public void initialize() {
+ // override if needed
+ }
+
+ @Override
+ public void initialize(IndependentContext context) {
+ // override if needed
+ }
}
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/DefaultASTXPathHandler.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/DefaultASTXPathHandler.java
index 974d426e80..3fa844f28a 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/DefaultASTXPathHandler.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/DefaultASTXPathHandler.java
@@ -12,6 +12,7 @@ import net.sf.saxon.sxpath.IndependentContext;
@Deprecated
@InternalApi
public class DefaultASTXPathHandler extends AbstractASTXPathHandler {
+
@Override
public void initialize() {
// override if needed
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/ImmutableLanguage.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/ImmutableLanguage.java
index b71e01d24d..73aeeb4265 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/ImmutableLanguage.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/ImmutableLanguage.java
@@ -8,6 +8,10 @@ package net.sourceforge.pmd.lang.rule;
* This is a tag interface to indicate that a Rule implementation class does not
* support changes to it's Language. The Language is integral to the proper
* functioning of the Rule.
+ *
+ * @deprecated No rule supports a change to their language. This will
+ * be made the default behaviour with PMD 7.0.0.
*/
+@Deprecated
public interface ImmutableLanguage {
}
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/MockRule.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/MockRule.java
index 0cfad63c71..5318030a02 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/MockRule.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/MockRule.java
@@ -20,7 +20,12 @@ import net.sourceforge.pmd.properties.PropertyFactory;
* functional Rule is not needed. For example, during unit testing, or as an
* editable surrogate used by IDE plugins. The Language of this Rule defaults to
* Java.
+ *
+ * @deprecated This is not a supported API. You need the pmd-test module
+ * on your classpath, or pmd-core's test sources. This will be removed
+ * in 7.0.0
*/
+@Deprecated
public class MockRule extends AbstractRule {
public MockRule() {
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/XPathRule.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/XPathRule.java
index 773d64a5c0..d2c438a6df 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/XPathRule.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/XPathRule.java
@@ -12,6 +12,7 @@ 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;
@@ -20,17 +21,19 @@ import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.rule.xpath.JaxenXPathRuleQuery;
import net.sourceforge.pmd.lang.rule.xpath.SaxonXPathRuleQuery;
import net.sourceforge.pmd.lang.rule.xpath.XPathRuleQuery;
+import net.sourceforge.pmd.lang.rule.xpath.XPathVersion;
import net.sourceforge.pmd.properties.EnumeratedProperty;
import net.sourceforge.pmd.properties.StringProperty;
/**
* Rule that tries to match an XPath expression against a DOM view of an AST.
- *
- * This rule needs a "xpath" property value in order to function.
*/
public class XPathRule extends AbstractRule {
- // TODO 7.0.0 use PropertyDescriptor
+ /**
+ * @deprecated Use {@link #XPathRule(XPathVersion, String)}
+ */
+ @Deprecated
public static final StringProperty XPATH_DESCRIPTOR = StringProperty.named("xpath")
.desc("XPath expression")
.defaultValue("")
@@ -47,7 +50,11 @@ public class XPathRule extends AbstractRule {
XPATH_VERSIONS = Collections.unmodifiableMap(tmp);
}
- // published, can't be converted
+
+ /**
+ * @deprecated Use {@link #XPathRule(XPathVersion, String)}
+ */
+ @Deprecated
public static final EnumeratedProperty VERSION_DESCRIPTOR = EnumeratedProperty.named("version")
.desc("XPath specification version")
.mappings(XPATH_VERSIONS)
@@ -63,6 +70,8 @@ public class XPathRule extends AbstractRule {
/**
* Creates a new XPathRule without the corresponding XPath query.
+ *
+ * @deprecated Use {@link #XPathRule(XPathVersion, String)}
*/
public XPathRule() {
definePropertyDescriptor(XPATH_DESCRIPTOR);
@@ -73,6 +82,8 @@ public class XPathRule extends AbstractRule {
/**
* Creates a new XPathRule and associates the XPath query.
+ *
+ * @deprecated Use {@link #XPathRule(XPathVersion, String)}
*/
public XPathRule(final String xPath) {
this();
@@ -80,21 +91,54 @@ public class XPathRule extends AbstractRule {
}
/**
- * Sets the XPath to query against the desired nodes in {@link #apply(List, RuleContext)}.
+ * Make a new XPath rule with the given version + expression
*
- * @param xPath the XPath query
+ * @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");
+ setXPath(expression);
+ setVersion(version.getXmlName());
+ }
+
+ /**
+ * Returns the version for this rule. Returns null if this is not
+ * set or invalid.
+ */
+ public XPathVersion getVersion() {
+ return XPathVersion.ofId(getProperty(VERSION_DESCRIPTOR));
+ }
+
+ /**
+ * Returns the XPath expression that implements this rule.
+ */
+ public String getXPathExpression() {
+ return getProperty(XPATH_DESCRIPTOR);
+ }
+
+ /**
+ * @deprecated Use the constructor {@link #XPathRule(XPathVersion, String)},
+ * don't set the expression after the fact.
+ */
+ @Deprecated
public void setXPath(final String xPath) {
setProperty(XPathRule.XPATH_DESCRIPTOR, xPath);
}
+ /**
+ * @deprecated Use the constructor {@link #XPathRule(XPathVersion, String)},
+ * don't set the version after the fact.
+ */
+ @Deprecated
public void setVersion(final String version) {
setProperty(XPathRule.VERSION_DESCRIPTOR, version);
}
- /**
- * Apply the rule to all nodes.
- */
@Override
public void apply(List extends Node> nodes, RuleContext ctx) {
for (Node node : nodes) {
@@ -107,7 +151,10 @@ public class XPathRule extends AbstractRule {
*
* @param node The Node that to be checked.
* @param data The RuleContext.
+ *
+ * @deprecated Use {@link #apply(List, RuleContext)}
*/
+ @Deprecated
public void evaluate(final Node node, final RuleContext data) {
if (xPathRuleQueryNeedsInitialization()) {
initXPathRuleQuery();
@@ -124,13 +171,21 @@ public class XPathRule extends AbstractRule {
* engine in which the query will be run it looks at the XPath version.
*/
private void initXPathRuleQuery() {
- String xpath = getProperty(XPATH_DESCRIPTOR);
- String version = getProperty(VERSION_DESCRIPTOR);
+ String xpath = getXPathExpression();
+ XPathVersion version = getVersion();
- initRuleQueryBasedOnVersion(version);
+ if (version == null) {
+ throw new IllegalStateException("Invalid XPath version, should have been caught by Rule::dysfunctionReason");
+ }
+
+ if (version == XPathVersion.XPATH_1_0) {
+ xpathRuleQuery = new JaxenXPathRuleQuery();
+ } else {
+ xpathRuleQuery = new SaxonXPathRuleQuery();
+ }
xpathRuleQuery.setXPath(xpath);
- xpathRuleQuery.setVersion(version);
+ xpathRuleQuery.setVersion(version.getXmlName());
xpathRuleQuery.setProperties(getPropertiesByPropertyDescriptor());
}
@@ -143,10 +198,6 @@ public class XPathRule extends AbstractRule {
return xpathRuleQuery == null;
}
- private void initRuleQueryBasedOnVersion(final String version) {
- xpathRuleQuery = XPATH_1_0.equals(version) ? new JaxenXPathRuleQuery() : new SaxonXPathRuleQuery();
- }
-
@Override
public List getRuleChainVisits() {
if (xPathRuleQueryNeedsInitialization()) {
@@ -161,10 +212,11 @@ public class XPathRule extends AbstractRule {
@Override
public String dysfunctionReason() {
- return hasXPathExpression() ? null : "Missing xPath expression";
- }
-
- private boolean hasXPathExpression() {
- return StringUtils.isNotBlank(getProperty(XPATH_DESCRIPTOR));
+ if (getVersion() == null) {
+ return "Invalid XPath version '" + getProperty(VERSION_DESCRIPTOR) + "'";
+ } else if (StringUtils.isBlank(getXPathExpression())) {
+ return "Missing XPath expression";
+ }
+ return null;
}
}
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/AbstractXPathRuleQuery.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/AbstractXPathRuleQuery.java
index 69c6d0afdf..5647fb9ec8 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/AbstractXPathRuleQuery.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/AbstractXPathRuleQuery.java
@@ -9,12 +9,17 @@ import java.util.List;
import java.util.Map;
import net.sourceforge.pmd.RuleContext;
+import net.sourceforge.pmd.annotation.InternalApi;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.properties.PropertyDescriptor;
/**
* This implementation of XPathRuleQuery provides support for RuleChain visits.
+ *
+ * @deprecated Internal API
*/
+@Deprecated
+@InternalApi
public abstract class AbstractXPathRuleQuery implements XPathRuleQuery {
/**
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/JaxenXPathRuleQuery.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/JaxenXPathRuleQuery.java
index 2ecd213c9a..1e331a7b28 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/JaxenXPathRuleQuery.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/JaxenXPathRuleQuery.java
@@ -31,12 +31,17 @@ import org.jaxen.expr.XPathFactory;
import org.jaxen.saxpath.Axis;
import net.sourceforge.pmd.RuleContext;
+import net.sourceforge.pmd.annotation.InternalApi;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.properties.PropertyDescriptor;
/**
* This is a Jaxen based XPathRule query.
+ *
+ * @deprecated Internal API
*/
+@Deprecated
+@InternalApi
public class JaxenXPathRuleQuery extends AbstractXPathRuleQuery {
private static final Logger LOG = Logger.getLogger(JaxenXPathRuleQuery.class.getName());
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/SaxonXPathRuleQuery.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/SaxonXPathRuleQuery.java
index 3f4178a754..231bd0a917 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/SaxonXPathRuleQuery.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/SaxonXPathRuleQuery.java
@@ -16,6 +16,7 @@ import java.util.logging.Logger;
import java.util.regex.Pattern;
import net.sourceforge.pmd.RuleContext;
+import net.sourceforge.pmd.annotation.InternalApi;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.ast.xpath.saxon.DocumentNode;
import net.sourceforge.pmd.lang.ast.xpath.saxon.ElementNode;
@@ -50,7 +51,11 @@ import net.sf.saxon.value.Value;
/**
* This is a Saxon based XPathRule query.
+ *
+ * @deprecated Internal API
*/
+@Deprecated
+@InternalApi
public class SaxonXPathRuleQuery extends AbstractXPathRuleQuery {
/**
* Special nodeName that references the root expression.
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/XPathRuleQuery.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/XPathRuleQuery.java
index dedd90deab..cd1d36c32f 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/XPathRuleQuery.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/XPathRuleQuery.java
@@ -8,6 +8,7 @@ import java.util.List;
import java.util.Map;
import net.sourceforge.pmd.RuleContext;
+import net.sourceforge.pmd.annotation.InternalApi;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.properties.PropertyDescriptor;
@@ -22,24 +23,38 @@ import net.sourceforge.pmd.properties.PropertyDescriptor;
* are recommended to manage internal state that is invariant over AST Nodes in
* a fashion which facilities high performance (e.g. caching).
*
+ *
+ * @deprecated This will be internalized in 7.0.0.
*/
+@InternalApi
+@Deprecated
public interface XPathRuleQuery {
/**
* XPath 1.0 version.
+ *
+ * @deprecated Use {@link XPathVersion}
*/
+ @Deprecated
String XPATH_1_0 = "1.0";
/**
* XPath 1.0 compatibility version.
+ *
+ * @deprecated Use {@link XPathVersion}
*/
+ @Deprecated
String XPATH_1_0_COMPATIBILITY = "1.0 compatibility";
/**
* XPath 2.0 version.
+ *
+ * @deprecated Use {@link XPathVersion}
*/
+ @Deprecated
String XPATH_2_0 = "2.0";
+
/**
* Set the XPath query string to be used.
*
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/XPathVersion.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/XPathVersion.java
new file mode 100644
index 0000000000..7177afa443
--- /dev/null
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/XPathVersion.java
@@ -0,0 +1,70 @@
+/*
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.rule.xpath;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Constants for XPath language version used in XPath queries.
+ */
+public enum XPathVersion {
+ /**
+ * XPath 1.0.
+ *
+ * @deprecated Will become unsupported in 7.0.0
+ */
+ @Deprecated
+ XPATH_1_0(XPathRuleQuery.XPATH_1_0),
+
+ /**
+ * XPath 1.0 compatibility mode.
+ *
+ * @deprecated Will become unsupported in 7.0.0
+ */
+ @Deprecated
+ XPATH_1_0_COMPATIBILITY(XPathRuleQuery.XPATH_1_0_COMPATIBILITY),
+
+ /** XPath 2.0. */
+ XPATH_2_0(XPathRuleQuery.XPATH_2_0);
+
+ private static final Map BY_NAME = new HashMap<>();
+ private final String version;
+
+
+ static {
+ for (XPathVersion value : values()) {
+ BY_NAME.put(value.getXmlName(), value);
+ }
+ }
+
+
+ XPathVersion(String version) {
+ this.version = version;
+ }
+
+
+ /**
+ * Returns the string used to represent the version in the XML.
+ *
+ * @return A string representation
+ */
+ public String getXmlName() {
+ return version;
+ }
+
+
+ /**
+ * Gets an XPath version from the string used to represent
+ * it in the XML.
+ *
+ * @param version A version string
+ *
+ * @return An XPath version, or null if the argument is not a valid version
+ */
+ public static XPathVersion ofId(String version) {
+ return BY_NAME.get(version);
+ }
+}
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TextTreeRenderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TextTreeRenderer.java
new file mode 100644
index 0000000000..8d1e822672
--- /dev/null
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TextTreeRenderer.java
@@ -0,0 +1,210 @@
+/*
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.util.treeexport;
+
+import java.io.IOException;
+
+import net.sourceforge.pmd.annotation.Experimental;
+import net.sourceforge.pmd.lang.ast.Node;
+import net.sourceforge.pmd.properties.AbstractPropertySource;
+import net.sourceforge.pmd.properties.PropertyDescriptor;
+import net.sourceforge.pmd.properties.PropertyFactory;
+import net.sourceforge.pmd.properties.PropertySource;
+
+/**
+ * A simple recursive printer. Output looks like so:
+ *
+ *
+ *
+ * +- LocalVariableDeclaration
+ * +- Type
+ * | +- PrimitiveType
+ * +- VariableDeclarator
+ * +- VariableDeclaratorId
+ * +- VariableInitializer
+ * +- 1 child not shown
+ *
+ *
+ *
+ * or
+ *
+ *
+ *
+ * └─ LocalVariableDeclaration
+ * ├─ Type
+ * │ └─ PrimitiveType
+ * └─ VariableDeclarator
+ * ├─ VariableDeclaratorId
+ * └─ VariableInitializer
+ * └─ 1 child not shown
+ *
+ *
+ *
+ *
+ * By default just prints the structure, like shown above. You can
+ * configure it to render nodes differently by overriding {@link #appendNodeInfoLn(Appendable, Node)}.
+ */
+@Experimental
+public class TextTreeRenderer implements TreeRenderer {
+
+ static final TreeRendererDescriptor DESCRIPTOR = new TreeRendererDescriptor() {
+
+ private final PropertyDescriptor onlyAscii =
+ PropertyFactory.booleanProperty("onlyAsciiChars")
+ .defaultValue(false)
+ .desc("Use only ASCII characters in the structure")
+ .build();
+
+ private final PropertyDescriptor maxLevel =
+ PropertyFactory.intProperty("maxLevel")
+ .defaultValue(-1)
+ .desc("Max level on which to recurse. Negative means unbounded")
+ .build();
+
+ @Override
+ public PropertySource newPropertyBundle() {
+
+ PropertySource bundle = new AbstractPropertySource() {
+ @Override
+ protected String getPropertySourceType() {
+ return "tree renderer";
+ }
+
+ @Override
+ public String getName() {
+ return "text";
+ }
+ };
+
+ bundle.definePropertyDescriptor(onlyAscii);
+ bundle.definePropertyDescriptor(maxLevel);
+
+ return bundle;
+ }
+
+ @Override
+ public String id() {
+ return "text";
+ }
+
+ @Override
+ public String description() {
+ return "Text renderer";
+ }
+
+ @Override
+ public TreeRenderer produceRenderer(PropertySource properties) {
+ return new TextTreeRenderer(properties.getProperty(onlyAscii), properties.getProperty(maxLevel));
+ }
+ };
+
+ private final Strings str;
+ private final int maxLevel;
+
+ /**
+ * Creates a new text renderer.
+ *
+ * @param onlyAscii Whether to output the skeleton of the tree with
+ * only ascii characters. If false, uses unicode chars
+ * like '├'
+ * @param maxLevel Max level on which to recurse. Negative means
+ * unbounded. If the max level is reached, a placeholder
+ * is dumped, like "1 child is not shown". This is
+ * controlled by {@link #appendBoundaryForNodeLn(Node, Appendable, String)}.
+ */
+ public TextTreeRenderer(boolean onlyAscii, int maxLevel) {
+ this.str = onlyAscii ? Strings.ASCII : Strings.UNICODE;
+ this.maxLevel = maxLevel;
+ }
+
+ @Override
+ public void renderSubtree(Node node, Appendable out) throws IOException {
+ printInnerNode(node, out, 0, "", true);
+ }
+
+ private String childPrefix(String prefix, boolean isTail) {
+ return prefix + (isTail ? str.gap : str.verticalEdge);
+ }
+
+
+ protected final void appendIndent(Appendable out, String prefix, boolean isTail) throws IOException {
+ out.append(prefix).append(isTail ? str.tailFork : str.fork);
+ }
+
+ /**
+ * Append info about the node. The indent has already been appended.
+ * This should end with a newline. The default just appends the name
+ * of the node, and no other information.
+ */
+ protected void appendNodeInfoLn(Appendable out, Node node) throws IOException {
+ out.append(node.getXPathNodeName()).append("\n");
+ }
+
+
+ private void printInnerNode(Node node,
+ Appendable out,
+ int level,
+ String prefix,
+ boolean isTail) throws IOException {
+
+ appendIndent(out, prefix, isTail);
+ appendNodeInfoLn(out, node);
+
+ if (level == maxLevel) {
+ if (node.getNumChildren() > 0) {
+ appendBoundaryForNodeLn(node, out, childPrefix(prefix, isTail));
+ }
+ } else {
+ int n = node.getNumChildren() - 1;
+ String childPrefix = childPrefix(prefix, isTail);
+ for (int i = 0; i < node.getNumChildren(); i++) {
+ Node child = node.getChild(i);
+ printInnerNode(child, out, level + 1, childPrefix, i == n);
+ }
+ }
+ }
+
+ protected void appendBoundaryForNodeLn(Node node, Appendable out, String indentStr) throws IOException {
+ appendIndent(out, indentStr, true);
+
+ if (node.getNumChildren() == 1) {
+ out.append("1 child is not shown");
+ } else {
+ out.append(String.valueOf(node.getNumChildren())).append(" children are not shown");
+ }
+
+ out.append('\n');
+ }
+
+ private static final class Strings {
+
+ private static final Strings ASCII = new Strings(
+ "+- ",
+ "+- ",
+ "| ",
+ " "
+ );
+ private static final Strings UNICODE = new Strings(
+ "└─ ",
+ "├─ ",
+ "│ ",
+ " "
+ );
+
+ private final String tailFork;
+ private final String fork;
+ private final String verticalEdge;
+ private final String gap;
+
+
+ private Strings(String tailFork, String fork, String verticalEdge, String gap) {
+ this.tailFork = tailFork;
+ this.fork = fork;
+ this.verticalEdge = verticalEdge;
+ this.gap = gap;
+ }
+ }
+
+}
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeRenderers.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeRenderers.java
index 3fc1e5ea1a..8f47c9c9e8 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeRenderers.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeRenderers.java
@@ -102,7 +102,10 @@ public final class TreeRenderers {
static {
- REGISTRY.put(XML.id(), XML);
+ List builtinDescriptors = Arrays.asList(XML, TextTreeRenderer.DESCRIPTOR);
+ for (TreeRendererDescriptor descriptor : builtinDescriptors) {
+ REGISTRY.put(descriptor.id(), descriptor);
+ }
}
diff --git a/pmd-core/src/main/resources/rulesets/releases/6230.xml b/pmd-core/src/main/resources/rulesets/releases/6230.xml
new file mode 100644
index 0000000000..e2d8620ff2
--- /dev/null
+++ b/pmd-core/src/main/resources/rulesets/releases/6230.xml
@@ -0,0 +1,14 @@
+
+
+
+
+This ruleset contains links to rules that are new in PMD v6.23.0
+
+
+
+
+
+
diff --git a/pmd-cs/src/main/java/net/sourceforge/pmd/cpd/CsTokenizer.java b/pmd-cs/src/main/java/net/sourceforge/pmd/cpd/CsTokenizer.java
index d54c3794dd..f56ee3d1a4 100644
--- a/pmd-cs/src/main/java/net/sourceforge/pmd/cpd/CsTokenizer.java
+++ b/pmd-cs/src/main/java/net/sourceforge/pmd/cpd/CsTokenizer.java
@@ -78,7 +78,7 @@ public class CsTokenizer extends AntlrTokenizer {
private void skipUsingDirectives(final AntlrToken currentToken, final Iterable remainingTokens) {
if (ignoreUsings) {
- final int type = currentToken.getType();
+ final int type = currentToken.getKind();
if (type == CSharpLexer.USING && isUsingDirective(remainingTokens)) {
discardingUsings = true;
} else if (type == CSharpLexer.SEMICOLON && discardingUsings) {
@@ -91,7 +91,7 @@ public class CsTokenizer extends AntlrTokenizer {
private boolean isUsingDirective(final Iterable remainingTokens) {
UsingState usingState = UsingState.KEYWORD;
for (final AntlrToken token : remainingTokens) {
- final int type = token.getType();
+ final int type = token.getKind();
if (usingState == UsingState.KEYWORD) {
// The previous token was a using keyword.
switch (type) {
@@ -147,7 +147,7 @@ public class CsTokenizer extends AntlrTokenizer {
}
private void skipNewLines(final AntlrToken currentToken) {
- discardingNL = currentToken.getType() == CSharpLexer.NL;
+ discardingNL = currentToken.getKind() == CSharpLexer.NL;
}
@Override
diff --git a/pmd-dart/src/main/java/net/sourceforge/pmd/cpd/DartTokenizer.java b/pmd-dart/src/main/java/net/sourceforge/pmd/cpd/DartTokenizer.java
index d8a53823d6..6050b5bd30 100644
--- a/pmd-dart/src/main/java/net/sourceforge/pmd/cpd/DartTokenizer.java
+++ b/pmd-dart/src/main/java/net/sourceforge/pmd/cpd/DartTokenizer.java
@@ -52,7 +52,7 @@ public class DartTokenizer extends AntlrTokenizer {
}
private void skipLibraryAndImport(final AntlrToken currentToken) {
- final int type = currentToken.getType();
+ final int type = currentToken.getKind();
if (type == Dart2Lexer.LIBRARY || type == Dart2Lexer.IMPORT) {
discardingLibraryAndImport = true;
} else if (discardingLibraryAndImport && (type == Dart2Lexer.SEMICOLON || type == Dart2Lexer.NEWLINE)) {
@@ -61,11 +61,11 @@ public class DartTokenizer extends AntlrTokenizer {
}
private void skipNewLines(final AntlrToken currentToken) {
- discardingNL = currentToken.getType() == Dart2Lexer.NEWLINE;
+ discardingNL = currentToken.getKind() == Dart2Lexer.NEWLINE;
}
private void skipSemicolons(final AntlrToken currentToken) {
- discardingSemicolon = currentToken.getType() == Dart2Lexer.SEMICOLON;
+ discardingSemicolon = currentToken.getKind() == Dart2Lexer.SEMICOLON;
}
@Override
diff --git a/pmd-doc/src/main/java/net/sourceforge/pmd/docs/RuleDocGenerator.java b/pmd-doc/src/main/java/net/sourceforge/pmd/docs/RuleDocGenerator.java
index 4d1ffa2dee..e0e6c2511f 100644
--- a/pmd-doc/src/main/java/net/sourceforge/pmd/docs/RuleDocGenerator.java
+++ b/pmd-doc/src/main/java/net/sourceforge/pmd/docs/RuleDocGenerator.java
@@ -405,10 +405,11 @@ public class RuleDocGenerator {
lines.addAll(EscapeUtils.escapeLines(toLines(stripIndentation(rule.getDescription()))));
lines.add("");
- if (rule instanceof XPathRule || rule instanceof RuleReference && ((RuleReference) rule).getRule() instanceof XPathRule) {
+ XPathRule xpathRule = asXPathRule(rule);
+ if (xpathRule != null) {
lines.add("**This rule is defined by the following XPath expression:**");
lines.add("``` xpath");
- lines.addAll(toLines(StringUtils.stripToEmpty(rule.getProperty(XPathRule.XPATH_DESCRIPTOR))));
+ lines.addAll(toLines(StringUtils.stripToEmpty(xpathRule.getXPathExpression())));
lines.add("```");
} else {
lines.add("**This rule is defined by the following Java class:** "
@@ -502,6 +503,15 @@ public class RuleDocGenerator {
}
}
+ private XPathRule asXPathRule(Rule rule) {
+ if (rule instanceof XPathRule) {
+ return (XPathRule) rule;
+ } else if (rule instanceof RuleReference && ((RuleReference) rule).getRule() instanceof XPathRule) {
+ return (XPathRule) ((RuleReference) rule).getRule();
+ }
+ return null;
+ }
+
private static boolean isDeprecated(PropertyDescriptor> propertyDescriptor) {
return propertyDescriptor.description() != null
&& propertyDescriptor.description().toLowerCase(Locale.ROOT).startsWith(DEPRECATED_RULE_PROPERTY_MARKER);
diff --git a/pmd-java/etc/grammar/Java.jjt b/pmd-java/etc/grammar/Java.jjt
index 2bbda165a6..d685315ce5 100644
--- a/pmd-java/etc/grammar/Java.jjt
+++ b/pmd-java/etc/grammar/Java.jjt
@@ -502,6 +502,53 @@ public class JavaParser {
return getToken(1).kind == IDENTIFIER && getToken(1).image.equals(keyword);
}
+ /**
+ * True if we're in a switch block, one precondition for parsing a yield
+ * statement.
+ */
+ private boolean inSwitchExprBlock = false;
+
+ private boolean isYieldStart() {
+ return inSwitchExprBlock && isJava13PreviewOr14()
+ && isKeyword("yield")
+ && mayStartExprAfterYield(2);
+ }
+
+ private boolean mayStartExprAfterYield(final int offset) {
+ // based off of https://hg.openjdk.java.net/jdk/jdk/file/bc3da0226ffa/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java#l2580
+ // please don't sue me
+ Token token = getToken(offset);
+ if (token == null) return false; // eof
+ switch (token.kind) {
+ case PLUS: case MINUS: case STRING_LITERAL: case CHARACTER_LITERAL:
+ case INTEGER_LITERAL: case FLOATING_POINT_LITERAL: case HEX_FLOATING_POINT_LITERAL:
+ case NULL: case IDENTIFIER: case TRUE: case FALSE:
+ case NEW: case SWITCH: case THIS: case SUPER:
+ return true;
+ case INCR: case DECR:
+ return getToken(offset + 1).kind != SEMICOLON; // eg yield++;
+ case LPAREN:
+ int lookahead = offset + 1;
+ int balance = 1;
+ Token t;
+ while ((t = getToken(lookahead)) != null && balance > 0) {
+ switch (t.kind) {
+ case LPAREN: balance++; break;
+ case RPAREN: balance--; break;
+ case COMMA: if (balance == 1) return false; // a method call, eg yield(1, 2);
+ }
+ lookahead++;
+ }
+ // lambda: yield () -> {};
+ // method call: yield ();
+ return t != null
+ && (lookahead != offset + 2 // ie ()
+ || t.kind == LAMBDA);
+ default:
+ return false;
+ }
+ }
+
private boolean shouldStartStatementInSwitch() {
switch (getToken(1).kind) {
case _DEFAULT:
@@ -1620,10 +1667,13 @@ void CastExpression() :
}
void SwitchExpression() :
-{}
+{boolean prevInSwitchBlock = inSwitchExprBlock;}
{
{checkForSwitchExpression();}
- "switch" "(" Expression() ")" SwitchBlock()
+ "switch" "(" Expression() ")"
+ {inSwitchExprBlock = true;}
+ SwitchBlock()
+ {inSwitchExprBlock = prevInSwitchBlock;}
}
void PrimaryExpression() :
@@ -1791,6 +1841,7 @@ void Statement() :
{}
{
LOOKAHEAD( { isNextTokenAnAssert() } ) AssertStatement()
+| LOOKAHEAD( { isYieldStart() } ) YieldStatement()
| LOOKAHEAD(2) LabeledStatement()
| Block()
| EmptyStatement()
@@ -1826,7 +1877,7 @@ void BlockStatement():
{}
{
LOOKAHEAD( { isNextTokenAnAssert() } ) AssertStatement()
-| LOOKAHEAD({ jdkVersion >= 13 && isKeyword("yield") }) YieldStatement()
+| LOOKAHEAD( { isYieldStart() } ) YieldStatement()
|
LOOKAHEAD(( "final" | Annotation() )* Type() )
LocalVariableDeclaration() ";"
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTRecordDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTRecordDeclaration.java
index 553190dd74..925425f8f5 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTRecordDeclaration.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTRecordDeclaration.java
@@ -57,7 +57,18 @@ public final class ASTRecordDeclaration extends AbstractAnyTypeDeclaration {
return isNested();
}
+ /**
+ * @deprecated Renamed to {@link #getRecordComponents()}
+ */
+ @Deprecated
public ASTRecordComponentList getComponentList() {
+ return getRecordComponents();
+ }
+
+ /** Returns the record component list. */
+ // @NonNull
+ @Override
+ public ASTRecordComponentList getRecordComponents() {
return getFirstChildOfType(ASTRecordComponentList.class);
}
}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractAnyTypeDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractAnyTypeDeclaration.java
index fbdacd93bc..4bd5fc0607 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractAnyTypeDeclaration.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractAnyTypeDeclaration.java
@@ -53,6 +53,16 @@ public abstract class AbstractAnyTypeDeclaration extends AbstractJavaAccessTypeN
return getImage();
}
+
+ /**
+ * Returns the record component list, or null if this is not a record
+ * declaration.
+ */
+ // @Nullable // TODO pull up to ASTAnyTypeDecl on 7.0.x
+ public ASTRecordComponentList getRecordComponents() {
+ return getFirstChildOfType(ASTRecordComponentList.class);
+ }
+
/**
* Returns true if the enclosing type of this type declaration
* is any of the given kinds. If this declaration is a top-level
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/ClassNamingConventionsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/ClassNamingConventionsRule.java
index b90c1804aa..efd4a782c5 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/ClassNamingConventionsRule.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/ClassNamingConventionsRule.java
@@ -30,7 +30,7 @@ public class ClassNamingConventionsRule extends AbstractNamingConventionRule interfaceRegex = defaultProp("interface").build();
private final PropertyDescriptor enumerationRegex = defaultProp("enum").build();
private final PropertyDescriptor annotationRegex = defaultProp("annotation").build();
- private final PropertyDescriptor utilityClassRegex = defaultProp("utility class").defaultValue("[A-Z][a-zA-Z0-9]+(Utils?|Helper)").build();
+ private final PropertyDescriptor utilityClassRegex = defaultProp("utility class").defaultValue("[A-Z][a-zA-Z0-9]+(Utils?|Helper|Constants)").build();
public ClassNamingConventionsRule() {
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/CloseResourceRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/CloseResourceRule.java
index bca558c9d0..ac3bb0bd53 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/CloseResourceRule.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/CloseResourceRule.java
@@ -93,7 +93,8 @@ public class CloseResourceRule extends AbstractJavaRule {
stringListProperty("allowedResourceTypes")
.desc("Exact class names that do not need to be closed")
.defaultValues("java.io.ByteArrayOutputStream", "java.io.ByteArrayInputStream", "java.io.StringWriter",
- "java.io.CharArrayWriter", "java.util.stream.Stream")
+ "java.io.CharArrayWriter", "java.util.stream.Stream", "java.util.stream.IntStream", "java.util.stream.LongStream",
+ "java.util.stream.DoubleStream")
.build();
diff --git a/pmd-java/src/main/resources/category/java/bestpractices.xml b/pmd-java/src/main/resources/category/java/bestpractices.xml
index 21fa614c9e..aa0c9b6115 100644
--- a/pmd-java/src/main/resources/category/java/bestpractices.xml
+++ b/pmd-java/src/main/resources/category/java/bestpractices.xml
@@ -1463,7 +1463,7 @@ public class FooTest extends TestCase {
void testCode() {
Object a, b;
assertTrue(a.equals(b)); // bad usage
- assertEquals(?a should equals b?, a, b); // good usage
+ assertEquals("a should equals b", a, b); // good usage
}
}
]]>
diff --git a/pmd-java/src/main/resources/category/java/codestyle.xml b/pmd-java/src/main/resources/category/java/codestyle.xml
index 60141271a6..57d59a88bd 100644
--- a/pmd-java/src/main/resources/category/java/codestyle.xml
+++ b/pmd-java/src/main/resources/category/java/codestyle.xml
@@ -1995,19 +1995,21 @@ which makes the code also more readable.
3
-
+
diff --git a/pmd-java/src/main/resources/category/java/multithreading.xml b/pmd-java/src/main/resources/category/java/multithreading.xml
index 268729b33c..b70ea643db 100644
--- a/pmd-java/src/main/resources/category/java/multithreading.xml
+++ b/pmd-java/src/main/resources/category/java/multithreading.xml
@@ -151,10 +151,28 @@ public class UsingThread extends Thread {
}
// Neither this,
-public class OtherThread implements Runnable {
- // Nor this ...
- public void methode() {
- Runnable thread = new Thread(); thread.run();
+public class UsingExecutorService {
+
+ public void methodX() {
+ ExecutorService executorService = Executors.newFixedThreadPool(5);
+ }
+}
+
+// Nor this,
+public class Example implements ExecutorService {
+
+}
+
+// Nor this,
+public class Example extends AbstractExecutorService {
+
+}
+
+// Nor this
+public class UsingExecutors {
+
+ public void methodX() {
+ Executors.newSingleThreadExecutor().submit(() -> System.out.println("Hello!"));
}
}
]]>
diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java14Test.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java14Test.java
index 334335db65..da41bac41e 100644
--- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java14Test.java
+++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java14Test.java
@@ -5,6 +5,9 @@
package net.sourceforge.pmd.lang.java.ast;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.assertThat;
+
import java.util.List;
import org.junit.Assert;
@@ -67,7 +70,7 @@ public class Java14Test {
Assert.assertEquals(Integer.TYPE, switchExpressions.get(0).getType());
Assert.assertEquals(4, switchExpressions.get(0).findChildrenOfType(ASTSwitchLabeledExpression.class).size());
Assert.assertEquals(Integer.TYPE, switchExpressions.get(0).getFirstChildOfType(ASTSwitchLabeledExpression.class)
- .getFirstChildOfType(ASTExpression.class).getType());
+ .getFirstChildOfType(ASTExpression.class).getType());
Assert.assertTrue(switchExpressions.get(1).getChild(3) instanceof ASTSwitchLabeledBlock);
@@ -79,6 +82,60 @@ public class Java14Test {
Assert.assertEquals(String.class, switchExpressions.get(3).getType());
}
+ @Test
+ public void checkYieldConditionalBehaviour() {
+ checkYieldStatements(java13p);
+ }
+
+ @Test
+ public void checkYieldConditionalBehaviourJ14() {
+ checkYieldStatements(java14);
+ }
+
+ private void checkYieldStatements(JavaParsingHelper parser) {
+ ASTCompilationUnit compilationUnit = parser.parseResource("YieldStatements.java");
+ List stmts = compilationUnit.findDescendantsOfType(ASTBlockStatement.class);
+ // fetch the interesting node, on the java-grammar branch this is not needed
+ for (int i = 0; i < stmts.size(); i++) {
+ JavaNode child = stmts.get(i).getChild(0);
+
+ if (child instanceof ASTStatement) {
+ stmts.set(i, child.getChild(0));
+ } else {
+ stmts.set(i, child);
+ }
+ }
+
+ Assert.assertEquals(18, stmts.size());
+
+ int i = 0;
+ assertThat(stmts.get(i++), instanceOf(ASTLocalVariableDeclaration.class));
+ assertThat(stmts.get(i++), instanceOf(ASTStatementExpression.class));
+ assertThat(stmts.get(i++), instanceOf(ASTStatementExpression.class));
+ assertThat(stmts.get(i++), instanceOf(ASTStatementExpression.class));
+
+ assertThat(stmts.get(i++), instanceOf(ASTStatementExpression.class));
+
+ assertThat(stmts.get(i++), instanceOf(ASTStatementExpression.class));
+ assertThat(stmts.get(i++), instanceOf(ASTStatementExpression.class));
+ assertThat(stmts.get(i++), instanceOf(ASTYieldStatement.class));
+ assertThat(stmts.get(i++), instanceOf(ASTYieldStatement.class));
+ assertThat(stmts.get(i++), instanceOf(ASTYieldStatement.class));
+ assertThat(stmts.get(i++), instanceOf(ASTStatementExpression.class));
+ assertThat(stmts.get(i++), instanceOf(ASTStatementExpression.class));
+
+ assertThat(stmts.get(i++), instanceOf(ASTIfStatement.class));
+
+ assertThat(stmts.get(i++), instanceOf(ASTStatementExpression.class));
+ assertThat(stmts.get(i++), instanceOf(ASTYieldStatement.class));
+
+ assertThat(stmts.get(i++), instanceOf(ASTYieldStatement.class));
+ assertThat(stmts.get(i++), instanceOf(ASTStatementExpression.class));
+ assertThat(stmts.get(i++), instanceOf(ASTYieldStatement.class));
+
+ Assert.assertEquals(i, stmts.size());
+ }
+
@Test
public void multipleCaseLabels() {
multipleCaseLabels(java13p);
diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/xpath/XPathMetricFunctionTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/xpath/XPathMetricFunctionTest.java
index 3e93c5049e..2dc75f74cc 100644
--- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/xpath/XPathMetricFunctionTest.java
+++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/xpath/XPathMetricFunctionTest.java
@@ -26,6 +26,7 @@ import net.sourceforge.pmd.lang.LanguageRegistry;
import net.sourceforge.pmd.lang.java.JavaLanguageModule;
import net.sourceforge.pmd.lang.java.xpath.MetricFunction;
import net.sourceforge.pmd.lang.rule.XPathRule;
+import net.sourceforge.pmd.lang.rule.xpath.XPathVersion;
/**
* @author Clément Fournier
@@ -42,8 +43,7 @@ public class XPathMetricFunctionTest {
private Rule makeXpathRuleFromXPath(String xpath) {
- XPathRule rule = new XPathRule();
- rule.setXPath(xpath);
+ XPathRule rule = new XPathRule(XPathVersion.XPATH_1_0, xpath);
rule.setMessage(VIOLATION_MESSAGE);
rule.setLanguage(LanguageRegistry.getLanguage(JavaLanguageModule.NAME));
return rule;
diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/XPathRuleTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/XPathRuleTest.java
index 2a1ca7afa4..a82c36426b 100644
--- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/XPathRuleTest.java
+++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/XPathRuleTest.java
@@ -12,7 +12,6 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
-import org.junit.Before;
import org.junit.Test;
import net.sourceforge.pmd.PMD;
@@ -35,9 +34,9 @@ import net.sourceforge.pmd.lang.rule.XPathRule;
import net.sourceforge.pmd.lang.rule.xpath.JaxenXPathRuleQuery;
import net.sourceforge.pmd.lang.rule.xpath.SaxonXPathRuleQuery;
import net.sourceforge.pmd.lang.rule.xpath.XPathRuleQuery;
+import net.sourceforge.pmd.lang.rule.xpath.XPathVersion;
import net.sourceforge.pmd.properties.PropertyDescriptor;
-import net.sourceforge.pmd.properties.StringMultiProperty;
-import net.sourceforge.pmd.properties.StringProperty;
+import net.sourceforge.pmd.properties.PropertyFactory;
import net.sourceforge.pmd.testframework.RuleTst;
/**
@@ -45,18 +44,16 @@ import net.sourceforge.pmd.testframework.RuleTst;
*/
public class XPathRuleTest extends RuleTst {
- XPathRule rule;
-
- @Before
- public void setUp() {
- rule = new XPathRule();
+ private XPathRule makeXPath(String expression) {
+ XPathRule rule = new XPathRule(XPathVersion.XPATH_2_0, expression);
rule.setLanguage(LanguageRegistry.getLanguage(JavaLanguageModule.NAME));
rule.setMessage("XPath Rule Failed");
+ return rule;
}
@Test
public void testPluginname() throws Exception {
- rule.setXPath("//VariableDeclaratorId[string-length(@Image) < 3]");
+ XPathRule rule = makeXPath("//VariableDeclaratorId[string-length(@Image) < 3]");
rule.setMessage("{0}");
Report report = getReportForTestString(rule, TEST1);
RuleViolation rv = report.iterator().next();
@@ -66,15 +63,14 @@ public class XPathRuleTest extends RuleTst {
@Test
public void testXPathMultiProperty() throws Exception {
- rule.setXPath("//VariableDeclaratorId[@Image=$forbiddenNames]");
+ XPathRule rule = makeXPath("//VariableDeclaratorId[@Image=$forbiddenNames]");
rule.setMessage("Avoid vars");
- rule.setVersion(XPathRuleQuery.XPATH_2_0);
- StringMultiProperty varDescriptor
- = StringMultiProperty.named("forbiddenNames")
- .desc("Forbidden names")
- .defaultValues("forbid1", "forbid2")
- .delim('$')
- .build();
+ PropertyDescriptor> varDescriptor
+ = PropertyFactory.stringListProperty("forbiddenNames")
+ .desc("Forbidden names")
+ .defaultValues("forbid1", "forbid2")
+ .delim('$')
+ .build();
rule.definePropertyDescriptor(varDescriptor);
@@ -90,9 +86,10 @@ public class XPathRuleTest extends RuleTst {
@Test
public void testVariables() throws Exception {
- rule.setXPath("//VariableDeclaratorId[@Image=$var]");
+ XPathRule rule = makeXPath("//VariableDeclaratorId[@Image=$var]");
rule.setMessage("Avoid vars");
- StringProperty varDescriptor = new StringProperty("var", "Test var", null, 1.0f);
+ PropertyDescriptor varDescriptor =
+ PropertyFactory.stringProperty("var").desc("Test var").defaultValue("").build();
rule.definePropertyDescriptor(varDescriptor);
rule.setProperty(varDescriptor, "fiddle");
Report report = getReportForTestString(rule, TEST2);
@@ -102,8 +99,7 @@ public class XPathRuleTest extends RuleTst {
@Test
public void testFnPrefixOnSaxon() throws Exception {
- rule.setXPath("//VariableDeclaratorId[fn:matches(@Image, 'fiddle')]");
- rule.setVersion(XPathRuleQuery.XPATH_2_0);
+ XPathRule rule = makeXPath("//VariableDeclaratorId[fn:matches(@Image, 'fiddle')]");
Report report = getReportForTestString(rule, TEST2);
RuleViolation rv = report.iterator().next();
assertEquals(3, rv.getBeginLine());
@@ -111,8 +107,7 @@ public class XPathRuleTest extends RuleTst {
@Test
public void testNoFnPrefixOnSaxon() throws Exception {
- rule.setXPath("//VariableDeclaratorId[matches(@Image, 'fiddle')]");
- rule.setVersion(XPathRuleQuery.XPATH_2_0);
+ XPathRule rule = makeXPath("//VariableDeclaratorId[matches(@Image, 'fiddle')]");
Report report = getReportForTestString(rule, TEST2);
RuleViolation rv = report.iterator().next();
assertEquals(3, rv.getBeginLine());
diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java14/YieldStatements.java b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java14/YieldStatements.java
new file mode 100644
index 0000000000..f56e25646f
--- /dev/null
+++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java14/YieldStatements.java
@@ -0,0 +1,35 @@
+/**
+ * @see JEP 361: Switch Expressions (Standard)
+ */
+public class YieldStatements {
+ {
+ int yield = 0;
+ yield = 2; // should be an assignment
+ yield (2); // should be a method call
+ yield(a,b); // should be a method call
+
+
+ yield = switch (e) { // must be a switch expr
+ case 1 -> {
+ yield(a,b); // should be a method call
+ yield = 2; // should be an assignment
+ yield (2); // should be a yield statement
+ yield++bar; // should be a yield statement (++bar is an expression)
+ yield--bar; // should be a yield statement (--bar is an expression)
+ yield++; // should be an increment (not an error)
+ yield--; // should be a decrement (not an error)
+
+ if (true) yield(2);
+ else yield 4;
+
+ yield = switch (foo) { // putting a switch in the middles checks the reset behavior
+ case 4 -> {yield(5);} // should be a yield statement
+ };
+
+ yield () -> {}; // should be a yield statement
+ yield (); // should be a method call
+ yield (2); // should be a yield statement
+ }
+ };
+ }
+}
diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/ClassNamingConventions.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/ClassNamingConventions.xml
index 0248ab34b9..53e4bd1f7c 100644
--- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/ClassNamingConventions.xml
+++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/ClassNamingConventions.xml
@@ -53,7 +53,7 @@
Utility class convention
1
- The utility class name 'Foo' doesn't match '[A-Z][a-zA-Z0-9]+(Utils?|Helper)'
+ The utility class name 'Foo' doesn't match '[A-Z][a-zA-Z0-9]+(Utils?|Helper|Constants)'
Class with only static members except constructors should be a utility class
1
- The utility class name 'Foo' doesn't match '[A-Z][a-zA-Z0-9]+(Utils?|Helper)'
+ The utility class name 'Foo' doesn't match '[A-Z][a-zA-Z0-9]+(Utils?|Helper|Constants)'
+
+ Utility class can have name constants
+ 0
+
+
+
diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UseDiamondOperator.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UseDiamondOperator.xml
index 7d61c4a29a..b65723cb19 100644
--- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UseDiamondOperator.xml
+++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UseDiamondOperator.xml
@@ -1,13 +1,12 @@
-
-
- Use Diamond
- 2
- 6,8
-
+
+ Use Diamond
+ 2
+ 6,11
+ strings = new ArrayList();
List strings2 = new ArrayList<>();
+ List> strings3 = new ArrayList<>();
+ // this is a known false negative, see at the bottom
+ List> strings4 = new ArrayList>>();
this.field = new ArrayList();
}
}
- ]]>
-
-
- False positive cases: anonymous classes, methods calls
- 0
-
+
+
+
+ False positive cases: anonymous classes, methods calls
+ 0
+ > typeReference;
public void foo() {
Collections.sort(files, new Comparator() {
@Override
@@ -49,8 +51,6 @@ public class Foo {
}
};
Iterator EMPTY_ITERATOR = new ArrayList().iterator();
- Class> type = null;
- typeReference = new WeakReference>(type);
((ListNode) rev).reverseCache = new SoftReference>(this);
}
public Map, Object> getOverriddenPropertiesByPropertyDescriptor() {
@@ -58,12 +58,13 @@ public class Foo {
}
}
]]>
-
-
- #1624[java] UseDiamondOperator doesn't work with var
- 1
- 6
-
+
+
+ #1624[java] UseDiamondOperator doesn't work with var
+ 1
+ 6
+ (); // flagged by rule
}
}
- ]]>
-
-
- Multiple initializations in a single declaration
- 1
- 6
-
+
+
+
+ Multiple initializations in a single declaration
+ 1
+ 6
+ (); // ok
}
}
- ]]>
-
-
+ ]]>
+
+
+
+ #1723 FP with var inside lambda (declaration)
+ 0
+ {
+ var foo = new ArrayList(5); // ok
+ System.err.println(foo);
+ };
+ }
+}
+ ]]>
+
+
+
+ #1723 FP with var inside lambda (assignment)
+ 0
+ {
+ var foo = new ArrayList(5); // ok
+ System.err.println(foo);
+ };
+ }
+}
+ ]]>
+
+
+
+ FP with array creation
+ 0
+ c = new Class>[0];
+ }
+}
+ ]]>
+
+
+
+
+
+
+ (J7) Version sensitive tests
+ 1
+ 6
+ > typeReference;
+ public void foo() {
+ // this should be positive in Java 8, negative in Java 7
+ // this is because in java 7, new WeakReference<>(String.class) types as WeakReference>
+ // which is incompatible with WeakReference>, whereas Java 8's type inference is better.
+ typeReference = new WeakReference>(String.class);
+ Class> type = null;
+ typeReference = new WeakReference>(type); // this should be positive on all versions
+ }
+}
+ ]]>
+ java 1.7
+
+
+
+ (J8) Version sensitive tests
+ 2
+ 4,6
+ > typeReference;
+ public void foo() {
+ typeReference = new WeakReference>(String.class); // pos
+ Class> type = null;
+ typeReference = new WeakReference>(type); // pos
+ }
+}
+ ]]>
+ java 1.8
+
+
+
\ No newline at end of file
diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/CloseResource.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/CloseResource.xml
index dbbc83e9d9..fa7e308b39 100644
--- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/CloseResource.xml
+++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/CloseResource.xml
@@ -1201,6 +1201,35 @@ public class CloseResourceStream {
]]>
+
+ #2402 [java] CloseResource possible false positive with primitive Streams
+ 0
+ i < 5);
+ iStream.anyMatch(i -> i < 5);
+ }
+
+ public void reproduceIntStream() {
+ LongStream lStream = LongStream.of(1).filter(i -> i < 5);
+ lStream.anyMatch(i -> i < 5);
+ }
+
+ public void reproduceIntStream() {
+ DoubleStream dStream = DoubleStream.of(1).filter(i -> i < 5);
+ dStream.anyMatch(i -> i < 5);
+ }
+}
+ ]]>
+
+
+
+
+
#1076 [java] CloseResource false positive on non-SQL classes called Statement
0
diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/Ecmascript3Handler.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/Ecmascript3Handler.java
index c43f3b5255..d27138b7a3 100644
--- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/Ecmascript3Handler.java
+++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/Ecmascript3Handler.java
@@ -10,9 +10,7 @@ import net.sourceforge.pmd.lang.AbstractLanguageVersionHandler;
import net.sourceforge.pmd.lang.Parser;
import net.sourceforge.pmd.lang.ParserOptions;
import net.sourceforge.pmd.lang.VisitorStarter;
-import net.sourceforge.pmd.lang.XPathHandler;
import net.sourceforge.pmd.lang.ast.Node;
-import net.sourceforge.pmd.lang.ast.xpath.DefaultASTXPathHandler;
import net.sourceforge.pmd.lang.ecmascript.ast.DumpFacade;
import net.sourceforge.pmd.lang.ecmascript.ast.EcmascriptNode;
import net.sourceforge.pmd.lang.ecmascript.rule.EcmascriptRuleViolationFactory;
@@ -23,11 +21,6 @@ import net.sourceforge.pmd.lang.rule.RuleViolationFactory;
*/
public class Ecmascript3Handler extends AbstractLanguageVersionHandler {
- @Override
- public XPathHandler getXPathHandler() {
- return new DefaultASTXPathHandler();
- }
-
@Override
public RuleViolationFactory getRuleViolationFactory() {
return EcmascriptRuleViolationFactory.INSTANCE;
diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspHandler.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspHandler.java
index 8c1dd425e5..f1184c8f23 100644
--- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspHandler.java
+++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspHandler.java
@@ -10,9 +10,7 @@ import net.sourceforge.pmd.lang.AbstractLanguageVersionHandler;
import net.sourceforge.pmd.lang.Parser;
import net.sourceforge.pmd.lang.ParserOptions;
import net.sourceforge.pmd.lang.VisitorStarter;
-import net.sourceforge.pmd.lang.XPathHandler;
import net.sourceforge.pmd.lang.ast.Node;
-import net.sourceforge.pmd.lang.ast.xpath.DefaultASTXPathHandler;
import net.sourceforge.pmd.lang.jsp.ast.DumpFacade;
import net.sourceforge.pmd.lang.jsp.ast.JspNode;
import net.sourceforge.pmd.lang.jsp.rule.JspRuleViolationFactory;
@@ -25,11 +23,6 @@ import net.sourceforge.pmd.lang.rule.RuleViolationFactory;
*/
public class JspHandler extends AbstractLanguageVersionHandler {
- @Override
- public XPathHandler getXPathHandler() {
- return new DefaultASTXPathHandler();
- }
-
@Override
public RuleViolationFactory getRuleViolationFactory() {
return JspRuleViolationFactory.INSTANCE;
diff --git a/pmd-kotlin/src/main/java/net/sourceforge/pmd/cpd/KotlinTokenizer.java b/pmd-kotlin/src/main/java/net/sourceforge/pmd/cpd/KotlinTokenizer.java
index dcfe0da1c0..35b94d48f0 100644
--- a/pmd-kotlin/src/main/java/net/sourceforge/pmd/cpd/KotlinTokenizer.java
+++ b/pmd-kotlin/src/main/java/net/sourceforge/pmd/cpd/KotlinTokenizer.java
@@ -50,7 +50,7 @@ public class KotlinTokenizer extends AntlrTokenizer {
}
private void skipPackageAndImport(final AntlrToken currentToken) {
- final int type = currentToken.getType();
+ final int type = currentToken.getKind();
if (type == Kotlin.PACKAGE || type == Kotlin.IMPORT) {
discardingPackageAndImport = true;
} else if (discardingPackageAndImport && (type == Kotlin.SEMICOLON || type == Kotlin.NL)) {
@@ -59,7 +59,7 @@ public class KotlinTokenizer extends AntlrTokenizer {
}
private void skipNewLines(final AntlrToken currentToken) {
- discardingNL = currentToken.getType() == Kotlin.NL;
+ discardingNL = currentToken.getKind() == Kotlin.NL;
}
@Override
diff --git a/pmd-lang-test/pom.xml b/pmd-lang-test/pom.xml
index 910eda2842..2de8d05bb9 100644
--- a/pmd-lang-test/pom.xml
+++ b/pmd-lang-test/pom.xml
@@ -132,7 +132,7 @@
com.github.oowekyala.treeutils
tree-matchers
- 2.0.1
+ 2.1.0
compile
diff --git a/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/AstMatcherDslAdapter.kt b/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/AstMatcherDslAdapter.kt
index 99ed28c236..419fafabff 100644
--- a/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/AstMatcherDslAdapter.kt
+++ b/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/AstMatcherDslAdapter.kt
@@ -4,6 +4,7 @@
package net.sourceforge.pmd.lang.ast.test
+import com.github.oowekyala.treeutils.DoublyLinkedTreeLikeAdapter
import com.github.oowekyala.treeutils.TreeLikeAdapter
import com.github.oowekyala.treeutils.matchers.MatchingConfig
import com.github.oowekyala.treeutils.matchers.TreeNodeWrapper
@@ -12,10 +13,14 @@ import com.github.oowekyala.treeutils.printers.KotlintestBeanTreePrinter
import net.sourceforge.pmd.lang.ast.Node
/** An adapter for [baseShouldMatchSubtree]. */
-object NodeTreeLikeAdapter : TreeLikeAdapter {
+object NodeTreeLikeAdapter : DoublyLinkedTreeLikeAdapter {
override fun getChildren(node: Node): List = node.findChildrenOfType(Node::class.java)
override fun nodeName(type: Class): String = type.simpleName.removePrefix("AST")
+
+ override fun getParent(node: Node): Node? = node.parent
+
+ override fun getChild(node: Node, index: Int): Node? = node.safeGetChild(index)
}
/** A subtree matcher written in the DSL documented on [TreeNodeWrapper]. */
diff --git a/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/BaseParsingHelper.kt b/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/BaseParsingHelper.kt
index 4a486a397c..c209327c9f 100644
--- a/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/BaseParsingHelper.kt
+++ b/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/BaseParsingHelper.kt
@@ -35,12 +35,18 @@ abstract class BaseParsingHelper, T : RootNode
@JvmStatic
val defaultNoProcess = Params(false, null, null, "")
+
@JvmStatic
val defaultProcess = Params(true, null, null, "")
}
}
+ internal val resourceLoader: Class<*>
+ get() = params.resourceLoader ?: javaClass
+
+ internal val resourcePrefix: String get() = params.resourcePrefix
+
/**
* Returns the language version with the given version string.
* If null, this defaults to the default language version for
@@ -138,10 +144,10 @@ abstract class BaseParsingHelper, T : RootNode
parse(readClassSource(clazz), version)
protected fun readResource(resourceName: String): String {
- val rloader = params.resourceLoader ?: javaClass
- val input = rloader.getResourceAsStream(params.resourcePrefix + resourceName)
- ?: throw IllegalArgumentException("Unable to find resource file ${params.resourcePrefix + resourceName} from $rloader")
+ val input = resourceLoader.getResourceAsStream(params.resourcePrefix + resourceName)
+ ?: throw IllegalArgumentException("Unable to find resource file ${params.resourcePrefix + resourceName} from $resourceLoader")
+
return consume(input)
}
diff --git a/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/BaseTreeDumpTest.kt b/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/BaseTreeDumpTest.kt
new file mode 100644
index 0000000000..4e52a66651
--- /dev/null
+++ b/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/BaseTreeDumpTest.kt
@@ -0,0 +1,74 @@
+/*
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.ast.test
+
+import net.sourceforge.pmd.util.treeexport.TreeRenderer
+import java.nio.file.Path
+import java.nio.file.Paths
+import kotlin.test.assertEquals
+
+
+/**
+ * Compare a dump of a file against a saved baseline.
+ *
+ * @param printer The node printer used to dump the trees
+ * @param extension Extension that the unparsed source file is supposed to have
+ */
+abstract class BaseTreeDumpTest(
+ val printer: TreeRenderer,
+ val extension: String
+) {
+
+ abstract val parser: BaseParsingHelper<*, *>
+
+ /**
+ * Executes the test. The test files are looked up using the [parser].
+ * The reference test file must be named [fileBaseName] + [ExpectedExt].
+ * The source file to parse must be named [fileBaseName] + [extension].
+ */
+ fun doTest(fileBaseName: String) {
+ val expectedFile = findTestFile(parser.resourceLoader, "${parser.resourcePrefix}/$fileBaseName$ExpectedExt").toFile()
+ val sourceFile = findTestFile(parser.resourceLoader, "${parser.resourcePrefix}/$fileBaseName$extension").toFile()
+
+ assert(sourceFile.isFile) {
+ "Source file $sourceFile is missing"
+ }
+
+ val parsed = parser.parse(sourceFile.readText()) // UTF-8
+ val actual = StringBuilder().also { printer.renderSubtree(parsed, it) }.toString()
+
+ if (!expectedFile.exists()) {
+ expectedFile.writeText(actual)
+ throw AssertionError("Reference file doesn't exist, created it at $expectedFile")
+ }
+
+ val expected = expectedFile.readText()
+
+ assertEquals(expected.normalize(), actual.normalize(), "Tree dump comparison failed, see the reference: $expectedFile")
+ }
+
+ // Outputting a path makes for better error messages
+ private val srcTestResources = let {
+ // this is set from maven surefire
+ System.getProperty("mvn.project.src.test.resources")
+ ?.let { Paths.get(it).toAbsolutePath() }
+ // that's for when the tests are run inside the IDE
+ ?: Paths.get(javaClass.protectionDomain.codeSource.location.file)
+ // go up from target/test-classes into the project root
+ .resolve("../../src/test/resources").normalize()
+ }
+
+ private fun findTestFile(contextClass: Class<*>, resourcePath: String): Path {
+ val path = contextClass.`package`.name.replace('.', '/')
+ return srcTestResources.resolve("$path/$resourcePath")
+ }
+
+ companion object {
+ const val ExpectedExt = ".txt"
+
+ fun String.normalize() = replace(Regex("\\R"), "\n")
+ }
+
+}
diff --git a/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/NodePrinters.kt b/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/NodePrinters.kt
new file mode 100644
index 0000000000..7e476c62c3
--- /dev/null
+++ b/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/NodePrinters.kt
@@ -0,0 +1,77 @@
+/*
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.ast.test
+
+import net.sourceforge.pmd.lang.ast.Node
+import net.sourceforge.pmd.lang.ast.xpath.Attribute
+import net.sourceforge.pmd.util.treeexport.TextTreeRenderer
+import org.apache.commons.lang3.StringEscapeUtils
+
+/**
+ * Prints just the structure, like so:
+ *
+ * └── LocalVariableDeclaration
+ * ├── Type
+ * │ └── PrimitiveType
+ * └── VariableDeclarator
+ * ├── VariableDeclaratorId
+ * └── VariableInitializer
+ * └── 1 child not shown
+ *
+ */
+val SimpleNodePrinter = TextTreeRenderer(true, -1)
+
+
+open class RelevantAttributePrinter : BaseNodeAttributePrinter() {
+
+ private val Ignored = setOf("BeginLine", "EndLine", "BeginColumn", "EndColumn", "FindBoundary", "SingleLine")
+
+ override fun ignoreAttribute(node: Node, attribute: Attribute): Boolean =
+ Ignored.contains(attribute.name) || attribute.name == "Image" && attribute.value == null
+
+}
+
+/**
+ * Base attribute printer, subclass to filter attributes.
+ */
+open class BaseNodeAttributePrinter : TextTreeRenderer(true, -1) {
+
+ protected open fun ignoreAttribute(node: Node, attribute: Attribute): Boolean = true
+
+ override fun appendNodeInfoLn(out: Appendable, node: Node) {
+ out.append(node.xPathNodeName)
+
+ node.xPathAttributesIterator
+ .asSequence()
+ // sort to get deterministic results
+ .sortedBy { it.name }
+ .filterNot { ignoreAttribute(node, it) }
+ .joinTo(buffer = out, prefix = "[", postfix = "]") {
+ "@${it.name} = ${valueToString(it.value)}"
+ }
+ }
+
+ protected open fun valueToString(value: Any?): String? {
+ return when (value) {
+ is String -> "\"" + StringEscapeUtils.unescapeJava(value) + "\""
+ is Char -> '\''.toString() + value.toString().replace("'".toRegex(), "\\'") + '\''.toString()
+ is Enum<*> -> value.enumDeclaringClass.simpleName + "." + value.name
+ is Class<*> -> value.canonicalName?.let { "$it.class" }
+ is Number, is Boolean, null -> value.toString()
+ else -> null
+ }
+ }
+
+ private val Enum<*>.enumDeclaringClass: Class<*>
+ get() = this.javaClass.let {
+ when {
+ it.isEnum -> it
+ else -> it.enclosingClass.takeIf { it.isEnum }
+ ?: throw IllegalStateException()
+ }
+ }
+
+}
+
diff --git a/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/ModelicaHandler.java b/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/ModelicaHandler.java
index cfb5df5bb0..e7c8dbd823 100644
--- a/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/ModelicaHandler.java
+++ b/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/ModelicaHandler.java
@@ -8,19 +8,13 @@ import net.sourceforge.pmd.lang.AbstractLanguageVersionHandler;
import net.sourceforge.pmd.lang.Parser;
import net.sourceforge.pmd.lang.ParserOptions;
import net.sourceforge.pmd.lang.VisitorStarter;
-import net.sourceforge.pmd.lang.XPathHandler;
import net.sourceforge.pmd.lang.ast.Node;
-import net.sourceforge.pmd.lang.ast.xpath.DefaultASTXPathHandler;
import net.sourceforge.pmd.lang.modelica.ast.ASTStoredDefinition;
import net.sourceforge.pmd.lang.modelica.resolver.ModelicaSymbolFacade;
import net.sourceforge.pmd.lang.modelica.rule.ModelicaRuleViolationFactory;
import net.sourceforge.pmd.lang.rule.RuleViolationFactory;
public class ModelicaHandler extends AbstractLanguageVersionHandler {
- @Override
- public XPathHandler getXPathHandler() {
- return new DefaultASTXPathHandler();
- }
@Override
public RuleViolationFactory getRuleViolationFactory() {
diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/PLSQLHandler.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/PLSQLHandler.java
index 4d354d1501..369561b835 100644
--- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/PLSQLHandler.java
+++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/PLSQLHandler.java
@@ -11,9 +11,7 @@ import net.sourceforge.pmd.lang.DataFlowHandler;
import net.sourceforge.pmd.lang.Parser;
import net.sourceforge.pmd.lang.ParserOptions;
import net.sourceforge.pmd.lang.VisitorStarter;
-import net.sourceforge.pmd.lang.XPathHandler;
import net.sourceforge.pmd.lang.ast.Node;
-import net.sourceforge.pmd.lang.ast.xpath.DefaultASTXPathHandler;
import net.sourceforge.pmd.lang.dfa.DFAGraphRule;
import net.sourceforge.pmd.lang.plsql.ast.ASTInput;
import net.sourceforge.pmd.lang.plsql.ast.DumpFacade;
@@ -83,11 +81,4 @@ public class PLSQLHandler extends AbstractLanguageVersionHandler {
};
}
- /**
- * Return minimal XPathHandler to cope with Jaxen XPath Rules.
- */
- @Override
- public XPathHandler getXPathHandler() {
- return new DefaultASTXPathHandler();
- }
}
diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/PLSQLXPathRuleTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/PLSQLXPathRuleTest.java
index 094edb08f4..7dd9c46395 100644
--- a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/PLSQLXPathRuleTest.java
+++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/PLSQLXPathRuleTest.java
@@ -4,7 +4,7 @@
package net.sourceforge.pmd.lang.plsql;
-import java.util.Arrays;
+import static java.util.Collections.singletonList;
import org.junit.Assert;
import org.junit.Test;
@@ -13,21 +13,19 @@ import net.sourceforge.pmd.RuleContext;
import net.sourceforge.pmd.lang.LanguageRegistry;
import net.sourceforge.pmd.lang.plsql.ast.ASTInput;
import net.sourceforge.pmd.lang.rule.XPathRule;
+import net.sourceforge.pmd.lang.rule.xpath.XPathVersion;
/**
* Tests to use XPath rules with PLSQL.
*/
public class PLSQLXPathRuleTest extends AbstractPLSQLParserTst {
- private ASTInput node = plsql.parse(
+ private final ASTInput node = plsql.parse(
"create or replace\n" + "package pkg_xpath_problem\n" + "AS\n" + " PROCEDURE pkg_minimal\n" + " IS\n"
+ " a_variable VARCHAR2(1);\n" + " BEGIN \n" + " --PRAGMA INLINE(output,'YES');\n"
+ " a_variable := 'Y' ;\n" + " END ;\n" + "end pkg_xpath_problem;\n" + "/\n" + "");
- private RuleContext ctx = new RuleContext();
-
public PLSQLXPathRuleTest() {
- ctx.setLanguageVersion(plsql.getDefaultVersion());
}
/**
@@ -35,10 +33,7 @@ public class PLSQLXPathRuleTest extends AbstractPLSQLParserTst {
*/
@Test
public void testXPathRule1() {
- XPathRule rule = createRule("1.0");
-
- rule.apply(Arrays.asList(node), ctx);
- Assert.assertEquals(2, ctx.getReport().treeSize());
+ testOnVersion(XPathVersion.XPATH_1_0);
}
/**
@@ -46,10 +41,7 @@ public class PLSQLXPathRuleTest extends AbstractPLSQLParserTst {
*/
@Test
public void testXPathRule1Compatibility() {
- XPathRule rule = createRule("1.0 compatibility");
-
- rule.apply(Arrays.asList(node), ctx);
- Assert.assertEquals(2, ctx.getReport().treeSize());
+ testOnVersion(XPathVersion.XPATH_1_0_COMPATIBILITY);
}
/**
@@ -57,18 +49,21 @@ public class PLSQLXPathRuleTest extends AbstractPLSQLParserTst {
*/
@Test
public void testXPathRule2() {
- XPathRule rule = createRule("2.0");
-
- rule.apply(Arrays.asList(node), ctx);
- Assert.assertEquals(2, ctx.getReport().treeSize());
+ testOnVersion(XPathVersion.XPATH_2_0);
}
- private XPathRule createRule(String version) {
- XPathRule rule = new XPathRule("//PrimaryPrefix");
+
+ private void testOnVersion(XPathVersion xpath10) {
+ XPathRule rule = new XPathRule(xpath10, "//PrimaryPrefix");
rule.setLanguage(LanguageRegistry.getLanguage(PLSQLLanguageModule.NAME));
- rule.setVersion(version);
rule.setMessage("Test Violation");
- return rule;
+
+ RuleContext ctx = new RuleContext();
+ ctx.setLanguageVersion(plsql.getDefaultVersion());
+
+ rule.apply(singletonList(node), ctx);
+ Assert.assertEquals(2, ctx.getReport().size());
}
+
}
diff --git a/pmd-scala/src/test/java/net/sourceforge/pmd/lang/scala/rule/XPathRuleTest.java b/pmd-scala/src/test/java/net/sourceforge/pmd/lang/scala/rule/XPathRuleTest.java
index aefa9ee487..d8bca517d9 100644
--- a/pmd-scala/src/test/java/net/sourceforge/pmd/lang/scala/rule/XPathRuleTest.java
+++ b/pmd-scala/src/test/java/net/sourceforge/pmd/lang/scala/rule/XPathRuleTest.java
@@ -6,14 +6,13 @@ package net.sourceforge.pmd.lang.scala.rule;
import static org.junit.Assert.assertEquals;
-import org.junit.Before;
import org.junit.Test;
import net.sourceforge.pmd.Report;
import net.sourceforge.pmd.RuleViolation;
import net.sourceforge.pmd.lang.LanguageRegistry;
import net.sourceforge.pmd.lang.rule.XPathRule;
-import net.sourceforge.pmd.lang.rule.xpath.XPathRuleQuery;
+import net.sourceforge.pmd.lang.rule.xpath.XPathVersion;
import net.sourceforge.pmd.lang.scala.ScalaLanguageModule;
import net.sourceforge.pmd.lang.scala.ast.BaseScalaTest;
@@ -21,22 +20,17 @@ public class XPathRuleTest extends BaseScalaTest {
private static final String SCALA_TEST = "/parserFiles/helloworld.scala";
- XPathRule rule;
-
- @Before
- public void setUp() {
- rule = new XPathRule();
- rule.setLanguage(LanguageRegistry.getLanguage(ScalaLanguageModule.NAME));
- rule.setMessage("XPath Rule Failed");
- }
-
@Test
public void testPrintHelloWorld() {
- String xpath = "//TermApply/TermName[@Image=\"println\"]";
- rule.setXPath(xpath);
- rule.setVersion(XPathRuleQuery.XPATH_2_0);
- Report report = scala.getReportForResource(rule, SCALA_TEST);
+ Report report = evaluate(SCALA_TEST, "//TermApply/TermName[@Image=\"println\"]");
RuleViolation rv = report.iterator().next();
assertEquals(2, rv.getBeginLine());
}
+
+ private Report evaluate(String testSource, String xpath) {
+ XPathRule rule = new XPathRule(XPathVersion.XPATH_2_0, xpath);
+ rule.setLanguage(LanguageRegistry.getLanguage(ScalaLanguageModule.NAME));
+ rule.setMessage("XPath Rule Failed");
+ return scala.getReportForResource(rule, testSource);
+ }
}
diff --git a/pmd-scala/src/test/kotlin/net/sourceforge/pmd/lang/scala/ast/ScalaParserTests.kt b/pmd-scala/src/test/kotlin/net/sourceforge/pmd/lang/scala/ast/ScalaParserTests.kt
new file mode 100644
index 0000000000..b156424fa7
--- /dev/null
+++ b/pmd-scala/src/test/kotlin/net/sourceforge/pmd/lang/scala/ast/ScalaParserTests.kt
@@ -0,0 +1,23 @@
+/*
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.scala.ast
+
+import net.sourceforge.pmd.lang.ast.test.BaseParsingHelper
+import net.sourceforge.pmd.lang.ast.test.BaseTreeDumpTest
+import net.sourceforge.pmd.lang.ast.test.SimpleNodePrinter
+import org.junit.Test
+
+class ScalaParserTests : BaseTreeDumpTest(SimpleNodePrinter, ".scala") {
+
+ override val parser: BaseParsingHelper<*, *>
+ get() = ScalaParsingHelper.DEFAULT.withResourceContext(javaClass, "testdata")
+
+ @Test
+ fun testSomeScalaFeatures() = doTest("List")
+
+ @Test
+ fun testPackageObject() = doTest("package")
+
+}
diff --git a/pmd-scala/src/test/resources/net/sourceforge/pmd/lang/scala/ast/testdata/List.scala b/pmd-scala/src/test/resources/net/sourceforge/pmd/lang/scala/ast/testdata/List.scala
new file mode 100644
index 0000000000..0c5ca22710
--- /dev/null
+++ b/pmd-scala/src/test/resources/net/sourceforge/pmd/lang/scala/ast/testdata/List.scala
@@ -0,0 +1,324 @@
+/*
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package scala
+package collection
+package immutable
+
+import scala.annotation.unchecked.uncheckedVariance
+import scala.annotation.tailrec
+import mutable.{Builder, ListBuffer}
+import scala.collection.generic.DefaultSerializable
+import scala.runtime.Statics.releaseFence
+
+/** A class for immutable linked lists representing ordered collections
+ * of elements of type `A`.
+ *
+ */
+@SerialVersionUID(3L)
+sealed abstract class List[+A]
+ extends AbstractSeq[A]
+ with LinearSeq[A]
+ with LinearSeqOps[A, List, List[A]]
+ with StrictOptimizedLinearSeqOps[A, List, List[A]]
+ with StrictOptimizedSeqOps[A, List, List[A]]
+ with IterableFactoryDefaults[A, List]
+ with DefaultSerializable {
+
+ override def iterableFactory: SeqFactory[List] = List
+
+ override def take(n: Int): List[A] = if (isEmpty || n <= 0) Nil else {
+ val h = new ::(head, Nil)
+ var t = h
+ var rest = tail
+ var i = 1
+ while ( {
+ if (rest.isEmpty) return this; i < n
+ }) {
+ i += 1
+ val nx = new ::(rest.head, Nil)
+ t.next = nx
+ t = nx
+ rest = rest.tail
+ }
+ releaseFence()
+ h
+ }
+
+ /**
+ * @example {{{
+ * // Given a list
+ * val letters = List('a','b','c','d','e')
+ *
+ * // `slice` returns all elements beginning at index `from` and afterwards,
+ * // up until index `until` (excluding index `until`.)
+ * letters.slice(1,3) // Returns List('b','c')
+ * }}}
+ */
+ override def slice(from: Int, until: Int): List[A] = {
+ val lo = scala.math.max(from, 0)
+ if (until <= lo || isEmpty) Nil
+ else this drop lo take (until - lo)
+ }
+
+ override def takeRight(n: Int): List[A] = {
+ @tailrec
+ def loop(lead: List[A], lag: List[A]): List[A] = lead match {
+ case Nil => lag
+ case _ :: tail => loop(tail, lag.tail)
+ }
+ loop(drop(n), this)
+ }
+
+ // dropRight is inherited from LinearSeq
+
+ override def splitAt(n: Int): (List[A], List[A]) = {
+ val b = new ListBuffer[A]
+ var i = 0
+ var these = this
+ while (!these.isEmpty && i < n) {
+ i += 1
+ b += these.head
+ these = these.tail
+ }
+ (b.toList, these)
+ }
+
+ override def updated[B >: A](index: Int, elem: B): List[B] = {
+ var i = 0
+ var current = this
+ val prefix = ListBuffer.empty[B]
+ while (i < index && current.nonEmpty) {
+ i += 1
+ prefix += current.head
+ current = current.tail
+ }
+ if (i == index && current.nonEmpty) {
+ prefix.prependToList(elem :: current.tail)
+ } else {
+ throw new IndexOutOfBoundsException(s"$index is out of bounds (min 0, max ${leng t h-1})")
+ }
+ }
+
+ final override def map[B](f: A => B): List[B] = {
+ if (this eq Nil) Nil else {
+ val h = new ::[B](f(head), Nil)
+ var t: ::[B] = h
+ var rest = tail
+ while (rest ne Nil) {
+ val nx = new ::(f(rest.head), Nil)
+ t.next = nx
+ t = nx
+ rest = rest.tail
+ }
+ releaseFence()
+ h
+ }
+ }
+
+ final override def collect[B](pf: PartialFunction[A, B]): List[B] = {
+ if (this eq Nil) Nil else {
+ var rest = this
+ var h: ::[B] = null
+ var x: Any = null
+ // Special case for first element
+ while (h eq null) {
+ x = pf.applyOrElse(rest.head, List.partialNotApplied)
+ if (x.asInstanceOf[AnyRef] ne List.partialNotApplied) h = new ::(x.asInstanceOf[B], Nil)
+ rest = rest.tail
+ if (rest eq Nil) return if (h eq null) Nil else h
+ }
+ var t = h
+ // Remaining elements
+ while (rest ne Nil) {
+ x = pf.applyOrElse(rest.head, List.partialNotApplied)
+ if (x.asInstanceOf[AnyRef] ne List.partialNotApplied) {
+ val nx = new ::(x.asInstanceOf[B], Nil)
+ t.next = nx
+ t = nx
+ }
+ rest = rest.tail
+ }
+ releaseFence()
+ h
+ }
+ }
+
+ final override def flatMap[B](f: A => IterableOnce[B]): List[B] = {
+ var rest = this
+ var h: ::[B] = null
+ var t: ::[B] = null
+ while (rest ne Nil) {
+ val it = f(rest.head).iterator
+ while (it.hasNext) {
+ val nx = new ::(it.next(), Nil)
+ if (t eq null) {
+ h = nx
+ } else {
+ t.next = nx
+ }
+ t = nx
+ }
+ rest = rest.tail
+ }
+ if (h eq null) Nil else {releaseFence(); h}
+ }
+
+ @inline final override def takeWhile(p: A => Boolean): List[A] = {
+ val b = new ListBuffer[A]
+ var these = this
+ while (!these.isEmpty && p(these.head)) {
+ b += these.head
+ these = these.tail
+ }
+ b.toList
+ }
+
+ @inline final override def span(p: A => Boolean): (List[A], List[A]) = {
+ val b = new ListBuffer[A]
+ var these = this
+ while (!these.isEmpty && p(these.head)) {
+ b += these.head
+ these = these.tail
+ }
+ (b.toList, these)
+ }
+
+ // Overridden with an implementation identical to the inherited one (at this time)
+ // solely so it can be finalized and thus inlinable.
+ @inline final override def foreach[U](f: A => U): Unit = {
+ var these = this
+ while (!these.isEmpty) {
+ f(these.head)
+ these = these.tail
+ }
+ }
+
+ final override def reverse: List[A] = {
+ var result: List[A] = Nil
+ var these = this
+ while (!these.isEmpty) {
+ result = these.head :: result
+ these = these.tail
+ }
+ result
+ }
+
+ final override def foldRight[B](z: B)(op: (A, B) => B): B = {
+ var acc = z
+ var these: List[A] = reverse
+ while (!these.isEmpty) {
+ acc = op(these.head, acc)
+ these = these.tail
+ }
+ acc
+ }
+
+ // Copy/Paste overrides to avoid interface calls inside loops.
+
+ override final def length: Int = {
+ var these = this
+ var len = 0
+ while (!these.isEmpty) {
+ len += 1
+ these = these.tail
+ }
+ len
+ }
+
+ override final def lengthCompare(len: Int): Int = {
+ @tailrec def loop(i: Int, xs: List[A]): Int = {
+ if (i == len)
+ if (xs.isEmpty) 0 else 1
+ else if (xs.isEmpty)
+ -1
+ else
+ loop(i + 1, xs.tail)
+ }
+ if (len < 0) 1
+ else loop(0, coll)
+ }
+
+ override final def forall(p: A => Boolean): Boolean = {
+ var these: List[A] = this
+ while (!these.isEmpty) {
+ if (!p(these.head)) return false
+ these = these.tail
+ }
+ true
+ }
+
+ override final def exists(p: A => Boolean): Boolean = {
+ var these: List[A] = this
+ while (!these.isEmpty) {
+ if (p(these.head)) return true
+ these = these.tail
+ }
+ false
+ }
+
+ override final def contains[A1 >: A](elem: A1): Boolean = {
+ var these: List[A] = this
+ while (!these.isEmpty) {
+ if (these.head == elem) return true
+ these = these.tail
+ }
+ false
+ }
+
+ override final def find(p: A => Boolean): Option[A] = {
+ var these: List[A] = this
+ while (!these.isEmpty) {
+ if (p(these.head)) return Some(these.head)
+ these = these.tail
+ }
+ None
+ }
+}
+// Internal code that mutates `next` _must_ call `Statics.releaseFence()` if either immediately, or
+// before a newly-allocated, thread-local :: instance is aliased (e.g. in ListBuffer.toList)
+final case class :: [+A](override val head: A, private[scala] var next: List[A @uncheckedVariance]) // sound because `next` is used only locally
+ extends List[A] {
+ releaseFence()
+ override def headOption: Some[A] = Some(head)
+ override def tail: List[A] = next
+}
+
+case object Nil extends List[Nothing] {
+ override def head: Nothing = throw new NoSuchElementException("head of empty list")
+ override def headOption: None.type = None
+ override def tail: Nothing = throw new UnsupportedOperationException("tail of empty list")
+ override def last: Nothing = throw new NoSuchElementException("last of empty list")
+ override def init: Nothing = throw new UnsupportedOperationException("init of empty list")
+ override def knownSize: Int = 0
+ override def iterator: Iterator[Nothing] = Iterator.empty
+ override def unzip[A1, A2](implicit asPair: Nothing => (A1, A2)): (List[A1], List[A2]) = EmptyUnzip
+
+ @transient
+ private[this] val EmptyUnzip = (Nil, Nil)
+}
+
+/**
+ * $factoryInfo
+ * @define coll list
+ * @define Coll `List`
+ */
+@SerialVersionUID(3L)
+object List extends StrictOptimizedSeqFactory[List] {
+ private val TupleOfNil = (Nil, Nil)
+
+ def from[B](coll: collection.IterableOnce[B]): List[B] = coll match {
+ case coll: List[B] => coll
+ case _ if coll.knownSize == 0 => empty[B]
+ case b: ListBuffer[B] => b.toList
+ case _ => ListBuffer.from(coll).toList
+ }
+
+ def newBuilder[A]: Builder[A, List[A]] = new ListBuffer()
+
+ def empty[A]: List[A] = Nil
+
+ @transient
+ private[collection] val partialNotApplied = new Function1[Any, Any] { def apply(x: Any): Any = this }
+}
diff --git a/pmd-scala/src/test/resources/net/sourceforge/pmd/lang/scala/ast/testdata/List.txt b/pmd-scala/src/test/resources/net/sourceforge/pmd/lang/scala/ast/testdata/List.txt
new file mode 100644
index 0000000000..52f77880f0
--- /dev/null
+++ b/pmd-scala/src/test/resources/net/sourceforge/pmd/lang/scala/ast/testdata/List.txt
@@ -0,0 +1,1634 @@
++- Source
+ +- Pkg
+ +- TermName
+ +- Pkg
+ +- TermName
+ +- Pkg
+ +- TermName
+ +- Import
+ | +- Importer
+ | +- TermSelect
+ | | +- TermSelect
+ | | | +- TermName
+ | | | +- TermName
+ | | +- TermName
+ | +- ImporteeName
+ | +- NameIndeterminate
+ +- Import
+ | +- Importer
+ | +- TermSelect
+ | | +- TermName
+ | | +- TermName
+ | +- ImporteeName
+ | +- NameIndeterminate
+ +- Import
+ | +- Importer
+ | +- TermName
+ | +- ImporteeName
+ | | +- NameIndeterminate
+ | +- ImporteeName
+ | +- NameIndeterminate
+ +- Import
+ | +- Importer
+ | +- TermSelect
+ | | +- TermSelect
+ | | | +- TermName
+ | | | +- TermName
+ | | +- TermName
+ | +- ImporteeName
+ | +- NameIndeterminate
+ +- Import
+ | +- Importer
+ | +- TermSelect
+ | | +- TermSelect
+ | | | +- TermName
+ | | | +- TermName
+ | | +- TermName
+ | +- ImporteeName
+ | +- NameIndeterminate
+ +- DefnClass
+ | +- ModAnnot
+ | | +- Init
+ | | +- TypeName
+ | | +- NameAnonymous
+ | | +- LitLong
+ | +- ModSealed
+ | +- ModAbstract
+ | +- TypeName
+ | +- TypeParam
+ | | +- ModCovariant
+ | | +- TypeName
+ | | +- TypeBounds
+ | +- CtorPrimary
+ | | +- NameAnonymous
+ | +- Template
+ | +- Init
+ | | +- TypeApply
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- NameAnonymous
+ | +- Init
+ | | +- TypeApply
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- NameAnonymous
+ | +- Init
+ | | +- TypeApply
+ | | | +- TypeName
+ | | | +- TypeName
+ | | | +- TypeName
+ | | | +- TypeApply
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- NameAnonymous
+ | +- Init
+ | | +- TypeApply
+ | | | +- TypeName
+ | | | +- TypeName
+ | | | +- TypeName
+ | | | +- TypeApply
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- NameAnonymous
+ | +- Init
+ | | +- TypeApply
+ | | | +- TypeName
+ | | | +- TypeName
+ | | | +- TypeName
+ | | | +- TypeApply
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- NameAnonymous
+ | +- Init
+ | | +- TypeApply
+ | | | +- TypeName
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- NameAnonymous
+ | +- Init
+ | | +- TypeName
+ | | +- NameAnonymous
+ | +- Self
+ | | +- NameAnonymous
+ | +- DefnDef
+ | | +- ModOverride
+ | | +- TermName
+ | | +- TypeApply
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- TermName
+ | +- DefnDef
+ | | +- ModOverride
+ | | +- TermName
+ | | +- TermParam
+ | | | +- TermName
+ | | | +- TypeName
+ | | +- TypeApply
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- TermIf
+ | | +- TermApplyInfix
+ | | | +- TermName
+ | | | +- TermName
+ | | | +- TermApplyInfix
+ | | | +- TermName
+ | | | +- TermName
+ | | | +- LitInt
+ | | +- TermName
+ | | +- TermBlock
+ | | +- DefnVal
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- TermNew
+ | | | +- Init
+ | | | +- TypeName
+ | | | +- NameAnonymous
+ | | | +- TermName
+ | | | +- TermName
+ | | +- DefnVar
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- TermName
+ | | +- DefnVar
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- TermName
+ | | +- DefnVar
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- LitInt
+ | | +- TermWhile
+ | | | +- TermBlock
+ | | | | +- TermIf
+ | | | | | +- TermSelect
+ | | | | | | +- TermName
+ | | | | | | +- TermName
+ | | | | | +- TermReturn
+ | | | | | | +- TermThis
+ | | | | | | +- NameAnonymous
+ | | | | | +- LitUnit
+ | | | | +- TermApplyInfix
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermBlock
+ | | | +- TermApplyInfix
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | | +- LitInt
+ | | | +- DefnVal
+ | | | | +- PatVar
+ | | | | | +- TermName
+ | | | | +- TermNew
+ | | | | +- Init
+ | | | | +- TypeName
+ | | | | +- NameAnonymous
+ | | | | +- TermSelect
+ | | | | | +- TermName
+ | | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermAssign
+ | | | | +- TermSelect
+ | | | | | +- TermName
+ | | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermAssign
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermAssign
+ | | | +- TermName
+ | | | +- TermSelect
+ | | | +- TermName
+ | | | +- TermName
+ | | +- TermApply
+ | | | +- TermName
+ | | +- TermName
+ | +- DefnDef
+ | | +- ModOverride
+ | | +- TermName
+ | | +- TermParam
+ | | | +- TermName
+ | | | +- TypeName
+ | | +- TermParam
+ | | | +- TermName
+ | | | +- TypeName
+ | | +- TypeApply
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- TermBlock
+ | | +- DefnVal
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- TermApply
+ | | | +- TermSelect
+ | | | | +- TermSelect
+ | | | | | +- TermName
+ | | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermName
+ | | | +- LitInt
+ | | +- TermIf
+ | | +- TermApplyInfix
+ | | | +- TermApplyInfix
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermName
+ | | | +- TermName
+ | | +- TermName
+ | | +- TermApplyInfix
+ | | +- TermApplyInfix
+ | | | +- TermThis
+ | | | | +- NameAnonymous
+ | | | +- TermName
+ | | | +- TermName
+ | | +- TermName
+ | | +- TermApplyInfix
+ | | +- TermName
+ | | +- TermName
+ | | +- TermName
+ | +- DefnDef
+ | | +- ModOverride
+ | | +- TermName
+ | | +- TermParam
+ | | | +- TermName
+ | | | +- TypeName
+ | | +- TypeApply
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- TermBlock
+ | | +- DefnDef
+ | | | +- ModAnnot
+ | | | | +- Init
+ | | | | +- TypeName
+ | | | | +- NameAnonymous
+ | | | +- TermName
+ | | | +- TermParam
+ | | | | +- TermName
+ | | | | +- TypeApply
+ | | | | +- TypeName
+ | | | | +- TypeName
+ | | | +- TermParam
+ | | | | +- TermName
+ | | | | +- TypeApply
+ | | | | +- TypeName
+ | | | | +- TypeName
+ | | | +- TypeApply
+ | | | | +- TypeName
+ | | | | +- TypeName
+ | | | +- TermMatch
+ | | | +- TermName
+ | | | +- Case
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- Case
+ | | | +- PatExtractInfix
+ | | | | +- PatWildcard
+ | | | | +- TermName
+ | | | | +- PatVar
+ | | | | +- TermName
+ | | | +- TermApply
+ | | | +- TermName
+ | | | +- TermName
+ | | | +- TermSelect
+ | | | +- TermName
+ | | | +- TermName
+ | | +- TermApply
+ | | +- TermName
+ | | +- TermApply
+ | | | +- TermName
+ | | | +- TermName
+ | | +- TermThis
+ | | +- NameAnonymous
+ | +- DefnDef
+ | | +- ModOverride
+ | | +- TermName
+ | | +- TermParam
+ | | | +- TermName
+ | | | +- TypeName
+ | | +- TypeTuple
+ | | | +- TypeApply
+ | | | | +- TypeName
+ | | | | +- TypeName
+ | | | +- TypeApply
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- TermBlock
+ | | +- DefnVal
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- TermNew
+ | | | +- Init
+ | | | +- TypeApply
+ | | | | +- TypeName
+ | | | | +- TypeName
+ | | | +- NameAnonymous
+ | | +- DefnVar
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- LitInt
+ | | +- DefnVar
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- TermThis
+ | | | +- NameAnonymous
+ | | +- TermWhile
+ | | | +- TermApplyInfix
+ | | | | +- TermApplyUnary
+ | | | | | +- TermName
+ | | | | | +- TermSelect
+ | | | | | +- TermName
+ | | | | | +- TermName
+ | | | | +- TermName
+ | | | | +- TermApplyInfix
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermBlock
+ | | | +- TermApplyInfix
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | | +- LitInt
+ | | | +- TermApplyInfix
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | | +- TermSelect
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermAssign
+ | | | +- TermName
+ | | | +- TermSelect
+ | | | +- TermName
+ | | | +- TermName
+ | | +- TermTuple
+ | | +- TermSelect
+ | | | +- TermName
+ | | | +- TermName
+ | | +- TermName
+ | +- DefnDef
+ | | +- ModOverride
+ | | +- TermName
+ | | +- TypeParam
+ | | | +- TypeName
+ | | | +- TypeBounds
+ | | | +- TypeName
+ | | +- TermParam
+ | | | +- TermName
+ | | | +- TypeName
+ | | +- TermParam
+ | | | +- TermName
+ | | | +- TypeName
+ | | +- TypeApply
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- TermBlock
+ | | +- DefnVar
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- LitInt
+ | | +- DefnVar
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- TermThis
+ | | | +- NameAnonymous
+ | | +- DefnVal
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- TermApplyType
+ | | | +- TermSelect
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TypeName
+ | | +- TermWhile
+ | | | +- TermApplyInfix
+ | | | | +- TermApplyInfix
+ | | | | | +- TermName
+ | | | | | +- TermName
+ | | | | | +- TermName
+ | | | | +- TermName
+ | | | | +- TermSelect
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermBlock
+ | | | +- TermApplyInfix
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | | +- LitInt
+ | | | +- TermApplyInfix
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | | +- TermSelect
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermAssign
+ | | | +- TermName
+ | | | +- TermSelect
+ | | | +- TermName
+ | | | +- TermName
+ | | +- TermIf
+ | | +- TermApplyInfix
+ | | | +- TermApplyInfix
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermName
+ | | | +- TermSelect
+ | | | +- TermName
+ | | | +- TermName
+ | | +- TermBlock
+ | | | +- TermApply
+ | | | +- TermSelect
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermApplyInfix
+ | | | +- TermName
+ | | | +- TermName
+ | | | +- TermSelect
+ | | | +- TermName
+ | | | +- TermName
+ | | +- TermBlock
+ | | +- TermThrow
+ | | +- TermNew
+ | | +- Init
+ | | +- TypeName
+ | | +- NameAnonymous
+ | | +- TermInterpolate
+ | | +- TermName
+ | | +- LitString
+ | | +- LitString
+ | | +- LitString
+ | | +- TermName
+ | | +- TermApplyInfix
+ | | +- TermName
+ | | +- TermName
+ | | +- TermApplyInfix
+ | | +- TermName
+ | | +- TermName
+ | | +- LitInt
+ | +- DefnDef
+ | | +- ModFinal
+ | | +- ModOverride
+ | | +- TermName
+ | | +- TypeParam
+ | | | +- TypeName
+ | | | +- TypeBounds
+ | | +- TermParam
+ | | | +- TermName
+ | | | +- TypeFunction
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- TypeApply
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- TermBlock
+ | | +- TermIf
+ | | +- TermApplyInfix
+ | | | +- TermThis
+ | | | | +- NameAnonymous
+ | | | +- TermName
+ | | | +- TermName
+ | | +- TermName
+ | | +- TermBlock
+ | | +- DefnVal
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- TermNew
+ | | | +- Init
+ | | | +- TypeApply
+ | | | | +- TypeName
+ | | | | +- TypeName
+ | | | +- NameAnonymous
+ | | | +- TermApply
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermName
+ | | +- DefnVar
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- TypeApply
+ | | | | +- TypeName
+ | | | | +- TypeName
+ | | | +- TermName
+ | | +- DefnVar
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- TermName
+ | | +- TermWhile
+ | | | +- TermApplyInfix
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermBlock
+ | | | +- DefnVal
+ | | | | +- PatVar
+ | | | | | +- TermName
+ | | | | +- TermNew
+ | | | | +- Init
+ | | | | +- TypeName
+ | | | | +- NameAnonymous
+ | | | | +- TermApply
+ | | | | | +- TermName
+ | | | | | +- TermSelect
+ | | | | | +- TermName
+ | | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermAssign
+ | | | | +- TermSelect
+ | | | | | +- TermName
+ | | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermAssign
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermAssign
+ | | | +- TermName
+ | | | +- TermSelect
+ | | | +- TermName
+ | | | +- TermName
+ | | +- TermApply
+ | | | +- TermName
+ | | +- TermName
+ | +- DefnDef
+ | | +- ModFinal
+ | | +- ModOverride
+ | | +- TermName
+ | | +- TypeParam
+ | | | +- TypeName
+ | | | +- TypeBounds
+ | | +- TermParam
+ | | | +- TermName
+ | | | +- TypeApply
+ | | | +- TypeName
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- TypeApply
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- TermBlock
+ | | +- TermIf
+ | | +- TermApplyInfix
+ | | | +- TermThis
+ | | | | +- NameAnonymous
+ | | | +- TermName
+ | | | +- TermName
+ | | +- TermName
+ | | +- TermBlock
+ | | +- DefnVar
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- TermThis
+ | | | +- NameAnonymous
+ | | +- DefnVar
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- TypeApply
+ | | | | +- TypeName
+ | | | | +- TypeName
+ | | | +- LitNull
+ | | +- DefnVar
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- TypeName
+ | | | +- LitNull
+ | | +- TermWhile
+ | | | +- TermApplyInfix
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | | +- LitNull
+ | | | +- TermBlock
+ | | | +- TermAssign
+ | | | | +- TermName
+ | | | | +- TermApply
+ | | | | +- TermSelect
+ | | | | | +- TermName
+ | | | | | +- TermName
+ | | | | +- TermSelect
+ | | | | | +- TermName
+ | | | | | +- TermName
+ | | | | +- TermSelect
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermIf
+ | | | | +- TermApplyInfix
+ | | | | | +- TermApplyType
+ | | | | | | +- TermSelect
+ | | | | | | | +- TermName
+ | | | | | | | +- TermName
+ | | | | | | +- TypeName
+ | | | | | +- TermName
+ | | | | | +- TermSelect
+ | | | | | +- TermName
+ | | | | | +- TermName
+ | | | | +- TermAssign
+ | | | | | +- TermName
+ | | | | | +- TermNew
+ | | | | | +- Init
+ | | | | | +- TypeName
+ | | | | | +- NameAnonymous
+ | | | | | +- TermApplyType
+ | | | | | | +- TermSelect
+ | | | | | | | +- TermName
+ | | | | | | | +- TermName
+ | | | | | | +- TypeName
+ | | | | | +- TermName
+ | | | | +- LitUnit
+ | | | +- TermAssign
+ | | | | +- TermName
+ | | | | +- TermSelect
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermIf
+ | | | +- TermApplyInfix
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermReturn
+ | | | | +- TermIf
+ | | | | +- TermApplyInfix
+ | | | | | +- TermName
+ | | | | | +- TermName
+ | | | | | +- LitNull
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- LitUnit
+ | | +- DefnVar
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- TermName
+ | | +- TermWhile
+ | | | +- TermApplyInfix
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermBlock
+ | | | +- TermAssign
+ | | | | +- TermName
+ | | | | +- TermApply
+ | | | | +- TermSelect
+ | | | | | +- TermName
+ | | | | | +- TermName
+ | | | | +- TermSelect
+ | | | | | +- TermName
+ | | | | | +- TermName
+ | | | | +- TermSelect
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermIf
+ | | | | +- TermApplyInfix
+ | | | | | +- TermApplyType
+ | | | | | | +- TermSelect
+ | | | | | | | +- TermName
+ | | | | | | | +- TermName
+ | | | | | | +- TypeName
+ | | | | | +- TermName
+ | | | | | +- TermSelect
+ | | | | | +- TermName
+ | | | | | +- TermName
+ | | | | +- TermBlock
+ | | | | | +- DefnVal
+ | | | | | | +- PatVar
+ | | | | | | | +- TermName
+ | | | | | | +- TermNew
+ | | | | | | +- Init
+ | | | | | | +- TypeName
+ | | | | | | +- NameAnonymous
+ | | | | | | +- TermApplyType
+ | | | | | | | +- TermSelect
+ | | | | | | | | +- TermName
+ | | | | | | | | +- TermName
+ | | | | | | | +- TypeName
+ | | | | | | +- TermName
+ | | | | | +- TermAssign
+ | | | | | | +- TermSelect
+ | | | | | | | +- TermName
+ | | | | | | | +- TermName
+ | | | | | | +- TermName
+ | | | | | +- TermAssign
+ | | | | | +- TermName
+ | | | | | +- TermName
+ | | | | +- LitUnit
+ | | | +- TermAssign
+ | | | +- TermName
+ | | | +- TermSelect
+ | | | +- TermName
+ | | | +- TermName
+ | | +- TermApply
+ | | | +- TermName
+ | | +- TermName
+ | +- DefnDef
+ | | +- ModFinal
+ | | +- ModOverride
+ | | +- TermName
+ | | +- TypeParam
+ | | | +- TypeName
+ | | | +- TypeBounds
+ | | +- TermParam
+ | | | +- TermName
+ | | | +- TypeFunction
+ | | | +- TypeName
+ | | | +- TypeApply
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- TypeApply
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- TermBlock
+ | | +- DefnVar
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- TermThis
+ | | | +- NameAnonymous
+ | | +- DefnVar
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- TypeApply
+ | | | | +- TypeName
+ | | | | +- TypeName
+ | | | +- LitNull
+ | | +- DefnVar
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- TypeApply
+ | | | | +- TypeName
+ | | | | +- TypeName
+ | | | +- LitNull
+ | | +- TermWhile
+ | | | +- TermApplyInfix
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermBlock
+ | | | +- DefnVal
+ | | | | +- PatVar
+ | | | | | +- TermName
+ | | | | +- TermSelect
+ | | | | +- TermApply
+ | | | | | +- TermName
+ | | | | | +- TermSelect
+ | | | | | +- TermName
+ | | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermWhile
+ | | | | +- TermSelect
+ | | | | | +- TermName
+ | | | | | +- TermName
+ | | | | +- TermBlock
+ | | | | +- DefnVal
+ | | | | | +- PatVar
+ | | | | | | +- TermName
+ | | | | | +- TermNew
+ | | | | | +- Init
+ | | | | | +- TypeName
+ | | | | | +- NameAnonymous
+ | | | | | +- TermApply
+ | | | | | | +- TermSelect
+ | | | | | | +- TermName
+ | | | | | | +- TermName
+ | | | | | +- TermName
+ | | | | +- TermIf
+ | | | | | +- TermApplyInfix
+ | | | | | | +- TermName
+ | | | | | | +- TermName
+ | | | | | | +- LitNull
+ | | | | | +- TermBlock
+ | | | | | | +- TermAssign
+ | | | | | | +- TermName
+ | | | | | | +- TermName
+ | | | | | +- TermBlock
+ | | | | | +- TermAssign
+ | | | | | +- TermSelect
+ | | | | | | +- TermName
+ | | | | | | +- TermName
+ | | | | | +- TermName
+ | | | | +- TermAssign
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermAssign
+ | | | +- TermName
+ | | | +- TermSelect
+ | | | +- TermName
+ | | | +- TermName
+ | | +- TermIf
+ | | +- TermApplyInfix
+ | | | +- TermName
+ | | | +- TermName
+ | | | +- LitNull
+ | | +- TermName
+ | | +- TermBlock
+ | | +- TermApply
+ | | | +- TermName
+ | | +- TermName
+ | +- DefnDef
+ | | +- ModAnnot
+ | | | +- Init
+ | | | +- TypeName
+ | | | +- NameAnonymous
+ | | +- ModFinal
+ | | +- ModOverride
+ | | +- TermName
+ | | +- TermParam
+ | | | +- TermName
+ | | | +- TypeFunction
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- TypeApply
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- TermBlock
+ | | +- DefnVal
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- TermNew
+ | | | +- Init
+ | | | +- TypeApply
+ | | | | +- TypeName
+ | | | | +- TypeName
+ | | | +- NameAnonymous
+ | | +- DefnVar
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- TermThis
+ | | | +- NameAnonymous
+ | | +- TermWhile
+ | | | +- TermApplyInfix
+ | | | | +- TermApplyUnary
+ | | | | | +- TermName
+ | | | | | +- TermSelect
+ | | | | | +- TermName
+ | | | | | +- TermName
+ | | | | +- TermName
+ | | | | +- TermApply
+ | | | | +- TermName
+ | | | | +- TermSelect
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermBlock
+ | | | +- TermApplyInfix
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | | +- TermSelect
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermAssign
+ | | | +- TermName
+ | | | +- TermSelect
+ | | | +- TermName
+ | | | +- TermName
+ | | +- TermSelect
+ | | +- TermName
+ | | +- TermName
+ | +- DefnDef
+ | | +- ModAnnot
+ | | | +- Init
+ | | | +- TypeName
+ | | | +- NameAnonymous
+ | | +- ModFinal
+ | | +- ModOverride
+ | | +- TermName
+ | | +- TermParam
+ | | | +- TermName
+ | | | +- TypeFunction
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- TypeTuple
+ | | | +- TypeApply
+ | | | | +- TypeName
+ | | | | +- TypeName
+ | | | +- TypeApply
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- TermBlock
+ | | +- DefnVal
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- TermNew
+ | | | +- Init
+ | | | +- TypeApply
+ | | | | +- TypeName
+ | | | | +- TypeName
+ | | | +- NameAnonymous
+ | | +- DefnVar
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- TermThis
+ | | | +- NameAnonymous
+ | | +- TermWhile
+ | | | +- TermApplyInfix
+ | | | | +- TermApplyUnary
+ | | | | | +- TermName
+ | | | | | +- TermSelect
+ | | | | | +- TermName
+ | | | | | +- TermName
+ | | | | +- TermName
+ | | | | +- TermApply
+ | | | | +- TermName
+ | | | | +- TermSelect
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermBlock
+ | | | +- TermApplyInfix
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | | +- TermSelect
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermAssign
+ | | | +- TermName
+ | | | +- TermSelect
+ | | | +- TermName
+ | | | +- TermName
+ | | +- TermTuple
+ | | +- TermSelect
+ | | | +- TermName
+ | | | +- TermName
+ | | +- TermName
+ | +- DefnDef
+ | | +- ModAnnot
+ | | | +- Init
+ | | | +- TypeName
+ | | | +- NameAnonymous
+ | | +- ModFinal
+ | | +- ModOverride
+ | | +- TermName
+ | | +- TypeParam
+ | | | +- TypeName
+ | | | +- TypeBounds
+ | | +- TermParam
+ | | | +- TermName
+ | | | +- TypeFunction
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- TypeName
+ | | +- TermBlock
+ | | +- DefnVar
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- TermThis
+ | | | +- NameAnonymous
+ | | +- TermWhile
+ | | +- TermApplyUnary
+ | | | +- TermName
+ | | | +- TermSelect
+ | | | +- TermName
+ | | | +- TermName
+ | | +- TermBlock
+ | | +- TermApply
+ | | | +- TermName
+ | | | +- TermSelect
+ | | | +- TermName
+ | | | +- TermName
+ | | +- TermAssign
+ | | +- TermName
+ | | +- TermSelect
+ | | +- TermName
+ | | +- TermName
+ | +- DefnDef
+ | | +- ModFinal
+ | | +- ModOverride
+ | | +- TermName
+ | | +- TypeApply
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- TermBlock
+ | | +- DefnVar
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- TypeApply
+ | | | | +- TypeName
+ | | | | +- TypeName
+ | | | +- TermName
+ | | +- DefnVar
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- TermThis
+ | | | +- NameAnonymous
+ | | +- TermWhile
+ | | | +- TermApplyUnary
+ | | | | +- TermName
+ | | | | +- TermSelect
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermBlock
+ | | | +- TermAssign
+ | | | | +- TermName
+ | | | | +- TermApplyInfix
+ | | | | +- TermSelect
+ | | | | | +- TermName
+ | | | | | +- TermName
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermAssign
+ | | | +- TermName
+ | | | +- TermSelect
+ | | | +- TermName
+ | | | +- TermName
+ | | +- TermName
+ | +- DefnDef
+ | | +- ModFinal
+ | | +- ModOverride
+ | | +- TermName
+ | | +- TypeParam
+ | | | +- TypeName
+ | | | +- TypeBounds
+ | | +- TermParam
+ | | | +- TermName
+ | | | +- TypeName
+ | | +- TermParam
+ | | | +- TermName
+ | | | +- TypeFunction
+ | | | +- TypeName
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- TypeName
+ | | +- TermBlock
+ | | +- DefnVar
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- TermName
+ | | +- DefnVar
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- TypeApply
+ | | | | +- TypeName
+ | | | | +- TypeName
+ | | | +- TermName
+ | | +- TermWhile
+ | | | +- TermApplyUnary
+ | | | | +- TermName
+ | | | | +- TermSelect
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermBlock
+ | | | +- TermAssign
+ | | | | +- TermName
+ | | | | +- TermApply
+ | | | | +- TermName
+ | | | | +- TermSelect
+ | | | | | +- TermName
+ | | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermAssign
+ | | | +- TermName
+ | | | +- TermSelect
+ | | | +- TermName
+ | | | +- TermName
+ | | +- TermName
+ | +- DefnDef
+ | | +- ModOverride
+ | | +- ModFinal
+ | | +- TermName
+ | | +- TypeName
+ | | +- TermBlock
+ | | +- DefnVar
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- TermThis
+ | | | +- NameAnonymous
+ | | +- DefnVar
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- LitInt
+ | | +- TermWhile
+ | | | +- TermApplyUnary
+ | | | | +- TermName
+ | | | | +- TermSelect
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermBlock
+ | | | +- TermApplyInfix
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | | +- LitInt
+ | | | +- TermAssign
+ | | | +- TermName
+ | | | +- TermSelect
+ | | | +- TermName
+ | | | +- TermName
+ | | +- TermName
+ | +- DefnDef
+ | | +- ModOverride
+ | | +- ModFinal
+ | | +- TermName
+ | | +- TermParam
+ | | | +- TermName
+ | | | +- TypeName
+ | | +- TypeName
+ | | +- TermBlock
+ | | +- DefnDef
+ | | | +- ModAnnot
+ | | | | +- Init
+ | | | | +- TypeName
+ | | | | +- NameAnonymous
+ | | | +- TermName
+ | | | +- TermParam
+ | | | | +- TermName
+ | | | | +- TypeName
+ | | | +- TermParam
+ | | | | +- TermName
+ | | | | +- TypeApply
+ | | | | +- TypeName
+ | | | | +- TypeName
+ | | | +- TypeName
+ | | | +- TermBlock
+ | | | +- TermIf
+ | | | +- TermApplyInfix
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermIf
+ | | | | +- TermSelect
+ | | | | | +- TermName
+ | | | | | +- TermName
+ | | | | +- LitInt
+ | | | | +- LitInt
+ | | | +- TermIf
+ | | | +- TermSelect
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- LitInt
+ | | | +- TermApply
+ | | | +- TermName
+ | | | +- TermApplyInfix
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | | +- LitInt
+ | | | +- TermSelect
+ | | | +- TermName
+ | | | +- TermName
+ | | +- TermIf
+ | | +- TermApplyInfix
+ | | | +- TermName
+ | | | +- TermName
+ | | | +- LitInt
+ | | +- LitInt
+ | | +- TermApply
+ | | +- TermName
+ | | +- LitInt
+ | | +- TermName
+ | +- DefnDef
+ | | +- ModOverride
+ | | +- ModFinal
+ | | +- TermName
+ | | +- TermParam
+ | | | +- TermName
+ | | | +- TypeFunction
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- TypeName
+ | | +- TermBlock
+ | | +- DefnVar
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- TypeApply
+ | | | | +- TypeName
+ | | | | +- TypeName
+ | | | +- TermThis
+ | | | +- NameAnonymous
+ | | +- TermWhile
+ | | | +- TermApplyUnary
+ | | | | +- TermName
+ | | | | +- TermSelect
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermBlock
+ | | | +- TermIf
+ | | | | +- TermApplyUnary
+ | | | | | +- TermName
+ | | | | | +- TermApply
+ | | | | | +- TermName
+ | | | | | +- TermSelect
+ | | | | | +- TermName
+ | | | | | +- TermName
+ | | | | +- TermReturn
+ | | | | | +- LitBoolean
+ | | | | +- LitUnit
+ | | | +- TermAssign
+ | | | +- TermName
+ | | | +- TermSelect
+ | | | +- TermName
+ | | | +- TermName
+ | | +- LitBoolean
+ | +- DefnDef
+ | | +- ModOverride
+ | | +- ModFinal
+ | | +- TermName
+ | | +- TermParam
+ | | | +- TermName
+ | | | +- TypeFunction
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- TypeName
+ | | +- TermBlock
+ | | +- DefnVar
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- TypeApply
+ | | | | +- TypeName
+ | | | | +- TypeName
+ | | | +- TermThis
+ | | | +- NameAnonymous
+ | | +- TermWhile
+ | | | +- TermApplyUnary
+ | | | | +- TermName
+ | | | | +- TermSelect
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermBlock
+ | | | +- TermIf
+ | | | | +- TermApply
+ | | | | | +- TermName
+ | | | | | +- TermSelect
+ | | | | | +- TermName
+ | | | | | +- TermName
+ | | | | +- TermReturn
+ | | | | | +- LitBoolean
+ | | | | +- LitUnit
+ | | | +- TermAssign
+ | | | +- TermName
+ | | | +- TermSelect
+ | | | +- TermName
+ | | | +- TermName
+ | | +- LitBoolean
+ | +- DefnDef
+ | | +- ModOverride
+ | | +- ModFinal
+ | | +- TermName
+ | | +- TypeParam
+ | | | +- TypeName
+ | | | +- TypeBounds
+ | | | +- TypeName
+ | | +- TermParam
+ | | | +- TermName
+ | | | +- TypeName
+ | | +- TypeName
+ | | +- TermBlock
+ | | +- DefnVar
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- TypeApply
+ | | | | +- TypeName
+ | | | | +- TypeName
+ | | | +- TermThis
+ | | | +- NameAnonymous
+ | | +- TermWhile
+ | | | +- TermApplyUnary
+ | | | | +- TermName
+ | | | | +- TermSelect
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermBlock
+ | | | +- TermIf
+ | | | | +- TermApplyInfix
+ | | | | | +- TermSelect
+ | | | | | | +- TermName
+ | | | | | | +- TermName
+ | | | | | +- TermName
+ | | | | | +- TermName
+ | | | | +- TermReturn
+ | | | | | +- LitBoolean
+ | | | | +- LitUnit
+ | | | +- TermAssign
+ | | | +- TermName
+ | | | +- TermSelect
+ | | | +- TermName
+ | | | +- TermName
+ | | +- LitBoolean
+ | +- DefnDef
+ | +- ModOverride
+ | +- ModFinal
+ | +- TermName
+ | +- TermParam
+ | | +- TermName
+ | | +- TypeFunction
+ | | +- TypeName
+ | | +- TypeName
+ | +- TypeApply
+ | | +- TypeName
+ | | +- TypeName
+ | +- TermBlock
+ | +- DefnVar
+ | | +- PatVar
+ | | | +- TermName
+ | | +- TypeApply
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- TermThis
+ | | +- NameAnonymous
+ | +- TermWhile
+ | | +- TermApplyUnary
+ | | | +- TermName
+ | | | +- TermSelect
+ | | | +- TermName
+ | | | +- TermName
+ | | +- TermBlock
+ | | +- TermIf
+ | | | +- TermApply
+ | | | | +- TermName
+ | | | | +- TermSelect
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermReturn
+ | | | | +- TermApply
+ | | | | +- TermName
+ | | | | +- TermSelect
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- LitUnit
+ | | +- TermAssign
+ | | +- TermName
+ | | +- TermSelect
+ | | +- TermName
+ | | +- TermName
+ | +- TermName
+ +- DefnClass
+ | +- ModFinal
+ | +- ModCase
+ | +- TypeName
+ | +- TypeParam
+ | | +- ModCovariant
+ | | +- TypeName
+ | | +- TypeBounds
+ | +- CtorPrimary
+ | | +- NameAnonymous
+ | | +- TermParam
+ | | | +- ModOverride
+ | | | +- ModValParam
+ | | | +- TermName
+ | | | +- TypeName
+ | | +- TermParam
+ | | +- ModPrivate
+ | | | +- NameIndeterminate
+ | | +- ModVarParam
+ | | +- TermName
+ | | +- TypeApply
+ | | +- TypeName
+ | | +- TypeAnnotate
+ | | +- TypeName
+ | | +- ModAnnot
+ | | +- Init
+ | | +- TypeName
+ | | +- NameAnonymous
+ | +- Template
+ | +- Init
+ | | +- TypeApply
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- NameAnonymous
+ | +- Self
+ | | +- NameAnonymous
+ | +- TermApply
+ | | +- TermName
+ | +- DefnDef
+ | | +- ModOverride
+ | | +- TermName
+ | | +- TypeApply
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- TermApply
+ | | +- TermName
+ | | +- TermName
+ | +- DefnDef
+ | +- ModOverride
+ | +- TermName
+ | +- TypeApply
+ | | +- TypeName
+ | | +- TypeName
+ | +- TermName
+ +- DefnObject
+ | +- ModCase
+ | +- TermName
+ | +- Template
+ | +- Init
+ | | +- TypeApply
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- NameAnonymous
+ | +- Self
+ | | +- NameAnonymous
+ | +- DefnDef
+ | | +- ModOverride
+ | | +- TermName
+ | | +- TypeName
+ | | +- TermThrow
+ | | +- TermNew
+ | | +- Init
+ | | +- TypeName
+ | | +- NameAnonymous
+ | | +- LitString
+ | +- DefnDef
+ | | +- ModOverride
+ | | +- TermName
+ | | +- TypeSingleton
+ | | | +- TermName
+ | | +- TermName
+ | +- DefnDef
+ | | +- ModOverride
+ | | +- TermName
+ | | +- TypeName
+ | | +- TermThrow
+ | | +- TermNew
+ | | +- Init
+ | | +- TypeName
+ | | +- NameAnonymous
+ | | +- LitString
+ | +- DefnDef
+ | | +- ModOverride
+ | | +- TermName
+ | | +- TypeName
+ | | +- TermThrow
+ | | +- TermNew
+ | | +- Init
+ | | +- TypeName
+ | | +- NameAnonymous
+ | | +- LitString
+ | +- DefnDef
+ | | +- ModOverride
+ | | +- TermName
+ | | +- TypeName
+ | | +- TermThrow
+ | | +- TermNew
+ | | +- Init
+ | | +- TypeName
+ | | +- NameAnonymous
+ | | +- LitString
+ | +- DefnDef
+ | | +- ModOverride
+ | | +- TermName
+ | | +- TypeName
+ | | +- LitInt
+ | +- DefnDef
+ | | +- ModOverride
+ | | +- TermName
+ | | +- TypeApply
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- TermSelect
+ | | +- TermName
+ | | +- TermName
+ | +- DefnDef
+ | | +- ModOverride
+ | | +- TermName
+ | | +- TypeParam
+ | | | +- TypeName
+ | | | +- TypeBounds
+ | | +- TypeParam
+ | | | +- TypeName
+ | | | +- TypeBounds
+ | | +- TermParam
+ | | | +- ModImplicit
+ | | | +- TermName
+ | | | +- TypeFunction
+ | | | +- TypeName
+ | | | +- TypeTuple
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- TypeTuple
+ | | | +- TypeApply
+ | | | | +- TypeName
+ | | | | +- TypeName
+ | | | +- TypeApply
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- TermName
+ | +- DefnVal
+ | +- ModAnnot
+ | | +- Init
+ | | +- TypeName
+ | | +- NameAnonymous
+ | +- ModPrivate
+ | | +- TermThis
+ | | +- NameAnonymous
+ | +- PatVar
+ | | +- TermName
+ | +- TermTuple
+ | +- TermName
+ | +- TermName
+ +- DefnObject
+ +- ModAnnot
+ | +- Init
+ | +- TypeName
+ | +- NameAnonymous
+ | +- LitLong
+ +- TermName
+ +- Template
+ +- Init
+ | +- TypeApply
+ | | +- TypeName
+ | | +- TypeName
+ | +- NameAnonymous
+ +- Self
+ | +- NameAnonymous
+ +- DefnVal
+ | +- ModPrivate
+ | | +- NameAnonymous
+ | +- PatVar
+ | | +- TermName
+ | +- TermTuple
+ | +- TermName
+ | +- TermName
+ +- DefnDef
+ | +- TermName
+ | +- TypeParam
+ | | +- TypeName
+ | | +- TypeBounds
+ | +- TermParam
+ | | +- TermName
+ | | +- TypeApply
+ | | +- TypeSelect
+ | | | +- TermName
+ | | | +- TypeName
+ | | +- TypeName
+ | +- TypeApply
+ | | +- TypeName
+ | | +- TypeName
+ | +- TermMatch
+ | +- TermName
+ | +- Case
+ | | +- PatTyped
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- TypeApply
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- TermName
+ | +- Case
+ | | +- PatWildcard
+ | | +- TermApplyInfix
+ | | | +- TermSelect
+ | | | | +- TermName
+ | | | | +- TermName
+ | | | +- TermName
+ | | | +- LitInt
+ | | +- TermApplyType
+ | | +- TermName
+ | | +- TypeName
+ | +- Case
+ | | +- PatTyped
+ | | | +- PatVar
+ | | | | +- TermName
+ | | | +- TypeApply
+ | | | +- TypeName
+ | | | +- TypeName
+ | | +- TermSelect
+ | | +- TermName
+ | | +- TermName
+ | +- Case
+ | +- PatWildcard
+ | +- TermSelect
+ | +- TermApply
+ | | +- TermSelect
+ | | | +- TermName
+ | | | +- TermName
+ | | +- TermName
+ | +- TermName
+ +- DefnDef
+ | +- TermName
+ | +- TypeParam
+ | | +- TypeName
+ | | +- TypeBounds
+ | +- TypeApply
+ | | +- TypeName
+ | | +- TypeName
+ | | +- TypeApply
+ | | +- TypeName
+ | | +- TypeName
+ | +- TermNew
+ | +- Init
+ | +- TypeName
+ | +- NameAnonymous
+ +- DefnDef
+ | +- TermName
+ | +- TypeParam
+ | | +- TypeName
+ | | +- TypeBounds
+ | +- TypeApply
+ | | +- TypeName
+ | | +- TypeName
+ | +- TermName
+ +- DefnVal
+ +- ModAnnot
+ | +- Init
+ | +- TypeName
+ | +- NameAnonymous
+ +- ModPrivate
+ | +- NameIndeterminate
+ +- PatVar
+ | +- TermName
+ +- TermNewAnonymous
+ +- Template
+ +- Init
+ | +- TypeApply
+ | | +- TypeName
+ | | +- TypeName
+ | | +- TypeName
+ | +- NameAnonymous
+ +- Self
+ | +- NameAnonymous
+ +- DefnDef
+ +- TermName
+ +- TermParam
+ | +- TermName
+ | +- TypeName
+ +- TypeName
+ +- TermThis
+ +- NameAnonymous
diff --git a/pmd-scala/src/test/resources/net/sourceforge/pmd/lang/scala/ast/testdata/package.scala b/pmd-scala/src/test/resources/net/sourceforge/pmd/lang/scala/ast/testdata/package.scala
new file mode 100644
index 0000000000..4acf9bf2b7
--- /dev/null
+++ b/pmd-scala/src/test/resources/net/sourceforge/pmd/lang/scala/ast/testdata/package.scala
@@ -0,0 +1,22 @@
+
+/*
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package scala.collection
+
+
+package object immutable {
+ type StringOps = scala.collection.StringOps
+ val StringOps = scala.collection.StringOps
+ type StringView = scala.collection.StringView
+ val StringView = scala.collection.StringView
+
+ @deprecated("Use Iterable instead of Traversable", "2.13.0")
+ type Traversable[+X] = Iterable[X]
+ @deprecated("Use Iterable instead of Traversable", "2.13.0")
+ val Traversable = Iterable
+
+ @deprecated("Use Map instead of DefaultMap", "2.13.0")
+ type DefaultMap[K, +V] = scala.collection.immutable.Map[K, V]
+}
\ No newline at end of file
diff --git a/pmd-scala/src/test/resources/net/sourceforge/pmd/lang/scala/ast/testdata/package.txt b/pmd-scala/src/test/resources/net/sourceforge/pmd/lang/scala/ast/testdata/package.txt
new file mode 100644
index 0000000000..0a5eedd4b5
--- /dev/null
+++ b/pmd-scala/src/test/resources/net/sourceforge/pmd/lang/scala/ast/testdata/package.txt
@@ -0,0 +1,90 @@
++- Source
+ +- Pkg
+ +- TermSelect
+ | +- TermName
+ | +- TermName
+ +- PkgObject
+ +- TermName
+ +- Template
+ +- Self
+ | +- NameAnonymous
+ +- DefnType
+ | +- TypeName
+ | +- TypeSelect
+ | +- TermSelect
+ | | +- TermName
+ | | +- TermName
+ | +- TypeName
+ +- DefnVal
+ | +- PatVar
+ | | +- TermName
+ | +- TermSelect
+ | +- TermSelect
+ | | +- TermName
+ | | +- TermName
+ | +- TermName
+ +- DefnType
+ | +- TypeName
+ | +- TypeSelect
+ | +- TermSelect
+ | | +- TermName
+ | | +- TermName
+ | +- TypeName
+ +- DefnVal
+ | +- PatVar
+ | | +- TermName
+ | +- TermSelect
+ | +- TermSelect
+ | | +- TermName
+ | | +- TermName
+ | +- TermName
+ +- DefnType
+ | +- ModAnnot
+ | | +- Init
+ | | +- TypeName
+ | | +- NameAnonymous
+ | | +- LitString
+ | | +- LitString
+ | +- TypeName
+ | +- TypeParam
+ | | +- ModCovariant
+ | | +- TypeName
+ | | +- TypeBounds
+ | +- TypeApply
+ | +- TypeName
+ | +- TypeName
+ +- DefnVal
+ | +- ModAnnot
+ | | +- Init
+ | | +- TypeName
+ | | +- NameAnonymous
+ | | +- LitString
+ | | +- LitString
+ | +- PatVar
+ | | +- TermName
+ | +- TermName
+ +- DefnType
+ +- ModAnnot
+ | +- Init
+ | +- TypeName
+ | +- NameAnonymous
+ | +- LitString
+ | +- LitString
+ +- TypeName
+ +- TypeParam
+ | +- TypeName
+ | +- TypeBounds
+ +- TypeParam
+ | +- ModCovariant
+ | +- TypeName
+ | +- TypeBounds
+ +- TypeApply
+ +- TypeSelect
+ | +- TermSelect
+ | | +- TermSelect
+ | | | +- TermName
+ | | | +- TermName
+ | | +- TermName
+ | +- TypeName
+ +- TypeName
+ +- TypeName
diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/VfHandler.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/VfHandler.java
index afaf88eedd..e40d90f8ab 100644
--- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/VfHandler.java
+++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/VfHandler.java
@@ -10,9 +10,7 @@ import net.sourceforge.pmd.lang.AbstractLanguageVersionHandler;
import net.sourceforge.pmd.lang.Parser;
import net.sourceforge.pmd.lang.ParserOptions;
import net.sourceforge.pmd.lang.VisitorStarter;
-import net.sourceforge.pmd.lang.XPathHandler;
import net.sourceforge.pmd.lang.ast.Node;
-import net.sourceforge.pmd.lang.ast.xpath.DefaultASTXPathHandler;
import net.sourceforge.pmd.lang.rule.RuleViolationFactory;
import net.sourceforge.pmd.lang.vf.ast.DumpFacade;
import net.sourceforge.pmd.lang.vf.ast.VfNode;
@@ -20,11 +18,6 @@ import net.sourceforge.pmd.lang.vf.rule.VfRuleViolationFactory;
public class VfHandler extends AbstractLanguageVersionHandler {
- @Override
- public XPathHandler getXPathHandler() {
- return new DefaultASTXPathHandler();
- }
-
@Override
public RuleViolationFactory getRuleViolationFactory() {
return VfRuleViolationFactory.INSTANCE;
diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/VmHandler.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/VmHandler.java
index c3cf2a665a..d3f5e54e7c 100644
--- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/VmHandler.java
+++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/VmHandler.java
@@ -10,9 +10,7 @@ import net.sourceforge.pmd.lang.AbstractLanguageVersionHandler;
import net.sourceforge.pmd.lang.Parser;
import net.sourceforge.pmd.lang.ParserOptions;
import net.sourceforge.pmd.lang.VisitorStarter;
-import net.sourceforge.pmd.lang.XPathHandler;
import net.sourceforge.pmd.lang.ast.Node;
-import net.sourceforge.pmd.lang.ast.xpath.DefaultASTXPathHandler;
import net.sourceforge.pmd.lang.rule.RuleViolationFactory;
import net.sourceforge.pmd.lang.vm.ast.AbstractVmNode;
import net.sourceforge.pmd.lang.vm.rule.VmRuleViolationFactory;
@@ -23,11 +21,6 @@ import net.sourceforge.pmd.lang.vm.rule.VmRuleViolationFactory;
*/
public class VmHandler extends AbstractLanguageVersionHandler {
- @Override
- public XPathHandler getXPathHandler() {
- return new DefaultASTXPathHandler();
- }
-
@Override
public RuleViolationFactory getRuleViolationFactory() {
return VmRuleViolationFactory.INSTANCE;
diff --git a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/XmlHandler.java b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/XmlHandler.java
index 921f73f414..0133a20cd0 100644
--- a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/XmlHandler.java
+++ b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/XmlHandler.java
@@ -10,9 +10,7 @@ import net.sourceforge.pmd.lang.AbstractLanguageVersionHandler;
import net.sourceforge.pmd.lang.Parser;
import net.sourceforge.pmd.lang.ParserOptions;
import net.sourceforge.pmd.lang.VisitorStarter;
-import net.sourceforge.pmd.lang.XPathHandler;
import net.sourceforge.pmd.lang.ast.Node;
-import net.sourceforge.pmd.lang.ast.xpath.DefaultASTXPathHandler;
import net.sourceforge.pmd.lang.rule.RuleViolationFactory;
import net.sourceforge.pmd.lang.xml.ast.DumpFacade;
import net.sourceforge.pmd.lang.xml.ast.XmlNode;
@@ -23,11 +21,6 @@ import net.sourceforge.pmd.lang.xml.rule.XmlRuleViolationFactory;
*/
public class XmlHandler extends AbstractLanguageVersionHandler {
- @Override
- public XPathHandler getXPathHandler() {
- return new DefaultASTXPathHandler();
- }
-
@Override
public RuleViolationFactory getRuleViolationFactory() {
return XmlRuleViolationFactory.INSTANCE;
diff --git a/pom.xml b/pom.xml
index 1dfe7db0e1..3252464717 100644
--- a/pom.xml
+++ b/pom.xml
@@ -256,6 +256,9 @@
once
alphabetical
+
+ ${project.basedir}/src/test/resources
+