From b5d0ffb02f9af23bb508f2491a5ff97be6eb261f Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 9 Nov 2023 20:02:23 +0100 Subject: [PATCH 1/8] New pmd-compat6 module This makes it possible to use PMD7 with maven-pmd-plugin --- pmd-compat6/README.md | 35 ++ pmd-compat6/pom.xml | 70 +++ .../src/it/cpd-for-java/invoker.properties | 2 + pmd-compat6/src/it/cpd-for-java/pom.xml | 66 ++ .../src/main/java/org/example/ClassA.java | 10 + .../src/main/java/org/example/ClassB.java | 10 + pmd-compat6/src/it/cpd-for-java/verify.bsh | 35 ++ .../it/cpd-for-javascript/invoker.properties | 2 + pmd-compat6/src/it/cpd-for-javascript/pom.xml | 74 +++ .../src/main/js/globalVariable.js | 7 + .../src/main/js/globalVariable2.js | 7 + .../src/it/cpd-for-javascript/verify.bsh | 35 ++ .../src/it/cpd-for-jsp/invoker.properties | 2 + pmd-compat6/src/it/cpd-for-jsp/pom.xml | 74 +++ .../src/main/jsp/classAttribute.jsp | 3 + .../src/main/jsp/classAttribute2.jsp | 3 + pmd-compat6/src/it/cpd-for-jsp/verify.bsh | 35 ++ .../src/it/pmd-for-java/invoker.properties | 2 + pmd-compat6/src/it/pmd-for-java/pom.xml | 66 ++ .../src/main/java/org/example/Main.java | 8 + pmd-compat6/src/it/pmd-for-java/verify.bsh | 32 + .../it/pmd-for-javascript/invoker.properties | 2 + pmd-compat6/src/it/pmd-for-javascript/pom.xml | 74 +++ .../src/main/js/globalVariable.js | 7 + .../src/it/pmd-for-javascript/verify.bsh | 32 + .../src/it/pmd-for-jsp/invoker.properties | 2 + pmd-compat6/src/it/pmd-for-jsp/pom.xml | 74 +++ .../src/main/jsp/classAttribute.jsp | 3 + pmd-compat6/src/it/pmd-for-jsp/verify.bsh | 32 + pmd-compat6/src/it/settings.xml | 35 ++ .../net/sourceforge/pmd/PMDConfiguration.java | 588 ++++++++++++++++++ .../main/java/net/sourceforge/pmd/Report.java | 432 +++++++++++++ .../net/sourceforge/pmd/RuleViolation.java | 196 ++++++ .../sourceforge/pmd/cpd/AbstractLanguage.java | 60 ++ .../java/net/sourceforge/pmd/cpd/CPD.java | 97 +++ .../sourceforge/pmd/cpd/CPDConfiguration.java | 341 ++++++++++ .../pmd/cpd/EcmascriptLanguage.java | 17 + .../pmd/cpd/EcmascriptTokenizer.java | 8 + .../net/sourceforge/pmd/cpd/JSPLanguage.java | 13 + .../net/sourceforge/pmd/cpd/JSPTokenizer.java | 8 + .../net/sourceforge/pmd/cpd/JavaLanguage.java | 26 + .../sourceforge/pmd/cpd/JavaTokenizer.java | 33 + .../net/sourceforge/pmd/cpd/Language.java | 25 + .../java/net/sourceforge/pmd/cpd/Mark.java | 104 ++++ .../net/sourceforge/pmd/cpd/XMLRenderer.java | 193 ++++++ .../pmd/cpd/renderer/CPDRenderer.java | 21 + .../util/filter/AbstractCompoundFilter.java | 65 ++ .../util/filter/AbstractDelegateFilter.java | 48 ++ .../pmd/util/filter/AndFilter.java | 43 ++ .../pmd/util/filter/DirectoryFilter.java | 31 + .../pmd/util/filter/FileExtensionFilter.java | 54 ++ .../sourceforge/pmd/util/filter/Filter.java | 20 + .../sourceforge/pmd/util/filter/Filters.java | 245 ++++++++ .../pmd/util/filter/NotFilter.java | 35 ++ .../sourceforge/pmd/util/filter/OrFilter.java | 43 ++ .../pmd/util/filter/RegexStringFilter.java | 98 +++ pom.xml | 12 + 57 files changed, 3695 insertions(+) create mode 100644 pmd-compat6/README.md create mode 100644 pmd-compat6/pom.xml create mode 100644 pmd-compat6/src/it/cpd-for-java/invoker.properties create mode 100644 pmd-compat6/src/it/cpd-for-java/pom.xml create mode 100644 pmd-compat6/src/it/cpd-for-java/src/main/java/org/example/ClassA.java create mode 100644 pmd-compat6/src/it/cpd-for-java/src/main/java/org/example/ClassB.java create mode 100644 pmd-compat6/src/it/cpd-for-java/verify.bsh create mode 100644 pmd-compat6/src/it/cpd-for-javascript/invoker.properties create mode 100644 pmd-compat6/src/it/cpd-for-javascript/pom.xml create mode 100644 pmd-compat6/src/it/cpd-for-javascript/src/main/js/globalVariable.js create mode 100644 pmd-compat6/src/it/cpd-for-javascript/src/main/js/globalVariable2.js create mode 100644 pmd-compat6/src/it/cpd-for-javascript/verify.bsh create mode 100644 pmd-compat6/src/it/cpd-for-jsp/invoker.properties create mode 100644 pmd-compat6/src/it/cpd-for-jsp/pom.xml create mode 100644 pmd-compat6/src/it/cpd-for-jsp/src/main/jsp/classAttribute.jsp create mode 100644 pmd-compat6/src/it/cpd-for-jsp/src/main/jsp/classAttribute2.jsp create mode 100644 pmd-compat6/src/it/cpd-for-jsp/verify.bsh create mode 100644 pmd-compat6/src/it/pmd-for-java/invoker.properties create mode 100644 pmd-compat6/src/it/pmd-for-java/pom.xml create mode 100644 pmd-compat6/src/it/pmd-for-java/src/main/java/org/example/Main.java create mode 100644 pmd-compat6/src/it/pmd-for-java/verify.bsh create mode 100644 pmd-compat6/src/it/pmd-for-javascript/invoker.properties create mode 100644 pmd-compat6/src/it/pmd-for-javascript/pom.xml create mode 100644 pmd-compat6/src/it/pmd-for-javascript/src/main/js/globalVariable.js create mode 100644 pmd-compat6/src/it/pmd-for-javascript/verify.bsh create mode 100644 pmd-compat6/src/it/pmd-for-jsp/invoker.properties create mode 100644 pmd-compat6/src/it/pmd-for-jsp/pom.xml create mode 100644 pmd-compat6/src/it/pmd-for-jsp/src/main/jsp/classAttribute.jsp create mode 100644 pmd-compat6/src/it/pmd-for-jsp/verify.bsh create mode 100644 pmd-compat6/src/it/settings.xml create mode 100644 pmd-compat6/src/main/java/net/sourceforge/pmd/PMDConfiguration.java create mode 100644 pmd-compat6/src/main/java/net/sourceforge/pmd/Report.java create mode 100644 pmd-compat6/src/main/java/net/sourceforge/pmd/RuleViolation.java create mode 100644 pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/AbstractLanguage.java create mode 100644 pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/CPD.java create mode 100644 pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java create mode 100644 pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/EcmascriptLanguage.java create mode 100644 pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/EcmascriptTokenizer.java create mode 100644 pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JSPLanguage.java create mode 100644 pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JSPTokenizer.java create mode 100644 pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JavaLanguage.java create mode 100644 pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JavaTokenizer.java create mode 100644 pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/Language.java create mode 100644 pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/Mark.java create mode 100644 pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java create mode 100644 pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/renderer/CPDRenderer.java create mode 100644 pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/AbstractCompoundFilter.java create mode 100644 pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/AbstractDelegateFilter.java create mode 100644 pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/AndFilter.java create mode 100644 pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/DirectoryFilter.java create mode 100644 pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/FileExtensionFilter.java create mode 100644 pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/Filter.java create mode 100644 pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/Filters.java create mode 100644 pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/NotFilter.java create mode 100644 pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/OrFilter.java create mode 100644 pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/RegexStringFilter.java diff --git a/pmd-compat6/README.md b/pmd-compat6/README.md new file mode 100644 index 00000000000..d07fc26518f --- /dev/null +++ b/pmd-compat6/README.md @@ -0,0 +1,35 @@ +# pmd-compat6 + +This module contains classes from PMD6, that have been removed in PMD7 and also restores +some removed methods. + +The goal is, that PMD7 can be used with [Maven PMD Plugin](https://maven.apache.org/plugins/maven-pmd-plugin) +without any further changes to the plugin. + +The plugin uses by default PMD Version 6.55.0, but it can be configured to +[Use a new PMD version at runtime](https://maven.apache.org/plugins/maven-pmd-plugin/examples/upgrading-PMD-at-runtime.html). + +Since PMD7 introduces many incompatible changes, another module is needed to restore +compatibility. This is this module. + +In order to use this compatibility module, it needs to be added as the _first_ dependency +when configuring maven-pmd-plugin. + +It is as simple as adding: + +```xml + + net.sourceforge.pmd + pmd-compat6 + ${pmdVersion} + +``` + +Note: The dependency "pmd-compat6" must be listed _first_ before pmd-core, pmd-java, and the others. + +Once the default version of PMD is upgraded to PMD7 in maven-pmd-plugin +(see [MPMD-379](https://issues.apache.org/jira/projects/MPMD/issues/MPMD-379)), this +compatibility module is no longer needed. + +The primary goal for this module is, to get maven-pmd-plugin working with PMD7. It might +be useful in other contexts, too, but no guarantee is given, that is works. diff --git a/pmd-compat6/pom.xml b/pmd-compat6/pom.xml new file mode 100644 index 00000000000..2410c7982a6 --- /dev/null +++ b/pmd-compat6/pom.xml @@ -0,0 +1,70 @@ + + + 4.0.0 + + + net.sourceforge.pmd + pmd + 7.0.0-rc4 + + + pmd-compat6 + 7.0.0-SNAPSHOT + PMD Compatibility Classes for PMD6 + + + 7.0.0-rc4 + 7.0.0-rc4 + 3.21.2 + + + + + net.sourceforge.pmd + pmd-core + ${pmd.version} + + + net.sourceforge.pmd + pmd-java + ${pmd.version} + + + net.sourceforge.pmd + pmd-javascript + ${pmd.version} + + + net.sourceforge.pmd + pmd-jsp + ${pmd.version} + + + + + + + org.apache.maven.plugins + maven-invoker-plugin + 3.6.0 + + ${project.build.directory}/it + src/it/settings.xml + ${project.build.directory}/local-repo + verify.bsh + + + + integration-test + + install + run + + + + + + + diff --git a/pmd-compat6/src/it/cpd-for-java/invoker.properties b/pmd-compat6/src/it/cpd-for-java/invoker.properties new file mode 100644 index 00000000000..0d92d959f3d --- /dev/null +++ b/pmd-compat6/src/it/cpd-for-java/invoker.properties @@ -0,0 +1,2 @@ +invoker.goals = verify +invoker.buildResult = failure diff --git a/pmd-compat6/src/it/cpd-for-java/pom.xml b/pmd-compat6/src/it/cpd-for-java/pom.xml new file mode 100644 index 00000000000..ea93703565a --- /dev/null +++ b/pmd-compat6/src/it/cpd-for-java/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + + net.sourceforge.pmd.pmd-compat6.it + cpd-for-java + 1.0-SNAPSHOT + + + 21 + 21 + UTF-8 + + + + + + org.apache.maven.plugins + maven-pmd-plugin + @maven-pmd-plugin.version.for.integrationtest@ + + + java-cpd-check + + cpd-check + + + + + true + false + 5 + + + + net.sourceforge.pmd + pmd-compat6 + @project.version@ + + + net.sourceforge.pmd + pmd-core + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-java + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-javascript + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-jsp + @pmd.version.for.integrationtest@ + + + + + + diff --git a/pmd-compat6/src/it/cpd-for-java/src/main/java/org/example/ClassA.java b/pmd-compat6/src/it/cpd-for-java/src/main/java/org/example/ClassA.java new file mode 100644 index 00000000000..3df3dde7d92 --- /dev/null +++ b/pmd-compat6/src/it/cpd-for-java/src/main/java/org/example/ClassA.java @@ -0,0 +1,10 @@ +package org.example; + +public class ClassA { + public int method1(int a, int b, int c) { + int d = (a + b + c + 1) * 10; + int e = (a + b + c - 1) * 5; + int f = (a + b + c); + return d * e * f + d + e + f; + } +} diff --git a/pmd-compat6/src/it/cpd-for-java/src/main/java/org/example/ClassB.java b/pmd-compat6/src/it/cpd-for-java/src/main/java/org/example/ClassB.java new file mode 100644 index 00000000000..994e917441b --- /dev/null +++ b/pmd-compat6/src/it/cpd-for-java/src/main/java/org/example/ClassB.java @@ -0,0 +1,10 @@ +package org.example; + +public class ClassB { + public int method1(int a, int b, int c) { + int d = (a + b + c + 1) * 10; + int e = (a + b + c - 1) * 5; + int f = (a + b + c); + return d * e * f + d + e + f; + } +} diff --git a/pmd-compat6/src/it/cpd-for-java/verify.bsh b/pmd-compat6/src/it/cpd-for-java/verify.bsh new file mode 100644 index 00000000000..c5fff32f9a4 --- /dev/null +++ b/pmd-compat6/src/it/cpd-for-java/verify.bsh @@ -0,0 +1,35 @@ +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; + +String readFile(File file) throws IOException { + StringBuilder content = new StringBuilder(); + for (String line : Files.readAllLines(file.toPath(), StandardCharsets.UTF_8)) { + content.append(line).append(System.lineSeparator()); + } + return content.toString(); +} + +File buildLogPath = new File(basedir, "build.log"); +String buildLog = readFile(buildLogPath); +if (!buildLog.contains("[INFO] CPD Failure: Found 8 lines of duplicated code at locations:")) { + throw new RuntimeException("No CPD failures detected, did CPD run?"); +} +if (!buildLog.contains("cpd-for-java/src/main/java/org/example/ClassA.java line 3")) { + throw new RuntimeException("No CPD failures detected, did CPD run?"); +} + +File cpdXmlReport = new File(basedir, "target/cpd.xml"); +if(!cpdXmlReport.exists()) +{ + throw new FileNotFoundException("Could not find cpd xml report: " + cpdXmlReport); +} +String cpdXml = readFile(cpdXmlReport); +if (!cpdXml.contains("")) { + throw new RuntimeException("Expected duplication has not been reported"); +} +if (!cpdXml.contains("cpd-for-java/src/main/java/org/example/ClassA.java\"/>")) { + throw new RuntimeException("Expected duplication has not been reported"); +} diff --git a/pmd-compat6/src/it/cpd-for-javascript/invoker.properties b/pmd-compat6/src/it/cpd-for-javascript/invoker.properties new file mode 100644 index 00000000000..0d92d959f3d --- /dev/null +++ b/pmd-compat6/src/it/cpd-for-javascript/invoker.properties @@ -0,0 +1,2 @@ +invoker.goals = verify +invoker.buildResult = failure diff --git a/pmd-compat6/src/it/cpd-for-javascript/pom.xml b/pmd-compat6/src/it/cpd-for-javascript/pom.xml new file mode 100644 index 00000000000..a252b32a348 --- /dev/null +++ b/pmd-compat6/src/it/cpd-for-javascript/pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + + net.sourceforge.pmd.pmd-compat6.it + cpd-for-javascript + 1.0-SNAPSHOT + + + UTF-8 + + + + + + org.apache.maven.plugins + maven-pmd-plugin + @maven-pmd-plugin.version.for.integrationtest@ + + + javascript-cpd-check + + cpd-check + + + + + true + false + 5 + javascript + + /category/ecmascript/bestpractices.xml + + + **/*.js + + + ${basedir}/src/main/js + + + + + net.sourceforge.pmd + pmd-compat6 + @project.version@ + + + net.sourceforge.pmd + pmd-core + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-java + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-javascript + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-jsp + @pmd.version.for.integrationtest@ + + + + + + diff --git a/pmd-compat6/src/it/cpd-for-javascript/src/main/js/globalVariable.js b/pmd-compat6/src/it/cpd-for-javascript/src/main/js/globalVariable.js new file mode 100644 index 00000000000..d9c86cf3982 --- /dev/null +++ b/pmd-compat6/src/it/cpd-for-javascript/src/main/js/globalVariable.js @@ -0,0 +1,7 @@ +function(arg) { + notDeclaredVariable = 1; // this will create a global variable and trigger the rule + + var someVar = 1; // this is a local variable, that's ok + + window.otherGlobal = 2; // this will not trigger the rule, although it is a global variable. +} diff --git a/pmd-compat6/src/it/cpd-for-javascript/src/main/js/globalVariable2.js b/pmd-compat6/src/it/cpd-for-javascript/src/main/js/globalVariable2.js new file mode 100644 index 00000000000..d9c86cf3982 --- /dev/null +++ b/pmd-compat6/src/it/cpd-for-javascript/src/main/js/globalVariable2.js @@ -0,0 +1,7 @@ +function(arg) { + notDeclaredVariable = 1; // this will create a global variable and trigger the rule + + var someVar = 1; // this is a local variable, that's ok + + window.otherGlobal = 2; // this will not trigger the rule, although it is a global variable. +} diff --git a/pmd-compat6/src/it/cpd-for-javascript/verify.bsh b/pmd-compat6/src/it/cpd-for-javascript/verify.bsh new file mode 100644 index 00000000000..c400c18faa5 --- /dev/null +++ b/pmd-compat6/src/it/cpd-for-javascript/verify.bsh @@ -0,0 +1,35 @@ +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; + +String readFile(File file) throws IOException { + StringBuilder content = new StringBuilder(); + for (String line : Files.readAllLines(file.toPath(), StandardCharsets.UTF_8)) { + content.append(line).append(System.lineSeparator()); + } + return content.toString(); +} + +File buildLogPath = new File(basedir, "build.log"); +String buildLog = readFile(buildLogPath); +if (!buildLog.contains("[INFO] CPD Failure: Found 7 lines of duplicated code at locations:")) { + throw new RuntimeException("No CPD failures detected, did CPD run?"); +} +if (!buildLog.contains("cpd-for-javascript/src/main/js/globalVariable.js line 1")) { + throw new RuntimeException("No CPD failures detected, did CPD run?"); +} + +File cpdXmlReport = new File(basedir, "target/cpd.xml"); +if(!cpdXmlReport.exists()) +{ + throw new FileNotFoundException("Could not find cpd xml report: " + cpdXmlReport); +} +String cpdXml = readFile(cpdXmlReport); +if (!cpdXml.contains("")) { + throw new RuntimeException("Expected duplication has not been reported"); +} +if (!cpdXml.contains("cpd-for-javascript/src/main/js/globalVariable.js\"/>")) { + throw new RuntimeException("Expected duplication has not been reported"); +} diff --git a/pmd-compat6/src/it/cpd-for-jsp/invoker.properties b/pmd-compat6/src/it/cpd-for-jsp/invoker.properties new file mode 100644 index 00000000000..0d92d959f3d --- /dev/null +++ b/pmd-compat6/src/it/cpd-for-jsp/invoker.properties @@ -0,0 +1,2 @@ +invoker.goals = verify +invoker.buildResult = failure diff --git a/pmd-compat6/src/it/cpd-for-jsp/pom.xml b/pmd-compat6/src/it/cpd-for-jsp/pom.xml new file mode 100644 index 00000000000..1726d641d5e --- /dev/null +++ b/pmd-compat6/src/it/cpd-for-jsp/pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + + net.sourceforge.pmd.pmd-compat6.it + cpd-for-jsp + 1.0-SNAPSHOT + + + UTF-8 + + + + + + org.apache.maven.plugins + maven-pmd-plugin + @maven-pmd-plugin.version.for.integrationtest@ + + + jsp-cpd-check + + cpd-check + + + + + true + false + 5 + jsp + + /category/jsp/bestpractices.xml + + + **/*.jsp + + + ${basedir}/src/main/jsp + + + + + net.sourceforge.pmd + pmd-compat6 + @project.version@ + + + net.sourceforge.pmd + pmd-core + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-java + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-javascript + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-jsp + @pmd.version.for.integrationtest@ + + + + + + diff --git a/pmd-compat6/src/it/cpd-for-jsp/src/main/jsp/classAttribute.jsp b/pmd-compat6/src/it/cpd-for-jsp/src/main/jsp/classAttribute.jsp new file mode 100644 index 00000000000..c86ca5aa9c5 --- /dev/null +++ b/pmd-compat6/src/it/cpd-for-jsp/src/main/jsp/classAttribute.jsp @@ -0,0 +1,3 @@ + +

