Merge branch '7.0.x' into clem.pmd7-remove-pmd-eol

This commit is contained in:
Clément Fournier committed 2023-03-14 21:54:45 +01:00
commit 5a662ecb3e
137 files changed
+3264 -1555

No files matched your search

+1
View File
@@ -10,6 +10,7 @@ on:
tags:
- '**'
pull_request:
merge_group:
schedule:
# build it monthly: At 04:00 on day-of-month 1.
- cron: '0 4 1 * *'
+1 -1
View File
@@ -2,7 +2,7 @@ repository: pmd/pmd
pmd:
version: 7.0.0-SNAPSHOT
previous_version: 6.54.0
previous_version: 6.55.0
date: ??-?????-2023
release_type: major
+18 -18
View File
@@ -160,24 +160,6 @@ entries:
- title: Security
output: web, pdf
url: /pmd_rules_apex_security.html
- title: null
output: web, pdf
subfolders:
- title: Ecmascript Rules
output: web, pdf
subfolderitems:
- title: Index
output: web, pdf
url: /pmd_rules_ecmascript.html
- title: Best Practices
output: web, pdf
url: /pmd_rules_ecmascript_bestpractices.html
- title: Code Style
output: web, pdf
url: /pmd_rules_ecmascript_codestyle.html
- title: Error Prone
output: web, pdf
url: /pmd_rules_ecmascript_errorprone.html
- title: null
output: web, pdf
subfolders:
@@ -247,6 +229,24 @@ entries:
- title: Security
output: web, pdf
url: /pmd_rules_jsp_security.html
- title: null
output: web, pdf
subfolders:
- title: JavaScript Rules
output: web, pdf
subfolderitems:
- title: Index
output: web, pdf
url: /pmd_rules_ecmascript.html
- title: Best Practices
output: web, pdf
url: /pmd_rules_ecmascript_bestpractices.html
- title: Code Style
output: web, pdf
url: /pmd_rules_ecmascript_codestyle.html
- title: Error Prone
output: web, pdf
url: /pmd_rules_ecmascript_errorprone.html
- title: null
output: web, pdf
subfolders:
+4 -3
View File
@@ -153,9 +153,9 @@ See {% jdoc core::lang.ast.NodeStream %} for more details.
#### JavaScript support
The JS specific parser options have been removed. The parser now always retains comments and uses version ES6.
The language module registers only one version (as before), now correctly with version "ES6" instead of "3".
Since there is only one version available for JavaScript there is actually no need to selected a specific version.
The default version is always ES6.
The language module registers a couple of different versions. The latest version, which supports ES6 and also some
new constructs (see [Rhino](https://github.com/mozilla/rhino)]), is the default. This should be fine for most
use cases.
#### New Rules
@@ -254,6 +254,7 @@ The following previously deprecated rules have been finally removed:
* [#3782](https://github.com/pmd/pmd/issues/3782): \[core] Language lifecycle
* [#3902](https://github.com/pmd/pmd/issues/3902): \[core] Violation decorators
* [#4035](https://github.com/pmd/pmd/issues/4035): \[core] ConcurrentModificationException in DefaultRuleViolationFactory
* [#4120](https://github.com/pmd/pmd/issues/4120): \[core] Explicitly name all language versions
* cli
* [#3828](https://github.com/pmd/pmd/issues/3828): \[core] Progress reporting
* [#4079](https://github.com/pmd/pmd/issues/4079): \[cli] Split off CLI implementation into a pmd-cli submodule
+12
View File
@@ -246,6 +246,18 @@ the breaking API changes will be performed in 7.0.0.
an API is tagged as `@Deprecated` or not in the latest minor release. During the development of 7.0.0,
we may decide to remove some APIs that were not tagged as deprecated, though we'll try to avoid it." %}
#### 6.55.0
##### Go
* The LanguageModule of Go, that only supports CPD execution, has been deprecated. This language
is not fully supported by PMD, so having a language module does not make sense. The functionality of CPD is
not affected by this change. The following class has been deprecated and will be removed with PMD 7.0.0:
* {% jdoc go::lang.go.GoLanguageModule %}
##### Java
* Support for Java 18 preview language features have been removed. The version "18-preview" is no longer available.
* The experimental class `net.sourceforge.pmd.lang.java.ast.ASTGuardedPattern` has been removed.
#### 6.54.0
##### PMD CLI
+5 -4
View File
@@ -15,9 +15,10 @@ Usually the latest non-preview Java Version is the default version.
| Java Version | Alias | Supported by PMD since |
|--------------|-------|------------------------|
| 20-preview | | 6.55.0 |
| 20 (default) | | 6.55.0 |
| 19-preview | | 6.48.0 |
| 19 (default) | | 6.48.0 |
| 18-preview | | 6.44.0 |
| 19 | | 6.48.0 |
| 18 | | 6.44.0 |
| 17 | | 6.37.0 |
| 16 | | 6.32.0 |
@@ -38,10 +39,10 @@ Usually the latest non-preview Java Version is the default version.
## Using Java preview features
In order to analyze a project with PMD that uses preview language features, you'll need to enable
it via the environment variable `PMD_JAVA_OPTS` and select the new language version, e.g. `19-preview`:
it via the environment variable `PMD_JAVA_OPTS` and select the new language version, e.g. `20-preview`:
export PMD_JAVA_OPTS=--enable-preview
pmd check --use-version java-19-preview ...
pmd check --use-version java-20-preview ...
Note: we only support preview language features for the latest two java versions.
+23 -2
View File
@@ -194,6 +194,21 @@ Just set the environment variable `PMD_JAVA_OPTS` before executing PMD, e.g.
windows="set \"PMD_JAVA_OPTS=--enable-preview\"
pmd.bat check -d src\main\java\ -f text -R rulesets/java/quickstart.xml" %}
## Additional runtime classpath
If you develop custom rules and package them as a jar file, you need to add it to PMD's runtime classpath.
You can either copy the jar file into the `lib/` subfolder alongside the other jar files, that are in PMD's
standard distribution.
Or you can set the environment variable `CLASSPATH` before starting PMD, e.g.
{% include cli_example.html
id="preview_classpath"
linux="export CLASSPATH=custom-rule-example.jar
pmd check -d ../../../src/main/java/ -f text -R myrule.xml"
windows="set CLASSPATH=custom-rule-example.jar
pmd.bat check -d ..\..\..\src\main\java\ -f text -R myrule.xml" %}
## Exit Status
Please note that if PMD detects any violations, it will exit with status 4 (since 5.3).
@@ -224,7 +239,14 @@ non-preview version. If you want to use an older version, so that e.g. rules tha
that are not available yet won't be executed, you need to specify a specific version via the `--use-version`
parameter.
These parameters are irrelevant for languages that don't support different versions.
The selected language version can also influence which rules are applied. Some rules might be relevant for
just a specific version of the language. Such rules are marked with either `minimumLanguageVersion` or
`maximumLanguageVersion` or both. Most rules apply for all language versions.
These parameters are most of the time irrelevant, if the rules apply for all versions.
The available versions depend on the language. You can get a list of the currently supported language versions
via the CLI option `--help`.
Example:
@@ -245,7 +267,6 @@ Example:
* [plsql](pmd_rules_plsql.html)
* [pom](pmd_rules_pom.html) (Maven POM)
* [scala](pmd_rules_scala.html)
* Supported Versions: 2.10, 2.11, 2.12, 2.13 (default)
* [swift](pmd_rules_swift.html)
* [vf](pmd_rules_vf.html) (Salesforce VisualForce)
* [vm](pmd_rules_vm.html) (Apache Velocity)
+4 -40
View File
@@ -211,48 +211,12 @@ sense with Java 1.7 and later. If your project uses Java 1.5, then you should co
accordingly and this rule won't be executed.
The specific version of a language to be used is selected via the `sourceLanguage`
nested element. Possible values are:
nested element. Example:
<sourceLanguage name="apex" version="48"/>
<sourceLanguage name="ecmascript" version="3"/>
<sourceLanguage name="java" version="1.3"/>
<sourceLanguage name="java" version="1.4"/>
<sourceLanguage name="java" version="1.5"/>
<sourceLanguage name="java" version="5"/> <!-- alias for 1.5 -->
<sourceLanguage name="java" version="1.6"/>
<sourceLanguage name="java" version="6"/> <!-- alias for 1.6 -->
<sourceLanguage name="java" version="1.7"/>
<sourceLanguage name="java" version="7"/> <!-- alias for 1.7 -->
<sourceLanguage name="java" version="1.8"/>
<sourceLanguage name="java" version="8"/> <!-- alias for 1.8 -->
<sourceLanguage name="java" version="9"/>
<sourceLanguage name="java" version="1.9"/> <!-- alias for 9 -->
<sourceLanguage name="java" version="10"/>
<sourceLanguage name="java" version="1.10"/> <!-- alias for 10 -->
<sourceLanguage name="java" version="11"/>
<sourceLanguage name="java" version="12"/>
<sourceLanguage name="java" version="13"/>
<sourceLanguage name="java" version="14"/>
<sourceLanguage name="java" version="15"/>
<sourceLanguage name="java" version="16"/>
<sourceLanguage name="java" version="17"/>
<sourceLanguage name="java" version="18"/>
<sourceLanguage name="java" version="18-preview"/>
<sourceLanguage name="java" version="19"/> <!-- this is the default -->
<sourceLanguage name="java" version="19-preview"/>
<sourceLanguage name="jsp" version=""/>
<sourceLanguage name="modelica" version=""/>
<sourceLanguage name="pom" version=""/>
<sourceLanguage name="plsql" version=""/>
<sourceLanguage name="scala" version="2.10"/>
<sourceLanguage name="scala" version="2.11"/>
<sourceLanguage name="scala" version="2.12"/>
<sourceLanguage name="scala" version="2.13"/> <!-- this is the default -->
<sourceLanguage name="vf" version=""/>
<sourceLanguage name="vm" version=""/>
<sourceLanguage name="wsdl" version=""/>
<sourceLanguage name="xml" version=""/>
<sourceLanguage name="xsl" version=""/>
The available versions depend on the language. You can get a list of the currently supported language versions
via the CLI option `--help`.
### Postprocessing the report file with XSLT
-19
View File
@@ -19,30 +19,11 @@ This is a {{ site.pmd.release_type }} release.
### New and noteworthy
#### T-SQL support
Thanks to the contribution from [Paul Guyot](https://github.com/pguyot) PMD now has CPD support
for T-SQL (Transact-SQL).
Being based on a proper Antlr grammar, CPD can:
* ignore comments
* honor [comment-based suppressions](pmd_userdocs_cpd.html#suppression)
### Fixed Issues
* java-errorprone
* [#4393](https://github.com/pmd/pmd/issues/4393): \[java] MissingStaticMethodInNonInstantiatableClass false-positive for Lombok's @UtilityClass for classes with non-private fields
### API Changes
* The LanguageModule of Go, that only supports CPD execution, has been deprecated. This language
is not fully supported by PMD, so having a language module does not make sense. The functionality of CPD is
not affected by this change. The following class has been deprecated and will be removed with PMD 7.0.0:
* {% jdoc go::lang.go.GoLanguageModule %}
### External Contributions
* [#4384](https://github.com/pmd/pmd/pull/4384): \[swift] Add more swift 5.x support (#unavailable mainly) - [Richard B.](https://github.com/kenji21) (@kenji21)
* [#4390](https://github.com/pmd/pmd/pull/4390): Add support for T-SQL using Antlr4 lexer - [Paul Guyot](https://github.com/pguyot) (@pguyot)
* [#4392](https://github.com/pmd/pmd/pull/4392): \[java] Fix #4393 MissingStaticMethodInNonInstantiatableClass: Fix false-positive for field-only class - [Dawid Ciok](https://github.com/dawiddc) (@dawiddc)
{% endtocmaker %}
+84
View File
@@ -5,6 +5,90 @@ permalink: pmd_release_notes_old.html
Previous versions of PMD can be downloaded here: https://github.com/pmd/pmd/releases
## 25-February-2023 - 6.55.0
The PMD team is pleased to announce PMD 6.55.0.
This is a minor release.
### Table Of Contents
* [New and noteworthy](#new-and-noteworthy)
* [PMD 7 Development](#pmd-7-development)
* [Java 20 Support](#java-20-support)
* [T-SQL support](#t-sql-support)
* [Fixed Issues](#fixed-issues)
* [API Changes](#api-changes)
* [Go](#go)
* [Java](#java)
* [External Contributions](#external-contributions)
* [Stats](#stats)
### New and noteworthy
#### PMD 7 Development
This release is the last planned release of PMD 6. The first version 6.0.0 was released in December 2017.
Over the course of more than 5 years we published almost every month a new minor version of PMD 6
with new features and improvements.
Already in November 2018 we started in parallel the development of the next major version 7.0.0,
and we are now in the process of finalizing the scope of the major version. We want to release a couple of
release candidates before publishing the final version 7.0.0.
We plan to release 7.0.0-rc1 soon. You can see the progress in [PMD 7 Tracking Issue #3898](https://github.com/pmd/pmd/issues/3898).
#### Java 20 Support
This release of PMD brings support for Java 20. There are no new standard language features.
PMD supports [JEP 433: Pattern Matching for switch (Fourth Preview)](https://openjdk.org/jeps/433) and
[JEP 432: Record Patterns (Second Preview)](https://openjdk.org/jeps/432) as preview language features.
In order to analyze a project with PMD that uses these language features,
you'll need to enable it via the environment variable `PMD_JAVA_OPTS` and select the new language
version `20-preview`:
export PMD_JAVA_OPTS=--enable-preview
./run.sh pmd --use-version java-20-preview ...
#### T-SQL support
Thanks to the contribution from [Paul Guyot](https://github.com/pguyot) PMD now has CPD support
for T-SQL (Transact-SQL).
Being based on a proper Antlr grammar, CPD can:
* ignore comments
* honor [comment-based suppressions](pmd_userdocs_cpd.html#suppression)
### Fixed Issues
* core
* [#4395](https://github.com/pmd/pmd/issues/4395): \[core] Support environment variable CLASSPATH with pmd.bat under Windows
* java
* [#4333](https://github.com/pmd/pmd/issues/4333): \[java] Support JDK 20
* java-errorprone
* [#4393](https://github.com/pmd/pmd/issues/4393): \[java] MissingStaticMethodInNonInstantiatableClass false-positive for Lombok's @UtilityClass for classes with non-private fields
### API Changes
#### Go
* The LanguageModule of Go, that only supports CPD execution, has been deprecated. This language
is not fully supported by PMD, so having a language module does not make sense. The functionality of CPD is
not affected by this change. The following class has been deprecated and will be removed with PMD 7.0.0:
* <a href="https://docs.pmd-code.org/apidocs/pmd-go/6.55.0/net/sourceforge/pmd/lang/go/GoLanguageModule.html#"><code>GoLanguageModule</code></a>
#### Java
* Support for Java 18 preview language features have been removed. The version "18-preview" is no longer available.
* The experimental class `net.sourceforge.pmd.lang.java.ast.ASTGuardedPattern` has been removed.
### External Contributions
* [#4384](https://github.com/pmd/pmd/pull/4384): \[swift] Add more swift 5.x support (#unavailable mainly) - [Richard B.](https://github.com/kenji21) (@kenji21)
* [#4390](https://github.com/pmd/pmd/pull/4390): Add support for T-SQL using Antlr4 lexer - [Paul Guyot](https://github.com/pguyot) (@pguyot)
* [#4392](https://github.com/pmd/pmd/pull/4392): \[java] Fix #4393 MissingStaticMethodInNonInstantiatableClass: Fix false-positive for field-only class - [Dawid Ciok](https://github.com/dawiddc) (@dawiddc)
### Stats
* 40 commits
* 11 closed tickets & PRs
* Days since last release: 28
## 28-January-2023 - 6.54.0
The PMD team is pleased to announce PMD 6.54.0.
@@ -6,6 +6,8 @@ package net.sourceforge.pmd.cpd;
import java.util.Properties;
import net.sourceforge.pmd.lang.apex.ApexLanguageModule;
public class ApexLanguage extends AbstractLanguage {
public ApexLanguage() {
@@ -13,7 +15,7 @@ public class ApexLanguage extends AbstractLanguage {
}
public ApexLanguage(Properties properties) {
super("Apex", "apex", new ApexTokenizer(), ".cls");
super(ApexLanguageModule.NAME, ApexLanguageModule.TERSE_NAME, new ApexTokenizer(), ApexLanguageModule.EXTENSIONS);
setProperties(properties);
}
@@ -4,22 +4,33 @@
package net.sourceforge.pmd.lang.apex;
import static net.sourceforge.pmd.util.CollectionUtil.listOf;
import java.util.List;
import net.sourceforge.pmd.annotation.InternalApi;
import net.sourceforge.pmd.lang.Language;
import net.sourceforge.pmd.lang.LanguageModuleBase;
import net.sourceforge.pmd.lang.LanguageProcessor;
import net.sourceforge.pmd.lang.LanguagePropertyBundle;
import net.sourceforge.pmd.lang.LanguageRegistry;
import apex.jorje.services.Version;
public class ApexLanguageModule extends LanguageModuleBase {
public static final String NAME = "Apex";
public static final String TERSE_NAME = "apex";
@InternalApi
public static final List<String> EXTENSIONS = listOf("cls", "trigger");
public ApexLanguageModule() {
super(LanguageMetadata.withId(TERSE_NAME).name(NAME).extensions("cls", "trigger")
.addDefaultVersion(String.valueOf((int) Version.CURRENT.getExternal())));
super(LanguageMetadata.withId(TERSE_NAME).name(NAME).extensions(EXTENSIONS)
.addVersion("52")
.addVersion("53")
.addVersion("54")
.addVersion("55")
.addVersion("56")
.addDefaultVersion("57"));
}
@Override
@@ -12,7 +12,7 @@ import net.sourceforge.pmd.AbstractLanguageVersionTest;
class LanguageVersionTest extends AbstractLanguageVersionTest {
static Collection<TestDescriptor> data() {
return Arrays.asList(new TestDescriptor(ApexLanguageModule.NAME, ApexLanguageModule.TERSE_NAME, "35",
getLanguage("Apex").getVersion("35")));
return Arrays.asList(new TestDescriptor(ApexLanguageModule.NAME, ApexLanguageModule.TERSE_NAME, "57",
ApexLanguageModule.getInstance().getDefaultVersion()));
}
}
-23
View File
@@ -13,24 +13,6 @@
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<id>add-javacc-generated-sources</id>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>${project.build.directory}/generated-sources/javacc</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
@@ -54,11 +36,6 @@
</plugins>
</build>
<dependencies>
<dependency>
<groupId>net.java.dev.javacc</groupId>
<artifactId>javacc</artifactId>
<scope>provided</scope> <!-- only needed for generating the parser via ant -->
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
@@ -66,25 +66,36 @@ public abstract class AbstractAnalysisCache implements AnalysisCache {
@Override
public boolean isUpToDate(final TextDocument document) {
try (TimedOperation ignored = TimeTracker.startOperation(TimedOperationCategory.ANALYSIS_CACHE, "up-to-date check")) {
// There is a new file being analyzed, prepare entry in updated cache
final AnalysisResult updatedResult = new AnalysisResult(document.getCheckSum(), new ArrayList<>());
updatedResultsCache.put(document.getPathId(), updatedResult);
// Now check the old cache
final AnalysisResult analysisResult = fileResultsCache.get(document.getPathId());
final AnalysisResult cachedResult = fileResultsCache.get(document.getPathId());
final AnalysisResult updatedResult;
// is this a known file? has it changed?
final boolean result = analysisResult != null
&& analysisResult.getFileChecksum() == updatedResult.getFileChecksum();
final boolean upToDate = cachedResult != null
&& cachedResult.getFileChecksum() == document.getCheckSum();
if (result) {
LOG.debug("Incremental Analysis cache HIT");
if (upToDate) {
LOG.trace("Incremental Analysis cache HIT");
/*
* Update cached violation "filename" to match the appropriate text document,
* so we can honor relativized paths for the current run
*/
final String displayName = document.getDisplayName();
cachedResult.getViolations().forEach(v -> ((CachedRuleViolation) v).setFileDisplayName(displayName));
// copy results over
updatedResult = cachedResult;
} else {
LOG.debug("Incremental Analysis cache MISS - {}",
analysisResult != null ? "file changed" : "no previous result found");
LOG.trace("Incremental Analysis cache MISS - {}",
cachedResult != null ? "file changed" : "no previous result found");
// New file being analyzed, create new empty entry
updatedResult = new AnalysisResult(document.getCheckSum(), new ArrayList<>());
}
return result;
updatedResultsCache.put(document.getPathId(), updatedResult);
return upToDate;
}
}
@@ -119,7 +130,7 @@ public abstract class AbstractAnalysisCache implements AnalysisCache {
boolean cacheIsValid = cacheExists();
if (cacheIsValid && ruleSets.getChecksum() != rulesetChecksum) {
LOG.info("Analysis cache invalidated, rulesets changed.");
LOG.debug("Analysis cache invalidated, rulesets changed.");
cacheIsValid = false;
}
@@ -131,7 +142,7 @@ public abstract class AbstractAnalysisCache implements AnalysisCache {
if (cacheIsValid && currentAuxClassPathChecksum != auxClassPathChecksum) {
// TODO some rules don't need that (in fact, some languages)
LOG.info("Analysis cache invalidated, auxclasspath changed.");
LOG.debug("Analysis cache invalidated, auxclasspath changed.");
cacheIsValid = false;
}
} else {
@@ -140,7 +151,7 @@ public abstract class AbstractAnalysisCache implements AnalysisCache {
final long currentExecutionClassPathChecksum = FINGERPRINTER.fingerprint(getClassPathEntries());
if (cacheIsValid && currentExecutionClassPathChecksum != executionClassPathChecksum) {
LOG.info("Analysis cache invalidated, execution classpath changed.");
LOG.debug("Analysis cache invalidated, execution classpath changed.");
cacheIsValid = false;
}
@@ -211,19 +222,12 @@ public abstract class AbstractAnalysisCache implements AnalysisCache {
@Override
public FileAnalysisListener startFileAnalysis(TextDocument file) {
String fileName = file.getPathId();
AnalysisResult analysisResult = updatedResultsCache.get(fileName);
if (analysisResult == null) {
analysisResult = new AnalysisResult(file.getCheckSum());
}
final AnalysisResult nonNullAnalysisResult = analysisResult;
final String fileName = file.getPathId();
return new FileAnalysisListener() {
@Override
public void onRuleViolation(RuleViolation violation) {
synchronized (nonNullAnalysisResult) {
nonNullAnalysisResult.addViolation(violation);
}
updatedResultsCache.get(fileName).addViolation(violation);
}
@Override
@@ -33,25 +33,31 @@ public final class CachedRuleViolation implements RuleViolation {
private final CachedRuleMapper mapper;
private final String description;
private final FileLocation location;
private final String ruleClassName;
private final String ruleName;
private final String ruleTargetLanguage;
private final Map<String, String> additionalInfo;
private FileLocation location;
private CachedRuleViolation(final CachedRuleMapper mapper, final String description,
final String fileName, final String ruleClassName, final String ruleName,
final String filePathId, final String ruleClassName, final String ruleName,
final String ruleTargetLanguage, final int beginLine, final int beginColumn,
final int endLine, final int endColumn,
final Map<String, String> additionalInfo) {
this.mapper = mapper;
this.description = description;
this.location = FileLocation.range(fileName, TextRange2d.range2d(beginLine, beginColumn, endLine, endColumn));
this.location = FileLocation.range(filePathId, TextRange2d.range2d(beginLine, beginColumn, endLine, endColumn));
this.ruleClassName = ruleClassName;
this.ruleName = ruleName;
this.ruleTargetLanguage = ruleTargetLanguage;
this.additionalInfo = additionalInfo;
}
void setFileDisplayName(String displayName) {
this.location = FileLocation.range(displayName,
TextRange2d.range2d(getBeginLine(), getBeginColumn(), getEndLine(), getEndColumn()));
}
@Override
public Rule getRule() {
@@ -78,13 +84,13 @@ public final class CachedRuleViolation implements RuleViolation {
* Helper method to load a {@link CachedRuleViolation} from an input stream.
*
* @param stream The stream from which to load the violation.
* @param fileName The name of the file on which this rule was reported.
* @param filePathId The name of the file on which this rule was reported.
* @param mapper The mapper to be used to obtain rule instances from the active rulesets.
* @return The loaded rule violation.
* @throws IOException
*/
/* package */ static CachedRuleViolation loadFromStream(final DataInputStream stream,
final String fileName, final CachedRuleMapper mapper) throws IOException {
final String filePathId, final CachedRuleMapper mapper) throws IOException {
final String description = stream.readUTF();
final String ruleClassName = stream.readUTF();
final String ruleName = stream.readUTF();
@@ -94,7 +100,7 @@ public final class CachedRuleViolation implements RuleViolation {
final int endLine = stream.readInt();
final int endColumn = stream.readInt();
final Map<String, String> additionalInfo = readAdditionalInfo(stream);
return new CachedRuleViolation(mapper, description, fileName, ruleClassName, ruleName, ruleTargetLanguage,
return new CachedRuleViolation(mapper, description, filePathId, ruleClassName, ruleName, ruleTargetLanguage,
beginLine, beginColumn, endLine, endColumn, additionalInfo);
}
@@ -129,7 +135,7 @@ public final class CachedRuleViolation implements RuleViolation {
FileLocation location = violation.getLocation();
stream.writeInt(location.getStartPos().getLine());
stream.writeInt(location.getStartPos().getColumn());
stream.writeInt(location.getEndPos().getColumn());
stream.writeInt(location.getEndPos().getLine());
stream.writeInt(location.getEndPos().getColumn());
Map<String, String> additionalInfo = violation.getAdditionalInfo();
stream.writeInt(additionalInfo.size());
@@ -74,21 +74,21 @@ public class FileAnalysisCache extends AbstractAnalysisCache {
// Cached results
while (inputStream.available() > 0) {
final String fileName = inputStream.readUTF();
final String filePathId = inputStream.readUTF();
final long checksum = inputStream.readLong();
final int countViolations = inputStream.readInt();
final List<RuleViolation> violations = new ArrayList<>(countViolations);
for (int i = 0; i < countViolations; i++) {
violations.add(CachedRuleViolation.loadFromStream(inputStream, fileName, ruleMapper));
violations.add(CachedRuleViolation.loadFromStream(inputStream, filePathId, ruleMapper));
}
fileResultsCache.put(fileName, new AnalysisResult(checksum, violations));
fileResultsCache.put(filePathId, new AnalysisResult(checksum, violations));
}
LOG.info("Analysis cache loaded");
LOG.debug("Analysis cache loaded from {}", cacheFile);
} else {
LOG.info("Analysis cache invalidated, PMD version changed.");
LOG.debug("Analysis cache invalidated, PMD version changed.");
}
} catch (final EOFException e) {
LOG.warn("Cache file {} is malformed, will not be used for current analysis", cacheFile.getPath());
@@ -132,7 +132,7 @@ public class FileAnalysisCache extends AbstractAnalysisCache {
for (final Map.Entry<String, AnalysisResult> resultEntry : updatedResultsCache.entrySet()) {
final List<RuleViolation> violations = resultEntry.getValue().getViolations();
outputStream.writeUTF(resultEntry.getKey()); // the full filename
outputStream.writeUTF(resultEntry.getKey()); // the path id
outputStream.writeLong(resultEntry.getValue().getFileChecksum());
outputStream.writeInt(violations.size());
@@ -141,9 +141,9 @@ public class FileAnalysisCache extends AbstractAnalysisCache {
}
}
if (cacheFileShouldBeCreated) {
LOG.info("Analysis cache created");
LOG.debug("Analysis cache created");
} else {
LOG.info("Analysis cache updated");
LOG.debug("Analysis cache updated");
}
} catch (final IOException e) {
LOG.error("Could not persist analysis cache to file: {}", e.getMessage());
@@ -11,6 +11,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import net.sourceforge.pmd.internal.util.PredicateUtil;
@@ -22,11 +23,23 @@ public abstract class AbstractLanguage implements Language {
private final List<String> extensions;
public AbstractLanguage(String name, String terseName, Tokenizer tokenizer, String... extensions) {
this(name, terseName, tokenizer, Arrays.asList(extensions));
}
protected AbstractLanguage(String name, String terseName, Tokenizer tokenizer, List<String> extensions) {
this.name = name;
this.terseName = terseName;
this.tokenizer = tokenizer;
this.fileFilter = PredicateUtil.toNormalizedFileFilter(PredicateUtil.getFileExtensionFilter(extensions).or(it -> Files.isDirectory(Paths.get(it))));
this.extensions = Arrays.asList(extensions);
List<String> extensionsWithDot = extensions.stream().map(e -> {
if (e.length() > 0 && e.charAt(0) != '.') {
return "." + e;
}
return e;
}).collect(Collectors.toList());
this.fileFilter = PredicateUtil.toNormalizedFileFilter(
PredicateUtil.getFileExtensionFilter(extensionsWithDot.toArray(new String[0]))
.or(it -> Files.isDirectory(Paths.get(it))));
this.extensions = extensionsWithDot;
}
@Override
@@ -8,6 +8,7 @@ import static net.sourceforge.pmd.util.CollectionUtil.setOf;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -21,7 +22,6 @@ import org.apache.commons.lang3.StringUtils;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import net.sourceforge.pmd.lang.LanguageModuleBase.LanguageMetadata.LangVersionMetadata;
import net.sourceforge.pmd.util.AssertionUtil;
import net.sourceforge.pmd.util.StringUtil;
@@ -44,7 +44,7 @@ public abstract class LanguageModuleBase implements Language {
* Construct a module instance using the given metadata. The metadata must
* be properly constructed.
*
* @throws IllegalStateException If the metadata is invalid (eg missing extensions or name)
* @throws IllegalStateException If the metadata is invalid (eg missing extensions or name or no versions)
*/
protected LanguageModuleBase(LanguageMetadata metadata) {
this.meta = metadata;
@@ -55,10 +55,7 @@ public abstract class LanguageModuleBase implements Language {
LanguageVersion defaultVersion = null;
if (metadata.versionMetadata.isEmpty()) {
// Many languages have just one version, which is implicitly
// created here.
// TODO #4120 remove this branch, before 7.0.0
metadata.versionMetadata.add(new LangVersionMetadata("", Collections.emptyList(), true));
throw new IllegalStateException("No versions for '" + getId() + "'");
}
int i = 0;
@@ -269,7 +266,7 @@ public abstract class LanguageModuleBase implements Language {
/**
* Record the {@linkplain Language#getExtensions() extensions}
* assigned to the language. Parameters should not start with a period
* assigned to the language. Extensions should not start with a period
* {@code .}.
*
* @param e1 First extensions
@@ -283,6 +280,25 @@ public abstract class LanguageModuleBase implements Language {
return this;
}
/**
* Record the {@linkplain Language#getExtensions() extensions}
* assigned to the language. Extensions should not start with a period
* {@code .}. At least one extension must be provided.
*
* @param extensions the extensions
*
* @throws NullPointerException If any extension is null
* @throws IllegalArgumentException If no extensions are provided
*/
public LanguageMetadata extensions(Collection<String> extensions) {
this.extensions = new ArrayList<>(new HashSet<>(extensions));
AssertionUtil.requireContainsNoNullValue("extensions", this.extensions);
if (this.extensions.isEmpty()) {
throw new IllegalArgumentException("At least one extension is required.");
}
return this;
}
/**
* Add a new version by its name.
*
@@ -290,7 +306,7 @@ public abstract class LanguageModuleBase implements Language {
* @param aliases Additional names that are mapped to this version. Must contain no spaces.
*
* @throws NullPointerException If any parameter is null
* @throws IllegalArgumentException If the name or aliases contain spaces
* @throws IllegalArgumentException If the name or aliases are empty or contain spaces
*/
public LanguageMetadata addVersion(String name, String... aliases) {
@@ -305,7 +321,7 @@ public abstract class LanguageModuleBase implements Language {
* @param aliases Additional names that are mapped to this version. Must contain no spaces.
*
* @throws NullPointerException If any parameter is null
* @throws IllegalArgumentException If the name or aliases contain spaces
* @throws IllegalArgumentException If the name or aliases are empty or contain spaces
*/
public LanguageMetadata addDefaultVersion(String name, String... aliases) {
versionMetadata.add(new LangVersionMetadata(name, Arrays.asList(aliases), true));
@@ -351,7 +367,7 @@ public abstract class LanguageModuleBase implements Language {
}
private static void checkVersionName(String name) {
if (SPACE_PAT.matcher(name).find()) { // TODO #4120 also check that the name is non-empty
if (StringUtils.isBlank(name) || SPACE_PAT.matcher(name).find()) {
throw new IllegalArgumentException("Invalid version name: " + StringUtil.inSingleQuotes(name));
}
}
@@ -31,7 +31,9 @@ public final class PlainTextLanguage extends SimpleLanguageModuleBase {
static final String TERSE_NAME = "text";
private PlainTextLanguage() {
super(LanguageMetadata.withId(TERSE_NAME).name("Plain text").extensions("plain-text-file-goo-extension"),
super(LanguageMetadata.withId(TERSE_NAME).name("Plain text")
.extensions("plain-text-file-goo-extension")
.addDefaultVersion("default"),
new TextLvh());
}
@@ -45,7 +45,8 @@ class NioTextFile extends BaseCloseable implements TextFile {
this.charset = charset;
this.languageVersion = languageVersion;
// using the URI here, that handles files inside zip archives automatically (schema "jar:file:...!/path/inside/zip")
this.pathId = path.toUri().toString();
// normalization ensures cannonical paths
this.pathId = path.normalize().toUri().toString();
}
@Override
@@ -19,6 +19,7 @@ import net.sourceforge.pmd.benchmark.TimeTracker;
import net.sourceforge.pmd.benchmark.TimedOperation;
import net.sourceforge.pmd.benchmark.TimedOperationCategory;
import net.sourceforge.pmd.internal.SystemProps;
import net.sourceforge.pmd.lang.LanguageVersion;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.ast.RootNode;
import net.sourceforge.pmd.reporting.FileAnalysisListener;
@@ -37,6 +38,7 @@ public class RuleApplicator {
// to eg type resolution.
private final TreeIndex idx;
private LanguageVersion currentLangVer;
public RuleApplicator(TreeIndex index) {
this.idx = index;
@@ -46,6 +48,7 @@ public class RuleApplicator {
public void index(RootNode root) {
idx.reset();
indexTree(root, idx);
currentLangVer = root.getLanguageVersion();
}
public void apply(Collection<? extends Rule> rules, FileAnalysisListener listener) {
@@ -54,6 +57,10 @@ public class RuleApplicator {
private void applyOnIndex(TreeIndex idx, Collection<? extends Rule> rules, FileAnalysisListener listener) {
for (Rule rule : rules) {
if (!RuleSet.applies(rule, currentLangVer)) {
continue; // No point in even trying to apply the rule
}
RuleContext ctx = RuleContext.create(listener, rule);
rule.start(ctx);
try (TimedOperation rcto = TimeTracker.startOperation(TimedOperationCategory.RULE, rule.getName())) {
@@ -62,9 +69,6 @@ public class RuleApplicator {
Iterator<? extends Node> targets = rule.getTargetSelector().getVisitedNodes(idx);
while (targets.hasNext()) {
Node node = targets.next();
if (!RuleSet.applies(rule, node.getTextDocument().getLanguageVersion())) {
continue;
}
try {
nodeCounter++;
@@ -10,6 +10,7 @@ import java.util.Collections;
import net.sf.saxon.expr.AxisExpression;
import net.sf.saxon.expr.Expression;
import net.sf.saxon.expr.FilterExpression;
import net.sf.saxon.expr.LetExpression;
import net.sf.saxon.expr.RootExpression;
import net.sf.saxon.expr.SlashExpression;
import net.sf.saxon.om.AxisInfo;
@@ -93,15 +94,69 @@ final class SaxonExprTransformations {
* Splits a venn expression with the union operator into single expressions.
*
* <p>E.g. "//A | //B | //C" will result in 3 expressions "//A", "//B", and "//C".
*
* This split will skip into any top-level lets. So, for "let $a := e1 in (e2 | e3)"
* this will return the subexpression e2 and e3. To ensure the splits are actually equivalent
* you will have to call {@link #copyTopLevelLets(Expression, Expression)} on each subexpression
* to turn them back into "let $a := e1 in e2" and "let $a := e1 in e3" respectively.
*/
static Iterable<Expression> splitUnions(Expression expr) {
SplitUnions unions = new SplitUnions();
unions.visit(expr);
if (unions.getExpressions().isEmpty()) {
return Collections.singletonList(expr);
} else {
return unions.getExpressions();
}
return unions.getExpressions();
}
/**
* Wraps a given subexpression in all top-level lets from the original.
* If the subexpression matches the original, then nothing is done.
*
* @param subexpr The subexpression that has been manipulated.
* @param original The original expression from which it was obtained by calling {@link #splitUnions(Expression)}.
* @return The subexpression, wrapped in a copy of all top-level let expression from the original.
*/
static Expression copyTopLevelLets(Expression subexpr, Expression original) {
if (!(original instanceof LetExpression)) {
return subexpr;
}
// Does it need them? Or is it already the same variable under the same assignment?
if (subexpr instanceof LetExpression) {
final LetExpression letSubexpr = (LetExpression) subexpr;
final LetExpression letOriginal = (LetExpression) original;
if (letOriginal.getVariableQName().equals(letSubexpr.getVariableQName())
&& letSubexpr.getSequence().toString().equals(letOriginal.getSequence().toString())) {
return subexpr;
}
}
final SaxonExprVisitor topLevelLetCopier = new SaxonExprVisitor() {
@Override
public Expression visit(LetExpression e) {
// keep copying
if (e.getAction() instanceof LetExpression) {
return super.visit(e);
}
// Manually craft the inner-most LetExpression
Expression sequence = visit(e.getSequence());
LetExpression result = new LetExpression();
result.setAction(subexpr);
result.setSequence(sequence);
result.setVariableQName(e.getVariableQName());
result.setRequiredType(e.getRequiredType());
result.setSlotNumber(e.getLocalSlotNumber());
return result;
}
};
if (original instanceof LetExpression) {
return topLevelLetCopier.visit(original);
}
return subexpr;
}
}
@@ -226,6 +226,7 @@ public class SaxonXPathRuleQuery {
Expression modified = subexpression;
modified = SaxonExprTransformations.hoistFilters(modified);
modified = SaxonExprTransformations.reduceRoot(modified);
modified = SaxonExprTransformations.copyTopLevelLets(modified, expr);
RuleChainAnalyzer rca = new RuleChainAnalyzer(xpathEvaluator.getConfiguration());
final Expression finalExpr = rca.visit(modified); // final because of lambda
@@ -11,6 +11,7 @@ import java.util.ArrayList;
import java.util.List;
import net.sf.saxon.expr.Expression;
import net.sf.saxon.expr.LetExpression;
import net.sf.saxon.expr.VennExpression;
import net.sf.saxon.expr.parser.Token;
import net.sf.saxon.expr.sort.DocumentSorter;
@@ -40,8 +41,8 @@ class SplitUnions extends SaxonExprVisitor {
@Override
public Expression visit(Expression e) {
// only flatten toplevel unions
if (e instanceof VennExpression || e instanceof DocumentSorter) {
// only flatten top level unions - skip sorters and let around it
if (e instanceof VennExpression || e instanceof DocumentSorter || e instanceof LetExpression) {
return super.visit(e);
} else {
return e;
@@ -26,11 +26,9 @@
<!-- this file contains are parse error explicitly -->
<exclude-pattern>.*/net/sourceforge/pmd/lang/java/ast/InfiniteLoopInLookahead.java</exclude-pattern>
<!-- the following files cannot be parsed with the latest java version (19 preview) anymore
since java 18 preview grammar is different.
-->
<exclude-pattern>.*/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java18p/GuardedAndParenthesizedPatterns.java</exclude-pattern>
<exclude-pattern>.*/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java18p/RefiningPatternsInSwitch.java</exclude-pattern>
<!-- with java-20-preview there is now invalid code in java-19-preview -->
<exclude-pattern>.*/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java19p/DealingWithNull.java</exclude-pattern>
<exclude-pattern>.*/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java19p/ExhaustiveSwitch.java</exclude-pattern>
<rule ref="category/java/bestpractices.xml" />
<rule ref="category/java/codestyle.xml" />
@@ -43,6 +43,8 @@ import net.sourceforge.pmd.lang.document.TextDocument;
import net.sourceforge.pmd.lang.document.TextFile;
import net.sourceforge.pmd.lang.document.TextFileContent;
import net.sourceforge.pmd.lang.document.TextRange2d;
import net.sourceforge.pmd.lang.rule.ParametricRuleViolation;
import net.sourceforge.pmd.reporting.FileAnalysisListener;
class FileAnalysisCacheTest {
@@ -111,16 +113,19 @@ class FileAnalysisCacheTest {
void testStorePersistsFilesWithViolations() throws IOException {
final FileAnalysisCache cache = new FileAnalysisCache(newCacheFile);
cache.checkValidity(mock(RuleSets.class), mock(ClassLoader.class));
final FileAnalysisListener cacheListener = cache.startFileAnalysis(sourceFile);
cache.isUpToDate(sourceFile);
final RuleViolation rv = mock(RuleViolation.class);
when(rv.getFilename()).thenReturn(sourceFile.getDisplayName());
when(rv.getLocation()).thenReturn(FileLocation.range(sourceFile.getDisplayName(), TextRange2d.range2d(1, 2, 3, 4)));
final TextRange2d textLocation = TextRange2d.range2d(1, 2, 3, 4);
when(rv.getLocation()).thenReturn(FileLocation.range(sourceFile.getDisplayName(), textLocation));
final net.sourceforge.pmd.Rule rule = mock(net.sourceforge.pmd.Rule.class, Mockito.RETURNS_SMART_NULLS);
when(rule.getLanguage()).thenReturn(mock(Language.class));
when(rv.getRule()).thenReturn(rule);
cache.startFileAnalysis(sourceFile).onRuleViolation(rv);
cacheListener.onRuleViolation(rv);
cache.persist();
final FileAnalysisCache reloadedCache = new FileAnalysisCache(newCacheFile);
@@ -130,8 +135,65 @@ class FileAnalysisCacheTest {
final List<RuleViolation> cachedViolations = reloadedCache.getCachedViolations(sourceFile);
assertEquals(1, cachedViolations.size(), "Cached rule violations count mismatch");
final RuleViolation cachedViolation = cachedViolations.get(0);
assertEquals(sourceFile.getDisplayName(), cachedViolation.getFilename());
assertEquals(textLocation.getStartLine(), cachedViolation.getBeginLine());
assertEquals(textLocation.getStartColumn(), cachedViolation.getBeginColumn());
assertEquals(textLocation.getEndLine(), cachedViolation.getEndLine());
assertEquals(textLocation.getEndColumn(), cachedViolation.getEndColumn());
}
@Test
void testDisplayNameIsRespected() throws Exception {
// This checks that the display name of the file is respected even if
// the file is assigned a different display name across runs. The path
// id is saved into the cache file, and the cache implementation updates the
// display name of the violations to match their current display name.
final net.sourceforge.pmd.Rule rule = mock(net.sourceforge.pmd.Rule.class, Mockito.RETURNS_SMART_NULLS);
when(rule.getLanguage()).thenReturn(mock(Language.class));
final TextRange2d textLocation = TextRange2d.range2d(1, 2, 3, 4);
TextFile mockFile = mock(TextFile.class);
when(mockFile.getDisplayName()).thenReturn("display0");
when(mockFile.getPathId()).thenReturn("pathId");
when(mockFile.getLanguageVersion()).thenReturn(dummyVersion);
when(mockFile.readContents()).thenReturn(TextFileContent.fromCharSeq("abc"));
final FileAnalysisCache cache = new FileAnalysisCache(newCacheFile);
cache.checkValidity(mock(RuleSets.class), mock(ClassLoader.class));
try (TextDocument doc0 = TextDocument.create(mockFile)) {
cache.isUpToDate(doc0);
try (FileAnalysisListener listener = cache.startFileAnalysis(doc0)) {
listener.onRuleViolation(new ParametricRuleViolation(rule, FileLocation.range(doc0.getDisplayName(), textLocation), "message"));
}
} finally {
cache.persist();
}
reloadWithOneViolation(mockFile);
// now try with another display name
when(mockFile.getDisplayName()).thenReturn("display2");
reloadWithOneViolation(mockFile);
}
private void reloadWithOneViolation(TextFile mockFile) throws IOException {
final FileAnalysisCache reloadedCache = new FileAnalysisCache(newCacheFile);
reloadedCache.checkValidity(mock(RuleSets.class), mock(ClassLoader.class));
try (TextDocument doc1 = TextDocument.create(mockFile)) {
assertTrue(reloadedCache.isUpToDate(doc1),
"Cache believes unmodified file with violations is not up to date");
List<RuleViolation> cachedViolations = reloadedCache.getCachedViolations(doc1);
assertEquals(1, cachedViolations.size(), "Cached rule violations count mismatch");
final RuleViolation cachedViolation = cachedViolations.get(0);
assertEquals(mockFile.getDisplayName(), cachedViolation.getFilename());
}
}
@Test
void testCacheValidityWithNoChanges() throws IOException {
final RuleSets rs = mock(RuleSets.class);
@@ -40,6 +40,32 @@ class ZipFileFingerprinterTest extends AbstractClasspathEntryFingerprinterTest {
assertEquals(baselineFingerprint, updateFingerprint(file));
assertNotEquals(originalFileSize, file.length());
}
@Test
void zipEntryOrderDoesNotAffectFingerprint() throws IOException {
final File zipFile = tempDir.resolve("foo.jar").toFile();
final ZipEntry fooEntry = new ZipEntry("lib/Foo.class");
final ZipEntry barEntry = new ZipEntry("lib/Bar.class");
overwriteZipFileContents(zipFile, fooEntry, barEntry);
final long baselineFingerprint = getBaseLineFingerprint(zipFile);
// swap order
overwriteZipFileContents(zipFile, barEntry, fooEntry);
assertEquals(baselineFingerprint, updateFingerprint(zipFile));
}
@Test
void nonClassZipEntryDoesNotAffectFingerprint() throws IOException {
final File zipFile = tempDir.resolve("foo.jar").toFile();
final ZipEntry fooEntry = new ZipEntry("lib/Foo.class");
final ZipEntry barEntry = new ZipEntry("bar.properties");
overwriteZipFileContents(zipFile, fooEntry);
final long baselineFingerprint = getBaseLineFingerprint(zipFile);
// add a properties file to the jar
overwriteZipFileContents(zipFile, fooEntry, barEntry);
assertEquals(baselineFingerprint, updateFingerprint(zipFile));
}
@Override
protected ClasspathEntryFingerprinter newFingerPrinter() {
@@ -28,6 +28,16 @@ class LanguageModuleBaseTest {
assertInvalidId("C");
assertInvalidId("ab-c");
assertThrows(NullPointerException.class, () -> LanguageMetadata.withId(null));
Exception e = assertThrows(IllegalArgumentException.class, () -> LanguageMetadata.withId("dummy").addVersion(""),
"Empty versions should not be allowed.");
assertEquals("Invalid version name: ''", e.getMessage());
assertThrows(IllegalArgumentException.class, () -> LanguageMetadata.withId("dummy").addVersion(" "),
"Empty versions should not be allowed.");
assertThrows(IllegalArgumentException.class, () -> LanguageMetadata.withId("dummy").addVersion(null),
"Empty versions should not be allowed.");
assertThrows(IllegalArgumentException.class, () -> LanguageMetadata.withId("dummy").addVersion("1.0", ""),
"Empty versions should not be allowed.");
}
@Test
@@ -36,6 +46,13 @@ class LanguageModuleBaseTest {
assertThat(lang.getDefaultVersion(), equalTo(lang.getVersion("abc")));
}
@Test
void testMissingVersions() {
Exception e = assertThrows(IllegalStateException.class, () -> makeLanguage(LanguageMetadata.withId("dumdum").name("Name").extensions("o")),
"Languages without versions should not be allowed.");
assertEquals("No versions for 'dumdum'", e.getMessage());
}
@Test
void testNoExtensions() {
Exception ex = assertThrows(IllegalStateException.class, () -> makeLanguage(LanguageMetadata.withId("dumdum").name("Name").addVersion("abc")));
@@ -374,6 +374,16 @@ class SaxonXPathRuleQueryTest {
assertExpression(expectedSubexpression, query.nodeNameToXPaths.get("DoStatement").get(0));
}
@Test
void ruleChainVisitsWithUnionsAndLets() {
PropertyDescriptor<Boolean> boolProperty = PropertyFactory.booleanProperty("checkAll").desc("test").defaultValue(true).build();
SaxonXPathRuleQuery query = createQuery("//dummyNode[$checkAll and ClassOrInterfaceType] | //ForStatement[not($checkAll)]", boolProperty);
List<String> ruleChainVisits = query.getRuleChainVisits();
assertEquals(2, ruleChainVisits.size());
assertTrue(ruleChainVisits.contains("dummyNode"));
assertTrue(ruleChainVisits.contains("ForStatement"));
}
private static void assertExpression(String expected, Expression actual) {
assertEquals(normalizeExprDump(expected),
normalizeExprDump(actual.toString()));
Loaded 30 of 137 files, more files were not shown because too many files have changed in this diff. Show more