diff --git a/.all-contributorsrc b/.all-contributorsrc index 531090e31d..7d94f872d8 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -6539,6 +6539,15 @@ "code" ] }, + { + "login": "filiprafalowicz", + "name": "filiprafalowicz", + "avatar_url": "https://avatars.githubusercontent.com/u/24355557?v=4", + "profile": "https://github.com/filiprafalowicz", + "contributions": [ + "code" + ] + }, { "login": "JerritEic", "name": "JerritEic", diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 87417153a5..b4b787a283 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,7 +23,7 @@ jobs: os: [ ubuntu-latest, windows-latest, macos-latest ] if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 2 - uses: actions/cache@v2 diff --git a/.github/workflows/git-repo-sync.yml b/.github/workflows/git-repo-sync.yml index 2219f2330a..250dd00933 100644 --- a/.github/workflows/git-repo-sync.yml +++ b/.github/workflows/git-repo-sync.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest continue-on-error: false steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 100 - name: Setup Environment diff --git a/.github/workflows/troubleshooting.yml b/.github/workflows/troubleshooting.yml index d58e0f2aa1..4554ddedf7 100644 --- a/.github/workflows/troubleshooting.yml +++ b/.github/workflows/troubleshooting.yml @@ -12,7 +12,7 @@ jobs: os: [ ubuntu-latest ] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions/cache@v2 with: path: | diff --git a/docs/pages/pmd/projectdocs/credits.md b/docs/pages/pmd/projectdocs/credits.md index c2d45320d9..fdac14b9c1 100644 --- a/docs/pages/pmd/projectdocs/credits.md +++ b/docs/pages/pmd/projectdocs/credits.md @@ -775,163 +775,164 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
ekkirala

πŸ›
emersonmoura

πŸ›
fairy

πŸ› +
filiprafalowicz

πŸ’»
foxmason

πŸ› -
frankegabor

πŸ› +
frankegabor

πŸ›
frankl

πŸ›
freafrea

πŸ›
fsapatin

πŸ›
gracia19

πŸ›
guo fei

πŸ›
gurmsc5

πŸ› -
gwilymatgearset

πŸ’» πŸ› +
gwilymatgearset

πŸ’» πŸ›
haigsn

πŸ›
hemanshu070

πŸ›
henrik242

πŸ›
hongpuwu

πŸ›
hvbtup

πŸ’» πŸ›
igniti GmbH

πŸ› -
ilovezfs

πŸ› +
ilovezfs

πŸ›
itaigilo

πŸ›
jakivey32

πŸ›
jbennett2091

πŸ›
jcamerin

πŸ›
jkeener1

πŸ›
jmetertea

πŸ› -
johnra2

πŸ’» +
johnra2

πŸ’»
josemanuelrolon

πŸ’» πŸ›
kabroxiko

πŸ’» πŸ›
karwer

πŸ›
kaulonline

πŸ›
kdaemonv

πŸ›
kenji21

πŸ’» πŸ› -
kfranic

πŸ› +
kfranic

πŸ›
khalidkh

πŸ›
krzyk

πŸ›
lasselindqvist

πŸ›
lihuaib

πŸ›
lonelyma1021

πŸ›
lpeddy

πŸ› -
lujiefsi

πŸ’» +
lujiefsi

πŸ’»
lyriccoder

πŸ›
marcelmore

πŸ›
matchbox

πŸ›
matthiaskraaz

πŸ›
meandonlyme

πŸ›
mikesive

πŸ› -
milossesic

πŸ› +
milossesic

πŸ›
mriddell95

πŸ›
mrlzh

πŸ›
msloan

πŸ›
mucharlaravalika

πŸ›
mvenneman

πŸ›
nareshl119

πŸ› -
nicolas-harraudeau-sonarsource

πŸ› +
nicolas-harraudeau-sonarsource

πŸ›
noerremark

πŸ›
novsirion

πŸ›
oggboy

πŸ›
oinume

πŸ›
orimarko

πŸ’» πŸ›
pallavi agarwal

πŸ› -
parksungrin

πŸ› +
parksungrin

πŸ›
patpatpat123

πŸ›
patriksevallius

πŸ›
pbrajesh1

πŸ›
phoenix384

πŸ›
piotrszymanski-sc

πŸ’»
plan3d

πŸ› -
poojasix

πŸ› +
poojasix

πŸ›
prabhushrikant

πŸ›
pujitha8783

πŸ›
r-r-a-j

πŸ›
raghujayjunk

πŸ›
rajeshveera

πŸ›
rajeswarreddy88

πŸ› -
recdevs

πŸ› +
recdevs

πŸ›
reudismam

πŸ’» πŸ›
rijkt

πŸ›
rillig-tk

πŸ›
rmohan20

πŸ’» πŸ›
rxmicro

πŸ›
ryan-gustafson

πŸ’» πŸ› -
sabi0

πŸ› +
sabi0

πŸ›
scais

πŸ›
sebbASF

πŸ›
sergeygorbaty

πŸ’»
shilko2013

πŸ›
simeonKondr

πŸ›
snajberk

πŸ› -
sniperrifle2004

πŸ› +
sniperrifle2004

πŸ›
snuyanzin

πŸ› πŸ’»
sratz

πŸ›
stonio

πŸ›
sturton

πŸ’» πŸ›
sudharmohan

πŸ›
suruchidawar

πŸ› -
svenfinitiv

πŸ› +
svenfinitiv

πŸ›
tashiscool

πŸ›
test-git-hook

πŸ›
testation21

πŸ’» πŸ›
thanosa

πŸ›
tiandiyixian

πŸ›
tobwoerk

πŸ› -
tprouvot

πŸ› +
tprouvot

πŸ›
trentchilders

πŸ›
triandicAnt

πŸ›
trishul14

πŸ›
tsui

πŸ›
winhkey

πŸ›
witherspore

πŸ› -
wjljack

πŸ› +
wjljack

πŸ›
wuchiuwong

πŸ›
xingsong

πŸ›
xioayuge

πŸ›
xnYi9wRezm

πŸ’» πŸ›
xuanuy

πŸ›
xyf0921

πŸ› -
yalechen-cyw3

πŸ› +
yalechen-cyw3

πŸ›
yasuharu-sato

πŸ›
zenglian

πŸ›
zgrzyt93

πŸ’» πŸ›
zh3ng

πŸ›
zt_soft

πŸ›
ztt79

πŸ› -
zzzzfeng

πŸ› +
zzzzfeng

πŸ›
ÁrpÑd MagosÑnyi

πŸ›
任贡杰

πŸ› diff --git a/docs/pages/pmd/userdocs/tools/java-api.md b/docs/pages/pmd/userdocs/tools/java-api.md index f313ba54dc..36ebb017ea 100644 --- a/docs/pages/pmd/userdocs/tools/java-api.md +++ b/docs/pages/pmd/userdocs/tools/java-api.md @@ -66,11 +66,11 @@ public class PmdExample { public static void main(String[] args) { PMDConfiguration configuration = new PMDConfiguration(); configuration.setInputPaths("/home/workspace/src/main/java/code"); - configuration.setRuleSets("rulesets/java/quickstart.xml"); + configuration.addRuleSet("rulesets/java/quickstart.xml"); configuration.setReportFormat("xml"); configuration.setReportFile("/home/workspace/pmd-report.xml"); - PMD.runPMD(configuration); + PMD.runPmd(configuration); } } ``` @@ -80,66 +80,75 @@ public class PmdExample { This gives you more control over which files are processed, but is also more complicated. You can also provide your own custom renderers. -1. First we create a `PMDConfiguration`. This is currently the only way to specify a ruleset: +1. First we create a `PMDConfiguration` and configure it, first the rules: ```java PMDConfiguration configuration = new PMDConfiguration(); configuration.setMinimumPriority(RulePriority.MEDIUM); - configuration.setRuleSets("rulesets/java/quickstart.xml"); + configuration.addRuleSet("rulesets/java/quickstart.xml"); ``` -2. In order to support type resolution, PMD needs to have access to the compiled classes and dependencies - as well. This is called "auxclasspath" and is also configured here. +2. Then we configure, which paths to analyze: + + ```java + configuration.setInputPaths("/home/workspace/src/main/java/code"); + ``` + +3. The we configure the default language version for Java. And in order to support type resolution, + PMD needs to have access to the compiled classes and dependencies as well. This is called + "auxclasspath" and is also configured here. + Note: you can specify multiple class paths separated by `:` on Unix-systems or `;` under Windows. ```java - configuration.prependClasspath("/home/workspace/target/classes:/home/.m2/repository/my/dependency.jar"); + configuration.setDefaultLanguageVersion(LanguageRegistry.findLanguageByTerseName("java").getVersion("11")); + configuration.prependAuxClasspath("/home/workspace/target/classes:/home/.m2/repository/my/dependency.jar"); ``` -3. Then we need to load the rulesets. This is done by using the configuration, taking the minimum priority into - account: +4. Then we configure the reporting. Configuring the report file is optional. If not specified, the report + will be written to `stdout`. ```java - RuleSetLoader ruleSetLoader = RuleSetLoader.fromPmdConfig(configuration); - List ruleSets = ruleSetLoader.loadFromResources(Arrays.asList(configuration.getRuleSets().split(","))); + configuration.setReportFormat("xml"); + configuration.setReportFile("/home/workspace/pmd-report.xml"); ``` -4. PMD operates on a list of `DataSource`. You can assemble a own list of `FileDataSource`, e.g. +5. Now an optional step: If you want to use additional renderers as in the example, set them up before + calling PMD. You can use a built-in renderer, e.g. `XMLRenderer` or a custom renderer implementing + `Renderer`. Note, that you must manually initialize the renderer by setting a suitable `Writer`: ```java - List files = Arrays.asList(new FileDataSource(new File("/path/to/src/MyClass.java"))); - ``` - -5. For reporting, you can use `GlobalAnalysisListener`, which receives events like violations and errors. - Useful implementations are provided by `Renderer` instances. To use a renderer, eg the built-in `XMLRenderer`, - create it and configure it with a suitable `Writer`. + Writer rendererOutput = new StringWriter(); + Renderer renderer = createRenderer(rendererOutput); - ```java - StringWriter rendererOutput = new StringWriter(); - Renderer xmlRenderer = new XMLRenderer("UTF-8"); - xmlRenderer.setWriter(rendererOutput); - // The listener is created from the renderer in the next listing + // ... + private static Renderer createRenderer(Writer writer) { + XMLRenderer xml = new XMLRenderer("UTF-8"); + xml.setWriter(writer); + return xml; + } ``` -6. Now, all the preparations are done, and PMD can be executed. This is done by calling - `PMD.processFiles(...)`. This method call takes the configuration, the rulesets, the files - to process, and the list of renderers. Provide an empty list, if you don't want to use - any renderer. Note: The auxclasspath needs to be closed explicitly. Otherwise the class or jar files may - remain open and file resources are leaked. +6. Finally we can start the PMD analysis. There is the possibility to fine-tune the configuration + by adding additional files to analyze or adding additional rulesets or renderers: ```java - try (GlobalAnalysisListener listener = xmlRenderer.newListener()) { - PMD.processFiles(configuration, ruleSets, files, listener); - } finally { - ClassLoader auxiliaryClassLoader = configuration.getClassLoader(); - if (auxiliaryClassLoader instanceof ClasspathClassLoader) { - ((ClasspathClassLoader) auxiliaryClassLoader).close(); - } + try (PmdAnalysis pmd = PmdAnalysis.create(configuration)) { + // optional: add more rulesets + pmd.addRuleSet(pmd.newRuleSetLoader().loadFromResource("custom-ruleset.xml")); + // optional: add more files + pmd.files().addFile(Paths.get("src", "main", "more-java", "ExtraSource.java")); + // optional: add more renderers + pmd.addRenderer(renderer); + + // or just call PMD + pmd.performAnalysis(); } ``` -7. After the call, the renderer will have been flushed by PMD (through its `GlobalAnalysisListener`). - Then you can check the rendered output. + The renderer will be automatically flushed and closed at the end of the analysis. + +7. Then you can check the rendered output. ``` java System.out.println("Rendered Report:"); @@ -152,51 +161,43 @@ Here is a complete example: import java.io.IOException; import java.io.StringWriter; import java.io.Writer; -import java.nio.file.FileSystems; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.PathMatcher; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; +import java.nio.file.Paths; -import net.sourceforge.pmd.PMD; import net.sourceforge.pmd.PMDConfiguration; +import net.sourceforge.pmd.PmdAnalysis; import net.sourceforge.pmd.RulePriority; -import net.sourceforge.pmd.RuleSet; -import net.sourceforge.pmd.RuleSetLoader; +import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.renderers.Renderer; import net.sourceforge.pmd.renderers.XMLRenderer; -import net.sourceforge.pmd.util.ClasspathClassLoader; -import net.sourceforge.pmd.util.datasource.DataSource; -import net.sourceforge.pmd.util.datasource.FileDataSource; public class PmdExample2 { public static void main(String[] args) throws IOException { PMDConfiguration configuration = new PMDConfiguration(); configuration.setMinimumPriority(RulePriority.MEDIUM); - configuration.setRuleSets("rulesets/java/quickstart.xml"); - configuration.prependClasspath("/home/workspace/target/classes"); - RuleSetLoader ruleSetLoader = RuleSetLoader.fromPmdConfig(configuration); - List ruleSets = ruleSetLoader.loadFromResources(Arrays.asList(configuration.getRuleSets().split(","))); + configuration.addRuleSet("rulesets/java/quickstart.xml"); - List files = determineFiles("/home/workspace/src/main/java/code"); + configuration.setInputPaths("/home/workspace/src/main/java/code"); + + configuration.setDefaultLanguageVersion(LanguageRegistry.findLanguageByTerseName("java").getVersion("11")); + configuration.prependAuxClasspath("/home/workspace/target/classes"); + + configuration.setReportFormat("xml"); + configuration.setReportFile("/home/workspace/pmd-report.xml"); Writer rendererOutput = new StringWriter(); Renderer renderer = createRenderer(rendererOutput); - try (GlobalAnalysisListener listener = renderer.newListener()) { - PMD.processFiles(configuration, ruleSets, files, listener); - } finally { - ClassLoader auxiliaryClassLoader = configuration.getClassLoader(); - if (auxiliaryClassLoader instanceof ClasspathClassLoader) { - ((ClasspathClassLoader) auxiliaryClassLoader).close(); - } + try (PmdAnalysis pmd = PmdAnalysis.create(configuration)) { + // optional: add more rulesets + pmd.addRuleSet(pmd.newRuleSetLoader().loadFromResource("custom-ruleset.xml")); + // optional: add more files + pmd.files().addFile(Paths.get("src", "main", "more-java", "ExtraSource.java")); + // optional: add more renderers + pmd.addRenderer(renderer); + + // or just call PMD + pmd.performAnalysis(); } System.out.println("Rendered Report:"); @@ -208,28 +209,6 @@ public class PmdExample2 { xml.setWriter(writer); return xml; } - - private static List determineFiles(String basePath) throws IOException { - Path dirPath = FileSystems.getDefault().getPath(basePath); - final PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:*.java"); - - final List files = new ArrayList<>(); - - Files.walkFileTree(dirPath, new SimpleFileVisitor() { - @Override - public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException { - if (matcher.matches(path.getFileName())) { - System.out.printf("Using %s%n", path); - files.add(new FileDataSource(path.toFile())); - } else { - System.out.printf("Ignoring %s%n", path); - } - return super.visitFile(path, attrs); - } - }); - System.out.printf("Analyzing %d files in %s%n", files.size(), basePath); - return files; - } } ``` diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index 8abb75f161..31b9997389 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -19,11 +19,83 @@ This is a {{ site.pmd.release_type }} release. ### New and noteworthy + +#### New programmatic API + +This release introduces a new programmatic API to replace the inflexible {% jdoc core::PMD %} class. +Programmatic execution of PMD should now be done with a {% jdoc core::PMDConfiguration %} +and a {% jdoc core::PmdAnalysis %}, for instance: + +```java +PMDConfiguration config = new PMDConfiguration(); +config.setDefaultLanguageVersion(LanguageRegistry.findLanguageByTerseName("java").getVersion("11")); +config.setInputPaths("src/main/java"); +config.prependAuxClasspath("target/classes"); +config.setMinimumPriority(RulePriority.HIGH); +config.addRuleSet("rulesets/java/quickstart.xml"); +config.setReportFormat("xml"); +config.setReportFile("target/pmd-report.xml"); + +try (PmdAnalysis pmd = PmdAnalysis.create(config)) { + // note: don't use `config` once a PmdAnalysis has been created. + // optional: add more rulesets + pmd.addRuleSet(pmd.newRuleSetLoader().loadFromResource("custom-ruleset.xml")); + // optional: add more files + pmd.files().addFile(Paths.get("src", "main", "more-java", "ExtraSource.java")); + // optional: add more renderers + pmd.addRenderer(renderer); + + // or just call PMD + pmd.performAnalysis(); +} +``` + +The `PMD` class still supports methods related to CLI execution: `runPmd` and `main`. +All other members are now deprecated for removal. +The CLI itself remains compatible, if you run PMD via command-line, no action is required on your part. + ### Fixed Issues +* apex-performance + * [#3773](https://github.com/pmd/pmd/pull/3773): \[apex] EagerlyLoadedDescribeSObjectResult false positives with SObjectField.getDescribe() +* core + * [#3299](https://github.com/pmd/pmd/issues/3299): \[core] Deprecate system properties of PMDCommandLineInterface + ### API Changes +#### Deprecated API + +* Several members of {% jdoc core::PMD %} have been newly deprecated, including: + - `PMD#EOL`: use `System#lineSeparator()` + - `PMD#SUPPRESS_MARKER`: use {% jdoc core::PMDConfiguration#DEFAULT_SUPPRESS_MARKER %} + - `PMD#processFiles`: use the [new programmatic API](#new-programmatic-api) + - `PMD#getApplicableFiles`: is internal +* {% jdoc !!core::PMDConfiguration#prependClasspath(java.lang.String) %} is deprecated + in favour of {% jdoc core::PMDConfiguration#prependAuxClasspath(java.lang.String) %}. +* {% jdoc !!core::PMDConfiguration#setRuleSets(java.lang.String) %} and + {% jdoc core::PMDConfiguration#getRuleSets() %} are deprecated. Use instead + {% jdoc core::PMDConfiguration#setRuleSets(java.util.List) %}, + {% jdoc core::PMDConfiguration#addRuleSet(java.lang.String) %}, + and {% jdoc core::PMDConfiguration#getRuleSetPaths() %}. +* Several members of {% jdoc test::cli.BaseCLITest %} have been deprecated with replacements. +* Several members of {% jdoc core::cli.PMDCommandLineInterface %} have been explicitly deprecated. + The whole class however was deprecated long ago already with 6.30.0. It is internal API and should + not be used. + +#### Experimental APIs + +* Together with the [new programmatic API](#new-programmatic-api) the interface + {% jdoc core::lang.document.TextFile %} has been added as *experimental*. It intends + to replace {% jdoc core::util.datasource.DataSource %} and {% jdoc core::cpd.SourceCode %} in the long term. + + This interface will change in PMD 7 to support read/write operations + and other things. You don't need to use it in PMD 6, as {% jdoc core::lang.document.FileCollector %} + decouples you from this. A file collector is available through {% jdoc !!core::PmdAnalysis#files() %}. + + ### External Contributions +* [#3773](https://github.com/pmd/pmd/pull/3773): \[apex] EagerlyLoadedDescribeSObjectResult false positives with SObjectField.getDescribe() - [@filiprafalowicz](https://github.com/filiprafalowicz) + {% endtocmaker %} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTAnnotation.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTAnnotation.java index a83622301c..36249a30c0 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTAnnotation.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTAnnotation.java @@ -18,7 +18,12 @@ public final class ASTAnnotation extends AbstractApexNode { return visitor.visit(this, data); } + public String getName() { + return node.getType().getApexName(); + } + @Override + @Deprecated public String getImage() { return node.getType().getApexName(); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTAnnotationParameter.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTAnnotationParameter.java index d06310df33..9c9a17e1ef 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTAnnotationParameter.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTAnnotationParameter.java @@ -38,6 +38,7 @@ public final class ASTAnnotationParameter extends AbstractApexNode identifiers = node.getNames(); + return identifiers != null + && identifiers.stream().anyMatch(id -> "sobjecttype".equalsIgnoreCase(id.getValue())); + } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/internal/ApexRuleViolationFactory.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/internal/ApexRuleViolationFactory.java index 044ce65358..10268c6dcf 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/internal/ApexRuleViolationFactory.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/internal/ApexRuleViolationFactory.java @@ -98,9 +98,9 @@ public final class ApexRuleViolationFactory extends DefaultRuleViolationFactory private static boolean suppresses(ASTAnnotation annot, Rule rule) { final String ruleAnno = "PMD." + rule.getName(); - if (annot.hasImageEqualTo("SuppressWarnings")) { - for (ASTAnnotationParameter param : annot.findChildrenOfType(ASTAnnotationParameter.class)) { - String image = param.getImage(); + if ("SuppressWarnings".equals(annot.getName())) { + for (ASTAnnotationParameter param : annot.children(ASTAnnotationParameter.class)) { + String image = param.getValue(); if (image != null) { Set paramValues = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); diff --git a/pmd-apex/src/main/resources/category/apex/performance.xml b/pmd-apex/src/main/resources/category/apex/performance.xml index 731a8e1aa3..50f749dd88 100644 --- a/pmd-apex/src/main/resources/category/apex/performance.xml +++ b/pmd-apex/src/main/resources/category/apex/performance.xml @@ -146,23 +146,26 @@ public class Something { This rule finds `DescribeSObjectResult`s which could have been loaded eagerly via `SObjectType.getDescribe()`. -When using `SObjectType.getDescribe()` or `Schema.describeSObjects()` without supplying a `SObjectDescribeOptions`, implicitely it will be using `SObjectDescribeOptions.DEFAULT` then all +When using `SObjectType.getDescribe()` or `Schema.describeSObjects()` without supplying a `SObjectDescribeOptions`, +implicitly it will be using `SObjectDescribeOptions.DEFAULT` and then all child relationships will be loaded eagerly regardless whether this information is needed or not. This has a potential negative performance impact. Instead [`SObjectType.getDescribe(options)`](https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_class_Schema_SObjectType.htm#unique_346834793) -or [`Schema.describeSObjects(SObjectTypes, options)`](https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_methods_system_schema.htm#apex_System_Schema_describeSObjects) should be used and a `SObjectDescribeOptions` should be supplied. By using +or [`Schema.describeSObjects(SObjectTypes, options)`](https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_methods_system_schema.htm#apex_System_Schema_describeSObjects) +should be used and a `SObjectDescribeOptions` should be supplied. By using `SObjectDescribeOptions.DEFERRED` the describe attributes will be lazily initialized at first use. -Lazy loading `DescribeSObjectResult` on picklist fields is not recommended. The lazy loaded +Lazy loading `DescribeSObjectResult` on picklist fields is not always recommended. The lazy loaded describe objects might not be 100% accurate. It might be safer to explicitly use -`SObjectDescribeOptions.FULL` in such a case. The same applies when you need the same `DescribeSObjectResult` to be consistent -accross different contexts and API versions. +`SObjectDescribeOptions.FULL` in such a case. The same applies when you need the same `DescribeSObjectResult` +to be consistent across different contexts and API versions. Properties: * `noDefault`: The behavior of `SObjectDescribeOptions.DEFAULT` changes from API Version 43 to 44: With API Version 43, the attributes are loaded eagerly. With API Version 44, they are loaded lazily. Simply using `SObjectDescribeOptions.DEFAULT` doesn't automatically make use of lazy loading. - (unless "Use Improved Schema Caching" critical update is applied, `SObjectDescribeOptions.DEFAULT` do fallback to lazy loading) + (unless "Use Improved Schema Caching" critical update is applied, `SObjectDescribeOptions.DEFAULT` does fallback + to lazy loading) With this property enabled, such usages are found. You might ignore this, if you can make sure, that you don't run a mix of API Versions. @@ -173,8 +176,20 @@ Properties: diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexParsingHelper.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexParsingHelper.java index fe7aa1022a..3320a312a9 100644 --- a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexParsingHelper.java +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexParsingHelper.java @@ -9,7 +9,7 @@ import net.sourceforge.pmd.lang.ast.test.BaseParsingHelper; public class ApexParsingHelper extends BaseParsingHelper { - public static final ApexParsingHelper DEFAULT = new ApexParsingHelper(Params.getDefaultProcess()); + public static final ApexParsingHelper DEFAULT = new ApexParsingHelper(Params.getDefault()); private ApexParsingHelper(Params p) { diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/ast/SafeNavigationOperator.txt b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/ast/SafeNavigationOperator.txt index 3915093d23..db0bb1da49 100644 --- a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/ast/SafeNavigationOperator.txt +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/ast/SafeNavigationOperator.txt @@ -9,7 +9,7 @@ | +- ModifierNode[@Abstract = "false", @ApexVersion = "54.0", @DefiningType = "Foo", @DeprecatedTestMethod = "false", @Final = "false", @Global = "false", @InheritedSharing = "false", @Location = "no location", @Modifiers = "0", @Namespace = "", @Override = "false", @Private = "false", @Protected = "false", @Public = "false", @RealLoc = "false", @Static = "false", @Test = "false", @TestOrTestSetup = "false", @Transient = "false", @Virtual = "false", @WebService = "false", @WithSharing = "false", @WithoutSharing = "false"] | +- FieldDeclaration[@ApexVersion = "54.0", @DefiningType = "Foo", @Image = "anIntegerField", @Location = "(5, 13, 198, 199)", @Name = "anIntegerField", @Namespace = "", @RealLoc = "true"] | +- VariableExpression[@ApexVersion = "54.0", @DefiningType = "Foo", @Image = "anIntegerField", @Location = "(5, 27, 212, 226)", @Namespace = "", @RealLoc = "true"] - | | +- ReferenceExpression[@ApexVersion = "54.0", @Context = null, @DefiningType = "Foo", @Location = "no location", @Namespace = "", @RealLoc = "false", @ReferenceType = "LOAD", @SafeNav = "true"] + | | +- ReferenceExpression[@ApexVersion = "54.0", @Context = null, @DefiningType = "Foo", @Location = "no location", @Namespace = "", @RealLoc = "false", @ReferenceType = "LOAD", @SObjectType = "false", @SafeNav = "true"] | | +- VariableExpression[@ApexVersion = "54.0", @DefiningType = "Foo", @Image = "anObject", @Location = "(5, 17, 202, 210)", @Namespace = "", @RealLoc = "true"] | | +- EmptyReferenceExpression[@ApexVersion = "54.0", @DefiningType = null, @Location = "no location", @Namespace = null, @RealLoc = "false"] | +- VariableExpression[@ApexVersion = "54.0", @DefiningType = "Foo", @Image = "x", @Location = "(5, 13, 198, 199)", @Namespace = "", @RealLoc = "true"] @@ -18,9 +18,9 @@ | +- ModifierNode[@Abstract = "false", @ApexVersion = "54.0", @DefiningType = "Foo", @DeprecatedTestMethod = "false", @Final = "false", @Global = "false", @InheritedSharing = "false", @Location = "no location", @Modifiers = "0", @Namespace = "", @Override = "false", @Private = "false", @Protected = "false", @Public = "false", @RealLoc = "false", @Static = "false", @Test = "false", @TestOrTestSetup = "false", @Transient = "false", @Virtual = "false", @WebService = "false", @WithSharing = "false", @WithoutSharing = "false"] | +- FieldDeclaration[@ApexVersion = "54.0", @DefiningType = "Foo", @Image = "profileUrl", @Location = "(8, 12, 365, 375)", @Name = "profileUrl", @Namespace = "", @RealLoc = "true"] | +- MethodCallExpression[@ApexVersion = "54.0", @DefiningType = "Foo", @FullMethodName = "toExternalForm", @InputParametersSize = "0", @Location = "(8, 47, 400, 414)", @MethodName = "toExternalForm", @Namespace = "", @RealLoc = "true"] - | | +- ReferenceExpression[@ApexVersion = "54.0", @Context = null, @DefiningType = "Foo", @Location = "no location", @Namespace = "", @RealLoc = "false", @ReferenceType = "METHOD", @SafeNav = "true"] + | | +- ReferenceExpression[@ApexVersion = "54.0", @Context = null, @DefiningType = "Foo", @Location = "no location", @Namespace = "", @RealLoc = "false", @ReferenceType = "METHOD", @SObjectType = "false", @SafeNav = "true"] | | +- MethodCallExpression[@ApexVersion = "54.0", @DefiningType = "Foo", @FullMethodName = "user.getProfileUrl", @InputParametersSize = "0", @Location = "(8, 30, 383, 396)", @MethodName = "getProfileUrl", @Namespace = "", @RealLoc = "true"] - | | +- ReferenceExpression[@ApexVersion = "54.0", @Context = null, @DefiningType = "Foo", @Image = "user", @Location = "(8, 25, 378, 382)", @Namespace = "", @RealLoc = "true", @ReferenceType = "METHOD", @SafeNav = "false"] + | | +- ReferenceExpression[@ApexVersion = "54.0", @Context = null, @DefiningType = "Foo", @Image = "user", @Location = "(8, 25, 378, 382)", @Namespace = "", @RealLoc = "true", @ReferenceType = "METHOD", @SObjectType = "false", @SafeNav = "false"] | +- VariableExpression[@ApexVersion = "54.0", @DefiningType = "Foo", @Image = "profileUrl", @Location = "(8, 12, 365, 375)", @Namespace = "", @RealLoc = "true"] | +- EmptyReferenceExpression[@ApexVersion = "54.0", @DefiningType = null, @Location = "no location", @Namespace = null, @RealLoc = "false"] +- Method[@ApexVersion = "54.0", @Arity = "1", @CanonicalName = "bar1", @Constructor = "false", @DefiningType = "Foo", @Image = "bar1", @Location = "(10, 17, 435, 439)", @Namespace = "", @RealLoc = "true", @ReturnType = "void", @Synthetic = "false"] @@ -30,15 +30,15 @@ | +- BlockStatement[@ApexVersion = "54.0", @CurlyBrace = "true", @DefiningType = "Foo", @Location = "(10, 32, 450, 538)", @Namespace = "", @RealLoc = "true"] | +- ExpressionStatement[@ApexVersion = "54.0", @DefiningType = "Foo", @Location = "(11, 12, 463, 465)", @Namespace = "", @RealLoc = "true"] | | +- VariableExpression[@ApexVersion = "54.0", @DefiningType = "Foo", @Image = "b", @Location = "(11, 12, 463, 464)", @Namespace = "", @RealLoc = "true"] - | | +- ReferenceExpression[@ApexVersion = "54.0", @Context = null, @DefiningType = "Foo", @Location = "no location", @Namespace = "", @RealLoc = "false", @ReferenceType = "LOAD", @SafeNav = "true"] + | | +- ReferenceExpression[@ApexVersion = "54.0", @Context = null, @DefiningType = "Foo", @Location = "no location", @Namespace = "", @RealLoc = "false", @ReferenceType = "LOAD", @SObjectType = "false", @SafeNav = "true"] | | +- VariableExpression[@ApexVersion = "54.0", @DefiningType = "Foo", @Image = "a", @Location = "(11, 9, 460, 461)", @Namespace = "", @RealLoc = "true"] | | +- EmptyReferenceExpression[@ApexVersion = "54.0", @DefiningType = null, @Location = "no location", @Namespace = null, @RealLoc = "false"] | +- ExpressionStatement[@ApexVersion = "54.0", @DefiningType = "Foo", @Location = "(12, 22, 527, 532)", @Namespace = "", @RealLoc = "true"] | +- MethodCallExpression[@ApexVersion = "54.0", @DefiningType = "Foo", @FullMethodName = "c1", @InputParametersSize = "0", @Location = "(12, 22, 527, 529)", @MethodName = "c1", @Namespace = "", @RealLoc = "true"] - | +- ReferenceExpression[@ApexVersion = "54.0", @Context = null, @DefiningType = "Foo", @Location = "no location", @Namespace = "", @RealLoc = "false", @ReferenceType = "METHOD", @SafeNav = "true"] + | +- ReferenceExpression[@ApexVersion = "54.0", @Context = null, @DefiningType = "Foo", @Location = "no location", @Namespace = "", @RealLoc = "false", @ReferenceType = "METHOD", @SObjectType = "false", @SafeNav = "true"] | +- CastExpression[@ApexVersion = "54.0", @DefiningType = "Foo", @Location = "(12, 10, 515, 518)", @Namespace = "", @RealLoc = "true"] | +- VariableExpression[@ApexVersion = "54.0", @DefiningType = "Foo", @Image = "b1", @Location = "(12, 17, 522, 524)", @Namespace = "", @RealLoc = "true"] - | +- ReferenceExpression[@ApexVersion = "54.0", @Context = null, @DefiningType = "Foo", @Location = "no location", @Namespace = "", @RealLoc = "false", @ReferenceType = "LOAD", @SafeNav = "true"] + | +- ReferenceExpression[@ApexVersion = "54.0", @Context = null, @DefiningType = "Foo", @Location = "no location", @Namespace = "", @RealLoc = "false", @ReferenceType = "LOAD", @SObjectType = "false", @SafeNav = "true"] | +- VariableExpression[@ApexVersion = "54.0", @DefiningType = "Foo", @Image = "a1", @Location = "(12, 13, 518, 520)", @Namespace = "", @RealLoc = "true"] | +- EmptyReferenceExpression[@ApexVersion = "54.0", @DefiningType = null, @Location = "no location", @Namespace = null, @RealLoc = "false"] +- Method[@ApexVersion = "54.0", @Arity = "2", @CanonicalName = "bar2", @Constructor = "false", @DefiningType = "Foo", @Image = "bar2", @Location = "(15, 17, 556, 560)", @Namespace = "", @RealLoc = "true", @ReturnType = "void", @Synthetic = "false"] @@ -50,9 +50,9 @@ | +- BlockStatement[@ApexVersion = "54.0", @CurlyBrace = "true", @DefiningType = "Foo", @Location = "(15, 41, 580, 688)", @Namespace = "", @RealLoc = "true"] | +- ExpressionStatement[@ApexVersion = "54.0", @DefiningType = "Foo", @Location = "(16, 25, 606, 613)", @Namespace = "", @RealLoc = "true"] | | +- VariableExpression[@ApexVersion = "54.0", @DefiningType = "Foo", @Image = "aField", @Location = "(16, 25, 606, 612)", @Namespace = "", @RealLoc = "true"] - | | +- ReferenceExpression[@ApexVersion = "54.0", @Context = null, @DefiningType = "Foo", @Location = "no location", @Namespace = "", @RealLoc = "false", @ReferenceType = "LOAD", @SafeNav = "false"] + | | +- ReferenceExpression[@ApexVersion = "54.0", @Context = null, @DefiningType = "Foo", @Location = "no location", @Namespace = "", @RealLoc = "false", @ReferenceType = "LOAD", @SObjectType = "false", @SafeNav = "false"] | | +- MethodCallExpression[@ApexVersion = "54.0", @DefiningType = "Foo", @FullMethodName = "aMethod", @InputParametersSize = "0", @Location = "(16, 15, 596, 603)", @MethodName = "aMethod", @Namespace = "", @RealLoc = "true"] - | | +- ReferenceExpression[@ApexVersion = "54.0", @Context = null, @DefiningType = "Foo", @Location = "no location", @Namespace = "", @RealLoc = "false", @ReferenceType = "METHOD", @SafeNav = "true"] + | | +- ReferenceExpression[@ApexVersion = "54.0", @Context = null, @DefiningType = "Foo", @Location = "no location", @Namespace = "", @RealLoc = "false", @ReferenceType = "METHOD", @SObjectType = "false", @SafeNav = "true"] | | +- ArrayLoadExpression[@ApexVersion = "54.0", @DefiningType = "Foo", @Location = "(16, 9, 590, 591)", @Namespace = "", @RealLoc = "true"] | | +- VariableExpression[@ApexVersion = "54.0", @DefiningType = "Foo", @Image = "a", @Location = "(16, 9, 590, 591)", @Namespace = "", @RealLoc = "true"] | | | +- EmptyReferenceExpression[@ApexVersion = "54.0", @DefiningType = null, @Location = "no location", @Namespace = null, @RealLoc = "false"] @@ -60,9 +60,9 @@ | | +- EmptyReferenceExpression[@ApexVersion = "54.0", @DefiningType = null, @Location = "no location", @Namespace = null, @RealLoc = "false"] | +- ExpressionStatement[@ApexVersion = "54.0", @DefiningType = "Foo", @Location = "(17, 25, 675, 682)", @Namespace = "", @RealLoc = "true"] | +- VariableExpression[@ApexVersion = "54.0", @DefiningType = "Foo", @Image = "aField", @Location = "(17, 25, 675, 681)", @Namespace = "", @RealLoc = "true"] - | +- ReferenceExpression[@ApexVersion = "54.0", @Context = null, @DefiningType = "Foo", @Location = "no location", @Namespace = "", @RealLoc = "false", @ReferenceType = "LOAD", @SafeNav = "true"] + | +- ReferenceExpression[@ApexVersion = "54.0", @Context = null, @DefiningType = "Foo", @Location = "no location", @Namespace = "", @RealLoc = "false", @ReferenceType = "LOAD", @SObjectType = "false", @SafeNav = "true"] | +- MethodCallExpression[@ApexVersion = "54.0", @DefiningType = "Foo", @FullMethodName = "aMethod", @InputParametersSize = "0", @Location = "(17, 14, 664, 671)", @MethodName = "aMethod", @Namespace = "", @RealLoc = "true"] - | +- ReferenceExpression[@ApexVersion = "54.0", @Context = null, @DefiningType = "Foo", @Location = "no location", @Namespace = "", @RealLoc = "false", @ReferenceType = "METHOD", @SafeNav = "false"] + | +- ReferenceExpression[@ApexVersion = "54.0", @Context = null, @DefiningType = "Foo", @Location = "no location", @Namespace = "", @RealLoc = "false", @ReferenceType = "METHOD", @SObjectType = "false", @SafeNav = "false"] | +- ArrayLoadExpression[@ApexVersion = "54.0", @DefiningType = "Foo", @Location = "(17, 9, 659, 660)", @Namespace = "", @RealLoc = "true"] | +- VariableExpression[@ApexVersion = "54.0", @DefiningType = "Foo", @Image = "a", @Location = "(17, 9, 659, 660)", @Namespace = "", @RealLoc = "true"] | | +- EmptyReferenceExpression[@ApexVersion = "54.0", @DefiningType = null, @Location = "no location", @Namespace = null, @RealLoc = "false"] @@ -77,14 +77,14 @@ | | +- ModifierNode[@Abstract = "false", @ApexVersion = "54.0", @DefiningType = "Foo", @DeprecatedTestMethod = "false", @Final = "false", @Global = "false", @InheritedSharing = "false", @Location = "no location", @Modifiers = "0", @Namespace = "", @Override = "false", @Private = "false", @Protected = "false", @Public = "false", @RealLoc = "false", @Static = "false", @Test = "false", @TestOrTestSetup = "false", @Transient = "false", @Virtual = "false", @WebService = "false", @WithSharing = "false", @WithoutSharing = "false"] | | +- VariableDeclaration[@ApexVersion = "54.0", @DefiningType = "Foo", @Image = "s", @Location = "(21, 16, 744, 745)", @Namespace = "", @RealLoc = "true", @Type = "String"] | | +- VariableExpression[@ApexVersion = "54.0", @DefiningType = "Foo", @Image = "BillingCity", @Location = "(21, 37, 765, 776)", @Namespace = "", @RealLoc = "true"] - | | | +- ReferenceExpression[@ApexVersion = "54.0", @Context = null, @DefiningType = "Foo", @Location = "no location", @Namespace = "", @RealLoc = "false", @ReferenceType = "LOAD", @SafeNav = "true"] + | | | +- ReferenceExpression[@ApexVersion = "54.0", @Context = null, @DefiningType = "Foo", @Location = "no location", @Namespace = "", @RealLoc = "false", @ReferenceType = "LOAD", @SObjectType = "false", @SafeNav = "true"] | | | +- VariableExpression[@ApexVersion = "54.0", @DefiningType = "Foo", @Image = "Account", @Location = "(21, 28, 756, 763)", @Namespace = "", @RealLoc = "true"] - | | | +- ReferenceExpression[@ApexVersion = "54.0", @Context = null, @DefiningType = "Foo", @Image = "contact", @Location = "(21, 20, 748, 755)", @Namespace = "", @RealLoc = "true", @ReferenceType = "LOAD", @SafeNav = "false"] + | | | +- ReferenceExpression[@ApexVersion = "54.0", @Context = null, @DefiningType = "Foo", @Image = "contact", @Location = "(21, 20, 748, 755)", @Namespace = "", @RealLoc = "true", @ReferenceType = "LOAD", @SObjectType = "false", @SafeNav = "false"] | | +- VariableExpression[@ApexVersion = "54.0", @DefiningType = "Foo", @Image = "s", @Location = "(21, 16, 744, 745)", @Namespace = "", @RealLoc = "true"] | | +- EmptyReferenceExpression[@ApexVersion = "54.0", @DefiningType = null, @Location = "no location", @Namespace = null, @RealLoc = "false"] | +- ReturnStatement[@ApexVersion = "54.0", @DefiningType = "Foo", @Location = "(23, 9, 841, 899)", @Namespace = "", @RealLoc = "true"] | +- VariableExpression[@ApexVersion = "54.0", @DefiningType = "Foo", @Image = "Name", @Location = "(23, 62, 894, 898)", @Namespace = "", @RealLoc = "true"] - | +- ReferenceExpression[@ApexVersion = "54.0", @Context = null, @DefiningType = "Foo", @Location = "no location", @Namespace = "", @RealLoc = "false", @ReferenceType = "LOAD", @SafeNav = "true"] + | +- ReferenceExpression[@ApexVersion = "54.0", @Context = null, @DefiningType = "Foo", @Location = "no location", @Namespace = "", @RealLoc = "false", @ReferenceType = "LOAD", @SObjectType = "false", @SafeNav = "true"] | +- SoqlExpression[@ApexVersion = "54.0", @CanonicalQuery = "SELECT Name FROM Account WHERE Id = :tmpVar1", @DefiningType = "Foo", @Location = "(23, 16, 848, 892)", @Namespace = "", @Query = "SELECT Name FROM Account WHERE Id = :accId", @RealLoc = "true"] | +- BindExpressions[@ApexVersion = "54.0", @DefiningType = "Foo", @Location = "(23, 16, 848, 892)", @Namespace = "", @RealLoc = "true"] | +- VariableExpression[@ApexVersion = "54.0", @DefiningType = "Foo", @Image = "accId", @Location = "(23, 54, 886, 891)", @Namespace = "", @RealLoc = "true"] diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/performance/xml/EagerlyLoadedDescribeSObjectResult.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/performance/xml/EagerlyLoadedDescribeSObjectResult.xml index c8f7aeb7ad..2e878dfa80 100644 --- a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/performance/xml/EagerlyLoadedDescribeSObjectResult.xml +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/performance/xml/EagerlyLoadedDescribeSObjectResult.xml @@ -7,6 +7,7 @@ No describer options 1 + 3 accounts) { @@ -21,6 +22,7 @@ public class Foo { No describer options using Schema class 1 + 3 accounts) { @@ -89,4 +91,54 @@ public class Foo { ]]> + + False positive with no describer options on SObjectField + 0 + + + + + + False positive on SObjectField with FieldDescribeOptions.FULL_DESCRIBE + 0 + + + + + False positive on SObjectField with FieldDescribeOptions.DEFAULT + 0 + + + + + False positive on SObjectField with FieldDescribeOptions.DEFAULT with noDefault=true + true + 0 + + diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java b/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java index eb8fcb2381..9c1b261c88 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java @@ -6,21 +6,14 @@ package net.sourceforge.pmd; import static net.sourceforge.pmd.util.CollectionUtil.listOf; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FilenameFilter; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; -import java.net.URISyntaxException; -import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; +import java.util.Comparator; import java.util.List; import java.util.Map.Entry; -import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,8 +22,6 @@ import org.slf4j.event.Level; import net.sourceforge.pmd.Report.GlobalReportBuilderListener; import net.sourceforge.pmd.benchmark.TextTimingReportRenderer; import net.sourceforge.pmd.benchmark.TimeTracker; -import net.sourceforge.pmd.benchmark.TimedOperation; -import net.sourceforge.pmd.benchmark.TimedOperationCategory; import net.sourceforge.pmd.benchmark.TimingReport; import net.sourceforge.pmd.benchmark.TimingReportRenderer; import net.sourceforge.pmd.cache.NoopAnalysisCache; @@ -38,38 +29,19 @@ import net.sourceforge.pmd.cli.PMDCommandLineInterface; import net.sourceforge.pmd.cli.PmdParametersParseResult; import net.sourceforge.pmd.cli.internal.CliMessages; import net.sourceforge.pmd.internal.Slf4jSimpleConfiguration; -import net.sourceforge.pmd.internal.util.AssertionUtil; -import net.sourceforge.pmd.lang.Language; -import net.sourceforge.pmd.lang.LanguageFilenameFilter; -import net.sourceforge.pmd.lang.LanguageVersion; -import net.sourceforge.pmd.lang.LanguageVersionDiscoverer; -import net.sourceforge.pmd.processor.AbstractPMDProcessor; import net.sourceforge.pmd.renderers.Renderer; -import net.sourceforge.pmd.reporting.GlobalAnalysisListener; -import net.sourceforge.pmd.reporting.GlobalAnalysisListener.ViolationCounterListener; -import net.sourceforge.pmd.util.ClasspathClassLoader; -import net.sourceforge.pmd.util.FileUtil; -import net.sourceforge.pmd.util.IOUtil; -import net.sourceforge.pmd.util.database.DBMSMetadata; -import net.sourceforge.pmd.util.database.DBURI; -import net.sourceforge.pmd.util.database.SourceObject; +import net.sourceforge.pmd.reporting.ReportStats; +import net.sourceforge.pmd.reporting.ReportStatsListener; import net.sourceforge.pmd.util.datasource.DataSource; -import net.sourceforge.pmd.util.datasource.ReaderDataSource; +import net.sourceforge.pmd.util.log.MessageReporter; +import net.sourceforge.pmd.util.log.internal.SimpleMessageReporter; /** - * This is the main class for interacting with PMD. The primary flow of all Rule - * process is controlled via interactions with this class. A command line - * interface is supported, as well as a programmatic API for integrating PMD - * with other software such as IDEs and Ant. - * - *

Main entrypoints are: - *

    - *
  • {@link #main(String[])} which exits the java process
  • - *
  • {@link #runPmd(String...)} which returns a {@link StatusCode}
  • - *
  • {@link #runPmd(PMDConfiguration)}
  • - *
  • {@link #processFiles(PMDConfiguration, List, Collection, List)}
  • - *
- * + * Entry point for PMD's CLI. Use {@link #runPmd(PMDConfiguration)} + * or {@link #runPmd(String...)} to mimic a CLI run. This class is + * not a supported programmatic API anymore, use {@link PmdAnalysis} + * for fine control over the PMD integration and execution. + * *

Warning: This class is not intended to be instantiated or subclassed. It will * be made final in PMD7. */ @@ -81,158 +53,60 @@ public final class PMD { /** * The line delimiter used by PMD in outputs. Usually the platform specific * line separator. + * + * @deprecated Use {@link System#lineSeparator()} */ - public static final String EOL = System.getProperty("line.separator", "\n"); + @Deprecated + public static final String EOL = System.lineSeparator(); - /** The default suppress marker string. */ - public static final String SUPPRESS_MARKER = "NOPMD"; + /** + * The default suppress marker string. + * + * @deprecated Use {@link PMDConfiguration#DEFAULT_SUPPRESS_MARKER} + */ + @Deprecated + public static final String SUPPRESS_MARKER = PMDConfiguration.DEFAULT_SUPPRESS_MARKER; private PMD() { } - /** - * Parses the given string as a database uri and returns a list of - * datasources. - * - * @param uriString the URI to parse - * - * @return list of data sources - * - * @throws IOException if the URI couldn't be parsed - * @see DBURI - * - * @deprecated Will be hidden as part of the parsing of {@link PMD#getApplicableFiles(PMDConfiguration, Set)} - */ - @Deprecated - public static List getURIDataSources(String uriString) throws IOException { - List dataSources = new ArrayList<>(); + private static ReportStats runAndReturnStats(PmdAnalysis pmd) { + if (pmd.getRulesets().isEmpty()) { + return ReportStats.empty(); + } + + @SuppressWarnings("PMD.CloseResource") + ReportStatsListener listener = new ReportStatsListener(); + + pmd.addListener(listener); try { - DBURI dbUri = new DBURI(uriString); - DBMSMetadata dbmsMetadata = new DBMSMetadata(dbUri); - log.debug("DBMSMetadata retrieved"); - List sourceObjectList = dbmsMetadata.getSourceObjectList(); - log.debug("Located {} database source objects", sourceObjectList.size()); - for (SourceObject sourceObject : sourceObjectList) { - String falseFilePath = sourceObject.getPseudoFileName(); - log.trace("Adding database source object {}", falseFilePath); - - try { - dataSources.add(new ReaderDataSource(dbmsMetadata.getSourceCode(sourceObject), falseFilePath)); - } catch (SQLException ex) { - log.warn("Cannot get SourceCode for {} - skipping ...", falseFilePath, ex); - } - } - } catch (URISyntaxException e) { - throw new IOException("Cannot get DataSources from DBURI - \"" + uriString + "\"", e); - } catch (SQLException e) { - throw new IOException("Cannot get DataSources from DBURI, couldn't access the database - \"" + uriString + "\"", e); - } catch (ClassNotFoundException e) { - throw new IOException("Cannot get DataSources from DBURI, probably missing database jdbc driver - \"" + uriString + "\"", e); + pmd.performAnalysis(); } catch (Exception e) { - throw new IOException("Encountered unexpected problem with URI \"" + uriString + "\"", e); + pmd.getReporter().errorEx("Exception during processing", e); + ReportStats stats = listener.getResult(); + printErrorDetected(1 + stats.getNumErrors()); + return stats; // should have been closed } - return dataSources; - } + ReportStats stats = listener.getResult(); - /** - * This method is the main entry point for command line usage. - * - * @param configuration the configuration to use - * - * @return number of violations found. - * - * @deprecated Use {@link #runPmd(PMDConfiguration)}. - */ - @Deprecated - public static int doPMD(final PMDConfiguration configuration) { - - // Load the RuleSets - final RuleSetLoader ruleSetFactory = RuleSetLoader.fromPmdConfig(configuration); - final List ruleSets = getRuleSetsWithBenchmark(configuration.getRuleSetPaths(), ruleSetFactory); - - final Set languages = getApplicableLanguages(configuration, ruleSets); - - try { - - final List files = getApplicableFiles(configuration, languages); - Renderer renderer = configuration.createRenderer(true); - - @SuppressWarnings("PMD.CloseResource") - ViolationCounterListener violationCounter = new ViolationCounterListener(); - @SuppressWarnings("PMD.CloseResource") - GlobalReportBuilderListener reportBuilder = new GlobalReportBuilderListener(); - - List allListeners = listOf( - reportBuilder, - renderer.newListener(), - violationCounter); - try (GlobalAnalysisListener listener = GlobalAnalysisListener.tee(allListeners)) { - try (TimedOperation ignored = TimeTracker.startOperation(TimedOperationCategory.FILE_PROCESSING)) { - processFiles(configuration, ruleSets, files, listener); - } - } - - Report report = reportBuilder.getResult(); - if (!report.getProcessingErrors().isEmpty()) { - printErrorDetected(report.getProcessingErrors().size()); - } - - return violationCounter.getResult(); - } catch (final Exception e) { - log.error("Exception during processing: {}", e.toString()); // only exception without stacktrace - log.debug("Exception during processing", e); // with stacktrace - printErrorDetected(1); - return PMDCommandLineInterface.NO_ERRORS_STATUS; // fixme? - } finally { - /* - * Make sure it's our own classloader before attempting to close it.... - * Maven + Jacoco provide us with a cloaseable classloader that if closed - * will throw a ClassNotFoundException. - */ - if (configuration.getClassLoader() instanceof ClasspathClassLoader) { - IOUtil.tryCloseClassLoader(configuration.getClassLoader()); - } + if (stats.getNumErrors() > 0) { + printErrorDetected(stats.getNumErrors()); } + + return stats; } - private static List getRuleSetsWithBenchmark(List rulesetPaths, RuleSetLoader factory) { - try (TimedOperation ignored = TimeTracker.startOperation(TimedOperationCategory.LOAD_RULES)) { - List ruleSets; - try { - ruleSets = factory.loadFromResources(rulesetPaths); - printRuleNamesInDebug(ruleSets); - if (isEmpty(ruleSets)) { - String msg = "No rules found. Maybe you misspelled a rule name? (" - + String.join(",", rulesetPaths) + ')'; - log.error(msg); - throw new IllegalArgumentException(msg); - } - } catch (RuleSetLoadException rsnfe) { - log.error("Ruleset not found", rsnfe); - throw rsnfe; - } - return ruleSets; - } - } - private static boolean isEmpty(List rsets) { - return rsets.stream().noneMatch(it -> it.size() > 0); - } - - /** - * If in debug modus, print the names of the rules. - * - * @param rulesets the RuleSets to print - */ - private static void printRuleNamesInDebug(List rulesets) { - if (log.isDebugEnabled()) { - for (RuleSet rset : rulesets) { - for (Rule r : rset.getRules()) { - log.debug("Loaded rule {}", r.getName()); - } - } + static void encourageToUseIncrementalAnalysis(final PMDConfiguration configuration) { + if (!configuration.isIgnoreIncrementalAnalysis() + && configuration.getAnalysisCache() instanceof NoopAnalysisCache + && log.isWarnEnabled()) { + final String version = + PMDVersion.isUnknown() || PMDVersion.isSnapshot() ? "latest" : "pmd-" + PMDVersion.VERSION; + log.warn("This analysis could be faster, please consider using Incremental Analysis: " + + "https://pmd.github.io/{}/pmd_userdocs_incremental_analysis.html", version); } } @@ -249,227 +123,25 @@ public final class PMD { * @return Report in which violations are accumulated * * @throws Exception If there was a problem when opening or closing the renderers + * + * @deprecated Use {@link PmdAnalysis} */ - @SuppressWarnings("PMD.CloseResource") + @Deprecated public static Report processFiles(PMDConfiguration configuration, List ruleSets, Collection files, List renderers) throws Exception { - - GlobalAnalysisListener rendererListeners = createComposedRendererListener(renderers); - GlobalReportBuilderListener reportBuilder = new GlobalReportBuilderListener(); - - List allListeners = listOf(reportBuilder, rendererListeners); - - try (GlobalAnalysisListener listener = GlobalAnalysisListener.tee(allListeners)) { - processFiles(configuration, ruleSets, new ArrayList<>(files), listener); + try (PmdAnalysis pmd = PmdAnalysis.create(configuration)) { + pmd.addRuleSets(ruleSets); + pmd.addRenderers(renderers); + @SuppressWarnings("PMD.CloseResource") + GlobalReportBuilderListener reportBuilder = new GlobalReportBuilderListener(); + List sortedFiles = new ArrayList<>(files); + sortedFiles.sort(Comparator.comparing(ds -> ds.getNiceFileName(false, ""))); + pmd.performAnalysisImpl(listOf(reportBuilder), sortedFiles); + return reportBuilder.getResult(); } - - return reportBuilder.getResult(); - } - - private static GlobalAnalysisListener createComposedRendererListener(List renderers) throws Exception { - if (renderers.isEmpty()) { - return GlobalAnalysisListener.noop(); - } - - List rendererListeners = new ArrayList<>(renderers.size()); - for (Renderer renderer : renderers) { - try { - rendererListeners.add(renderer.newListener()); - } catch (IOException ioe) { - // close listeners so far, throw their close exception or the ioe - IOUtil.ensureClosed(rendererListeners, ioe); - throw AssertionUtil.shouldNotReachHere("ensureClosed should have thrown"); - } - } - return GlobalAnalysisListener.tee(rendererListeners); - } - - - /** - * Run PMD using the given configuration. This replaces the other overload. - * - * @param configuration Configuration for the run. Note that the files, - * and rulesets, are ignored, as they are supplied - * as parameters - * @param ruleSets Parsed rulesets - * @param files Files to process, will be closed by this method. - * @param listener Listener to which analysis events are forwarded. - * The listener is NOT closed by this routine and should - * be closed by the caller. - * - * @throws Exception If there was a problem when opening or closing the renderers - */ - public static void processFiles(PMDConfiguration configuration, - List ruleSets, - List files, - GlobalAnalysisListener listener) throws Exception { - - final RuleSets rs = new RuleSets(ruleSets); - - // todo Just like we throw for invalid properties, "broken rules" - // shouldn't be a "config error". This is the only instance of - // config errors... - - for (final Rule rule : removeBrokenRules(rs)) { - listener.onConfigError(new Report.ConfigurationError(rule, rule.dysfunctionReason())); - } - - encourageToUseIncrementalAnalysis(configuration); - - List sortedFiles = sortFiles(configuration, files); - - // Make sure the cache is listening for analysis results - listener = GlobalAnalysisListener.tee(listOf(listener, configuration.getAnalysisCache())); - - configuration.getAnalysisCache().checkValidity(rs, configuration.getClassLoader()); - - Exception ex = null; - try (AbstractPMDProcessor processor = AbstractPMDProcessor.newFileProcessor(configuration)) { - processor.processFiles(rs, sortedFiles, listener); - } catch (Exception e) { - ex = e; - } finally { - configuration.getAnalysisCache().persist(); - IOUtil.ensureClosed(sortedFiles, ex); - } - } - - - /** - * Remove and return the misconfigured rules from the rulesets and log them - * for good measure. - * - * @param ruleSets RuleSets to prune of broken rules. - * - * @return Set - */ - private static Set removeBrokenRules(final RuleSets ruleSets) { - final Set brokenRules = new HashSet<>(); - ruleSets.removeDysfunctionalRules(brokenRules); - - for (final Rule rule : brokenRules) { - log.warn("Removed misconfigured rule: {} cause: {}", rule.getName(), rule.dysfunctionReason()); - } - - return brokenRules; - } - - - private static List sortFiles(final PMDConfiguration configuration, Collection files) { - // the input collection may be unmodifiable - List result = new ArrayList<>(files); - - if (configuration.isStressTest()) { - // randomize processing order - Collections.shuffle(result); - } else { - final boolean useShortNames = configuration.isReportShortNames(); - final String inputPaths = configuration.getInputPaths(); - result.sort((left, right) -> { - String leftString = left.getNiceFileName(useShortNames, inputPaths); - String rightString = right.getNiceFileName(useShortNames, inputPaths); - return leftString.compareTo(rightString); - }); - } - - return result; - } - - private static void encourageToUseIncrementalAnalysis(final PMDConfiguration configuration) { - if (!configuration.isIgnoreIncrementalAnalysis() - && configuration.getAnalysisCache() instanceof NoopAnalysisCache - && log.isWarnEnabled()) { - final String version = - PMDVersion.isUnknown() || PMDVersion.isSnapshot() ? "latest" : "pmd-" + PMDVersion.VERSION; - log.warn("This analysis could be faster, please consider using Incremental Analysis: " - + "https://pmd.github.io/{}/pmd_userdocs_incremental_analysis.html", version); - } - } - - /** - * Determines all the files, that should be analyzed by PMD. - * - * @param configuration - * contains either the file path or the DB URI, from where to - * load the files - * @param languages - * used to filter by file extension - * @return List of {@link DataSource} of files - */ - public static List getApplicableFiles(PMDConfiguration configuration, Set languages) throws IOException { - try (TimedOperation ignored = TimeTracker.startOperation(TimedOperationCategory.COLLECT_FILES)) { - return internalGetApplicableFiles(configuration, languages); - } - } - - private static List internalGetApplicableFiles(PMDConfiguration configuration, - Set languages) throws IOException { - FilenameFilter fileSelector = configuration.isForceLanguageVersion() ? new AcceptAllFilenames() : new LanguageFilenameFilter(languages); - List files = new ArrayList<>(); - - if (null != configuration.getInputPaths()) { - files.addAll(FileUtil.collectFiles(configuration.getInputPaths(), fileSelector)); - } - - if (null != configuration.getInputUri()) { - String uriString = configuration.getInputUri(); - files.addAll(getURIDataSources(uriString)); - } - - if (null != configuration.getInputFilePath()) { - String inputFilePath = configuration.getInputFilePath(); - File file = new File(inputFilePath); - if (!file.exists()) { - throw new FileNotFoundException(inputFilePath); - } - - try { - String filePaths = FileUtil.readFilelist(file); - files.addAll(FileUtil.collectFiles(filePaths, fileSelector)); - } catch (IOException ex) { - throw new IOException("Problem with Input File Path: " + inputFilePath, ex); - } - - } - - if (null != configuration.getIgnoreFilePath()) { - String ignoreFilePath = configuration.getIgnoreFilePath(); - File file = new File(ignoreFilePath); - if (!file.exists()) { - throw new FileNotFoundException(ignoreFilePath); - } - - try { - String filePaths = FileUtil.readFilelist(file); - files.removeAll(FileUtil.collectFiles(filePaths, fileSelector)); - } catch (IOException ex) { - log.error("Problem with Ignore File", ex); - throw new RuntimeException("Problem with Ignore File Path: " + ignoreFilePath, ex); - } - } - return files; - } - - private static Set getApplicableLanguages(final PMDConfiguration configuration, final List ruleSets) { - final Set languages = new HashSet<>(); - final LanguageVersionDiscoverer discoverer = configuration.getLanguageVersionDiscoverer(); - - for (final RuleSet ruleSet : ruleSets) { - for (Rule rule : ruleSet.getRules()) { - final Language ruleLanguage = rule.getLanguage(); - if (!languages.contains(ruleLanguage)) { - final LanguageVersion version = discoverer.getDefaultLanguageVersion(ruleLanguage); - if (RuleSet.applies(rule, version)) { - languages.add(ruleLanguage); - log.debug("Using {} version: {}", ruleLanguage.getShortName(), version.getShortName()); - } - } - } - } - return languages; } /** @@ -483,23 +155,6 @@ public final class PMD { PMDCommandLineInterface.setStatusCodeOrExit(exitCode.toInt()); } - /** - * Parses the command line arguments and executes PMD. Returns the - * exit code without exiting the VM. - * - * @param args command line arguments - * - * @return the exit code, where 0 means successful execution, - * 1 means error, 4 means there have been - * violations found. - * - * @deprecated Use {@link #runPmd(String...)}. - */ - @Deprecated - public static int run(final String[] args) { - return runPmd(args).toInt(); - } - /** * Parses the command line arguments and executes PMD. Returns the * status code without exiting the VM. Note that the arguments parsing @@ -515,6 +170,7 @@ public final class PMD { public static StatusCode runPmd(String... args) { PmdParametersParseResult parseResult = PmdParametersParseResult.extractParameters(args); + // todo these warnings/errors should be output on a PmdRenderer if (!parseResult.getDeprecatedOptionsUsed().isEmpty()) { Entry first = parseResult.getDeprecatedOptionsUsed().entrySet().iterator().next(); log.warn("Some deprecated options were used on the command-line, including {}", first.getKey()); @@ -560,44 +216,68 @@ public final class PMD { // only reconfigure logging, if debug flag was used on command line // otherwise just use whatever is in conf/simplelogger.properties which happens automatically if (configuration.isDebug()) { - Slf4jSimpleConfiguration.reconfigureDefaultLogLevel(Level.DEBUG); + Slf4jSimpleConfiguration.reconfigureDefaultLogLevel(Level.TRACE); // need to reload the logger with the new configuration log = LoggerFactory.getLogger(PMD.class); } + // create a top-level reporter + // TODO CLI errors should also be reported through this + // TODO this should not use the logger as backend, otherwise without + // slf4j implementation binding, errors are entirely ignored. + MessageReporter pmdReporter = new SimpleMessageReporter(log); // always install java.util.logging to slf4j bridge Slf4jSimpleConfiguration.installJulBridge(); // logging, mostly for testing purposes Level defaultLogLevel = Slf4jSimpleConfiguration.getDefaultLogLevel(); - log.atLevel(defaultLogLevel).log("Log level is at {}", defaultLogLevel); + log.info("Log level is at {}", defaultLogLevel); - StatusCode status; try { - int violations = PMD.doPMD(configuration); - if (violations > 0 && configuration.isFailOnViolation()) { - status = StatusCode.VIOLATIONS_FOUND; - } else { - status = StatusCode.OK; + PmdAnalysis pmd; + try { + pmd = PmdAnalysis.create(configuration, pmdReporter); + } catch (Exception e) { + pmdReporter.errorEx("Could not initialize analysis", e); + return StatusCode.ERROR; } - } catch (Exception e) { - System.err.println(e.getMessage()); - status = StatusCode.ERROR; - } finally { - if (configuration.isBenchmark()) { - final TimingReport timingReport = TimeTracker.stopGlobalTracking(); - - // TODO get specified report format from config - final TimingReportRenderer renderer = new TextTimingReportRenderer(); - try { - // Don't close this writer, we don't want to close stderr - @SuppressWarnings("PMD.CloseResource") - final Writer writer = new OutputStreamWriter(System.err); - renderer.render(timingReport, writer); - } catch (final IOException e) { - System.err.println(e.getMessage()); + try { + ReportStats stats; + stats = PMD.runAndReturnStats(pmd); + if (pmdReporter.numErrors() > 0) { + // processing errors are ignored + return StatusCode.ERROR; + } else if (stats.getNumViolations() > 0 && configuration.isFailOnViolation()) { + return StatusCode.VIOLATIONS_FOUND; + } else { + return StatusCode.OK; } + } finally { + pmd.close(); + } + + } catch (Exception e) { + pmdReporter.errorEx("Exception while running PMD.", e); + printErrorDetected(1); + return StatusCode.ERROR; + } finally { + finishBenchmarker(configuration); + } + } + + private static void finishBenchmarker(PMDConfiguration configuration) { + if (configuration.isBenchmark()) { + final TimingReport timingReport = TimeTracker.stopGlobalTracking(); + + // TODO get specified report format from config + final TimingReportRenderer renderer = new TextTimingReportRenderer(); + try { + // Don't close this writer, we don't want to close stderr + @SuppressWarnings("PMD.CloseResource") + final Writer writer = new OutputStreamWriter(System.err); + renderer.render(timingReport, writer); + } catch (final IOException e) { + System.err.println(e.getMessage()); } } - return status; } /** @@ -633,10 +313,4 @@ public final class PMD { } - private static final class AcceptAllFilenames implements FilenameFilter { - @Override - public boolean accept(File dir, String name) { - return true; - } - } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/PMDConfiguration.java b/pmd-core/src/main/java/net/sourceforge/pmd/PMDConfiguration.java index 1b9931f537..bc6e618aa2 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/PMDConfiguration.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/PMDConfiguration.java @@ -13,12 +13,14 @@ import java.util.Objects; import java.util.Properties; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import net.sourceforge.pmd.annotation.DeprecatedUntil700; import net.sourceforge.pmd.cache.AnalysisCache; import net.sourceforge.pmd.cache.FileAnalysisCache; import net.sourceforge.pmd.cache.NoopAnalysisCache; import net.sourceforge.pmd.cli.PmdParametersParseResult; +import net.sourceforge.pmd.internal.util.AssertionUtil; import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.LanguageVersion; import net.sourceforge.pmd.lang.LanguageVersionDiscoverer; @@ -44,7 +46,7 @@ import net.sourceforge.pmd.util.ClasspathClassLoader; * {@link #getClassLoader()} *

  • A means to configure a ClassLoader using a prepended classpath String, * instead of directly setting it programmatically. - * {@link #prependClasspath(String)}
  • + * {@link #prependAuxClasspath(String)} *
  • A LanguageVersionDiscoverer instance, which defaults to using the default * LanguageVersion of each Language. Means are provided to change the * LanguageVersion for each Language. @@ -88,15 +90,19 @@ import net.sourceforge.pmd.util.ClasspathClassLoader; * */ public class PMDConfiguration extends AbstractConfiguration { + + /** The default suppress marker string. */ + public static final String DEFAULT_SUPPRESS_MARKER = "NOPMD"; + // General behavior options - private String suppressMarker = PMD.SUPPRESS_MARKER; + private String suppressMarker = DEFAULT_SUPPRESS_MARKER; private int threads = Runtime.getRuntime().availableProcessors(); private ClassLoader classLoader = getClass().getClassLoader(); private LanguageVersionDiscoverer languageVersionDiscoverer = new LanguageVersionDiscoverer(); private LanguageVersion forceLanguageVersion; // Rule and source file options - private List ruleSets; + private List ruleSets = new ArrayList<>(); private RulePriority minimumPriority = RulePriority.LOW; private String inputPaths; private String inputUri; @@ -197,13 +203,46 @@ public class PMDConfiguration extends AbstractConfiguration { * if the given classpath is invalid (e.g. does not exist) * @see PMDConfiguration#setClassLoader(ClassLoader) * @see ClasspathClassLoader + * + * @deprecated Use {@link #prependAuxClasspath(String)}, which doesn't + * throw a checked {@link IOException} */ + @Deprecated public void prependClasspath(String classpath) throws IOException { - if (classLoader == null) { - classLoader = PMDConfiguration.class.getClassLoader(); + try { + prependAuxClasspath(classpath); + } catch (IllegalArgumentException e) { + throw new IOException(e); } - if (classpath != null) { - classLoader = new ClasspathClassLoader(classpath, classLoader); + } + + /** + * Prepend the specified classpath like string to the current ClassLoader of + * the configuration. If no ClassLoader is currently configured, the + * ClassLoader used to load the {@link PMDConfiguration} class will be used + * as the parent ClassLoader of the created ClassLoader. + * + *

    If the classpath String looks like a URL to a file (i.e. starts with + * file://) the file will be read with each line representing + * an entry on the classpath.

    + * + * @param classpath The prepended classpath. + * + * @throws IllegalArgumentException if the given classpath is invalid (e.g. does not exist) + * @see PMDConfiguration#setClassLoader(ClassLoader) + */ + public void prependAuxClasspath(String classpath) { + try { + if (classLoader == null) { + classLoader = PMDConfiguration.class.getClassLoader(); + } + if (classpath != null) { + classLoader = new ClasspathClassLoader(classpath, classLoader); + } + } catch (IOException e) { + // Note: IOExceptions shouldn't appear anymore, they should already be converted + // to IllegalArgumentException in ClasspathClassLoader. + throw new IllegalArgumentException(e); } } @@ -244,6 +283,7 @@ public class PMDConfiguration extends AbstractConfiguration { */ public void setForceLanguageVersion(LanguageVersion forceLanguageVersion) { this.forceLanguageVersion = forceLanguageVersion; + languageVersionDiscoverer.setForcedVersion(forceLanguageVersion); } /** @@ -253,6 +293,7 @@ public class PMDConfiguration extends AbstractConfiguration { * the LanguageVersion */ public void setDefaultLanguageVersion(LanguageVersion languageVersion) { + Objects.requireNonNull(languageVersion); setDefaultLanguageVersions(Arrays.asList(languageVersion)); } @@ -265,6 +306,7 @@ public class PMDConfiguration extends AbstractConfiguration { */ public void setDefaultLanguageVersions(List languageVersions) { for (LanguageVersion languageVersion : languageVersions) { + Objects.requireNonNull(languageVersion); languageVersionDiscoverer.setDefaultLanguageVersion(languageVersion); } } @@ -310,7 +352,10 @@ public class PMDConfiguration extends AbstractConfiguration { */ @Deprecated @DeprecatedUntil700 - public String getRuleSets() { + public @Nullable String getRuleSets() { + if (ruleSets.isEmpty()) { + return null; + } return String.join(",", ruleSets); } @@ -319,17 +364,34 @@ public class PMDConfiguration extends AbstractConfiguration { * * @see RuleSetLoader#loadFromResource(String) */ - public List getRuleSetPaths() { + public @NonNull List<@NonNull String> getRuleSetPaths() { return ruleSets; } /** - * Sets the rulesets. + * Sets the list of ruleset paths to load when starting the analysis. + * + * @param ruleSetPaths A list of ruleset paths, understandable by {@link RuleSetLoader#loadFromResource(String)}. * * @throws NullPointerException If the parameter is null */ - public void setRuleSets(@NonNull List ruleSets) { - this.ruleSets = new ArrayList<>(ruleSets); + public void setRuleSets(@NonNull List<@NonNull String> ruleSetPaths) { + AssertionUtil.requireParamNotNull("ruleSetPaths", ruleSetPaths); + AssertionUtil.requireContainsNoNullValue("ruleSetPaths", ruleSetPaths); + this.ruleSets = new ArrayList<>(ruleSetPaths); + } + + /** + * Add a new ruleset paths to load when starting the analysis. + * This list is initially empty. + * + * @param rulesetPath A ruleset path, understandable by {@link RuleSetLoader#loadFromResource(String)}. + * + * @throws NullPointerException If the parameter is null + */ + public void addRuleSet(@NonNull String rulesetPath) { + AssertionUtil.requireParamNotNull("rulesetPath", rulesetPath); + this.ruleSets.add(rulesetPath); } /** @@ -337,12 +399,16 @@ public class PMDConfiguration extends AbstractConfiguration { * * @param ruleSets the rulesets to set * - * @deprecated Use {@link #setRuleSets(List)} + * @deprecated Use {@link #setRuleSets(List)} or {@link #addRuleSet(String)}. */ @Deprecated @DeprecatedUntil700 - public void setRuleSets(String ruleSets) { - this.ruleSets = Arrays.asList(ruleSets.split(",")); + public void setRuleSets(@Nullable String ruleSets) { + if (ruleSets == null) { + this.ruleSets = new ArrayList<>(); + } else { + this.ruleSets = new ArrayList<>(Arrays.asList(ruleSets.split(","))); + } } /** diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/PmdAnalysis.java b/pmd-core/src/main/java/net/sourceforge/pmd/PmdAnalysis.java new file mode 100644 index 0000000000..1b74fa5ab8 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/PmdAnalysis.java @@ -0,0 +1,403 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd; + +import static net.sourceforge.pmd.util.CollectionUtil.listOf; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.sourceforge.pmd.Report.GlobalReportBuilderListener; +import net.sourceforge.pmd.annotation.InternalApi; +import net.sourceforge.pmd.benchmark.TimeTracker; +import net.sourceforge.pmd.benchmark.TimedOperation; +import net.sourceforge.pmd.benchmark.TimedOperationCategory; +import net.sourceforge.pmd.cache.AnalysisCacheListener; +import net.sourceforge.pmd.internal.util.AssertionUtil; +import net.sourceforge.pmd.internal.util.FileCollectionUtil; +import net.sourceforge.pmd.lang.Language; +import net.sourceforge.pmd.lang.LanguageVersion; +import net.sourceforge.pmd.lang.LanguageVersionDiscoverer; +import net.sourceforge.pmd.lang.document.FileCollector; +import net.sourceforge.pmd.processor.AbstractPMDProcessor; +import net.sourceforge.pmd.renderers.Renderer; +import net.sourceforge.pmd.reporting.GlobalAnalysisListener; +import net.sourceforge.pmd.util.ClasspathClassLoader; +import net.sourceforge.pmd.util.IOUtil; +import net.sourceforge.pmd.util.datasource.DataSource; +import net.sourceforge.pmd.util.log.MessageReporter; +import net.sourceforge.pmd.util.log.internal.SimpleMessageReporter; + +/** + * Main programmatic API of PMD. Create and configure a {@link PMDConfiguration}, + * then use {@link #create(PMDConfiguration)} to obtain an instance. + * You can perform additional configuration on the instance, eg adding + * files to process, or additional rulesets and renderers. Then, call + * {@link #performAnalysis()}. Example: + *
    {@code
    + *   PMDConfiguration config = new PMDConfiguration();
    + *   config.setDefaultLanguageVersion(LanguageRegistry.findLanguageByTerseName("java").getVersion("11"));
    + *   config.setInputPaths("src/main/java");
    + *   config.prependClasspath("target/classes");
    + *   config.setMinimumPriority(RulePriority.HIGH);
    + *   config.addRuleSet("rulesets/java/quickstart.xml");
    + *   config.setReportFormat("xml");
    + *   config.setReportFile("target/pmd-report.xml");
    + *
    + *   try (PmdAnalysis pmd = PmdAnalysis.create(config)) {
    + *     // note: don't use `config` once a PmdAnalysis has been created.
    + *     // optional: add more rulesets
    + *     pmd.addRuleSet(pmd.newRuleSetLoader().loadFromResource("custom-ruleset.xml"));
    + *     // optional: add more files
    + *     pmd.files().addFile(Paths.get("src", "main", "more-java", "ExtraSource.java"));
    + *     // optional: add more renderers
    + *     pmd.addRenderer(renderer);
    + *
    + *     pmd.performAnalysis();
    + *   }
    + * }
    + * + */ +public final class PmdAnalysis implements AutoCloseable { + + private static final Logger LOG = LoggerFactory.getLogger(PmdAnalysis.class); + + private final FileCollector collector; + private final List renderers = new ArrayList<>(); + private final List listeners = new ArrayList<>(); + private final List ruleSets = new ArrayList<>(); + private final PMDConfiguration configuration; + private final MessageReporter reporter; + + private boolean closed; + + /** + * Constructs a new instance. The files paths (input files, filelist, + * exclude list, etc) given in the configuration are collected into + * the file collector ({@link #files()}), but more can be added + * programmatically using the file collector. + */ + private PmdAnalysis(PMDConfiguration config, MessageReporter reporter) { + this.configuration = config; + this.reporter = reporter; + this.collector = FileCollector.newCollector( + config.getLanguageVersionDiscoverer(), + reporter + ); + } + + /** + * Constructs a new instance from a configuration. + * + *
      + *
    • The files paths (input files, filelist, + * exclude list, etc) are explored and the files to analyse are + * collected into the file collector ({@link #files()}). + * More can be added programmatically using the file collector. + *
    • The rulesets given in the configuration are loaded ({@link PMDConfiguration#getRuleSets()}) + *
    • A renderer corresponding to the parameters of the configuration + * is created and added (but not started). + *
    + */ + public static PmdAnalysis create(PMDConfiguration config) { + return create( + config, + new SimpleMessageReporter(LoggerFactory.getLogger(PmdAnalysis.class)) + ); + } + + @InternalApi + static PmdAnalysis create(PMDConfiguration config, MessageReporter reporter) { + PmdAnalysis pmd = new PmdAnalysis(config, reporter); + + // note: do not filter files by language + // they could be ignored later. The problem is if you call + // addRuleSet later, then you could be enabling new languages + // So the files should not be pruned in advance + FileCollectionUtil.collectFiles(config, pmd.files()); + + if (config.getReportFormat() != null) { + Renderer renderer = config.createRenderer(true); + pmd.addRenderer(renderer); + } + + if (!config.getRuleSetPaths().isEmpty()) { + final RuleSetLoader ruleSetLoader = pmd.newRuleSetLoader(); + final List ruleSets = ruleSetLoader.loadRuleSetsWithoutException(config.getRuleSetPaths()); + pmd.addRuleSets(ruleSets); + } + return pmd; + } + + // test only + List rulesets() { + return ruleSets; + } + + // test only + List renderers() { + return renderers; + } + + + /** + * Returns the file collector for the analysed sources. + */ + public FileCollector files() { + return collector; // todo user can close collector programmatically + } + + /** + * Returns a new ruleset loader, which can be used to create new + * rulesets (add them then with {@link #addRuleSet(RuleSet)}). + * + *
    {@code
    +     * try (PmdAnalysis pmd = create(config)) {
    +     *     pmd.addRuleSet(pmd.newRuleSetLoader().loadFromResource("custom-ruleset.xml"));
    +     * }
    +     * }
    + */ + public RuleSetLoader newRuleSetLoader() { + RuleSetLoader loader = RuleSetLoader.fromPmdConfig(configuration); + loader.setReporter(this.reporter); + return loader; + } + + /** + * Add a new renderer. The given renderer must not already be started, + * it will be started by {@link #performAnalysis()}. + * + * @throws NullPointerException If the parameter is null + */ + public void addRenderer(Renderer renderer) { + AssertionUtil.requireParamNotNull("renderer", renderer); + this.renderers.add(renderer); + } + + /** + * Add several renderers at once. + * + * @throws NullPointerException If the parameter is null, or any of its items is null. + */ + public void addRenderers(Collection renderers) { + renderers.forEach(this::addRenderer); + } + + /** + * Add a new listener. As per the contract of {@link GlobalAnalysisListener}, + * this object must be ready for interaction. However, nothing will + * be done with the listener until {@link #performAnalysis()} is called. + * The listener will be closed by {@link #performAnalysis()}, or + * {@link #close()}, whichever happens first. + * + * @throws NullPointerException If the parameter is null + */ + public void addListener(GlobalAnalysisListener listener) { + AssertionUtil.requireParamNotNull("listener", listener); + this.listeners.add(listener); + } + + /** + * Add several listeners at once. + * + * @throws NullPointerException If the parameter is null, or any of its items is null. + * @see #addListener(GlobalAnalysisListener) + */ + public void addListeners(Collection listeners) { + listeners.forEach(this::addListener); + } + + /** + * Add a new ruleset. + * + * @throws NullPointerException If the parameter is null + */ + public void addRuleSet(RuleSet ruleSet) { + AssertionUtil.requireParamNotNull("rule set", ruleSet); + this.ruleSets.add(ruleSet); + } + + /** + * Add several rulesets at once. + * + * @throws NullPointerException If the parameter is null, or any of its items is null. + */ + public void addRuleSets(Collection ruleSets) { + ruleSets.forEach(this::addRuleSet); + } + + /** + * Returns an unmodifiable view of the ruleset list. That will be + * processed. + */ + public List getRulesets() { + return Collections.unmodifiableList(ruleSets); + } + + + /** + * Run PMD with the current state of this instance. This will start + * and finish the registered renderers, and close all + * {@linkplain #addListener(GlobalAnalysisListener) registered listeners}. + * All files collected in the {@linkplain #files() file collector} are + * processed. This does not return a report, as the analysis results + * are consumed by {@link GlobalAnalysisListener} instances (of which + * Renderers are a special case). Note that this does + * not throw, errors are instead accumulated into a {@link MessageReporter}. + */ + public void performAnalysis() { + performAnalysisImpl(Collections.emptyList()); + } + + /** + * Run PMD with the current state of this instance. This will start + * and finish the registered renderers. All files collected in the + * {@linkplain #files() file collector} are processed. Returns the + * output report. Note that this does not throw, errors are instead + * accumulated into a {@link MessageReporter}. + */ + public Report performAnalysisAndCollectReport() { + try (GlobalReportBuilderListener reportBuilder = new GlobalReportBuilderListener()) { + performAnalysisImpl(listOf(reportBuilder)); // closes the report builder + return reportBuilder.getResultImpl(); + } + } + + void performAnalysisImpl(List extraListeners) { + try (FileCollector files = collector) { + files.filterLanguages(getApplicableLanguages()); + List dataSources = FileCollectionUtil.collectorToDataSource(files); + performAnalysisImpl(extraListeners, dataSources); + } + } + + void performAnalysisImpl(List extraListeners, List dataSources) { + RuleSets rulesets = new RuleSets(this.ruleSets); + + GlobalAnalysisListener listener; + try { + @SuppressWarnings("PMD.CloseResource") AnalysisCacheListener cacheListener = new AnalysisCacheListener(configuration.getAnalysisCache(), rulesets, configuration.getClassLoader()); + listener = GlobalAnalysisListener.tee(listOf(createComposedRendererListener(renderers), GlobalAnalysisListener.tee(listeners), GlobalAnalysisListener.tee(extraListeners), cacheListener)); + } catch (Exception e) { + reporter.errorEx("Exception while initializing analysis listeners", e); + throw new RuntimeException("Exception while initializing analysis listeners", e); + } + + try (TimedOperation ignored = TimeTracker.startOperation(TimedOperationCategory.FILE_PROCESSING)) { + + for (final Rule rule : removeBrokenRules(rulesets)) { + // todo Just like we throw for invalid properties, "broken rules" + // shouldn't be a "config error". This is the only instance of + // config errors... + listener.onConfigError(new Report.ConfigurationError(rule, rule.dysfunctionReason())); + } + + PMD.encourageToUseIncrementalAnalysis(configuration); + try (AbstractPMDProcessor processor = AbstractPMDProcessor.newFileProcessor(configuration)) { + processor.processFiles(rulesets, dataSources, listener); + } + } finally { + try { + listener.close(); + } catch (Exception e) { + reporter.errorEx("Exception while initializing analysis listeners", e); + // todo better exception + throw new RuntimeException("Exception while initializing analysis listeners", e); + } + } + } + + + private static GlobalAnalysisListener createComposedRendererListener(List renderers) throws Exception { + if (renderers.isEmpty()) { + return GlobalAnalysisListener.noop(); + } + + List rendererListeners = new ArrayList<>(renderers.size()); + for (Renderer renderer : renderers) { + try { + @SuppressWarnings("PMD.CloseResource") + GlobalAnalysisListener listener = + Objects.requireNonNull(renderer.newListener(), "Renderer should provide non-null listener"); + rendererListeners.add(listener); + } catch (Exception ioe) { + // close listeners so far, throw their close exception or the ioe + IOUtil.ensureClosed(rendererListeners, ioe); + throw AssertionUtil.shouldNotReachHere("ensureClosed should have thrown"); + } + } + return GlobalAnalysisListener.tee(rendererListeners); + } + + private Set getApplicableLanguages() { + final Set languages = new HashSet<>(); + final LanguageVersionDiscoverer discoverer = configuration.getLanguageVersionDiscoverer(); + + for (RuleSet ruleSet : ruleSets) { + for (final Rule rule : ruleSet.getRules()) { + final Language ruleLanguage = rule.getLanguage(); + if (!languages.contains(ruleLanguage)) { + final LanguageVersion version = discoverer.getDefaultLanguageVersion(ruleLanguage); + if (RuleSet.applies(rule, version)) { + languages.add(ruleLanguage); + LOG.trace("Using {} version ''{}''", version.getLanguage().getName(), version.getTerseName()); + } + } + } + } + return languages; + } + + /** + * Remove and return the misconfigured rules from the rulesets and log them + * for good measure. + */ + private Set removeBrokenRules(final RuleSets ruleSets) { + final Set brokenRules = new HashSet<>(); + ruleSets.removeDysfunctionalRules(brokenRules); + + for (final Rule rule : brokenRules) { + reporter.warn("Removed misconfigured rule: {} cause: {}", + rule.getName(), rule.dysfunctionReason()); + } + + return brokenRules; + } + + + public MessageReporter getReporter() { + return reporter; + } + + @Override + public void close() { + if (closed) { + return; + } + closed = true; + collector.close(); + + // close listeners if analysis is not run. + IOUtil.closeAll(listeners); + + /* + * Make sure it's our own classloader before attempting to close it.... + * Maven + Jacoco provide us with a cloaseable classloader that if closed + * will throw a ClassNotFoundException. + */ + if (configuration.getClassLoader() instanceof ClasspathClassLoader) { + IOUtil.tryCloseClassLoader(configuration.getClassLoader()); + } + } + +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/Report.java b/pmd-core/src/main/java/net/sourceforge/pmd/Report.java index 9513f5901e..78f4861cc4 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/Report.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/Report.java @@ -28,7 +28,7 @@ import net.sourceforge.pmd.util.datasource.DataSource; * and configuration errors. * *

    A report may be created by a {@link GlobalReportBuilderListener} that you - * use as the {@link GlobalAnalysisListener} in {@linkplain PMD#processFiles(PMDConfiguration, List, List, GlobalAnalysisListener) PMD's entry point}. + * use as the {@linkplain GlobalAnalysisListener} in {@link PmdAnalysis#performAnalysisAndCollectReport() PMD's entry point}. * You can also create one manually with {@link #buildReport(Consumer)}. */ public final class Report { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/Rule.java b/pmd-core/src/main/java/net/sourceforge/pmd/Rule.java index 414fc8d2b3..e37c849670 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/Rule.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/Rule.java @@ -6,10 +6,8 @@ package net.sourceforge.pmd; import java.util.List; -import net.sourceforge.pmd.annotation.Experimental; import net.sourceforge.pmd.lang.Language; import net.sourceforge.pmd.lang.LanguageVersion; -import net.sourceforge.pmd.lang.ast.AstProcessingStage; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.rule.RuleTargetSelector; import net.sourceforge.pmd.properties.PropertySource; @@ -251,31 +249,6 @@ public interface Rule extends PropertySource { void setPriority(RulePriority priority); - /** - * Returns true if this rule depends on the given processing stage - * to run. If so, any ruleset including this rule, in which the rule - * is not misconfigured, will execute the analysis reified in the - * given stage before applying rules on the AST. - * - *

    The default returns false. Each language should implement this - * method in its abstract rule base class, and probably mark its - * implementation as final for consistency within the language - * implementation. AST processing stages are language-specific, and - * any non-trivial implementation should throw an {@link IllegalArgumentException} - * when given a stage that isn't defined on the language of the rule. - * - * @param stage Processing stage for which to check for a dependency. - * - * @return True if this rule depends on the given processing stage. - * - * @since 7.0.0 - */ - @Experimental - default boolean dependsOn(AstProcessingStage stage) { - return true; - } - - /** * Returns the object that selects the nodes to which this rule applies. * The selected nodes will be handed to {@link #apply(Node, RuleContext)}. diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/RuleSetLoader.java b/pmd-core/src/main/java/net/sourceforge/pmd/RuleSetLoader.java index e177ee4eb6..1a75e5d3a5 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/RuleSetLoader.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/RuleSetLoader.java @@ -16,11 +16,16 @@ import java.util.Properties; import org.apache.commons.lang3.StringUtils; import org.checkerframework.checker.nullness.qual.NonNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.Language; import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.util.CollectionUtil; import net.sourceforge.pmd.util.ResourceLoader; +import net.sourceforge.pmd.util.log.MessageReporter; +import net.sourceforge.pmd.util.log.internal.NoopReporter; /** * Configurable object to load rulesets from XML resources. @@ -29,12 +34,26 @@ import net.sourceforge.pmd.util.ResourceLoader; * or some such overload. */ public final class RuleSetLoader { + private static final Logger LOG = LoggerFactory.getLogger(RuleSetLoader.class); private ResourceLoader resourceLoader = new ResourceLoader(RuleSetLoader.class.getClassLoader()); private RulePriority minimumPriority = RulePriority.LOW; private boolean warnDeprecated = true; private @NonNull RuleSetFactoryCompatibility compatFilter = RuleSetFactoryCompatibility.DEFAULT; private boolean includeDeprecatedRuleReferences = false; + private MessageReporter reporter = new NoopReporter(); // non-null + + /** + * Create a new RuleSetLoader with a default configuration. + * The defaults are described on each configuration method of this class. + */ + public RuleSetLoader() { // NOPMD UnnecessaryConstructor + // default + } + + void setReporter(MessageReporter reporter) { + this.reporter = reporter; + } /** * Specify that the given classloader should be used to resolve @@ -145,7 +164,7 @@ public final class RuleSetLoader { * * @throws RuleSetLoadException If any error occurs (eg, invalid syntax) */ - public RuleSet loadFromString(String filename, String rulesetXmlContent) { + public RuleSet loadFromString(String filename, final String rulesetXmlContent) { return loadFromResource(new RuleSetReferenceId(filename) { @Override public InputStream getInputStream(ResourceLoader rl) { @@ -171,6 +190,52 @@ public final class RuleSetLoader { return ruleSets; } + /** + * Loads a list of rulesets, if any has an error, report it on the contextual + * error reporter instead of aborting, and continue loading the rest. + * + *

    Internal API: might be published later, or maybe in PMD 7 this + * will be the default behaviour of every method of this class. + */ + @InternalApi + public List loadRuleSetsWithoutException(List rulesetPaths) { + List ruleSets = new ArrayList<>(rulesetPaths.size()); + boolean anyRules = false; + for (String path : rulesetPaths) { + try { + RuleSet ruleset = this.loadFromResource(path); + anyRules |= !ruleset.getRules().isEmpty(); + printRulesInDebug(path, ruleset); + ruleSets.add(ruleset); + } catch (RuleSetLoadException e) { + if (e.getCause() != null) { + // eg RuleSetNotFoundException + reporter.errorEx("Cannot load ruleset {0}", new Object[] { path }, e.getCause()); + } else { + reporter.errorEx("Cannot load ruleset {0}", new Object[] { path }, e); + } + } + } + if (!anyRules) { + reporter.warn("No rules found. Maybe you misspelled a rule name? ({})", + StringUtils.join(rulesetPaths, ',')); + } + return ruleSets; + } + + void printRulesInDebug(String path, RuleSet ruleset) { + if (LOG.isDebugEnabled()) { + LOG.debug("Rules loaded from {}:", path); + for (Rule rule : ruleset.getRules()) { + LOG.debug("- {} ({})", rule.getName(), rule.getLanguage().getName()); + } + } + if (ruleset.getRules().isEmpty()) { + reporter.warn("No rules found in ruleset {}", path); + } + + } + /** * Parses several resources into a list of rulesets. * diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/ant/internal/PMDTaskImpl.java b/pmd-core/src/main/java/net/sourceforge/pmd/ant/internal/PMDTaskImpl.java index 0f3f8f3114..4117010f49 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/ant/internal/PMDTaskImpl.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/ant/internal/PMDTaskImpl.java @@ -4,10 +4,9 @@ package net.sourceforge.pmd.ant.internal; -import java.io.File; -import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.StringJoiner; @@ -22,12 +21,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.event.Level; -import net.sourceforge.pmd.PMD; import net.sourceforge.pmd.PMDConfiguration; -import net.sourceforge.pmd.Rule; +import net.sourceforge.pmd.PmdAnalysis; import net.sourceforge.pmd.RulePriority; -import net.sourceforge.pmd.RuleSet; -import net.sourceforge.pmd.RuleSetLoadException; import net.sourceforge.pmd.RuleSetLoader; import net.sourceforge.pmd.ant.Formatter; import net.sourceforge.pmd.ant.PMDTask; @@ -38,11 +34,11 @@ import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.LanguageVersion; import net.sourceforge.pmd.reporting.FileAnalysisListener; import net.sourceforge.pmd.reporting.GlobalAnalysisListener; -import net.sourceforge.pmd.reporting.GlobalAnalysisListener.ViolationCounterListener; +import net.sourceforge.pmd.reporting.ReportStats; +import net.sourceforge.pmd.reporting.ReportStatsListener; import net.sourceforge.pmd.util.ClasspathClassLoader; import net.sourceforge.pmd.util.IOUtil; import net.sourceforge.pmd.util.datasource.DataSource; -import net.sourceforge.pmd.util.datasource.FileDataSource; public class PMDTaskImpl { @@ -51,7 +47,7 @@ public class PMDTaskImpl { private final List formatters = new ArrayList<>(); private final List filesets = new ArrayList<>(); private final PMDConfiguration configuration = new PMDConfiguration(); - private final String rulesetPaths; + private boolean failOnError; private boolean failOnRuleViolation; private int maxRuleViolations = 0; private String failuresPropertyName; @@ -62,12 +58,16 @@ public class PMDTaskImpl { if (task.getSuppressMarker() != null) { configuration.setSuppressMarker(task.getSuppressMarker()); } + this.failOnError = task.isFailOnError(); this.failOnRuleViolation = task.isFailOnRuleViolation(); this.maxRuleViolations = task.getMaxRuleViolations(); if (this.maxRuleViolations > 0) { this.failOnRuleViolation = true; } - this.rulesetPaths = task.getRulesetFiles() == null ? "" : task.getRulesetFiles(); + if (task.getRulesetFiles() != null) { + configuration.setRuleSets(Arrays.asList(task.getRulesetFiles().split(","))); + } + configuration.setRuleSetFactoryCompatibilityEnabled(!task.isNoRuleSetCompatibility()); if (task.getEncoding() != null) { configuration.setSourceEncoding(task.getEncoding()); @@ -100,46 +100,49 @@ public class PMDTaskImpl { private void doTask() { setupClassLoader(); - // Setup RuleSetFactory and validate RuleSets - RuleSetLoader rulesetLoader = RuleSetLoader.fromPmdConfig(configuration) - .loadResourcesWith(setupResourceLoader()); - - List rules = loadRulesets(rulesetLoader); - if (configuration.getSuppressMarker() != null) { project.log("Setting suppress marker to be " + configuration.getSuppressMarker(), Project.MSG_VERBOSE); } - @SuppressWarnings("PMD.CloseResource") - ViolationCounterListener reportSizeListener = new ViolationCounterListener(); - - final List files = new ArrayList<>(); - final List reportShortNamesPaths = new ArrayList<>(); + @SuppressWarnings("PMD.CloseResource") final List reportShortNamesPaths = new ArrayList<>(); StringJoiner fullInputPath = new StringJoiner(","); - for (FileSet fs : filesets) { - DirectoryScanner ds = fs.getDirectoryScanner(project); - for (String srcFile : ds.getIncludedFiles()) { - File file = new File(ds.getBasedir() + File.separator + srcFile); - files.add(new FileDataSource(file)); + List ruleSetPaths = expandRuleSetPaths(configuration.getRuleSetPaths()); + // don't let PmdAnalysis.create create rulesets itself. + configuration.setRuleSets(Collections.emptyList()); + + ReportStats stats; + try (PmdAnalysis pmd = PmdAnalysis.create(configuration)) { + RuleSetLoader rulesetLoader = + pmd.newRuleSetLoader().loadResourcesWith(setupResourceLoader()); + pmd.addRuleSets(rulesetLoader.loadRuleSetsWithoutException(ruleSetPaths)); + + for (FileSet fileset : filesets) { + DirectoryScanner ds = fileset.getDirectoryScanner(project); + for (String srcFile : ds.getIncludedFiles()) { + pmd.files().addFile(ds.getBasedir().toPath().resolve(srcFile)); + } + + final String commonInputPath = ds.getBasedir().getPath(); + fullInputPath.add(commonInputPath); + if (configuration.isReportShortNames()) { + reportShortNamesPaths.add(commonInputPath); + } } - final String commonInputPath = ds.getBasedir().getPath(); - fullInputPath.add(commonInputPath); - if (configuration.isReportShortNames()) { - reportShortNamesPaths.add(commonInputPath); + @SuppressWarnings("PMD.CloseResource") + ReportStatsListener reportStatsListener = new ReportStatsListener(); + pmd.addListener(getListener(reportStatsListener, reportShortNamesPaths, fullInputPath.toString())); + + pmd.performAnalysis(); + stats = reportStatsListener.getResult(); + if (failOnError && pmd.getReporter().numErrors() > 0) { + throw new BuildException("Some errors occurred while running PMD"); } } - configuration.setInputPaths(fullInputPath.toString()); - try (GlobalAnalysisListener listener = getListener(reportSizeListener, reportShortNamesPaths)) { - PMD.processFiles(configuration, rules, files, listener); - } catch (Exception e) { - throw new BuildException("Exception while closing data sources", e); - } - - int problemCount = reportSizeListener.getResult(); + int problemCount = stats.getNumViolations(); project.log(problemCount + " problems found", Project.MSG_VERBOSE); if (failuresPropertyName != null && problemCount > 0) { @@ -152,34 +155,27 @@ public class PMDTaskImpl { } } - private List loadRulesets(RuleSetLoader rulesetLoader) { - try { - // This is just used to validate and display rules. Each thread will create its own ruleset - // Substitute env variables/properties - String ruleSetString = project.replaceProperties(rulesetPaths); - - List rulesets = Arrays.asList(ruleSetString.split(",")); - List rulesetList = rulesetLoader.loadFromResources(rulesets); - if (rulesetList.isEmpty()) { - throw new BuildException("No rulesets"); - } - logRulesUsed(rulesetList); - return rulesetList; - } catch (RuleSetLoadException e) { - throw new BuildException(e.getMessage(), e); + private List expandRuleSetPaths(List ruleSetPaths) { + List paths = new ArrayList<>(ruleSetPaths); + for (int i = 0; i < paths.size(); i++) { + paths.set(i, project.replaceProperties(paths.get(i))); } + return paths; } - private @NonNull GlobalAnalysisListener getListener(ViolationCounterListener reportSizeListener, List reportShortNamesPaths) { + private @NonNull GlobalAnalysisListener getListener(ReportStatsListener reportSizeListener, + List reportShortNamesPaths, + String inputPaths) { List renderers = new ArrayList<>(formatters.size() + 1); try { - renderers.add(makeLogListener(configuration.getInputPaths())); + renderers.add(makeLogListener(inputPaths)); renderers.add(reportSizeListener); for (Formatter formatter : formatters) { project.log("Sending a report to " + formatter, Project.MSG_VERBOSE); renderers.add(formatter.newListener(project, reportShortNamesPaths)); } - } catch (IOException e) { + return GlobalAnalysisListener.tee(renderers); + } catch (Exception e) { // close those opened so far Exception e2 = IOUtil.closeAll(renderers); if (e2 != null) { @@ -187,8 +183,6 @@ public class PMDTaskImpl { } throw new BuildException("Exception while initializing renderers", e); } - - return GlobalAnalysisListener.tee(renderers); } private GlobalAnalysisListener makeLogListener(String commonInputPath) { @@ -233,9 +227,9 @@ public class PMDTaskImpl { try { if (auxClasspath != null) { project.log("Using auxclasspath: " + auxClasspath, Project.MSG_VERBOSE); - configuration.prependClasspath(auxClasspath.toString()); + configuration.prependAuxClasspath(auxClasspath.toString()); } - } catch (IOException ioe) { + } catch (IllegalArgumentException ioe) { throw new BuildException(ioe.getMessage(), ioe); } } @@ -257,13 +251,4 @@ public class PMDTaskImpl { } } - private void logRulesUsed(List rulesets) { - project.log("Using these rulesets: " + rulesetPaths, Project.MSG_VERBOSE); - - for (RuleSet ruleSet : rulesets) { - for (Rule rule : ruleSet.getRules()) { - project.log("Using rule " + rule.getName(), Project.MSG_VERBOSE); - } - } - } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/TimeTracker.java b/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/TimeTracker.java index 2eb42776bb..c008ec4ff5 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/TimeTracker.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/TimeTracker.java @@ -12,6 +12,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; /** * A time tracker class to measure time spent on different sections of PMD analysis. @@ -157,6 +158,18 @@ public final class TimeTracker { } } + public static void bench(String label, Runnable runnable) { + try (TimedOperation ignored = startOperation(TimedOperationCategory.LANGUAGE_SPECIFIC_PROCESSING, label)) { + runnable.run(); + } + } + + public static T bench(String label, Supplier runnable) { + try (TimedOperation ignored = startOperation(TimedOperationCategory.LANGUAGE_SPECIFIC_PROCESSING, label)) { + return runnable.get(); + } + } + /** * An entry in the open timers queue. Defines an operation that has started and hasn't finished yet. */ diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cache/AbstractAnalysisCache.java b/pmd-core/src/main/java/net/sourceforge/pmd/cache/AbstractAnalysisCache.java index 9089eb05bd..624515579c 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cache/AbstractAnalysisCache.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cache/AbstractAnalysisCache.java @@ -26,6 +26,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.sourceforge.pmd.PMDVersion; +import net.sourceforge.pmd.Report.ProcessingError; import net.sourceforge.pmd.RuleSets; import net.sourceforge.pmd.RuleViolation; import net.sourceforge.pmd.annotation.InternalApi; @@ -210,13 +211,26 @@ public abstract class AbstractAnalysisCache implements AnalysisCache { } @Override - public FileAnalysisListener startFileAnalysis(DataSource filename) { - return violation -> { - final AnalysisResult analysisResult = - updatedResultsCache.get(violation.getFilename()); + public FileAnalysisListener startFileAnalysis(DataSource dataSource) { + String fileName = dataSource.getNiceFileName(false, ""); + File sourceFile = new File(fileName); + AnalysisResult analysisResult = updatedResultsCache.get(fileName); + if (analysisResult == null) { + analysisResult = new AnalysisResult(sourceFile); + } + final AnalysisResult nonNullAnalysisResult = analysisResult; - synchronized (analysisResult) { - analysisResult.addViolation(violation); + return new FileAnalysisListener() { + @Override + public void onRuleViolation(RuleViolation violation) { + synchronized (nonNullAnalysisResult) { + nonNullAnalysisResult.addViolation(violation); + } + } + + @Override + public void onError(ProcessingError error) { + analysisFailed(sourceFile); } }; } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cache/AnalysisCache.java b/pmd-core/src/main/java/net/sourceforge/pmd/cache/AnalysisCache.java index ea1f6f5273..f16490263a 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cache/AnalysisCache.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cache/AnalysisCache.java @@ -5,6 +5,7 @@ package net.sourceforge.pmd.cache; import java.io.File; +import java.io.IOException; import java.util.List; import net.sourceforge.pmd.RuleSets; @@ -12,6 +13,7 @@ import net.sourceforge.pmd.RuleViolation; import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.reporting.FileAnalysisListener; import net.sourceforge.pmd.reporting.GlobalAnalysisListener; +import net.sourceforge.pmd.util.datasource.DataSource; /** * An analysis cache for incremental analysis. @@ -22,12 +24,12 @@ import net.sourceforge.pmd.reporting.GlobalAnalysisListener; */ @Deprecated @InternalApi -public interface AnalysisCache extends GlobalAnalysisListener { +public interface AnalysisCache { /** * Persists the updated analysis results on whatever medium is used by the cache. */ - void persist(); + void persist() throws IOException; /** * Checks if a given file is up to date in the cache and can be skipped from analysis. @@ -59,8 +61,16 @@ public interface AnalysisCache extends GlobalAnalysisListener { * cache is invalidated. This needs to be called before analysis, as it * conditions the good behaviour of {@link #isUpToDate(File)}. * - * @param ruleSets The rulesets configured for this analysis. + * @param ruleSets The rulesets configured for this analysis. * @param auxclassPathClassLoader The class loader for auxclasspath configured for this analysis. */ void checkValidity(RuleSets ruleSets, ClassLoader auxclassPathClassLoader); + + /** + * Returns a listener that will be used like in {@link GlobalAnalysisListener#startFileAnalysis(DataSource)}. + * This should record violations, and call {@link #analysisFailed(File)} + * upon error. + */ + FileAnalysisListener startFileAnalysis(DataSource file); + } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cache/AnalysisCacheListener.java b/pmd-core/src/main/java/net/sourceforge/pmd/cache/AnalysisCacheListener.java new file mode 100644 index 0000000000..f6c4d12558 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cache/AnalysisCacheListener.java @@ -0,0 +1,40 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cache; + +import java.io.IOException; + +import net.sourceforge.pmd.RuleSets; +import net.sourceforge.pmd.annotation.InternalApi; +import net.sourceforge.pmd.reporting.FileAnalysisListener; +import net.sourceforge.pmd.reporting.GlobalAnalysisListener; +import net.sourceforge.pmd.util.datasource.DataSource; + +/** + * Adapter to wrap {@link AnalysisCache} behaviour in a {@link GlobalAnalysisListener}. + */ +@Deprecated +@InternalApi +public class AnalysisCacheListener implements GlobalAnalysisListener { + + private final AnalysisCache cache; + + public AnalysisCacheListener(AnalysisCache cache, RuleSets ruleSets, ClassLoader classLoader) { + this.cache = cache; + cache.checkValidity(ruleSets, classLoader); + } + + + @Override + public FileAnalysisListener startFileAnalysis(DataSource file) { + return cache.startFileAnalysis(file); + } + + @Override + public void close() throws IOException { + cache.persist(); + } + +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cache/FileAnalysisCache.java b/pmd-core/src/main/java/net/sourceforge/pmd/cache/FileAnalysisCache.java index 5c1f29f4db..d2739cfc92 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cache/FileAnalysisCache.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cache/FileAnalysisCache.java @@ -151,11 +151,6 @@ public class FileAnalysisCache extends AbstractAnalysisCache { } } - @Override - public void close() throws Exception { - // nothing to do, PMD calls persist explicitly - } - @Override protected boolean cacheExists() { return cacheFile.exists() && cacheFile.isFile() && cacheFile.length() > 0; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cache/NoopAnalysisCache.java b/pmd-core/src/main/java/net/sourceforge/pmd/cache/NoopAnalysisCache.java index 56127ea4a7..3a3dee4470 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cache/NoopAnalysisCache.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cache/NoopAnalysisCache.java @@ -53,8 +53,4 @@ public class NoopAnalysisCache implements AnalysisCache { return FileAnalysisListener.noop(); } - @Override - public void close() throws Exception { - // noop - } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDCommandLineInterface.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDCommandLineInterface.java index 7419d11afd..5e90676c06 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDCommandLineInterface.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDCommandLineInterface.java @@ -8,6 +8,7 @@ import java.util.Properties; import java.util.stream.Collectors; import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.PMD.StatusCode; import net.sourceforge.pmd.PMDVersion; import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.Language; @@ -28,13 +29,39 @@ import com.beust.jcommander.ParameterException; @InternalApi public final class PMDCommandLineInterface { + @Deprecated public static final String PROG_NAME = "pmd"; + /** + * @deprecated This is used for testing, but support for it will be removed in PMD 7. + * Use {@link PMD#runPmd(String...)} or an overload to avoid exiting the VM. In PMD 7, + * {@link PMD#main(String[])} will call {@link System#exit(int)} always. + */ + @Deprecated public static final String NO_EXIT_AFTER_RUN = "net.sourceforge.pmd.cli.noExit"; + + /** + * @deprecated This is used for testing, but support for it will be removed in PMD 7. + * Use {@link PMD#runPmd(String...)} or an overload to avoid exiting the VM. In PMD 7, + * {@link PMD#main(String[])} will call {@link System#exit(int)} always. + */ + @Deprecated public static final String STATUS_CODE_PROPERTY = "net.sourceforge.pmd.cli.status"; + /** + * @deprecated Use {@link StatusCode#OK} + */ + @Deprecated public static final int NO_ERRORS_STATUS = 0; + /** + * @deprecated Use {@link StatusCode#ERROR} + */ + @Deprecated public static final int ERROR_STATUS = 1; + /** + * @deprecated Use {@link StatusCode#VIOLATIONS_FOUND} + */ + @Deprecated public static final int VIOLATIONS_FOUND = 4; private PMDCommandLineInterface() { } @@ -126,7 +153,10 @@ public final class PMDCommandLineInterface { * For testing purpose only... * * @param args + * + * @deprecated Use {@link PMD#runPmd(String...)} */ + @Deprecated public static void main(String[] args) { System.out.println(PMDCommandLineInterface.buildUsageText()); } @@ -160,14 +190,6 @@ public final class PMDCommandLineInterface { return buf.toString(); } - /** - * @deprecated Use {@link PMD#main(String[])} - */ - @Deprecated - public static void run(String[] args) { - setStatusCodeOrExit(PMD.run(args)); - } - public static void setStatusCodeOrExit(int status) { if (isExitAfterRunSet()) { System.exit(status); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDParameters.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDParameters.java index 892093af24..7cea583a26 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDParameters.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDParameters.java @@ -4,7 +4,6 @@ package net.sourceforge.pmd.cli; -import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -235,8 +234,8 @@ public class PMDParameters { } try { - configuration.prependClasspath(this.getAuxclasspath()); - } catch (IOException e) { + configuration.prependAuxClasspath(this.getAuxclasspath()); + } catch (IllegalArgumentException e) { throw new IllegalArgumentException("Invalid auxiliary classpath: " + e.getMessage(), e); } return configuration; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/internal/RulesetStageDependencyHelper.java b/pmd-core/src/main/java/net/sourceforge/pmd/internal/RulesetStageDependencyHelper.java deleted file mode 100644 index cd02f2e6d1..0000000000 --- a/pmd-core/src/main/java/net/sourceforge/pmd/internal/RulesetStageDependencyHelper.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.internal; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.SortedSet; -import java.util.TreeSet; -import java.util.concurrent.ConcurrentHashMap; - -import net.sourceforge.pmd.PMDConfiguration; -import net.sourceforge.pmd.Rule; -import net.sourceforge.pmd.RuleSets; -import net.sourceforge.pmd.benchmark.TimeTracker; -import net.sourceforge.pmd.benchmark.TimedOperation; -import net.sourceforge.pmd.benchmark.TimedOperationCategory; -import net.sourceforge.pmd.lang.LanguageVersion; -import net.sourceforge.pmd.lang.ast.AstAnalysisContext; -import net.sourceforge.pmd.lang.ast.AstProcessingStage; -import net.sourceforge.pmd.lang.ast.RootNode; - -/** - * - * @author ClΓ©ment Fournier - */ -public class RulesetStageDependencyHelper { - - // cache by ruleset - private final Map>>> dependenciesByRuleset = new ConcurrentHashMap<>(); - private final PMDConfiguration configuration; - - public RulesetStageDependencyHelper(PMDConfiguration configuration) { - this.configuration = configuration; - } - - - List> testOnlyGetDependencies(RuleSets ruleSets, LanguageVersion languageVersion) { - return getDependencies(ruleSets, languageVersion); - } - - /** Gets the stage dependencies of the ruleset for the given language version. */ - private List> getDependencies(RuleSets ruleSets, LanguageVersion languageVersion) { - Map>> byLanguage = dependenciesByRuleset.computeIfAbsent(ruleSets, r -> new ConcurrentHashMap<>()); - - return byLanguage.computeIfAbsent(languageVersion, l -> buildDependencyList(ruleSets, l)); - } - - - public void runLanguageSpecificStages(RuleSets ruleSets, LanguageVersion languageVersion, RootNode rootNode) { - AstAnalysisContext context = buildContext(languageVersion); - - getDependencies(ruleSets, languageVersion) - .forEach(stage -> executeProcessingStage(stage, rootNode, context)); - - } - - private void executeProcessingStage(AstProcessingStage stage, RootNode root, AstAnalysisContext context) { - - String label = stage.getLanguage().getShortName() + ": " + stage.getDisplayName(); - try (TimedOperation ignored = TimeTracker.startOperation(TimedOperationCategory.LANGUAGE_SPECIFIC_PROCESSING, label)) { - stage.processAST(root, context); - } - } - - /** Builds a sorted list of the dependencies of the given ruleset. */ - private List> buildDependencyList(RuleSets ruleSets, LanguageVersion languageVersion) { - List> stages = new ArrayList<>(languageVersion.getLanguageVersionHandler().getProcessingStages()); - SortedSet> result = new TreeSet<>(AstProcessingStage.COMPARATOR); - - // this loops runs until either all stages have already been - // picked or there are no rules left, whichever comes first - for (Rule rule : ruleSets.getAllRules()) { - if (stages.isEmpty()) { - break; // to the return - } - for (AstProcessingStage stage : stages) { - if (rule.dependsOn(stage)) { - result.add(stage); - result.addAll(stage.getDependencies()); - } - } - stages.removeAll(result); - } - - return Collections.unmodifiableList(new ArrayList<>(result)); - } - - - // TODO this could be made language specific as well - private AstAnalysisContext buildContext(LanguageVersion languageVersion) { - return new AstAnalysisContext() { - @Override - public ClassLoader getTypeResolutionClassLoader() { - return configuration.getClassLoader(); - } - - - @Override - public LanguageVersion getLanguageVersion() { - return languageVersion; - } - }; - } - -} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/internal/util/AssertionUtil.java b/pmd-core/src/main/java/net/sourceforge/pmd/internal/util/AssertionUtil.java index b1034a6252..fca762b178 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/internal/util/AssertionUtil.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/internal/util/AssertionUtil.java @@ -25,10 +25,12 @@ public final class AssertionUtil { /** @throws NullPointerException if $name */ public static void requireContainsNoNullValue(String name, Collection c) { + int i = 0; for (Object o : c) { if (o == null) { - throw new NullPointerException(name + " contains null elements"); + throw new NullPointerException(name + " contains a null element at index " + i); } + i++; } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/internal/util/FileCollectionUtil.java b/pmd-core/src/main/java/net/sourceforge/pmd/internal/util/FileCollectionUtil.java new file mode 100644 index 0000000000..697d16f927 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/internal/util/FileCollectionUtil.java @@ -0,0 +1,183 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.internal.util; + +import java.io.IOException; +import java.io.Reader; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.sourceforge.pmd.PMDConfiguration; +import net.sourceforge.pmd.lang.Language; +import net.sourceforge.pmd.lang.document.FileCollector; +import net.sourceforge.pmd.lang.document.TextFile; +import net.sourceforge.pmd.util.FileUtil; +import net.sourceforge.pmd.util.database.DBMSMetadata; +import net.sourceforge.pmd.util.database.DBURI; +import net.sourceforge.pmd.util.database.SourceObject; +import net.sourceforge.pmd.util.datasource.DataSource; +import net.sourceforge.pmd.util.log.MessageReporter; +import net.sourceforge.pmd.util.log.internal.ErrorsAsWarningsReporter; + +/** + * @author ClΓ©ment Fournier + */ +public final class FileCollectionUtil { + + private static final Logger LOG = LoggerFactory.getLogger(FileCollectionUtil.class); + + private FileCollectionUtil() { + + } + + public static List collectorToDataSource(FileCollector collector) { + List result = new ArrayList<>(); + for (TextFile file : collector.getCollectedFiles()) { + result.add(file.toDataSourceCompat()); + } + return result; + } + + public static FileCollector collectFiles(PMDConfiguration configuration, Set languages, MessageReporter reporter) { + FileCollector collector = collectFiles(configuration, reporter); + collector.filterLanguages(languages); + return collector; + } + + private static FileCollector collectFiles(PMDConfiguration configuration, MessageReporter reporter) { + FileCollector collector = FileCollector.newCollector( + configuration.getLanguageVersionDiscoverer(), + reporter + ); + collectFiles(configuration, collector); + return collector; + } + + public static void collectFiles(PMDConfiguration configuration, FileCollector collector) { + if (configuration.getSourceEncoding() != null) { + collector.setCharset(configuration.getSourceEncoding()); + } + + if (configuration.getInputPaths() != null) { + collectFiles(collector, configuration.getInputPaths()); + } + + if (configuration.getInputUri() != null) { + collectDB(collector, configuration.getInputUri()); + } + + if (configuration.getInputFilePath() != null) { + collectFileList(collector, configuration.getInputFilePath()); + } + + if (configuration.getIgnoreFilePath() != null) { + // This is to be able to interpret the log (will report 'adding' xxx) + LOG.debug("Now collecting files to exclude."); + // errors like "excluded file does not exist" are reported as warnings. + // todo better reporting of *where* exactly the path is + MessageReporter mutedLog = new ErrorsAsWarningsReporter(collector.getReporter()); + try (FileCollector excludeCollector = collector.newCollector(mutedLog)) { + collectFileList(excludeCollector, configuration.getIgnoreFilePath()); + collector.exclude(excludeCollector); + } + } + } + + + public static void collectFiles(FileCollector collector, String fileLocations) { + for (String rootLocation : fileLocations.split(",")) { + try { + collector.relativizeWith(rootLocation); + addRoot(collector, rootLocation); + } catch (IOException e) { + collector.getReporter().errorEx("Error collecting " + rootLocation, e); + } + } + } + + public static void collectFileList(FileCollector collector, String fileListLocation) { + LOG.debug("Reading file list {}.", fileListLocation); + Path path = Paths.get(fileListLocation); + if (!Files.exists(path)) { + collector.getReporter().error("No such file {}", fileListLocation); + return; + } + + String filePaths; + try { + filePaths = FileUtil.readFilelist(path.toFile()); + } catch (IOException e) { + collector.getReporter().errorEx("Error reading {}", new Object[] { fileListLocation }, e); + return; + } + collectFiles(collector, filePaths); + } + + private static void addRoot(FileCollector collector, String rootLocation) throws IOException { + Path path = Paths.get(rootLocation); + if (!Files.exists(path)) { + collector.getReporter().error("No such file {}", path); + return; + } + + if (Files.isDirectory(path)) { + LOG.debug("Adding directory {}.", path); + collector.addDirectory(path); + } else if (rootLocation.endsWith(".zip") || rootLocation.endsWith(".jar")) { + LOG.debug("Adding zip file {}.", path); + @SuppressWarnings("PMD.CloseResource") + FileSystem fs = collector.addZipFile(path); + if (fs == null) { + return; + } + for (Path zipRoot : fs.getRootDirectories()) { + collector.addFileOrDirectory(zipRoot); + } + } else if (Files.isRegularFile(path)) { + LOG.debug("Adding regular file {}.", path); + collector.addFile(path); + } else { + LOG.debug("Ignoring {}: not a regular file or directory", path); + } + } + + public static void collectDB(FileCollector collector, String uriString) { + try { + LOG.debug("Connecting to {}", uriString); + DBURI dbUri = new DBURI(uriString); + DBMSMetadata dbmsMetadata = new DBMSMetadata(dbUri); + LOG.trace("DBMSMetadata retrieved"); + List sourceObjectList = dbmsMetadata.getSourceObjectList(); + LOG.trace("Located {} database source objects", sourceObjectList.size()); + for (SourceObject sourceObject : sourceObjectList) { + String falseFilePath = sourceObject.getPseudoFileName(); + LOG.trace("Adding database source object {}", falseFilePath); + + try (Reader sourceCode = dbmsMetadata.getSourceCode(sourceObject)) { + String source = IOUtils.toString(sourceCode); + collector.addSourceFile(source, falseFilePath); + } catch (SQLException ex) { + collector.getReporter().warnEx("Cannot get SourceCode for {} - skipping ...", + new Object[] { falseFilePath }, + ex); + } + } + } catch (ClassNotFoundException e) { + collector.getReporter().errorEx("Cannot get files from DB - probably missing database JDBC driver", e); + } catch (Exception e) { + collector.getReporter().errorEx("Cannot get files from DB - ''{}''", new Object[] { uriString }, e); + } + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/AbstractPmdLanguageVersionHandler.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/AbstractPmdLanguageVersionHandler.java index 0444155638..cf02bee9c2 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/AbstractPmdLanguageVersionHandler.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/AbstractPmdLanguageVersionHandler.java @@ -4,13 +4,8 @@ package net.sourceforge.pmd.lang; -import java.util.Collections; -import java.util.List; import java.util.Locale; -import org.apache.commons.lang3.EnumUtils; - -import net.sourceforge.pmd.lang.ast.AstProcessingStage; import net.sourceforge.pmd.properties.PropertyDescriptor; import net.sourceforge.pmd.properties.PropertySource; @@ -25,38 +20,6 @@ import net.sourceforge.pmd.properties.PropertySource; public abstract class AbstractPmdLanguageVersionHandler extends AbstractLanguageVersionHandler { - private final List> processingStages; - - - /** - * Declare processing stages within an enum. An enum is the best way - * to declare them since the illegality of forward references naturally - * prevents circular dependencies to be declared. The natural ordering - * on enums is also a sound and stable ordering for processing stages. - * - * @param processingStagesEnum Enum class - * @param Type of the enum class - */ - protected & AstProcessingStage> AbstractPmdLanguageVersionHandler(Class processingStagesEnum) { - this.processingStages = EnumUtils.getEnumList(processingStagesEnum); - } - - - /** - * Declare no optional processing stages as of yet. - */ - protected AbstractPmdLanguageVersionHandler() { - this.processingStages = Collections.emptyList(); - } - - - @Override - public final List> getProcessingStages() { - return processingStages; - } - - - /** * Returns the environment variable name that a user can set in order to override the default value. */ diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/BaseLanguageModule.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/BaseLanguageModule.java index 4413c302ca..4491ff2a42 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/BaseLanguageModule.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/BaseLanguageModule.java @@ -154,7 +154,7 @@ public abstract class BaseLanguageModule implements Language { @Override public String toString() { - return "LanguageModule:" + name + '(' + this.getClass().getSimpleName() + ')'; + return getTerseName(); } @Override diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageVersion.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageVersion.java index 94bf68af88..b1ac43e348 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageVersion.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageVersion.java @@ -35,7 +35,7 @@ public class LanguageVersion implements Comparable { private final Language language; private final String version; - private final LanguageVersionHandler languageVersionHandler; + private final LanguageVersionHandler languageVersionHandler; // note: this is null if this is a cpd-only language... /** * @deprecated Use {@link Language#getVersion(String)}. This is only diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageVersionDiscoverer.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageVersionDiscoverer.java index 61e997c29c..4e22b03a4f 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageVersionDiscoverer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageVersionDiscoverer.java @@ -8,6 +8,11 @@ import java.io.File; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; + +import org.apache.commons.lang3.StringUtils; + +import net.sourceforge.pmd.internal.util.AssertionUtil; /** * This class can discover the LanguageVersion of a source file. Further, every @@ -17,6 +22,23 @@ import java.util.Map; public class LanguageVersionDiscoverer { private Map languageToLanguageVersion = new HashMap<>(); + private LanguageVersion forcedVersion; + + public LanguageVersionDiscoverer() { + this(null); + } + + /** + * Build a new instance. + * + * @param forcedVersion If non-null, all files should be assigned this version. + * The methods of this class still work as usual and do not + * care about the forced language version. + */ + public LanguageVersionDiscoverer(LanguageVersion forcedVersion) { + this.forcedVersion = forcedVersion; + } + /** * Set the given LanguageVersion as the current default for it's Language. * @@ -25,6 +47,7 @@ public class LanguageVersionDiscoverer { * @return The previous default version for the language. */ public LanguageVersion setDefaultLanguageVersion(LanguageVersion languageVersion) { + AssertionUtil.requireParamNotNull("languageVersion", languageVersion); LanguageVersion currentLanguageVersion = languageToLanguageVersion.put(languageVersion.getLanguage(), languageVersion); if (currentLanguageVersion == null) { @@ -41,6 +64,7 @@ public class LanguageVersionDiscoverer { * @return The current default version for the language. */ public LanguageVersion getDefaultLanguageVersion(Language language) { + Objects.requireNonNull(language); LanguageVersion languageVersion = languageToLanguageVersion.get(language); if (languageVersion == null) { languageVersion = language.getDefaultVersion(); @@ -81,6 +105,14 @@ public class LanguageVersionDiscoverer { return languageVersion; } + public LanguageVersion getForcedVersion() { + return forcedVersion; + } + + public void setForcedVersion(LanguageVersion forceLanguageVersion) { + this.forcedVersion = forceLanguageVersion; + } + /** * Get the Languages of a given source file. * @@ -106,11 +138,8 @@ public class LanguageVersionDiscoverer { // Get the extensions from a file private String getExtension(String fileName) { - String extension = null; - int extensionIndex = 1 + fileName.lastIndexOf('.'); - if (extensionIndex > 0) { - extension = fileName.substring(extensionIndex); - } - return extension; + return StringUtils.substringAfterLast(fileName, "."); } + + } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageVersionHandler.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageVersionHandler.java index e10e42ec75..1b228e8dc6 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageVersionHandler.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageVersionHandler.java @@ -4,12 +4,7 @@ package net.sourceforge.pmd.lang; -import static java.util.Collections.emptyList; - -import java.util.List; - import net.sourceforge.pmd.annotation.Experimental; -import net.sourceforge.pmd.lang.ast.AstProcessingStage; import net.sourceforge.pmd.lang.ast.Parser; import net.sourceforge.pmd.lang.metrics.LanguageMetricsProvider; import net.sourceforge.pmd.lang.rule.RuleViolationFactory; @@ -37,17 +32,6 @@ public interface LanguageVersionHandler { } - /** - * Returns the list of all supported optional processing stages. - * - * @return A list of all optional processing stages. - */ - @Experimental - default List> getProcessingStages() { - return emptyList(); - } - - /** * @deprecated This is transitional */ diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/AstAnalysisContext.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/AstAnalysisContext.java deleted file mode 100644 index d98b41d066..0000000000 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/AstAnalysisContext.java +++ /dev/null @@ -1,34 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.ast; - -import net.sourceforge.pmd.annotation.Experimental; -import net.sourceforge.pmd.lang.LanguageVersion; - - -/** - * Configuration relevant to e.g. an {@link AstProcessingStage}. - * - * @author ClΓ©ment Fournier - * @since 7.0.0 - */ -@Experimental -public interface AstAnalysisContext { - - - /** - * Gets the classloader used for type resolution. - * - * @return The classloader. - */ - ClassLoader getTypeResolutionClassLoader(); - - - /** - * Returns the language version used for this analysis. - */ - LanguageVersion getLanguageVersion(); - -} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/AstProcessingStage.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/AstProcessingStage.java deleted file mode 100644 index ecc8f301c7..0000000000 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/AstProcessingStage.java +++ /dev/null @@ -1,121 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.ast; - -import static java.util.Collections.emptyList; - -import java.util.Comparator; -import java.util.List; - -import net.sourceforge.pmd.RuleSets; -import net.sourceforge.pmd.annotation.Experimental; -import net.sourceforge.pmd.lang.Language; -import net.sourceforge.pmd.lang.LanguageVersionHandler; - -// @formatter:off -/** - * Represents one of the stages applying on the AST - * after parsing is done. Each of these stages implicitly - * depends on the parser stage. - * - *

    An analysis on a file goes through the following stages: - *

      - *
    • Parsing stage: taking the source and configuration, and returning an AST - *
    • Language-specific AST visits: sequence of stages specific to each language. - * Each stage performs side effects on the AST, e.g. to resolve such things as comments, - * types, DFA graph, etc. - *
    • Rulechain application: all rulechain rules are run on the final AST - *
    • Rule application: other rules are run - *
    - * - *

    These steps are run on each file during the analysis, unless the cache entry of the file is up-to-date. - * They're all run sequentially by the same thread. Rule application performs side-effects on the Report, - * which is rendered after all files have been processed. - * - *

    Parsing and rule[chain] application stages are considered special and are handled differently for now. - * A {@link LanguageVersionHandler} is responsible for listing all available {@link AstProcessingStage}s - * (see {@link LanguageVersionHandler#getProcessingStages()}). The actual set of stages that will get executed - * for a run is the union of the dependencies of the rules in the run {@link RuleSets}. - * - *

    Additional doc, to be moved elsewhere probably: - *

    PMD's execution goes through other more global stages (not sure about the exact order): - *

      - *
    • Rule loading: creates a {@link RuleSets} from the ruleset files - *
    • Cache loading: loads the cache file for incremental analysis if any, creates the new cache - *
    • Report creation: creates a report object for the rules to act on - *
    • File collection: collects the files to analyse and dispatches them to worker threads. - * Each file undergoes the steps described above. - *
    • Report rendering - *
    • Cache persisting - *
    - * - * - * @author ClΓ©ment Fournier - * @since 7.0.0 - */ -// @formatter:on -@Experimental -public interface AstProcessingStage> extends Comparable { - - /** - * Compares processing stages of possibly different kinds. - */ - Comparator> COMPARATOR = AstProcessingStage::compare; - - - /** - * Returns the language this processing stage applies to. - */ - Language getLanguage(); - - - /** - * Gets the stages on which this stage depends. - * E.g. the type resolution stage may depend on the - * qualified name resolution stage. - * - *

    Returns an empty list if this stage only depends - * on the parser stage. - */ - default List getDependencies() { - return emptyList(); - } - - - /** - * Returns the name of this stage, used e.g. to display in a - * benchmark report. - * - * @return The name of the stage. - */ - String getDisplayName(); - - - /** - * Performs some side effects on the AST, e.g. to resolve something. - * - * @param rootNode Root of the tree - * @param configuration Configuration - */ - void processAST(RootNode rootNode, AstAnalysisContext configuration); - - - /** - * Same contract as {@link Comparable#compareTo(Object)}, but we can't extend - * Comparable with that type argument if we implement processing stages within - * an enum. - * - * @param t the object to compare - * - * @return a negative integer, zero, or a positive integer as this object - * is less than, equal to, or greater than the specified object. - */ - @SuppressWarnings("unchecked") - default int compare(AstProcessingStage t) { - return this.compareTo((T) t); - } - - -} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Parser.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Parser.java index 7e7b04fec1..348d104a3a 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Parser.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Parser.java @@ -4,11 +4,13 @@ package net.sourceforge.pmd.lang.ast; +import static net.sourceforge.pmd.internal.util.AssertionUtil.requireParamNotNull; + import java.util.Objects; import org.checkerframework.checker.nullness.qual.NonNull; -import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.PMDConfiguration; import net.sourceforge.pmd.lang.LanguageVersion; import net.sourceforge.pmd.properties.AbstractPropertySource; import net.sourceforge.pmd.properties.PropertyDescriptor; @@ -48,23 +50,29 @@ public interface Parser { private final String filepath; private final String sourceText; private final SemanticErrorReporter reporter; + private final ClassLoader auxclasspathClassLoader; private final PropertySource propertySource; - public ParserTask(LanguageVersion lv, String filepath, String sourceText, SemanticErrorReporter reporter) { - this.lv = Objects.requireNonNull(lv, "lv was null"); - this.filepath = Objects.requireNonNull(filepath, "filepath was null"); - this.sourceText = Objects.requireNonNull(sourceText, "sourceText was null"); - this.reporter = Objects.requireNonNull(reporter, "reporter was null"); + public ParserTask(LanguageVersion lv, String filepath, String sourceText, SemanticErrorReporter reporter, ClassLoader auxclasspathClassLoader) { + this.lv = requireParamNotNull("language version", lv); + this.filepath = requireParamNotNull("filepath", filepath); + this.sourceText = requireParamNotNull("sourceText", sourceText); + this.reporter = requireParamNotNull("reporter", reporter); + this.auxclasspathClassLoader = requireParamNotNull("auxclasspathClassLoader", auxclasspathClassLoader); this.propertySource = new ParserTaskProperties(); propertySource.definePropertyDescriptor(COMMENT_MARKER); } + public ParserTask(LanguageVersion lv, String filepath, String sourceText, SemanticErrorReporter reporter) { + this(lv, filepath, sourceText, reporter, Parser.class.getClassLoader()); + } + public static final PropertyDescriptor COMMENT_MARKER = PropertyFactory.stringProperty("suppressionCommentMarker") .desc("deprecated! NOPMD") - .defaultValue(PMD.SUPPRESS_MARKER) + .defaultValue(PMDConfiguration.DEFAULT_SUPPRESS_MARKER) .build(); @Deprecated // transitional until language properties are implemented @@ -72,6 +80,10 @@ public interface Parser { return propertySource; } + @Deprecated // transitional until language properties are implemented + public ClassLoader getAuxclasspathClassLoader() { + return auxclasspathClassLoader; + } public LanguageVersion getLanguageVersion() { return lv; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/javacc/AbstractJjtreeNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/javacc/AbstractJjtreeNode.java index 1ea0dae5da..7caaa8e15c 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/javacc/AbstractJjtreeNode.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/javacc/AbstractJjtreeNode.java @@ -38,6 +38,7 @@ public abstract class AbstractJjtreeNode, N e } @Override + // @Deprecated // todo deprecate, will change tree dump tests public String getImage() { return image; } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/document/FileCollector.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/document/FileCollector.java new file mode 100644 index 0000000000..55c7fb5d24 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/document/FileCollector.java @@ -0,0 +1,400 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.document; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystem; +import java.nio.file.FileSystemNotFoundException; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.ProviderNotFoundException; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.sourceforge.pmd.PmdAnalysis; +import net.sourceforge.pmd.annotation.InternalApi; +import net.sourceforge.pmd.internal.util.AssertionUtil; +import net.sourceforge.pmd.lang.Language; +import net.sourceforge.pmd.lang.LanguageVersion; +import net.sourceforge.pmd.lang.LanguageVersionDiscoverer; +import net.sourceforge.pmd.util.IOUtil; +import net.sourceforge.pmd.util.log.MessageReporter; + +/** + * Collects files to analyse before a PMD run. This API allows opening + * zip files and makes sure they will be closed at the end of a run. + * + * @author ClΓ©ment Fournier + */ +@SuppressWarnings("PMD.CloseResource") +public final class FileCollector implements AutoCloseable { + private static final Logger LOG = LoggerFactory.getLogger(FileCollector.class); + + private final List allFilesToProcess = new ArrayList<>(); + private final List resourcesToClose = new ArrayList<>(); + private Charset charset = StandardCharsets.UTF_8; + private final LanguageVersionDiscoverer discoverer; + private final MessageReporter reporter; + private final List relativizeRoots = new ArrayList<>(); + private boolean closed; + + // construction + + private FileCollector(LanguageVersionDiscoverer discoverer, MessageReporter reporter) { + this.discoverer = discoverer; + this.reporter = reporter; + } + + /** + * Internal API: please use {@link PmdAnalysis#files()} instead of + * creating a collector yourself. + */ + @InternalApi + public static FileCollector newCollector(LanguageVersionDiscoverer discoverer, MessageReporter reporter) { + return new FileCollector(discoverer, reporter); + } + + /** + * Returns a new collector using the configuration except for the logger. + */ + @InternalApi + public FileCollector newCollector(MessageReporter logger) { + FileCollector fileCollector = new FileCollector(discoverer, logger); + fileCollector.charset = this.charset; + fileCollector.relativizeRoots.addAll(this.relativizeRoots); + return fileCollector; + } + + // public behaviour + + /** + * Returns an unmodifiable list of all files that have been collected. + * + *

    Internal: This might be unstable until PMD 7, but it's internal. + */ + @InternalApi + public List getCollectedFiles() { + if (closed) { + throw new IllegalStateException("Collector was closed!"); + } + allFilesToProcess.sort(Comparator.comparing(TextFile::getPathId)); + return Collections.unmodifiableList(allFilesToProcess); + } + + + /** + * Returns the reporter for the file collection phase. + */ + @InternalApi + public MessageReporter getReporter() { + return reporter; + } + + /** + * Close registered resources like zip files. + */ + @Override + public void close() { + if (closed) { + return; + } + closed = true; + Exception exception = IOUtil.closeAll(resourcesToClose); + if (exception != null) { + reporter.errorEx("Error while closing resources", exception); + } + } + + // collection + + /** + * Add a file, language is determined automatically from + * the extension/file patterns. The encoding is the current + * encoding ({@link #setCharset(Charset)}). + * + * @param file File to add + * + * @return True if the file has been added + */ + public boolean addFile(Path file) { + if (!Files.isRegularFile(file)) { + reporter.error("Not a regular file {}", file); + return false; + } + LanguageVersion languageVersion = discoverLanguage(file.toString()); + if (languageVersion != null) { + addFileImpl(new NioTextFile(file, charset, languageVersion, getDisplayName(file))); + return true; + } + return false; + } + + /** + * Add a file with the given language (which overrides the file patterns). + * The encoding is the current encoding ({@link #setCharset(Charset)}). + * + * @param file Path to a file + * @param language A language. The language version will be taken to be the + * contextual default version. + * + * @return True if the file has been added + */ + public boolean addFile(Path file, Language language) { + AssertionUtil.requireParamNotNull("language", language); + if (!Files.isRegularFile(file)) { + reporter.error("Not a regular file {}", file); + return false; + } + NioTextFile nioTextFile = new NioTextFile(file, charset, discoverer.getDefaultLanguageVersion(language), getDisplayName(file)); + addFileImpl(nioTextFile); + return true; + } + + /** + * Add a pre-configured text file. The language version will be checked + * to match the contextual default for the language (the file cannot be added + * if it has a different version). + * + * @return True if the file has been added + */ + public boolean addFile(TextFile textFile) { + AssertionUtil.requireParamNotNull("textFile", textFile); + if (checkContextualVersion(textFile)) { + addFileImpl(textFile); + return true; + } + return false; + } + + /** + * Add a text file given its contents and a name. The language version + * will be determined from the name as usual. + * + * @return True if the file has been added + */ + public boolean addSourceFile(String pathId, String sourceContents) { + AssertionUtil.requireParamNotNull("sourceContents", sourceContents); + AssertionUtil.requireParamNotNull("pathId", pathId); + + LanguageVersion version = discoverLanguage(pathId); + if (version != null) { + addFileImpl(new StringTextFile(sourceContents, pathId, pathId, version)); + return true; + } + + return false; + } + + private void addFileImpl(TextFile textFile) { + LOG.trace("Adding file {} (lang: {}) ", textFile.getPathId(), textFile.getLanguageVersion().getTerseName()); + allFilesToProcess.add(textFile); + } + + private LanguageVersion discoverLanguage(String file) { + if (discoverer.getForcedVersion() != null) { + return discoverer.getForcedVersion(); + } + List languages = discoverer.getLanguagesForFile(file); + + if (languages.isEmpty()) { + LOG.trace("File {} matches no known language, ignoring", file); + return null; + } + Language lang = languages.get(0); + if (languages.size() > 1) { + LOG.trace("File {} matches multiple languages ({}), selecting {}", file, languages, lang); + } + return discoverer.getDefaultLanguageVersion(lang); + } + + /** + * Whether the LanguageVersion of the file matches the one set in + * the {@link LanguageVersionDiscoverer}. This is required to ensure + * that all files for a given language have the same language version. + */ + private boolean checkContextualVersion(TextFile textFile) { + LanguageVersion fileVersion = textFile.getLanguageVersion(); + Language language = fileVersion.getLanguage(); + LanguageVersion contextVersion = discoverer.getDefaultLanguageVersion(language); + if (!fileVersion.equals(contextVersion)) { + reporter.error( + "Cannot add file {}: version ''{}'' does not match ''{}''", + textFile.getPathId(), + fileVersion, + contextVersion + ); + return false; + } + return true; + } + + private String getDisplayName(Path file) { + return getDisplayName(file, relativizeRoots); + } + + /** + * Return the textfile's display name. + * test only + */ + static String getDisplayName(Path file, List relativizeRoots) { + String fileName = file.toString(); + for (String root : relativizeRoots) { + if (file.startsWith(root)) { + if (fileName.startsWith(File.separator, root.length())) { + // remove following '/' + return fileName.substring(root.length() + 1); + } + return fileName.substring(root.length()); + } + } + return fileName; + } + + + /** + * Add a directory recursively using {@link #addFile(Path)} on + * all regular files. + * + * @param dir Directory path + * + * @return True if the directory has been added + */ + public boolean addDirectory(Path dir) throws IOException { + if (!Files.isDirectory(dir)) { + reporter.error("Not a directory {}", dir); + return false; + } + Files.walkFileTree(dir, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (attrs.isRegularFile()) { + FileCollector.this.addFile(file); + } + return super.visitFile(file, attrs); + } + }); + return true; + } + + + /** + * Add a file or directory recursively. Language is determined automatically + * from the extension/file patterns. + * + * @return True if the file or directory has been added + */ + public boolean addFileOrDirectory(Path file) throws IOException { + if (Files.isDirectory(file)) { + return addDirectory(file); + } else if (Files.isRegularFile(file)) { + return addFile(file); + } else { + reporter.error("Not a file or directory {}", file); + return false; + } + } + + /** + * Opens a zip file and returns a FileSystem for its contents, so + * it can be explored with the {@link Path} API. You can then call + * {@link #addFile(Path)} and such. The zip file is registered as + * a resource to close at the end of analysis. + */ + public FileSystem addZipFile(Path zipFile) { + if (!Files.isRegularFile(zipFile)) { + throw new IllegalArgumentException("Not a regular file: " + zipFile); + } + URI zipUri = URI.create("zip:" + zipFile.toUri()); + try { + FileSystem fs = FileSystems.getFileSystem(zipUri); + resourcesToClose.add(fs); + return fs; + } catch (FileSystemNotFoundException | ProviderNotFoundException e) { + reporter.errorEx("Cannot open zip file " + zipFile, e); + return null; + } + } + + // configuration + + /** + * Sets the charset to use for subsequent calls to {@link #addFile(Path)} + * and other overloads using a {@link Path}. + * + * @param charset A charset + */ + public void setCharset(Charset charset) { + this.charset = Objects.requireNonNull(charset); + } + + /** + * Add a prefix that is used to relativize file paths as their display name. + * For instance, when adding a file {@code /tmp/src/main/java/org/foo.java}, + * and relativizing with {@code /tmp/src/}, the registered {@link TextFile} + * will have a path id of {@code /tmp/src/main/java/org/foo.java}, and a + * display name of {@code main/java/org/foo.java}. + * + * This only matters for files added from a {@link Path} object. + * + * @param prefix Prefix to relativize (if a directory, include a trailing slash) + */ + public void relativizeWith(String prefix) { + this.relativizeRoots.add(Objects.requireNonNull(prefix)); + } + + // filtering + + /** + * Remove all files collected by the given collector from this one. + */ + public void exclude(FileCollector excludeCollector) { + Set toExclude = new HashSet<>(excludeCollector.allFilesToProcess); + for (Iterator iterator = allFilesToProcess.iterator(); iterator.hasNext();) { + TextFile file = iterator.next(); + if (toExclude.contains(file)) { + LOG.trace("Excluding file {}", file.getPathId()); + iterator.remove(); + } + } + } + + /** + * Exclude all collected files whose language is not part of the given + * collection. + */ + public void filterLanguages(Set languages) { + for (Iterator iterator = allFilesToProcess.iterator(); iterator.hasNext();) { + TextFile file = iterator.next(); + Language lang = file.getLanguageVersion().getLanguage(); + if (!languages.contains(lang)) { + LOG.trace("Filtering out {}, no rules for language {}", file.getPathId(), lang); + iterator.remove(); + } + } + } + + @Override + public String toString() { + return "FileCollector{filesToProcess=" + allFilesToProcess + '}'; + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/document/NioTextFile.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/document/NioTextFile.java new file mode 100644 index 0000000000..705ef530a7 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/document/NioTextFile.java @@ -0,0 +1,101 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.document; + +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Objects; + +import org.apache.commons.io.IOUtils; + +import net.sourceforge.pmd.annotation.Experimental; +import net.sourceforge.pmd.internal.util.AssertionUtil; +import net.sourceforge.pmd.lang.LanguageVersion; +import net.sourceforge.pmd.util.datasource.DataSource; +import net.sourceforge.pmd.util.datasource.FileDataSource; + +/** + * A {@link TextFile} backed by a file in some {@link FileSystem}. + */ +@Experimental +class NioTextFile implements TextFile { + + private final Path path; + private final Charset charset; + private final LanguageVersion languageVersion; + private final String displayName; + private final String pathId; + + NioTextFile(Path path, Charset charset, LanguageVersion languageVersion, String displayName) { + AssertionUtil.requireParamNotNull("path", path); + AssertionUtil.requireParamNotNull("charset", charset); + AssertionUtil.requireParamNotNull("language version", languageVersion); + + this.displayName = displayName; + this.path = path; + this.charset = charset; + this.languageVersion = languageVersion; + this.pathId = path.toAbsolutePath().toString(); + } + + @Override + public LanguageVersion getLanguageVersion() { + return languageVersion; + } + + @Override + public String getDisplayName() { + return displayName; + } + + @Override + public String getPathId() { + return pathId; + } + + + @Override + public String readContents() throws IOException { + + if (!Files.isRegularFile(path)) { + throw new IOException("Not a regular file: " + path); + } + + try (BufferedReader br = Files.newBufferedReader(path, charset)) { + return IOUtils.toString(br); + } + } + + @Override + public DataSource toDataSourceCompat() { + return new FileDataSource(path.toFile()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + NioTextFile that = (NioTextFile) o; + return Objects.equals(path, that.path); + } + + @Override + public int hashCode() { + return Objects.hash(pathId); + } + + @Override + public String toString() { + return getPathId(); + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/document/StringTextFile.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/document/StringTextFile.java new file mode 100644 index 0000000000..598823e555 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/document/StringTextFile.java @@ -0,0 +1,94 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.document; + +import java.io.StringReader; +import java.util.Objects; + +import net.sourceforge.pmd.annotation.Experimental; +import net.sourceforge.pmd.internal.util.AssertionUtil; +import net.sourceforge.pmd.lang.LanguageVersion; +import net.sourceforge.pmd.util.datasource.DataSource; +import net.sourceforge.pmd.util.datasource.ReaderDataSource; + +/** + * Read-only view on a string. + * + * @author ClΓ©ment Fournier + */ +@Experimental +class StringTextFile implements TextFile { + + private final String content; + private final String pathId; + private final String displayName; + private final LanguageVersion languageVersion; + + StringTextFile(String content, + String pathId, + String displayName, + LanguageVersion languageVersion) { + AssertionUtil.requireParamNotNull("source text", content); + AssertionUtil.requireParamNotNull("file name", displayName); + AssertionUtil.requireParamNotNull("file ID", pathId); + AssertionUtil.requireParamNotNull("language version", languageVersion); + + this.languageVersion = languageVersion; + this.content = content; + this.pathId = pathId; + this.displayName = displayName; + } + + + @Override + public LanguageVersion getLanguageVersion() { + return languageVersion; + } + + @Override + public String getDisplayName() { + return displayName; + } + + @Override + public String getPathId() { + return pathId; + } + + @Override + public String readContents() { + return content; + } + + @Override + public DataSource toDataSourceCompat() { + return new ReaderDataSource( + new StringReader(content), + pathId + ); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + StringTextFile that = (StringTextFile) o; + return Objects.equals(pathId, that.pathId); + } + + @Override + public int hashCode() { + return Objects.hash(pathId); + } + + @Override + public String toString() { + return getPathId(); + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/document/TextFile.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/document/TextFile.java new file mode 100644 index 0000000000..34d232d38a --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/document/TextFile.java @@ -0,0 +1,98 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.document; + +import java.io.File; +import java.io.IOException; + +import net.sourceforge.pmd.PmdAnalysis; +import net.sourceforge.pmd.annotation.Experimental; +import net.sourceforge.pmd.cpd.SourceCode; +import net.sourceforge.pmd.lang.LanguageVersion; +import net.sourceforge.pmd.util.datasource.DataSource; + +/** + * Represents some location containing character data. Despite the name, + * it's not necessarily backed by a file in the file-system: it may be + * eg an in-memory buffer, or a zip entry, ie it's an abstraction. Text + * files are the input which PMD and CPD process. + * + *

    Text files must provide read access, and may provide write access. + * This interface only provides block IO operations, while {@link TextDocument} adds logic + * about incremental edition (eg replacing a single region of text). + * + *

    This interface is meant to replace {@link DataSource} and {@link SourceCode.CodeLoader}. + * "DataSource" is not an appropriate name for a file which can be written + * to, also, the "data" it provides is text, not bytes. + * + *

    Experimental

    + * This interface will change in PMD 7 to support read/write operations + * and other things. You don't need to use it in PMD 6, as {@link FileCollector} + * decouples you from this. A file collector is available through {@link PmdAnalysis#files()}. + */ +@Experimental +public interface TextFile { + + /** + * The name used for a file that has no name. This is mostly only + * relevant for unit tests. + */ + String UNKNOWN_FILENAME = "(unknown file)"; + + + /** + * Returns the language version which should be used to process this + * file. This is a property of the file, which allows sources for + * several different language versions to be processed in the same + * PMD run. It also makes it so, that the file extension is not interpreted + * to find out the language version after the initial file collection + * phase. + * + * @return A language version + */ + LanguageVersion getLanguageVersion(); + + + /** + * Returns an identifier for the path of this file. This should not + * be interpreted as a {@link File}, it may not be a file on this + * filesystem. The only requirement for this method, is that two + * distinct text files should have distinct path IDs, and that from + * one analysis to the next, the path ID of logically identical files + * be the same. + * + *

    Basically this may be implemented as a URL, or a file path. It + * is used to index violation caches. + */ + String getPathId(); + + + /** + * Returns a display name for the file. This name is used for + * reporting and should not be interpreted. It may be relative + * to a directory, may use platform-specific path separators, + * may not be normalized. Use {@link #getPathId()} when you + * want an identifier. + */ + String getDisplayName(); + + + /** + * Reads the contents of the underlying character source. + * + * @return The most up-to-date content + * + * @throws IOException If this instance is closed + * @throws IOException If reading causes an IOException + */ + String readContents() throws IOException; + + /** + * Compatibility with {@link DataSource} (pmd internals still use DataSource in PMD 6). + */ + @Deprecated + DataSource toDataSourceCompat(); + +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/document/internal/LanguageDiscoverer.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/document/internal/LanguageDiscoverer.java new file mode 100644 index 0000000000..0544cc175c --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/document/internal/LanguageDiscoverer.java @@ -0,0 +1,63 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.document.internal; + +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; + +import net.sourceforge.pmd.lang.Language; +import net.sourceforge.pmd.lang.LanguageRegistry; + +/** + * Discovers the languages applicable to a file. + */ +public class LanguageDiscoverer { + + + private final Language forcedLanguage; + + /** + * Build a new instance. + * + * @param forcedLanguage If non-null, all files will be assigned this language. + */ + public LanguageDiscoverer(Language forcedLanguage) { + this.forcedLanguage = forcedLanguage; + } + + /** + * Get the Languages of a given source file. + * + * @param sourceFile The file. + * + * @return The Languages for the source file, may be empty. + */ + public List getLanguagesForFile(Path sourceFile) { + return getLanguagesForFile(sourceFile.getFileName().toString()); + } + + /** + * Get the Languages of a given source file. + * + * @param fileName The file name. + * + * @return The Languages for the source file, may be empty. + */ + public List getLanguagesForFile(String fileName) { + if (forcedLanguage != null) { + return Collections.singletonList(forcedLanguage); + } + String extension = getExtension(fileName); + return LanguageRegistry.findByExtension(extension); + } + + // Get the extensions from a file + private String getExtension(String fileName) { + return StringUtils.substringAfterLast(fileName, "."); + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/RuleReference.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/RuleReference.java index 1cd20d9363..57ba908c5c 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/RuleReference.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/RuleReference.java @@ -16,7 +16,6 @@ import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.RulePriority; import net.sourceforge.pmd.RuleSetReference; import net.sourceforge.pmd.lang.LanguageVersion; -import net.sourceforge.pmd.lang.ast.AstProcessingStage; import net.sourceforge.pmd.properties.PropertyDescriptor; import net.sourceforge.pmd.util.StringUtil; @@ -278,12 +277,6 @@ public class RuleReference extends AbstractDelegateRule { } - @Override - public boolean dependsOn(AstProcessingStage stage) { - return getRule().dependsOn(stage); - } - - public RuleSetReference getRuleSetReference() { return ruleSetReference; } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/XPathRule.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/XPathRule.java index 7e0039d637..32f18070be 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/XPathRule.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/XPathRule.java @@ -19,7 +19,6 @@ import org.slf4j.LoggerFactory; import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.annotation.DeprecatedUntil700; -import net.sourceforge.pmd.lang.ast.AstProcessingStage; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.rule.xpath.PmdXPathException; import net.sourceforge.pmd.lang.rule.xpath.XPathVersion; @@ -196,12 +195,6 @@ public final class XPathRule extends AbstractRule { return null; } - @Override - public boolean dependsOn(AstProcessingStage stage) { - // FIXME must be made language-specific - return true; - } - private static Map getXPathVersions() { Map tmp = new HashMap<>(); for (XPathVersion v : XPathVersion.values()) { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/processor/PmdRunnable.java b/pmd-core/src/main/java/net/sourceforge/pmd/processor/PmdRunnable.java index ea49880b17..87df7b0eb6 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/processor/PmdRunnable.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/processor/PmdRunnable.java @@ -7,6 +7,9 @@ package net.sourceforge.pmd.processor; import java.io.File; import java.io.IOException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import net.sourceforge.pmd.PMDConfiguration; import net.sourceforge.pmd.Report; import net.sourceforge.pmd.RuleSets; @@ -14,7 +17,6 @@ import net.sourceforge.pmd.RuleViolation; import net.sourceforge.pmd.benchmark.TimeTracker; import net.sourceforge.pmd.benchmark.TimedOperation; import net.sourceforge.pmd.benchmark.TimedOperationCategory; -import net.sourceforge.pmd.internal.RulesetStageDependencyHelper; import net.sourceforge.pmd.internal.SystemProps; import net.sourceforge.pmd.lang.LanguageVersion; import net.sourceforge.pmd.lang.LanguageVersionHandler; @@ -32,14 +34,13 @@ import net.sourceforge.pmd.util.datasource.DataSource; */ abstract class PmdRunnable implements Runnable { + private static final Logger LOGGER = LoggerFactory.getLogger(PmdRunnable.class); private final DataSource dataSource; private final File file; private final GlobalAnalysisListener globalListener; private final PMDConfiguration configuration; - private final RulesetStageDependencyHelper dependencyHelper; - PmdRunnable(DataSource dataSource, GlobalAnalysisListener globalListener, PMDConfiguration configuration) { @@ -50,7 +51,6 @@ abstract class PmdRunnable implements Runnable { this.file = new File(realFileName); this.globalListener = globalListener; this.configuration = configuration; - this.dependencyHelper = new RulesetStageDependencyHelper(configuration); } /** @@ -82,7 +82,6 @@ abstract class PmdRunnable implements Runnable { if (e instanceof Error && !SystemProps.isErrorRecoveryMode()) { // NOPMD: throw e; } - configuration.getAnalysisCache().analysisFailed(file); // The listener handles logging if needed, // it may also rethrow the error, as a FileAnalysisException (which we let through below) @@ -130,7 +129,8 @@ abstract class PmdRunnable implements Runnable { languageVersion, filename, sourceCode, - SemanticErrorReporter.noop() // TODO + SemanticErrorReporter.reportToLogger(LOGGER), + configuration.getClassLoader() ); @@ -143,8 +143,6 @@ abstract class PmdRunnable implements Runnable { RootNode rootNode = parse(parser, task); - dependencyHelper.runLanguageSpecificStages(ruleSets, languageVersion, rootNode); - ruleSets.apply(rootNode, listener); } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/RendererFactory.java b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/RendererFactory.java index 05385eff45..226f2737e7 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/RendererFactory.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/RendererFactory.java @@ -15,6 +15,7 @@ import java.util.TreeMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import net.sourceforge.pmd.internal.util.AssertionUtil; import net.sourceforge.pmd.properties.PropertyDescriptor; /** @@ -61,6 +62,7 @@ public final class RendererFactory { * @return A Renderer instance. */ public static Renderer createRenderer(String reportFormat, Properties properties) { + AssertionUtil.requireParamNotNull("reportFormat", reportFormat); Class rendererClass = getRendererClass(reportFormat); Constructor constructor = getRendererConstructor(rendererClass); @@ -101,6 +103,7 @@ public final class RendererFactory { @SuppressWarnings("unchecked") private static Class getRendererClass(String reportFormat) { + AssertionUtil.requireParamNotNull("reportFormat", reportFormat); Class rendererClass = REPORT_FORMAT_TO_RENDERER.get(reportFormat); // Look up a custom renderer class diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/reporting/GlobalAnalysisListener.java b/pmd-core/src/main/java/net/sourceforge/pmd/reporting/GlobalAnalysisListener.java index ca71b7a608..c2a8f7d586 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/reporting/GlobalAnalysisListener.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/reporting/GlobalAnalysisListener.java @@ -85,17 +85,7 @@ public interface GlobalAnalysisListener extends AutoCloseable { * A listener that does nothing. */ static GlobalAnalysisListener noop() { - return new GlobalAnalysisListener() { - @Override - public FileAnalysisListener startFileAnalysis(DataSource file) { - return FileAnalysisListener.noop(); - } - - @Override - public void close() { - // do nothing - } - }; + return NoopAnalysisListener.INSTANCE; } /** @@ -111,10 +101,11 @@ public interface GlobalAnalysisListener extends AutoCloseable { */ static GlobalAnalysisListener tee(Collection listeners) { AssertionUtil.requireParamNotNull("Listeners", listeners); - AssertionUtil.requireNotEmpty("Listeners", listeners); AssertionUtil.requireContainsNoNullValue("Listeners", listeners); - if (listeners.size() == 1) { + if (listeners.isEmpty()) { + return noop(); + } else if (listeners.size() == 1) { return listeners.iterator().next(); } @@ -150,6 +141,7 @@ public interface GlobalAnalysisListener extends AutoCloseable { List myList = listeners.stream() .flatMap(l -> l instanceof TeeListener ? ((TeeListener) l).myList.stream() : Stream.of(l)) + .filter(l -> !(l instanceof NoopAnalysisListener)) .collect(CollectionUtil.toUnmodifiableList()); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/reporting/NoopAnalysisListener.java b/pmd-core/src/main/java/net/sourceforge/pmd/reporting/NoopAnalysisListener.java new file mode 100644 index 0000000000..fcd5da121c --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/reporting/NoopAnalysisListener.java @@ -0,0 +1,29 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.reporting; + +import net.sourceforge.pmd.util.datasource.DataSource; + +/** + * @author Clément Fournier + */ +final class NoopAnalysisListener implements GlobalAnalysisListener { + + static final NoopAnalysisListener INSTANCE = new NoopAnalysisListener(); + + private NoopAnalysisListener() { + + } + + @Override + public FileAnalysisListener startFileAnalysis(DataSource file) { + return FileAnalysisListener.noop(); + } + + @Override + public void close() { + // do nothing + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/reporting/ReportStats.java b/pmd-core/src/main/java/net/sourceforge/pmd/reporting/ReportStats.java new file mode 100644 index 0000000000..eef7cd7dc5 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/reporting/ReportStats.java @@ -0,0 +1,38 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.reporting; + +/** + * Summarized info about a report. + * + * @author Clément Fournier + */ +public final class ReportStats { + + private final int numErrors; + private final int numViolations; + + ReportStats(int numErrors, int numViolations) { + this.numErrors = numErrors; + this.numViolations = numViolations; + } + + public static ReportStats empty() { + return new ReportStats(0, 0); + } + + public int getNumErrors() { + return numErrors; + } + + public int getNumViolations() { + return numViolations; + } + + @Override + public String toString() { + return "ReportStats{numErrors=" + numErrors + ", numViolations=" + numViolations + '}'; + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/reporting/ReportStatsListener.java b/pmd-core/src/main/java/net/sourceforge/pmd/reporting/ReportStatsListener.java new file mode 100644 index 0000000000..baabb2d12b --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/reporting/ReportStatsListener.java @@ -0,0 +1,63 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.reporting; + +import java.util.concurrent.atomic.AtomicInteger; + +import net.sourceforge.pmd.Report.ProcessingError; +import net.sourceforge.pmd.RuleViolation; +import net.sourceforge.pmd.util.BaseResultProducingCloseable; +import net.sourceforge.pmd.util.datasource.DataSource; + +/** + * Collects summarized info about a PMD run. + * + * @author Clément Fournier + */ +public final class ReportStatsListener extends BaseResultProducingCloseable implements GlobalAnalysisListener { + + private final AtomicInteger numErrors = new AtomicInteger(0); + private final AtomicInteger numViolations = new AtomicInteger(0); + + @Override + public FileAnalysisListener startFileAnalysis(DataSource file) { + return new FileAnalysisListener() { + // this object does not need thread-safety so we avoid using atomics, + // except during the merge. + private int numErrors = 0; + private int numViolations = 0; + + @Override + public void onRuleViolation(RuleViolation violation) { + numViolations++; + } + + @Override + public void onError(ProcessingError error) { + numErrors++; + } + + @Override + public void close() { + if (numErrors > 0) { + ReportStatsListener.this.numErrors.addAndGet(this.numErrors); + } + if (numViolations > 0) { + ReportStatsListener.this.numViolations.addAndGet(this.numViolations); + } + } + }; + } + + @Override + protected ReportStats getResultImpl() { + return new ReportStats( + numErrors.get(), + numViolations.get() + ); + } + + +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/rules/RuleBuilder.java b/pmd-core/src/main/java/net/sourceforge/pmd/rules/RuleBuilder.java index 47313a12f7..beabbc6080 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/rules/RuleBuilder.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/rules/RuleBuilder.java @@ -75,7 +75,7 @@ public class RuleBuilder { Language lang = LanguageRegistry.findLanguageByTerseName(languageName); if (lang == null) { throw new IllegalArgumentException( - "Unknown Language '" + languageName + "' for rule" + name + ", supported Languages are " + "Unknown Language '" + languageName + "' for rule " + name + ", supported Languages are " + LanguageRegistry.getLanguages().stream().map(Language::getTerseName).collect(Collectors.joining(", ")) ); } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/ClasspathClassLoader.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/ClasspathClassLoader.java index 6e31ee3986..0266574901 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/ClasspathClassLoader.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/ClasspathClassLoader.java @@ -20,6 +20,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.sourceforge.pmd.annotation.InternalApi; +import net.sourceforge.pmd.internal.util.AssertionUtil; /** * Create a ClassLoader which loads classes using a CLASSPATH like String. If @@ -57,17 +58,19 @@ public class ClasspathClassLoader extends URLClassLoader { return urlList.toArray(new URL[0]); } - private static URL[] initURLs(String classpath) throws IOException { - if (classpath == null) { - throw new IllegalArgumentException("classpath argument cannot be null"); - } + private static URL[] initURLs(String classpath) { + AssertionUtil.requireParamNotNull("classpath", classpath); final List urls = new ArrayList<>(); - if (classpath.startsWith("file:")) { - // Treat as file URL - addFileURLs(urls, new URL(classpath)); - } else { - // Treat as classpath - addClasspathURLs(urls, classpath); + try { + if (classpath.startsWith("file:")) { + // Treat as file URL + addFileURLs(urls, new URL(classpath)); + } else { + // Treat as classpath + addClasspathURLs(urls, classpath); + } + } catch (IOException e) { + throw new IllegalArgumentException("Cannot prepend classpath " + classpath + "\n" + e.getMessage(), e); } return urls.toArray(new URL[0]); } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/FileUtil.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/FileUtil.java index 28f1aa886c..38484692ed 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/FileUtil.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/FileUtil.java @@ -99,7 +99,7 @@ public final class FileUtil { } private static List collect(List dataSources, String fileLocation, - FilenameFilter filenameFilter) { + FilenameFilter filenameFilter) { File file = new File(fileLocation); if (!file.exists()) { throw new RuntimeException("File " + file.getName() + " doesn't exist"); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/StringUtil.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/StringUtil.java index d25bf1027a..cde70a9b78 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/StringUtil.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/StringUtil.java @@ -4,6 +4,7 @@ package net.sourceforge.pmd.util; +import java.text.MessageFormat; import java.util.List; import java.util.Locale; import java.util.regex.Matcher; @@ -513,6 +514,13 @@ public final class StringUtil { return retval.toString(); } + /** + * Escape the string so that it appears literally when interpreted + * by a {@link MessageFormat}. + */ + public static String quoteMessageFormat(String str) { + return str.replaceAll("'", "''"); + } public enum CaseConvention { /** SCREAMING_SNAKE_CASE. */ diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/log/MessageReporter.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/log/MessageReporter.java new file mode 100644 index 0000000000..689e4f41c4 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/log/MessageReporter.java @@ -0,0 +1,69 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.log; + +import java.text.MessageFormat; + +import org.slf4j.event.Level; + +import net.sourceforge.pmd.annotation.InternalApi; + +/** + * Façade to report user-facing messages (info, warning and error). + * Note: messages are formatted using {@link MessageFormat}. + * + *

    Internal API: this is a transitional API that will be significantly + * changed in PMD 7, with the transition to SLF4J. See https://github.com/pmd/pmd/issues/3816 + * + * TODO rename to PmdReporter + * + * @author ClΓ©ment Fournier + */ +@InternalApi +public interface MessageReporter { + + boolean isLoggable(Level level); + + void log(Level level, String message, Object... formatArgs); + + void logEx(Level level, String message, Object[] formatArgs, Throwable error); + + default void info(String message, Object... formatArgs) { + log(Level.INFO, message, formatArgs); + } + + default void warn(String message, Object... formatArgs) { + log(Level.WARN, message, formatArgs); + } + + default void warnEx(String message, Throwable error) { + logEx(Level.WARN, message, new Object[0], error); + } + + default void warnEx(String message, Object[] formatArgs, Throwable error) { + logEx(Level.WARN, message, formatArgs, error); + } + + default void error(String message, Object... formatArgs) { + log(Level.ERROR, message, formatArgs); + } + + default void errorEx(String message, Throwable error) { + logEx(Level.ERROR, message, new Object[0], error); + } + + default void errorEx(String message, Object[] formatArgs, Throwable error) { + logEx(Level.ERROR, message, formatArgs, error); + } + + /** + * Returns the number of errors reported on this instance. + * Any call to {@link #log(Level, String, Object...)} or + * {@link #logEx(Level, String, Object[], Throwable)} with a level + * of {@link Level#ERROR} should increment this number. + */ + int numErrors(); + +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/log/internal/ErrorsAsWarningsReporter.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/log/internal/ErrorsAsWarningsReporter.java new file mode 100644 index 0000000000..80270d9163 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/log/internal/ErrorsAsWarningsReporter.java @@ -0,0 +1,41 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.log.internal; + +import org.slf4j.event.Level; + +import net.sourceforge.pmd.annotation.InternalApi; +import net.sourceforge.pmd.util.log.MessageReporter; + +/** + * Turns errors into warnings reported on another logger. + * + * @author ClΓ©ment Fournier + */ +@InternalApi +public final class ErrorsAsWarningsReporter extends MessageReporterBase { + + private final MessageReporter backend; + + public ErrorsAsWarningsReporter(MessageReporter backend) { + this.backend = backend; + } + + @Override + protected boolean isLoggableImpl(Level level) { + if (level == Level.ERROR) { + level = Level.WARN; + } + return super.isLoggableImpl(level); + } + + @Override + protected void logImpl(Level level, String message, Object[] formatArgs) { + if (level == Level.ERROR) { + level = Level.WARN; + } + backend.log(level, message, formatArgs); + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/log/internal/MessageReporterBase.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/log/internal/MessageReporterBase.java new file mode 100644 index 0000000000..64f045174d --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/log/internal/MessageReporterBase.java @@ -0,0 +1,80 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.log.internal; + +import java.text.MessageFormat; + +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.slf4j.event.Level; + +import net.sourceforge.pmd.util.StringUtil; +import net.sourceforge.pmd.util.log.MessageReporter; + +/** + * Base implementation. + * + * @author ClΓ©ment Fournier + */ +abstract class MessageReporterBase implements MessageReporter { + + private int numErrors; + private Level minLevel = Level.TRACE; + + /** + * null level means off. + */ + public final void setLevel(Level minLevel) { + this.minLevel = minLevel; + } + + @Override + public final boolean isLoggable(Level level) { + return minLevel != null + && minLevel.compareTo(level) >= 0 + && isLoggableImpl(level); + } + + protected boolean isLoggableImpl(Level level) { + return true; + } + + @Override + public void logEx(Level level, String message, Object[] formatArgs, Throwable error) { + if (isLoggable(level)) { + message = MessageFormat.format(message, formatArgs); + String errorMessage = error.getMessage(); + if (errorMessage == null) { + errorMessage = error.getClass().getSimpleName(); + } + errorMessage = StringUtil.quoteMessageFormat(errorMessage); + log(level, message + ": " + errorMessage); + if (isLoggable(Level.DEBUG)) { + String stackTrace = StringUtil.quoteMessageFormat(ExceptionUtils.getStackTrace(error)); + log(Level.DEBUG, stackTrace); + } + } + } + + @Override + public final void log(Level level, String message, Object... formatArgs) { + if (level == Level.ERROR) { + this.numErrors++; + } + if (isLoggable(level)) { + logImpl(level, message, formatArgs); + } + } + + /** + * Perform logging assuming {@link #isLoggable(Level)} is true. + */ + protected abstract void logImpl(Level level, String message, Object[] formatArgs); + + + @Override + public int numErrors() { + return numErrors; + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/log/internal/NoopReporter.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/log/internal/NoopReporter.java new file mode 100644 index 0000000000..98eb4a72a3 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/log/internal/NoopReporter.java @@ -0,0 +1,31 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.log.internal; + +import org.slf4j.event.Level; + +import net.sourceforge.pmd.annotation.InternalApi; +import net.sourceforge.pmd.util.log.MessageReporter; + +/** + * A logger that ignores all messages. + * + * @author ClΓ©ment Fournier + */ +@InternalApi +public final class NoopReporter extends MessageReporterBase implements MessageReporter { + + // note: not singleton because PmdLogger accumulates error count. + + @Override + protected boolean isLoggableImpl(Level level) { + return false; + } + + @Override + protected void logImpl(Level level, String message, Object[] formatArgs) { + // noop + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/log/internal/SimpleMessageReporter.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/log/internal/SimpleMessageReporter.java new file mode 100644 index 0000000000..672e40df98 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/log/internal/SimpleMessageReporter.java @@ -0,0 +1,36 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.log.internal; + +import org.slf4j.Logger; +import org.slf4j.event.Level; + +import net.sourceforge.pmd.annotation.InternalApi; +import net.sourceforge.pmd.util.log.MessageReporter; + +/** + * A {@link Logger} (java.util) based logger impl. + * + * @author ClΓ©ment Fournier + */ +@InternalApi +public class SimpleMessageReporter extends MessageReporterBase implements MessageReporter { + + private final Logger backend; + + public SimpleMessageReporter(Logger backend) { + this.backend = backend; + } + + @Override + protected boolean isLoggableImpl(Level level) { + return backend.isEnabledForLevel(level); + } + + @Override + protected void logImpl(Level level, String message, Object[] formatArgs) { + backend.atLevel(level).log(message, formatArgs); + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExportCli.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExportCli.java index 9df543dc00..923e0b970a 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExportCli.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExportCli.java @@ -26,7 +26,6 @@ import net.sourceforge.pmd.lang.Language; import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.LanguageVersion; import net.sourceforge.pmd.lang.LanguageVersionHandler; -import net.sourceforge.pmd.lang.ast.AstAnalysisContext; import net.sourceforge.pmd.lang.ast.Parser; import net.sourceforge.pmd.lang.ast.Parser.ParserTask; import net.sourceforge.pmd.lang.ast.RootNode; @@ -185,20 +184,6 @@ public class TreeExportCli { ParserTask task = new ParserTask(langVersion, filename, fullSource, SemanticErrorReporter.noop()); RootNode root = parser.parse(task); - AstAnalysisContext ctx = new AstAnalysisContext() { - @Override - public ClassLoader getTypeResolutionClassLoader() { - return getClass().getClassLoader(); - } - - @Override - public LanguageVersion getLanguageVersion() { - return langVersion; - } - }; - - languageHandler.getProcessingStages().forEach(it -> it.processAST(root, ctx)); - renderer.renderSubtree(root, System.out); } finally { source.close(); diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/PmdAnalysisTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/PmdAnalysisTest.java new file mode 100644 index 0000000000..5c437fc1a9 --- /dev/null +++ b/pmd-core/src/test/java/net/sourceforge/pmd/PmdAnalysisTest.java @@ -0,0 +1,75 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import java.io.IOException; + +import org.junit.Test; +import org.mockito.ArgumentMatchers; + +import net.sourceforge.pmd.renderers.Renderer; + +/** + * @author ClΓ©ment Fournier + */ +public class PmdAnalysisTest { + + @Test + public void testPmdAnalysisWithEmptyConfig() { + PMDConfiguration config = new PMDConfiguration(); + try (PmdAnalysis pmd = PmdAnalysis.create(config)) { + assertThat(pmd.files().getCollectedFiles(), empty()); + assertThat(pmd.rulesets(), empty()); + assertThat(pmd.renderers(), empty()); + } + } + + @Test + public void testRendererInteractions() throws IOException { + PMDConfiguration config = new PMDConfiguration(); + config.setInputPaths("sample-source/dummy"); + Renderer renderer = spy(Renderer.class); + try (PmdAnalysis pmd = PmdAnalysis.create(config)) { + pmd.addRenderer(renderer); + verify(renderer, never()).start(); + pmd.performAnalysis(); + } + + verify(renderer, times(1)).renderFileReport(ArgumentMatchers.any()); + verify(renderer, times(1)).start(); + verify(renderer, times(1)).end(); + verify(renderer, times(1)).flush(); + } + + @Test + public void testRulesetLoading() { + PMDConfiguration config = new PMDConfiguration(); + config.addRuleSet("rulesets/dummy/basic.xml"); + try (PmdAnalysis pmd = PmdAnalysis.create(config)) { + assertThat(pmd.rulesets(), hasSize(1)); + } + } + + @Test + public void testRulesetWhenSomeoneHasAnError() { + PMDConfiguration config = new PMDConfiguration(); + config.addRuleSet("rulesets/dummy/basic.xml"); + config.addRuleSet("rulesets/xxxe/notaruleset.xml"); + try (PmdAnalysis pmd = PmdAnalysis.create(config)) { + assertThat(pmd.rulesets(), hasSize(1)); // no failure + assertThat(pmd.getReporter().numErrors(), equalTo(1)); + } + } + +} diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/ConfigurationTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/PmdConfigurationTest.java similarity index 84% rename from pmd-core/src/test/java/net/sourceforge/pmd/ConfigurationTest.java rename to pmd-core/src/test/java/net/sourceforge/pmd/PmdConfigurationTest.java index d765920004..99c321530f 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/ConfigurationTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/PmdConfigurationTest.java @@ -4,9 +4,13 @@ package net.sourceforge.pmd; +import static net.sourceforge.pmd.util.CollectionUtil.listOf; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; @@ -16,6 +20,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.nio.charset.StandardCharsets; +import java.util.Collections; import java.util.Properties; import org.junit.Assert; @@ -29,7 +34,7 @@ import net.sourceforge.pmd.renderers.CSVRenderer; import net.sourceforge.pmd.renderers.Renderer; import net.sourceforge.pmd.util.ClasspathClassLoader; -public class ConfigurationTest { +public class PmdConfigurationTest { @Rule public TemporaryFolder folder = new TemporaryFolder(); @@ -37,7 +42,7 @@ public class ConfigurationTest { @Test public void testSuppressMarker() { PMDConfiguration configuration = new PMDConfiguration(); - assertEquals("Default suppress marker", PMD.SUPPRESS_MARKER, configuration.getSuppressMarker()); + assertEquals("Default suppress marker", PMDConfiguration.DEFAULT_SUPPRESS_MARKER, configuration.getSuppressMarker()); configuration.setSuppressMarker("CUSTOM_MARKER"); assertEquals("Changed suppress marker", "CUSTOM_MARKER", configuration.getSuppressMarker()); } @@ -51,10 +56,10 @@ public class ConfigurationTest { } @Test - public void testClassLoader() throws IOException { + public void testClassLoader() { PMDConfiguration configuration = new PMDConfiguration(); assertEquals("Default ClassLoader", PMDConfiguration.class.getClassLoader(), configuration.getClassLoader()); - configuration.prependClasspath("some.jar"); + configuration.prependAuxClasspath("some.jar"); assertEquals("Prepended ClassLoader class", ClasspathClassLoader.class, configuration.getClassLoader().getClass()); URL[] urls = ((ClasspathClassLoader) configuration.getClassLoader()).getURLs(); @@ -68,31 +73,31 @@ public class ConfigurationTest { } @Test - public void auxClasspathWithRelativeFileEmpty() throws IOException { + public void auxClasspathWithRelativeFileEmpty() { String relativeFilePath = "src/test/resources/net/sourceforge/pmd/cli/auxclasspath-empty.cp"; PMDConfiguration configuration = new PMDConfiguration(); - configuration.prependClasspath("file:" + relativeFilePath); + configuration.prependAuxClasspath("file:" + relativeFilePath); URL[] urls = ((ClasspathClassLoader) configuration.getClassLoader()).getURLs(); Assert.assertEquals(0, urls.length); } @Test - public void auxClasspathWithRelativeFileEmpty2() throws IOException { + public void auxClasspathWithRelativeFileEmpty2() { String relativeFilePath = "./src/test/resources/net/sourceforge/pmd/cli/auxclasspath-empty.cp"; PMDConfiguration configuration = new PMDConfiguration(); - configuration.prependClasspath("file:" + relativeFilePath); + configuration.prependAuxClasspath("file:" + relativeFilePath); URL[] urls = ((ClasspathClassLoader) configuration.getClassLoader()).getURLs(); Assert.assertEquals(0, urls.length); } @Test - public void auxClasspathWithRelativeFile() throws IOException, URISyntaxException { + public void auxClasspathWithRelativeFile() throws URISyntaxException { final String FILE_SCHEME = "file"; String currentWorkingDirectory = new File("").getAbsoluteFile().toURI().getPath(); String relativeFilePath = "src/test/resources/net/sourceforge/pmd/cli/auxclasspath.cp"; PMDConfiguration configuration = new PMDConfiguration(); - configuration.prependClasspath("file:" + relativeFilePath); + configuration.prependAuxClasspath("file:" + relativeFilePath); URL[] urls = ((ClasspathClassLoader) configuration.getClassLoader()).getURLs(); URI[] uris = new URI[urls.length]; for (int i = 0; i < urls.length; i++) { @@ -111,6 +116,30 @@ public class ConfigurationTest { Assert.assertArrayEquals(expectedUris, uris); } + @Test + public void testRuleSetsLegacy() { + PMDConfiguration configuration = new PMDConfiguration(); + assertNull("Default RuleSets", configuration.getRuleSets()); + configuration.setRuleSets("/rulesets/basic.xml"); + assertEquals("Changed RuleSets", "/rulesets/basic.xml", configuration.getRuleSets()); + configuration.setRuleSets((String) null); + assertNull(configuration.getRuleSets()); + } + + @Test + public void testRuleSets() { + PMDConfiguration configuration = new PMDConfiguration(); + assertThat(configuration.getRuleSetPaths(), empty()); + configuration.setRuleSets(listOf("/rulesets/basic.xml")); + assertEquals(listOf("/rulesets/basic.xml"), configuration.getRuleSetPaths()); + configuration.addRuleSet("foo.xml"); + assertEquals(listOf("/rulesets/basic.xml", "foo.xml"), configuration.getRuleSetPaths()); + configuration.setRuleSets(Collections.emptyList()); + assertThat(configuration.getRuleSetPaths(), empty()); + // should be addable even though we set it to an unmodifiable empty list + configuration.addRuleSet("foo.xml"); + assertEquals(listOf("foo.xml"), configuration.getRuleSetPaths()); + } @Test public void testMinimumPriority() { @@ -232,7 +261,7 @@ public class ConfigurationTest { } @Test - public void testAnalysisCacheLocation() throws IOException { + public void testAnalysisCacheLocation() { final PMDConfiguration configuration = new PMDConfiguration(); configuration.setAnalysisCacheLocation(null); diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/ant/PMDTaskTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/ant/PMDTaskTest.java index c7bfe16b0c..81acee181d 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/ant/PMDTaskTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/ant/PMDTaskTest.java @@ -91,7 +91,7 @@ public class PMDTaskTest { try (InputStream in = new FileInputStream("target/pmd-ant-test.txt")) { String actual = IOUtils.toString(in, StandardCharsets.UTF_8); // remove any trailing newline - actual = actual.replaceAll("\n|\r", ""); + actual = actual.trim(); Assert.assertEquals("sample.dummy:1:\tSampleXPathRule:\tTest Rule 2", actual); } } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/cache/FileAnalysisCacheTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/cache/FileAnalysisCacheTest.java index 78bbb13f4f..a81cbb55b5 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/cache/FileAnalysisCacheTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/cache/FileAnalysisCacheTest.java @@ -82,21 +82,21 @@ public class FileAnalysisCacheTest { } @Test - public void testStoreCreatesFile() { + public void testStoreCreatesFile() throws Exception { final FileAnalysisCache cache = new FileAnalysisCache(unexistingCacheFile); cache.persist(); assertTrue("Cache file doesn't exist after store", unexistingCacheFile.exists()); } @Test - public void testStoreOnUnwritableFileShouldntThrow() { + public void testStoreOnUnwritableFileShouldntThrow() throws IOException { emptyCacheFile.setWritable(false); final FileAnalysisCache cache = new FileAnalysisCache(emptyCacheFile); cache.persist(); } @Test - public void testStorePersistsFilesWithViolations() { + public void testStorePersistsFilesWithViolations() throws IOException { final FileAnalysisCache cache = new FileAnalysisCache(newCacheFile); cache.checkValidity(mock(RuleSets.class), mock(ClassLoader.class)); cache.isUpToDate(sourceFile); @@ -107,7 +107,9 @@ public class FileAnalysisCacheTest { when(rule.getLanguage()).thenReturn(mock(Language.class)); when(rv.getRule()).thenReturn(rule); - cache.startFileAnalysis(mock(DataSource.class)).onRuleViolation(rv); + DataSource ds = mock(DataSource.class); + when(ds.getNiceFileName(false, "")).thenReturn(sourceFile.getPath()); + cache.startFileAnalysis(ds).onRuleViolation(rv); cache.persist(); final FileAnalysisCache reloadedCache = new FileAnalysisCache(newCacheFile); @@ -120,7 +122,7 @@ public class FileAnalysisCacheTest { } @Test - public void testCacheValidityWithNoChanges() { + public void testCacheValidityWithNoChanges() throws IOException { final RuleSets rs = mock(RuleSets.class); final ClassLoader cl = mock(ClassLoader.class); @@ -150,7 +152,7 @@ public class FileAnalysisCacheTest { } @Test - public void testRulesetChangeInvalidatesCache() { + public void testRulesetChangeInvalidatesCache() throws IOException { final RuleSets rs = mock(RuleSets.class); final ClassLoader cl = mock(ClassLoader.class); @@ -367,7 +369,7 @@ public class FileAnalysisCacheTest { } private void setupCacheWithFiles(final File cacheFile, final RuleSets ruleSets, - final ClassLoader classLoader, final File... files) { + final ClassLoader classLoader, final File... files) throws IOException { // Setup a cache file with an entry for an empty Source.java with no violations final FileAnalysisCache cache = new FileAnalysisCache(cacheFile); cache.checkValidity(ruleSets, classLoader); diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/cli/CoreCliTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/cli/CoreCliTest.java index ed7078f2af..47106eb626 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/cli/CoreCliTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/cli/CoreCliTest.java @@ -5,6 +5,7 @@ package net.sourceforge.pmd.cli; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsStringIgnoringCase; import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertEquals; @@ -19,6 +20,7 @@ import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; +import org.apache.commons.io.IOUtils; import org.hamcrest.Matcher; import org.junit.AfterClass; import org.junit.Before; @@ -124,6 +126,19 @@ public class CoreCliTest { assertTrue("Report file should have been created", Files.exists(reportFile)); } + @Test + public void testFileCollectionWithUnknownFiles() throws IOException { + Path reportFile = tempRoot().resolve("out/reportFile.txt"); + Files.createFile(srcDir.resolve("foo.not_analysable")); + assertFalse("Report file should not exist", Files.exists(reportFile)); + + runPmdSuccessfully("--no-cache", "--dir", srcDir, "--rulesets", DUMMY_RULESET, "--report-file", reportFile, "--debug"); + + assertTrue("Report file should have been created", Files.exists(reportFile)); + String reportText = IOUtils.toString(Files.newBufferedReader(reportFile, StandardCharsets.UTF_8)); + assertThat(reportText, not(containsStringIgnoringCase("error"))); + } + @Test public void testNonExistentReportFileDeprecatedOptions() { Path reportFile = tempRoot().resolve("out/reportFile.txt"); @@ -180,13 +195,13 @@ public class CoreCliTest { @Test public void debugLogging() { runPmdSuccessfully("--debug", "--no-cache", "--dir", srcDir, "--rulesets", DUMMY_RULESET); - errStreamCaptor.getLog().contains("[main] DEBUG net.sourceforge.pmd.PMD - Log level is at DEBUG"); + assertThat(errStreamCaptor.getLog(), containsString("[main] INFO net.sourceforge.pmd.PMD - Log level is at TRACE")); } @Test public void defaultLogging() { runPmdSuccessfully("--no-cache", "--dir", srcDir, "--rulesets", DUMMY_RULESET); - errStreamCaptor.getLog().contains("[main] INFO net.sourceforge.pmd.PMD - Log level is at INFO"); + assertThat(errStreamCaptor.getLog(), containsString("[main] INFO net.sourceforge.pmd.PMD - Log level is at INFO")); } @@ -240,8 +255,8 @@ public class CoreCliTest { } private static void runPmd(int expectedExitCode, Object[] args) { - int actualExitCode = PMD.run(argsToString(args)); - assertEquals("Exit code", expectedExitCode, actualExitCode); + StatusCode actualExitCode = PMD.runPmd(argsToString(args)); + assertEquals("Exit code", expectedExitCode, actualExitCode.toInt()); } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/cli/PMDFilelistTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/cli/PMDFilelistTest.java index d93dbd738b..e0e5d2d8e6 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/cli/PMDFilelistTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/cli/PMDFilelistTest.java @@ -4,93 +4,85 @@ package net.sourceforge.pmd.cli; -import java.io.IOException; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.hasSize; -import org.junit.Assert; +import java.io.IOException; +import java.util.List; + +import org.checkerframework.checker.nullness.qual.NonNull; import org.junit.Test; -import net.sourceforge.pmd.PMD; import net.sourceforge.pmd.PMDConfiguration; -import net.sourceforge.pmd.lang.DummyLanguageModule; -import net.sourceforge.pmd.lang.Language; -import net.sourceforge.pmd.util.datasource.DataSource; +import net.sourceforge.pmd.internal.util.FileCollectionUtil; +import net.sourceforge.pmd.lang.LanguageVersionDiscoverer; +import net.sourceforge.pmd.lang.document.FileCollector; +import net.sourceforge.pmd.lang.document.TextFile; +import net.sourceforge.pmd.util.log.internal.NoopReporter; public class PMDFilelistTest { + + private static @NonNull FileCollector newCollector() { + return FileCollector.newCollector(new LanguageVersionDiscoverer(), new NoopReporter()); + } + @Test public void testGetApplicableFiles() throws IOException { - Set languages = new HashSet<>(); - languages.add(new DummyLanguageModule()); + FileCollector collector = newCollector(); - PMDConfiguration configuration = new PMDConfiguration(); - configuration.setInputFilePath("src/test/resources/net/sourceforge/pmd/cli/filelist.txt"); + FileCollectionUtil.collectFileList(collector, "src/test/resources/net/sourceforge/pmd/cli/filelist.txt"); - List applicableFiles = PMD.getApplicableFiles(configuration, languages); - Assert.assertEquals(2, applicableFiles.size()); - Assert.assertTrue(applicableFiles.get(0).getNiceFileName(false, "").endsWith("somefile.dummy")); - Assert.assertTrue(applicableFiles.get(1).getNiceFileName(false, "").endsWith("anotherfile.dummy")); + List applicableFiles = collector.getCollectedFiles(); + assertThat(applicableFiles, hasSize(2)); + assertThat(applicableFiles.get(0).getPathId(), endsWith("anotherfile.dummy")); + assertThat(applicableFiles.get(1).getPathId(), endsWith("somefile.dummy")); } @Test public void testGetApplicableFilesMultipleLines() throws IOException { - Set languages = new HashSet<>(); - languages.add(new DummyLanguageModule()); + FileCollector collector = newCollector(); - PMDConfiguration configuration = new PMDConfiguration(); - configuration.setInputFilePath("src/test/resources/net/sourceforge/pmd/cli/filelist2.txt"); + FileCollectionUtil.collectFileList(collector, "src/test/resources/net/sourceforge/pmd/cli/filelist2.txt"); - List applicableFiles = PMD.getApplicableFiles(configuration, languages); - Assert.assertEquals(3, applicableFiles.size()); - Assert.assertTrue(applicableFiles.get(0).getNiceFileName(false, "").endsWith("somefile.dummy")); - Assert.assertTrue(applicableFiles.get(1).getNiceFileName(false, "").endsWith("anotherfile.dummy")); - Assert.assertTrue(applicableFiles.get(2).getNiceFileName(false, "").endsWith("somefile.dummy")); + List applicableFiles = collector.getCollectedFiles(); + assertThat(applicableFiles, hasSize(3)); + assertThat(applicableFiles.get(0).getPathId(), endsWith("anotherfile.dummy")); + assertThat(applicableFiles.get(1).getPathId(), endsWith("somefile.dummy")); + assertThat(applicableFiles.get(2).getPathId(), endsWith("somefile.dummy")); } @Test - public void testGetApplicatbleFilesWithIgnores() throws IOException { - Set languages = new HashSet<>(); - languages.add(new DummyLanguageModule()); + public void testGetApplicableFilesWithIgnores() throws IOException { + FileCollector collector = newCollector(); PMDConfiguration configuration = new PMDConfiguration(); configuration.setInputFilePath("src/test/resources/net/sourceforge/pmd/cli/filelist3.txt"); configuration.setIgnoreFilePath("src/test/resources/net/sourceforge/pmd/cli/ignorelist.txt"); + FileCollectionUtil.collectFiles(configuration, collector); - List applicableFiles = PMD.getApplicableFiles(configuration, languages); - Assert.assertEquals(2, applicableFiles.size()); - Assert.assertTrue(applicableFiles.get(0).getNiceFileName(false, "").endsWith("somefile2.dummy")); - Assert.assertTrue(applicableFiles.get(1).getNiceFileName(false, "").endsWith("somefile4.dummy")); + List applicableFiles = collector.getCollectedFiles(); + assertThat(applicableFiles, hasSize(2)); + assertThat(applicableFiles.get(0).getPathId(), endsWith("somefile2.dummy")); + assertThat(applicableFiles.get(1).getPathId(), endsWith("somefile4.dummy")); } @Test - public void testGetApplicatbleFilesWithDirAndIgnores() throws IOException { - Set languages = new HashSet<>(); - languages.add(new DummyLanguageModule()); + public void testGetApplicableFilesWithDirAndIgnores() throws IOException { PMDConfiguration configuration = new PMDConfiguration(); configuration.setInputPaths("src/test/resources/net/sourceforge/pmd/cli/src"); configuration.setIgnoreFilePath("src/test/resources/net/sourceforge/pmd/cli/ignorelist.txt"); - List applicableFiles = PMD.getApplicableFiles(configuration, languages); - Assert.assertEquals(4, applicableFiles.size()); - Collections.sort(applicableFiles, new Comparator() { - @Override - public int compare(DataSource o1, DataSource o2) { - if (o1 == null && o2 != null) { - return -1; - } else if (o1 != null && o2 == null) { - return 1; - } else { - return o1.getNiceFileName(false, "").compareTo(o2.getNiceFileName(false, "")); - } - } - }); - Assert.assertTrue(applicableFiles.get(0).getNiceFileName(false, "").endsWith("anotherfile.dummy")); - Assert.assertTrue(applicableFiles.get(1).getNiceFileName(false, "").endsWith("somefile.dummy")); - Assert.assertTrue(applicableFiles.get(2).getNiceFileName(false, "").endsWith("somefile2.dummy")); - Assert.assertTrue(applicableFiles.get(3).getNiceFileName(false, "").endsWith("somefile4.dummy")); + FileCollector collector = newCollector(); + FileCollectionUtil.collectFiles(configuration, collector); + + List applicableFiles = collector.getCollectedFiles(); + assertThat(applicableFiles, hasSize(4)); + assertThat(applicableFiles.get(0).getPathId(), endsWith("anotherfile.dummy")); + assertThat(applicableFiles.get(1).getPathId(), endsWith("somefile.dummy")); + assertThat(applicableFiles.get(2).getPathId(), endsWith("somefile2.dummy")); + assertThat(applicableFiles.get(3).getPathId(), endsWith("somefile4.dummy")); } + } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/internal/StageDependencyTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/internal/StageDependencyTest.java deleted file mode 100644 index 7d4d8595af..0000000000 --- a/pmd-core/src/test/java/net/sourceforge/pmd/internal/StageDependencyTest.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.internal; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.junit.Assert; -import org.junit.Test; - -import net.sourceforge.pmd.PMDConfiguration; -import net.sourceforge.pmd.Rule; -import net.sourceforge.pmd.RuleContext; -import net.sourceforge.pmd.RuleSet; -import net.sourceforge.pmd.RuleSets; -import net.sourceforge.pmd.lang.Language; -import net.sourceforge.pmd.lang.LanguageRegistry; -import net.sourceforge.pmd.lang.LanguageVersion; -import net.sourceforge.pmd.lang.ast.AstProcessingStage; -import net.sourceforge.pmd.lang.ast.DummyAstStages; -import net.sourceforge.pmd.lang.ast.DummyNode; -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.ast.Parser; -import net.sourceforge.pmd.lang.ast.Parser.ParserTask; -import net.sourceforge.pmd.lang.ast.RootNode; -import net.sourceforge.pmd.lang.ast.SemanticErrorReporter; -import net.sourceforge.pmd.lang.rule.AbstractRule; - -public class StageDependencyTest { - - private final LanguageVersion version = LanguageRegistry.findLanguageByTerseName("dummy").getVersion("1.0"); - - private DummyNode process(String source, RuleSets ruleSets) { - return process(source, ruleSets, new RulesetStageDependencyHelper(new PMDConfiguration())); - } - - private DummyNode process(String source, RuleSets ruleSets, RulesetStageDependencyHelper helper) { - - // TODO Handle Rules having different parser options. - - Parser parser = version.getLanguageVersionHandler().getParser(); - - ParserTask task = new ParserTask(version, "dummyfile.dummy", source, SemanticErrorReporter.noop()); - - RootNode rootNode = parser.parse(task); - - helper.runLanguageSpecificStages(ruleSets, version, rootNode); - - return (DummyNode) rootNode; - } - - - @Test - public void testSimpleDependency() { - - DummyNode root = process("foo bar", withRules(new PredicateTestRule(DummyAstStages.FOO))); - - Assert.assertTrue(DummyAstStages.FOO.hasProcessed(root)); - Assert.assertFalse(DummyAstStages.BAR.hasProcessed(root)); - } - - @Test - public void testNoDependency() { - - DummyNode root = process("foo bar", withRules(new PredicateTestRule())); - - Assert.assertFalse(DummyAstStages.FOO.hasProcessed(root)); - Assert.assertFalse(DummyAstStages.BAR.hasProcessed(root)); - } - - @Test - public void testDependencyUnion() { - - DummyNode root = - process("foo bar", - withRules( - new PredicateTestRule(DummyAstStages.FOO), - new PredicateTestRule(DummyAstStages.BAR) - ) - ); - - Assert.assertTrue(DummyAstStages.FOO.hasProcessed(root)); - Assert.assertTrue(DummyAstStages.BAR.hasProcessed(root)); - } - - @Test - public void testTransitiveDependency() { - - DummyNode root = process("foo bar", withRules(new PredicateTestRule(DummyAstStages.RUNS_FOO))); - - Assert.assertTrue(DummyAstStages.FOO.hasProcessed(root)); - Assert.assertFalse(DummyAstStages.BAR.hasProcessed(root)); - Assert.assertTrue(DummyAstStages.RUNS_FOO.hasProcessed(root)); - } - - @Test - public void testNoRecomputation() { - - PMDConfiguration configuration = new PMDConfiguration(); - RulesetStageDependencyHelper helper = new RulesetStageDependencyHelper(configuration); - - RuleSets ruleSets = withRules(new PredicateTestRule(DummyAstStages.RUNS_FOO)); - - List> stages1 = helper.testOnlyGetDependencies(ruleSets, version); - - process("foo bar", ruleSets); - - List> stages2 = helper.testOnlyGetDependencies(ruleSets, version); - - Assert.assertSame(stages1, stages2); - } - - @Test - public void testDependencyOrdering() { - - PMDConfiguration configuration = new PMDConfiguration(); - RulesetStageDependencyHelper helper = new RulesetStageDependencyHelper(configuration); - - RuleSets ruleSets = withRules( - new PredicateTestRule(DummyAstStages.FOO), - new PredicateTestRule(DummyAstStages.BAR) - ); - - RuleSets ruleSets2 = withRules( - new PredicateTestRule(DummyAstStages.BAR), - new PredicateTestRule(DummyAstStages.FOO) - ); - - List> stages1 = helper.testOnlyGetDependencies(ruleSets, version); - List> stages2 = helper.testOnlyGetDependencies(ruleSets2, version); - - Assert.assertNotSame(stages1, stages2); - Assert.assertEquals(stages1, stages2); - } - - - private static RuleSets withRules(Rule r, Rule... rs) { - List rsets = new ArrayList<>(); - rsets.add(RuleSet.forSingleRule(r)); - for (Rule rule : rs) { - rsets.add(RuleSet.forSingleRule(rule)); - } - - return new RuleSets(rsets); - } - - private static class PredicateTestRule extends AbstractRule { - - private final List dependencies; - - PredicateTestRule(DummyAstStages... dependencies) { - this.dependencies = Arrays.asList(dependencies); - } - - @Override - public Language getLanguage() { - return LanguageRegistry.findLanguageByTerseName("dummy"); - } - - @Override - public boolean dependsOn(AstProcessingStage stage) { - return dependencies.contains(stage); - } - - @Override - public void apply(Node target, RuleContext ctx) { - - } - - @Override - public int hashCode() { - return 0; - } - - @Override - public boolean equals(Object o) { - return this == o; - } - } - - -} diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/DummyLanguageModule.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/DummyLanguageModule.java index 55ff4de166..3b9d4f6d72 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/DummyLanguageModule.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/DummyLanguageModule.java @@ -8,7 +8,6 @@ import org.checkerframework.checker.nullness.qual.NonNull; import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.RuleViolation; -import net.sourceforge.pmd.lang.ast.DummyAstStages; import net.sourceforge.pmd.lang.ast.DummyRoot; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.ast.Parser; @@ -38,9 +37,6 @@ public class DummyLanguageModule extends BaseLanguageModule { } public static class Handler extends AbstractPmdLanguageVersionHandler { - public Handler() { - super(DummyAstStages.class); - } @Override public RuleViolationFactory getRuleViolationFactory() { diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/DummyAstStages.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/DummyAstStages.java deleted file mode 100644 index 1644db9433..0000000000 --- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/DummyAstStages.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.ast; - -import java.util.Collections; -import java.util.List; - -import net.sourceforge.pmd.lang.Language; -import net.sourceforge.pmd.lang.LanguageRegistry; - -/** - * @author ClΓ©ment Fournier - */ -public enum DummyAstStages implements AstProcessingStage { - FOO, - BAR, - RUNS_FOO { - @Override - public List getDependencies() { - return Collections.singletonList(FOO); - } - }; - - - @Override - public Language getLanguage() { - return LanguageRegistry.findLanguageByTerseName("dummy"); - } - - @Override - public List getDependencies() { - return Collections.emptyList(); - } - - public boolean hasProcessed(DummyNode node) { - return node.getUserData().containsKey(name() + "_STAGE"); - } - - @Override - public String getDisplayName() { - return name(); - } - - @Override - public void processAST(RootNode rootNode, AstAnalysisContext configuration) { - ((DummyNode) rootNode).getUserData().put(name() + "_STAGE", "done"); - } - -} diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/document/FileCollectorTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/document/FileCollectorTest.java new file mode 100644 index 0000000000..191c004db0 --- /dev/null +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/document/FileCollectorTest.java @@ -0,0 +1,144 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.document; + +import static net.sourceforge.pmd.util.CollectionUtil.listOf; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import net.sourceforge.pmd.lang.Language; +import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.lang.LanguageVersion; +import net.sourceforge.pmd.lang.LanguageVersionDiscoverer; + +/** + * @author ClΓ©ment Fournier + */ +public class FileCollectorTest { + + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + @Test + public void testAddFile() throws IOException { + Path root = tempFolder.getRoot().toPath(); + Path foo = newFile(root, "foo.dummy"); + Path bar = newFile(root, "bar.unknown"); + + FileCollector collector = newCollector(); + + assertTrue("should be dummy language", collector.addFile(foo)); + assertFalse("should be unknown language", collector.addFile(bar)); + + assertCollected(collector, listOf("foo.dummy")); + } + + @Test + public void testAddFileForceLanguage() throws IOException { + Path root = tempFolder.getRoot().toPath(); + Path bar = newFile(root, "bar.unknown"); + + Language dummy = LanguageRegistry.findLanguageByTerseName("dummy"); + + FileCollector collector = newCollector(dummy.getDefaultVersion()); + + assertTrue("should be unknown language", collector.addFile(bar, dummy)); + assertCollected(collector, listOf("bar.unknown")); + assertNoErrors(collector); + } + + @Test + public void testAddFileNotExists() { + Path root = tempFolder.getRoot().toPath(); + + FileCollector collector = newCollector(); + + assertFalse(collector.addFile(root.resolve("does_not_exist.dummy"))); + assertEquals(1, collector.getReporter().numErrors()); + } + + @Test + public void testAddFileNotAFile() throws IOException { + Path root = tempFolder.getRoot().toPath(); + Path dir = root.resolve("src"); + Files.createDirectories(dir); + + FileCollector collector = newCollector(); + assertFalse(collector.addFile(dir)); + assertEquals(1, collector.getReporter().numErrors()); + } + + @Test + public void testAddDirectory() throws IOException { + Path root = tempFolder.getRoot().toPath(); + newFile(root, "src/foo.dummy"); + newFile(root, "src/bar.unknown"); + newFile(root, "src/x/bar.dummy"); + + FileCollector collector = newCollector(); + + collector.addDirectory(root.resolve("src")); + + assertCollected(collector, listOf("src/foo.dummy", "src/x/bar.dummy")); + } + + @Test + public void testRelativize() throws IOException { + String displayName = FileCollector.getDisplayName(Paths.get("a", "b", "c"), listOf(Paths.get("a").toString())); + assertEquals(displayName, Paths.get("b", "c").toString()); + } + + private Path newFile(Path root, String path) throws IOException { + Path resolved = root.resolve(path); + Files.createDirectories(resolved.getParent()); + Files.createFile(resolved); + return resolved; + } + + private void assertCollected(FileCollector collector, List relPaths) { + Map actual = new LinkedHashMap<>(); + for (TextFile file : collector.getCollectedFiles()) { + actual.put(file.getDisplayName(), file.getLanguageVersion().getTerseName()); + } + + relPaths = new ArrayList<>(relPaths); + for (int i = 0; i < relPaths.size(); i++) { + // normalize, we want display names to be platform-specific + relPaths.set(i, relPaths.get(i).replace('/', File.separatorChar)); + } + + assertEquals(relPaths, new ArrayList<>(actual.keySet())); + } + + private void assertNoErrors(FileCollector collector) { + assertEquals("No errors expected", 0, collector.getReporter().numErrors()); + } + + private FileCollector newCollector() { + return newCollector(null); + } + + private FileCollector newCollector(LanguageVersion forcedVersion) { + LanguageVersionDiscoverer discoverer = new LanguageVersionDiscoverer(forcedVersion); + FileCollector collector = FileCollector.newCollector(discoverer, new TestMessageReporter()); + collector.relativizeWith(tempFolder.getRoot().getAbsolutePath()); + return collector; + } +} diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/document/TestMessageReporter.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/document/TestMessageReporter.java new file mode 100644 index 0000000000..8689530e30 --- /dev/null +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/document/TestMessageReporter.java @@ -0,0 +1,23 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.document; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.sourceforge.pmd.util.log.internal.SimpleMessageReporter; + +/** + * @author ClΓ©ment Fournier + */ +public class TestMessageReporter extends SimpleMessageReporter { + + private static final Logger LOG = LoggerFactory.getLogger(TestMessageReporter.class.getName()); + + public TestMessageReporter() { + super(LOG); + setLevel(null); + } +} diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/processor/GlobalListenerTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/processor/GlobalListenerTest.java index 6d749e5775..d10f676ca5 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/processor/GlobalListenerTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/processor/GlobalListenerTest.java @@ -4,7 +4,6 @@ package net.sourceforge.pmd.processor; -import static net.sourceforge.pmd.util.CollectionUtil.listOf; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; @@ -13,15 +12,13 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import java.util.List; - import org.checkerframework.checker.nullness.qual.NonNull; import org.junit.Test; import org.mockito.Mockito; import net.sourceforge.pmd.FooRule; -import net.sourceforge.pmd.PMD; import net.sourceforge.pmd.PMDConfiguration; +import net.sourceforge.pmd.PmdAnalysis; import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.RuleSet; @@ -31,20 +28,11 @@ import net.sourceforge.pmd.lang.ast.FileAnalysisException; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.reporting.GlobalAnalysisListener; import net.sourceforge.pmd.reporting.GlobalAnalysisListener.ViolationCounterListener; -import net.sourceforge.pmd.util.datasource.DataSource; public class GlobalListenerTest { static final int NUM_DATA_SOURCES = 3; - static List mockDataSources() { - return listOf( - DataSource.forString("abc", "fname1.dummy"), - DataSource.forString("abcd", "fname2.dummy"), - DataSource.forString("abcd", "fname21.dummy") - ); - } - @Test public void testViolationCounter() throws Exception { @@ -89,7 +77,7 @@ public class GlobalListenerTest { runPmd(config, GlobalAnalysisListener.noop(), rule); verify(mockCache).checkValidity(any(), any()); - verify(mockCache).persist(); + verify(mockCache, times(1)).persist(); verify(mockCache, times(NUM_DATA_SOURCES)).isUpToDate(any()); } @@ -105,7 +93,7 @@ public class GlobalListenerTest { // cache methods are called regardless verify(mockCache).checkValidity(any(), any()); - verify(mockCache).persist(); + verify(mockCache, times(1)).persist(); verify(mockCache, times(NUM_DATA_SOURCES)).isUpToDate(any()); } @@ -127,7 +115,7 @@ public class GlobalListenerTest { // cache methods are called regardless verify(mockCache).checkValidity(any(), any()); - verify(mockCache).persist(); + verify(mockCache, times(1)).persist(); verify(mockCache, times(1)).isUpToDate(any()); } @@ -140,16 +128,14 @@ public class GlobalListenerTest { return config; } - private void runPmd(PMDConfiguration config, GlobalAnalysisListener listener, Rule rule) throws Exception { - try { - PMD.processFiles( - config, - listOf(RuleSet.forSingleRule(rule)), - mockDataSources(), - listener - ); - } finally { - listener.close(); + private void runPmd(PMDConfiguration config, GlobalAnalysisListener listener, Rule rule) { + try (PmdAnalysis pmd = PmdAnalysis.create(config)) { + pmd.addRuleSet(RuleSet.forSingleRule(rule)); + pmd.files().addSourceFile("fname1.dummy", "abc"); + pmd.files().addSourceFile("fname2.dummy", "abcd"); + pmd.files().addSourceFile("fname21.dummy", "abcd"); + pmd.addListener(listener); + pmd.performAnalysis(); } } @@ -159,7 +145,7 @@ public class GlobalListenerTest { @Override public void apply(Node node, RuleContext ctx) { if (node.getAstInfo().getFileName().contains("1")) { - addViolation(ctx, node); + ctx.addViolation(node); } } } diff --git a/pmd-core/src/test/resources/sample-source/dummy/foo.dummy b/pmd-core/src/test/resources/sample-source/dummy/foo.dummy new file mode 100644 index 0000000000..4b1d189a0f --- /dev/null +++ b/pmd-core/src/test/resources/sample-source/dummy/foo.dummy @@ -0,0 +1 @@ +A dummy file. diff --git a/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java b/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java index 890185cd17..5ad517cf14 100644 --- a/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java +++ b/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java @@ -108,7 +108,7 @@ public class BinaryDistributionIT extends AbstractBinaryDistributionTest { result = PMDExecutor.runPMD(tempDir, "-d", srcDir, "-R", "src/test/resources/rulesets/sample-ruleset.xml", "-r", folder.newFile().toString(), "--debug"); result.assertExecutionResult(4); - result.assertErrorOutputContains("[main] DEBUG net.sourceforge.pmd.PMD - Log level is at DEBUG"); + result.assertErrorOutputContains("[main] INFO net.sourceforge.pmd.PMD - Log level is at TRACE"); } @Test diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AstDisambiguationPass.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AstDisambiguationPass.java index d698822019..6a4fe24939 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AstDisambiguationPass.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AstDisambiguationPass.java @@ -6,18 +6,17 @@ package net.sourceforge.pmd.lang.java.ast; import static net.sourceforge.pmd.lang.java.symbols.table.internal.JavaSemanticErrors.CANNOT_RESOLVE_AMBIGUOUS_NAME; -import static net.sourceforge.pmd.lang.java.symbols.table.internal.JavaSemanticErrors.CANNOT_RESOLVE_SYMBOL; import java.util.Iterator; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import net.sourceforge.pmd.benchmark.TimeTracker; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.ast.NodeStream; import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken; import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.ASTNamedReferenceExpr; -import net.sourceforge.pmd.lang.java.internal.JavaAstProcessor; import net.sourceforge.pmd.lang.java.symbols.JClassSymbol; import net.sourceforge.pmd.lang.java.symbols.JTypeDeclSymbol; import net.sourceforge.pmd.lang.java.symbols.table.JSymbolTable; @@ -57,7 +56,7 @@ final class AstDisambiguationPass { */ public static void disambigWithCtx(NodeStream nodes, ReferenceCtx ctx) { assert ctx != null : "Null context"; - JavaAstProcessor.bench("AST disambiguation", () -> nodes.forEach(it -> it.acceptVisitor(DisambigVisitor.INSTANCE, ctx))); + TimeTracker.bench("AST disambiguation", () -> nodes.forEach(it -> it.acceptVisitor(DisambigVisitor.INSTANCE, ctx))); } @@ -196,7 +195,7 @@ final class AstDisambiguationPass { final JTypeMirror resolved = ctx.resolveSingleTypeName(type.getSymbolTable(), type.getSimpleName(), type); JTypeDeclSymbol sym; if (resolved == null) { - ctx.getLogger().warning(type, CANNOT_RESOLVE_SYMBOL, type.getSimpleName()); + ctx.reportCannotResolveSymbol(type, type.getSimpleName()); sym = setArity(type, ctx, type.getSimpleName()); } else { sym = resolved.getSymbol(); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParser.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParser.java index c4c84e7b81..3d70e291c3 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParser.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParser.java @@ -11,6 +11,7 @@ import net.sourceforge.pmd.lang.ast.impl.javacc.JavaCharStream; import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccTokenDocument; import net.sourceforge.pmd.lang.ast.impl.javacc.JjtreeParserAdapter; import net.sourceforge.pmd.lang.java.ast.internal.LanguageLevelChecker; +import net.sourceforge.pmd.lang.java.internal.JavaAstProcessor; /** * Adapter for the JavaParser, using the specified grammar version. @@ -21,9 +22,11 @@ import net.sourceforge.pmd.lang.java.ast.internal.LanguageLevelChecker; public class JavaParser extends JjtreeParserAdapter { private final LanguageLevelChecker checker; + private final boolean postProcess; - public JavaParser(LanguageLevelChecker checker) { + public JavaParser(LanguageLevelChecker checker, boolean postProcess) { this.checker = checker; + this.postProcess = postProcess; } @@ -44,9 +47,17 @@ public class JavaParser extends JjtreeParserAdapter { parser.setJdkVersion(checker.getJdkVersion()); parser.setPreview(checker.isPreviewEnabled()); - ASTCompilationUnit acu = parser.CompilationUnit(); - acu.setAstInfo(new AstInfo<>(task, acu, parser.getSuppressMap())); - checker.check(acu); - return acu; + ASTCompilationUnit root = parser.CompilationUnit(); + root.setAstInfo(new AstInfo<>(task, root, parser.getSuppressMap())); + checker.check(root); + + if (postProcess) { + JavaAstProcessor processor = JavaAstProcessor.create(task.getAuxclasspathClassLoader(), + task.getLanguageVersion(), + task.getReporter()); + processor.process(root); + } + + return root; } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/internal/JavaAstProcessor.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/internal/JavaAstProcessor.java index b5c95310bc..335d3c8a53 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/internal/JavaAstProcessor.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/internal/JavaAstProcessor.java @@ -4,23 +4,23 @@ package net.sourceforge.pmd.lang.java.internal; +import static net.sourceforge.pmd.lang.java.symbols.table.internal.JavaSemanticErrors.CANNOT_RESOLVE_SYMBOL; + import java.util.IdentityHashMap; import java.util.Locale; import java.util.Map; -import java.util.function.Supplier; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.slf4j.event.Level; import net.sourceforge.pmd.benchmark.TimeTracker; -import net.sourceforge.pmd.benchmark.TimedOperation; -import net.sourceforge.pmd.benchmark.TimedOperationCategory; import net.sourceforge.pmd.lang.LanguageVersion; import net.sourceforge.pmd.lang.ast.NodeStream; import net.sourceforge.pmd.lang.ast.SemanticErrorReporter; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.InternalApiBridge; +import net.sourceforge.pmd.lang.java.ast.JavaNode; import net.sourceforge.pmd.lang.java.symbols.JClassSymbol; import net.sourceforge.pmd.lang.java.symbols.JTypeDeclSymbol; import net.sourceforge.pmd.lang.java.symbols.SymbolResolver; @@ -43,8 +43,6 @@ import net.sourceforge.pmd.lang.java.types.internal.infer.TypeInferenceLogger.Ve */ public final class JavaAstProcessor { - private static final Logger DEFAULT_LOG = LoggerFactory.getLogger(JavaAstProcessor.class); - private static final Map TYPE_SYSTEMS = new IdentityHashMap<>(); private static final Level INFERENCE_LOG_LEVEL; @@ -100,10 +98,31 @@ public final class JavaAstProcessor { } - public JClassSymbol findSymbolCannotFail(String name) { - JClassSymbol found = getSymResolver().resolveClassFromCanonicalName(name); - return found == null ? makeUnresolvedReference(name, 0) - : found; + /** + * Find a symbol from the auxclasspath. If not found, will create + * an unresolved symbol. + */ + public @NonNull JClassSymbol findSymbolCannotFail(String name) { + return findSymbolCannotFail(null, name); + } + + /** + * Find a symbol from the auxclasspath. If not found, will create + * an unresolved symbol, and may report the failure if the location is non-null. + */ + public @NonNull JClassSymbol findSymbolCannotFail(@Nullable JavaNode location, String canoName) { + JClassSymbol found = getSymResolver().resolveClassFromCanonicalName(canoName); + if (found == null) { + if (location != null) { + reportCannotResolveSymbol(location, canoName); + } + return makeUnresolvedReference(canoName, 0); + } + return found; + } + + public void reportCannotResolveSymbol(@NonNull JavaNode location, String canoName) { + getLogger().warning(location, CANNOT_RESOLVE_SYMBOL, canoName); } public JClassSymbol makeUnresolvedReference(String canonicalName, int typeArity) { @@ -138,7 +157,7 @@ public final class JavaAstProcessor { */ public void process(ASTCompilationUnit acu) { - SymbolResolver knownSyms = bench("Symbol resolution", () -> SymbolResolutionPass.traverse(this, acu)); + SymbolResolver knownSyms = TimeTracker.bench("1. Symbol resolution", () -> SymbolResolutionPass.traverse(this, acu)); // Now symbols are on the relevant nodes this.symResolver = SymbolResolver.layer(knownSyms, this.symResolver); @@ -147,21 +166,17 @@ public final class JavaAstProcessor { // as scopes depend on type resolution in some cases. InternalApiBridge.initTypeResolver(acu, this, typeInferenceLogger); - bench("2. Symbol table resolution", () -> SymbolTableResolver.traverse(this, acu)); - bench("3. AST disambiguation", () -> InternalApiBridge.disambigWithCtx(NodeStream.of(acu), ReferenceCtx.root(this, acu))); - bench("4. Comment assignment", () -> InternalApiBridge.assignComments(acu)); - bench("5. Usage resolution", () -> InternalApiBridge.usageResolution(this, acu)); - bench("6. Override resolution", () -> InternalApiBridge.overrideResolution(this, acu)); + TimeTracker.bench("2. Symbol table resolution", () -> SymbolTableResolver.traverse(this, acu)); + TimeTracker.bench("3. AST disambiguation", () -> InternalApiBridge.disambigWithCtx(NodeStream.of(acu), ReferenceCtx.root(this, acu))); + TimeTracker.bench("4. Comment assignment", () -> InternalApiBridge.assignComments(acu)); + TimeTracker.bench("5. Usage resolution", () -> InternalApiBridge.usageResolution(this, acu)); + TimeTracker.bench("6. Override resolution", () -> InternalApiBridge.overrideResolution(this, acu)); } public TypeSystem getTypeSystem() { return typeSystem; } - public static SemanticErrorReporter defaultLogger() { - return SemanticErrorReporter.reportToLogger(DEFAULT_LOG); - } - public static JavaAstProcessor create(SymbolResolver symResolver, TypeSystem typeSystem, LanguageVersion languageVersion, @@ -191,6 +206,13 @@ public final class JavaAstProcessor { ); } + + public static JavaAstProcessor create(ClassLoader classLoader, + LanguageVersion languageVersion, + SemanticErrorReporter logger) { + return create(classLoader, languageVersion, logger, defaultTypeInfLogger()); + } + public static JavaAstProcessor create(TypeSystem typeSystem, LanguageVersion languageVersion, SemanticErrorReporter semanticLogger, @@ -204,15 +226,4 @@ public final class JavaAstProcessor { ); } - public static void bench(String label, Runnable runnable) { - try (TimedOperation ignored = TimeTracker.startOperation(TimedOperationCategory.LANGUAGE_SPECIFIC_PROCESSING, label)) { - runnable.run(); - } - } - - public static T bench(String label, Supplier runnable) { - try (TimedOperation ignored = TimeTracker.startOperation(TimedOperationCategory.LANGUAGE_SPECIFIC_PROCESSING, label)) { - return runnable.get(); - } - } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/internal/JavaLanguageHandler.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/internal/JavaLanguageHandler.java index c90973e476..8052595681 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/internal/JavaLanguageHandler.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/internal/JavaLanguageHandler.java @@ -50,7 +50,6 @@ public class JavaLanguageHandler extends AbstractPmdLanguageVersionHandler { } public JavaLanguageHandler(int jdkVersion, boolean preview) { - super(JavaProcessingStage.class); this.levelChecker = new LanguageLevelChecker<>(jdkVersion, preview, ReportingStrategy.reporterThatThrows()); } @@ -60,7 +59,11 @@ public class JavaLanguageHandler extends AbstractPmdLanguageVersionHandler { @Override public Parser getParser() { - return new JavaParser(levelChecker); + return new JavaParser(levelChecker, true); + } + + public JavaParser getParserWithoutProcessing() { + return new JavaParser(levelChecker, false); } @Override diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/internal/JavaProcessingStage.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/internal/JavaProcessingStage.java deleted file mode 100644 index 4c03dff3c0..0000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/internal/JavaProcessingStage.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.internal; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import net.sourceforge.pmd.annotation.Experimental; -import net.sourceforge.pmd.lang.Language; -import net.sourceforge.pmd.lang.LanguageRegistry; -import net.sourceforge.pmd.lang.ast.AstAnalysisContext; -import net.sourceforge.pmd.lang.ast.AstProcessingStage; -import net.sourceforge.pmd.lang.ast.RootNode; -import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; -import net.sourceforge.pmd.lang.java.ast.JavaParser; -import net.sourceforge.pmd.lang.java.ast.internal.LanguageLevelChecker; -import net.sourceforge.pmd.lang.java.symboltable.SymbolFacade; - - -/** - * Java processing stages. - * - * @author ClΓ©ment Fournier - * @since 7.0.0 - */ -@Experimental -public enum JavaProcessingStage implements AstProcessingStage { - - /** - * This acts as a merged stage, non-optional. Ideally this would be encapsulated - * in the {@link JavaParser}, like the {@link LanguageLevelChecker}. - */ - JAVA_PROCESSING("Java processing") { - @Override - public void processAST(RootNode rootNode, AstAnalysisContext configuration) { - JavaAstProcessor.create(configuration.getTypeResolutionClassLoader(), configuration.getLanguageVersion(), JavaAstProcessor.defaultLogger(), JavaAstProcessor.defaultTypeInfLogger()) - .process((ASTCompilationUnit) rootNode); - } - }, - - /** - * Symbol table analysis. - */ - SYMBOL_RESOLUTION("Symbol table") { - @Override - public void processAST(RootNode rootNode, AstAnalysisContext configuration) { - // kept for compatibility with existing tests - new SymbolFacade().initializeWith(configuration.getTypeResolutionClassLoader(), (ASTCompilationUnit) rootNode); - } - }; - - private final String displayName; - private final List dependencies; - - JavaProcessingStage(String displayName, JavaProcessingStage... dependencies) { - this.displayName = displayName; - this.dependencies = Collections.unmodifiableList(Arrays.asList(dependencies)); - } - - @Override - public List getDependencies() { - return dependencies; - } - - - @Override - public String getDisplayName() { - return displayName; - } - - - @Override - public final Language getLanguage() { - return LanguageRegistry.findLanguageByTerseName("java"); - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractJavaRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractJavaRule.java index 73efb9c94e..0da448c81b 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractJavaRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractJavaRule.java @@ -10,13 +10,11 @@ import org.checkerframework.checker.nullness.qual.Nullable; import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.lang.LanguageRegistry; -import net.sourceforge.pmd.lang.ast.AstProcessingStage; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.JavaLanguageModule; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration; import net.sourceforge.pmd.lang.java.ast.JavaParserVisitor; -import net.sourceforge.pmd.lang.java.internal.JavaProcessingStage; import net.sourceforge.pmd.lang.rule.AbstractRule; @@ -52,12 +50,4 @@ public abstract class AbstractJavaRule extends AbstractRule implements JavaParse return false; } - @Override - public boolean dependsOn(AstProcessingStage stage) { - if (!(stage instanceof JavaProcessingStage)) { - throw new IllegalArgumentException("Processing stage wasn't a Java one: " + stage); - } - - return true; - } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/table/internal/ReferenceCtx.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/table/internal/ReferenceCtx.java index 063f1613c8..800b67c249 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/table/internal/ReferenceCtx.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/table/internal/ReferenceCtx.java @@ -46,6 +46,10 @@ public final class ReferenceCtx { this.enclosingClass = enclosingClass; } + public void reportCannotResolveSymbol(JavaNode location, String simpleName) { + processor.reportCannotResolveSymbol(location, simpleName); + } + public static ReferenceCtx root(JavaAstProcessor processor, ASTCompilationUnit root) { return new ReferenceCtx(processor, root.getPackageName(), null); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/table/internal/SymTableFactory.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/table/internal/SymTableFactory.java index 069e659158..46ffb2b987 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/table/internal/SymTableFactory.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/table/internal/SymTableFactory.java @@ -257,7 +257,7 @@ final class SymTableFactory { if (!anImport.isStatic()) { // Single-Type-Import Declaration - JClassSymbol type = processor.findSymbolCannotFail(anImport.getImportedName()); + JClassSymbol type = processor.findSymbolCannotFail(anImport, anImport.getImportedName()); importedTypes.append(type.getTypeSystem().typeOf(type, false)); } } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/ExcludeLinesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/ExcludeLinesTest.java index 4de7893d34..4e5ef18906 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/ExcludeLinesTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/ExcludeLinesTest.java @@ -9,11 +9,11 @@ import static net.sourceforge.pmd.lang.ast.test.TestUtilsKt.assertSuppressed; import org.junit.Test; +import net.sourceforge.pmd.lang.java.BaseParserTest; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; -import net.sourceforge.pmd.lang.java.symboltable.BaseNonParserTest; -public class ExcludeLinesTest extends BaseNonParserTest { +public class ExcludeLinesTest extends BaseParserTest { @Test public void testAcceptance() { diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/ReportTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/ReportTest.java index 24a56156da..baa95be954 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/ReportTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/ReportTest.java @@ -17,7 +17,7 @@ import net.sourceforge.pmd.lang.java.JavaParsingHelper; public class ReportTest { - private final JavaParsingHelper java = JavaParsingHelper.WITH_PROCESSING; + private final JavaParsingHelper java = JavaParsingHelper.DEFAULT; @Test public void testBasic() { diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/cli/CLITest.java b/pmd-java/src/test/java/net/sourceforge/pmd/cli/CLITest.java index 892f1b82ae..016f208fd6 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/cli/CLITest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/cli/CLITest.java @@ -4,20 +4,18 @@ package net.sourceforge.pmd.cli; -import static org.junit.Assert.assertTrue; - -import java.io.File; -import java.util.regex.Pattern; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; import org.junit.AfterClass; -import org.junit.Assert; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.contrib.java.lang.system.RestoreSystemProperties; import org.junit.rules.TestRule; +import net.sourceforge.pmd.PMD.StatusCode; import net.sourceforge.pmd.internal.Slf4jSimpleConfiguration; -import net.sourceforge.pmd.util.FileUtil; /** * @author Romain Pelisse <belaran@gmail.com> @@ -35,64 +33,65 @@ public class CLITest extends BaseCLITest { Slf4jSimpleConfiguration.reconfigureDefaultLogLevel(null); } + @Before + public void setupLogging() { + Slf4jSimpleConfiguration.reconfigureDefaultLogLevel(null); + } + + @Test public void minimalArgs() { - String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/bestpractices.xml,category/java/design.xml", }; - runTest(args, "minimalArgs"); + runTest("-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/bestpractices.xml,category/java/design.xml"); } @Test public void minimumPriority() { String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/design.xml", "-min", "1", }; - runTest(args, "minimumPriority"); + runTest(args); } @Test public void usingDebug() { - String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/design.xml", "-debug", }; - runTest(args, "minimalArgsWithDebug"); + runTest("-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/design.xml", "-debug"); } @Test public void usingDebugLongOption() { - String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/design.xml", "--debug", }; - runTest(args, "minimalArgsWithDebug"); + runTest("-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/design.xml", "--debug"); } @Test public void changeJavaVersion() { String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/design.xml", "-version", "1.5", "-language", - "java", "-debug", }; - String resultFilename = runTest(args, "chgJavaVersion"); - assertTrue("Invalid Java version", - FileUtil.findPatternInFile(new File(resultFilename), "Using Java version: Java 1.5")); + "java", "--debug", }; + String log = runTest(args); + assertThat(log, containsPattern("Adding file .*\\.java \\(lang: java 1\\.5\\)")); } @Test public void exitStatusNoViolations() { - String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/design.xml", }; - runTest(args, "exitStatusNoViolations"); + runTest("-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/design.xml"); } @Test public void exitStatusWithViolations() { String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/errorprone.xml", }; - String resultFilename = runTest(args, "exitStatusWithViolations", 4); - assertTrue(FileUtil.findPatternInFile(new File(resultFilename), "Avoid empty if")); + String log = runTest(StatusCode.VIOLATIONS_FOUND, args); + assertThat(log, containsString("Avoid empty if")); } @Test public void exitStatusWithViolationsAndWithoutFailOnViolations() { String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/errorprone.xml", "-failOnViolation", "false", }; - String resultFilename = runTest(args, "exitStatusWithViolationsAndWithoutFailOnViolations", 0); - assertTrue(FileUtil.findPatternInFile(new File(resultFilename), "Avoid empty if")); + String log = runTest(StatusCode.OK, args); + assertThat(log, containsString("Avoid empty if")); } @Test public void exitStatusWithViolationsAndWithoutFailOnViolationsLongOption() { String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/errorprone.xml", "--fail-on-violation", "false", }; - String resultFilename = runTest(args, "exitStatusWithViolationsAndWithoutFailOnViolations", 0); - assertTrue(FileUtil.findPatternInFile(new File(resultFilename), "Avoid empty if")); + String log = runTest(StatusCode.OK, args); + assertThat(log, containsString("Avoid empty if")); } /** @@ -101,12 +100,9 @@ public class CLITest extends BaseCLITest { @Test public void testWrongRuleset() { String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/designn.xml", }; - String filename = TEST_OUPUT_DIRECTORY + "testWrongRuleset.txt"; - createTestOutputFile(filename); - runPMDWith(args); - Assert.assertEquals(1, getStatusCode()); - assertTrue(FileUtil.findPatternInFile(new File(filename), - "Can't find resource 'category/java/designn.xml' for rule 'null'." + " Make sure the resource is a valid file")); + String log = runTest(StatusCode.ERROR, args); + assertThat(log, containsString("Can't find resource 'category/java/designn.xml' for rule 'null'." + + " Make sure the resource is a valid file")); } /** @@ -115,12 +111,9 @@ public class CLITest extends BaseCLITest { @Test public void testWrongRulesetWithRulename() { String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/designn.xml/UseCollectionIsEmpty", }; - String filename = TEST_OUPUT_DIRECTORY + "testWrongRuleset.txt"; - createTestOutputFile(filename); - runPMDWith(args); - Assert.assertEquals(1, getStatusCode()); - assertTrue(FileUtil.findPatternInFile(new File(filename), - "Can't find resource 'category/java/designn.xml' for rule " + "'UseCollectionIsEmpty'.")); + String log = runTest(StatusCode.ERROR, args); + assertThat(log, containsString("Can't find resource 'category/java/designn.xml' for rule " + + "'UseCollectionIsEmpty'.")); } /** @@ -129,11 +122,8 @@ public class CLITest extends BaseCLITest { @Test public void testWrongRulename() { String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/design.xml/ThisRuleDoesNotExist", }; - String filename = TEST_OUPUT_DIRECTORY + "testWrongRuleset.txt"; - createTestOutputFile(filename); - runPMDWith(args); - Assert.assertEquals(1, getStatusCode()); - assertTrue(FileUtil.findPatternInFile(new File(filename), Pattern - .quote("No rules found. Maybe you misspelled a rule name?" + " (category/java/design.xml/ThisRuleDoesNotExist)"))); + String log = runTest(StatusCode.OK, args); + assertThat(log, containsString("No rules found. Maybe you misspelled a rule name?" + + " (category/java/design.xml/ThisRuleDoesNotExist)")); } } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/BaseNonParserTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/BaseParserTest.java similarity index 66% rename from pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/BaseNonParserTest.java rename to pmd-java/src/test/java/net/sourceforge/pmd/lang/java/BaseParserTest.java index 67ee741d4a..a42dc34154 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/BaseNonParserTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/BaseParserTest.java @@ -1,13 +1,9 @@ -/** +/* * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.symboltable; +package net.sourceforge.pmd.lang.java; -import java.util.List; - -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.JavaParsingHelper; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.ASTExpression; @@ -15,20 +11,18 @@ import net.sourceforge.pmd.lang.java.ast.ASTExpression; * Base class for tests that usually need processing stages to run when * parsing code. */ -public abstract class BaseNonParserTest { +public abstract class BaseParserTest { - protected final JavaParsingHelper java = JavaParsingHelper.WITH_PROCESSING.withResourceContext(getClass()); + protected final JavaParsingHelper java = JavaParsingHelper.DEFAULT.withResourceContext(getClass()); protected final JavaParsingHelper java5 = java.withDefaultVersion("1.5"); + protected final JavaParsingHelper java8 = java.withDefaultVersion("1.8"); + protected final JavaParsingHelper java9 = java.withDefaultVersion("9"); protected ASTCompilationUnit parseCode(final String code) { return java.parse(code); } - protected List getOrderedNodes(Class target, String code) { - return JavaParsingHelper.WITH_PROCESSING.getNodes(target, code); - } - /** * Parse and return an expression. Some variables are predeclared. */ diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/JavaParsingHelper.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/JavaParsingHelper.java index e7b507aeff..3e9a884637 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/JavaParsingHelper.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/JavaParsingHelper.java @@ -4,6 +4,8 @@ package net.sourceforge.pmd.lang.java; +import static net.sourceforge.pmd.lang.ast.Parser.ParserTask; + import java.io.PrintStream; import java.text.MessageFormat; import java.util.ArrayList; @@ -13,19 +15,17 @@ import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.checkerframework.checker.nullness.qual.NonNull; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import net.sourceforge.pmd.internal.Slf4jSimpleConfiguration; -import net.sourceforge.pmd.lang.LanguageVersion; -import net.sourceforge.pmd.lang.LanguageVersionHandler; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.ast.SemanticErrorReporter; import net.sourceforge.pmd.lang.ast.SemanticException; import net.sourceforge.pmd.lang.ast.test.BaseParsingHelper; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; +import net.sourceforge.pmd.lang.java.ast.JavaParser; import net.sourceforge.pmd.lang.java.internal.JavaAstProcessor; +import net.sourceforge.pmd.lang.java.internal.JavaLanguageHandler; import net.sourceforge.pmd.lang.java.types.TypeSystem; import net.sourceforge.pmd.lang.java.types.internal.infer.TypeInferenceLogger; import net.sourceforge.pmd.lang.java.types.internal.infer.TypeInferenceLogger.SimpleLogger; @@ -42,11 +42,13 @@ public class JavaParsingHelper extends BaseParsingHelper ops = java.getNodes(ASTFormalParameter.class, TEST1, "1.5"); - for (ASTFormalParameter b : ops) { - ASTVariableDeclaratorId variableDeclId = b.getFirstDescendantOfType(ASTVariableDeclaratorId.class); - if (!"x".equals(variableDeclId.getImage())) { - assertTrue(b.isVarargs()); - nrOfVarArgs++; - } else { - assertFalse(b.isVarargs()); - nrOfNoVarArgs++; - } - } - - // Ensure that both possibilities are tested - assertEquals(1, nrOfVarArgs); - assertEquals(1, nrOfNoVarArgs); - } - - private static final String TEST1 = "class Foo {\n void bar(int x, int... others) {}\n}"; -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTImportDeclarationTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTImportDeclarationTest.java index 7547f00363..070170b319 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTImportDeclarationTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTImportDeclarationTest.java @@ -12,6 +12,7 @@ import java.util.List; import org.junit.Test; import net.sourceforge.pmd.lang.ast.ParseException; +import net.sourceforge.pmd.lang.java.BaseParserTest; public class ASTImportDeclarationTest extends BaseParserTest { diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTInitializerTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTInitializerTest.java index f333179d1b..cbb3527f99 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTInitializerTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTInitializerTest.java @@ -6,12 +6,13 @@ package net.sourceforge.pmd.lang.java.ast; import org.junit.Test; +import net.sourceforge.pmd.lang.java.BaseParserTest; + public class ASTInitializerTest extends BaseParserTest { @Test public void testDontCrashOnBlockStatement() { - java.parse(TEST1); + java.parse("public class Foo { { x = 5; } }"); } - private static final String TEST1 = "public class Foo {\n {\n x = 5;\n }\n}"; } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTModuleDeclarationTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTModuleDeclarationTest.java index 0216dab627..4be1ebb900 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTModuleDeclarationTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTModuleDeclarationTest.java @@ -12,18 +12,18 @@ import java.util.List; import org.junit.Test; +import net.sourceforge.pmd.lang.java.BaseParserTest; + public class ASTModuleDeclarationTest extends BaseParserTest { @Test public final void jdk9ModuleInfo() { ASTCompilationUnit ast = java9.parseResource("jdkversiontests/jdk9_module_info.java"); - List modules = ast.findDescendantsOfType(ASTModuleDeclaration.class); - assertEquals(1, modules.size()); - ASTModuleDeclaration module = modules.get(0); + ASTModuleDeclaration module = ast.descendants(ASTModuleDeclaration.class).firstOrThrow(); assertTrue(module.isOpen()); assertEquals("com.example.foo", module.getImage()); assertEquals(7, module.getNumChildren()); - List directives = module.findChildrenOfType(ASTModuleDirective.class); + List directives = module.children(ASTModuleDirective.class).toList(); assertEquals(7, directives.size()); // requires com.example.foo.http; diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTPackageDeclarationTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTPackageDeclarationTest.java index f9152ec1ba..5fa767ab6f 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTPackageDeclarationTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTPackageDeclarationTest.java @@ -8,6 +8,8 @@ import static org.junit.Assert.assertEquals; import org.junit.Test; +import net.sourceforge.pmd.lang.java.BaseParserTest; + public class ASTPackageDeclarationTest extends BaseParserTest { private static final String PACKAGE_INFO_ANNOTATED = "@Deprecated\npackage net.sourceforge.pmd.foobar;\n"; diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabelTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabelTest.java index abf5bb6c6c..72c8447bdb 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabelTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabelTest.java @@ -11,11 +11,13 @@ import java.util.List; import org.junit.Test; +import net.sourceforge.pmd.lang.java.BaseParserTest; + public class ASTSwitchLabelTest extends BaseParserTest { @Test public void testDefaultOff() { - List ops = java.getNodes(ASTSwitchLabel.class, TEST1); + List ops = java.getNodes(ASTSwitchLabel.class, "public class Foo {\n void bar() {\n switch (x) {\n case 1: y = 2;\n }\n }\n}"); assertFalse(ops.get(0).isDefault()); } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchStatementTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchStatementTest.java index 037de44c5b..f512d16415 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchStatementTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchStatementTest.java @@ -7,14 +7,16 @@ package net.sourceforge.pmd.lang.java.ast; import org.junit.Assert; import org.junit.Test; +import net.sourceforge.pmd.lang.java.BaseParserTest; + public class ASTSwitchStatementTest extends BaseParserTest { @Test public void exhaustiveEnumSwitchWithDefault() { - ASTSwitchStatement switchStatement = getNodes(ASTSwitchStatement.class, + ASTSwitchStatement switchStatement = java.parse( "import java.nio.file.AccessMode; class Foo { void bar(AccessMode m) {" + "switch (m) { case READ: break; default: break; } } }") - .get(0); + .descendants(ASTSwitchStatement.class).firstOrThrow(); Assert.assertFalse(switchStatement.isExhaustiveEnumSwitch()); // this should not throw a NPE... Assert.assertTrue(switchStatement.hasDefaultCase()); Assert.assertTrue(switchStatement.isFallthroughSwitch()); @@ -22,10 +24,9 @@ public class ASTSwitchStatementTest extends BaseParserTest { @Test public void defaultCaseWithArrowBlock() { - ASTSwitchStatement switchStatement = getNodes(ASTSwitchStatement.class, - "class Foo { void bar(int x) {" - + "switch (x) { default -> { } } } }") - .get(0); + ASTSwitchStatement switchStatement = + java.parse("class Foo { void bar(int x) {switch (x) { default -> { } } } }") + .descendants(ASTSwitchStatement.class).firstOrThrow(); Assert.assertFalse(switchStatement.isExhaustiveEnumSwitch()); Assert.assertTrue(switchStatement.iterator().hasNext()); Assert.assertTrue(switchStatement.hasDefaultCase()); @@ -34,10 +35,9 @@ public class ASTSwitchStatementTest extends BaseParserTest { @Test public void emptySwitch() { - ASTSwitchStatement switchStatement = getNodes(ASTSwitchStatement.class, - "class Foo { void bar(int x) {" - + "switch (x) { } } }") - .get(0); + ASTSwitchStatement switchStatement = + java.parse("class Foo { void bar(int x) {switch (x) { } } }") + .descendants(ASTSwitchStatement.class).firstOrThrow(); Assert.assertFalse(switchStatement.isExhaustiveEnumSwitch()); Assert.assertFalse(switchStatement.iterator().hasNext()); Assert.assertFalse(switchStatement.hasDefaultCase()); @@ -47,7 +47,7 @@ public class ASTSwitchStatementTest extends BaseParserTest { @Test public void defaultCaseWithArrowExprs() { ASTSwitchStatement switchStatement = - getNodes(ASTSwitchStatement.class, + java.parse( "import net.sourceforge.pmd.lang.java.rule.bestpractices.switchstmtsshouldhavedefault.SimpleEnum;\n" + "\n" + " public class Foo {\n" @@ -59,7 +59,7 @@ public class ASTSwitchStatementTest extends BaseParserTest { + " }\n" + " }\n" + " }") - .get(0); + .descendants(ASTSwitchStatement.class).firstOrThrow(); Assert.assertFalse(switchStatement.isExhaustiveEnumSwitch()); Assert.assertTrue(switchStatement.iterator().hasNext()); Assert.assertFalse(switchStatement.isFallthroughSwitch()); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorIdTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorIdTest.java index c9259722b5..2fe8636e5b 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorIdTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorIdTest.java @@ -10,6 +10,7 @@ import static org.junit.Assert.assertTrue; import org.junit.Test; +import net.sourceforge.pmd.lang.java.BaseParserTest; import net.sourceforge.pmd.lang.java.ast.internal.PrettyPrintingUtil; public class ASTVariableDeclaratorIdTest extends BaseParserTest { diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/BaseParserTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/BaseParserTest.java deleted file mode 100644 index 3901cd87b0..0000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/BaseParserTest.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.ast; - -import java.util.List; - -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.JavaParsingHelper; - -/** - * Base class for tests that usually need processing stages to run when - * parsing code. - */ -public abstract class BaseParserTest { - - protected final JavaParsingHelper java = JavaParsingHelper.JUST_PARSE.withResourceContext(getClass()); - protected final JavaParsingHelper java5 = java.withDefaultVersion("1.5"); - protected final JavaParsingHelper java8 = java.withDefaultVersion("1.8"); - protected final JavaParsingHelper java9 = java.withDefaultVersion("9"); - - - protected ASTCompilationUnit parseCode(final String code) { - return java.parse(code); - } - - protected List getNodes(Class target, String code) { - return JavaParsingHelper.WITH_PROCESSING.getNodes(target, code); - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/CommentAssignmentTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/CommentAssignmentTest.java index 7508ce63e4..604bf2d010 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/CommentAssignmentTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/CommentAssignmentTest.java @@ -13,9 +13,9 @@ import java.util.List; import org.junit.Assert; import org.junit.Test; -import net.sourceforge.pmd.lang.java.symboltable.BaseNonParserTest; +import net.sourceforge.pmd.lang.java.BaseParserTest; -public class CommentAssignmentTest extends BaseNonParserTest { +public class CommentAssignmentTest extends BaseParserTest { /** * Blank lines in comments should not raise an exception. See bug #1048. diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/CommentTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/CommentTest.java index 70e381897e..9b603ee958 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/CommentTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/CommentTest.java @@ -9,9 +9,9 @@ import org.junit.Assert; import org.junit.Test; import net.sourceforge.pmd.PMD; -import net.sourceforge.pmd.lang.java.symboltable.BaseNonParserTest; +import net.sourceforge.pmd.lang.java.BaseParserTest; -public class CommentTest extends BaseNonParserTest { +public class CommentTest extends BaseParserTest { @Test public void testMultiLinesInSingleLine() { String comment = "/* single line. */"; diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ConstantExpressionsTests.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ConstantExpressionsTests.java index 318c693a83..2138855725 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ConstantExpressionsTests.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ConstantExpressionsTests.java @@ -11,9 +11,9 @@ import org.junit.Test; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.function.Executable; -import net.sourceforge.pmd.lang.java.symboltable.BaseNonParserTest; +import net.sourceforge.pmd.lang.java.BaseParserTest; -public class ConstantExpressionsTests extends BaseNonParserTest { +public class ConstantExpressionsTests extends BaseParserTest { private Executable isConst(String expr, Object value) { return () -> { diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/EncodingTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/EncodingTest.java index bc4ced4c2a..a4b04a4c25 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/EncodingTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/EncodingTest.java @@ -8,14 +8,15 @@ import static org.junit.Assert.assertEquals; import org.junit.Test; +import net.sourceforge.pmd.lang.java.BaseParserTest; + public class EncodingTest extends BaseParserTest { @Test public void testDecodingOfUTF8() { - ASTCompilationUnit acu = java.parse(TEST_UTF8); - String methodName = acu.getFirstDescendantOfType(ASTMethodDeclaration.class).getImage(); + ASTCompilationUnit acu = java.parse("class Foo { void Γ©() {} }"); + String methodName = acu.descendants(ASTMethodDeclaration.class).firstOrThrow().getName(); assertEquals("Γ©", methodName); } - private static final String TEST_UTF8 = "class Foo {\n void Γ©() {}\n void fiddle() {}\n}"; } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/FormalCommentTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/FormalCommentTest.java index 5c8f8b11da..7e0642b6c9 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/FormalCommentTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/FormalCommentTest.java @@ -10,6 +10,7 @@ import org.junit.Assert; import org.junit.Test; import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken; +import net.sourceforge.pmd.lang.java.BaseParserTest; public class FormalCommentTest extends BaseParserTest { diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/JDKVersionTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/JDKVersionTest.java index 82fcdcc54e..832f51e87c 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/JDKVersionTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/JDKVersionTest.java @@ -18,7 +18,7 @@ import net.sourceforge.pmd.lang.java.JavaParsingHelper; public class JDKVersionTest { - private final JavaParsingHelper java3 = JavaParsingHelper.JUST_PARSE + private final JavaParsingHelper java3 = JavaParsingHelper.DEFAULT .withDefaultVersion("1.3") .withResourceContext(JDKVersionTest.class, "jdkversiontests/"); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java10Test.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java10Test.java index 9f9086f0aa..ec7780e573 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java10Test.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java10Test.java @@ -26,8 +26,8 @@ import net.sourceforge.pmd.lang.java.types.TypeTestUtil; public class Java10Test { private final JavaParsingHelper java10 = - JavaParsingHelper.WITH_PROCESSING.withDefaultVersion("10") - .withResourceContext(Java10Test.class, "jdkversiontests/java10/"); + JavaParsingHelper.DEFAULT.withDefaultVersion("10") + .withResourceContext(Java10Test.class, "jdkversiontests/java10/"); private final JavaParsingHelper java9 = java10.withDefaultVersion("9"); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java14Test.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java14Test.java index 0dfaa82373..836f0de1e3 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java14Test.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java14Test.java @@ -24,8 +24,8 @@ import net.sourceforge.pmd.lang.java.JavaParsingHelper; @Ignore("Needs to be fixed for new AST structure. All of this is already much better tested in Kotlin, I don't want to port these tests...") public class Java14Test { private final JavaParsingHelper java14 = - JavaParsingHelper.WITH_PROCESSING.withDefaultVersion("14") - .withResourceContext(Java14Test.class, "jdkversiontests/java14/"); + JavaParsingHelper.DEFAULT.withDefaultVersion("14") + .withResourceContext(Java14Test.class, "jdkversiontests/java14/"); private final JavaParsingHelper java13 = java14.withDefaultVersion("13"); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java15TreeDumpTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java15TreeDumpTest.java index ecb6cdbdb6..20df93fd03 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java15TreeDumpTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java15TreeDumpTest.java @@ -13,8 +13,8 @@ import net.sourceforge.pmd.lang.java.JavaParsingHelper; public class Java15TreeDumpTest extends BaseJavaTreeDumpTest { private final JavaParsingHelper java15 = - JavaParsingHelper.WITH_PROCESSING.withDefaultVersion("15") - .withResourceContext(Java15TreeDumpTest.class, "jdkversiontests/java15/"); + JavaParsingHelper.DEFAULT.withDefaultVersion("15") + .withResourceContext(Java15TreeDumpTest.class, "jdkversiontests/java15/"); private final JavaParsingHelper java14 = java15.withDefaultVersion("14"); @Override diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java16PreviewTreeDumpTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java16PreviewTreeDumpTest.java index 67c86b8817..858b597ffb 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java16PreviewTreeDumpTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java16PreviewTreeDumpTest.java @@ -18,8 +18,8 @@ import net.sourceforge.pmd.lang.java.JavaParsingHelper; public class Java16PreviewTreeDumpTest extends BaseTreeDumpTest { private final JavaParsingHelper java16p = - JavaParsingHelper.WITH_PROCESSING.withDefaultVersion("16-preview") - .withResourceContext(Java16PreviewTreeDumpTest.class, "jdkversiontests/java16p/"); + JavaParsingHelper.DEFAULT.withDefaultVersion("16-preview") + .withResourceContext(Java16PreviewTreeDumpTest.class, "jdkversiontests/java16p/"); private final JavaParsingHelper java16 = java16p.withDefaultVersion("16"); public Java16PreviewTreeDumpTest() { diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java16TreeDumpTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java16TreeDumpTest.java index 001a187131..7c852d2a53 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java16TreeDumpTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java16TreeDumpTest.java @@ -18,8 +18,8 @@ import net.sourceforge.pmd.lang.java.types.JPrimitiveType; public class Java16TreeDumpTest extends BaseJavaTreeDumpTest { private final JavaParsingHelper java16 = - JavaParsingHelper.WITH_PROCESSING.withDefaultVersion("16") - .withResourceContext(Java16TreeDumpTest.class, "jdkversiontests/java16/"); + JavaParsingHelper.DEFAULT.withDefaultVersion("16") + .withResourceContext(Java16TreeDumpTest.class, "jdkversiontests/java16/"); private final JavaParsingHelper java16p = java16.withDefaultVersion("16-preview"); private final JavaParsingHelper java15 = java16.withDefaultVersion("15"); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java17PreviewTreeDumpTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java17PreviewTreeDumpTest.java index 0e1e2eb5c9..86d198fc52 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java17PreviewTreeDumpTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java17PreviewTreeDumpTest.java @@ -19,8 +19,8 @@ import net.sourceforge.pmd.lang.java.JavaParsingHelper; public class Java17PreviewTreeDumpTest extends BaseTreeDumpTest { private final JavaParsingHelper java17p = - JavaParsingHelper.WITH_PROCESSING.withDefaultVersion("17-preview") - .withResourceContext(Java17PreviewTreeDumpTest.class, "jdkversiontests/java17p/"); + JavaParsingHelper.DEFAULT.withDefaultVersion("17-preview") + .withResourceContext(Java17PreviewTreeDumpTest.class, "jdkversiontests/java17p/"); private final JavaParsingHelper java17 = java17p.withDefaultVersion("17"); public Java17PreviewTreeDumpTest() { diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java17TreeDumpTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java17TreeDumpTest.java index 96c4390e0f..19356ba1fa 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java17TreeDumpTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java17TreeDumpTest.java @@ -16,8 +16,8 @@ import net.sourceforge.pmd.lang.java.JavaParsingHelper; public class Java17TreeDumpTest extends BaseTreeDumpTest { private final JavaParsingHelper java17 = - JavaParsingHelper.WITH_PROCESSING.withDefaultVersion("17") - .withResourceContext(Java17TreeDumpTest.class, "jdkversiontests/java17/"); + JavaParsingHelper.DEFAULT.withDefaultVersion("17") + .withResourceContext(Java17TreeDumpTest.class, "jdkversiontests/java17/"); private final JavaParsingHelper java17p = java17.withDefaultVersion("17-preview"); private final JavaParsingHelper java16 = java17.withDefaultVersion("16"); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java8Test.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java8Test.java index 96064e75dd..c86f124f8f 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java8Test.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java8Test.java @@ -10,8 +10,8 @@ import net.sourceforge.pmd.lang.java.JavaParsingHelper; public class Java8Test { private final JavaParsingHelper java8 = - JavaParsingHelper.WITH_PROCESSING.withDefaultVersion("8") - .withResourceContext(Java8Test.class); + JavaParsingHelper.DEFAULT.withDefaultVersion("8") + .withResourceContext(Java8Test.class); @Test public void interfaceMethodShouldBeParseable() { diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/JavaQualifiedNameTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/JavaQualifiedNameTest.java index a55fe2d048..3494b6eb00 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/JavaQualifiedNameTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/JavaQualifiedNameTest.java @@ -24,7 +24,7 @@ public class JavaQualifiedNameTest { private List getNodes(Class target, String code) { - return JavaParsingHelper.WITH_PROCESSING.withDefaultVersion("15").getNodes(target, code); + return JavaParsingHelper.DEFAULT.withDefaultVersion("15").getNodes(target, code); } @Test diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java index ce36be02e5..3a6161dbc8 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java @@ -23,7 +23,7 @@ import net.sourceforge.pmd.lang.symboltable.NameDeclaration; import net.sourceforge.pmd.lang.symboltable.NameOccurrence; public class ParserCornersTest extends BaseJavaTreeDumpTest { - private final JavaParsingHelper java = JavaParsingHelper.WITH_PROCESSING.withResourceContext(getClass()); + private final JavaParsingHelper java = JavaParsingHelper.DEFAULT.withResourceContext(getClass()); private final JavaParsingHelper java4 = java.withDefaultVersion("1.4"); private final JavaParsingHelper java5 = java.withDefaultVersion("1.5"); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/SimpleNodeTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/SimpleNodeTest.java index 08077df94a..f13a357ae9 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/SimpleNodeTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/SimpleNodeTest.java @@ -17,7 +17,7 @@ import org.junit.Test; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.ast.test.TestUtilsKt; -import net.sourceforge.pmd.lang.java.JavaParsingHelper; +import net.sourceforge.pmd.lang.java.BaseParserTest; @Ignore("This test is Java specific even though parts of it should apply to any language implementation") // The Java specific parts depend on the grammar and are subject to breaking during the grammar update process @@ -174,7 +174,7 @@ public class SimpleNodeTest extends BaseParserTest { @Test public void testParentMethods() { - ASTCompilationUnit u = JavaParsingHelper.JUST_PARSE.parse(TEST1); + ASTCompilationUnit u = java.parse(TEST1); ASTMethodDeclarator d = u.getFirstDescendantOfType(ASTMethodDeclarator.class); assertSame("getFirstParentOfType ASTMethodDeclaration", d.getParent(), diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/TextBlockEscapeTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/TextBlockEscapeTest.java index cdedc88f8e..63f30f4510 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/TextBlockEscapeTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/TextBlockEscapeTest.java @@ -8,6 +8,8 @@ import static org.junit.Assert.assertEquals; import org.junit.Test; +import net.sourceforge.pmd.lang.java.BaseParserTest; + public class TextBlockEscapeTest extends BaseParserTest { @Test diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/internal/PrettyPrintingUtilTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/internal/PrettyPrintingUtilTest.java index fbb158d5d1..aaa272bf1c 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/internal/PrettyPrintingUtilTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/internal/PrettyPrintingUtilTest.java @@ -17,13 +17,13 @@ import org.hamcrest.Matcher; import org.hamcrest.MatcherAssert; import org.junit.Test; +import net.sourceforge.pmd.lang.java.BaseParserTest; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.ASTMethodCall; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; -import net.sourceforge.pmd.lang.java.symboltable.BaseNonParserTest; import net.sourceforge.pmd.util.StringUtil; -public class PrettyPrintingUtilTest extends BaseNonParserTest { +public class PrettyPrintingUtilTest extends BaseParserTest { @Test public void displaySignatureTestWithExtraDimensions() { diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsProviderTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsProviderTest.java index 42e4b873b5..74f6f024e9 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsProviderTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsProviderTest.java @@ -24,7 +24,7 @@ import net.sourceforge.pmd.lang.metrics.Metric; */ public class JavaMetricsProviderTest { - private final JavaParsingHelper java8 = JavaParsingHelper.WITH_PROCESSING.withDefaultVersion("1.8"); + private final JavaParsingHelper java8 = JavaParsingHelper.DEFAULT.withDefaultVersion("1.8"); @Test public void testComputeAllMetrics() { diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/MetricsMemoizationTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/MetricsMemoizationTest.java index 8000b1952c..667cbf673f 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/MetricsMemoizationTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/MetricsMemoizationTest.java @@ -15,12 +15,12 @@ import java.util.Random; import org.junit.Test; import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.java.BaseParserTest; import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; import net.sourceforge.pmd.lang.java.ast.JavaVisitorBase; import net.sourceforge.pmd.lang.java.metrics.testdata.MetricsVisitorTestData; -import net.sourceforge.pmd.lang.java.symboltable.BaseNonParserTest; import net.sourceforge.pmd.lang.metrics.Metric; import net.sourceforge.pmd.lang.metrics.MetricOptions; import net.sourceforge.pmd.lang.metrics.MetricsUtil; @@ -28,7 +28,7 @@ import net.sourceforge.pmd.lang.metrics.MetricsUtil; /** * @author ClΓ©ment Fournier */ -public class MetricsMemoizationTest extends BaseNonParserTest { +public class MetricsMemoizationTest extends BaseParserTest { private final Metric randomMetric = randomMetric(); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/JavaRuleViolationTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/JavaRuleViolationTest.java index 494d91ffdb..1515e37f58 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/JavaRuleViolationTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/JavaRuleViolationTest.java @@ -42,7 +42,7 @@ public class JavaRuleViolationTest { } private ASTCompilationUnit parse(final String code) { - return JavaParsingHelper.WITH_PROCESSING.parse(code); + return JavaParsingHelper.DEFAULT.parse(code); } /** diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/XPathRuleTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/XPathRuleTest.java index dcf626bd79..ef00d1de9d 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/XPathRuleTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/XPathRuleTest.java @@ -126,7 +126,7 @@ public class XPathRuleTest extends RuleTst { @Test public void testFollowingSibling() throws Exception { final String source = "public interface dummy extends Foo, Bar, Baz {}"; - ASTCompilationUnit cu = JavaParsingHelper.WITH_PROCESSING.parse(source); + ASTCompilationUnit cu = JavaParsingHelper.DEFAULT.parse(source); String xpath = "//ExtendsList/ClassOrInterfaceType/following-sibling::ClassOrInterfaceType"; @@ -143,7 +143,7 @@ public class XPathRuleTest extends RuleTst { } private static Report getReportForTestString(Rule r, String test) { - return JavaParsingHelper.WITH_PROCESSING.executeRule(r, test); + return JavaParsingHelper.DEFAULT.executeRule(r, test); } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UselessParenthesesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UselessParenthesesTest.java index 25c3814242..3e1bf44e0d 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UselessParenthesesTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UselessParenthesesTest.java @@ -19,7 +19,7 @@ public class UselessParenthesesTest extends PmdRuleTst { Executable testImpl(String expression, Necessity necessity) { return () -> { String file = "class Foo {{ int a,b,c,d; float f1, f2, f3; String s; Object e = " + expression + ";}}"; - ASTCompilationUnit acu = JavaParsingHelper.WITH_PROCESSING.parse(file); + ASTCompilationUnit acu = JavaParsingHelper.DEFAULT.parse(file); ASTExpression paren = acu.descendants(ASTExpression.class).crossFindBoundaries().first(ASTExpression::isParenthesized); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtilTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtilTest.java index 75d856dd94..0f3652301c 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtilTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtilTest.java @@ -14,12 +14,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.Test; +import net.sourceforge.pmd.lang.java.BaseParserTest; import net.sourceforge.pmd.lang.java.ast.ASTExpression; import net.sourceforge.pmd.lang.java.ast.ASTInfixExpression; import net.sourceforge.pmd.lang.java.ast.ASTVariableAccess; -import net.sourceforge.pmd.lang.java.symboltable.BaseNonParserTest; -public class JavaRuleUtilTest extends BaseNonParserTest { +public class JavaRuleUtilTest extends BaseParserTest { @Test public void testCamelCaseWords() { diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/xpath/internal/BaseXPathFunctionTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/xpath/internal/BaseXPathFunctionTest.java index 4ea93fdb7f..b0e8901217 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/xpath/internal/BaseXPathFunctionTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/xpath/internal/BaseXPathFunctionTest.java @@ -18,8 +18,8 @@ import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.ast.FileAnalysisException; import net.sourceforge.pmd.lang.ast.test.TestUtilsKt; +import net.sourceforge.pmd.lang.java.BaseParserTest; import net.sourceforge.pmd.lang.java.JavaLanguageModule; -import net.sourceforge.pmd.lang.java.symboltable.BaseNonParserTest; import net.sourceforge.pmd.lang.rule.XPathRule; import net.sourceforge.pmd.lang.rule.xpath.PmdXPathException; import net.sourceforge.pmd.lang.rule.xpath.XPathVersion; @@ -28,7 +28,7 @@ import net.sourceforge.pmd.lang.rule.xpath.XPathVersion; * @author ClΓ©ment Fournier * @since 7.0.0 */ -public class BaseXPathFunctionTest extends BaseNonParserTest { +public class BaseXPathFunctionTest extends BaseParserTest { private static final String VIOLATION_MESSAGE = "violation"; private static final String RULE_NAME_PLACEHOLDER = "$rule_name"; diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symbols/table/internal/AbruptCompletionTests.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symbols/table/internal/AbruptCompletionTests.java index c0d993aea9..048e702359 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symbols/table/internal/AbruptCompletionTests.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symbols/table/internal/AbruptCompletionTests.java @@ -13,16 +13,16 @@ import org.junit.Test; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.function.Executable; +import net.sourceforge.pmd.lang.java.BaseParserTest; import net.sourceforge.pmd.lang.java.JavaParsingHelper; import net.sourceforge.pmd.lang.java.ast.ASTBlock; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.ASTIfStatement; import net.sourceforge.pmd.lang.java.ast.ASTStatement; -import net.sourceforge.pmd.lang.java.symboltable.BaseNonParserTest; import junit.framework.AssertionFailedError; -public class AbruptCompletionTests extends BaseNonParserTest { +public class AbruptCompletionTests extends BaseParserTest { private final JavaParsingHelper java17 = java.withDefaultVersion("17"); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symbols/table/internal/PatternBindingsTests.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symbols/table/internal/PatternBindingsTests.java index b5ade11b72..3183aab108 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symbols/table/internal/PatternBindingsTests.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symbols/table/internal/PatternBindingsTests.java @@ -16,15 +16,15 @@ import org.junit.Test; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.function.Executable; +import net.sourceforge.pmd.lang.java.BaseParserTest; import net.sourceforge.pmd.lang.java.JavaParsingHelper; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.ASTExpression; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; import net.sourceforge.pmd.lang.java.symbols.table.internal.PatternBindingsUtil.BindSet; -import net.sourceforge.pmd.lang.java.symboltable.BaseNonParserTest; import net.sourceforge.pmd.util.CollectionUtil; -public class PatternBindingsTests extends BaseNonParserTest { +public class PatternBindingsTests extends BaseParserTest { private final JavaParsingHelper java15p = java.withDefaultVersion("17"); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/AcceptanceTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/AcceptanceTest.java index 5caf3c9f4d..acc562f48a 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/AcceptanceTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/AcceptanceTest.java @@ -17,6 +17,7 @@ import org.junit.Test; import net.sourceforge.pmd.PMD; import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.java.BaseParserTest; import net.sourceforge.pmd.lang.java.ast.ASTBlock; import net.sourceforge.pmd.lang.java.ast.ASTCatchClause; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; @@ -30,7 +31,7 @@ import net.sourceforge.pmd.lang.symboltable.NameOccurrence; import net.sourceforge.pmd.lang.symboltable.Scope; @Ignore -public class AcceptanceTest extends BaseNonParserTest { +public class AcceptanceTest extends BaseParserTest { @Test public void testClashingSymbols() { diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/ClassScopeTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/ClassScopeTest.java index c7cda7ce2d..6351fe0c35 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/ClassScopeTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/ClassScopeTest.java @@ -17,6 +17,7 @@ import org.junit.Ignore; import org.junit.Test; import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.lang.java.BaseParserTest; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; @@ -32,7 +33,7 @@ import net.sourceforge.pmd.lang.symboltable.NameDeclaration; import net.sourceforge.pmd.lang.symboltable.NameOccurrence; @Ignore -public class ClassScopeTest extends BaseNonParserTest { +public class ClassScopeTest extends BaseParserTest { @Test public void testEnumsClassScope() { diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/GlobalScopeTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/GlobalScopeTest.java index 42a631f05e..f80b59cdec 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/GlobalScopeTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/GlobalScopeTest.java @@ -13,13 +13,14 @@ import org.junit.Ignore; import org.junit.Test; import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.lang.java.BaseParserTest; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.symboltable.NameDeclaration; import net.sourceforge.pmd.lang.symboltable.NameOccurrence; import net.sourceforge.pmd.lang.symboltable.Scope; @Ignore -public class GlobalScopeTest extends BaseNonParserTest { +public class GlobalScopeTest extends BaseParserTest { @Test public void testClassDeclAppears() { diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/LocalScopeTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/LocalScopeTest.java index a7a41a0eff..83630f99bc 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/LocalScopeTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/LocalScopeTest.java @@ -13,6 +13,7 @@ import org.junit.Ignore; import org.junit.Test; import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.lang.java.BaseParserTest; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter; import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration; @@ -21,7 +22,7 @@ import net.sourceforge.pmd.lang.symboltable.NameDeclaration; import net.sourceforge.pmd.lang.symboltable.NameOccurrence; @Ignore -public class LocalScopeTest extends BaseNonParserTest { +public class LocalScopeTest extends BaseParserTest { @Test public void testLocalVariableDeclarationFound() { diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/MethodNameDeclarationTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/MethodNameDeclarationTest.java index 48b3543079..828a5f972f 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/MethodNameDeclarationTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/MethodNameDeclarationTest.java @@ -14,13 +14,14 @@ import org.junit.Ignore; import org.junit.Test; import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.lang.java.BaseParserTest; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.symboltable.NameDeclaration; import net.sourceforge.pmd.lang.symboltable.NameOccurrence; @Ignore -public class MethodNameDeclarationTest extends BaseNonParserTest { +public class MethodNameDeclarationTest extends BaseParserTest { @Test public void testEquality() { diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/MethodScopeTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/MethodScopeTest.java index 34b6ccb5c6..0ca2f77957 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/MethodScopeTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/MethodScopeTest.java @@ -13,13 +13,14 @@ import org.junit.Ignore; import org.junit.Test; import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.lang.java.BaseParserTest; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; import net.sourceforge.pmd.lang.symboltable.NameDeclaration; import net.sourceforge.pmd.lang.symboltable.NameOccurrence; @Ignore -public class MethodScopeTest extends BaseNonParserTest { +public class MethodScopeTest extends BaseParserTest { @Test public void testMethodParameterOccurrenceRecorded() { diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/NameOccurrencesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/NameOccurrencesTest.java index e2ac16f4f9..01d0a58caa 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/NameOccurrencesTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/NameOccurrencesTest.java @@ -14,11 +14,12 @@ import org.junit.Ignore; import org.junit.Test; import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.lang.java.BaseParserTest; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression; @Ignore -public class NameOccurrencesTest extends BaseNonParserTest { +public class NameOccurrencesTest extends BaseParserTest { @Test public void testSuper() { diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/ScopeAndDeclarationFinderTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/ScopeAndDeclarationFinderTest.java index 58ec72fc41..4bd3abb318 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/ScopeAndDeclarationFinderTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/ScopeAndDeclarationFinderTest.java @@ -12,6 +12,7 @@ import org.junit.Ignore; import org.junit.Test; import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.lang.java.BaseParserTest; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBody; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; @@ -21,7 +22,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression; import net.sourceforge.pmd.lang.symboltable.NameDeclaration; @Ignore -public class ScopeAndDeclarationFinderTest extends BaseNonParserTest { +public class ScopeAndDeclarationFinderTest extends BaseParserTest { /** * Unit test for https://sourceforge.net/p/pmd/bugs/1317/ diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/ScopeCreationVisitorTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/ScopeCreationVisitorTest.java index 193c660243..d748cf2883 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/ScopeCreationVisitorTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/ScopeCreationVisitorTest.java @@ -10,12 +10,13 @@ import org.junit.Ignore; import org.junit.Test; import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.lang.java.BaseParserTest; import net.sourceforge.pmd.lang.java.ast.ASTBlock; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.ASTIfStatement; @Ignore -public class ScopeCreationVisitorTest extends BaseNonParserTest { +public class ScopeCreationVisitorTest extends BaseParserTest { @Test public void testScopesAreCreated() { diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/SourceFileScopeTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/SourceFileScopeTest.java index 609d7fa4aa..befb8bb121 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/SourceFileScopeTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/SourceFileScopeTest.java @@ -15,12 +15,13 @@ import org.junit.Ignore; import org.junit.Test; import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.lang.java.BaseParserTest; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.symboltable.NameDeclaration; import net.sourceforge.pmd.lang.symboltable.NameOccurrence; @Ignore -public class SourceFileScopeTest extends BaseNonParserTest { +public class SourceFileScopeTest extends BaseParserTest { @Test public void testClassDeclAppears() { diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/VariableNameDeclarationTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/VariableNameDeclarationTest.java index c205e5f492..b682ba0add 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/VariableNameDeclarationTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/VariableNameDeclarationTest.java @@ -15,13 +15,14 @@ import org.junit.Ignore; import org.junit.Test; import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.lang.java.BaseParserTest; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; import net.sourceforge.pmd.lang.symboltable.NameDeclaration; import net.sourceforge.pmd.lang.symboltable.Scope; @Ignore -public class VariableNameDeclarationTest extends BaseNonParserTest { +public class VariableNameDeclarationTest extends BaseParserTest { @Test public void testConstructor() { diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/GenericMethodReferenceTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/GenericMethodReferenceTest.java index bbf100514c..08c0232ec2 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/GenericMethodReferenceTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/GenericMethodReferenceTest.java @@ -20,7 +20,7 @@ public class GenericMethodReferenceTest { @Test public void typeResolveVariable() { - ASTCompilationUnit root = JavaParsingHelper.WITH_PROCESSING.parseClass(GenericMethodReference.class); + ASTCompilationUnit root = JavaParsingHelper.DEFAULT.parseClass(GenericMethodReference.class); root.descendants(ASTVariableDeclaratorId.class).forEach(variable -> { Assert.assertTrue(variable.getName().startsWith("supplier")); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/InvocationMatcherTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/InvocationMatcherTest.java index 626a0f6206..598d27eec3 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/InvocationMatcherTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/InvocationMatcherTest.java @@ -11,12 +11,12 @@ import org.hamcrest.MatcherAssert; import org.junit.Assert; import org.junit.Test; +import net.sourceforge.pmd.lang.java.BaseParserTest; import net.sourceforge.pmd.lang.java.ast.ASTConstructorCall; import net.sourceforge.pmd.lang.java.ast.ASTMethodCall; import net.sourceforge.pmd.lang.java.ast.InvocationNode; -import net.sourceforge.pmd.lang.java.symboltable.BaseNonParserTest; -public class InvocationMatcherTest extends BaseNonParserTest { +public class InvocationMatcherTest extends BaseParserTest { @Test public void testSimpleMatcher() { diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/TypeTestUtilTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/TypeTestUtilTest.java index fc98af1276..72ead82d26 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/TypeTestUtilTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/TypeTestUtilTest.java @@ -14,6 +14,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import net.sourceforge.pmd.lang.java.BaseParserTest; import net.sourceforge.pmd.lang.java.ast.ASTAnnotation; import net.sourceforge.pmd.lang.java.ast.ASTAnnotationTypeDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTAnonymousClassDeclaration; @@ -23,10 +24,9 @@ import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTType; import net.sourceforge.pmd.lang.java.ast.TypeNode; -import net.sourceforge.pmd.lang.java.symboltable.BaseNonParserTest; import net.sourceforge.pmd.lang.java.types.testdata.SomeClassWithAnon; -public class TypeTestUtilTest extends BaseNonParserTest { +public class TypeTestUtilTest extends BaseParserTest { @Rule public final ExpectedException expect = ExpectedException.none(); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/TypesTreeDumpTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/TypesTreeDumpTest.java index 78c7214dcf..6f1180e1fb 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/TypesTreeDumpTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/TypesTreeDumpTest.java @@ -31,7 +31,7 @@ public class TypesTreeDumpTest extends BaseTreeDumpTest { @Override public @NonNull BaseParsingHelper getParser() { - return JavaParsingHelper.WITH_PROCESSING.withResourceContext(getClass()); + return JavaParsingHelper.DEFAULT.withResourceContext(getClass()); } @Test diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclarationTest.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclarationTest.kt index 331930a0e7..3dba0caf2a 100644 --- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclarationTest.kt +++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclarationTest.kt @@ -219,6 +219,7 @@ class ASTMethodDeclarationTest : ParserTestSpec({ it::getFormalParameters shouldBe formalsList(1) { child { + it::isVarargs shouldBe true it::getModifiers shouldBe modifiers { annotation("Oha") } diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/KotlinTestingDsl.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/KotlinTestingDsl.kt index fa1a6e342a..31d1eb30ad 100644 --- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/KotlinTestingDsl.kt +++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/KotlinTestingDsl.kt @@ -36,7 +36,7 @@ enum class JavaVersion : Comparable { val pmdVersion get() = LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion(pmdName) - val parser: JavaParsingHelper = WITH_PROCESSING.withDefaultVersion(pmdName) + val parser: JavaParsingHelper = DEFAULT.withDefaultVersion(pmdName) operator fun not(): List = values().toList() - this diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/TestUtilitiesForTypes.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/TestUtilitiesForTypes.kt index 955274f357..6dd913262b 100644 --- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/TestUtilitiesForTypes.kt +++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/TestUtilitiesForTypes.kt @@ -28,7 +28,7 @@ import kotlin.test.assertTrue is done lazily. */ -val javaParser: JavaParsingHelper = JavaParsingHelper.WITH_PROCESSING +val javaParser: JavaParsingHelper = JavaParsingHelper.DEFAULT val testTypeSystem: TypeSystem get() = JavaParsingHelper.TEST_TYPE_SYSTEM diff --git a/pmd-javascript/src/test/java/net/sourceforge/pmd/cli/CLITest.java b/pmd-javascript/src/test/java/net/sourceforge/pmd/cli/CLITest.java index a051f56a6f..718995de62 100644 --- a/pmd-javascript/src/test/java/net/sourceforge/pmd/cli/CLITest.java +++ b/pmd-javascript/src/test/java/net/sourceforge/pmd/cli/CLITest.java @@ -4,14 +4,10 @@ package net.sourceforge.pmd.cli; -import static org.junit.Assert.assertTrue; - -import java.io.File; +import static org.hamcrest.MatcherAssert.assertThat; import org.junit.Test; -import net.sourceforge.pmd.util.FileUtil; - /** * @author Romain Pelisse <belaran@gmail.com> * @@ -20,9 +16,8 @@ public class CLITest extends BaseCLITest { @Test public void useEcmaScript() { String[] args = { "-d", SOURCE_FOLDER, "-f", "xml", "-R", "ecmascript-basic", "-l", - "ecmascript", "-debug", }; - String resultFilename = runTest(args, "useEcmaScript"); - assertTrue("Invalid JavaScript version", - FileUtil.findPatternInFile(new File(resultFilename), "Using Ecmascript version: Ecmascript ES6")); + "ecmascript", "--debug", }; + String log = runTest(args); + assertThat(log, containsPattern("Adding file .*\\.js \\(lang: ecmascript ES6\\)")); } } diff --git a/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/ast/JsParsingHelper.java b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/ast/JsParsingHelper.java index 055891146a..ac28fc7203 100644 --- a/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/ast/JsParsingHelper.java +++ b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/ast/JsParsingHelper.java @@ -9,7 +9,7 @@ import net.sourceforge.pmd.lang.ecmascript.EcmascriptLanguageModule; public final class JsParsingHelper extends BaseParsingHelper { - public static final JsParsingHelper DEFAULT = new JsParsingHelper(Params.getDefaultProcess()); + public static final JsParsingHelper DEFAULT = new JsParsingHelper(Params.getDefault()); private JsParsingHelper(Params params) { super(EcmascriptLanguageModule.NAME, ASTAstRoot.class, params); diff --git a/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/ast/JspParsingHelper.java b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/ast/JspParsingHelper.java index bc647b37c2..46110e66bc 100644 --- a/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/ast/JspParsingHelper.java +++ b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/ast/JspParsingHelper.java @@ -9,7 +9,7 @@ import net.sourceforge.pmd.lang.jsp.JspLanguageModule; public final class JspParsingHelper extends BaseParsingHelper { - public static final JspParsingHelper DEFAULT = new JspParsingHelper(Params.getDefaultProcess()); + public static final JspParsingHelper DEFAULT = new JspParsingHelper(Params.getDefault()); private JspParsingHelper(Params params) { super(JspLanguageModule.NAME, ASTCompilationUnit.class, params); diff --git a/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/BaseParsingHelper.kt b/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/BaseParsingHelper.kt index e5c8a96be5..268bcd9745 100644 --- a/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/BaseParsingHelper.kt +++ b/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/BaseParsingHelper.kt @@ -38,10 +38,7 @@ abstract class BaseParsingHelper, T : RootNode companion object { @JvmStatic - val defaultNoProcess = Params(false, null, null, "") - - @JvmStatic - val defaultProcess = Params(true, null, null, "") + val default = Params(true, null, null, "") } } @@ -124,7 +121,6 @@ abstract class BaseParsingHelper, T : RootNode ): T { val lversion = if (version == null) defaultVersion else getVersion(version) val handler = lversion.languageVersionHandler - val parser = handler.parser val source = DataSource.forString(sourceCode, fileName) val toString = DataSource.readToString(source, StandardCharsets.UTF_8) // this removed the BOM val task = Parser.ParserTask(lversion, fileName, toString, SemanticErrorReporter.noop()) @@ -132,35 +128,12 @@ abstract class BaseParsingHelper, T : RootNode handler.declareParserTaskProperties(it) it.setProperty(Parser.ParserTask.COMMENT_MARKER, params.suppressMarker) } - val rootNode = rootClass.cast(parser.parse(task)) - if (params.doProcess) { - postProcessing(handler, lversion, rootNode) - } - return rootNode + return doParse(params, task) } - /** - * Select the processing stages that this should run in [postProcessing], - * by default runs everything. - */ - protected open fun selectProcessingStages(handler: LanguageVersionHandler): List> = - handler.processingStages - - /** - * Called only if [Params.doProcess] is true. - */ - protected open fun postProcessing(handler: LanguageVersionHandler, lversion: LanguageVersion, rootNode: T) { - val astAnalysisContext = object : AstAnalysisContext { - override fun getTypeResolutionClassLoader(): ClassLoader = javaClass.classLoader - - override fun getLanguageVersion(): LanguageVersion = lversion - } - - val stages = selectProcessingStages(handler).sortedWith { o1, o2 -> o1.compare(o2) } - - stages.forEach { - it.processAST(rootNode, astAnalysisContext) - } + protected open fun doParse(params: Params, task: Parser.ParserTask): T { + val parser = task.languageVersion.languageVersionHandler.parser + return rootClass.cast(parser.parse(task)) } /** @@ -252,5 +225,12 @@ abstract class BaseParsingHelper, T : RootNode } fun executeRuleOnResource(rule: Rule, resourcePath: String): Report = - executeRule(rule, readResource(resourcePath)) + executeRule(rule, code = readResource(resourcePath)) + + fun executeRuleOnFile(rule: Rule, path: Path): Report = + executeRule( + rule, + code = Files.newBufferedReader(path).readText(), + fileName = path.toString() + ) } diff --git a/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/ModelicaHandler.java b/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/ModelicaHandler.java index 1fe768cdb8..2deb0547f9 100644 --- a/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/ModelicaHandler.java +++ b/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/ModelicaHandler.java @@ -7,16 +7,9 @@ package net.sourceforge.pmd.lang.modelica; import net.sourceforge.pmd.lang.AbstractPmdLanguageVersionHandler; import net.sourceforge.pmd.lang.ast.Parser; import net.sourceforge.pmd.lang.modelica.ast.ModelicaParser; -import net.sourceforge.pmd.lang.modelica.internal.ModelicaProcessingStage; public class ModelicaHandler extends AbstractPmdLanguageVersionHandler { - public ModelicaHandler() { - super(ModelicaProcessingStage.class); - } - - - @Override public Parser getParser() { return new ModelicaParser(); diff --git a/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/ast/ModelicaParser.java b/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/ast/ModelicaParser.java index 3712b7e6e1..2e7d2e0d79 100644 --- a/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/ast/ModelicaParser.java +++ b/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/ast/ModelicaParser.java @@ -4,10 +4,12 @@ package net.sourceforge.pmd.lang.modelica.ast; +import net.sourceforge.pmd.benchmark.TimeTracker; import net.sourceforge.pmd.lang.ast.CharStream; import net.sourceforge.pmd.lang.ast.ParseException; import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccTokenDocument; import net.sourceforge.pmd.lang.ast.impl.javacc.JjtreeParserAdapter; +import net.sourceforge.pmd.lang.modelica.resolver.ModelicaSymbolFacade; public class ModelicaParser extends JjtreeParserAdapter { @@ -19,7 +21,9 @@ public class ModelicaParser extends JjtreeParserAdapter { @Override protected ASTStoredDefinition parseImpl(CharStream cs, ParserTask task) throws ParseException { - return new ModelicaParserImpl(cs).StoredDefinition().makeTaskInfo(task); + ASTStoredDefinition root = new ModelicaParserImpl(cs).StoredDefinition().makeTaskInfo(task); + TimeTracker.bench("Modelica symbols", () -> ModelicaSymbolFacade.process(root)); + return root; } } diff --git a/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/internal/ModelicaProcessingStage.java b/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/internal/ModelicaProcessingStage.java deleted file mode 100644 index 30f6266a59..0000000000 --- a/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/internal/ModelicaProcessingStage.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.modelica.internal; - -import net.sourceforge.pmd.lang.Language; -import net.sourceforge.pmd.lang.LanguageRegistry; -import net.sourceforge.pmd.lang.ast.AstAnalysisContext; -import net.sourceforge.pmd.lang.ast.AstProcessingStage; -import net.sourceforge.pmd.lang.ast.RootNode; -import net.sourceforge.pmd.lang.modelica.ModelicaLanguageModule; -import net.sourceforge.pmd.lang.modelica.ast.ASTStoredDefinition; -import net.sourceforge.pmd.lang.modelica.resolver.ModelicaSymbolFacade; - -/** - * @author ClΓ©ment Fournier - */ -public enum ModelicaProcessingStage implements AstProcessingStage { - SYMBOL_RESOLUTION("Symbol resolution") { - @Override - public void processAST(RootNode rootNode, AstAnalysisContext configuration) { - new ModelicaSymbolFacade().initializeWith((ASTStoredDefinition) rootNode); - } - }; - - private final String displayName; - - ModelicaProcessingStage(String displayName) { - this.displayName = displayName; - } - - - @Override - public Language getLanguage() { - return LanguageRegistry.getLanguage(ModelicaLanguageModule.NAME); - } - - @Override - public String getDisplayName() { - return displayName; - } -} diff --git a/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/resolver/ModelicaSymbolFacade.java b/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/resolver/ModelicaSymbolFacade.java index cf91c2edda..6786d3193f 100644 --- a/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/resolver/ModelicaSymbolFacade.java +++ b/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/resolver/ModelicaSymbolFacade.java @@ -4,10 +4,17 @@ package net.sourceforge.pmd.lang.modelica.resolver; +import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.modelica.ast.ASTStoredDefinition; -public class ModelicaSymbolFacade { - public void initializeWith(ASTStoredDefinition node) { +@InternalApi +public final class ModelicaSymbolFacade { + + private ModelicaSymbolFacade() { + // util class + } + + public static void process(ASTStoredDefinition node) { ScopeAndDeclarationFinder sc = new ScopeAndDeclarationFinder(); node.acceptVisitor(sc, null); } diff --git a/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/resolver/ScopeAndDeclarationFinder.java b/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/resolver/ScopeAndDeclarationFinder.java index 66a47e3cf9..fb3bceb9a9 100644 --- a/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/resolver/ScopeAndDeclarationFinder.java +++ b/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/resolver/ScopeAndDeclarationFinder.java @@ -14,7 +14,7 @@ import net.sourceforge.pmd.lang.modelica.ast.InternalModelicaNodeApi; import net.sourceforge.pmd.lang.modelica.ast.ModelicaNode; import net.sourceforge.pmd.lang.modelica.ast.ModelicaParserVisitorAdapter; -public class ScopeAndDeclarationFinder extends ModelicaParserVisitorAdapter { +class ScopeAndDeclarationFinder extends ModelicaParserVisitorAdapter { private final Deque scopes = new ArrayDeque<>(); ScopeAndDeclarationFinder() { diff --git a/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/rule/AbstractModelicaRule.java b/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/rule/AbstractModelicaRule.java index 417cd767fb..5950b7fe9c 100644 --- a/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/rule/AbstractModelicaRule.java +++ b/pmd-modelica/src/main/java/net/sourceforge/pmd/lang/modelica/rule/AbstractModelicaRule.java @@ -6,11 +6,9 @@ package net.sourceforge.pmd.lang.modelica.rule; import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.lang.LanguageRegistry; -import net.sourceforge.pmd.lang.ast.AstProcessingStage; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.modelica.ModelicaLanguageModule; import net.sourceforge.pmd.lang.modelica.ast.ModelicaParserVisitor; -import net.sourceforge.pmd.lang.modelica.internal.ModelicaProcessingStage; import net.sourceforge.pmd.lang.rule.AbstractRule; /** @@ -26,11 +24,4 @@ public abstract class AbstractModelicaRule extends AbstractRule implements Model target.acceptVisitor(this, ctx); } - @Override - public boolean dependsOn(AstProcessingStage stage) { - if (!(stage instanceof ModelicaProcessingStage)) { - throw new IllegalArgumentException("Processing stage wasn't a Modelica one: " + stage); - } - return true; - } } diff --git a/pmd-modelica/src/test/java/net/sourceforge/pmd/lang/modelica/ModelicaParsingHelper.java b/pmd-modelica/src/test/java/net/sourceforge/pmd/lang/modelica/ModelicaParsingHelper.java index de4c39c3bd..ddc3cde1ad 100644 --- a/pmd-modelica/src/test/java/net/sourceforge/pmd/lang/modelica/ModelicaParsingHelper.java +++ b/pmd-modelica/src/test/java/net/sourceforge/pmd/lang/modelica/ModelicaParsingHelper.java @@ -11,7 +11,7 @@ import net.sourceforge.pmd.lang.modelica.ast.ASTStoredDefinition; public class ModelicaParsingHelper extends BaseParsingHelper { /** This runs all processing stages when parsing. */ - public static final ModelicaParsingHelper DEFAULT = new ModelicaParsingHelper(Params.getDefaultProcess()); + public static final ModelicaParsingHelper DEFAULT = new ModelicaParsingHelper(Params.getDefault()); private ModelicaParsingHelper(Params params) { super(ModelicaLanguageModule.NAME, ASTStoredDefinition.class, params); diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/PLSQLHandler.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/PLSQLHandler.java index a403100e40..5c6b7295e3 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/PLSQLHandler.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/PLSQLHandler.java @@ -16,11 +16,6 @@ import net.sourceforge.pmd.lang.plsql.ast.PLSQLParser; */ public class PLSQLHandler extends AbstractPmdLanguageVersionHandler { - - public PLSQLHandler() { - super(PlsqlProcessingStage.class); - } - @Override public Parser getParser() { return new PLSQLParser(); diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/PlsqlProcessingStage.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/PlsqlProcessingStage.java deleted file mode 100644 index 53f496db42..0000000000 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/PlsqlProcessingStage.java +++ /dev/null @@ -1,70 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.plsql; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import net.sourceforge.pmd.annotation.Experimental; -import net.sourceforge.pmd.lang.Language; -import net.sourceforge.pmd.lang.LanguageRegistry; -import net.sourceforge.pmd.lang.ast.AstAnalysisContext; -import net.sourceforge.pmd.lang.ast.AstProcessingStage; -import net.sourceforge.pmd.lang.ast.RootNode; -import net.sourceforge.pmd.lang.plsql.ast.ASTInput; -import net.sourceforge.pmd.lang.plsql.symboltable.SymbolFacade; - - -/** - * PL-SQL AST processing stages. - * - * @author ClΓ©ment Fournier - * @since 7.0.0 - */ -@Experimental -public enum PlsqlProcessingStage implements AstProcessingStage { - - /** - * Symbol table analysis. - */ - SYMBOL_RESOLUTION("Symbol table") { - @Override - public void processAST(RootNode rootNode, AstAnalysisContext configuration) { - new SymbolFacade().initializeWith((ASTInput) rootNode); - } - }; - - - private final String displayName; - private final List dependencies; - - - PlsqlProcessingStage(String displayName, PlsqlProcessingStage... dependencies) { - this.displayName = displayName; - this.dependencies = Collections.unmodifiableList(Arrays.asList(dependencies)); - } - - - @Override - public Language getLanguage() { - return LanguageRegistry.findLanguageByTerseName("plsql"); - } - - - @Override - public List getDependencies() { - return dependencies; - } - - - @Override - public String getDisplayName() { - return displayName; - } - - -} - diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/PLSQLParser.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/PLSQLParser.java index c15ab94e8d..611d7d077a 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/PLSQLParser.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/PLSQLParser.java @@ -6,10 +6,12 @@ package net.sourceforge.pmd.lang.plsql.ast; import org.checkerframework.checker.nullness.qual.Nullable; +import net.sourceforge.pmd.benchmark.TimeTracker; import net.sourceforge.pmd.lang.ast.CharStream; import net.sourceforge.pmd.lang.ast.ParseException; import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccTokenDocument; import net.sourceforge.pmd.lang.ast.impl.javacc.JjtreeParserAdapter; +import net.sourceforge.pmd.lang.plsql.symboltable.SymbolFacade; public class PLSQLParser extends JjtreeParserAdapter { @@ -25,7 +27,9 @@ public class PLSQLParser extends JjtreeParserAdapter { @Override protected ASTInput parseImpl(CharStream cs, ParserTask task) throws ParseException { - return new PLSQLParserImpl(cs).Input().addTaskInfo(task); + ASTInput root = new PLSQLParserImpl(cs).Input().addTaskInfo(task); + TimeTracker.bench("PLSQL symbols", () -> SymbolFacade.process(root)); + return root; } } diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/AbstractPLSQLRule.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/AbstractPLSQLRule.java index 7d5c66d05a..97e226c28e 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/AbstractPLSQLRule.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/AbstractPLSQLRule.java @@ -7,10 +7,8 @@ package net.sourceforge.pmd.lang.plsql.rule; import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.lang.LanguageRegistry; -import net.sourceforge.pmd.lang.ast.AstProcessingStage; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.plsql.PLSQLLanguageModule; -import net.sourceforge.pmd.lang.plsql.PlsqlProcessingStage; import net.sourceforge.pmd.lang.plsql.ast.ASTInput; import net.sourceforge.pmd.lang.plsql.ast.ASTPackageBody; import net.sourceforge.pmd.lang.plsql.ast.ASTPackageSpecification; @@ -82,14 +80,6 @@ public abstract class AbstractPLSQLRule extends AbstractRule implements PLSQLPar return false; } - @Override - public boolean dependsOn(AstProcessingStage stage) { - if (!(stage instanceof PlsqlProcessingStage)) { - throw new IllegalArgumentException("Processing stage wasn't a " + PLSQLLanguageModule.NAME + " one: " + stage); - } - return true; - } - /* * Treat all Executable Code */ diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/SymbolFacade.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/SymbolFacade.java index 88f645dd9b..3cda6ef332 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/SymbolFacade.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/SymbolFacade.java @@ -6,8 +6,13 @@ package net.sourceforge.pmd.lang.plsql.symboltable; import net.sourceforge.pmd.lang.plsql.ast.ASTInput; -public class SymbolFacade { - public void initializeWith(ASTInput node) { +public final class SymbolFacade { + + private SymbolFacade() { + + } + + public static void process(ASTInput node) { ScopeAndDeclarationFinder sc = new ScopeAndDeclarationFinder(); node.acceptVisitor(sc, null); OccurrenceFinder of = new OccurrenceFinder(); diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/AbstractPLSQLParserTst.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/AbstractPLSQLParserTst.java index 9d49470e56..058bcb2125 100644 --- a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/AbstractPLSQLParserTst.java +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/AbstractPLSQLParserTst.java @@ -6,6 +6,6 @@ package net.sourceforge.pmd.lang.plsql; public abstract class AbstractPLSQLParserTst { - protected final PlsqlParsingHelper plsql = PlsqlParsingHelper.WITH_PROCESSING.withResourceContext(getClass()); + protected final PlsqlParsingHelper plsql = PlsqlParsingHelper.DEFAULT.withResourceContext(getClass()); } diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/PlsqlParsingHelper.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/PlsqlParsingHelper.java index c340aabaad..57400761e0 100644 --- a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/PlsqlParsingHelper.java +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/PlsqlParsingHelper.java @@ -9,10 +9,8 @@ import net.sourceforge.pmd.lang.plsql.ast.ASTInput; public class PlsqlParsingHelper extends BaseParsingHelper { - /** This just runs the parser and no processing stages. */ - public static final PlsqlParsingHelper JUST_PARSE = new PlsqlParsingHelper(Params.getDefaultNoProcess()); /** This runs all processing stages when parsing. */ - public static final PlsqlParsingHelper WITH_PROCESSING = new PlsqlParsingHelper(Params.getDefaultProcess()); + public static final PlsqlParsingHelper DEFAULT = new PlsqlParsingHelper(Params.getDefault()); private PlsqlParsingHelper(Params params) { super(PLSQLLanguageModule.NAME, ASTInput.class, params); diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/PlsqlTreeDumpTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/PlsqlTreeDumpTest.java index 3a4c1cc8f8..9e2e7c6699 100644 --- a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/PlsqlTreeDumpTest.java +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/PlsqlTreeDumpTest.java @@ -19,7 +19,7 @@ public class PlsqlTreeDumpTest extends BaseTreeDumpTest { @Override public BaseParsingHelper getParser() { - return PlsqlParsingHelper.WITH_PROCESSING.withResourceContext(getClass()); + return PlsqlParsingHelper.DEFAULT.withResourceContext(getClass()); } @Test diff --git a/pmd-scala-modules/pmd-scala-common/src/test/java/net/sourceforge/pmd/lang/scala/ast/ScalaParsingHelper.java b/pmd-scala-modules/pmd-scala-common/src/test/java/net/sourceforge/pmd/lang/scala/ast/ScalaParsingHelper.java index 7c4e8d4978..f2387cc649 100644 --- a/pmd-scala-modules/pmd-scala-common/src/test/java/net/sourceforge/pmd/lang/scala/ast/ScalaParsingHelper.java +++ b/pmd-scala-modules/pmd-scala-common/src/test/java/net/sourceforge/pmd/lang/scala/ast/ScalaParsingHelper.java @@ -9,7 +9,7 @@ import net.sourceforge.pmd.lang.scala.ScalaLanguageModule; public final class ScalaParsingHelper extends BaseParsingHelper { - public static final ScalaParsingHelper DEFAULT = new ScalaParsingHelper(Params.getDefaultProcess()); + public static final ScalaParsingHelper DEFAULT = new ScalaParsingHelper(Params.getDefault()); private ScalaParsingHelper(Params params) { super(ScalaLanguageModule.NAME, ASTSource.class, params); diff --git a/pmd-swift/src/test/java/net/sourceforge/pmd/lang/swift/ast/SwiftParsingHelper.java b/pmd-swift/src/test/java/net/sourceforge/pmd/lang/swift/ast/SwiftParsingHelper.java index ebfb36b62d..bc1828ac0f 100644 --- a/pmd-swift/src/test/java/net/sourceforge/pmd/lang/swift/ast/SwiftParsingHelper.java +++ b/pmd-swift/src/test/java/net/sourceforge/pmd/lang/swift/ast/SwiftParsingHelper.java @@ -15,7 +15,7 @@ import net.sourceforge.pmd.lang.swift.ast.SwiftParser.SwTopLevel; */ public class SwiftParsingHelper extends BaseParsingHelper { - public static final SwiftParsingHelper DEFAULT = new SwiftParsingHelper(Params.getDefaultNoProcess()); + public static final SwiftParsingHelper DEFAULT = new SwiftParsingHelper(Params.getDefault()); public SwiftParsingHelper(@NotNull Params params) { diff --git a/pmd-test/src/main/java/net/sourceforge/pmd/cli/BaseCLITest.java b/pmd-test/src/main/java/net/sourceforge/pmd/cli/BaseCLITest.java index 1c8645fc78..54e9739159 100644 --- a/pmd-test/src/main/java/net/sourceforge/pmd/cli/BaseCLITest.java +++ b/pmd-test/src/main/java/net/sourceforge/pmd/cli/BaseCLITest.java @@ -4,20 +4,28 @@ package net.sourceforge.pmd.cli; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.nio.file.Files; +import java.util.regex.Pattern; import org.apache.commons.io.IOUtils; +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.PMD.StatusCode; +import net.sourceforge.pmd.internal.util.AssertionUtil; /** * @author Romain Pelisse <belaran@gmail.com> @@ -72,25 +80,79 @@ public abstract class BaseCLITest { } } + /** + * @deprecated Use {@link #runTest(String...)}, note that + * it returns the log while this returns the name of a file containing the log. + */ + @Deprecated protected String runTest(String[] args, String testname) { return runTest(args, testname, 0); } + /** + * @deprecated Use {@link #runTest(StatusCode, String...)}, note that + * it returns the log while this returns the name of a file containing the log. + */ + @Deprecated protected String runTest(String[] args, String testname, int expectedExitCode) { String filename = TEST_OUPUT_DIRECTORY + testname + ".txt"; long start = System.currentTimeMillis(); createTestOutputFile(filename); System.out.println("Start running test " + testname); - runPMDWith(args); - checkStatusCode(expectedExitCode); + StatusCode statusCode = PMD.runPmd(args); + assertEquals(expectedExitCode, statusCode.toInt()); System.out.println("Test finished successfully after " + (System.currentTimeMillis() - start) + "ms."); return filename; } + /** + * Returns the log output. + */ + protected String runTest(String... args) { + return runTest(StatusCode.OK, args); + } + + /** + * Returns the log output. + * + * @deprecated Use {@link #runTest(StatusCode, String...)} + */ + @Deprecated + protected String runTest(int expectedExitCode, String... args) { + switch (expectedExitCode) { + case 0: + return runTest(StatusCode.OK, args); + case 1: + return runTest(StatusCode.ERROR, args); + case 4: + return runTest(StatusCode.VIOLATIONS_FOUND, args); + default: + throw AssertionUtil.shouldNotReachHere("unknown status code " + expectedExitCode); + } + } + + protected String runTest(StatusCode expectedExitCode, String... args) { + ByteArrayOutputStream console = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(console); + System.setOut(out); + System.setErr(out); + StatusCode statusCode = PMD.runPmd(args); + assertEquals(expectedExitCode, statusCode); + return console.toString(); + } + + /** + * @deprecated Use {@link #runTest(StatusCode, String...)} + */ + @Deprecated protected void runPMDWith(String[] args) { PMD.main(args); } + /** + * @deprecated Use {@link #runTest(StatusCode, String...)} instead of checking the return code manually + */ + @Deprecated protected void checkStatusCode(int expectedExitCode) { int statusCode = getStatusCode(); if (statusCode != expectedExitCode) { @@ -98,7 +160,28 @@ public abstract class BaseCLITest { } } + /** + * @deprecated Use {@link #runTest(StatusCode, String...)} instead + * of checking the return code manually + */ + @Deprecated protected int getStatusCode() { return Integer.parseInt(System.getProperty(PMDCommandLineInterface.STATUS_CODE_PROPERTY)); } + + public static Matcher containsPattern(final String regex) { + return new BaseMatcher() { + final Pattern pattern = Pattern.compile(regex); + + @Override + public void describeTo(Description description) { + description.appendText("a string containing the pattern '" + this.pattern + "'"); + } + + @Override + public boolean matches(Object o) { + return o instanceof String && pattern.matcher((String) o).find(); + } + }; + } } diff --git a/pmd-test/src/main/java/net/sourceforge/pmd/testframework/RuleTst.java b/pmd-test/src/main/java/net/sourceforge/pmd/testframework/RuleTst.java index e3a35697ac..e9ed0fe288 100644 --- a/pmd-test/src/main/java/net/sourceforge/pmd/testframework/RuleTst.java +++ b/pmd-test/src/main/java/net/sourceforge/pmd/testframework/RuleTst.java @@ -263,7 +263,7 @@ public abstract class RuleTst { if (isUseAuxClasspath) { // configure the "auxclasspath" option for unit testing - configuration.prependClasspath("."); + configuration.prependAuxClasspath("."); } else { // simple class loader, that doesn't delegate to parent. // this allows us in the tests to simulate PMD run without diff --git a/pmd-visualforce/src/test/java/net/sourceforge/pmd/lang/vf/ast/VfParsingHelper.java b/pmd-visualforce/src/test/java/net/sourceforge/pmd/lang/vf/ast/VfParsingHelper.java index bcb97098f5..bd9292b8bd 100644 --- a/pmd-visualforce/src/test/java/net/sourceforge/pmd/lang/vf/ast/VfParsingHelper.java +++ b/pmd-visualforce/src/test/java/net/sourceforge/pmd/lang/vf/ast/VfParsingHelper.java @@ -9,7 +9,7 @@ import net.sourceforge.pmd.lang.vf.VfLanguageModule; public final class VfParsingHelper extends BaseParsingHelper { - public static final VfParsingHelper DEFAULT = new VfParsingHelper(Params.getDefaultProcess()); + public static final VfParsingHelper DEFAULT = new VfParsingHelper(Params.getDefault()); private VfParsingHelper(Params params) { super(VfLanguageModule.NAME, ASTCompilationUnit.class, params); diff --git a/pmd-visualforce/src/test/java/net/sourceforge/pmd/lang/vf/rule/security/VfUnescapeElTest.java b/pmd-visualforce/src/test/java/net/sourceforge/pmd/lang/vf/rule/security/VfUnescapeElTest.java index 3cd0019f9b..1f76408722 100644 --- a/pmd-visualforce/src/test/java/net/sourceforge/pmd/lang/vf/rule/security/VfUnescapeElTest.java +++ b/pmd-visualforce/src/test/java/net/sourceforge/pmd/lang/vf/rule/security/VfUnescapeElTest.java @@ -4,28 +4,20 @@ package net.sourceforge.pmd.lang.vf.rule.security; -import static net.sourceforge.pmd.util.CollectionUtil.listOf; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Collections; import java.util.List; import org.junit.Test; -import net.sourceforge.pmd.PMD; -import net.sourceforge.pmd.PMDConfiguration; import net.sourceforge.pmd.Report; import net.sourceforge.pmd.Rule; -import net.sourceforge.pmd.RuleSet; import net.sourceforge.pmd.RuleViolation; -import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.vf.VFTestUtils; import net.sourceforge.pmd.lang.vf.ast.VfParsingHelper; import net.sourceforge.pmd.testframework.PmdRuleTst; -import net.sourceforge.pmd.util.datasource.FileDataSource; public class VfUnescapeElTest extends PmdRuleTst { public static final String EXPECTED_RULE_MESSAGE = "Avoid unescaped user controlled content in EL"; @@ -34,7 +26,7 @@ public class VfUnescapeElTest extends PmdRuleTst { * Verify that CustomFields stored in sfdx project format are correctly parsed */ @Test - public void testSfdxCustomFields() throws Exception { + public void testSfdxCustomFields() { Path vfPagePath = VFTestUtils.getMetadataPath(this, VFTestUtils.MetadataFormat.SFDX, VFTestUtils.MetadataType.Vf) .resolve("StandardAccount.page"); @@ -47,7 +39,7 @@ public class VfUnescapeElTest extends PmdRuleTst { RuleViolation ruleViolation = ruleViolations.get(i); assertEquals(EXPECTED_RULE_MESSAGE, ruleViolation.getDescription()); int expectedLineNumber = firstLineWithErrors + i; - if ((ruleViolations.size() + firstLineWithErrors - 1) == expectedLineNumber) { + if (ruleViolations.size() + firstLineWithErrors - 1 == expectedLineNumber) { // The last line has two errors on the same page expectedLineNumber = expectedLineNumber - 1; } @@ -59,7 +51,7 @@ public class VfUnescapeElTest extends PmdRuleTst { * Verify that CustomFields stored in mdapi format are correctly parsed */ @Test - public void testMdapiCustomFields() throws Exception { + public void testMdapiCustomFields() { Path vfPagePath = VFTestUtils.getMetadataPath(this, VFTestUtils.MetadataFormat.MDAPI, VFTestUtils.MetadataType.Vf).resolve("StandardAccount.page"); Report report = runRule(vfPagePath); @@ -77,7 +69,7 @@ public class VfUnescapeElTest extends PmdRuleTst { * Tests a page with a single Apex controller */ @Test - public void testApexController() throws Exception { + public void testApexController() { Path vfPagePath = VFTestUtils.getMetadataPath(this, VFTestUtils.MetadataFormat.SFDX, VFTestUtils.MetadataType.Vf).resolve("ApexController.page"); Report report = runRule(vfPagePath); @@ -96,7 +88,7 @@ public class VfUnescapeElTest extends PmdRuleTst { * Tests a page with a standard controller and two Apex extensions */ @Test - public void testExtensions() throws Exception { + public void testExtensions() { Path vfPagePath = VFTestUtils.getMetadataPath(this, VFTestUtils.MetadataFormat.SFDX, VFTestUtils.MetadataType.Vf) .resolve(Paths.get("StandardAccountWithExtensions.page")); @@ -114,32 +106,8 @@ public class VfUnescapeElTest extends PmdRuleTst { /** * Runs a rule against a Visualforce page on the file system. */ - private Report runRule(Path vfPagePath) throws Exception { - Node node = VfParsingHelper.DEFAULT.parseFile(vfPagePath); - assertNotNull(node); - - PMDConfiguration config = new PMDConfiguration(); - config.setIgnoreIncrementalAnalysis(true); - // simple class loader, that doesn't delegate to parent. - // this allows us in the tests to simulate PMD run without - // auxclasspath, not even the classes from the test dependencies - // will be found. - config.setClassLoader(new ClassLoader() { - @Override - protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { - if (name.startsWith("java.") || name.startsWith("javax.")) { - return super.loadClass(name, resolve); - } - throw new ClassNotFoundException(name); - } - }); + private Report runRule(Path vfPagePath) { Rule rule = findRule("category/vf/security.xml", "VfUnescapeEl"); - - return PMD.processFiles( - config, - listOf(RuleSet.forSingleRule(rule)), - listOf(new FileDataSource(vfPagePath.toAbsolutePath().toFile())), - Collections.emptyList() - ); + return VfParsingHelper.DEFAULT.executeRuleOnFile(rule, vfPagePath); } } diff --git a/pmd-vm/src/test/java/net/sourceforge/pmd/lang/vm/ast/VmParsingHelper.java b/pmd-vm/src/test/java/net/sourceforge/pmd/lang/vm/ast/VmParsingHelper.java index 00a2319c63..38365e7270 100644 --- a/pmd-vm/src/test/java/net/sourceforge/pmd/lang/vm/ast/VmParsingHelper.java +++ b/pmd-vm/src/test/java/net/sourceforge/pmd/lang/vm/ast/VmParsingHelper.java @@ -9,7 +9,7 @@ import net.sourceforge.pmd.lang.vm.VmLanguageModule; public final class VmParsingHelper extends BaseParsingHelper { - public static final VmParsingHelper DEFAULT = new VmParsingHelper(Params.getDefaultProcess()); + public static final VmParsingHelper DEFAULT = new VmParsingHelper(Params.getDefault()); private VmParsingHelper(Params params) { super(VmLanguageModule.NAME, ASTTemplate.class, params); diff --git a/pmd-xml/src/test/java/net/sourceforge/pmd/lang/xml/XmlCliTest.java b/pmd-xml/src/test/java/net/sourceforge/pmd/lang/xml/XmlCliTest.java index ee5722798b..14e717b237 100644 --- a/pmd-xml/src/test/java/net/sourceforge/pmd/lang/xml/XmlCliTest.java +++ b/pmd-xml/src/test/java/net/sourceforge/pmd/lang/xml/XmlCliTest.java @@ -4,18 +4,17 @@ package net.sourceforge.pmd.lang.xml; -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; +import static net.sourceforge.pmd.util.CollectionUtil.listOf; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.junit.Assert; import org.junit.Test; +import net.sourceforge.pmd.PMD.StatusCode; import net.sourceforge.pmd.cli.BaseCLITest; public class XmlCliTest extends BaseCLITest { @@ -23,44 +22,38 @@ public class XmlCliTest extends BaseCLITest { private static final String RULE_MESSAGE = "A tags are not allowed"; private String[] createArgs(String directory, String... args) { - List arguments = new ArrayList<>(); - arguments.add("-f"); - arguments.add("text"); - arguments.add("-no-cache"); - arguments.add("-R"); - arguments.add(BASE_DIR + "/ruleset.xml"); - arguments.add("-d"); - arguments.add(BASE_DIR + directory); + List arguments = new ArrayList<>(listOf( + "-f", + "text", + "-no-cache", + "-R", + BASE_DIR + "/ruleset.xml", + "-d", + BASE_DIR + directory + )); arguments.addAll(Arrays.asList(args)); return arguments.toArray(new String[0]); } @Test public void analyzeSingleXmlWithoutForceLanguage() { - String resultFilename = runTest(createArgs("/src/file1.ext"), "analyzeSingleXmlWithoutForceLanguage", 0); - assertRuleMessage(0, resultFilename); + String log = runTest(StatusCode.OK, createArgs("/src/file1.ext")); + assertRuleMessage(0, log); } @Test public void analyzeSingleXmlWithForceLanguage() { - String resultFilename = runTest(createArgs("/src/file1.ext", "-force-language", "xml"), - "analyzeSingleXmlWithForceLanguage", 4); - assertRuleMessage(1, resultFilename); + String log = runTest(StatusCode.VIOLATIONS_FOUND, createArgs("/src/file1.ext", "-force-language", "xml")); + assertRuleMessage(1, log); } @Test public void analyzeDirectoryWithForceLanguage() { - String resultFilename = runTest(createArgs("/src/", "-force-language", "xml"), - "analyzeDirectoryWithForceLanguage", 4); - assertRuleMessage(3, resultFilename); + String log = runTest(StatusCode.VIOLATIONS_FOUND, createArgs("/src/", "-force-language", "xml")); + assertRuleMessage(3, log); } - private void assertRuleMessage(int expectedCount, String resultFilename) { - try { - String result = FileUtils.readFileToString(new File(resultFilename), StandardCharsets.UTF_8); - Assert.assertEquals(expectedCount, StringUtils.countMatches(result, RULE_MESSAGE)); - } catch (IOException e) { - throw new AssertionError(e); - } + private void assertRuleMessage(int expectedCount, String log) { + Assert.assertEquals(expectedCount, StringUtils.countMatches(log, RULE_MESSAGE)); } } diff --git a/pmd-xml/src/test/java/net/sourceforge/pmd/lang/xml/XmlParsingHelper.java b/pmd-xml/src/test/java/net/sourceforge/pmd/lang/xml/XmlParsingHelper.java index 8faa47e547..62974c6509 100644 --- a/pmd-xml/src/test/java/net/sourceforge/pmd/lang/xml/XmlParsingHelper.java +++ b/pmd-xml/src/test/java/net/sourceforge/pmd/lang/xml/XmlParsingHelper.java @@ -14,9 +14,9 @@ import net.sourceforge.pmd.lang.xml.ast.internal.XmlParserImpl.RootXmlNode; */ public final class XmlParsingHelper extends BaseParsingHelper { - public static final XmlParsingHelper XML = new XmlParsingHelper(XmlLanguageModule.NAME, Params.getDefaultProcess()); - public static final XmlParsingHelper WSDL = new XmlParsingHelper(WsdlLanguageModule.NAME, Params.getDefaultProcess()); - public static final XmlParsingHelper POM = new XmlParsingHelper(PomLanguageModule.NAME, Params.getDefaultProcess()); + public static final XmlParsingHelper XML = new XmlParsingHelper(XmlLanguageModule.NAME, Params.getDefault()); + public static final XmlParsingHelper WSDL = new XmlParsingHelper(WsdlLanguageModule.NAME, Params.getDefault()); + public static final XmlParsingHelper POM = new XmlParsingHelper(PomLanguageModule.NAME, Params.getDefault()); private XmlParsingHelper(String langName, Params params) {