Some text

+ diff --git a/pmd-compat6/src/it/cpd-for-jsp/src/main/jsp/classAttribute2.jsp b/pmd-compat6/src/it/cpd-for-jsp/src/main/jsp/classAttribute2.jsp new file mode 100644 index 00000000000..c86ca5aa9c5 --- /dev/null +++ b/pmd-compat6/src/it/cpd-for-jsp/src/main/jsp/classAttribute2.jsp @@ -0,0 +1,3 @@ + +

Some text

+ diff --git a/pmd-compat6/src/it/cpd-for-jsp/verify.bsh b/pmd-compat6/src/it/cpd-for-jsp/verify.bsh new file mode 100644 index 00000000000..36b11503dd6 --- /dev/null +++ b/pmd-compat6/src/it/cpd-for-jsp/verify.bsh @@ -0,0 +1,35 @@ +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; + +String readFile(File file) throws IOException { + StringBuilder content = new StringBuilder(); + for (String line : Files.readAllLines(file.toPath(), StandardCharsets.UTF_8)) { + content.append(line).append(System.lineSeparator()); + } + return content.toString(); +} + +File buildLogPath = new File(basedir, "build.log"); +String buildLog = readFile(buildLogPath); +if (!buildLog.contains("[INFO] CPD Failure: Found 3 lines of duplicated code at locations:")) { + throw new RuntimeException("No CPD failures detected, did CPD run?"); +} +if (!buildLog.contains("cpd-for-jsp/src/main/jsp/classAttribute.jsp line 1")) { + throw new RuntimeException("No CPD failures detected, did CPD run?"); +} + +File cpdXmlReport = new File(basedir, "target/cpd.xml"); +if(!cpdXmlReport.exists()) +{ + throw new FileNotFoundException("Could not find cpd xml report: " + cpdXmlReport); +} +String cpdXml = readFile(cpdXmlReport); +if (!cpdXml.contains("")) { + throw new RuntimeException("Expected duplication has not been reported"); +} +if (!cpdXml.contains("cpd-for-jsp/src/main/jsp/classAttribute.jsp\"/>")) { + throw new RuntimeException("Expected duplication has not been reported"); +} diff --git a/pmd-compat6/src/it/pmd-for-java/invoker.properties b/pmd-compat6/src/it/pmd-for-java/invoker.properties new file mode 100644 index 00000000000..0d92d959f3d --- /dev/null +++ b/pmd-compat6/src/it/pmd-for-java/invoker.properties @@ -0,0 +1,2 @@ +invoker.goals = verify +invoker.buildResult = failure diff --git a/pmd-compat6/src/it/pmd-for-java/pom.xml b/pmd-compat6/src/it/pmd-for-java/pom.xml new file mode 100644 index 00000000000..7d77929cf98 --- /dev/null +++ b/pmd-compat6/src/it/pmd-for-java/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + + net.sourceforge.pmd.pmd-compat6.it + pmd-for-java + 1.0-SNAPSHOT + + + 21 + 21 + UTF-8 + + + + + + org.apache.maven.plugins + maven-pmd-plugin + @maven-pmd-plugin.version.for.integrationtest@ + + + java-check + + check + + + + + true + false + 5 + + + + net.sourceforge.pmd + pmd-compat6 + @project.version@ + + + net.sourceforge.pmd + pmd-core + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-java + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-javascript + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-jsp + @pmd.version.for.integrationtest@ + + + + + + diff --git a/pmd-compat6/src/it/pmd-for-java/src/main/java/org/example/Main.java b/pmd-compat6/src/it/pmd-for-java/src/main/java/org/example/Main.java new file mode 100644 index 00000000000..a1609790b58 --- /dev/null +++ b/pmd-compat6/src/it/pmd-for-java/src/main/java/org/example/Main.java @@ -0,0 +1,8 @@ +package org.example; + +public class Main { + public static void main(String[] args) { + String thisIsAUnusedLocalVar = "a"; + System.out.println("Hello world!"); + } +} diff --git a/pmd-compat6/src/it/pmd-for-java/verify.bsh b/pmd-compat6/src/it/pmd-for-java/verify.bsh new file mode 100644 index 00000000000..47eed9d5eaa --- /dev/null +++ b/pmd-compat6/src/it/pmd-for-java/verify.bsh @@ -0,0 +1,32 @@ +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; + +String readFile(File file) throws IOException { + StringBuilder content = new StringBuilder(); + for (String line : Files.readAllLines(file.toPath(), StandardCharsets.UTF_8)) { + content.append(line).append(System.lineSeparator()); + } + return content.toString(); +} + +File buildLogPath = new File(basedir, "build.log"); +String buildLog = readFile(buildLogPath); +if (!buildLog.contains("[INFO] PMD Failure: org.example.Main:5 Rule:UnusedLocalVariable")) { + throw new RuntimeException("No pmd violation detected, did PMD run?"); +} + +File pmdXmlReport = new File(basedir, "target/pmd.xml"); +if(!pmdXmlReport.exists()) +{ + throw new FileNotFoundException("Could not find pmd xml report: " + pmdXmlReport); +} +String pmdXml = readFile(pmdXmlReport); +if (!pmdXml.contains("")) { + throw new RuntimeException("Expected violation has not been reported"); +} diff --git a/pmd-compat6/src/it/pmd-for-javascript/invoker.properties b/pmd-compat6/src/it/pmd-for-javascript/invoker.properties new file mode 100644 index 00000000000..0d92d959f3d --- /dev/null +++ b/pmd-compat6/src/it/pmd-for-javascript/invoker.properties @@ -0,0 +1,2 @@ +invoker.goals = verify +invoker.buildResult = failure diff --git a/pmd-compat6/src/it/pmd-for-javascript/pom.xml b/pmd-compat6/src/it/pmd-for-javascript/pom.xml new file mode 100644 index 00000000000..e1fd383e42c --- /dev/null +++ b/pmd-compat6/src/it/pmd-for-javascript/pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + + net.sourceforge.pmd.pmd-compat6.it + pmd-for-javascript + 1.0-SNAPSHOT + + + UTF-8 + + + + + + org.apache.maven.plugins + maven-pmd-plugin + @maven-pmd-plugin.version.for.integrationtest@ + + + javascript-check + + check + + + + + true + false + 5 + javascript + + /category/ecmascript/bestpractices.xml + + + **/*.js + + + ${basedir}/src/main/js + + + + + net.sourceforge.pmd + pmd-compat6 + @project.version@ + + + net.sourceforge.pmd + pmd-core + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-java + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-javascript + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-jsp + @pmd.version.for.integrationtest@ + + + + + + diff --git a/pmd-compat6/src/it/pmd-for-javascript/src/main/js/globalVariable.js b/pmd-compat6/src/it/pmd-for-javascript/src/main/js/globalVariable.js new file mode 100644 index 00000000000..d9c86cf3982 --- /dev/null +++ b/pmd-compat6/src/it/pmd-for-javascript/src/main/js/globalVariable.js @@ -0,0 +1,7 @@ +function(arg) { + notDeclaredVariable = 1; // this will create a global variable and trigger the rule + + var someVar = 1; // this is a local variable, that's ok + + window.otherGlobal = 2; // this will not trigger the rule, although it is a global variable. +} diff --git a/pmd-compat6/src/it/pmd-for-javascript/verify.bsh b/pmd-compat6/src/it/pmd-for-javascript/verify.bsh new file mode 100644 index 00000000000..54ab5cedb9c --- /dev/null +++ b/pmd-compat6/src/it/pmd-for-javascript/verify.bsh @@ -0,0 +1,32 @@ +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; + +String readFile(File file) throws IOException { + StringBuilder content = new StringBuilder(); + for (String line : Files.readAllLines(file.toPath(), StandardCharsets.UTF_8)) { + content.append(line).append(System.lineSeparator()); + } + return content.toString(); +} + +File buildLogPath = new File(basedir, "build.log"); +String buildLog = readFile(buildLogPath); +if (!buildLog.contains("[INFO] PMD Failure: globalVariable.js:2 Rule:GlobalVariable")) { + throw new RuntimeException("No pmd violation detected, did PMD run?"); +} + +File pmdXmlReport = new File(basedir, "target/pmd.xml"); +if(!pmdXmlReport.exists()) +{ + throw new FileNotFoundException("Could not find pmd xml report: " + pmdXmlReport); +} +String pmdXml = readFile(pmdXmlReport); +if (!pmdXml.contains("")) { + throw new RuntimeException("Expected violation has not been reported"); +} diff --git a/pmd-compat6/src/it/pmd-for-jsp/invoker.properties b/pmd-compat6/src/it/pmd-for-jsp/invoker.properties new file mode 100644 index 00000000000..0d92d959f3d --- /dev/null +++ b/pmd-compat6/src/it/pmd-for-jsp/invoker.properties @@ -0,0 +1,2 @@ +invoker.goals = verify +invoker.buildResult = failure diff --git a/pmd-compat6/src/it/pmd-for-jsp/pom.xml b/pmd-compat6/src/it/pmd-for-jsp/pom.xml new file mode 100644 index 00000000000..3784e88e698 --- /dev/null +++ b/pmd-compat6/src/it/pmd-for-jsp/pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + + net.sourceforge.pmd.pmd-compat6.it + pmd-for-jsp + 1.0-SNAPSHOT + + + UTF-8 + + + + + + org.apache.maven.plugins + maven-pmd-plugin + @maven-pmd-plugin.version.for.integrationtest@ + + + jsp-check + + check + + + + + true + false + 5 + jsp + + /category/jsp/bestpractices.xml + + + **/*.jsp + + + ${basedir}/src/main/jsp + + + + + net.sourceforge.pmd + pmd-compat6 + @project.version@ + + + net.sourceforge.pmd + pmd-core + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-java + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-javascript + @pmd.version.for.integrationtest@ + + + net.sourceforge.pmd + pmd-jsp + @pmd.version.for.integrationtest@ + + + + + + diff --git a/pmd-compat6/src/it/pmd-for-jsp/src/main/jsp/classAttribute.jsp b/pmd-compat6/src/it/pmd-for-jsp/src/main/jsp/classAttribute.jsp new file mode 100644 index 00000000000..c86ca5aa9c5 --- /dev/null +++ b/pmd-compat6/src/it/pmd-for-jsp/src/main/jsp/classAttribute.jsp @@ -0,0 +1,3 @@ + +

Some text

+ diff --git a/pmd-compat6/src/it/pmd-for-jsp/verify.bsh b/pmd-compat6/src/it/pmd-for-jsp/verify.bsh new file mode 100644 index 00000000000..e564c7a9509 --- /dev/null +++ b/pmd-compat6/src/it/pmd-for-jsp/verify.bsh @@ -0,0 +1,32 @@ +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; + +String readFile(File file) throws IOException { + StringBuilder content = new StringBuilder(); + for (String line : Files.readAllLines(file.toPath(), StandardCharsets.UTF_8)) { + content.append(line).append(System.lineSeparator()); + } + return content.toString(); +} + +File buildLogPath = new File(basedir, "build.log"); +String buildLog = readFile(buildLogPath); +if (!buildLog.contains("[INFO] PMD Failure: classAttribute.jsp:2 Rule:NoClassAttribute")) { + throw new RuntimeException("No pmd violation detected, did PMD run?"); +} + +File pmdXmlReport = new File(basedir, "target/pmd.xml"); +if(!pmdXmlReport.exists()) +{ + throw new FileNotFoundException("Could not find pmd xml report: " + pmdXmlReport); +} +String pmdXml = readFile(pmdXmlReport); +if (!pmdXml.contains("")) { + throw new RuntimeException("Expected violation has not been reported"); +} diff --git a/pmd-compat6/src/it/settings.xml b/pmd-compat6/src/it/settings.xml new file mode 100644 index 00000000000..b7724658de8 --- /dev/null +++ b/pmd-compat6/src/it/settings.xml @@ -0,0 +1,35 @@ + + + + + it-repo + + + local.central + @localRepositoryUrl@ + + true + + + true + + + + + + local.central + @localRepositoryUrl@ + + true + + + true + + + + + + + it-repo + + diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/PMDConfiguration.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/PMDConfiguration.java new file mode 100644 index 00000000000..87175d74055 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/PMDConfiguration.java @@ -0,0 +1,588 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This class has been taken from 7.0.0-SNAPSHOT +// Changes: setSourceEncoding + +package net.sourceforge.pmd; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Properties; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.slf4j.LoggerFactory; + +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.internal.util.ClasspathClassLoader; +import net.sourceforge.pmd.lang.Language; +import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.renderers.Renderer; +import net.sourceforge.pmd.renderers.RendererFactory; +import net.sourceforge.pmd.util.AssertionUtil; +import net.sourceforge.pmd.util.log.internal.SimpleMessageReporter; + +/** + * This class contains the details for the runtime configuration of a + * PMD run. Once configured, use {@link PmdAnalysis#create(PMDConfiguration)} + * in a try-with-resources to execute the analysis (see {@link PmdAnalysis}). + * + *

Rulesets

