Merge branch '7.0.x' into clem.pmd7-remove-pmd-eol
This commit is contained in:
137 files changed
+3264
-1555
No files matched your search
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,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 %}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
+29
-25
@@ -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++;
|
||||
|
||||
+57
-2
@@ -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;
|
||||
}
|
||||
}
|
||||
+1
@@ -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
|
||||
|
||||
|
||||
+3
-2
@@ -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" />
|
||||
|
||||
+64
-2
@@ -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);
|
||||
|
||||
+26
@@ -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")));
|
||||
|
||||
+10
@@ -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
Reference in new issue
Block a user