+ * + *
    + *
  • You can configure paths to the rulesets to use with {@link #addRuleSet(String)}. + * These can be file paths or classpath resources.
  • + *
  • Use {@link #setMinimumPriority(RulePriority)} to control the minimum priority a + * rule must have to be included. Defaults to the lowest priority, ie all rules are loaded.
  • + *
  • Use {@link #setRuleSetFactoryCompatibilityEnabled(boolean)} to disable the + * compatibility measures for removed and renamed rules in the rulesets that will + * be loaded. + *
+ * + *

Source files

+ * + *
    + *
  • The default encoding of source files is the system default as + * returned by System.getProperty("file.encoding"). + * You can set it with {@link #setSourceEncoding(Charset)}.
  • + *
  • The source files to analyze can be given in many ways. See + * {@link #addInputPath(Path)} {@link #setInputFilePath(Path)}, {@link #setInputUri(URI)}. + *
  • Files are assigned a language based on their name. The language + * version of languages can be given with + * {@link #setDefaultLanguageVersion(LanguageVersion)}. + * The default language assignment can be overridden with + * {@link #setForceLanguageVersion(LanguageVersion)}.
  • + *
+ * + *

Rendering

+ * + *
    + *
  • The renderer format to use for Reports. {@link #getReportFormat()}
  • + *
  • The file to which the Report should render. {@link #getReportFile()}
  • + *
  • Configure the root paths that are used to relativize file names in reports via {@link #addRelativizeRoot(Path)}. + * This enables to get short names in reports.
  • + *
  • The initialization properties to use when creating a Renderer instance. + * {@link #getReportProperties()}
  • + *
  • An indicator of whether to show suppressed Rule violations in Reports. + * {@link #isShowSuppressedViolations()}
  • + *
+ * + *

Language configuration

+ *
    + *
  • Use {@link #setSuppressMarker(String)} to change the comment marker for suppression comments. Defaults to {@value #DEFAULT_SUPPRESS_MARKER}.
  • + *
  • See {@link #setClassLoader(ClassLoader)} and {@link #prependAuxClasspath(String)} for + * information for how to configure classpath for Java analysis.
  • + *
  • You can set additional language properties with {@link #getLanguageProperties(Language)}
  • + *
+ * + *

Miscellaneous

+ *
    + *
  • Use {@link #setThreads(int)} to control the parallelism of the analysis. Defaults + * one thread per available processor. {@link #getThreads()}
  • + *
+ */ +public class PMDConfiguration extends AbstractConfiguration { + private static final LanguageRegistry DEFAULT_REGISTRY = LanguageRegistry.PMD; + + /** The default suppress marker string. */ + public static final String DEFAULT_SUPPRESS_MARKER = "NOPMD"; + private Path reportFile; + + // General behavior options + private String suppressMarker = DEFAULT_SUPPRESS_MARKER; + private int threads = Runtime.getRuntime().availableProcessors(); + private ClassLoader classLoader = getClass().getClassLoader(); + + // Rule and source file options + private List ruleSets = new ArrayList<>(); + private RulePriority minimumPriority = RulePriority.LOW; + private boolean ruleSetFactoryCompatibilityEnabled = true; + + // Reporting options + private String reportFormat; + private Properties reportProperties = new Properties(); + private boolean showSuppressedViolations = false; + private boolean failOnViolation = true; + + private AnalysisCache analysisCache = new NoopAnalysisCache(); + private boolean ignoreIncrementalAnalysis; + + public PMDConfiguration() { + this(DEFAULT_REGISTRY); + } + + public PMDConfiguration(@NonNull LanguageRegistry languageRegistry) { + super(languageRegistry, new SimpleMessageReporter(LoggerFactory.getLogger(PmdAnalysis.class))); + } + + /** + * Get the suppress marker. This is the source level marker used to indicate + * a RuleViolation should be suppressed. + * + * @return The suppress marker. + */ + public String getSuppressMarker() { + return suppressMarker; + } + + /** + * Set the suppress marker. + * + * @param suppressMarker + * The suppress marker to use. + */ + public void setSuppressMarker(String suppressMarker) { + Objects.requireNonNull(suppressMarker, "Suppress marker was null"); + this.suppressMarker = suppressMarker; + } + + /** + * Get the number of threads to use when processing Rules. + * + * @return The number of threads. + */ + public int getThreads() { + return threads; + } + + /** + * Set the number of threads to use when processing Rules. + * + * @param threads + * The number of threads. + */ + public void setThreads(int threads) { + this.threads = threads; + } + + /** + * Get the ClassLoader being used by PMD when processing Rules. + * + * @return The ClassLoader being used + */ + public ClassLoader getClassLoader() { + return classLoader; + } + + /** + * Set the ClassLoader being used by PMD when processing Rules. Setting a + * value of null will cause the default ClassLoader to be used. + * + * @param classLoader + * The ClassLoader to use + */ + public void setClassLoader(ClassLoader classLoader) { + if (classLoader == null) { + this.classLoader = getClass().getClassLoader(); + } else { + this.classLoader = 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 IOException + * 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 { + try { + prependAuxClasspath(classpath); + } catch (IllegalArgumentException e) { + throw new IOException(e); + } + } + + /** + * 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.

+ * + *

You can specify multiple class paths separated by `:` on Unix-systems or `;` under Windows. + * See {@link File#pathSeparator}. + * + * @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); + } + } + + /** + * Get the comma separated list of RuleSet URIs. + * + * @return The RuleSet URIs. + * + * @deprecated Use {@link #getRuleSetPaths()} + */ + @Deprecated + @DeprecatedUntil700 + public @Nullable String getRuleSets() { + if (ruleSets.isEmpty()) { + return null; + } + return String.join(",", ruleSets); + } + + /** + * Returns the list of ruleset URIs. + * + * @see RuleSetLoader#loadFromResource(String) + */ + public @NonNull List<@NonNull String> getRuleSetPaths() { + return 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<@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); + } + + /** + * Set the comma separated list of RuleSet URIs. + * + * @param ruleSets the rulesets to set + * + * @deprecated Use {@link #setRuleSets(List)} or {@link #addRuleSet(String)}. + */ + @Deprecated + @DeprecatedUntil700 + public void setRuleSets(@Nullable String ruleSets) { + if (ruleSets == null) { + this.ruleSets = new ArrayList<>(); + } else { + this.ruleSets = new ArrayList<>(Arrays.asList(ruleSets.split(","))); + } + } + + /** + * Get the minimum priority threshold when loading Rules from RuleSets. + * + * @return The minimum priority threshold. + */ + public RulePriority getMinimumPriority() { + return minimumPriority; + } + + /** + * Set the minimum priority threshold when loading Rules from RuleSets. + * + * @param minimumPriority + * The minimum priority. + */ + public void setMinimumPriority(RulePriority minimumPriority) { + this.minimumPriority = minimumPriority; + } + + + /** + * Create a Renderer instance based upon the configured reporting options. + * No writer is created. + * + * @return renderer + */ + public Renderer createRenderer() { + return createRenderer(false); + } + + /** + * Create a Renderer instance based upon the configured reporting options. + * If withReportWriter then we'll configure it with a writer for the + * reportFile specified. + * + * @param withReportWriter + * whether to configure a writer or not + * @return A Renderer instance. + */ + public Renderer createRenderer(boolean withReportWriter) { + Renderer renderer = RendererFactory.createRenderer(reportFormat, reportProperties); + renderer.setShowSuppressedViolations(showSuppressedViolations); + if (withReportWriter) { + renderer.setReportFile(getReportFile()); + } + return renderer; + } + + /** + * Get the report format. + * + * @return The report format. + */ + public String getReportFormat() { + return reportFormat; + } + + /** + * Set the report format. This should be a name of a Renderer. + * + * @param reportFormat + * The report format. + * + * @see Renderer + */ + public void setReportFormat(String reportFormat) { + this.reportFormat = reportFormat; + } + + /** + * Get whether the report should show suppressed violations. + * + * @return true if showing suppressed violations, + * false otherwise. + */ + public boolean isShowSuppressedViolations() { + return showSuppressedViolations; + } + + /** + * Set whether the report should show suppressed violations. + * + * @param showSuppressedViolations + * true if showing suppressed violations, + * false otherwise. + */ + public void setShowSuppressedViolations(boolean showSuppressedViolations) { + this.showSuppressedViolations = showSuppressedViolations; + } + + /** + * Get the Report properties. These are used to create the Renderer. + * + * @return The report properties. + */ + public Properties getReportProperties() { + return reportProperties; + } + + /** + * Set the Report properties. These are used to create the Renderer. + * + * @param reportProperties + * The Report properties to set. + */ + public void setReportProperties(Properties reportProperties) { + this.reportProperties = reportProperties; + } + + /** + * Whether PMD should exit with status 4 (the default behavior, true) if + * violations are found or just with 0 (to not break the build, e.g.). + * + * @return failOnViolation + */ + public boolean isFailOnViolation() { + return failOnViolation; + } + + /** + * Sets whether PMD should exit with status 4 (the default behavior, true) + * if violations are found or just with 0 (to not break the build, e.g.). + * + * @param failOnViolation + * failOnViolation + */ + public void setFailOnViolation(boolean failOnViolation) { + this.failOnViolation = failOnViolation; + } + + /** + * Checks if the rule set factory compatibility feature is enabled. + * + * @return true, if the rule set factory compatibility feature is enabled + * + * @see RuleSetLoader#enableCompatibility(boolean) + */ + public boolean isRuleSetFactoryCompatibilityEnabled() { + return ruleSetFactoryCompatibilityEnabled; + } + + /** + * Sets the rule set factory compatibility feature enabled/disabled. + * + * @param ruleSetFactoryCompatibilityEnabled {@code true} if the feature should be enabled + * + * @see RuleSetLoader#enableCompatibility(boolean) + */ + public void setRuleSetFactoryCompatibilityEnabled(boolean ruleSetFactoryCompatibilityEnabled) { + this.ruleSetFactoryCompatibilityEnabled = ruleSetFactoryCompatibilityEnabled; + } + + /** + * Retrieves the currently used analysis cache. Will never be null. + * + * @return The currently used analysis cache. Never null. + */ + public AnalysisCache getAnalysisCache() { + // Make sure we are not null + if (analysisCache == null || isIgnoreIncrementalAnalysis() && !(analysisCache instanceof NoopAnalysisCache)) { + // sets a noop cache + setAnalysisCache(new NoopAnalysisCache()); + } + + return analysisCache; + } + + /** + * Sets the analysis cache to be used. Setting a + * value of {@code null} will cause a Noop AnalysisCache to be used. + * If incremental analysis was explicitly disabled ({@link #isIgnoreIncrementalAnalysis()}), + * then this method is a noop. + * + * @param cache The analysis cache to be used. + */ + public void setAnalysisCache(final AnalysisCache cache) { + // the doc says it's a noop if incremental analysis was disabled, + // but it's actually the getter that enforces that + this.analysisCache = cache == null ? new NoopAnalysisCache() : cache; + } + + /** + * Sets the location of the analysis cache to be used. This will automatically configure + * and appropriate AnalysisCache implementation. + * + * @param cacheLocation The location of the analysis cache to be used. + */ + public void setAnalysisCacheLocation(final String cacheLocation) { + setAnalysisCache(cacheLocation == null + ? new NoopAnalysisCache() + : new FileAnalysisCache(new File(cacheLocation))); + } + + + /** + * Sets whether the user has explicitly disabled incremental analysis or not. + * If so, incremental analysis is not used, and all suggestions to use it are + * disabled. The analysis cached location is ignored, even if it's specified. + * + * @param noCache Whether to ignore incremental analysis or not + */ + public void setIgnoreIncrementalAnalysis(boolean noCache) { + // see #getAnalysisCache for the implementation. + this.ignoreIncrementalAnalysis = noCache; + } + + + /** + * Returns whether incremental analysis was explicitly disabled by the user + * or not. + * + * @return {@code true} if incremental analysis is explicitly disabled + */ + public boolean isIgnoreIncrementalAnalysis() { + return ignoreIncrementalAnalysis; + } + + + /** + * Get the file to which the report should render. + * + * @return The file to which to render. + * @deprecated Use {@link #getReportFilePath()} + */ + @Deprecated + public String getReportFile() { + return reportFile == null ? null : reportFile.toString(); + } + + /** + * Get the file to which the report should render. + * + * @return The file to which to render. + */ + public Path getReportFilePath() { + return reportFile; + } + + /** + * Set the file to which the report should render. + * + * @param reportFile the file to set + * @deprecated Use {@link #setReportFile(Path)} + */ + @Deprecated + public void setReportFile(String reportFile) { + this.reportFile = reportFile == null ? null : Paths.get(reportFile); + } + + /** + * Set the file to which the report should render. + * + * @param reportFile the file to set + */ + public void setReportFile(Path reportFile) { + this.reportFile = reportFile; + } + + // ------------------- compat extensions -------------------- + @Deprecated + public void setSourceEncoding(String sourceEncoding) { + setSourceEncoding(Charset.forName(Objects.requireNonNull(sourceEncoding))); + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/Report.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/Report.java new file mode 100644 index 00000000000..89adae02907 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/Report.java @@ -0,0 +1,432 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This class has been taken from 7.0.0-SNAPSHOT +// Changes: filterViolations(net.sourceforge.pmd.util.Predicate filter) + +package net.sourceforge.pmd; + +import static java.util.Collections.synchronizedList; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Predicate; + +import net.sourceforge.pmd.annotation.DeprecatedUntil700; +import net.sourceforge.pmd.annotation.Experimental; +import net.sourceforge.pmd.annotation.InternalApi; +import net.sourceforge.pmd.lang.document.FileId; +import net.sourceforge.pmd.lang.document.TextFile; +import net.sourceforge.pmd.renderers.AbstractAccumulatingRenderer; +import net.sourceforge.pmd.reporting.FileAnalysisListener; +import net.sourceforge.pmd.reporting.GlobalAnalysisListener; +import net.sourceforge.pmd.util.BaseResultProducingCloseable; + +/** + * A {@link Report} collects all informations during a PMD execution. This + * includes violations, suppressed violations, metrics, error during processing + * and configuration errors. + * + *

A report may be created by a {@link GlobalReportBuilderListener} that you + * use as the {@linkplain GlobalAnalysisListener} in {@link PmdAnalysis#performAnalysisAndCollectReport() PMD's entry point}. + * You can also create one manually with {@link #buildReport(Consumer)}. + * + *

For special use cases, like filtering the report after PMD analysis and + * before rendering the report, some transformation operations are provided: + *

    + *
  • {@link #filterViolations(Predicate)}
  • + *
  • {@link #union(Report)}
  • + *
+ * These methods create a new {@link Report} rather than modifying their receiver. + *

+ */ +public final class Report { + // todo move to package reporting + + private final List violations = synchronizedList(new ArrayList<>()); + private final List suppressedRuleViolations = synchronizedList(new ArrayList<>()); + private final List errors = synchronizedList(new ArrayList<>()); + private final List configErrors = synchronizedList(new ArrayList<>()); + + @DeprecatedUntil700 + @InternalApi + public Report() { // NOPMD - UnnecessaryConstructor + // TODO: should be package-private, you have to use a listener to build a report. + } + + /** + * Represents a configuration error. + */ + public static class ConfigurationError { + + private final Rule rule; + private final String issue; + + /** + * Creates a new configuration error for a specific rule. + * + * @param theRule + * the rule which is configured wrongly + * @param theIssue + * the reason, why the configuration is wrong + */ + public ConfigurationError(Rule theRule, String theIssue) { + rule = theRule; + issue = theIssue; + } + + /** + * Gets the wrongly configured rule + * + * @return the wrongly configured rule + */ + public Rule rule() { + return rule; + } + + /** + * Gets the reason for the configuration error. + * + * @return the issue + */ + public String issue() { + return issue; + } + } + + /** + * Represents a processing error, such as a parse error. + */ + public static class ProcessingError { + + private final Throwable error; + private final FileId file; + + /** + * Creates a new processing error + * + * @param error + * the error + * @param file + * the file during which the error occurred + */ + public ProcessingError(Throwable error, FileId file) { + this.error = error; + this.file = file; + } + + public String getMsg() { + return error.getClass().getSimpleName() + ": " + error.getMessage(); + } + + public String getDetail() { + try (StringWriter stringWriter = new StringWriter(); + PrintWriter writer = new PrintWriter(stringWriter)) { + error.printStackTrace(writer); + return stringWriter.toString(); + } catch (IOException e) { + // IOException on close - should never happen when using StringWriter + throw new RuntimeException(e); + } + } + + public FileId getFileId() { + return file; + } + + public Throwable getError() { + return error; + } + } + + /** + * Represents a violation, that has been suppressed. + */ + public static class SuppressedViolation { + + private final RuleViolation rv; + private final String userMessage; + private final ViolationSuppressor suppressor; + + /** + * Creates a suppressed violation. + * + * @param rv The violation, that has been suppressed + * @param suppressor The suppressor which suppressed the violation + * @param userMessage Any relevant info given by the suppressor + */ + public SuppressedViolation(RuleViolation rv, ViolationSuppressor suppressor, String userMessage) { + this.suppressor = suppressor; + this.rv = rv; + this.userMessage = userMessage; + } + + public ViolationSuppressor getSuppressor() { + return suppressor; + } + + public RuleViolation getRuleViolation() { + return this.rv; + } + + public String getUserMessage() { + return userMessage; + } + } + + /** + * Adds a new rule violation to the report and notify the listeners. + * + * @param violation the violation to add + * + * @deprecated PMD's way of creating a report is internal and may be changed in pmd 7. + */ + @DeprecatedUntil700 + @Deprecated + @InternalApi + public void addRuleViolation(RuleViolation violation) { + synchronized (violations) { + // note that this binary search is inefficient as we usually + // report violations file by file. + int index = Collections.binarySearch(violations, violation, RuleViolation.DEFAULT_COMPARATOR); + violations.add(index < 0 ? -index - 1 : index, violation); + } + } + + /** + * Adds a new suppressed violation. + */ + private void addSuppressedViolation(SuppressedViolation sv) { + suppressedRuleViolations.add(sv); + } + + /** + * Adds a new configuration error to the report. + * + * @param error the error to add + * + * @deprecated PMD's way of creating a report is internal and may be changed in pmd 7. + */ + @DeprecatedUntil700 + @Deprecated + @InternalApi + public void addConfigError(ConfigurationError error) { + configErrors.add(error); + } + + /** + * Adds a new processing error to the report. + * + * @param error + * the error to add + * @deprecated PMD's way of creating a report is internal and may be changed in pmd 7. + */ + @DeprecatedUntil700 + @Deprecated + @InternalApi + public void addError(ProcessingError error) { + errors.add(error); + } + + /** + * Merges the given report into this report. This might be necessary, if a + * summary over all violations is needed as PMD creates one report per file + * by default. + * + *

This is synchronized on an internal lock (note that other mutation + * operations are not synchronized, todo for pmd 7). + * + * @param r the report to be merged into this. + * + * @see AbstractAccumulatingRenderer + * + * @deprecated Convert Renderer to use the reports. + */ + @Deprecated + public void merge(Report r) { + errors.addAll(r.errors); + configErrors.addAll(r.configErrors); + suppressedRuleViolations.addAll(r.suppressedRuleViolations); + + for (RuleViolation violation : r.getViolations()) { + addRuleViolation(violation); + } + } + + + /** + * Returns an unmodifiable list of violations that were suppressed. + */ + public List getSuppressedViolations() { + return Collections.unmodifiableList(suppressedRuleViolations); + } + + /** + * Returns an unmodifiable list of violations that have been + * recorded until now. None of those violations were suppressed. + * + *

The violations list is sorted with {@link RuleViolation#DEFAULT_COMPARATOR}. + */ + public List getViolations() { + return Collections.unmodifiableList(violations); + } + + + /** + * Returns an unmodifiable list of processing errors that have been + * recorded until now. + */ + public List getProcessingErrors() { + return Collections.unmodifiableList(errors); + } + + + /** + * Returns an unmodifiable list of configuration errors that have + * been recorded until now. + */ + public List getConfigurationErrors() { + return Collections.unmodifiableList(configErrors); + } + + /** + * Create a report by making side effects on a {@link FileAnalysisListener}. + * This wraps a {@link ReportBuilderListener}. + */ + public static Report buildReport(Consumer lambda) { + return BaseResultProducingCloseable.using(new ReportBuilderListener(), lambda); + } + + /** + * A {@link FileAnalysisListener} that accumulates events into a + * {@link Report}. + */ + public static final class ReportBuilderListener extends BaseResultProducingCloseable implements FileAnalysisListener { + + private final Report report; + + public ReportBuilderListener() { + this(new Report()); + } + + ReportBuilderListener(Report report) { + this.report = report; + } + + @Override + protected Report getResultImpl() { + return report; + } + + @Override + public void onRuleViolation(RuleViolation violation) { + report.addRuleViolation(violation); + } + + @Override + public void onSuppressedRuleViolation(SuppressedViolation violation) { + report.addSuppressedViolation(violation); + } + + @Override + public void onError(ProcessingError error) { + report.addError(error); + } + + @Override + public String toString() { + return "ReportBuilderListener"; + } + } + + /** + * A {@link GlobalAnalysisListener} that accumulates the events of + * all files into a {@link Report}. + */ + public static final class GlobalReportBuilderListener extends BaseResultProducingCloseable implements GlobalAnalysisListener { + + private final Report report = new Report(); + + @Override + public FileAnalysisListener startFileAnalysis(TextFile file) { + // note that the report is shared, but Report is now thread-safe + return new ReportBuilderListener(this.report); + } + + @Override + public void onConfigError(ConfigurationError error) { + report.addConfigError(error); + } + + @Override + protected Report getResultImpl() { + return report; + } + } + + /** + * Creates a new report taking all the information from this report, + * but filtering the violations. + * + * @param filter when true, the violation will be kept. + * @return copy of this report + */ + @Experimental + public Report filterViolations(Predicate filter) { + Report copy = new Report(); + + for (RuleViolation violation : violations) { + if (filter.test(violation)) { + copy.addRuleViolation(violation); + } + } + + copy.suppressedRuleViolations.addAll(suppressedRuleViolations); + copy.errors.addAll(errors); + copy.configErrors.addAll(configErrors); + return copy; + } + + /** + * Creates a new report by combining this report with another report. + * This is similar to {@link #merge(Report)}, but instead a new report + * is created. The lowest start time and greatest end time are kept in the copy. + * + * @param other the other report to combine + * @return + */ + @Experimental + public Report union(Report other) { + Report copy = new Report(); + + for (RuleViolation violation : violations) { + copy.addRuleViolation(violation); + } + for (RuleViolation violation : other.violations) { + copy.addRuleViolation(violation); + } + + copy.suppressedRuleViolations.addAll(suppressedRuleViolations); + copy.suppressedRuleViolations.addAll(other.suppressedRuleViolations); + + copy.errors.addAll(errors); + copy.errors.addAll(other.errors); + copy.configErrors.addAll(configErrors); + copy.configErrors.addAll(other.configErrors); + + return copy; + } + + // ------------------- compat extensions -------------------- + @Deprecated + public Report filterViolations(net.sourceforge.pmd.util.Predicate filter) { + Predicate javaPredicate = filter::test; + return filterViolations(javaPredicate); + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/RuleViolation.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/RuleViolation.java new file mode 100644 index 00000000000..dcb30300520 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/RuleViolation.java @@ -0,0 +1,196 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This class has been taken from 7.0.0-SNAPSHOT +// Changes: getFilename + +package net.sourceforge.pmd; + +import java.util.Comparator; +import java.util.Map; + +import net.sourceforge.pmd.annotation.DeprecatedUntil700; +import net.sourceforge.pmd.lang.document.FileId; +import net.sourceforge.pmd.lang.document.FileLocation; + +/** + * A RuleViolation is created by a Rule when it identifies a violation of the + * Rule constraints. RuleViolations are simple data holders that are collected + * into a {@link Report}. + * + *

Since PMD 6.21.0, implementations of this interface are considered internal + * API and hence deprecated. Clients should exclusively use this interface. + * + * @see Rule + */ +public interface RuleViolation { + // todo move to package reporting + + /** + * A comparator for rule violations. This compares all exposed attributes + * of a violation, filename first. The remaining parameters are compared + * in an unspecified order. + */ + Comparator DEFAULT_COMPARATOR = + Comparator.comparing(RuleViolation::getFileId) + .thenComparingInt(RuleViolation::getBeginLine) + .thenComparingInt(RuleViolation::getBeginColumn) + .thenComparing(RuleViolation::getDescription, Comparator.nullsLast(Comparator.naturalOrder())) + .thenComparingInt(RuleViolation::getEndLine) + .thenComparingInt(RuleViolation::getEndColumn) + .thenComparing(rv -> rv.getRule().getName()); + + + /** + * Key in {@link #getAdditionalInfo()} for the name of the class in + * which the violation was identified. + */ + String CLASS_NAME = "className"; + /** + * Key in {@link #getAdditionalInfo()} for the name of the variable + * related to the violation. + */ + String VARIABLE_NAME = "variableName"; + /** + * Key in {@link #getAdditionalInfo()} for the name of the method in + * which the violation was identified. + */ + String METHOD_NAME = "methodName"; + /** + * Key in {@link #getAdditionalInfo()} for the name of the package in + * which the violation was identified. + */ + String PACKAGE_NAME = "packageName"; + + /** + * Get the Rule which identified this violation. + * + * @return The identifying Rule. + */ + Rule getRule(); + + /** + * Get the description of this violation. + * + * @return The description. + */ + String getDescription(); + + + /** + * Returns the location where the violation should be reported. + */ + FileLocation getLocation(); + + /** + * Return the ID of the file where the violation was found. + */ + default FileId getFileId() { + return getLocation().getFileId(); + } + + /** + * Get the begin line number in the source file in which this violation was + * identified. + * + * @return Begin line number. + */ + default int getBeginLine() { + return getLocation().getStartPos().getLine(); + } + + /** + * Get the column number of the begin line in the source file in which this + * violation was identified. + * + * @return Begin column number. + */ + default int getBeginColumn() { + return getLocation().getStartPos().getColumn(); + } + + /** + * Get the end line number in the source file in which this violation was + * identified. + * + * @return End line number. + */ + default int getEndLine() { + return getLocation().getEndPos().getLine(); + } + + /** + * Get the column number of the end line in the source file in which this + * violation was identified. + * + * @return End column number. + */ + default int getEndColumn() { + return getLocation().getEndPos().getColumn(); + } + + /** + * A map of additional key-value pairs known about this violation. + * What data is in there is language specific. Common keys supported + * by several languages are defined as constants on this interface. + * The map is unmodifiable. + */ + Map getAdditionalInfo(); + + + /** + * Get the package name of the Class in which this violation was identified. + * + * @return The package name. + * + * @deprecated Use {@link #PACKAGE_NAME} + */ + @Deprecated + @DeprecatedUntil700 + default String getPackageName() { + return getAdditionalInfo().get(PACKAGE_NAME); + } + + /** + * Get the name of the Class in which this violation was identified. + * + * @return The Class name. + * @deprecated Use {@link #CLASS_NAME} + */ + @Deprecated + @DeprecatedUntil700 + default String getClassName() { + return getAdditionalInfo().get(CLASS_NAME); + } + + /** + * Get the method name in which this violation was identified. + * + * @return The method name. + * @deprecated Use {@link #METHOD_NAME} + */ + @Deprecated + @DeprecatedUntil700 + default String getMethodName() { + return getAdditionalInfo().get(METHOD_NAME); + } + + /** + * Get the variable name on which this violation was identified. + * + * @return The variable name. + * @deprecated Use {@link #VARIABLE_NAME} + */ + @Deprecated + @DeprecatedUntil700 + default String getVariableName() { + return getAdditionalInfo().get(VARIABLE_NAME); + } + + // ------------------- compat extensions -------------------- + @Deprecated + default String getFilename() { + return getLocation().getFileId().getFileName(); + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/AbstractLanguage.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/AbstractLanguage.java new file mode 100644 index 00000000000..c6d1d847609 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/AbstractLanguage.java @@ -0,0 +1,60 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This file has been taken from 6.55.0 + +package net.sourceforge.pmd.cpd; + +import java.io.FilenameFilter; +import java.util.Arrays; +import java.util.List; +import java.util.Properties; + +import net.sourceforge.pmd.util.filter.Filters; + +public abstract class AbstractLanguage implements Language { + private final String name; + private final String terseName; + private final Tokenizer tokenizer; + private final FilenameFilter fileFilter; + private final List extensions; + + public AbstractLanguage(String name, String terseName, Tokenizer tokenizer, String... extensions) { + this.name = name; + this.terseName = terseName; + this.tokenizer = tokenizer; + fileFilter = Filters.toFilenameFilter(Filters.getFileExtensionOrDirectoryFilter(extensions)); + this.extensions = Arrays.asList(extensions); + } + + @Override + public FilenameFilter getFileFilter() { + return fileFilter; + } + + @Override + public Tokenizer getTokenizer() { + return tokenizer; + } + + @Override + public void setProperties(Properties properties) { + // needs to be implemented by subclasses. + } + + @Override + public String getName() { + return name; + } + + @Override + public String getTerseName() { + return terseName; + } + + @Override + public List getExtensions() { + return extensions; + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/CPD.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/CPD.java new file mode 100644 index 00000000000..c570dcec07f --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/CPD.java @@ -0,0 +1,97 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cpd; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import net.sourceforge.pmd.internal.util.FileFinder; +import net.sourceforge.pmd.internal.util.IOUtil; + +/** + * Adapter for PMD 7. This exposes CPD interface of PMD 6 but runs PMD 7 under the hood. + */ +public class CPD { + private final CPDConfiguration configuration; + private final List files = new ArrayList<>(); + private Set current = new HashSet<>(); + private CPDReport report; + + public CPD(CPDConfiguration configuration) { + this.configuration = configuration; + } + + public void addAllInDirectory(File dir) throws IOException { + addDirectory(dir, false); + } + + public void addRecursively(File dir) throws IOException { + addDirectory(dir, true); + } + + public void add(List files) throws IOException { + for (File f : files) { + add(f); + } + } + + private void addDirectory(File dir, boolean recurse) throws IOException { + if (!dir.exists()) { + throw new FileNotFoundException("Couldn't find directory " + dir); + } + FileFinder finder = new FileFinder(); + // TODO - could use SourceFileSelector here + add(finder.findFilesFrom(dir, configuration.filenameFilter(), recurse)); + } + + public void add(File file) throws IOException { + if (configuration.isSkipDuplicates()) { + // TODO refactor this thing into a separate class + String signature = file.getName() + '_' + file.length(); + if (current.contains(signature)) { + System.err.println("Skipping " + file.getAbsolutePath() + + " since it appears to be a duplicate file and --skip-duplicate-files is set"); + return; + } + current.add(signature); + } + + if (!IOUtil.equalsNormalizedPaths(file.getAbsoluteFile().getCanonicalPath(), file.getAbsolutePath())) { + System.err.println("Skipping " + file + " since it appears to be a symlink"); + return; + } + + if (!file.exists()) { + System.err.println("Skipping " + file + " since it doesn't exist (broken symlink?)"); + return; + } + + files.add(file.toPath()); + } + + public void go() { + try (CpdAnalysis cpd = CpdAnalysis.create(configuration)) { + files.forEach(cpd.files()::addFile); + cpd.performAnalysis(this::collectReport); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void collectReport(CPDReport cpdReport) { + this.report = cpdReport; + } + + public Iterator getMatches() { + return report.getMatches().iterator(); + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java new file mode 100644 index 00000000000..a747e69b2c8 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java @@ -0,0 +1,341 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This class has been taken from 7.0.0-SNAPSHOT +// Changes: setLanguage, setSourceEncoding, filenameFilter + +package net.sourceforge.pmd.cpd; + +import java.beans.IntrospectionException; +import java.beans.PropertyDescriptor; +import java.io.File; +import java.io.FilenameFilter; +import java.lang.reflect.Method; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.slf4j.LoggerFactory; + +import net.sourceforge.pmd.AbstractConfiguration; +import net.sourceforge.pmd.internal.util.FileFinder; +import net.sourceforge.pmd.internal.util.FileUtil; +import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.lang.ecmascript.EcmascriptLanguageModule; +import net.sourceforge.pmd.lang.java.JavaLanguageModule; +import net.sourceforge.pmd.lang.jsp.JspLanguageModule; +import net.sourceforge.pmd.util.log.internal.SimpleMessageReporter; + +/** + * + * @author Brian Remedios + * @author Romain Pelisse - <belaran@gmail.com> + */ +public class CPDConfiguration extends AbstractConfiguration { + + public static final String DEFAULT_LANGUAGE = "java"; + public static final String DEFAULT_RENDERER = "text"; + + private static final Map> RENDERERS = new HashMap<>(); + + + static { + RENDERERS.put(DEFAULT_RENDERER, SimpleRenderer.class); + RENDERERS.put("xml", XMLRenderer.class); + RENDERERS.put("csv", CSVRenderer.class); + RENDERERS.put("csv_with_linecount_per_file", CSVWithLinecountPerFileRenderer.class); + RENDERERS.put("vs", VSRenderer.class); + } + + + private int minimumTileSize; + + private boolean skipDuplicates; + + private String rendererName = DEFAULT_RENDERER; + + private @Nullable CPDReportRenderer cpdReportRenderer; + + private boolean ignoreLiterals; + + private boolean ignoreIdentifiers; + + private boolean ignoreAnnotations; + + private boolean ignoreUsings; + + private boolean ignoreLiteralSequences = false; + + private boolean ignoreIdentifierAndLiteralSequences = false; + + private boolean skipLexicalErrors = false; + + private boolean noSkipBlocks = false; + + private String skipBlocksPattern = CpdLanguageProperties.DEFAULT_SKIP_BLOCKS_PATTERN; + + private boolean help; + + private boolean failOnViolation = true; + + + public CPDConfiguration() { + this(LanguageRegistry.CPD); + } + + public CPDConfiguration(LanguageRegistry languageRegistry) { + super(languageRegistry, new SimpleMessageReporter(LoggerFactory.getLogger(CpdAnalysis.class))); + } + + @Override + public void setSourceEncoding(Charset sourceEncoding) { + super.setSourceEncoding(sourceEncoding); + if (cpdReportRenderer != null) { + setRendererEncoding(cpdReportRenderer, sourceEncoding); + } + } + + static CPDReportRenderer createRendererByName(String name, Charset encoding) { + if (name == null || "".equals(name)) { + name = DEFAULT_RENDERER; + } + Class rendererClass = RENDERERS.get(name.toLowerCase(Locale.ROOT)); + if (rendererClass == null) { + Class klass; + try { + klass = Class.forName(name); + if (CPDReportRenderer.class.isAssignableFrom(klass)) { + rendererClass = (Class) klass; + } else { + throw new IllegalArgumentException("Class " + name + " does not implement " + CPDReportRenderer.class); + } + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException("Cannot find class " + name); + } + } + + CPDReportRenderer renderer; + try { + renderer = rendererClass.getDeclaredConstructor().newInstance(); + setRendererEncoding(renderer, encoding); + } catch (Exception e) { + System.err.println("Couldn't instantiate renderer, defaulting to SimpleRenderer: " + e); + renderer = new SimpleRenderer(); + } + return renderer; + } + + private static void setRendererEncoding(@NonNull Object renderer, Charset encoding) { + try { + PropertyDescriptor encodingProperty = new PropertyDescriptor("encoding", renderer.getClass()); + Method method = encodingProperty.getWriteMethod(); + if (method == null) { + return; + } + if (method.getParameterTypes()[0] == Charset.class) { + method.invoke(renderer, encoding); + } else if (method.getParameterTypes()[0] == String.class) { + method.invoke(renderer, encoding.name()); + } + } catch (IntrospectionException | ReflectiveOperationException ignored) { + // ignored - maybe this renderer doesn't have a encoding property + } + } + + public static Set getRenderers() { + return Collections.unmodifiableSet(RENDERERS.keySet()); + } + + public int getMinimumTileSize() { + return minimumTileSize; + } + + public void setMinimumTileSize(int minimumTileSize) { + this.minimumTileSize = minimumTileSize; + } + + public boolean isSkipDuplicates() { + return skipDuplicates; + } + + public void setSkipDuplicates(boolean skipDuplicates) { + this.skipDuplicates = skipDuplicates; + } + + public String getRendererName() { + return rendererName; + } + + public void setRendererName(String rendererName) { + this.rendererName = rendererName; + if (rendererName == null) { + this.cpdReportRenderer = null; + } + this.cpdReportRenderer = createRendererByName(rendererName, getSourceEncoding()); + } + + + public CPDReportRenderer getCPDReportRenderer() { + return cpdReportRenderer; + } + + void setRenderer(CPDReportRenderer renderer) { + this.cpdReportRenderer = renderer; + } + + public boolean isIgnoreLiterals() { + return ignoreLiterals; + } + + public void setIgnoreLiterals(boolean ignoreLiterals) { + this.ignoreLiterals = ignoreLiterals; + } + + public boolean isIgnoreIdentifiers() { + return ignoreIdentifiers; + } + + public void setIgnoreIdentifiers(boolean ignoreIdentifiers) { + this.ignoreIdentifiers = ignoreIdentifiers; + } + + public boolean isIgnoreAnnotations() { + return ignoreAnnotations; + } + + public void setIgnoreAnnotations(boolean ignoreAnnotations) { + this.ignoreAnnotations = ignoreAnnotations; + } + + public boolean isIgnoreUsings() { + return ignoreUsings; + } + + public void setIgnoreUsings(boolean ignoreUsings) { + this.ignoreUsings = ignoreUsings; + } + + public boolean isIgnoreLiteralSequences() { + return ignoreLiteralSequences; + } + + public void setIgnoreLiteralSequences(boolean ignoreLiteralSequences) { + this.ignoreLiteralSequences = ignoreLiteralSequences; + } + + public boolean isIgnoreIdentifierAndLiteralSequences() { + return ignoreIdentifierAndLiteralSequences; + } + + public void setIgnoreIdentifierAndLiteralSequences(boolean ignoreIdentifierAndLiteralSequences) { + this.ignoreIdentifierAndLiteralSequences = ignoreIdentifierAndLiteralSequences; + } + + public boolean isSkipLexicalErrors() { + return skipLexicalErrors; + } + + public void setSkipLexicalErrors(boolean skipLexicalErrors) { + this.skipLexicalErrors = skipLexicalErrors; + } + + public boolean isHelp() { + return help; + } + + public void setHelp(boolean help) { + this.help = help; + } + + public boolean isNoSkipBlocks() { + return noSkipBlocks; + } + + public void setNoSkipBlocks(boolean noSkipBlocks) { + this.noSkipBlocks = noSkipBlocks; + } + + public String getSkipBlocksPattern() { + return skipBlocksPattern; + } + + public void setSkipBlocksPattern(String skipBlocksPattern) { + this.skipBlocksPattern = skipBlocksPattern; + } + + public boolean isFailOnViolation() { + return failOnViolation; + } + + public void setFailOnViolation(boolean failOnViolation) { + this.failOnViolation = failOnViolation; + } + + // ------------------- compat extensions -------------------- + private FilenameFilter filenameFilter; + + public void setLanguage(Language language) { + if (language instanceof JavaLanguage) { + filenameFilter = language.getFileFilter(); + setForceLanguageVersion(JavaLanguageModule.getInstance().getDefaultVersion()); + } else if (language instanceof EcmascriptLanguage) { + filenameFilter = language.getFileFilter(); + setForceLanguageVersion(EcmascriptLanguageModule.getInstance().getDefaultVersion()); + } else if (language instanceof JSPLanguage) { + filenameFilter = language.getFileFilter(); + setForceLanguageVersion(JspLanguageModule.getInstance().getDefaultVersion()); + } else { + throw new UnsupportedOperationException("Language " + language.getName() + " is not supported"); + } + } + + public void setSourceEncoding(String sourceEncoding) { + setSourceEncoding(Charset.forName(Objects.requireNonNull(sourceEncoding))); + } + + public FilenameFilter filenameFilter() { + if (getForceLanguageVersion() == null) { + throw new IllegalStateException("Language is null."); + } + + final FilenameFilter languageFilter = filenameFilter; + final Set exclusions = new HashSet<>(); + + if (getExcludes() != null) { + FileFinder finder = new FileFinder(); + for (Path excludedFile : getExcludes()) { + if (Files.isDirectory(excludedFile)) { + List files = finder.findFilesFrom(excludedFile.toFile(), languageFilter, true); + for (File f : files) { + exclusions.add(FileUtil.normalizeFilename(f.getAbsolutePath())); + } + } else { + exclusions.add(FileUtil.normalizeFilename(excludedFile.toAbsolutePath().toString())); + } + } + } + + return new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + File f = new File(dir, name); + if (exclusions.contains(FileUtil.normalizeFilename(f.getAbsolutePath()))) { + System.err.println("Excluding " + f.getAbsolutePath()); + return false; + } + return languageFilter.accept(dir, name); + } + }; + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/EcmascriptLanguage.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/EcmascriptLanguage.java new file mode 100644 index 00000000000..ecd2a5a2cf0 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/EcmascriptLanguage.java @@ -0,0 +1,17 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This file has been taken from 6.55.0 + +package net.sourceforge.pmd.cpd; + +/** + * + * @author Zev Blut zb@ubit.com + */ +public class EcmascriptLanguage extends AbstractLanguage { + public EcmascriptLanguage() { + super("JavaScript", "ecmascript", new EcmascriptTokenizer(), ".js"); + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/EcmascriptTokenizer.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/EcmascriptTokenizer.java new file mode 100644 index 00000000000..f75312c4697 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/EcmascriptTokenizer.java @@ -0,0 +1,8 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cpd; + +public class EcmascriptTokenizer extends net.sourceforge.pmd.lang.ecmascript.cpd.EcmascriptTokenizer { +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JSPLanguage.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JSPLanguage.java new file mode 100644 index 00000000000..e1501bad5d6 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JSPLanguage.java @@ -0,0 +1,13 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This file has been taken from 6.55.0 + +package net.sourceforge.pmd.cpd; + +public class JSPLanguage extends AbstractLanguage { + public JSPLanguage() { + super("JSP", "jsp", new JSPTokenizer(), ".jsp", ".jspx", ".jspf", ".tag"); + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JSPTokenizer.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JSPTokenizer.java new file mode 100644 index 00000000000..76b768afd86 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JSPTokenizer.java @@ -0,0 +1,8 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cpd; + +public class JSPTokenizer extends net.sourceforge.pmd.lang.jsp.cpd.JSPTokenizer { +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JavaLanguage.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JavaLanguage.java new file mode 100644 index 00000000000..344a5d9bbc4 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JavaLanguage.java @@ -0,0 +1,26 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This file has been taken from 6.55.0 +// Changes: setProperties doesn't work, provide properties in constructor already + +package net.sourceforge.pmd.cpd; + +import java.util.Properties; + +public class JavaLanguage extends AbstractLanguage { + public JavaLanguage() { + this(System.getProperties()); + } + + public JavaLanguage(Properties properties) { + super("Java", "java", new JavaTokenizer(properties), ".java"); + } + + @Override + public final void setProperties(Properties properties) { + // note: this might be actually incompatible + throw new UnsupportedOperationException(); + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JavaTokenizer.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JavaTokenizer.java new file mode 100644 index 00000000000..113663cffa1 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JavaTokenizer.java @@ -0,0 +1,33 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cpd; + +import java.util.Properties; + +import net.sourceforge.pmd.lang.java.JavaLanguageModule; +import net.sourceforge.pmd.lang.java.internal.JavaLanguageProperties; + +public class JavaTokenizer extends net.sourceforge.pmd.lang.java.cpd.JavaTokenizer { + public JavaTokenizer(Properties properties) { + super(convertLanguageProperties(properties)); + } + + private static final String IGNORE_LITERALS = "ignore_literals"; + private static final String IGNORE_IDENTIFIERS = "ignore_identifiers"; + private static final String IGNORE_ANNOTATIONS = "ignore_annotations"; + + private static JavaLanguageProperties convertLanguageProperties(Properties properties) { + boolean ignoreAnnotations = Boolean.parseBoolean(properties.getProperty(IGNORE_ANNOTATIONS, "false")); + boolean ignoreLiterals = Boolean.parseBoolean(properties.getProperty(IGNORE_LITERALS, "false")); + boolean ignoreIdentifiers = Boolean.parseBoolean(properties.getProperty(IGNORE_IDENTIFIERS, "false")); + + JavaLanguageProperties javaLanguageProperties = (JavaLanguageProperties) JavaLanguageModule.getInstance().newPropertyBundle(); + javaLanguageProperties.setProperty(CpdLanguageProperties.CPD_IGNORE_METADATA, ignoreAnnotations); + javaLanguageProperties.setProperty(CpdLanguageProperties.CPD_ANONYMIZE_LITERALS, ignoreLiterals); + javaLanguageProperties.setProperty(CpdLanguageProperties.CPD_ANONYMIZE_IDENTIFIERS, ignoreIdentifiers); + + return javaLanguageProperties; + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/Language.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/Language.java new file mode 100644 index 00000000000..d2ee070bb5d --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/Language.java @@ -0,0 +1,25 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This file has been taken from 6.55.0 + +package net.sourceforge.pmd.cpd; + +import java.io.FilenameFilter; +import java.util.List; +import java.util.Properties; + +public interface Language { + String getName(); + + String getTerseName(); + + Tokenizer getTokenizer(); + + FilenameFilter getFileFilter(); + + void setProperties(Properties properties); + + List getExtensions(); +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/Mark.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/Mark.java new file mode 100644 index 00000000000..2bcfdc15ea1 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/Mark.java @@ -0,0 +1,104 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This class has been taken from 7.0.0-SNAPSHOT +// Changes: getFilename + +package net.sourceforge.pmd.cpd; + +import java.util.Objects; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +import net.sourceforge.pmd.lang.document.FileId; +import net.sourceforge.pmd.lang.document.FileLocation; +import net.sourceforge.pmd.lang.document.TextRange2d; + +/** + * A range of tokens in a source file, identified by a start and end + * token (both included in the range). The start and end token may be + * the same token. + */ +public final class Mark implements Comparable { + + private final @NonNull TokenEntry token; + private @Nullable TokenEntry endToken; + + Mark(@NonNull TokenEntry token) { + this.token = token; + } + + @NonNull TokenEntry getToken() { + return this.token; + } + + @NonNull TokenEntry getEndToken() { + return endToken == null ? token : endToken; + } + + /** + * Return the location of this source range in the source file. + */ + public FileLocation getLocation() { + TokenEntry endToken = getEndToken(); + return FileLocation.range( + getFileId(), + TextRange2d.range2d(token.getBeginLine(), token.getBeginColumn(), + endToken.getEndLine(), endToken.getEndColumn())); + } + + FileId getFileId() { + return token.getFileId(); + } + + public int getBeginTokenIndex() { + return this.token.getIndex(); + } + + public int getEndTokenIndex() { + return getEndToken().getIndex(); + } + + void setEndToken(@NonNull TokenEntry endToken) { + assert endToken.getFileId().equals(token.getFileId()) + : "Tokens are not from the same file"; + this.endToken = endToken; + } + + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + token.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Mark other = (Mark) obj; + return Objects.equals(token, other.token) + && Objects.equals(endToken, other.endToken); + } + + @Override + public int compareTo(Mark other) { + return getToken().compareTo(other.getToken()); + } + + // ------------------- compat extensions -------------------- + public String getFilename() { + return this.token.getFileId().getOriginalPath(); + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java new file mode 100644 index 00000000000..4ebab60230f --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java @@ -0,0 +1,193 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This class has been taken from 7.0.0-SNAPSHOT +// Changes: implements old interface CPDRenderer, old render(Iterator matches, Writer writer) method + +package net.sourceforge.pmd.cpd; + +import java.io.IOException; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import net.sourceforge.pmd.cpd.renderer.CPDRenderer; +import net.sourceforge.pmd.lang.document.Chars; +import net.sourceforge.pmd.lang.document.FileId; +import net.sourceforge.pmd.lang.document.FileLocation; +import net.sourceforge.pmd.lang.document.TextFile; +import net.sourceforge.pmd.lang.java.JavaLanguageModule; +import net.sourceforge.pmd.util.StringUtil; + +/** + * @author Philippe T'Seyen - original implementation + * @author Romain Pelisse - javax.xml implementation + * + */ +public final class XMLRenderer implements CPDReportRenderer, CPDRenderer { + + private String encoding; + + /** + * Creates a XML Renderer with the default (platform dependent) encoding. + */ + public XMLRenderer() { + this(null); + } + + /** + * Creates a XML Renderer with a specific output encoding. + * + * @param encoding + * the encoding to use or null. If null, default (platform + * dependent) encoding is used. + */ + public XMLRenderer(String encoding) { + setEncoding(encoding); + } + + public void setEncoding(String encoding) { + if (encoding != null) { + this.encoding = encoding; + } else { + this.encoding = System.getProperty("file.encoding"); + } + } + + public String getEncoding() { + return this.encoding; + } + + private Document createDocument() { + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder parser = factory.newDocumentBuilder(); + return parser.newDocument(); + } catch (ParserConfigurationException e) { + throw new IllegalStateException(e); + } + } + + private void dumpDocToWriter(Document doc, Writer writer) { + try { + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer transformer = tf.newTransformer(); + transformer.setOutputProperty(OutputKeys.VERSION, "1.0"); + transformer.setOutputProperty(OutputKeys.METHOD, "xml"); + transformer.setOutputProperty(OutputKeys.ENCODING, encoding); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.CDATA_SECTION_ELEMENTS, "codefragment"); + transformer.transform(new DOMSource(doc), new StreamResult(writer)); + } catch (TransformerException e) { + throw new IllegalStateException(e); + } + } + + + @Override + public void render(final CPDReport report, final Writer writer) throws IOException { + final Document doc = createDocument(); + final Element root = doc.createElement("pmd-cpd"); + final Map numberOfTokensPerFile = report.getNumberOfTokensPerFile(); + doc.appendChild(root); + + for (final Map.Entry pair : numberOfTokensPerFile.entrySet()) { + final Element fileElement = doc.createElement("file"); + fileElement.setAttribute("path", report.getDisplayName(pair.getKey())); + fileElement.setAttribute("totalNumberOfTokens", String.valueOf(pair.getValue())); + root.appendChild(fileElement); + } + + for (Match match : report.getMatches()) { + Element dupElt = createDuplicationElement(doc, match); + addFilesToDuplicationElement(doc, dupElt, match, report); + addCodeSnippet(doc, dupElt, match, report); + root.appendChild(dupElt); + } + dumpDocToWriter(doc, writer); + writer.flush(); + } + + private void addFilesToDuplicationElement(Document doc, Element duplication, Match match, CPDReport report) { + for (Mark mark : match) { + final Element file = doc.createElement("file"); + FileLocation loc = mark.getLocation(); + file.setAttribute("line", String.valueOf(loc.getStartLine())); + // only remove invalid characters, escaping is done by the DOM impl. + String filenameXml10 = StringUtil.removedInvalidXml10Characters(report.getDisplayName(loc.getFileId())); + file.setAttribute("path", filenameXml10); + file.setAttribute("endline", String.valueOf(loc.getEndLine())); + file.setAttribute("column", String.valueOf(loc.getStartColumn())); + file.setAttribute("endcolumn", String.valueOf(loc.getEndColumn())); + file.setAttribute("begintoken", String.valueOf(mark.getBeginTokenIndex())); + file.setAttribute("endtoken", String.valueOf(mark.getEndTokenIndex())); + duplication.appendChild(file); + } + } + + private void addCodeSnippet(Document doc, Element duplication, Match match, CPDReport report) { + Chars codeSnippet = report.getSourceCodeSlice(match.getFirstMark()); + if (codeSnippet != null) { + // the code snippet has normalized line endings + String platformSpecific = codeSnippet.toString().replace("\n", System.lineSeparator()); + Element codefragment = doc.createElement("codefragment"); + // only remove invalid characters, escaping is not necessary in CDATA. + // if the string contains the end marker of a CDATA section, then the DOM impl will + // create two cdata sections automatically. + codefragment.appendChild(doc.createCDATASection(StringUtil.removedInvalidXml10Characters(platformSpecific))); + duplication.appendChild(codefragment); + } + } + + private Element createDuplicationElement(Document doc, Match match) { + Element duplication = doc.createElement("duplication"); + duplication.setAttribute("lines", String.valueOf(match.getLineCount())); + duplication.setAttribute("tokens", String.valueOf(match.getTokenCount())); + return duplication; + } + + // ------------------- compat extensions -------------------- + @Override + public void render(Iterator matches, Writer writer) throws IOException { + List matchesList = new ArrayList<>(); + matches.forEachRemaining(matchesList::add); + + List textFiles = new ArrayList<>(); + Set paths = new HashSet<>(); + for (Match match : matchesList) { + for (Mark mark : match.getMarkSet()) { + paths.add(mark.getFilename()); + } + } + for (String path : paths) { + textFiles.add(TextFile.forPath(Paths.get(path), StandardCharsets.UTF_8, JavaLanguageModule.getInstance().getDefaultVersion())); + } + + try (SourceManager sourcManager = new SourceManager(textFiles)) { + CPDReport report = new CPDReport(sourcManager, matchesList, Collections.emptyMap()); + render(report, writer); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/renderer/CPDRenderer.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/renderer/CPDRenderer.java new file mode 100644 index 00000000000..f79d108642f --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/renderer/CPDRenderer.java @@ -0,0 +1,21 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This file has been taken from 6.55.0 + +package net.sourceforge.pmd.cpd.renderer; + +import java.io.IOException; +import java.io.Writer; +import java.util.Iterator; + +import net.sourceforge.pmd.cpd.Match; + +/** + * @deprecated Use {@link CPDReportRenderer} + */ +@Deprecated +public interface CPDRenderer { + void render(Iterator matches, Writer writer) throws IOException; +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/AbstractCompoundFilter.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/AbstractCompoundFilter.java new file mode 100644 index 00000000000..851c4bb492a --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/AbstractCompoundFilter.java @@ -0,0 +1,65 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This file has been taken from 6.55.0 + +package net.sourceforge.pmd.util.filter; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * A base class for Filters which implements behavior using a List of other + * Filters. + * + * @param + * The underlying type on which the filter applies. + * @deprecated See {@link Filter} + */ +@Deprecated +public abstract class AbstractCompoundFilter implements Filter { + + protected List> filters; + + public AbstractCompoundFilter() { + filters = new ArrayList<>(2); + } + + public AbstractCompoundFilter(Filter... filters) { + this.filters = Arrays.asList(filters); + } + + public List> getFilters() { + return filters; + } + + public void setFilters(List> filters) { + this.filters = filters; + } + + public void addFilter(Filter filter) { + filters.add(filter); + } + + protected abstract String getOperator(); + + @Override + public String toString() { + + if (filters.isEmpty()) { + return "()"; + } + + StringBuilder builder = new StringBuilder(); + builder.append('(').append(filters.get(0)); + + for (int i = 1; i < filters.size(); i++) { + builder.append(' ').append(getOperator()).append(' '); + builder.append(filters.get(i)); + } + builder.append(')'); + return builder.toString(); + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/AbstractDelegateFilter.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/AbstractDelegateFilter.java new file mode 100644 index 00000000000..e6ca4e47d22 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/AbstractDelegateFilter.java @@ -0,0 +1,48 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This file has been taken from 6.55.0 + +package net.sourceforge.pmd.util.filter; + +/** + * A base class for Filters which implements behavior using delegation to an + * underlying filter. + * + * @param + * The underlying type on which the filter applies. + * @deprecated See {@link Filter} + */ +@Deprecated +public abstract class AbstractDelegateFilter implements Filter { + protected Filter filter; + + public AbstractDelegateFilter() { + // default constructor + } + + public AbstractDelegateFilter(Filter filter) { + this.filter = filter; + } + + public Filter getFilter() { + return filter; + } + + public void setFilter(Filter filter) { + this.filter = filter; + } + + // Subclass should override to do something other the simply delegate. + @Override + public boolean filter(T obj) { + return filter.filter(obj); + } + + // Subclass should override to do something other the simply delegate. + @Override + public String toString() { + return filter.toString(); + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/AndFilter.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/AndFilter.java new file mode 100644 index 00000000000..be8ff31fea0 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/AndFilter.java @@ -0,0 +1,43 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This file has been taken from 6.55.0 + +package net.sourceforge.pmd.util.filter; + +/** + * A logical AND of a list of Filters. This implementation is short circuiting. + * + * @param + * The underlying type on which the filter applies. + * @deprecated See {@link Filter} + */ +@Deprecated +public class AndFilter extends AbstractCompoundFilter { + + public AndFilter() { + super(); + } + + public AndFilter(Filter... filters) { + super(filters); + } + + @Override + public boolean filter(T obj) { + boolean match = true; + for (Filter filter : filters) { + if (!filter.filter(obj)) { + match = false; + break; + } + } + return match; + } + + @Override + protected String getOperator() { + return "and"; + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/DirectoryFilter.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/DirectoryFilter.java new file mode 100644 index 00000000000..c1669e91d3b --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/DirectoryFilter.java @@ -0,0 +1,31 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This file has been taken from 6.55.0 + +package net.sourceforge.pmd.util.filter; + +import java.io.File; + +/** + * Directory filter. + * @deprecated See {@link Filter} + */ +@Deprecated +public final class DirectoryFilter implements Filter { + public static final DirectoryFilter INSTANCE = new DirectoryFilter(); + + private DirectoryFilter() { + } + + @Override + public boolean filter(File file) { + return file.isDirectory(); + } + + @Override + public String toString() { + return "is Directory"; + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/FileExtensionFilter.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/FileExtensionFilter.java new file mode 100644 index 00000000000..06259afce33 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/FileExtensionFilter.java @@ -0,0 +1,54 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This file has been taken from 6.55.0 + +package net.sourceforge.pmd.util.filter; + +import java.io.File; +import java.util.Locale; + +/** + * @deprecated See {@link Filter} + */ +@Deprecated +public class FileExtensionFilter implements Filter { + protected final String[] extensions; + protected final boolean ignoreCase; + + /** + * Matches any files with the given extensions, ignoring case + */ + public FileExtensionFilter(String... extensions) { + this(true, extensions); + } + + /** + * Matches any files with the given extensions, optionally ignoring case. + */ + public FileExtensionFilter(boolean ignoreCase, String... extensions) { + this.extensions = extensions; + this.ignoreCase = ignoreCase; + if (ignoreCase) { + for (int i = 0; i < this.extensions.length; i++) { + this.extensions[i] = this.extensions[i].toUpperCase(Locale.ROOT); + } + } + } + + @Override + public boolean filter(File file) { + boolean accept = extensions == null; + if (!accept) { + for (String extension : extensions) { + String name = file.getName(); + if (ignoreCase ? name.toUpperCase(Locale.ROOT).endsWith(extension) : name.endsWith(extension)) { + accept = true; + break; + } + } + } + return accept; + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/Filter.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/Filter.java new file mode 100644 index 00000000000..e99a14bb44c --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/Filter.java @@ -0,0 +1,20 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This file has been taken from 6.55.0 + +package net.sourceforge.pmd.util.filter; + +/** + * A Filter interface, used for filtering arbitrary objects. + * + * @param + * The underlying type on which the filter applies. + * + * @deprecated Will be replaced with standard java.util.function.Predicate with 7.0.0 + */ +@Deprecated +public interface Filter { + boolean filter(T obj); +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/Filters.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/Filters.java new file mode 100644 index 00000000000..699b6143083 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/Filters.java @@ -0,0 +1,245 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This file has been taken from 6.55.0 + +package net.sourceforge.pmd.util.filter; + +import java.io.File; +import java.io.FilenameFilter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import net.sourceforge.pmd.annotation.InternalApi; + +/** + * Utility class for working with Filters. Contains builder style methods, apply + * methods, as well as mechanisms for adapting Filters and FilenameFilters. + * + * @deprecated Internal API, see {@link Filter} + */ +@Deprecated +@InternalApi +public final class Filters { + + private Filters() { } + + /** + * Filter a given Collection. + * + * @param + * Type of the Collection. + * @param filter + * A Filter upon the Type of objects in the Collection. + * @param collection + * The Collection to filter. + * @return A List containing only those objects for which the Filter + * returned true. + */ + public static List filter(Filter filter, Collection collection) { + List list = new ArrayList<>(); + for (T obj : collection) { + if (filter.filter(obj)) { + list.add(obj); + } + } + return list; + } + + /** + * Get a File Filter for files with the given extensions, ignoring case. + * + * @param extensions + * The extensions to filter. + * @return A File Filter. + */ + public static Filter getFileExtensionFilter(String... extensions) { + return new FileExtensionFilter(extensions); + } + + /** + * Get a File Filter for directories. + * + * @return A File Filter. + */ + public static Filter getDirectoryFilter() { + return DirectoryFilter.INSTANCE; + } + + /** + * Get a File Filter for directories or for files with the given extensions, + * ignoring case. + * + * @param extensions + * The extensions to filter. + * @return A File Filter. + */ + public static Filter getFileExtensionOrDirectoryFilter(String... extensions) { + return new OrFilter<>(getFileExtensionFilter(extensions), getDirectoryFilter()); + } + + /** + * Given a String Filter, expose as a File Filter. The File paths are + * normalized to a standard pattern using / as a path separator + * which can be used cross platform easily in a regular expression based + * String Filter. + * + * @param filter + * A String Filter. + * @return A File Filter. + */ + public static Filter toNormalizedFileFilter(final Filter filter) { + return new Filter() { + @Override + public boolean filter(File file) { + String path = file.getPath(); + path = path.replace('\\', '/'); + return filter.filter(path); + } + + @Override + public String toString() { + return filter.toString(); + } + }; + } + + /** + * Given a String Filter, expose as a Filter on another type. The + * toString() method is called on the objects of the other type + * and delegated to the String Filter. + * + * @param + * The desired type. + * @param filter + * The existing String Filter. + * @return A Filter on the desired type. + */ + public static Filter fromStringFilter(final Filter filter) { + return new Filter() { + @Override + public boolean filter(T obj) { + return filter.filter(obj.toString()); + } + + @Override + public String toString() { + return filter.toString(); + } + }; + } + + /** + * Given a File Filter, expose as a FilenameFilter. + * + * @param filter + * The File Filter. + * @return A FilenameFilter. + */ + public static FilenameFilter toFilenameFilter(final Filter filter) { + return new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return filter.filter(new File(dir, name)); + } + + @Override + public String toString() { + return filter.toString(); + } + }; + } + + /** + * Given a FilenameFilter, expose as a File Filter. + * + * @param filter + * The FilenameFilter. + * @return A File Filter. + */ + public static Filter toFileFilter(final FilenameFilter filter) { + return new Filter() { + @Override + public boolean filter(File file) { + return filter.accept(file.getParentFile(), file.getName()); + } + + @Override + public String toString() { + return filter.toString(); + } + }; + } + + /** + * Construct a String Filter using set of include and exclude regular + * expressions. If there are no include regular expressions provide, then a + * regular expression is added which matches every String by default. A + * String is included as long as it matches an include regular expression + * and does not match an exclude regular expression. + *

+ * In other words, exclude patterns override include patterns. + * + * @param includeRegexes + * The include regular expressions. May be null. + * @param excludeRegexes + * The exclude regular expressions. May be null. + * @return A String Filter. + */ + public static Filter buildRegexFilterExcludeOverInclude(List includeRegexes, + List excludeRegexes) { + OrFilter includeFilter = new OrFilter<>(); + if (includeRegexes == null || includeRegexes.isEmpty()) { + includeFilter.addFilter(new RegexStringFilter(".*")); + } else { + for (String includeRegex : includeRegexes) { + includeFilter.addFilter(new RegexStringFilter(includeRegex)); + } + } + + OrFilter excludeFilter = new OrFilter<>(); + if (excludeRegexes != null) { + for (String excludeRegex : excludeRegexes) { + excludeFilter.addFilter(new RegexStringFilter(excludeRegex)); + } + } + + return new AndFilter<>(includeFilter, new NotFilter<>(excludeFilter)); + } + + /** + * Construct a String Filter using set of include and exclude regular + * expressions. If there are no include regular expressions provide, then a + * regular expression is added which matches every String by default. A + * String is included as long as the case that there is an include which + * matches or there is not an exclude which matches. + *

+ * In other words, include patterns override exclude patterns. + * + * @param includeRegexes + * The include regular expressions. May be null. + * @param excludeRegexes + * The exclude regular expressions. May be null. + * @return A String Filter. + */ + public static Filter buildRegexFilterIncludeOverExclude(List includeRegexes, + List excludeRegexes) { + OrFilter includeFilter = new OrFilter<>(); + if (includeRegexes != null) { + for (String includeRegex : includeRegexes) { + includeFilter.addFilter(new RegexStringFilter(includeRegex)); + } + } + + OrFilter excludeFilter = new OrFilter<>(); + if (excludeRegexes != null) { + for (String excludeRegex : excludeRegexes) { + excludeFilter.addFilter(new RegexStringFilter(excludeRegex)); + } + } + + return new OrFilter<>(includeFilter, new NotFilter<>(excludeFilter)); + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/NotFilter.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/NotFilter.java new file mode 100644 index 00000000000..26cc1245711 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/NotFilter.java @@ -0,0 +1,35 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This file has been taken from 6.55.0 + +package net.sourceforge.pmd.util.filter; + +/** + * A logical NEGATION of a Filter. + * + * @param + * The underlying type on which the filter applies. + * @deprecated See {@link Filter} + */ +@Deprecated +public class NotFilter extends net.sourceforge.pmd.util.filter.AbstractDelegateFilter { + public NotFilter() { + super(); + } + + public NotFilter(Filter filter) { + super(filter); + } + + @Override + public boolean filter(T obj) { + return !filter.filter(obj); + } + + @Override + public String toString() { + return "not (" + filter + ")"; + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/OrFilter.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/OrFilter.java new file mode 100644 index 00000000000..9c57d99a1fc --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/OrFilter.java @@ -0,0 +1,43 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This file has been taken from 6.55.0 + +package net.sourceforge.pmd.util.filter; + +/** + * A logical OR of a list of Filters. This implementation is short circuiting. + * + * @param + * The underlying type on which the filter applies. + * @deprecated See {@link Filter} + */ +@Deprecated +public class OrFilter extends AbstractCompoundFilter { + + public OrFilter() { + super(); + } + + public OrFilter(Filter... filters) { + super(filters); + } + + @Override + public boolean filter(T obj) { + boolean match = false; + for (Filter filter : filters) { + if (filter.filter(obj)) { + match = true; + break; + } + } + return match; + } + + @Override + protected String getOperator() { + return "or"; + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/RegexStringFilter.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/RegexStringFilter.java new file mode 100644 index 00000000000..fd8b933cb40 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/RegexStringFilter.java @@ -0,0 +1,98 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This file has been taken from 6.55.0 + +package net.sourceforge.pmd.util.filter; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +/** + * A filter which uses a regular expression to match Strings. Invalid regular + * expressions will match nothing. + *

+ * Because regular expression matching is slow, and a common usage is to match + * some sort of relative file path, the regular expression is checked to see if + * it can be evaluated using much faster calls to + * {@link String#endsWith(String)}. + * @deprecated See {@link Filter} + */ +@Deprecated +public class RegexStringFilter implements Filter { + /** + * Matches regular expressions begin with an optional {@code ^}, then + * {@code .*}, then a literal path, with an optional file extension, and + * finally an optional {@code $} at the end. The {@code .} in the extension + * may or may not be preceded by a {@code \} escape. The literal path + * portion is determine by the absence of any of the following characters: + * \ [ ( . * ? + | { $ + * + * There are two capturing groups in the expression. The first is for the + * literal path. The second is for the file extension, without the escaping. + * The concatenation of these two captures creates the {@link String} which + * can be used with {@link String#endsWith(String)}. + * + * For ease of reference, the non-Java escaped form of this pattern is: + * \^?\.\*([^\\\[\(\.\*\?\+\|\{\$]+)(?:\\?(\.\w+))?\$? + */ + private static final Pattern ENDS_WITH = Pattern + .compile("\\^?\\.\\*([^\\\\\\[\\(\\.\\*\\?\\+\\|\\{\\$]+)(?:\\\\?(\\.\\w+))?\\$?"); + + protected String regex; + protected Pattern pattern; + protected String endsWith; + + public RegexStringFilter(String regex) { + this.regex = regex; + optimize(); + } + + public String getRegex() { + return this.regex; + } + + public String getEndsWith() { + return this.endsWith; + } + + protected void optimize() { + final Matcher matcher = ENDS_WITH.matcher(this.regex); + if (matcher.matches()) { + final String literalPath = matcher.group(1); + final String fileExtension = matcher.group(2); + if (fileExtension != null) { + this.endsWith = literalPath + fileExtension; + } else { + this.endsWith = literalPath; + } + } else { + try { + this.pattern = Pattern.compile(this.regex); + } catch (PatternSyntaxException ignored) { + // If the regular expression is invalid, then pattern will be + // null. + } + } + } + + @Override + public boolean filter(String obj) { + if (this.endsWith != null) { + return obj.endsWith(this.endsWith); + } else if (this.pattern != null) { + return this.pattern.matcher(obj).matches(); + } else { + // The regular expression must have been bad, so it will match + // nothing. + return false; + } + } + + @Override + public String toString() { + return "matches " + this.regex; + } +} diff --git a/pom.xml b/pom.xml index ef6b71625dc..81421c67c4a 100644 --- a/pom.xml +++ b/pom.xml @@ -1185,6 +1185,18 @@ pmd-dist + + + pmd-compat6-module + + + !skipPmdCompat6 + + + + pmd-compat6 + + From 1c43a4689c4e1ce76365ee17290008dd083f21e4 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 9 Nov 2023 20:58:41 +0100 Subject: [PATCH 2/8] Log failing integration tests --- pmd-compat6/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pmd-compat6/pom.xml b/pmd-compat6/pom.xml index 2410c7982a6..7bcdddf695b 100644 --- a/pmd-compat6/pom.xml +++ b/pmd-compat6/pom.xml @@ -54,6 +54,7 @@ src/it/settings.xml ${project.build.directory}/local-repo verify.bsh + true From 64d20e036840fd6efd34619015b17298873dc3b4 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 9 Nov 2023 21:29:20 +0100 Subject: [PATCH 3/8] Fix it verification scripts, only build for java11 --- pmd-compat6/src/it/cpd-for-java/pom.xml | 4 ++-- pmd-compat6/src/it/cpd-for-java/verify.bsh | 5 +++-- pmd-compat6/src/it/cpd-for-javascript/verify.bsh | 5 +++-- pmd-compat6/src/it/cpd-for-jsp/verify.bsh | 5 +++-- pmd-compat6/src/it/pmd-for-java/pom.xml | 4 ++-- pmd-compat6/src/it/pmd-for-java/verify.bsh | 3 ++- pmd-compat6/src/it/pmd-for-javascript/verify.bsh | 3 ++- pmd-compat6/src/it/pmd-for-jsp/verify.bsh | 3 ++- 8 files changed, 19 insertions(+), 13 deletions(-) diff --git a/pmd-compat6/src/it/cpd-for-java/pom.xml b/pmd-compat6/src/it/cpd-for-java/pom.xml index ea93703565a..285c37467b5 100644 --- a/pmd-compat6/src/it/cpd-for-java/pom.xml +++ b/pmd-compat6/src/it/cpd-for-java/pom.xml @@ -9,8 +9,8 @@ 1.0-SNAPSHOT - 21 - 21 + 11 + 11 UTF-8 diff --git a/pmd-compat6/src/it/cpd-for-java/verify.bsh b/pmd-compat6/src/it/cpd-for-java/verify.bsh index c5fff32f9a4..f209426cd44 100644 --- a/pmd-compat6/src/it/cpd-for-java/verify.bsh +++ b/pmd-compat6/src/it/cpd-for-java/verify.bsh @@ -17,7 +17,8 @@ String buildLog = readFile(buildLogPath); if (!buildLog.contains("[INFO] CPD Failure: Found 8 lines of duplicated code at locations:")) { throw new RuntimeException("No CPD failures detected, did CPD run?"); } -if (!buildLog.contains("cpd-for-java/src/main/java/org/example/ClassA.java line 3")) { +File classA = new File("cpd-for-java/src/main/java/org/example/ClassA.java"); +if (!buildLog.contains(classA + " line 3")) { throw new RuntimeException("No CPD failures detected, did CPD run?"); } @@ -30,6 +31,6 @@ String cpdXml = readFile(cpdXmlReport); if (!cpdXml.contains("")) { throw new RuntimeException("Expected duplication has not been reported"); } -if (!cpdXml.contains("cpd-for-java/src/main/java/org/example/ClassA.java\"/>")) { +if (!cpdXml.contains(classA + "\"/>")) { throw new RuntimeException("Expected duplication has not been reported"); } diff --git a/pmd-compat6/src/it/cpd-for-javascript/verify.bsh b/pmd-compat6/src/it/cpd-for-javascript/verify.bsh index c400c18faa5..e31d6be8ee0 100644 --- a/pmd-compat6/src/it/cpd-for-javascript/verify.bsh +++ b/pmd-compat6/src/it/cpd-for-javascript/verify.bsh @@ -17,7 +17,8 @@ String buildLog = readFile(buildLogPath); if (!buildLog.contains("[INFO] CPD Failure: Found 7 lines of duplicated code at locations:")) { throw new RuntimeException("No CPD failures detected, did CPD run?"); } -if (!buildLog.contains("cpd-for-javascript/src/main/js/globalVariable.js line 1")) { +File globalVariable = new File("cpd-for-javascript/src/main/js/globalVariable.js"); +if (!buildLog.contains(globalVariable + " line 1")) { throw new RuntimeException("No CPD failures detected, did CPD run?"); } @@ -30,6 +31,6 @@ String cpdXml = readFile(cpdXmlReport); if (!cpdXml.contains("")) { throw new RuntimeException("Expected duplication has not been reported"); } -if (!cpdXml.contains("cpd-for-javascript/src/main/js/globalVariable.js\"/>")) { +if (!cpdXml.contains(globalVariable + "\"/>")) { throw new RuntimeException("Expected duplication has not been reported"); } diff --git a/pmd-compat6/src/it/cpd-for-jsp/verify.bsh b/pmd-compat6/src/it/cpd-for-jsp/verify.bsh index 36b11503dd6..17420241fef 100644 --- a/pmd-compat6/src/it/cpd-for-jsp/verify.bsh +++ b/pmd-compat6/src/it/cpd-for-jsp/verify.bsh @@ -17,7 +17,8 @@ String buildLog = readFile(buildLogPath); if (!buildLog.contains("[INFO] CPD Failure: Found 3 lines of duplicated code at locations:")) { throw new RuntimeException("No CPD failures detected, did CPD run?"); } -if (!buildLog.contains("cpd-for-jsp/src/main/jsp/classAttribute.jsp line 1")) { +File classAttribute = new File("cpd-for-jsp/src/main/jsp/classAttribute.jsp"); +if (!buildLog.contains(classAttribute + " line 1")) { throw new RuntimeException("No CPD failures detected, did CPD run?"); } @@ -30,6 +31,6 @@ String cpdXml = readFile(cpdXmlReport); if (!cpdXml.contains("")) { throw new RuntimeException("Expected duplication has not been reported"); } -if (!cpdXml.contains("cpd-for-jsp/src/main/jsp/classAttribute.jsp\"/>")) { +if (!cpdXml.contains(classAttribute + "\"/>")) { throw new RuntimeException("Expected duplication has not been reported"); } diff --git a/pmd-compat6/src/it/pmd-for-java/pom.xml b/pmd-compat6/src/it/pmd-for-java/pom.xml index 7d77929cf98..32991b1ba3e 100644 --- a/pmd-compat6/src/it/pmd-for-java/pom.xml +++ b/pmd-compat6/src/it/pmd-for-java/pom.xml @@ -9,8 +9,8 @@ 1.0-SNAPSHOT - 21 - 21 + 11 + 11 UTF-8 diff --git a/pmd-compat6/src/it/pmd-for-java/verify.bsh b/pmd-compat6/src/it/pmd-for-java/verify.bsh index 47eed9d5eaa..f148872c0e1 100644 --- a/pmd-compat6/src/it/pmd-for-java/verify.bsh +++ b/pmd-compat6/src/it/pmd-for-java/verify.bsh @@ -27,6 +27,7 @@ String pmdXml = readFile(pmdXmlReport); if (!pmdXml.contains("")) { +File mainFile = new File("pmd-for-java/src/main/java/org/example/Main.java"); +if (!pmdXml.contains(mainFile + "\">")) { throw new RuntimeException("Expected violation has not been reported"); } diff --git a/pmd-compat6/src/it/pmd-for-javascript/verify.bsh b/pmd-compat6/src/it/pmd-for-javascript/verify.bsh index 54ab5cedb9c..0d190f5f00f 100644 --- a/pmd-compat6/src/it/pmd-for-javascript/verify.bsh +++ b/pmd-compat6/src/it/pmd-for-javascript/verify.bsh @@ -27,6 +27,7 @@ String pmdXml = readFile(pmdXmlReport); if (!pmdXml.contains("")) { +File globalVariable = new File("pmd-for-javascript/src/main/js/globalVariable.js"); +if (!pmdXml.contains(globalVariable + "\">")) { throw new RuntimeException("Expected violation has not been reported"); } diff --git a/pmd-compat6/src/it/pmd-for-jsp/verify.bsh b/pmd-compat6/src/it/pmd-for-jsp/verify.bsh index e564c7a9509..55f734081ea 100644 --- a/pmd-compat6/src/it/pmd-for-jsp/verify.bsh +++ b/pmd-compat6/src/it/pmd-for-jsp/verify.bsh @@ -27,6 +27,7 @@ String pmdXml = readFile(pmdXmlReport); if (!pmdXml.contains("")) { +File classAttribute = new File("pmd-for-jsp/src/main/jsp/classAttribute.jsp"); +if (!pmdXml.contains(classAttribute + "\">")) { throw new RuntimeException("Expected violation has not been reported"); } From dd2bb98aa06a126b787171042fb4942e1225579e Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 9 Nov 2023 21:57:21 +0100 Subject: [PATCH 4/8] Fix PMD dogfood issue --- .../pmd/util/filter/RegexStringFilter.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/RegexStringFilter.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/RegexStringFilter.java index fd8b933cb40..010f8f60659 100644 --- a/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/RegexStringFilter.java +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/RegexStringFilter.java @@ -82,13 +82,12 @@ protected void optimize() { public boolean filter(String obj) { if (this.endsWith != null) { return obj.endsWith(this.endsWith); - } else if (this.pattern != null) { - return this.pattern.matcher(obj).matches(); - } else { - // The regular expression must have been bad, so it will match - // nothing. - return false; } + + return this.pattern != null && this.pattern.matcher(obj).matches(); + // if pattern was null, + // The regular expression must have been bad, so it will match + // nothing. } @Override From 9dd646a03017dd77d424d9d78ee07136515deec6 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Tue, 14 Nov 2023 20:21:21 +0100 Subject: [PATCH 5/8] Support formats csv and txt for CPD --- .../src/it/cpd-for-java/invoker.properties | 4 +- pmd-compat6/src/it/cpd-for-java/verify.bsh | 30 +++- .../net/sourceforge/pmd/cpd/CSVRenderer.java | 132 ++++++++++++++++++ .../sourceforge/pmd/cpd/RendererHelper.java | 49 +++++++ .../sourceforge/pmd/cpd/SimpleRenderer.java | 90 ++++++++++++ .../net/sourceforge/pmd/cpd/XMLRenderer.java | 21 +-- 6 files changed, 303 insertions(+), 23 deletions(-) create mode 100644 pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/CSVRenderer.java create mode 100644 pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/RendererHelper.java create mode 100644 pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/SimpleRenderer.java diff --git a/pmd-compat6/src/it/cpd-for-java/invoker.properties b/pmd-compat6/src/it/cpd-for-java/invoker.properties index 0d92d959f3d..a6149b5b142 100644 --- a/pmd-compat6/src/it/cpd-for-java/invoker.properties +++ b/pmd-compat6/src/it/cpd-for-java/invoker.properties @@ -1,2 +1,4 @@ -invoker.goals = verify +invoker.goals.1 = verify +invoker.goals.2 = pmd:cpd-check -Dformat=csv +invoker.goals.3 = pmd:cpd-check -Dformat=txt invoker.buildResult = failure diff --git a/pmd-compat6/src/it/cpd-for-java/verify.bsh b/pmd-compat6/src/it/cpd-for-java/verify.bsh index f209426cd44..8eaad55526d 100644 --- a/pmd-compat6/src/it/cpd-for-java/verify.bsh +++ b/pmd-compat6/src/it/cpd-for-java/verify.bsh @@ -14,6 +14,9 @@ String readFile(File file) throws IOException { File buildLogPath = new File(basedir, "build.log"); String buildLog = readFile(buildLogPath); +if (buildLog.contains("An API incompatibility was encountered while")) { + throw new RuntimeException("Executing failed due to API incompatibility"); +} if (!buildLog.contains("[INFO] CPD Failure: Found 8 lines of duplicated code at locations:")) { throw new RuntimeException("No CPD failures detected, did CPD run?"); } @@ -23,8 +26,7 @@ if (!buildLog.contains(classA + " line 3")) { } File cpdXmlReport = new File(basedir, "target/cpd.xml"); -if(!cpdXmlReport.exists()) -{ +if (!cpdXmlReport.exists()) { throw new FileNotFoundException("Could not find cpd xml report: " + cpdXmlReport); } String cpdXml = readFile(cpdXmlReport); @@ -34,3 +36,27 @@ if (!cpdXml.contains("")) { if (!cpdXml.contains(classA + "\"/>")) { throw new RuntimeException("Expected duplication has not been reported"); } + +File csvReport = new File(basedir, "target/cpd.csv"); +if (!csvReport.exists()) { + throw new FileNotFoundException("Could not find cpd csv report: " + csvReport); +} +String csv = readFile(csvReport); +if (!csv.contains("8,67,2,3,")) { + throw new RuntimeException("Expected duplication in CSV has not been reported"); +} +if (!csv.contains(classA + ",")) { + throw new RuntimeException("Expected duplication in CSV has not been reported"); +} + +File textReport = new File(basedir, "target/cpd.txt"); +if (!textReport.exists()) { + throw new FileNotFoundException("Could not find cpd text report: " + textReport); +} +String text = readFile(textReport); +if (!text.contains("Found a 8 line (67 tokens) duplication in the following files:")) { + throw new RuntimeException("Expected duplication in TXT has not been reported"); +} +if (!text.contains("Starting at line 3 of ") && !text.contains(classA.toString())) { + throw new RuntimeException("Expected duplication in TXT has not been reported"); +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/CSVRenderer.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/CSVRenderer.java new file mode 100644 index 00000000000..0c030d580a8 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/CSVRenderer.java @@ -0,0 +1,132 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This class has been taken from 7.0.0-SNAPSHOT +// Changes: implements old interface CPDRenderer, old render(Iterator matches, Writer writer) method + +package net.sourceforge.pmd.cpd; + +import java.io.IOException; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import org.apache.commons.lang3.StringEscapeUtils; + +import net.sourceforge.pmd.cpd.renderer.CPDRenderer; +import net.sourceforge.pmd.lang.document.FileLocation; +import net.sourceforge.pmd.lang.document.TextFile; +import net.sourceforge.pmd.lang.java.JavaLanguageModule; + +/** + * Renders a report to CSV. The CSV format renders each match (duplication) + * as a single line with the following columns: + *

    + *
  • lines (optional): The number of lines the first mark of a match spans. + * Only output if the {@code lineCountPerFile} is disabled (see ctor params).
  • + *
  • tokens: The number of duplicated tokens in a match (size of the match).
  • + *
  • occurrences: The number of duplicates in a match (number of times the tokens were found in distinct places).
  • + *
+ * + *

Trailing each line are pairs (or triples, if {@code lineCountPerFile} is enabled) + * of fields describing each file where the duplication was found in the format + * {@code (start line, line count (optional), file path)}. These repeat at least twice. + * + *

Examples

+ *

+ * Example without {@code lineCountPerFile}: + *

{@code
+ * lines,tokens,occurrences
+ * 10,75,2,48,/var/file1,73,/var/file2
+ * }
+ * This describes one match with the following characteristics: + *
    + *
  • The first duplicate instance is 10 lines long; + *
  • 75 duplicated tokens; + *
  • 2 duplicate instances; + *
  • The first duplicate instance is in file {@code /var/file1} and starts at line 48;
  • + *
  • The second duplicate instance is in file {@code /var/file2} and starts at line 73.
  • + *
+ *

+ * Example with {@code lineCountPerFile}: + *

{@code
+ * tokens,occurrences
+ * 75,2,48,10,/var/file1,73,12,/var/file2
+ * }
+ * This describes one match with the following characteristics: + *
    + *
  • 75 duplicated tokens + *
  • 2 duplicate instances + *
  • The first duplicate instance is in file {@code /var/file1}, starts at line 48, and is 10 lines long;
  • + *
  • The second duplicate instance is in file {@code /var/file2}, starts at line 73, and is 12 lines long.
  • + *
+ */ +public class CSVRenderer implements CPDReportRenderer, CPDRenderer { + + private final char separator; + private final boolean lineCountPerFile; + + public static final char DEFAULT_SEPARATOR = ','; + public static final boolean DEFAULT_LINECOUNTPERFILE = false; + + public CSVRenderer() { + this(DEFAULT_SEPARATOR, DEFAULT_LINECOUNTPERFILE); + } + + public CSVRenderer(boolean lineCountPerFile) { + this(DEFAULT_SEPARATOR, lineCountPerFile); + } + + public CSVRenderer(char separatorChar) { + this(separatorChar, DEFAULT_LINECOUNTPERFILE); + } + + public CSVRenderer(char separatorChar, boolean lineCountPerFile) { + this.separator = separatorChar; + this.lineCountPerFile = lineCountPerFile; + } + + @Override + public void render(CPDReport report, Writer writer) throws IOException { + if (!lineCountPerFile) { + writer.append("lines").append(separator); + } + writer.append("tokens").append(separator).append("occurrences").append(System.lineSeparator()); + + for (Match match : report.getMatches()) { + if (!lineCountPerFile) { + writer.append(String.valueOf(match.getLineCount())).append(separator); + } + writer.append(String.valueOf(match.getTokenCount())).append(separator) + .append(String.valueOf(match.getMarkCount())).append(separator); + for (Iterator marks = match.iterator(); marks.hasNext();) { + Mark mark = marks.next(); + FileLocation loc = mark.getLocation(); + + writer.append(String.valueOf(loc.getStartLine())).append(separator); + if (lineCountPerFile) { + writer.append(String.valueOf(loc.getLineCount())).append(separator); + } + writer.append(StringEscapeUtils.escapeCsv(report.getDisplayName(loc.getFileId()))); + if (marks.hasNext()) { + writer.append(separator); + } + } + writer.append(System.lineSeparator()); + } + writer.flush(); + } + + // ------------------- compat extensions -------------------- + @Override + public void render(Iterator matches, Writer writer) throws IOException { + RendererHelper.render(matches, writer, this); + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/RendererHelper.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/RendererHelper.java new file mode 100644 index 00000000000..b77b570ae04 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/RendererHelper.java @@ -0,0 +1,49 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cpd; + +import java.io.IOException; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import net.sourceforge.pmd.cpd.renderer.CPDRenderer; +import net.sourceforge.pmd.lang.document.TextFile; +import net.sourceforge.pmd.lang.java.JavaLanguageModule; + +final class RendererHelper { + private RendererHelper() { + // utility class + } + + static void render(Iterator matches, Writer writer, CPDReportRenderer renderer) throws IOException { + List matchesList = new ArrayList<>(); + matches.forEachRemaining(matchesList::add); + + List textFiles = new ArrayList<>(); + Set paths = new HashSet<>(); + for (Match match : matchesList) { + for (Mark mark : match.getMarkSet()) { + paths.add(mark.getFilename()); + } + } + for (String path : paths) { + textFiles.add(TextFile.forPath(Paths.get(path), StandardCharsets.UTF_8, JavaLanguageModule.getInstance().getDefaultVersion())); + } + + try (SourceManager sourceManager = new SourceManager(textFiles)) { + CPDReport report = new CPDReport(sourceManager, matchesList, Collections.emptyMap()); + renderer.render(report, writer); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/SimpleRenderer.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/SimpleRenderer.java new file mode 100644 index 00000000000..d4aaff80386 --- /dev/null +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/SimpleRenderer.java @@ -0,0 +1,90 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +// This class has been taken from 7.0.0-SNAPSHOT +// Changes: implements old interface CPDRenderer, old render(Iterator matches, Writer writer) method + +package net.sourceforge.pmd.cpd; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Writer; +import java.util.Iterator; + +import net.sourceforge.pmd.cpd.renderer.CPDRenderer; +import net.sourceforge.pmd.lang.document.Chars; +import net.sourceforge.pmd.lang.document.FileLocation; +import net.sourceforge.pmd.util.StringUtil; + +public class SimpleRenderer implements CPDReportRenderer, CPDRenderer { + + private String separator; + private boolean trimLeadingWhitespace; + + public static final String DEFAULT_SEPARATOR = "====================================================================="; + + public SimpleRenderer() { + this(false); + } + + public SimpleRenderer(boolean trimLeadingWhitespace) { + this(DEFAULT_SEPARATOR); + this.trimLeadingWhitespace = trimLeadingWhitespace; + } + + public SimpleRenderer(String theSeparator) { + separator = theSeparator; + } + + @Override + public void render(CPDReport report, Writer writer0) throws IOException { + PrintWriter writer = new PrintWriter(writer0); + Iterator matches = report.getMatches().iterator(); + if (matches.hasNext()) { + renderOn(report, writer, matches.next()); + } + + while (matches.hasNext()) { + Match match = matches.next(); + writer.println(separator); + renderOn(report, writer, match); + } + writer.flush(); + } + + private void renderOn(CPDReport report, PrintWriter writer, Match match) throws IOException { + + writer.append("Found a ").append(String.valueOf(match.getLineCount())).append(" line (").append(String.valueOf(match.getTokenCount())) + .append(" tokens) duplication in the following files: ").println(); + + for (Mark mark : match) { + FileLocation loc = mark.getLocation(); + writer.append("Starting at line ") + .append(String.valueOf(loc.getStartLine())) + .append(" of ").append(report.getDisplayName(loc.getFileId())) + .println(); + } + + writer.println(); // add a line to separate the source from the desc above + + Chars source = report.getSourceCodeSlice(match.getFirstMark()); + + if (trimLeadingWhitespace) { + for (Chars line : StringUtil.linesWithTrimIndent(source)) { + line.writeFully(writer); + writer.println(); + } + return; + } + + source.writeFully(writer); + writer.println(); + } + + // ------------------- compat extensions -------------------- + @Override + public void render(Iterator matches, Writer writer) throws IOException { + RendererHelper.render(matches, writer, this); + } +} diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java index 4ebab60230f..59efa1893d4 100644 --- a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java @@ -169,25 +169,6 @@ private Element createDuplicationElement(Document doc, Match match) { // ------------------- compat extensions -------------------- @Override public void render(Iterator matches, Writer writer) throws IOException { - List matchesList = new ArrayList<>(); - matches.forEachRemaining(matchesList::add); - - List textFiles = new ArrayList<>(); - Set paths = new HashSet<>(); - for (Match match : matchesList) { - for (Mark mark : match.getMarkSet()) { - paths.add(mark.getFilename()); - } - } - for (String path : paths) { - textFiles.add(TextFile.forPath(Paths.get(path), StandardCharsets.UTF_8, JavaLanguageModule.getInstance().getDefaultVersion())); - } - - try (SourceManager sourcManager = new SourceManager(textFiles)) { - CPDReport report = new CPDReport(sourcManager, matchesList, Collections.emptyMap()); - render(report, writer); - } catch (Exception e) { - throw new RuntimeException(e); - } + RendererHelper.render(matches, writer, this); } } From a0814be8953668431d014dc5e776fd91dfdafab8 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Tue, 14 Nov 2023 20:31:15 +0100 Subject: [PATCH 6/8] Test formats csv and txt for PMD --- .../src/it/pmd-for-java/invoker.properties | 4 +++- pmd-compat6/src/it/pmd-for-java/verify.bsh | 24 +++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/pmd-compat6/src/it/pmd-for-java/invoker.properties b/pmd-compat6/src/it/pmd-for-java/invoker.properties index 0d92d959f3d..bfaaa1661c7 100644 --- a/pmd-compat6/src/it/pmd-for-java/invoker.properties +++ b/pmd-compat6/src/it/pmd-for-java/invoker.properties @@ -1,2 +1,4 @@ -invoker.goals = verify +invoker.goals.1 = verify +invoker.goals.2 = pmd:check -Dformat=csv +invoker.goals.3 = pmd:check -Dformat=txt invoker.buildResult = failure diff --git a/pmd-compat6/src/it/pmd-for-java/verify.bsh b/pmd-compat6/src/it/pmd-for-java/verify.bsh index f148872c0e1..f0325f4c97e 100644 --- a/pmd-compat6/src/it/pmd-for-java/verify.bsh +++ b/pmd-compat6/src/it/pmd-for-java/verify.bsh @@ -14,13 +14,15 @@ String readFile(File file) throws IOException { File buildLogPath = new File(basedir, "build.log"); String buildLog = readFile(buildLogPath); +if (buildLog.contains("An API incompatibility was encountered while")) { + throw new RuntimeException("Executing failed due to API incompatibility"); +} if (!buildLog.contains("[INFO] PMD Failure: org.example.Main:5 Rule:UnusedLocalVariable")) { throw new RuntimeException("No pmd violation detected, did PMD run?"); } File pmdXmlReport = new File(basedir, "target/pmd.xml"); -if(!pmdXmlReport.exists()) -{ +if(!pmdXmlReport.exists()) { throw new FileNotFoundException("Could not find pmd xml report: " + pmdXmlReport); } String pmdXml = readFile(pmdXmlReport); @@ -31,3 +33,21 @@ File mainFile = new File("pmd-for-java/src/main/java/org/example/Main.java"); if (!pmdXml.contains(mainFile + "\">")) { throw new RuntimeException("Expected violation has not been reported"); } + +File pmdCsvReport = new File(basedir, "target/pmd.csv"); +if (!pmdCsvReport.exists()) { + throw new FileNotFoundException("Could not find pmd CSV report: " + pmdCsvReport); +} +String csvReport = readFile(pmdCsvReport); +if (!csvReport.contains(mainFile + "\",\"3\",\"5\",\"Avoid unused local")) { + throw new RuntimeException("Expected violation has not been reported in CSV"); +} + +File pmdTextReport = new File(basedir, "target/pmd.txt"); +if (!pmdTextReport.exists()) { + throw new FileNotFoundException("Could not find pmd TXT report: " + pmdTextReport); +} +String textReport = readFile(pmdTextReport); +if (!textReport.contains(mainFile + ":5:\tUnusedLocalVariable")) { + throw new RuntimeException("Expected violation has not been reported in TXT"); +} From 94cdd8a0225309be67f85a1e0dd99b4a3a154282 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Tue, 14 Nov 2023 20:44:04 +0100 Subject: [PATCH 7/8] [doc] Update release notes and migration guide (#4717) --- docs/pages/pmd/userdocs/migrating_to_pmd7.md | 79 ++++---------------- docs/pages/release_notes.md | 27 +++++++ 2 files changed, 42 insertions(+), 64 deletions(-) diff --git a/docs/pages/pmd/userdocs/migrating_to_pmd7.md b/docs/pages/pmd/userdocs/migrating_to_pmd7.md index 3c03736f57e..22ccbc3422b 100644 --- a/docs/pages/pmd/userdocs/migrating_to_pmd7.md +++ b/docs/pages/pmd/userdocs/migrating_to_pmd7.md @@ -3138,70 +3138,21 @@ See the use case[I'm using only built-in rules](#im-using-only-built-in-rules) a #### Maven -* Due to some changes in PMD's API, you can't simply pull in the new PMD 7 dependency using the - approach documented in [Upgrading PMD at Runtime](https://maven.apache.org/plugins/maven-pmd-plugin/examples/upgrading-PMD-at-runtime.html). -* A new maven-pmd-plugin version, that supports PMD 7 is in the works. See [MPMD-379](https://issues.apache.org/jira/browse/MPMD-379). -* As long as no new maven-pmd-plugin version with PMD 7 support is released, you can try it out using a - SNAPSHOT version: - 1. Add the Apache SNAPSHOT maven repository: - ```xml - - apache.snapshots - Apache Snapshot Repository - https://repository.apache.org/snapshots - - false - - - true - - - ``` - 2. Use the version **3.21.1-pmd-7-SNAPSHOT** of the maven-pmd-plugin - 3. Override the dependencies of the plugin to use PMD 7, e.g. - ```xml - - - {{site.pmd.version}} - 3.21.1-pmd-7.0.0-SNAPSHOT - - ... - - - - - org.apache.maven.plugins - maven-pmd-plugin - ${mavenPmdPluginVersion} - - - net.sourceforge.pmd - pmd-core - ${pmdVersion} - - - net.sourceforge.pmd - pmd-java - ${pmdVersion} - - - net.sourceforge.pmd - pmd-javascript - ${pmdVersion} - - - net.sourceforge.pmd - pmd-jsp - ${pmdVersion} - - - - - - - ... - - ``` +* Due to some changes in PMD's API, you can't simply pull in the new PMD 7 dependency. +* However, there is now a compatibility module, that makes it possible to use PMD 7 with Maven. In addition to the PMD 7 + dependencies documented in [Upgrading PMD at Runtime](https://maven.apache.org/plugins/maven-pmd-plugin/examples/upgrading-PMD-at-runtime.html) + you need to add additionally the following dependency (available with 7.0.0-rc4): + +```xml + + net.sourceforge.pmd + pmd-compat6 + ${pmdVersion} + +``` + +It is important to add this dependency as the **first** in the list, so that maven-pmd-plugin sees the (old) +compatible versions of some classes. #### Gradle diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index 88fd9ff46ce..f1d0d6f06d9 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -41,6 +41,31 @@ The remaining section describes the complete release notes for 7.0.0. #### New and Noteworthy +##### Maven PMD Plugin compatibility with PMD 7 + +In order to use PMD 7 with [maven-pmd-plugin](https://maven.apache.org/plugins/maven-pmd-plugin/) a new +compatibility module has been created. This allows to use PMD 7 by simply adding one additional dependency: + +1. Follow the guide [Upgrading PMD at Runtime](https://maven.apache.org/plugins/maven-pmd-plugin/examples/upgrading-PMD-at-runtime.html) +2. Add additionally the following dependency: + +```xml + + net.sourceforge.pmd + pmd-compat6 + ${pmdVersion} + +``` + +It is important to add this dependency as the **first** in the list, so that maven-pmd-plugin sees the (old) +compatible versions of some classes. + +Note: This compatibility modules only works for the built-in rules, that are still available in PMD 7 (you need +to review your rulesets). +As PMD 7 revamped the Java module, if you have custom rules, you need to migrate the rules. +For more information, see the [Detailed Release Notes for PMD 7]({{ baseurl }}pmd_release_notes_pmd7.html#revamped-java) +and [Migration Guide for PMD 7]({{ baseurl }}pmd_userdocs_migrating_to_pmd7.html). + #### Rule Changes **New Rules** @@ -62,6 +87,7 @@ The remaining section describes the complete release notes for 7.0.0. * miscellaneous * [#4699](https://github.com/pmd/pmd/pull/4699): Make PMD buildable with java 21 * [#4586](https://github.com/pmd/pmd/pull/4586): Use explicit encoding in ruleset xml files + * [#4741](https://github.com/pmd/pmd/pull/4741): Add pmd-compat6 module for maven-pmd-plugin * apex-performance * [#4675](https://github.com/pmd/pmd/issues/4675): \[apex] New Rule: OperationWithHighCostInLoop * java-codestyle @@ -440,6 +466,7 @@ See also [Detailed Release Notes for PMD 7]({{ baseurl }}pmd_release_notes_pmd7. * [#4586](https://github.com/pmd/pmd/pull/4586): Use explicit encoding in ruleset xml files * [#4691](https://github.com/pmd/pmd/issues/4691): \[CVEs] Critical and High CEVs reported on PMD and PMD dependencies * [#4699](https://github.com/pmd/pmd/pull/4699): Make PMD buildable with java 21 + * [#4741](https://github.com/pmd/pmd/pull/4741): Add pmd-compat6 module for maven-pmd-plugin * ant * [#4080](https://github.com/pmd/pmd/issues/4080): \[ant] Split off Ant integration into a new submodule * core From aed95e6870978c10910be102b4caaf38401aea91 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Tue, 14 Nov 2023 21:07:37 +0100 Subject: [PATCH 8/8] Remove unused imports --- .../main/java/net/sourceforge/pmd/cpd/CSVRenderer.java | 9 --------- .../java/net/sourceforge/pmd/cpd/RendererHelper.java | 1 - .../main/java/net/sourceforge/pmd/cpd/XMLRenderer.java | 9 --------- 3 files changed, 19 deletions(-) diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/CSVRenderer.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/CSVRenderer.java index 0c030d580a8..0f6b313d3ef 100644 --- a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/CSVRenderer.java +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/CSVRenderer.java @@ -9,21 +9,12 @@ import java.io.IOException; import java.io.Writer; -import java.nio.charset.StandardCharsets; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; import java.util.Iterator; -import java.util.List; -import java.util.Set; import org.apache.commons.lang3.StringEscapeUtils; import net.sourceforge.pmd.cpd.renderer.CPDRenderer; import net.sourceforge.pmd.lang.document.FileLocation; -import net.sourceforge.pmd.lang.document.TextFile; -import net.sourceforge.pmd.lang.java.JavaLanguageModule; /** * Renders a report to CSV. The CSV format renders each match (duplication) diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/RendererHelper.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/RendererHelper.java index b77b570ae04..f7291e9ae53 100644 --- a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/RendererHelper.java +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/RendererHelper.java @@ -15,7 +15,6 @@ import java.util.List; import java.util.Set; -import net.sourceforge.pmd.cpd.renderer.CPDRenderer; import net.sourceforge.pmd.lang.document.TextFile; import net.sourceforge.pmd.lang.java.JavaLanguageModule; diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java index 59efa1893d4..31f2b41d0a1 100644 --- a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java +++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java @@ -9,15 +9,8 @@ import java.io.IOException; import java.io.Writer; -import java.nio.charset.StandardCharsets; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; import java.util.Iterator; -import java.util.List; import java.util.Map; -import java.util.Set; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -35,8 +28,6 @@ import net.sourceforge.pmd.lang.document.Chars; import net.sourceforge.pmd.lang.document.FileId; import net.sourceforge.pmd.lang.document.FileLocation; -import net.sourceforge.pmd.lang.document.TextFile; -import net.sourceforge.pmd.lang.java.JavaLanguageModule; import net.sourceforge.pmd.util.StringUtil; /**