From 537a7e203ac1e6c0db401f917b271b057ee94953 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Tue, 9 Aug 2022 20:27:01 +0200 Subject: [PATCH 001/254] ant: create pmd-ant module --- pmd-ant/pom.xml | 83 +++++++++++++++++++ .../net/sourceforge/pmd/ant}/CPDTask.java | 14 +++- .../net/sourceforge/pmd/ant/Formatter.java | 0 .../java/net/sourceforge/pmd/ant/PMDTask.java | 0 .../sourceforge/pmd/ant/RuleSetWrapper.java | 0 .../sourceforge/pmd/ant/SourceLanguage.java | 0 .../pmd/ant/internal/PMDTaskImpl.java | 0 .../Slf4jSimpleConfigurationForAnt.java | 0 .../sourceforge/pmd/ant/AbstractAntTest.java | 0 .../net/sourceforge/pmd/ant/CPDTaskTest.java | 2 +- .../sourceforge/pmd/ant/CpdDummyLanguage.java | 19 +++++ .../sourceforge/pmd/ant/FormatterTest.java | 2 +- .../net/sourceforge/pmd/ant/PMDTaskTest.java | 4 +- .../services/net.sourceforge.pmd.cpd.Language | 1 + .../net/sourceforge/pmd/ant/src/sample.dummy | 0 .../sourceforge/pmd/ant/xml/cpdtasktest.xml | 2 +- .../pmd/ant/xml/expected-pmd-ant-xml.xml | 0 .../sourceforge/pmd/ant/xml/pmdtasktest.xml | 0 pmd-core/pom.xml | 5 -- pmd-test/pom.xml | 4 + pom.xml | 6 ++ 21 files changed, 130 insertions(+), 12 deletions(-) create mode 100644 pmd-ant/pom.xml rename {pmd-core/src/main/java/net/sourceforge/pmd/cpd => pmd-ant/src/main/java/net/sourceforge/pmd/ant}/CPDTask.java (95%) rename {pmd-core => pmd-ant}/src/main/java/net/sourceforge/pmd/ant/Formatter.java (100%) rename {pmd-core => pmd-ant}/src/main/java/net/sourceforge/pmd/ant/PMDTask.java (100%) rename {pmd-core => pmd-ant}/src/main/java/net/sourceforge/pmd/ant/RuleSetWrapper.java (100%) rename {pmd-core => pmd-ant}/src/main/java/net/sourceforge/pmd/ant/SourceLanguage.java (100%) rename {pmd-core => pmd-ant}/src/main/java/net/sourceforge/pmd/ant/internal/PMDTaskImpl.java (100%) rename {pmd-core => pmd-ant}/src/main/java/net/sourceforge/pmd/ant/internal/Slf4jSimpleConfigurationForAnt.java (100%) rename {pmd-core => pmd-ant}/src/test/java/net/sourceforge/pmd/ant/AbstractAntTest.java (100%) rename {pmd-core => pmd-ant}/src/test/java/net/sourceforge/pmd/ant/CPDTaskTest.java (99%) create mode 100644 pmd-ant/src/test/java/net/sourceforge/pmd/ant/CpdDummyLanguage.java rename {pmd-core => pmd-ant}/src/test/java/net/sourceforge/pmd/ant/FormatterTest.java (99%) rename {pmd-core => pmd-ant}/src/test/java/net/sourceforge/pmd/ant/PMDTaskTest.java (98%) create mode 100644 pmd-ant/src/test/resources/META-INF/services/net.sourceforge.pmd.cpd.Language rename {pmd-core => pmd-ant}/src/test/resources/net/sourceforge/pmd/ant/src/sample.dummy (100%) rename {pmd-core => pmd-ant}/src/test/resources/net/sourceforge/pmd/ant/xml/cpdtasktest.xml (88%) rename {pmd-core => pmd-ant}/src/test/resources/net/sourceforge/pmd/ant/xml/expected-pmd-ant-xml.xml (100%) rename {pmd-core => pmd-ant}/src/test/resources/net/sourceforge/pmd/ant/xml/pmdtasktest.xml (100%) diff --git a/pmd-ant/pom.xml b/pmd-ant/pom.xml new file mode 100644 index 0000000000..a61772136e --- /dev/null +++ b/pmd-ant/pom.xml @@ -0,0 +1,83 @@ + + + + + + pmd + net.sourceforge.pmd + 7.0.0-SNAPSHOT + + 4.0.0 + + pmd-ant + PMD Ant Integration + Apache Ant integration for PMD. + + + + net.sourceforge.pmd + pmd-core + + + org.apache.ant + ant + + + + + + org.apache.ant + ant-testutil + test + + + org.slf4j + slf4j-simple + test + + + com.github.tomakehurst + wiremock-jre8 + test + + + org.hamcrest + hamcrest + test + + + org.junit.jupiter + junit-jupiter + test + + + org.junit.platform + junit-platform-suite + test + + + pl.pragmatists + JUnitParams + test + + + org.mockito + mockito-core + test + + + com.github.stefanbirkner + system-rules + test + + + com.github.stefanbirkner + system-lambda + test + + + diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDTask.java b/pmd-ant/src/main/java/net/sourceforge/pmd/ant/CPDTask.java similarity index 95% rename from pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDTask.java rename to pmd-ant/src/main/java/net/sourceforge/pmd/ant/CPDTask.java index 1999ba4291..fd4de35934 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDTask.java +++ b/pmd-ant/src/main/java/net/sourceforge/pmd/ant/CPDTask.java @@ -1,8 +1,8 @@ -/** +/* * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.cpd; +package net.sourceforge.pmd.ant; import java.io.BufferedWriter; import java.io.File; @@ -23,6 +23,16 @@ import org.apache.tools.ant.Task; import org.apache.tools.ant.types.EnumeratedAttribute; import org.apache.tools.ant.types.FileSet; +import net.sourceforge.pmd.cpd.CPD; +import net.sourceforge.pmd.cpd.CPDConfiguration; +import net.sourceforge.pmd.cpd.CPDReport; +import net.sourceforge.pmd.cpd.CSVRenderer; +import net.sourceforge.pmd.cpd.Language; +import net.sourceforge.pmd.cpd.LanguageFactory; +import net.sourceforge.pmd.cpd.ReportException; +import net.sourceforge.pmd.cpd.SimpleRenderer; +import net.sourceforge.pmd.cpd.Tokenizer; +import net.sourceforge.pmd.cpd.XMLRenderer; import net.sourceforge.pmd.cpd.renderer.CPDRendererAdapter; import net.sourceforge.pmd.cpd.renderer.CPDReportRenderer; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/ant/Formatter.java b/pmd-ant/src/main/java/net/sourceforge/pmd/ant/Formatter.java similarity index 100% rename from pmd-core/src/main/java/net/sourceforge/pmd/ant/Formatter.java rename to pmd-ant/src/main/java/net/sourceforge/pmd/ant/Formatter.java diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/ant/PMDTask.java b/pmd-ant/src/main/java/net/sourceforge/pmd/ant/PMDTask.java similarity index 100% rename from pmd-core/src/main/java/net/sourceforge/pmd/ant/PMDTask.java rename to pmd-ant/src/main/java/net/sourceforge/pmd/ant/PMDTask.java diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/ant/RuleSetWrapper.java b/pmd-ant/src/main/java/net/sourceforge/pmd/ant/RuleSetWrapper.java similarity index 100% rename from pmd-core/src/main/java/net/sourceforge/pmd/ant/RuleSetWrapper.java rename to pmd-ant/src/main/java/net/sourceforge/pmd/ant/RuleSetWrapper.java diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/ant/SourceLanguage.java b/pmd-ant/src/main/java/net/sourceforge/pmd/ant/SourceLanguage.java similarity index 100% rename from pmd-core/src/main/java/net/sourceforge/pmd/ant/SourceLanguage.java rename to pmd-ant/src/main/java/net/sourceforge/pmd/ant/SourceLanguage.java diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/ant/internal/PMDTaskImpl.java b/pmd-ant/src/main/java/net/sourceforge/pmd/ant/internal/PMDTaskImpl.java similarity index 100% rename from pmd-core/src/main/java/net/sourceforge/pmd/ant/internal/PMDTaskImpl.java rename to pmd-ant/src/main/java/net/sourceforge/pmd/ant/internal/PMDTaskImpl.java diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/ant/internal/Slf4jSimpleConfigurationForAnt.java b/pmd-ant/src/main/java/net/sourceforge/pmd/ant/internal/Slf4jSimpleConfigurationForAnt.java similarity index 100% rename from pmd-core/src/main/java/net/sourceforge/pmd/ant/internal/Slf4jSimpleConfigurationForAnt.java rename to pmd-ant/src/main/java/net/sourceforge/pmd/ant/internal/Slf4jSimpleConfigurationForAnt.java diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/ant/AbstractAntTest.java b/pmd-ant/src/test/java/net/sourceforge/pmd/ant/AbstractAntTest.java similarity index 100% rename from pmd-core/src/test/java/net/sourceforge/pmd/ant/AbstractAntTest.java rename to pmd-ant/src/test/java/net/sourceforge/pmd/ant/AbstractAntTest.java diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/ant/CPDTaskTest.java b/pmd-ant/src/test/java/net/sourceforge/pmd/ant/CPDTaskTest.java similarity index 99% rename from pmd-core/src/test/java/net/sourceforge/pmd/ant/CPDTaskTest.java rename to pmd-ant/src/test/java/net/sourceforge/pmd/ant/CPDTaskTest.java index adce8f770c..5be00096c5 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/ant/CPDTaskTest.java +++ b/pmd-ant/src/test/java/net/sourceforge/pmd/ant/CPDTaskTest.java @@ -1,4 +1,4 @@ -/** +/* * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ diff --git a/pmd-ant/src/test/java/net/sourceforge/pmd/ant/CpdDummyLanguage.java b/pmd-ant/src/test/java/net/sourceforge/pmd/ant/CpdDummyLanguage.java new file mode 100644 index 0000000000..bddeaffcdd --- /dev/null +++ b/pmd-ant/src/test/java/net/sourceforge/pmd/ant/CpdDummyLanguage.java @@ -0,0 +1,19 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.ant; + +import net.sourceforge.pmd.cpd.AbstractLanguage; +import net.sourceforge.pmd.cpd.AnyTokenizer; + +/** + * Sample language for testing LanguageFactory. + * + */ +public class CpdDummyLanguage extends AbstractLanguage { + + public CpdDummyLanguage() { + super("CPD Dummy Language used in tests", "Cpddummy", new AnyTokenizer(), "dummy"); + } +} diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/ant/FormatterTest.java b/pmd-ant/src/test/java/net/sourceforge/pmd/ant/FormatterTest.java similarity index 99% rename from pmd-core/src/test/java/net/sourceforge/pmd/ant/FormatterTest.java rename to pmd-ant/src/test/java/net/sourceforge/pmd/ant/FormatterTest.java index 533b2fd50b..0bb8b1f4f6 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/ant/FormatterTest.java +++ b/pmd-ant/src/test/java/net/sourceforge/pmd/ant/FormatterTest.java @@ -1,4 +1,4 @@ -/** +/* * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/ant/PMDTaskTest.java b/pmd-ant/src/test/java/net/sourceforge/pmd/ant/PMDTaskTest.java similarity index 98% rename from pmd-core/src/test/java/net/sourceforge/pmd/ant/PMDTaskTest.java rename to pmd-ant/src/test/java/net/sourceforge/pmd/ant/PMDTaskTest.java index 1d2aef0d88..d78afc9147 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/ant/PMDTaskTest.java +++ b/pmd-ant/src/test/java/net/sourceforge/pmd/ant/PMDTaskTest.java @@ -1,11 +1,11 @@ -/** +/* * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ package net.sourceforge.pmd.ant; +import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsString; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; diff --git a/pmd-ant/src/test/resources/META-INF/services/net.sourceforge.pmd.cpd.Language b/pmd-ant/src/test/resources/META-INF/services/net.sourceforge.pmd.cpd.Language new file mode 100644 index 0000000000..8d6bddbd3e --- /dev/null +++ b/pmd-ant/src/test/resources/META-INF/services/net.sourceforge.pmd.cpd.Language @@ -0,0 +1 @@ +net.sourceforge.pmd.ant.CpdDummyLanguage diff --git a/pmd-core/src/test/resources/net/sourceforge/pmd/ant/src/sample.dummy b/pmd-ant/src/test/resources/net/sourceforge/pmd/ant/src/sample.dummy similarity index 100% rename from pmd-core/src/test/resources/net/sourceforge/pmd/ant/src/sample.dummy rename to pmd-ant/src/test/resources/net/sourceforge/pmd/ant/src/sample.dummy diff --git a/pmd-core/src/test/resources/net/sourceforge/pmd/ant/xml/cpdtasktest.xml b/pmd-ant/src/test/resources/net/sourceforge/pmd/ant/xml/cpdtasktest.xml similarity index 88% rename from pmd-core/src/test/resources/net/sourceforge/pmd/ant/xml/cpdtasktest.xml rename to pmd-ant/src/test/resources/net/sourceforge/pmd/ant/xml/cpdtasktest.xml index 96caefd243..5a76ac24da 100644 --- a/pmd-core/src/test/resources/net/sourceforge/pmd/ant/xml/cpdtasktest.xml +++ b/pmd-ant/src/test/resources/net/sourceforge/pmd/ant/xml/cpdtasktest.xml @@ -3,7 +3,7 @@ - + diff --git a/pmd-core/src/test/resources/net/sourceforge/pmd/ant/xml/expected-pmd-ant-xml.xml b/pmd-ant/src/test/resources/net/sourceforge/pmd/ant/xml/expected-pmd-ant-xml.xml similarity index 100% rename from pmd-core/src/test/resources/net/sourceforge/pmd/ant/xml/expected-pmd-ant-xml.xml rename to pmd-ant/src/test/resources/net/sourceforge/pmd/ant/xml/expected-pmd-ant-xml.xml diff --git a/pmd-core/src/test/resources/net/sourceforge/pmd/ant/xml/pmdtasktest.xml b/pmd-ant/src/test/resources/net/sourceforge/pmd/ant/xml/pmdtasktest.xml similarity index 100% rename from pmd-core/src/test/resources/net/sourceforge/pmd/ant/xml/pmdtasktest.xml rename to pmd-ant/src/test/resources/net/sourceforge/pmd/ant/xml/pmdtasktest.xml diff --git a/pmd-core/pom.xml b/pmd-core/pom.xml index f37faf57b8..c993793136 100644 --- a/pmd-core/pom.xml +++ b/pmd-core/pom.xml @@ -64,11 +64,6 @@ - - org.apache.ant - ant - provided - net.java.dev.javacc javacc diff --git a/pmd-test/pom.xml b/pmd-test/pom.xml index e26caa145c..3db1e3cd09 100644 --- a/pmd-test/pom.xml +++ b/pmd-test/pom.xml @@ -45,6 +45,10 @@ net.sourceforge.pmd pmd-test-schema + + net.sourceforge.pmd + pmd-ant + org.apache.ant ant diff --git a/pom.xml b/pom.xml index 100f52d713..9cece48232 100644 --- a/pom.xml +++ b/pom.xml @@ -726,6 +726,11 @@ pmd-core ${project.version} + + net.sourceforge.pmd + pmd-ant + ${project.version} + net.sourceforge.pmd pmd-test-schema @@ -1243,5 +1248,6 @@ pmd-visualforce pmd-vm pmd-xml + pmd-ant From dcc3dd56545762b50faecb379edb40ab08989b35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Tue, 9 Aug 2022 20:51:07 +0200 Subject: [PATCH 002/254] core: share test classes with ant module This is to avoid duplicating dummy language module and dummy rules --- pmd-ant/pom.xml | 11 +++++++++++ .../java/net/sourceforge/pmd/ant/PMDTaskTest.java | 7 ++++--- .../services/net.sourceforge.pmd.cpd.Language | 1 - .../net/sourceforge/pmd/ant/xml/pmdtasktest.xml | 6 ++++-- pmd-core/pom.xml | 13 +++++++++++++ 5 files changed, 32 insertions(+), 6 deletions(-) delete mode 100644 pmd-ant/src/test/resources/META-INF/services/net.sourceforge.pmd.cpd.Language diff --git a/pmd-ant/pom.xml b/pmd-ant/pom.xml index a61772136e..8a9195b257 100644 --- a/pmd-ant/pom.xml +++ b/pmd-ant/pom.xml @@ -29,6 +29,17 @@ + + + + net.sourceforge.pmd + pmd-core + ${project.version} + test + tests + test-jar + + org.apache.ant ant-testutil diff --git a/pmd-ant/src/test/java/net/sourceforge/pmd/ant/PMDTaskTest.java b/pmd-ant/src/test/java/net/sourceforge/pmd/ant/PMDTaskTest.java index d78afc9147..5623c23284 100644 --- a/pmd-ant/src/test/java/net/sourceforge/pmd/ant/PMDTaskTest.java +++ b/pmd-ant/src/test/java/net/sourceforge/pmd/ant/PMDTaskTest.java @@ -9,10 +9,11 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; import org.apache.tools.ant.BuildException; import org.junit.jupiter.api.BeforeEach; @@ -71,7 +72,7 @@ class PMDTaskTest extends AbstractAntTest { void testWithShortFilenames() throws IOException { executeTarget("testWithShortFilenames"); - try (InputStream in = new FileInputStream("target/pmd-ant-test.txt")) { + try (InputStream in = Files.newInputStream(Paths.get("target/pmd-ant-test.txt"))) { String actual = IOUtil.readToString(in, StandardCharsets.UTF_8); // remove any trailing newline actual = actual.trim(); @@ -83,7 +84,7 @@ class PMDTaskTest extends AbstractAntTest { void testXmlFormatter() throws IOException { executeTarget("testXmlFormatter"); - try (InputStream in = new FileInputStream("target/pmd-ant-xml.xml"); + try (InputStream in = Files.newInputStream(Paths.get("target/pmd-ant-xml.xml")); InputStream expectedStream = PMDTaskTest.class.getResourceAsStream("xml/expected-pmd-ant-xml.xml")) { String actual = IOUtil.readToString(in, StandardCharsets.UTF_8); actual = actual.replaceFirst("timestamp=\"[^\"]+\"", "timestamp=\"\""); diff --git a/pmd-ant/src/test/resources/META-INF/services/net.sourceforge.pmd.cpd.Language b/pmd-ant/src/test/resources/META-INF/services/net.sourceforge.pmd.cpd.Language deleted file mode 100644 index 8d6bddbd3e..0000000000 --- a/pmd-ant/src/test/resources/META-INF/services/net.sourceforge.pmd.cpd.Language +++ /dev/null @@ -1 +0,0 @@ -net.sourceforge.pmd.ant.CpdDummyLanguage diff --git a/pmd-ant/src/test/resources/net/sourceforge/pmd/ant/xml/pmdtasktest.xml b/pmd-ant/src/test/resources/net/sourceforge/pmd/ant/xml/pmdtasktest.xml index 71c482915a..da96098178 100644 --- a/pmd-ant/src/test/resources/net/sourceforge/pmd/ant/xml/pmdtasktest.xml +++ b/pmd-ant/src/test/resources/net/sourceforge/pmd/ant/xml/pmdtasktest.xml @@ -27,7 +27,8 @@ - ${pmd.home}/src/test/resources/rulesets/dummy/basic.xml + + rulesets/dummy/basic.xml @@ -37,7 +38,8 @@ - ${pmd.home}/src/test/resources/rulesets/dummy/basic.xml + + rulesets/dummy/basic.xml diff --git a/pmd-core/pom.xml b/pmd-core/pom.xml index c993793136..2724ccccd2 100644 --- a/pmd-core/pom.xml +++ b/pmd-core/pom.xml @@ -61,6 +61,19 @@ pmd-core-checkstyle-suppressions.xml + + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + From ef201d110b2af765bfe1f68041372e3e1dfc2766 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Tue, 9 Aug 2022 20:57:41 +0200 Subject: [PATCH 003/254] ant: add to pmd-dist --- pmd-ant/pom.xml | 2 ++ pmd-dist/pom.xml | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/pmd-ant/pom.xml b/pmd-ant/pom.xml index 8a9195b257..68f3463143 100644 --- a/pmd-ant/pom.xml +++ b/pmd-ant/pom.xml @@ -25,6 +25,8 @@ org.apache.ant ant + + provided diff --git a/pmd-dist/pom.xml b/pmd-dist/pom.xml index 2f2062dd91..a774170524 100644 --- a/pmd-dist/pom.xml +++ b/pmd-dist/pom.xml @@ -134,6 +134,11 @@ pmd-core ${project.version} + + net.sourceforge.pmd + pmd-ant + ${project.version} + net.sourceforge.pmd pmd-cpp From 4d59a1f0a10c6c1825c2c6d8372e858052c73419 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Tue, 9 Aug 2022 21:02:02 +0200 Subject: [PATCH 004/254] ant: doc --- .../src/main/java/net/sourceforge/pmd/ant/CPDTask.java | 3 ++- .../src/main/java/net/sourceforge/pmd/ant/PMDTask.java | 6 +++++- .../main/java/net/sourceforge/pmd/ant/package-info.java | 8 ++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 pmd-ant/src/main/java/net/sourceforge/pmd/ant/package-info.java diff --git a/pmd-ant/src/main/java/net/sourceforge/pmd/ant/CPDTask.java b/pmd-ant/src/main/java/net/sourceforge/pmd/ant/CPDTask.java index fd4de35934..e6a27bbd8a 100644 --- a/pmd-ant/src/main/java/net/sourceforge/pmd/ant/CPDTask.java +++ b/pmd-ant/src/main/java/net/sourceforge/pmd/ant/CPDTask.java @@ -37,7 +37,8 @@ import net.sourceforge.pmd.cpd.renderer.CPDRendererAdapter; import net.sourceforge.pmd.cpd.renderer.CPDReportRenderer; /** - * CPDTask + * CPD Ant task. Setters of this class are interpreted by Ant as properties + * settable in the XML. This is therefore published API. * *

Runs the CPD utility via ant. The ant task looks like this:

* diff --git a/pmd-ant/src/main/java/net/sourceforge/pmd/ant/PMDTask.java b/pmd-ant/src/main/java/net/sourceforge/pmd/ant/PMDTask.java index 192a714f6e..f504a858fc 100644 --- a/pmd-ant/src/main/java/net/sourceforge/pmd/ant/PMDTask.java +++ b/pmd-ant/src/main/java/net/sourceforge/pmd/ant/PMDTask.java @@ -1,4 +1,4 @@ -/** +/* * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ @@ -18,6 +18,10 @@ import org.apache.tools.ant.types.Reference; import net.sourceforge.pmd.RulePriority; import net.sourceforge.pmd.ant.internal.PMDTaskImpl; +/** + * PMD Ant task. Setters of this class are interpreted by Ant as properties + * settable in the XML. This is therefore published API. + */ public class PMDTask extends Task { private Path classpath; diff --git a/pmd-ant/src/main/java/net/sourceforge/pmd/ant/package-info.java b/pmd-ant/src/main/java/net/sourceforge/pmd/ant/package-info.java new file mode 100644 index 0000000000..c1aa7288ca --- /dev/null +++ b/pmd-ant/src/main/java/net/sourceforge/pmd/ant/package-info.java @@ -0,0 +1,8 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +/** + * PMD and CPD integration for the Apache Ant build system. + */ +package net.sourceforge.pmd.ant; From c95d970aab0182297197cf9a38c9b6d44b3e9809 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Tue, 9 Aug 2022 21:07:05 +0200 Subject: [PATCH 005/254] doc: update release notes --- docs/pages/7_0_0_release_notes.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/pages/7_0_0_release_notes.md b/docs/pages/7_0_0_release_notes.md index de1b50f21d..45c49efeb3 100644 --- a/docs/pages/7_0_0_release_notes.md +++ b/docs/pages/7_0_0_release_notes.md @@ -284,6 +284,9 @@ The following previously deprecated rules have been finally removed: `net.sourceforge.pmd.lang.rule.xpath`, `net.sourceforge.pmd.lang.rule`, and various language-specific packages (which were made internal). +* The implementation of the Ant integration has been moved from the module `pmd-core` to a new module `pmd-ant`. + This involves classes in packages `net.sourceforge.pmd.ant` and the class `net.sourceforge.pmd.cpd.CPDTask`. + #### Metrics framework The metrics framework has been made simpler and more general. From 6920d248b9c958e65c6eb8f29d933ee763132599 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Tue, 9 Aug 2022 22:38:58 +0200 Subject: [PATCH 006/254] ant: cleanup --- .../sourceforge/pmd/ant/CpdDummyLanguage.java | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 pmd-ant/src/test/java/net/sourceforge/pmd/ant/CpdDummyLanguage.java diff --git a/pmd-ant/src/test/java/net/sourceforge/pmd/ant/CpdDummyLanguage.java b/pmd-ant/src/test/java/net/sourceforge/pmd/ant/CpdDummyLanguage.java deleted file mode 100644 index bddeaffcdd..0000000000 --- a/pmd-ant/src/test/java/net/sourceforge/pmd/ant/CpdDummyLanguage.java +++ /dev/null @@ -1,19 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.ant; - -import net.sourceforge.pmd.cpd.AbstractLanguage; -import net.sourceforge.pmd.cpd.AnyTokenizer; - -/** - * Sample language for testing LanguageFactory. - * - */ -public class CpdDummyLanguage extends AbstractLanguage { - - public CpdDummyLanguage() { - super("CPD Dummy Language used in tests", "Cpddummy", new AnyTokenizer(), "dummy"); - } -} From 986192b2c1caa37faee8b8798fca93d6be350324 Mon Sep 17 00:00:00 2001 From: Thomas Prouvot Date: Tue, 30 Aug 2022 09:11:31 +0200 Subject: [PATCH 007/254] Add new Assert class updates --- pmd-apex-6.49.0-SNAPSHOT.jar | Bin 0 -> 355874 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 pmd-apex-6.49.0-SNAPSHOT.jar diff --git a/pmd-apex-6.49.0-SNAPSHOT.jar b/pmd-apex-6.49.0-SNAPSHOT.jar new file mode 100644 index 0000000000000000000000000000000000000000..82c0e96d43d22ee3f9f00104a944d9339fe669ce GIT binary patch literal 355874 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
h|_66jB21R)q1Lph_6J1ac6v1vSLOv40fG;#Wp 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 z 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

HMD%Ry;YSs)U)L}`{;K(*_;MDC_~*JLZ}A$8|I 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 literal 0 HcmV?d00001 From c77a0f3219f62c01886b6ec8a08a284f3e4f7ef0 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Tue, 4 Oct 2022 19:09:58 +0200 Subject: [PATCH 008/254] [doc] Update CPD doc --- docs/pages/pmd/userdocs/cpd/cpd.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pages/pmd/userdocs/cpd/cpd.md b/docs/pages/pmd/userdocs/cpd/cpd.md index d909eb4f2f..8e989594f5 100644 --- a/docs/pages/pmd/userdocs/cpd/cpd.md +++ b/docs/pages/pmd/userdocs/cpd/cpd.md @@ -254,7 +254,7 @@ Andy Glover wrote an Ant task for CPD; here's how to use it: ```xml - + From 98a46866a57961e503f1f34a0c848af737cae628 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Tue, 4 Oct 2022 19:10:23 +0200 Subject: [PATCH 009/254] [doc] Update release notes (#4080, #4089) --- docs/_plugins/jdoc_namespace_tag.rb | 2 +- docs/pages/7_0_0_release_notes.md | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/_plugins/jdoc_namespace_tag.rb b/docs/_plugins/jdoc_namespace_tag.rb index f17070f01c..d6d411054e 100644 --- a/docs/_plugins/jdoc_namespace_tag.rb +++ b/docs/_plugins/jdoc_namespace_tag.rb @@ -99,7 +99,7 @@ class JDocNamespaceDeclaration < Liquid::Tag private JDOC_NAMESPACE_MAP = "jdoc_nspaces" - RESERVED_NSPACES = ['apex', 'core', 'cpp', 'cs', 'dart', 'dist', 'doc', 'fortran', 'go', 'groovy', 'java', + RESERVED_NSPACES = ['ant', 'apex', 'core', 'cpp', 'cs', 'dart', 'dist', 'doc', 'fortran', 'go', 'groovy', 'java', 'javascript', 'jsp', 'kotlin', 'lua', 'matlab', 'objectivec', 'perl', 'php', 'plsql', 'python', 'ruby', 'scala', 'swift', 'test', 'test-schema', 'ui', diff --git a/docs/pages/7_0_0_release_notes.md b/docs/pages/7_0_0_release_notes.md index 45c49efeb3..deb3b07d02 100644 --- a/docs/pages/7_0_0_release_notes.md +++ b/docs/pages/7_0_0_release_notes.md @@ -184,6 +184,8 @@ The following previously deprecated rules have been finally removed: * miscellaneous * [#896](https://github.com/pmd/pmd/issues/896): \[all] Use slf4j * [#1451](https://github.com/pmd/pmd/issues/1451): \[core] RulesetFactoryCompatibility stores the whole ruleset file in memory as a string +* ant + * [#4080](https://github.com/pmd/pmd/issues/4080): \[ant] Split off Ant integration into a new submodule * core * [#4035](https://github.com/pmd/pmd/issues/4035): \[core] ConcurrentModificationException in DefaultRuleViolationFactory * cli @@ -285,7 +287,9 @@ The following previously deprecated rules have been finally removed: (which were made internal). * The implementation of the Ant integration has been moved from the module `pmd-core` to a new module `pmd-ant`. - This involves classes in packages `net.sourceforge.pmd.ant` and the class `net.sourceforge.pmd.cpd.CPDTask`. + This involves classes in package {% jdoc_package ant::ant %}. The ant CPDTask class `net.sourceforge.pmd.cpd.CPDTask` + has been moved into the same package {% jdoc_package ant::ant %}. You'll need to update your taskdef entries in your + build.xml files with the FQCN {% jdoc !!ant::ant.CPDTask %} if you use it anywhere. #### Metrics framework From 11763cdc604a8288b0b079f6707ce8eebb53747d Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Tue, 4 Oct 2022 19:32:29 +0200 Subject: [PATCH 010/254] [doc] Show PMD 7 release notes --- docs/_data/sidebars/pmd_sidebar.yml | 2 +- docs/pages/7_0_0_release_notes.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/_data/sidebars/pmd_sidebar.yml b/docs/_data/sidebars/pmd_sidebar.yml index 123001a044..fef62bf958 100644 --- a/docs/_data/sidebars/pmd_sidebar.yml +++ b/docs/_data/sidebars/pmd_sidebar.yml @@ -23,7 +23,7 @@ entries: output: web, pdf type: homepage - title: Release notes - url: /pmd_release_notes.html + url: /pmd_release_notes7.html output: web, pdf - title: PMD 7.0.0 development url: /pmd_next_major_development.html diff --git a/docs/pages/7_0_0_release_notes.md b/docs/pages/7_0_0_release_notes.md index deb3b07d02..4aff611397 100644 --- a/docs/pages/7_0_0_release_notes.md +++ b/docs/pages/7_0_0_release_notes.md @@ -1,6 +1,6 @@ --- title: PMD Release Notes -permalink: pmd_release_notes.html +permalink: pmd_release_notes7.html keywords: changelog, release notes --- From a1bcad2db2ccece6401b0dfae5ce5a983d1410c5 Mon Sep 17 00:00:00 2001 From: LynnBroe Date: Wed, 5 Oct 2022 13:54:41 +0800 Subject: [PATCH 011/254] fix #4141 --- .../resources/category/java/documentation.xml | 5 +++- .../xml/UncommentedEmptyConstructor.xml | 26 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/pmd-java/src/main/resources/category/java/documentation.xml b/pmd-java/src/main/resources/category/java/documentation.xml index 9081295102..8a331af2e6 100644 --- a/pmd-java/src/main/resources/category/java/documentation.xml +++ b/pmd-java/src/main/resources/category/java/documentation.xml @@ -102,7 +102,10 @@ and unintentional empty constructors. [@containsComment = false()] [not(BlockStatement)] [$ignoreExplicitConstructorInvocation = true() or not(ExplicitConstructorInvocation)] - [not(../Annotation/MarkerAnnotation/Name[pmd-java:typeIs('javax.inject.Inject')])] + [not(../Annotation/MarkerAnnotation/Name[ + pmd-java:typeIs('javax.inject.Inject') + or pmd-java:typeIs('org.springframework.beans.factory.annotation.Autowired') + ])] ]]> diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/documentation/xml/UncommentedEmptyConstructor.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/documentation/xml/UncommentedEmptyConstructor.xml index 2d11209715..6ca1de2d16 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/documentation/xml/UncommentedEmptyConstructor.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/documentation/xml/UncommentedEmptyConstructor.xml @@ -187,4 +187,30 @@ public class MyClass { } ]]> + + + #4141 UncommentedEmptyConstructor FP when annotated constructor with @Autowired + 0 + + + + + #4141 UncommentedEmptyConstructor FP when annotated constructor with @Autowired + 0 + + + From f9ccab3d7bea8cb14d619d42a1ccf3c6cfe33017 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 6 Oct 2022 10:03:25 +0200 Subject: [PATCH 012/254] Bump protobuf-java from 3.16.1 to 3.16.3 Fixes https://github.com/pmd/pmd/security/dependabot/29 Fixes https://github.com/advisories/GHSA-h4h5-3hr4-j3g2 Fixes CVE-2022-3171 --- pom.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1e3456c9ec..057ccd1db7 100644 --- a/pom.xml +++ b/pom.xml @@ -942,11 +942,13 @@ com.google.protobuf protobuf-java - 3.16.1 + 3.16.3 From d1fa989b3c2257ab924e87eca25a236244d09e36 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 6 Oct 2022 10:11:33 +0200 Subject: [PATCH 013/254] Bump snakeyaml from 1.32 to 1.33 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 057ccd1db7..fb64541507 100644 --- a/pom.xml +++ b/pom.xml @@ -770,7 +770,7 @@ org.yaml snakeyaml - 1.32 + 1.33 From 6faf6ed223d9cb4d0b06a7b30d77315a55a78e32 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 6 Oct 2022 16:25:20 +0200 Subject: [PATCH 014/254] [doc] Update docs to reflect supported languages (#4143) Refs #4059 --- docs/index.md | 4 ++-- docs/pages/pmd/userdocs/cli_reference.md | 8 +++++--- docs/pages/pmd/userdocs/installation.md | 4 ++-- docs/pages/release_notes.md | 2 ++ pom.xml | 4 ++-- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/docs/index.md b/docs/index.md index 6066c103a8..46df788333 100644 --- a/docs/index.md +++ b/docs/index.md @@ -7,7 +7,7 @@ summary: > Welcome to the documentation site for PMD and CPD!

-last_updated: August 2017 +last_updated: October 2022 author: Jeff Jensen , Andreas Dangel , Clément Fournier --- @@ -29,7 +29,7 @@ author: Jeff Jensen , Andreas Dangel PMD is a source code analyzer. It finds common programming flaws like unused variables, empty catch blocks, unnecessary object creation, and so forth. It supports Java, JavaScript, Salesforce.com Apex and Visualforce, - Modelica, PLSQL, Apache Velocity, XML, XSL, Scala. + Modelica, PLSQL, Apache Velocity, HTML, XML, XSL, Scala. Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code in - C/C++, C#, Dart, Fortran, Go, Groovy, Java, JavaScript, JSP, Kotlin, Lua, Matlab, Modelica, + C/C++, C#, Dart, Fortran, Gherkin, Go, Groovy, HTML, Java, JavaScript, JSP, Kotlin, Lua, Matlab, Modelica, Objective-C, Perl, PHP, PLSQL, Python, Ruby, Salesforce.com Apex, Scala, Swift and Visualforce. From 9c182974e744b5c2f71f1d0337d09aa62b58595c Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 6 Oct 2022 17:39:56 +0200 Subject: [PATCH 015/254] [doc] Fix ruledoc for POM: ProjectVersionAsDependencyVersion --- pmd-xml/pom.xml | 1 + pmd-xml/src/main/resources/category/pom/errorprone.xml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pmd-xml/pom.xml b/pmd-xml/pom.xml index 733153d749..4e51df9ec1 100644 --- a/pmd-xml/pom.xml +++ b/pmd-xml/pom.xml @@ -30,6 +30,7 @@ ${*} + \ diff --git a/pmd-xml/src/main/resources/category/pom/errorprone.xml b/pmd-xml/src/main/resources/category/pom/errorprone.xml index cfedde95c5..a882baa52f 100644 --- a/pmd-xml/src/main/resources/category/pom/errorprone.xml +++ b/pmd-xml/src/main/resources/category/pom/errorprone.xml @@ -67,7 +67,7 @@ The following types are considered valid: pom, jar, maven-plugin, ejb, war, ear, externalInfoUrl="${pmd.website.baseurl}/pmd_rules_pom_errorprone.html#projectversionasdependencyversion"> Using that expression in dependency declarations seems like a shortcut, but it can go wrong. -By far the most common problem is the use of ${project.version} in a BOM or parent POM. +By far the most common problem is the use of \${project.version} in a BOM or parent POM. 3 @@ -92,7 +92,7 @@ By far the most common problem is the use of ${project.version} in a BOM or ... ... - ${project.version} + \${project.version} ]]> From 17b8661dab2dcd248065c62370729d9001b6b2f1 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 6 Oct 2022 17:41:39 +0200 Subject: [PATCH 016/254] [doc] Use java highlighting in issue templates by default --- .github/ISSUE_TEMPLATE/0rule_violation_false-positive.md | 2 +- .github/ISSUE_TEMPLATE/1rule_violation_false-negative.md | 2 +- .github/ISSUE_TEMPLATE/2new_rule.md | 2 +- .github/ISSUE_TEMPLATE/4bug_report.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/0rule_violation_false-positive.md b/.github/ISSUE_TEMPLATE/0rule_violation_false-positive.md index e02c12eaa8..8faed7c14a 100644 --- a/.github/ISSUE_TEMPLATE/0rule_violation_false-positive.md +++ b/.github/ISSUE_TEMPLATE/0rule_violation_false-positive.md @@ -19,7 +19,7 @@ Please provide the rule name and a link to the rule documentation: **Code Sample demonstrating the issue:** -``` +```java ``` diff --git a/.github/ISSUE_TEMPLATE/1rule_violation_false-negative.md b/.github/ISSUE_TEMPLATE/1rule_violation_false-negative.md index b9480d2c27..51702e0038 100644 --- a/.github/ISSUE_TEMPLATE/1rule_violation_false-negative.md +++ b/.github/ISSUE_TEMPLATE/1rule_violation_false-negative.md @@ -19,7 +19,7 @@ Please provide the rule name and a link to the rule documentation: **Code Sample demonstrating the issue:** -``` +```java ``` diff --git a/.github/ISSUE_TEMPLATE/2new_rule.md b/.github/ISSUE_TEMPLATE/2new_rule.md index 4e79d37f11..52901a1e22 100644 --- a/.github/ISSUE_TEMPLATE/2new_rule.md +++ b/.github/ISSUE_TEMPLATE/2new_rule.md @@ -17,7 +17,7 @@ assignees: '' **Code Sample:** This should include code, that should be flagged by the rule. If possible, the "correct" code according to this new rule should also be demonstrated. -``` +```java ``` diff --git a/.github/ISSUE_TEMPLATE/4bug_report.md b/.github/ISSUE_TEMPLATE/4bug_report.md index 5c752659ea..0b3ada2739 100644 --- a/.github/ISSUE_TEMPLATE/4bug_report.md +++ b/.github/ISSUE_TEMPLATE/4bug_report.md @@ -25,7 +25,7 @@ A clear and concise description of what the bug is. **Code Sample demonstrating the issue:** -``` +```java ``` From 78b69fdb960e21a16f0756a823658c57657c5375 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 6 Oct 2022 17:45:38 +0200 Subject: [PATCH 017/254] Update @LynnBroe as a contributor --- .all-contributorsrc | 3 ++- docs/pages/pmd/projectdocs/credits.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 4c9d8d553e..e70e090519 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -6866,7 +6866,8 @@ "avatar_url": "https://avatars.githubusercontent.com/u/109954313?v=4", "profile": "https://github.com/LynnBroe", "contributions": [ - "code" + "code", + "bug" ] }, { diff --git a/docs/pages/pmd/projectdocs/credits.md b/docs/pages/pmd/projectdocs/credits.md index 4296e0d000..a51ed0163a 100644 --- a/docs/pages/pmd/projectdocs/credits.md +++ b/docs/pages/pmd/projectdocs/credits.md @@ -429,7 +429,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Lukebray

🛠-
Lynn

💻 +
Lynn

💻 ðŸ›
Lyor Goldstein

ðŸ›
MCMicS

ðŸ›
Macarse

🛠From 38a0950fb9b593a51f2ac2a2d004124452d5939e Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 6 Oct 2022 17:46:13 +0200 Subject: [PATCH 018/254] [doc] Update release notes (#4141, #4142) --- docs/pages/release_notes.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index b8f8783555..ce78e6112e 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -15,10 +15,13 @@ This is a {{ site.pmd.release_type }} release. ### New and noteworthy ### Fixed Issues +* java-documentation + * [#4141](https://github.com/pmd/pmd/issues/4141): \[java] UncommentedEmptyConstructor FP when constructor annotated with @Autowired ### API Changes ### External Contributions +* [#4142](https://github.com/pmd/pmd/pull/4142): \[java] fix #4141 Update UncommentedEmptyConstructor - ignore @Autowired annotations - [Lynn](https://github.com/LynnBroe) (@LynnBroe) {% endtocmaker %} From 1063d6c2243c49a6379dac38cc0f39f1f0c702c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Thu, 6 Oct 2022 13:02:43 -0300 Subject: [PATCH 019/254] Update docs/pages/release_notes.md Co-authored-by: Andreas Dangel --- docs/pages/release_notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index 1107d2084f..3cec147b8a 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -16,7 +16,7 @@ This is a {{ site.pmd.release_type }} release. ### Fixed Issues * doc - * [#4143](https://github.com/pmd/pmd/pull/4142) \[doc] Update docs to reflect supported languages + * [#4144](https://github.com/pmd/pmd/pull/4144) \[doc] Update docs to reflect supported languages ### API Changes From 14b4a805d07db59d5b34957f9197e1d97d5e2aeb Mon Sep 17 00:00:00 2001 From: Yasar Shaikh Date: Fri, 7 Oct 2022 20:11:39 +0530 Subject: [PATCH 020/254] Added support for Do-While for "AvoidArrayLoops" --- pmd-java/src/main/resources/category/java/performance.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pmd-java/src/main/resources/category/java/performance.xml b/pmd-java/src/main/resources/category/java/performance.xml index 84a7da762d..9acf6b581a 100644 --- a/pmd-java/src/main/resources/category/java/performance.xml +++ b/pmd-java/src/main/resources/category/java/performance.xml @@ -80,7 +80,7 @@ Instead of manually copying data between two arrays, use the efficient Arrays.co Date: Sun, 9 Oct 2022 22:23:37 +0530 Subject: [PATCH 021/254] [java] added unit test of `do-while` for AvoidArrayLoops --- .../rule/performance/xml/AvoidArrayLoops.xml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidArrayLoops.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidArrayLoops.xml index 19e2627b52..cfbd55d3f0 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidArrayLoops.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidArrayLoops.xml @@ -48,6 +48,22 @@ public class Foo { ]]> + + copy via do-while loop + 1 + + + copy involving multiple arrays is ok 0 From 2954b486cfc4dcf8ef6f4bbbfd23eec7c2ea7015 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Mon, 10 Oct 2022 18:00:58 +0200 Subject: [PATCH 022/254] Add @tcopeland as a contributor --- .all-contributorsrc | 11 +++++ docs/pages/pmd/projectdocs/credits.md | 65 ++++++++++++++------------- 2 files changed, 44 insertions(+), 32 deletions(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index e70e090519..87b91d71c7 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -6941,6 +6941,17 @@ "contributions": [ "bug" ] + }, + { + "login": "tcopeland", + "name": "Tom Copeland", + "avatar_url": "https://avatars.githubusercontent.com/u/70536?v=4", + "profile": "https://thomasleecopeland.com/", + "contributions": [ + "bug", + "code", + "doc" + ] } ], "contributorsPerLine": 7, diff --git a/docs/pages/pmd/projectdocs/credits.md b/docs/pages/pmd/projectdocs/credits.md index a51ed0163a..8854bb1eed 100644 --- a/docs/pages/pmd/projectdocs/credits.md +++ b/docs/pages/pmd/projectdocs/credits.md @@ -701,291 +701,292 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Thunderforge

💻 ðŸ›
Tim van der Lippe

ðŸ›
Tobias Weimer

💻 🛠+
Tom Copeland

🛠💻 📖
Tom Daly

ðŸ›
Tomer Figenblat

ðŸ›
Tomi De Lucca

💻 🛠-
Torsten Kleiber

🛠+
Torsten Kleiber

ðŸ›
TrackerSB

ðŸ›
Ullrich Hafner

ðŸ›
Utku Cuhadaroglu

💻 ðŸ›
Valentin Brandl

ðŸ›
Valeria

ðŸ›
Vasily Anisimov

🛠-
Vibhor Goyal

🛠+
Vibhor Goyal

ðŸ›
Vickenty Fesunov

ðŸ›
Victor Noël

ðŸ›
Vincent Galloy

💻
Vincent HUYNH

ðŸ›
Vincent Maurin

ðŸ›
Vincent Privat

🛠-
Vishhwas

🛠+
Vishhwas

ðŸ›
Vitaly

ðŸ›
Vitaly Polonetsky

ðŸ›
Vojtech Polivka

ðŸ›
Vsevolod Zholobov

ðŸ›
Vyom Yadav

💻
Wang Shidong

🛠-
Waqas Ahmed

🛠+
Waqas Ahmed

ðŸ›
Wayne J. Earl

ðŸ›
Wchenghui

ðŸ›
Will Winder

ðŸ›
William Brockhus

💻 ðŸ›
Wilson Kurniawan

ðŸ›
Wim Deblauwe

🛠-
Woongsik Choi

🛠+
Woongsik Choi

ðŸ›
XenoAmess

💻 ðŸ›
Yang

💻
YaroslavTER

ðŸ›
Young Chan

💻 ðŸ›
YuJin Kim

ðŸ›
Yuri Dolzhenko

🛠-
Yurii Dubinka

🛠+
Yurii Dubinka

ðŸ›
Zoltan Farkas

ðŸ›
Zustin

ðŸ›
aaronhurst-google

🛠💻
alexmodis

ðŸ›
andreoss

ðŸ›
andrey81inmd

💻 🛠-
anicoara

🛠+
anicoara

ðŸ›
arunprasathav

ðŸ›
asiercamara

ðŸ›
astillich-igniti

💻
avesolovksyy

ðŸ›
avishvat

ðŸ›
avivmu

🛠-
axelbarfod1

🛠+
axelbarfod1

ðŸ›
b-3-n

ðŸ›
balbhadra9

ðŸ›
base23de

ðŸ›
bergander

ðŸ›
berkam

💻 ðŸ›
breizh31

🛠-
caesarkim

🛠+
caesarkim

ðŸ›
carolyujing

ðŸ›
cbfiddle

ðŸ›
cesares-basilico

ðŸ›
chrite

ðŸ›
cobratbq

ðŸ›
coladict

🛠-
cosmoJFH

🛠+
cosmoJFH

ðŸ›
cristalp

ðŸ›
crunsk

ðŸ›
cwholmes

ðŸ›
cyberjj999

ðŸ›
cyw3

ðŸ›
d1ss0nanz

🛠-
dalizi007

💻 +
dalizi007

💻
danbrycefairsailcom

ðŸ›
dariansanity

ðŸ›
darrenmiliband

ðŸ›
davidburstrom

ðŸ›
dbirkman-paloalto

ðŸ›
deepak-patra

🛠-
dependabot[bot]

💻 🛠+
dependabot[bot]

💻 ðŸ›
dinesh150

ðŸ›
diziaq

ðŸ›
dreaminpast123

ðŸ›
duanyanan

ðŸ›
dutt-sanjay

ðŸ›
dylanleung

🛠-
dzeigler

🛠+
dzeigler

ðŸ›
ekkirala

ðŸ›
emersonmoura

ðŸ›
fairy

ðŸ›
filiprafalowicz

💻
foxmason

ðŸ›
frankegabor

🛠-
frankl

🛠+
frankl

ðŸ›
freafrea

ðŸ›
fsapatin

ðŸ›
gracia19

ðŸ›
guo fei

ðŸ›
gurmsc5

ðŸ›
gwilymatgearset

💻 🛠-
haigsn

🛠+
haigsn

ðŸ›
hemanshu070

ðŸ›
henrik242

ðŸ›
hongpuwu

ðŸ›
hvbtup

💻 ðŸ›
igniti GmbH

ðŸ›
ilovezfs

🛠-
itaigilo

🛠+
itaigilo

ðŸ›
jakivey32

ðŸ›
jbennett2091

ðŸ›
jcamerin

ðŸ›
jkeener1

ðŸ›
jmetertea

ðŸ›
johnra2

💻 -
josemanuelrolon

💻 🛠+
josemanuelrolon

💻 ðŸ›
kabroxiko

💻 ðŸ›
karwer

ðŸ›
kaulonline

ðŸ›
kdaemonv

ðŸ›
kenji21

💻 ðŸ›
kfranic

🛠-
khalidkh

🛠+
khalidkh

ðŸ›
koalalam

ðŸ›
krzyk

ðŸ›
lasselindqvist

ðŸ›
lgemeinhardt

ðŸ›
lihuaib

ðŸ›
lonelyma1021

🛠-
lpeddy

🛠+
lpeddy

ðŸ›
lujiefsi

💻
lukelukes

💻
lyriccoder

ðŸ›
marcelmore

ðŸ›
matchbox

ðŸ›
matthiaskraaz

🛠-
meandonlyme

🛠+
meandonlyme

ðŸ›
mikesive

ðŸ›
milossesic

ðŸ›
mohan-chinnappan-n

💻
mriddell95

ðŸ›
mrlzh

ðŸ›
msloan

🛠-
mucharlaravalika

🛠+
mucharlaravalika

ðŸ›
mvenneman

ðŸ›
nareshl119

ðŸ›
nicolas-harraudeau-sonarsource

ðŸ›
noerremark

ðŸ›
novsirion

ðŸ›
oggboy

🛠-
oinume

🛠+
oinume

ðŸ›
orimarko

💻 ðŸ›
pacvz

💻
pallavi agarwal

ðŸ›
parksungrin

ðŸ›
patpatpat123

ðŸ›
patriksevallius

🛠-
pbrajesh1

🛠+
pbrajesh1

ðŸ›
phoenix384

ðŸ›
piotrszymanski-sc

💻
plan3d

ðŸ›
poojasix

ðŸ›
prabhushrikant

ðŸ›
pujitha8783

🛠-
r-r-a-j

🛠+
r-r-a-j

ðŸ›
raghujayjunk

ðŸ›
rajeshveera

ðŸ›
rajeswarreddy88

ðŸ›
recdevs

ðŸ›
reudismam

💻 ðŸ›
rijkt

🛠-
rillig-tk

🛠+
rillig-tk

ðŸ›
rmohan20

💻 ðŸ›
rxmicro

ðŸ›
ryan-gustafson

💻 ðŸ›
sabi0

ðŸ›
scais

ðŸ›
sebbASF

🛠-
sergeygorbaty

💻 +
sergeygorbaty

💻
shilko2013

ðŸ›
shiomiyan

📖
simeonKondr

ðŸ›
snajberk

ðŸ›
sniperrifle2004

ðŸ›
snuyanzin

🛠💻 -
sratz

🛠+
sratz

ðŸ›
stonio

ðŸ›
sturton

💻 ðŸ›
sudharmohan

ðŸ›
suruchidawar

ðŸ›
svenfinitiv

ðŸ›
tashiscool

🛠-
test-git-hook

🛠+
test-git-hook

ðŸ›
testation21

💻 ðŸ›
thanosa

ðŸ›
tiandiyixian

ðŸ›
tobwoerk

ðŸ›
tprouvot

🛠💻
trentchilders

🛠-
triandicAnt

🛠+
triandicAnt

ðŸ›
trishul14

ðŸ›
tsui

ðŸ›
winhkey

ðŸ›
witherspore

ðŸ›
wjljack

ðŸ›
wuchiuwong

🛠-
xingsong

🛠+
xingsong

ðŸ›
xioayuge

ðŸ›
xnYi9wRezm

💻 ðŸ›
xuanuy

ðŸ›
xyf0921

ðŸ›
yalechen-cyw3

ðŸ›
yasuharu-sato

🛠-
zenglian

🛠+
zenglian

ðŸ›
zgrzyt93

💻 ðŸ›
zh3ng

ðŸ›
zt_soft

ðŸ›
ztt79

ðŸ›
zzzzfeng

ðŸ›
Ãrpád Magosányi

🛠-
任贵æ°

🛠+
任贵æ°

ðŸ›
茅延安

💻 From 4a7ba3e6c068d5850f39ea0db2beaddfcb6734ec Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Mon, 10 Oct 2022 18:12:47 +0200 Subject: [PATCH 023/254] [doc] Update contributors, mention founders --- docs/pages/pmd/projectdocs/credits.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/pages/pmd/projectdocs/credits.md b/docs/pages/pmd/projectdocs/credits.md index 8854bb1eed..7b896102db 100644 --- a/docs/pages/pmd/projectdocs/credits.md +++ b/docs/pages/pmd/projectdocs/credits.md @@ -1002,13 +1002,19 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d These are collected contributors before we moved to github. +### Founders + +According to the book [PMD Applied](https://pmdapplied.thomasleecopeland.com/) the following people are the founders of PMD: + +* [Tom Copeland](https://thomasleecopeland.com/) ([@tcopeland](https://github.com/tcopeland)) - PMD core, lead developer, JDeveloper plugin, initial Gel plugin, + initial jEdit plugin, IDEAJ integration, BlueJ extension +* David Dixon-Peugh - PMD core, much of the early work on the grammar, initial Emacs plugin +* [David Craine](http://dcraine.blogspot.com/) - JBuilder plugin + ### Committers -* David Dixon-Peugh - PMD core, much of the early work on the grammar, initial Emacs plugin * Philippe Herlin - Eclipse plugin, fixed bugs in RuleSetFactory * Nascif Abousalh Neto - Emacs plugin -* [Tom Copeland](https://thomasleecopeland.com/) - PMD core, lead developer, JDeveloper plugin, initial Gel plugin, - initial jEdit plugin, IDEAJ integration, BlueJ extension * Jiger Patel - jEdit plugin * Alan Ezust - jEdit plugin * Ole-Martin Mork - NetBeans plugin @@ -1037,7 +1043,6 @@ These are collected contributors before we moved to github. * Gunnlaugur Thor Briem - NetBeans plugin, Maven build script fixes, bug report on JavaCC parser's use of java.lang.Error -* [David Craine](http://dcraine.blogspot.com/) - JBuilder plugin * Tom Burke - Eclipse plugin * Alex Chaffee - various bugfixes and features * Siegfried Goeschl - original Maven plugin, various bugfixes and features From fd594f682cf8ef59a33d4e5792bf13b51c16d9e6 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Mon, 10 Oct 2022 18:13:19 +0200 Subject: [PATCH 024/254] [doc] Update "Articles about PMD" --- docs/pages/pmd/projectdocs/trivia/news.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/pages/pmd/projectdocs/trivia/news.md b/docs/pages/pmd/projectdocs/trivia/news.md index 2cf8db566d..fc31bbf8cd 100644 --- a/docs/pages/pmd/projectdocs/trivia/news.md +++ b/docs/pages/pmd/projectdocs/trivia/news.md @@ -36,6 +36,9 @@ author: Tom Copeland ### PMD in general and other Language Modules +* April 2022 - Prediction of who should refactor the code, research available at + . + * February 2021 - Artem Krosheninnikov's talk about Quality Assurance Automation: [Artem Krosheninnikov, Wrike - How static analysis can help in QAA processes]( https://www.youtube.com/watch?v=L42zH5ne074) From a48b56b5c356b82bf8795554fcf60d04d57c95ea Mon Sep 17 00:00:00 2001 From: Thomas Prouvot Date: Tue, 11 Oct 2022 16:47:16 +0200 Subject: [PATCH 025/254] Add new apex rule ApexUnitTestClassShouldHaveRunAs --- .gitignore | 1 + pmd-apex-6.49.0-SNAPSHOT.jar | Bin 355874 -> 0 bytes .../ApexUnitTestClassShouldHaveRunAsRule.java | 45 ++++++++ .../resources/category/apex/bestpractices.xml | 38 +++++++ .../main/resources/rulesets/apex/apexunit.xml | 1 + .../resources/rulesets/apex/quickstart.xml | 3 + .../ApexUnitTestClassShouldHaveRunAsTest.java | 11 ++ .../xml/ApexUnitTestClassShouldHaveRunAs.xml | 99 ++++++++++++++++++ .../main/resources/rulesets/releases/6510.xml | 14 +++ 9 files changed, 212 insertions(+) delete mode 100644 pmd-apex-6.49.0-SNAPSHOT.jar create mode 100644 pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveRunAsRule.java create mode 100644 pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveRunAsTest.java create mode 100644 pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/bestpractices/xml/ApexUnitTestClassShouldHaveRunAs.xml create mode 100644 pmd-core/src/main/resources/rulesets/releases/6510.xml diff --git a/.gitignore b/.gitignore index a4b24e4553..a4967e344f 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ node_modules # rule docs are generated docs/pages/pmd/rules +.history/* \ No newline at end of file diff --git a/pmd-apex-6.49.0-SNAPSHOT.jar b/pmd-apex-6.49.0-SNAPSHOT.jar deleted file mode 100644 index 82c0e96d43d22ee3f9f00104a944d9339fe669ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 355874 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

h|_66jB21R)q1Lph_6J1ac6v1vSLOv40fG;#Wp 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 z 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

HMD%Ry;YSs)U)L}`{;K(*_;MDC_~*JLZ}A$8|I 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 diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveRunAsRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveRunAsRule.java new file mode 100644 index 0000000000..4a403c50ee --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveRunAsRule.java @@ -0,0 +1,45 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.bestpractices; + +import java.util.ArrayList; +import java.util.List; + +import net.sourceforge.pmd.lang.apex.ast.ASTBlockStatement; +import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.lang.apex.ast.ASTRunAsBlockStatement; +import net.sourceforge.pmd.lang.apex.ast.ApexNode; +import net.sourceforge.pmd.lang.apex.rule.AbstractApexUnitTestRule; + +/** + * Apex unit tests should have System.runAs methods in them + * + * @author t.prouvot + */ +public class ApexUnitTestClassShouldHaveRunAsRule extends AbstractApexUnitTestRule { + + @Override + public Object visit(ASTMethod node, Object data) { + if (!isTestMethodOrClass(node)) { + return data; + } + + return checkForRunAsStatements(node, data); + } + + private Object checkForRunAsStatements(ApexNode node, Object data) { + final List blockStatements = node.findDescendantsOfType(ASTBlockStatement.class); + final List runAsStatements = new ArrayList<>(); + + for (ASTBlockStatement blockStatement : blockStatements) { + runAsStatements.addAll(blockStatement.findDescendantsOfType(ASTRunAsBlockStatement.class)); + } + + if (!!runAsStatements.isEmpty()) { + addViolation(data, node); + } + return data; + } +} diff --git a/pmd-apex/src/main/resources/category/apex/bestpractices.xml b/pmd-apex/src/main/resources/category/apex/bestpractices.xml index 84afa3c915..c54bb17323 100644 --- a/pmd-apex/src/main/resources/category/apex/bestpractices.xml +++ b/pmd-apex/src/main/resources/category/apex/bestpractices.xml @@ -64,6 +64,44 @@ public class Foo { + + +Apex unit tests should include at least one runAs method. This makes the tests more robust, and independent from the +user running it. + + 3 + + + + + + diff --git a/pmd-apex/src/main/resources/rulesets/apex/quickstart.xml b/pmd-apex/src/main/resources/rulesets/apex/quickstart.xml index 5fd3386416..2543193a41 100644 --- a/pmd-apex/src/main/resources/rulesets/apex/quickstart.xml +++ b/pmd-apex/src/main/resources/rulesets/apex/quickstart.xml @@ -250,6 +250,9 @@ + + 3 + diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveRunAsTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveRunAsTest.java new file mode 100644 index 0000000000..1c20e4ad7c --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveRunAsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ApexUnitTestClassShouldHaveRunAsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/bestpractices/xml/ApexUnitTestClassShouldHaveRunAs.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/bestpractices/xml/ApexUnitTestClassShouldHaveRunAs.xml new file mode 100644 index 0000000000..4eefe99873 --- /dev/null +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/bestpractices/xml/ApexUnitTestClassShouldHaveRunAs.xml @@ -0,0 +1,99 @@ + + + + + Problematic apex unit test - no runAs calls + 1 + + + + + + + ApexUnitTestClassShouldHaveRunAs assumes APEX is case sensitive + 0 + + + + + + + ApexUnitTestClassShouldHaveRunAs normal test case + 0 + + + + + + + ApexUnitTestClassShouldHaveRunAs: Verify use of custom class, negative test + 2 + 3,7 + + + + + \ No newline at end of file diff --git a/pmd-core/src/main/resources/rulesets/releases/6510.xml b/pmd-core/src/main/resources/rulesets/releases/6510.xml new file mode 100644 index 0000000000..34f6df9363 --- /dev/null +++ b/pmd-core/src/main/resources/rulesets/releases/6510.xml @@ -0,0 +1,14 @@ + + + + +This ruleset contains links to rules that are new in PMD v6.51.0 + + + + + + From 3ee4926f64e0fd1ccf808e16c249cc98c7bbe73b Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 13 Oct 2022 11:08:42 +0200 Subject: [PATCH 026/254] [java] StringToString false-positive with local method name confusion Fixes #3977 --- .../rule/performance/StringToStringRule.java | 9 ++++++ .../rule/performance/xml/StringToString.xml | 28 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/StringToStringRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/StringToStringRule.java index 606a9100a8..3b6b00a51f 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/StringToStringRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/StringToStringRule.java @@ -250,6 +250,15 @@ public class StringToStringRule extends AbstractJavaRule { if (!methodCallArgs.isArguments()) { return null; } + + // exclude method call chains + if (methodCall.getIndexInParent() > 0) { + JavaNode previousSibling = methodCall.getParent().getChild(methodCall.getIndexInParent() - 1); + if (previousSibling instanceof ASTPrimarySuffix && ((ASTPrimarySuffix) previousSibling).isArguments()) { + return null; + } + } + ASTArguments arguments = methodCallArgs.getFirstChildOfType(ASTArguments.class); ASTArgumentList argumentList = arguments.getFirstChildOfType(ASTArgumentList.class); List candidates = getMethodsByNameAndArgsCount(methodName, arguments.size()); diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/StringToString.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/StringToString.xml index 3642768b5c..0551612555 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/StringToString.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/StringToString.xml @@ -313,4 +313,32 @@ public class Foo { } ]]> + + + [java] StringToString false-positive with local method name confusion #3977 + 0 + + From 58b3f02a165f811ea330a85a74918694e20719f3 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 13 Oct 2022 11:46:30 +0200 Subject: [PATCH 027/254] [java] StringToString - use type resolution --- .../rule/performance/StringToStringRule.java | 15 ++++---- .../stringtostring/StringToStringFP.java | 27 +++++++++++++++ .../rule/performance/stringtostring/User.java | 3 ++ .../rule/performance/xml/StringToString.xml | 34 ++++++++++++++----- 4 files changed, 62 insertions(+), 17 deletions(-) create mode 100644 pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/stringtostring/StringToStringFP.java diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/StringToStringRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/StringToStringRule.java index 3b6b00a51f..461d6e62f5 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/StringToStringRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/StringToStringRule.java @@ -186,6 +186,13 @@ public class StringToStringRule extends AbstractJavaRule { } private boolean calledMethodReturnsString(JavaNode methodCall, ASTPrimarySuffix methodCallArgs) { + // first try to use type resolution for method call expression + // the return type is attached on the PrimarySuffix node which contains the arguments + if (methodCallArgs.isArguments() && methodCallArgs.getTypeDefinition() != null) { + return TypeTestUtil.isA(String.class, methodCallArgs); + } + + // next: find method in the local compilation unit String returnTypeName = getCalledMethodReturnTypeName(methodCall, methodCallArgs); return "String".equals(returnTypeName); } @@ -251,14 +258,6 @@ public class StringToStringRule extends AbstractJavaRule { return null; } - // exclude method call chains - if (methodCall.getIndexInParent() > 0) { - JavaNode previousSibling = methodCall.getParent().getChild(methodCall.getIndexInParent() - 1); - if (previousSibling instanceof ASTPrimarySuffix && ((ASTPrimarySuffix) previousSibling).isArguments()) { - return null; - } - } - ASTArguments arguments = methodCallArgs.getFirstChildOfType(ASTArguments.class); ASTArgumentList argumentList = arguments.getFirstChildOfType(ASTArgumentList.class); List candidates = getMethodsByNameAndArgsCount(methodName, arguments.size()); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/stringtostring/StringToStringFP.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/stringtostring/StringToStringFP.java new file mode 100644 index 0000000000..8e4fee4e7f --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/stringtostring/StringToStringFP.java @@ -0,0 +1,27 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.performance.stringtostring; + +public class StringToStringFP { + private String s; + + public void print(A a) { + this.s = a.getB().getC().toString(); // line 5 - false positive + this.s = String.valueOf(a.getB().getC()); // workaround + System.out.println(s); + } + + public String getC() { + return ""; + } + + interface A { + B getB(); + } + + interface B { + Character getC(); + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/stringtostring/User.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/stringtostring/User.java index 309fc5eeee..6f2d313c98 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/stringtostring/User.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/stringtostring/User.java @@ -6,4 +6,7 @@ package net.sourceforge.pmd.lang.java.rule.performance.stringtostring; public class User { + public String getName() { + return "username"; + } } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/StringToString.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/StringToString.xml index 0551612555..f5b88039ac 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/StringToString.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/StringToString.xml @@ -250,7 +250,7 @@ public class Foo { import java.io.*; class B { public void foo() { - String s = new A().str().toString(); // not detected because str() is from another class + String s = new A().str().toString(); // not detected because str() is from another class which is not on the auxclasspath s = getString().toString(); // detected s = getData(new FileInputStream()).toString(); // detected because of argument (sub) type s = getData(new Integer(4), new Integer(5)).toString(); // detected because of unique args count @@ -318,11 +318,12 @@ public class Foo { [java] StringToString false-positive with local method name confusion #3977 0 + + + + Use type resolution for method call return type + 1 + 5 + From b26e0bda8cf0e8884908d3cb8adf3007682403a7 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 13 Oct 2022 11:53:28 +0200 Subject: [PATCH 028/254] [java] StringToString - consider literals Fixes #3681 --- .../rule/performance/StringToStringRule.java | 15 +++++++++++++-- .../java/rule/performance/xml/StringToString.xml | 16 ++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/StringToStringRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/StringToStringRule.java index 461d6e62f5..0cc9958a6a 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/StringToStringRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/StringToStringRule.java @@ -111,7 +111,7 @@ public class StringToStringRule extends AbstractJavaRule { for (NameOccurrence varUsage : varId.getUsages()) { NameOccurrence qualifier = getVarUsageQualifier(varUsage); if (isToStringOnStringCall(varId, qualifier)) { - addViolation(data, varUsage.getLocation()); + asCtx(data).addViolation(varUsage.getLocation()); } } } @@ -164,7 +164,7 @@ public class StringToStringRule extends AbstractJavaRule { JavaNode prevMethodCall = primaryExpr.getChild(callIndex - 2); ASTPrimarySuffix prevMethodCallArgs = (ASTPrimarySuffix) primaryExpr.getChild(callIndex - 1); if (calledMethodReturnsString(prevMethodCall, prevMethodCallArgs)) { - addViolation(data, methodCall); + asCtx(data).addViolation(methodCall); } } } @@ -172,6 +172,17 @@ public class StringToStringRule extends AbstractJavaRule { return super.visit(primaryExpr, data); } + @Override + public Object visit(ASTPrimarySuffix node, Object data) { + if (isToStringMethodCall(node) && node.getIndexInParent() > 0) { + JavaNode previousSibling = node.getParent().getChild(node.getIndexInParent() - 1); + if (previousSibling instanceof ASTPrimaryPrefix && TypeTestUtil.isA(String.class, (ASTPrimaryPrefix) previousSibling)) { + asCtx(data).addViolation(node); + } + } + return super.visit(node, data); + } + private boolean hasChainedMethods(ASTPrimaryExpression primaryExpr) { return primaryExpr.getNumChildren() >= 4; } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/StringToString.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/StringToString.xml index f5b88039ac..1256bfbe96 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/StringToString.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/StringToString.xml @@ -355,6 +355,22 @@ public class Foo { String s = user.getName().toString(); } } +]]> + + + + [java] StringToString doesn't trigger on string literals #3681 + 1 + 3 + From 5d08ecd5be1248a5eda9dc4471b75c32160f9813 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 13 Oct 2022 12:25:34 +0200 Subject: [PATCH 029/254] [java] StringToString - use type resolution for field access Fixes #2080 --- .../rule/performance/StringToStringRule.java | 11 ++++++++- .../performance/stringtostring/Issue2080.java | 20 ++++++++++++++++ .../rule/performance/xml/StringToString.xml | 23 +++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/stringtostring/Issue2080.java diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/StringToStringRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/StringToStringRule.java index 0cc9958a6a..3d823726d5 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/StringToStringRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/StringToStringRule.java @@ -11,6 +11,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTArgumentList; import net.sourceforge.pmd.lang.java.ast.ASTArguments; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBody; @@ -133,11 +134,19 @@ public class StringToStringRule extends AbstractJavaRule { private boolean isToStringOnStringCall(ASTVariableDeclaratorId varDeclaratorId, NameOccurrence qualifier) { if (qualifier != null) { return isNotAMethodReference(qualifier) && isNotAnArrayField(varDeclaratorId, qualifier) - && isToString(qualifier.getImage()); + && isToString(qualifier.getImage()) && isStringAccess(qualifier); } return false; } + private boolean isStringAccess(NameOccurrence qualifier) { + Node parent = qualifier.getLocation().getParent(); + if (parent instanceof ASTPrimaryPrefix) { + return TypeTestUtil.isA(String.class, (ASTPrimaryPrefix) parent); + } + return true; + } + private boolean isNotAnArrayField(ASTVariableDeclaratorId varDeclaratorId, NameOccurrence qualifier) { return !varDeclaratorId.hasArrayType() || isNotAName(qualifier); } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/stringtostring/Issue2080.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/stringtostring/Issue2080.java new file mode 100644 index 0000000000..122ee476ef --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/stringtostring/Issue2080.java @@ -0,0 +1,20 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.performance.stringtostring; + +public final class Issue2080 { + String value; + + public void foo() { + A a = new A(); + value = a.value.toString(); // false positive + } + + class A { + B value; + } + + class B { } +} diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/StringToString.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/StringToString.xml index 1256bfbe96..d1d1b0cb44 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/StringToString.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/StringToString.xml @@ -371,6 +371,29 @@ class Impl { return 'a'.toString(); } } +]]> + + + + [java] StringToString rule false-positive with field access #2080 + 0 + From 1f26319325c20e369ed7bd45138aacd43bcb698e Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 13 Oct 2022 13:17:53 +0200 Subject: [PATCH 030/254] [java] Support class literal in type resolution Fixes #3437 --- .../java/typeresolution/ClassTypeResolver.java | 6 +++++- .../pmd/typeresolution/ClassTypeResolverTest.java | 9 +++++++++ .../java/rule/performance/xml/StringToString.xml | 14 ++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/ClassTypeResolver.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/ClassTypeResolver.java index 9a813dbf2e..efb8e492af 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/ClassTypeResolver.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/ClassTypeResolver.java @@ -1130,7 +1130,11 @@ public class ClassTypeResolver extends JavaParserVisitorAdapter implements Nulla @Override public Object visit(ASTPrimaryPrefix node, Object data) { super.visit(node, data); - rollupTypeUnary(node); + if ("class".equals(node.jjtGetLastToken().getImage())) { + node.setTypeDefinition(JavaTypeDefinition.forClass(Class.class)); + } else { + rollupTypeUnary(node); + } return data; } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/ClassTypeResolverTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/ClassTypeResolverTest.java index 3b8680ccca..8bef625e79 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/ClassTypeResolverTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/ClassTypeResolverTest.java @@ -1891,6 +1891,15 @@ public class ClassTypeResolverTest { assertEquals(forClass(Arrays.class), expr.getFirstChildOfType(ASTPrimaryPrefix.class).getTypeDefinition()); } + @Test + public void testClassLiteral() { + ASTCompilationUnit unit = java11.parse("public class ClassLiteral { String s = ClassLiteral.class.getName(); }"); + ASTPrimaryPrefix node = unit.getFirstDescendantOfType(ASTPrimaryPrefix.class); + assertEquals("class", node.jjtGetLastToken().getImage()); + assertNotNull(node.getTypeDefinition()); + assertSame(Class.class, node.getTypeDefinition().getType()); + } + private JavaTypeDefinition getChildTypeDef(Node node, int childIndex) { return ((TypeNode) node.getChild(childIndex)).getTypeDefinition(); } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/StringToString.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/StringToString.xml index d1d1b0cb44..9f18d23a10 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/StringToString.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/StringToString.xml @@ -394,6 +394,20 @@ public final class Issue2080 { class B { } } +]]> + + + + [java] StringToString doesn't trigger on Bar.class.getSimpleName().toString() #3437 + 1 + 4 + From 39c26d97f9e69c81458984b78e934963ad432ef8 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 13 Oct 2022 13:20:49 +0200 Subject: [PATCH 031/254] [doc] Update release notes (#2080, #3437, #3681, #3977) --- docs/pages/release_notes.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index ffc4e4ac9c..5806b4fa23 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -19,6 +19,11 @@ This is a {{ site.pmd.release_type }} release. * [#4144](https://github.com/pmd/pmd/pull/4144) \[doc] Update docs to reflect supported languages * java-documentation * [#4141](https://github.com/pmd/pmd/issues/4141): \[java] UncommentedEmptyConstructor FP when constructor annotated with @Autowired +* java-performance + * [#2080](https://github.com/pmd/pmd/issues/2080): \[java] StringToString rule false-positive with field access + * [#3437](https://github.com/pmd/pmd/issues/3437): \[java] StringToString doesn't trigger on Bar.class.getSimpleName().toString() + * [#3681](https://github.com/pmd/pmd/issues/3681): \[java] StringToString doesn't trigger on string literals + * [#3977](https://github.com/pmd/pmd/issues/3977): \[java] StringToString false-positive with local method name confusion ### API Changes From 896c7c6b781a11b566277b38343e24f2b212d8eb Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 13 Oct 2022 14:43:42 +0200 Subject: [PATCH 032/254] [java] StringToString - update javadoc --- .../rule/performance/StringToStringRule.java | 32 ------------------- .../rule/performance/xml/StringToString.xml | 32 +++++++++++++++++++ 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/StringToStringRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/StringToStringRule.java index 3d823726d5..0295c52253 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/StringToStringRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/StringToStringRule.java @@ -36,38 +36,6 @@ import net.sourceforge.pmd.lang.symboltable.ScopedNode; /** * Finds toString() call on String object. - * - * Note: due to an issue with type resolution, this implementation doesn't detect cases when toString() - * call is chained to a method returning String which is not declared in the class having the call or the call - * arguments are not of the exact same type as method parameters are, excluding the case when method name and - * number of it's parameters is enough to identify the method. Example: - *

{@code
- *    class A {
- *         public String str() {
- *            return "exampleStr";
- *         }
- *    }
- *    class B {
- *        public void foo() {
- *            String s = new A().str().toString(); // not detected because str() is from another class
- *            s = getString().toString(); // detected
- *            s = getData(new FileInputStream()).toString(); // detected because of argument type
- *            s = getData(new Integer(4), new Integer(5)).toString(); // detected because of unique args count
- *        }
- *        public String getString() {
- *            return "exampleStr";
- *        }
- *        public String getData(InputStream is) {
- *            return "argsResolutionIssueExample";
- *        }
- *        public int getData(String s) {
- *            return 0;
- *        }
- *        public String getData(Number a, Number b) {
- *            return "uniqueArgsCountExample";
- *        }
- *    }
- *    }
*/ public class StringToStringRule extends AbstractJavaRule { diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/StringToString.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/StringToString.xml index 9f18d23a10..ddba030397 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/StringToString.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/StringToString.xml @@ -271,6 +271,38 @@ class B { ]]> + + Limitations with type resolution + 4 + 7,8,9,10 + + + variable as method call argument 1 From 46e2007ce42706f8a40bf5958eaaf4cf7b4ea94b Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 13 Oct 2022 15:08:57 +0200 Subject: [PATCH 033/254] [java] StringToString - fix false positive --- .../rule/performance/StringToStringRule.java | 2 +- .../rule/performance/stringtostring/User.java | 6 +++++ .../rule/performance/xml/StringToString.xml | 26 +++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/StringToStringRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/StringToStringRule.java index 0295c52253..f1284ba071 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/StringToStringRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/StringToStringRule.java @@ -191,7 +191,7 @@ public class StringToStringRule extends AbstractJavaRule { } private String getCalledMethodName(JavaNode methodCall) { - ASTName name = methodCall.getFirstDescendantOfType(ASTName.class); + ASTName name = methodCall.getFirstChildOfType(ASTName.class); return name != null ? name.getImage() : methodCall.getImage(); } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/stringtostring/User.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/stringtostring/User.java index 6f2d313c98..2eb922808b 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/stringtostring/User.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/stringtostring/User.java @@ -4,9 +4,15 @@ package net.sourceforge.pmd.lang.java.rule.performance.stringtostring; +import java.util.Locale; + public class User { public String getName() { return "username"; } + + private String convert(String s) { + return s.toLowerCase(Locale.ROOT); + } } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/StringToString.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/StringToString.xml index ddba030397..15e367e21a 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/StringToString.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/StringToString.xml @@ -440,6 +440,32 @@ public class Foo { return new StringJoiner(",", Bar.class.getSimpleName().toString() + "[", "]"); } } +]]> + + + + False positive with unqualified toString() call + 0 + From 82e7da5d0e760c1546dd20f814aa447ca03704fb Mon Sep 17 00:00:00 2001 From: Yasar Shaikh Date: Fri, 14 Oct 2022 12:50:36 +0530 Subject: [PATCH 034/254] Added issue number in the Description for the AvoidArrayLoops Co-authored-by: Andreas Dangel --- .../pmd/lang/java/rule/performance/xml/AvoidArrayLoops.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidArrayLoops.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidArrayLoops.xml index cfbd55d3f0..9b95869542 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidArrayLoops.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidArrayLoops.xml @@ -49,7 +49,7 @@ public class Foo { - copy via do-while loop + copy via do-while loop #4091 1 Date: Fri, 14 Oct 2022 09:21:34 +0200 Subject: [PATCH 035/254] Update pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveRunAsRule.java Co-authored-by: Andreas Dangel --- .../ApexUnitTestClassShouldHaveRunAsRule.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveRunAsRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveRunAsRule.java index 4a403c50ee..105cc57196 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveRunAsRule.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveRunAsRule.java @@ -30,12 +30,7 @@ public class ApexUnitTestClassShouldHaveRunAsRule extends AbstractApexUnitTestRu } private Object checkForRunAsStatements(ApexNode node, Object data) { - final List blockStatements = node.findDescendantsOfType(ASTBlockStatement.class); - final List runAsStatements = new ArrayList<>(); - - for (ASTBlockStatement blockStatement : blockStatements) { - runAsStatements.addAll(blockStatement.findDescendantsOfType(ASTRunAsBlockStatement.class)); - } + final List runAsStatements = node.findDescendantsOfType(ASTRunAsBlockStatement.class); if (!!runAsStatements.isEmpty()) { addViolation(data, node); From d6f3b3c68e3f3dad282bfba8c8226ff909771bf8 Mon Sep 17 00:00:00 2001 From: Thomas Prouvot Date: Fri, 14 Oct 2022 09:26:47 +0200 Subject: [PATCH 036/254] revert changes on deprecvated rule --- pmd-apex/src/main/resources/rulesets/apex/apexunit.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/pmd-apex/src/main/resources/rulesets/apex/apexunit.xml b/pmd-apex/src/main/resources/rulesets/apex/apexunit.xml index 9c6547ef8a..efcca12aae 100644 --- a/pmd-apex/src/main/resources/rulesets/apex/apexunit.xml +++ b/pmd-apex/src/main/resources/rulesets/apex/apexunit.xml @@ -9,7 +9,6 @@ - From ef48c7628c8489149c09b8f71ad91b6395ac5942 Mon Sep 17 00:00:00 2001 From: Thomas Prouvot Date: Fri, 14 Oct 2022 09:36:53 +0200 Subject: [PATCH 037/254] Add a test for a normal class --- .../xml/ApexUnitTestClassShouldHaveRunAs.xml | 80 ++++++++++++------- 1 file changed, 51 insertions(+), 29 deletions(-) diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/bestpractices/xml/ApexUnitTestClassShouldHaveRunAs.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/bestpractices/xml/ApexUnitTestClassShouldHaveRunAs.xml index 4eefe99873..3f23a6cd33 100644 --- a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/bestpractices/xml/ApexUnitTestClassShouldHaveRunAs.xml +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/bestpractices/xml/ApexUnitTestClassShouldHaveRunAs.xml @@ -1,11 +1,11 @@ - - Problematic apex unit test - no runAs calls - 1 - - + Problematic apex unit test - no runAs calls + 1 + + - - + + - - ApexUnitTestClassShouldHaveRunAs assumes APEX is case sensitive - 0 - - + ApexUnitTestClassShouldHaveRunAs assumes APEX is case sensitive + 0 + + - - + + - - ApexUnitTestClassShouldHaveRunAs normal test case - 0 - - + ApexUnitTestClassShouldHaveRunAs normal test case + 0 + + - - + + - - ApexUnitTestClassShouldHaveRunAs: Verify use of custom class, negative test - 2 - 3,7 - - + ApexUnitTestClassShouldHaveRunAs: Verify use of custom class, negative test + 2 + 3,7 + + - - + + + + + ApexUnitTestClassShouldHaveRunAs - no runAs calls outside a test class + 0 + + + + \ No newline at end of file From 78a49175a51665dbfd329df020371d02efa2e2bd Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Fri, 14 Oct 2022 09:48:36 +0200 Subject: [PATCH 038/254] [java] Use type resolution for UseArrayListInsteadOfVector Fixes #4148 --- docs/pages/release_notes.md | 2 + .../resources/category/java/performance.xml | 7 ++- .../xml/UseArrayListInsteadOfVector.xml | 49 +++++++++++++++++-- 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index ffc4e4ac9c..f26a7e1327 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -19,6 +19,8 @@ This is a {{ site.pmd.release_type }} release. * [#4144](https://github.com/pmd/pmd/pull/4144) \[doc] Update docs to reflect supported languages * java-documentation * [#4141](https://github.com/pmd/pmd/issues/4141): \[java] UncommentedEmptyConstructor FP when constructor annotated with @Autowired +* java-performance + * [#4148](https://github.com/pmd/pmd/issues/4148): \[java] UseArrayListInsteadOfVector ignores Vector when other classes are imported ### API Changes diff --git a/pmd-java/src/main/resources/category/java/performance.xml b/pmd-java/src/main/resources/category/java/performance.xml index 84a7da762d..a873398f3a 100644 --- a/pmd-java/src/main/resources/category/java/performance.xml +++ b/pmd-java/src/main/resources/category/java/performance.xml @@ -928,16 +928,15 @@ ArrayList is a much better Collection implementation than Vector if thread-safe - - TEST0 + No problem using List and ArrayList 0 - TEST1 + Just using Vector 1 + 4 - TEST2 + Using Vector as field 1 + 3 - TEST3 + Using Vector as List 1 + 4 #1146 real problem 1 + 4 + + + [java] UseArrayListInsteadOfVector ignores Vector when other classes are imported #4148 - sample 1 + 1 + 6 + + + + + [java] UseArrayListInsteadOfVector ignores Vector when other classes are imported #4148 - sample 2 + 1 + 5 + + From 3cd67eabf31ba9eae61dae51c95f5a41e975b9a6 Mon Sep 17 00:00:00 2001 From: Thomas Prouvot Date: Fri, 14 Oct 2022 09:52:04 +0200 Subject: [PATCH 039/254] Set default message and priority to the rule --- .../ApexUnitTestClassShouldHaveRunAsRule.java | 7 +++++++ pmd-apex/src/main/resources/rulesets/apex/quickstart.xml | 4 +--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveRunAsRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveRunAsRule.java index 105cc57196..2e5a62502d 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveRunAsRule.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveRunAsRule.java @@ -7,6 +7,7 @@ package net.sourceforge.pmd.lang.apex.rule.bestpractices; import java.util.ArrayList; import java.util.List; +import net.sourceforge.pmd.RulePriority; import net.sourceforge.pmd.lang.apex.ast.ASTBlockStatement; import net.sourceforge.pmd.lang.apex.ast.ASTMethod; import net.sourceforge.pmd.lang.apex.ast.ASTRunAsBlockStatement; @@ -20,6 +21,12 @@ import net.sourceforge.pmd.lang.apex.rule.AbstractApexUnitTestRule; */ public class ApexUnitTestClassShouldHaveRunAsRule extends AbstractApexUnitTestRule { + public ApexUnitTestClassShouldHaveRunAsRule() { + setName("ApexUnitTestClassShouldHaveRunAsRule"); + setMessage("Apex unit test classes should have at least one System.runAs() call"); + setPriority(RulePriority.MEDIUM); + } + @Override public Object visit(ASTMethod node, Object data) { if (!isTestMethodOrClass(node)) { diff --git a/pmd-apex/src/main/resources/rulesets/apex/quickstart.xml b/pmd-apex/src/main/resources/rulesets/apex/quickstart.xml index 2543193a41..98b159c09f 100644 --- a/pmd-apex/src/main/resources/rulesets/apex/quickstart.xml +++ b/pmd-apex/src/main/resources/rulesets/apex/quickstart.xml @@ -250,9 +250,7 @@ - - 3 - + From bac0e6e999a0b8015cc9f40c355a6e88a7a83e44 Mon Sep 17 00:00:00 2001 From: Thomas Prouvot <35368290+tprouvot@users.noreply.github.com> Date: Fri, 14 Oct 2022 09:52:52 +0200 Subject: [PATCH 040/254] Update pmd-apex/src/main/resources/category/apex/bestpractices.xml Co-authored-by: Andreas Dangel --- pmd-apex/src/main/resources/category/apex/bestpractices.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pmd-apex/src/main/resources/category/apex/bestpractices.xml b/pmd-apex/src/main/resources/category/apex/bestpractices.xml index c54bb17323..66133ba92b 100644 --- a/pmd-apex/src/main/resources/category/apex/bestpractices.xml +++ b/pmd-apex/src/main/resources/category/apex/bestpractices.xml @@ -67,7 +67,7 @@ public class Foo { From 150f109db480b70361f1a38c53ab0bf97545b177 Mon Sep 17 00:00:00 2001 From: Thomas Prouvot <35368290+tprouvot@users.noreply.github.com> Date: Fri, 14 Oct 2022 10:03:45 +0200 Subject: [PATCH 041/254] Update pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveRunAsRule.java Co-authored-by: Andreas Dangel --- .../bestpractices/ApexUnitTestClassShouldHaveRunAsRule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveRunAsRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveRunAsRule.java index 2e5a62502d..11b9641c11 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveRunAsRule.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveRunAsRule.java @@ -39,7 +39,7 @@ public class ApexUnitTestClassShouldHaveRunAsRule extends AbstractApexUnitTestRu private Object checkForRunAsStatements(ApexNode node, Object data) { final List runAsStatements = node.findDescendantsOfType(ASTRunAsBlockStatement.class); - if (!!runAsStatements.isEmpty()) { + if (runAsStatements.isEmpty()) { addViolation(data, node); } return data; From d0d2d2a6f737fa7db6a37c040913345378749a5b Mon Sep 17 00:00:00 2001 From: Thomas Prouvot Date: Fri, 14 Oct 2022 10:24:00 +0200 Subject: [PATCH 042/254] Remove unused imports --- .../bestpractices/ApexUnitTestClassShouldHaveRunAsRule.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveRunAsRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveRunAsRule.java index 2e5a62502d..dade018d57 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveRunAsRule.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveRunAsRule.java @@ -4,11 +4,9 @@ package net.sourceforge.pmd.lang.apex.rule.bestpractices; -import java.util.ArrayList; import java.util.List; import net.sourceforge.pmd.RulePriority; -import net.sourceforge.pmd.lang.apex.ast.ASTBlockStatement; import net.sourceforge.pmd.lang.apex.ast.ASTMethod; import net.sourceforge.pmd.lang.apex.ast.ASTRunAsBlockStatement; import net.sourceforge.pmd.lang.apex.ast.ApexNode; @@ -39,7 +37,7 @@ public class ApexUnitTestClassShouldHaveRunAsRule extends AbstractApexUnitTestRu private Object checkForRunAsStatements(ApexNode node, Object data) { final List runAsStatements = node.findDescendantsOfType(ASTRunAsBlockStatement.class); - if (!!runAsStatements.isEmpty()) { + if (runAsStatements.isEmpty()) { addViolation(data, node); } return data; From 173a41b71215580f97ad59de3449c8bb1dfc2870 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Fri, 14 Oct 2022 10:26:18 +0200 Subject: [PATCH 043/254] [java] UseArrayListInsteadOfVector - use typeIsExactly --- .../src/main/resources/category/java/performance.xml | 2 +- .../performance/xml/UseArrayListInsteadOfVector.xml | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/pmd-java/src/main/resources/category/java/performance.xml b/pmd-java/src/main/resources/category/java/performance.xml index a873398f3a..97975ae971 100644 --- a/pmd-java/src/main/resources/category/java/performance.xml +++ b/pmd-java/src/main/resources/category/java/performance.xml @@ -929,7 +929,7 @@ ArrayList is a much better Collection implementation than Vector if thread-safe diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/UseArrayListInsteadOfVector.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/UseArrayListInsteadOfVector.xml index e10af1a2cc..e1d4e96a60 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/UseArrayListInsteadOfVector.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/UseArrayListInsteadOfVector.xml @@ -114,6 +114,17 @@ public class C { java.util.Vector v = new java.util.Vector(); // should report a warning } } +]]> + + + + Only consider Vector and not subclasses + 0 + From a88ad1cd5da58ca52f89d008ff4aacc013a1b116 Mon Sep 17 00:00:00 2001 From: Thomas Prouvot <35368290+tprouvot@users.noreply.github.com> Date: Fri, 14 Oct 2022 11:01:01 +0200 Subject: [PATCH 044/254] Update pmd-core/src/main/resources/rulesets/releases/6510.xml Co-authored-by: Andreas Dangel --- pmd-core/src/main/resources/rulesets/releases/6510.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pmd-core/src/main/resources/rulesets/releases/6510.xml b/pmd-core/src/main/resources/rulesets/releases/6510.xml index 34f6df9363..f9d787e9c6 100644 --- a/pmd-core/src/main/resources/rulesets/releases/6510.xml +++ b/pmd-core/src/main/resources/rulesets/releases/6510.xml @@ -8,7 +8,7 @@ This ruleset contains links to rules that are new in PMD v6.51.0 - + From d1328b2ba7203854835bf0c2adaf1ab4c552a8e2 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Fri, 14 Oct 2022 11:41:51 +0200 Subject: [PATCH 045/254] [java] AvoidArrayLoops - rework XPath expression Fixes #1167 --- docs/pages/release_notes.md | 2 + .../resources/category/java/performance.xml | 46 ++++++++++--------- .../rule/performance/xml/AvoidArrayLoops.xml | 44 +++++++++++++++++- 3 files changed, 69 insertions(+), 23 deletions(-) diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index ffc4e4ac9c..9d5bbc503f 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -19,6 +19,8 @@ This is a {{ site.pmd.release_type }} release. * [#4144](https://github.com/pmd/pmd/pull/4144) \[doc] Update docs to reflect supported languages * java-documentation * [#4141](https://github.com/pmd/pmd/issues/4141): \[java] UncommentedEmptyConstructor FP when constructor annotated with @Autowired +* java-performance + * [#1167](https://github.com/pmd/pmd/issues/1167): \[java] AvoidArrayLoops false positive on double assignment ### API Changes diff --git a/pmd-java/src/main/resources/category/java/performance.xml b/pmd-java/src/main/resources/category/java/performance.xml index 84a7da762d..3e7db732f7 100644 --- a/pmd-java/src/main/resources/category/java/performance.xml +++ b/pmd-java/src/main/resources/category/java/performance.xml @@ -80,22 +80,22 @@ Instead of manually copying data between two arrays, use the efficient Arrays.co @@ -103,17 +103,19 @@ or AdditiveExpression) and count(.//PrimaryPrefix/Name)=1] 0 + + + [java] AvoidArrayLoops false positive on double assignment #1167 + 0 + + + + + Nested array on RHS + 0 + + From 30885b6212b182027eff8c84590a0976539b2eee Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Fri, 14 Oct 2022 11:51:57 +0200 Subject: [PATCH 046/254] [java] AvoidArrayLoops - ignore multi-dim array assignment --- .../resources/category/java/performance.xml | 2 +- .../rule/performance/xml/AvoidArrayLoops.xml | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/pmd-java/src/main/resources/category/java/performance.xml b/pmd-java/src/main/resources/category/java/performance.xml index 3e7db732f7..9d6d20f59e 100644 --- a/pmd-java/src/main/resources/category/java/performance.xml +++ b/pmd-java/src/main/resources/category/java/performance.xml @@ -86,7 +86,7 @@ Instead of manually copying data between two arrays, use the efficient Arrays.co [*/Statement/Block/BlockStatement/Statement /StatementExpression[AssignmentOperator[@Image = '=']] (: LHS - left hand side :) - [PrimaryExpression/PrimarySuffix[@ArrayDereference = true()] + [PrimaryExpression[count(PrimarySuffix) = 1]/PrimarySuffix[@ArrayDereference = true()] /Expression[PrimaryExpression or AdditiveExpression] [count(.//PrimaryPrefix/Name) = 1] //PrimaryPrefix/Name/@Image] diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidArrayLoops.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidArrayLoops.xml index 24502e798e..345bffa225 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidArrayLoops.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidArrayLoops.xml @@ -158,6 +158,31 @@ class AvoidArrayLoops { } } } +]]> + + + + Ignore multi-dim array assignment #1167 + 0 + From c8649b73e42950d1fc780561e7aa5cd3ff851506 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Fri, 14 Oct 2022 12:06:29 +0200 Subject: [PATCH 047/254] [java] AvoidArrayLoops - consider final variables Fixes #3847 --- docs/pages/release_notes.md | 1 + .../resources/category/java/performance.xml | 8 ++++-- .../rule/performance/xml/AvoidArrayLoops.xml | 25 +++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index 9d5bbc503f..4c931a7256 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -21,6 +21,7 @@ This is a {{ site.pmd.release_type }} release. * [#4141](https://github.com/pmd/pmd/issues/4141): \[java] UncommentedEmptyConstructor FP when constructor annotated with @Autowired * java-performance * [#1167](https://github.com/pmd/pmd/issues/1167): \[java] AvoidArrayLoops false positive on double assignment + * [#3847](https://github.com/pmd/pmd/issues/3847): \[java] AvoidArrayLoops should consider final variables ### API Changes diff --git a/pmd-java/src/main/resources/category/java/performance.xml b/pmd-java/src/main/resources/category/java/performance.xml index 9d6d20f59e..5d7999a730 100644 --- a/pmd-java/src/main/resources/category/java/performance.xml +++ b/pmd-java/src/main/resources/category/java/performance.xml @@ -88,12 +88,16 @@ Instead of manually copying data between two arrays, use the efficient Arrays.co (: LHS - left hand side :) [PrimaryExpression[count(PrimarySuffix) = 1]/PrimarySuffix[@ArrayDereference = true()] /Expression[PrimaryExpression or AdditiveExpression] - [count(.//PrimaryPrefix/Name) = 1] + [count(.//PrimaryPrefix/Name) = 1 or + (count(.//PrimaryPrefix/Name) = 2 and .//PrimaryPrefix/Name/@Image = //VariableDeclaratorId[@Final = true()]/@Name) + ] //PrimaryPrefix/Name/@Image] (: RHS - right hand side :) [Expression/PrimaryExpression/PrimarySuffix[@ArrayDereference = true()][count(..//PrimarySuffix) = 1] /Expression[PrimaryExpression or AdditiveExpression] - [count(.//PrimaryPrefix/Name) = 1] + [count(.//PrimaryPrefix/Name) = 1 or + (count(.//PrimaryPrefix/Name) = 2 and .//PrimaryPrefix/Name/@Image = //VariableDeclaratorId[@Final = true()]/@Name) + ] //PrimaryPrefix/Name/@Image] ] ]]> diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidArrayLoops.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidArrayLoops.xml index 345bffa225..e6d723ac84 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidArrayLoops.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidArrayLoops.xml @@ -183,6 +183,31 @@ class AvoidArrayLoops { } } } +]]> + + + + [java] AvoidArrayLoops should consider final variables #3847 + 3 + 6,10,13 + From a9f00ecadb1e0e0a34aed184c9297d777e83426b Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Fri, 14 Oct 2022 13:57:41 +0200 Subject: [PATCH 048/254] [java] AvoidArrayLoops - enhance documentation Fixes #2692 --- docs/pages/release_notes.md | 1 + .../resources/category/java/performance.xml | 39 ++++++++++++++++--- .../rule/performance/xml/AvoidArrayLoops.xml | 35 +++++++++++++++++ 3 files changed, 69 insertions(+), 6 deletions(-) diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index 4c931a7256..21553147dd 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -21,6 +21,7 @@ This is a {{ site.pmd.release_type }} release. * [#4141](https://github.com/pmd/pmd/issues/4141): \[java] UncommentedEmptyConstructor FP when constructor annotated with @Autowired * java-performance * [#1167](https://github.com/pmd/pmd/issues/1167): \[java] AvoidArrayLoops false positive on double assignment + * [#2692](https://github.com/pmd/pmd/issues/2692): \[java] \[doc] AvoidArrayLoops flags copy assignment in same array as sub-optimal * [#3847](https://github.com/pmd/pmd/issues/3847): \[java] AvoidArrayLoops should consider final variables ### API Changes diff --git a/pmd-java/src/main/resources/category/java/performance.xml b/pmd-java/src/main/resources/category/java/performance.xml index 5d7999a730..4ebcb9505b 100644 --- a/pmd-java/src/main/resources/category/java/performance.xml +++ b/pmd-java/src/main/resources/category/java/performance.xml @@ -72,7 +72,12 @@ sb.append('a'); // use this instead class="net.sourceforge.pmd.lang.rule.XPathRule" externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_performance.html#avoidarrayloops"> -Instead of manually copying data between two arrays, use the efficient Arrays.copyOf or System.arraycopy method instead. +Instead of manually copying data between two arrays, use the more efficient `Arrays.copyOf` +or `System.arraycopy` method instead. + +To copy only part of the array, use `Arrays.copyOfRange` or `System.arraycopy`. + +If you want to copy/move elements inside the _same_ array (e.g. shift the elements), use `System.arraycopy`. 3 @@ -106,23 +111,45 @@ Instead of manually copying data between two arrays, use the efficient Arrays.co + + + 0; i--) { + a[i] = a[i - 1]; + } + // equivalent + System.arraycopy(a, 0, a, 1, a.length - 1); + } +} ]]> diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidArrayLoops.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidArrayLoops.xml index e6d723ac84..282a60b277 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidArrayLoops.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidArrayLoops.xml @@ -208,6 +208,41 @@ class AvoidArrayLoops { } } } +]]> + + + + Shifting left and right #2692 + 2 + 9,19 + 0; i--) { + ints[i] = ints[i - 1]; + } + System.out.println(Arrays.toString(ints)); // [1, 1, 2, 3, 4, 5, 6, 7, 8, 9] + ints = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + System.arraycopy(ints, 0, ints, 1, ints.length - 1); + System.out.println(Arrays.toString(ints)); // [1, 1, 2, 3, 4, 5, 6, 7, 8, 9] + } +} ]]> From df2d1b806e4c20c292bf7c6213128b9c2756fba3 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Fri, 14 Oct 2022 16:57:47 +0200 Subject: [PATCH 049/254] [apex] ApexUnitTestClassShouldHaveRunAsRule - remove unnecessary constructor --- .../ApexUnitTestClassShouldHaveRunAsRule.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveRunAsRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveRunAsRule.java index dade018d57..7a417a43b0 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveRunAsRule.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveRunAsRule.java @@ -6,7 +6,6 @@ package net.sourceforge.pmd.lang.apex.rule.bestpractices; import java.util.List; -import net.sourceforge.pmd.RulePriority; import net.sourceforge.pmd.lang.apex.ast.ASTMethod; import net.sourceforge.pmd.lang.apex.ast.ASTRunAsBlockStatement; import net.sourceforge.pmd.lang.apex.ast.ApexNode; @@ -19,12 +18,6 @@ import net.sourceforge.pmd.lang.apex.rule.AbstractApexUnitTestRule; */ public class ApexUnitTestClassShouldHaveRunAsRule extends AbstractApexUnitTestRule { - public ApexUnitTestClassShouldHaveRunAsRule() { - setName("ApexUnitTestClassShouldHaveRunAsRule"); - setMessage("Apex unit test classes should have at least one System.runAs() call"); - setPriority(RulePriority.MEDIUM); - } - @Override public Object visit(ASTMethod node, Object data) { if (!isTestMethodOrClass(node)) { From 8834af16f64aa1153bd3b12af8f772bf1eda6ba2 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Fri, 14 Oct 2022 16:58:40 +0200 Subject: [PATCH 050/254] [doc] Update release notes (#4149, #4150) --- docs/pages/release_notes.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index ffc4e4ac9c..2ac4d95984 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -14,7 +14,20 @@ This is a {{ site.pmd.release_type }} release. ### New and noteworthy +#### New Rules +* The new Apex rule {% rule apex/bestpractices/ApexUnitTestClassShouldHaveRunAs %} ensures that unit tests + use [System.runAs()](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_testing_tools_runas.htm) + at least once. This makes the tests more robust, and independent from the user running it. + +```xml + +``` + +The rule is part of the quickstart.xml ruleset. + ### Fixed Issues +* apex + * [#4149](https://github.com/pmd/pmd/issues/4149): \[apex] New rule: ApexUnitTestClassShouldHaveRunAs * doc * [#4144](https://github.com/pmd/pmd/pull/4144) \[doc] Update docs to reflect supported languages * java-documentation @@ -24,6 +37,7 @@ This is a {{ site.pmd.release_type }} release. ### External Contributions * [#4142](https://github.com/pmd/pmd/pull/4142): \[java] fix #4141 Update UncommentedEmptyConstructor - ignore @Autowired annotations - [Lynn](https://github.com/LynnBroe) (@LynnBroe) +* [#4150](https://github.com/pmd/pmd/pull/4150): \[apex] New rule ApexUnitTestClassShouldHaveRunAs #4149 - [Thomas Prouvot](https://github.com/tprouvot) (@tprouvot) {% endtocmaker %} From 96a1ca0a135edb9e8d3220bec69ef6b734f0e23e Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Fri, 14 Oct 2022 19:29:30 +0200 Subject: [PATCH 051/254] [java] Fix UnnecessaryFullyQualifiedName for current package java.lang Fixes #4139 --- docs/pages/release_notes.md | 2 ++ .../UnnecessaryFullyQualifiedNameRule.java | 8 +++++++- .../unnecessaryfullyqualifiedname/Deprecated.java | 11 +++++++++++ .../codestyle/xml/UnnecessaryFullyQualifiedName.xml | 13 +++++++++++++ 4 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/unnecessaryfullyqualifiedname/Deprecated.java diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index 2ac4d95984..333c431973 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -30,6 +30,8 @@ The rule is part of the quickstart.xml ruleset. * [#4149](https://github.com/pmd/pmd/issues/4149): \[apex] New rule: ApexUnitTestClassShouldHaveRunAs * doc * [#4144](https://github.com/pmd/pmd/pull/4144) \[doc] Update docs to reflect supported languages +* java-codestyle + * [#4139](https://github.com/pmd/pmd/issues/4139): \[java] UnnecessaryFullyQualifiedName FP when the same simple class name exists in the current package * java-documentation * [#4141](https://github.com/pmd/pmd/issues/4141): \[java] UncommentedEmptyConstructor FP when constructor annotated with @Autowired diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryFullyQualifiedNameRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryFullyQualifiedNameRule.java index 8091d944e3..050bcc571c 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryFullyQualifiedNameRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryFullyQualifiedNameRule.java @@ -187,7 +187,7 @@ public class UnnecessaryFullyQualifiedNameRule extends AbstractJavaRule { } if (matches.isEmpty()) { - if (isJavaLangImplicit(node)) { + if (isJavaLangImplicit(node) && !existsSameTypeInCurrentPackage(node)) { asCtx(data).addViolation(node, node.getImage(), "java.lang.*", "implicit "); } else if (isSamePackage(node, name)) { @@ -209,6 +209,12 @@ public class UnnecessaryFullyQualifiedNameRule extends AbstractJavaRule { } } + private boolean existsSameTypeInCurrentPackage(TypeNode node) { + String simpleName = node.getTypeDefinition().getType().getSimpleName(); + String fullyQualifiedName = currentPackage + "." + simpleName; + return node.getRoot().getClassTypeResolver().classNameExists(fullyQualifiedName); + } + private boolean hasSameSimpleNameInScope(TypeNode node) { final ASTCompilationUnit root = node.getRoot(); final List declarationDescendants = root.findDescendantsOfType(ASTClassOrInterfaceDeclaration.class); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/unnecessaryfullyqualifiedname/Deprecated.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/unnecessaryfullyqualifiedname/Deprecated.java new file mode 100644 index 0000000000..30b6d80c3a --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/unnecessaryfullyqualifiedname/Deprecated.java @@ -0,0 +1,11 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle.unnecessaryfullyqualifiedname; + +/** + * This class should not be confused with the annotation java.lang.Deprecated. + */ +public final class Deprecated { +} diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UnnecessaryFullyQualifiedName.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UnnecessaryFullyQualifiedName.xml index 8be00145a9..4b8332a225 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UnnecessaryFullyQualifiedName.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UnnecessaryFullyQualifiedName.xml @@ -728,6 +728,19 @@ package net.sourceforge.pmd.lang.java.rule.codestyle.unnecessaryfullyqualifiedna public class ClassA { public static class Foo implements net.sourceforge.pmd.lang.java.rule.codestyle.unnecessaryfullyqualifiedname.Foo {} } +]]> + + + + [java] UnnecessaryFullyQualifiedName FP when the same simple class name exists in the current package #4139 + 0 + From b9737e23b5a71b68a4135257e6bb10797cb16d52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Fri, 14 Oct 2022 23:34:52 -0300 Subject: [PATCH 052/254] Backport to PMD6 changes to accomodate PicoCli in PMD7 - Companion PR for #4059 - Add deprecations and path the way for the upcoming changes to the CLI in PMD7 --- .../main/java/net/sourceforge/pmd/PMD.java | 5 + .../net/sourceforge/pmd/PMDConfiguration.java | 174 ++++++++++++++++-- .../sourceforge/pmd/cli/PMDParameters.java | 26 +++ .../pmd/cli/PmdParametersParseResult.java | 7 + .../java/net/sourceforge/pmd/cpd/CPD.java | 8 + .../pmd/cpd/CPDCommandLineInterface.java | 1 + .../sourceforge/pmd/cpd/CPDConfiguration.java | 6 +- pmd-dist/src/main/resources/scripts/run.sh | 14 +- 8 files changed, 217 insertions(+), 24 deletions(-) diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java b/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java index 7c3ab37f96..632761e002 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java @@ -60,7 +60,10 @@ import net.sourceforge.pmd.util.log.internal.SimpleMessageReporter; * *

Warning: This class is not intended to be instantiated or subclassed. It will * be made final in PMD7. + * + * @deprecated This class is to be removed in PMD 7 in favor of a unified PmdCli entry point. {@link PmdAnalysis} should be used for non-CLI use-cases. */ +@Deprecated public class PMD { @@ -539,7 +542,9 @@ public class PMD { * Represents status codes that are used as exit codes during CLI runs. * * @see #runPmd(String[]) + * @deprecated This class is to be removed in PMD 7 in favor of a unified PmdCli entry point. */ + @Deprecated public enum StatusCode { /** No errors, no violations. This is exit code {@code 0}. */ OK(0), diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/PMDConfiguration.java b/pmd-core/src/main/java/net/sourceforge/pmd/PMDConfiguration.java index ad3b9603c1..e25b657363 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/PMDConfiguration.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/PMDConfiguration.java @@ -6,6 +6,9 @@ package net.sourceforge.pmd; import java.io.File; import java.io.IOException; +import java.net.URI; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -103,21 +106,23 @@ public class PMDConfiguration extends AbstractConfiguration { // Rule and source file options private List ruleSets = new ArrayList<>(); private RulePriority minimumPriority = RulePriority.LOW; - private List inputPaths = new ArrayList<>(); - private String inputUri; - private String inputFilePath; - private String ignoreFilePath; + private List inputPaths = new ArrayList<>(); + private URI inputUri; + private Path inputFilePath; + private Path ignoreFilePath; private boolean ruleSetFactoryCompatibilityEnabled = true; // Reporting options private String reportFormat; - private String reportFile; + private Path reportFile; private boolean reportShortNames = false; private Properties reportProperties = new Properties(); private boolean showSuppressedViolations = false; private boolean failOnViolation = true; + @Deprecated private boolean stressTest; + @Deprecated private boolean benchmark; private AnalysisCache analysisCache = new NoopAnalysisCache(); private boolean ignoreIncrementalAnalysis; @@ -433,8 +438,24 @@ public class PMDConfiguration extends AbstractConfiguration { * Returns an unmodifiable list. * * @throws NullPointerException If the parameter is null + * @deprecated Use {@link #getInputPathList()} */ + @Deprecated public List getAllInputPaths() { + final List ret = new ArrayList<>(inputPaths.size()); + for (final Path p : inputPaths) { + ret.add(p.toString()); + } + + return Collections.unmodifiableList(ret); + } + + /** + * Returns an unmodifiable list. + * + * @throws NullPointerException If the parameter is null + */ + public List getInputPathList() { return Collections.unmodifiableList(inputPaths); } @@ -448,8 +469,24 @@ public class PMDConfiguration extends AbstractConfiguration { */ @Deprecated public void setInputPaths(String inputPaths) { - List paths = new ArrayList<>(); - Collections.addAll(paths, inputPaths.split(",")); + List paths = new ArrayList<>(); + for (String s : inputPaths.split(",")) { + paths.add(Paths.get(s)); + } + this.inputPaths = paths; + } + + /** + * Set the input paths to the given list of paths. + * @throws NullPointerException If the parameter is null + * @deprecated Use {@link #setInputPathList(List)} + */ + @Deprecated + public void setInputPaths(List inputPaths) { + final List paths = new ArrayList<>(inputPaths.size()); + for (String s : inputPaths) { + paths.add(Paths.get(s)); + } this.inputPaths = paths; } @@ -457,7 +494,7 @@ public class PMDConfiguration extends AbstractConfiguration { * Set the input paths to the given list of paths. * @throws NullPointerException If the parameter is null */ - public void setInputPaths(List inputPaths) { + public void setInputPathList(final List inputPaths) { this.inputPaths = new ArrayList<>(inputPaths); } @@ -465,17 +502,45 @@ public class PMDConfiguration extends AbstractConfiguration { * Add an input path. It is not split on commas. * * @throws NullPointerException If the parameter is null + * @deprecated Use {@link #addInputPath(Path)} */ + @Deprecated public void addInputPath(String inputPath) { + Objects.requireNonNull(inputPath); + this.inputPaths.add(Paths.get(inputPath)); + } + + /** + * Add an input path. It is not split on commas. + * + * @throws NullPointerException If the parameter is null + */ + public void addInputPath(Path inputPath) { Objects.requireNonNull(inputPath); this.inputPaths.add(inputPath); } + /** + * @deprecated Use {@link #getInputFile()} + */ + @Deprecated public String getInputFilePath() { + return inputFilePath.toString(); + } + + public Path getInputFile() { return inputFilePath; } + /** + * @deprecated Use {@link #getIgnoreFile()} + */ + @Deprecated public String getIgnoreFilePath() { + return ignoreFilePath.toString(); + } + + public Path getIgnoreFile() { return ignoreFilePath; } @@ -483,10 +548,21 @@ public class PMDConfiguration extends AbstractConfiguration { * The input file path points to a single file, which contains a * comma-separated list of source file names to process. * - * @param inputFilePath - * path to the file + * @param inputFilePath path to the file + * @deprecated Use {@link #setInputFilePath(Path)} */ + @Deprecated public void setInputFilePath(String inputFilePath) { + this.inputFilePath = inputFilePath == null ? null : Paths.get(inputFilePath); + } + + /** + * The input file path points to a single file, which contains a + * comma-separated list of source file names to process. + * + * @param inputFilePath path to the file + */ + public void setInputFilePath(Path inputFilePath) { this.inputFilePath = inputFilePath; } @@ -494,10 +570,21 @@ public class PMDConfiguration extends AbstractConfiguration { * The input file path points to a single file, which contains a * comma-separated list of source file names to ignore. * - * @param ignoreFilePath - * path to the file + * @param ignoreFilePath path to the file + * @deprecated Use {@link #setIgnoreFilePath(Path)} */ + @Deprecated public void setIgnoreFilePath(String ignoreFilePath) { + this.ignoreFilePath = ignoreFilePath == null ? null : Paths.get(ignoreFilePath); + } + + /** + * The input file path points to a single file, which contains a + * comma-separated list of source file names to ignore. + * + * @param ignoreFilePath path to the file + */ + public void setIgnoreFilePath(Path ignoreFilePath) { this.ignoreFilePath = ignoreFilePath; } @@ -505,18 +592,39 @@ public class PMDConfiguration extends AbstractConfiguration { * Get the input URI to process for source code objects. * * @return URI + * @deprecated Use {@link #getUri} */ + @Deprecated public String getInputUri() { + return inputUri.toString(); + } + + /** + * Get the input URI to process for source code objects. + * + * @return URI + */ + public URI getUri() { return inputUri; } /** * Set the input URI to process for source code objects. * - * @param inputUri - * a single URI + * @param inputUri a single URI + * @deprecated Use {@link PMDConfiguration#setInputUri(URI)} */ + @Deprecated public void setInputUri(String inputUri) { + this.inputUri = inputUri == null ? null : URI.create(inputUri); + } + + /** + * Set the input URI to process for source code objects. + * + * @param inputUri a single URI + */ + public void setInputUri(URI inputUri) { this.inputUri = inputUri; } @@ -562,10 +670,10 @@ public class PMDConfiguration extends AbstractConfiguration { Renderer renderer = RendererFactory.createRenderer(reportFormat, reportProperties); renderer.setShowSuppressedViolations(showSuppressedViolations); if (reportShortNames && inputPaths != null) { - renderer.setUseShortNames(Collections.unmodifiableList(new ArrayList<>(inputPaths))); + renderer.setUseShortNames(getAllInputPaths()); } if (withReportWriter) { - renderer.setReportFile(reportFile); + renderer.setReportFile(getReportFile()); } return renderer; } @@ -595,18 +703,39 @@ public class PMDConfiguration extends AbstractConfiguration { * 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.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 + * @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; } @@ -657,7 +786,10 @@ public class PMDConfiguration extends AbstractConfiguration { * * @return true if stress test is enbaled, false * otherwise. + * + * @deprecated For removal */ + @Deprecated public boolean isStressTest() { return stressTest; } @@ -668,7 +800,9 @@ public class PMDConfiguration extends AbstractConfiguration { * @param stressTest * The stree test indicator to set. * @see #isStressTest() + * @deprecated For removal. */ + @Deprecated public void setStressTest(boolean stressTest) { this.stressTest = stressTest; } @@ -679,7 +813,9 @@ public class PMDConfiguration extends AbstractConfiguration { * * @return true if benchmark logging is enbaled, * false otherwise. + * @deprecated This behavior is down to CLI, not part of the core analysis. */ + @Deprecated public boolean isBenchmark() { return benchmark; } @@ -690,7 +826,9 @@ public class PMDConfiguration extends AbstractConfiguration { * @param benchmark * The benchmark indicator to set. * @see #isBenchmark() + * @deprecated This behavior is down to CLI, not part of the core analysis. */ + @Deprecated public void setBenchmark(boolean benchmark) { this.benchmark = benchmark; } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDParameters.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDParameters.java index 6b1888db54..b2a39a2a65 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDParameters.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDParameters.java @@ -14,6 +14,7 @@ import net.sourceforge.pmd.PMD; import net.sourceforge.pmd.PMDConfiguration; import net.sourceforge.pmd.RulePriority; import net.sourceforge.pmd.annotation.InternalApi; +import net.sourceforge.pmd.lang.Language; import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.LanguageVersion; @@ -163,6 +164,9 @@ public class PMDParameters { @Parameter(names = { "--no-cache", "-no-cache" }, description = "Explicitly disable incremental analysis. The '-cache' option is ignored if this switch is present in the command line.") private boolean noCache = false; + @Parameter(names = "--use-version", description = "The language version PMD should use when parsing source code in the language-version format, ie: 'java-1.8'") + private List languageVersions = new ArrayList<>(); + // this has to be a public static class, so that JCommander can use it! public static class PropertyConverter implements IStringConverter { @@ -258,6 +262,24 @@ public class PMDParameters { configuration.getLanguageVersionDiscoverer().setDefaultLanguageVersion(languageVersion); } + for (String langVerStr : this.getLanguageVersions()) { + int dashPos = langVerStr.indexOf('-'); + if (dashPos == -1) { + throw new IllegalArgumentException("Invalid language version: " + langVerStr); + } + String langStr = langVerStr.substring(0, dashPos); + String verStr = langVerStr.substring(dashPos + 1); + Language lang = LanguageRegistry.findLanguageByTerseName(langStr); + LanguageVersion langVer = null; + if (lang != null) { + langVer = lang.getVersion(verStr); + } + if (lang == null || langVer == null) { + throw new IllegalArgumentException("Invalid language version: " + langVerStr); + } + configuration.getLanguageVersionDiscoverer().setDefaultLanguageVersion(langVer); + } + try { configuration.prependAuxClasspath(this.getAuxclasspath()); } catch (IllegalArgumentException e) { @@ -348,6 +370,10 @@ public class PMDParameters { return language != null ? language : LanguageRegistry.getDefaultLanguage().getTerseName(); } + public List getLanguageVersions() { + return languageVersions; + } + public String getForceLanguage() { return forceLanguage != null ? forceLanguage : ""; } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/PmdParametersParseResult.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/PmdParametersParseResult.java index 2f863ba4d9..f38d691939 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cli/PmdParametersParseResult.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/PmdParametersParseResult.java @@ -166,6 +166,13 @@ public final class PmdParametersParseResult { m.put("-norulesetcompatibility", "--no-ruleset-compatibility"); m.put("-cache", "--cache"); m.put("-no-cache", "--no-cache"); + m.put("-v", "--use-version"); // In PMD 7, -v will enable verbose mode + m.put("-V", "--verbose"); // In PMD 7, -V will show the tool version + m.put("-min", "--minimum-priority"); + m.put("-version", "--use-version"); + m.put("-language", "--use-version"); + m.put("-l", "--use-version"); + SUGGESTED_REPLACEMENT = Collections.unmodifiableMap(m); } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java index b7a39e7112..c999b3fbe0 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java @@ -31,6 +31,10 @@ import net.sourceforge.pmd.util.database.DBURI; import net.sourceforge.pmd.util.database.SourceObject; import net.sourceforge.pmd.util.log.ScopedLogHandlersManager; +/** + * @deprecated This class is to be removed in PMD 7 in favor of a unified PmdCli entry point. + */ +@Deprecated public class CPD { private static final Logger LOGGER = Logger.getLogger(CPD.class.getName()); @@ -257,6 +261,10 @@ public class CPD { return new CPDReport(matchAlgorithm.getMatches(), numberOfTokensPerFile); } + /** + * @deprecated This class is to be removed in PMD 7 in favor of a unified PmdCli entry point. + */ + @Deprecated public enum StatusCode { OK(0), ERROR(1), diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDCommandLineInterface.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDCommandLineInterface.java index e43e8f8366..990defedad 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDCommandLineInterface.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDCommandLineInterface.java @@ -133,6 +133,7 @@ public final class CPDCommandLineInterface { m.put("--failOnViolation", "--fail-on-violation"); m.put("-failOnViolation", "--fail-on-violation"); m.put("--filelist", "--file-list"); + m.put("--files", "--dir"); SUGGESTED_REPLACEMENT = Collections.unmodifiableMap(m); } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java index 82906e807b..1393e3d0fc 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java @@ -116,7 +116,7 @@ public class CPDConfiguration extends AbstractConfiguration { required = false) private String skipBlocksPattern = Tokenizer.DEFAULT_SKIP_BLOCKS_PATTERN; - @Parameter(names = "--files", variableArity = true, description = "List of files and directories to process", + @Parameter(names = { "--files", "-d", "--dir" }, variableArity = true, description = "List of files and directories to process", required = false, converter = FileConverter.class) private List files; @@ -141,7 +141,7 @@ public class CPDConfiguration extends AbstractConfiguration { description = "By default CPD exits with status 4 if code duplications are found. Disable this option with '-failOnViolation false' to exit with 0 instead and just write the report.") private boolean failOnViolation = true; - @Parameter(names = { "--debug", "--verbose" }, description = "Debug mode.") + @Parameter(names = { "--debug", "--verbose", "-v", "-D" }, description = "Debug mode.") private boolean debug = false; // this has to be a public static class, so that JCommander can use it! @@ -156,7 +156,7 @@ public class CPDConfiguration extends AbstractConfiguration { } } - @Parameter(names = "--encoding", description = "Character encoding to use when processing files", required = false) + @Parameter(names = { "--encoding", "-e" }, description = "Character encoding to use when processing files", required = false) public void setEncoding(String encoding) { this.encoding = encoding; setSourceEncoding(encoding); diff --git a/pmd-dist/src/main/resources/scripts/run.sh b/pmd-dist/src/main/resources/scripts/run.sh index ece76fc949..b07ac0e4bf 100755 --- a/pmd-dist/src/main/resources/scripts/run.sh +++ b/pmd-dist/src/main/resources/scripts/run.sh @@ -10,7 +10,7 @@ usage() { } valid_app_options () { - echo "pmd, cpd, cpdgui, designer, bgastviewer, designerold, ast-dump" + echo "pmd, cpd, cpd-gui, designer, bgastviewer, designerold, ast-dump" } is_cygwin() { @@ -159,10 +159,12 @@ function add_openjfx_classpath() { then script_exit "The environment variable JAVAFX_HOME is missing." else + # The wildcard will include only jar files, but we need to access also + # property files such as javafx.properties that lay bare in the dir if [ -n "$classpath" ]; then - classpath="$classpath:${JAVAFX_HOME}/lib/*" + classpath="$classpath:${JAVAFX_HOME}/lib/*:${JAVAFX_HOME}/lib/" else - classpath="${JAVAFX_HOME}/lib/*" + classpath="${JAVAFX_HOME}/lib/*:${JAVAFX_HOME}/lib/" fi fi fi @@ -187,12 +189,18 @@ case "${APPNAME}" in readonly CLASSNAME="net.sourceforge.pmd.util.fxdesigner.DesignerStarter" ;; "designerold") + echo "'designerold' is deprecated and will be removed in PMD 7.0.0, try the new 'designer' instead." readonly CLASSNAME="net.sourceforge.pmd.util.designer.Designer" ;; "bgastviewer") + echo "'bgastviewer' is deprecated and will be removed in PMD 7.0.0, try the new 'designer' instead." readonly CLASSNAME="net.sourceforge.pmd.util.viewer.Viewer" ;; "cpdgui") + echo "'cpdgui' is deprecated and will be removed in PMD 7.0.0, use 'cpd-gui' instead." + readonly CLASSNAME="net.sourceforge.pmd.cpd.GUI" + ;; + "cpd-gui") readonly CLASSNAME="net.sourceforge.pmd.cpd.GUI" ;; "ast-dump") From 7d0208f6ac12161b6dca857995160e88bf294b95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Fri, 14 Oct 2022 23:56:11 -0300 Subject: [PATCH 053/254] Avoid NPEs --- .../main/java/net/sourceforge/pmd/PMDConfiguration.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/PMDConfiguration.java b/pmd-core/src/main/java/net/sourceforge/pmd/PMDConfiguration.java index e25b657363..023c017912 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/PMDConfiguration.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/PMDConfiguration.java @@ -525,7 +525,7 @@ public class PMDConfiguration extends AbstractConfiguration { */ @Deprecated public String getInputFilePath() { - return inputFilePath.toString(); + return inputFilePath == null ? null : inputFilePath.toString(); } public Path getInputFile() { @@ -537,7 +537,7 @@ public class PMDConfiguration extends AbstractConfiguration { */ @Deprecated public String getIgnoreFilePath() { - return ignoreFilePath.toString(); + return ignoreFilePath == null ? null : ignoreFilePath.toString(); } public Path getIgnoreFile() { @@ -596,7 +596,7 @@ public class PMDConfiguration extends AbstractConfiguration { */ @Deprecated public String getInputUri() { - return inputUri.toString(); + return inputUri == null ? null : inputUri.toString(); } /** @@ -707,7 +707,7 @@ public class PMDConfiguration extends AbstractConfiguration { */ @Deprecated public String getReportFile() { - return reportFile.toString(); + return reportFile == null ? null : reportFile.toString(); } /** From 9406b7747f9add4a86c798508f49d8ee84df8b3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Fri, 22 Jul 2022 10:53:58 -0300 Subject: [PATCH 054/254] Add PicoCli dependency --- pmd-core/pom.xml | 4 ++++ pom.xml | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/pmd-core/pom.xml b/pmd-core/pom.xml index 8a20b776cd..08e5e4daaa 100644 --- a/pmd-core/pom.xml +++ b/pmd-core/pom.xml @@ -76,6 +76,10 @@ com.beust jcommander + + info.picocli + picocli + net.sf.saxon diff --git a/pom.xml b/pom.xml index 32302eadfc..1de54f7139 100644 --- a/pom.xml +++ b/pom.xml @@ -711,6 +711,11 @@ jcommander 1.48 + + info.picocli + picocli + 4.6.3 + org.ow2.asm asm From e4f5d027b2d86f675208d8da53d0bb4b6365ad20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Fri, 22 Jul 2022 10:55:39 -0300 Subject: [PATCH 055/254] Cleanup access to supported Renderer formats --- .../main/java/net/sourceforge/pmd/ant/Formatter.java | 2 +- .../sourceforge/pmd/cli/PMDCommandLineInterface.java | 2 +- .../sourceforge/pmd/renderers/RendererFactory.java | 12 +++++++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/pmd-ant/src/main/java/net/sourceforge/pmd/ant/Formatter.java b/pmd-ant/src/main/java/net/sourceforge/pmd/ant/Formatter.java index 043666ae98..6099351996 100644 --- a/pmd-ant/src/main/java/net/sourceforge/pmd/ant/Formatter.java +++ b/pmd-ant/src/main/java/net/sourceforge/pmd/ant/Formatter.java @@ -147,7 +147,7 @@ public class Formatter { } private static String[] validRendererCodes() { - return RendererFactory.REPORT_FORMAT_TO_RENDERER.keySet().toArray(new String[0]); + return RendererFactory.supportedRenderers().toArray(new String[0]); } private static String unknownRendererMessage(String userSpecifiedType) { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDCommandLineInterface.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDCommandLineInterface.java index d6e3ac6132..8de8e63855 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDCommandLineInterface.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDCommandLineInterface.java @@ -166,7 +166,7 @@ public final class PMDCommandLineInterface { private static String getReports() { StringBuilder buf = new StringBuilder(); - for (String reportName : RendererFactory.REPORT_FORMAT_TO_RENDERER.keySet()) { + for (String reportName : RendererFactory.supportedRenderers()) { Renderer renderer = RendererFactory.createRenderer(reportName, new Properties()); buf.append(" ").append(reportName).append(": "); if (!reportName.equals(renderer.getName())) { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/RendererFactory.java b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/RendererFactory.java index 226f2737e7..5bf6c47ac8 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/RendererFactory.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/RendererFactory.java @@ -10,6 +10,7 @@ import java.lang.reflect.Modifier; import java.util.Collections; import java.util.Map; import java.util.Properties; +import java.util.Set; import java.util.TreeMap; import org.slf4j.Logger; @@ -27,7 +28,7 @@ public final class RendererFactory { private static final Logger LOG = LoggerFactory.getLogger(RendererFactory.class); - public static final Map> REPORT_FORMAT_TO_RENDERER; + private static final Map> REPORT_FORMAT_TO_RENDERER; static { Map> map = new TreeMap<>(); @@ -52,6 +53,15 @@ public final class RendererFactory { private RendererFactory() { } + /** + * Retrieves a collection of all supported renderer names. + * + * @return The set of all supported renderer names. + */ + public static Set supportedRenderers() { + return Collections.unmodifiableSet(REPORT_FORMAT_TO_RENDERER.keySet()); + } + /** * Construct an instance of a Renderer based on report format name. * From 2da7c8c46e9cd2e7876d5f22f457249a1ef7d35f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Fri, 22 Jul 2022 10:56:52 -0300 Subject: [PATCH 056/254] Initial PicoCli PoC - Basic setup to test it's usage, but nonfunctional --- .../pmd/cli/internal/ExecutionResult.java | 17 + .../sourceforge/pmd/cli/internal/PMDCLI.java | 38 ++ .../cli/internal/commands/PMDBaseCommand.java | 21 + .../pmd/cli/internal/commands/PMDCommand.java | 476 ++++++++++++++++++ .../commands/mixins/SubCommandHelpMixin.java | 13 + 5 files changed, 565 insertions(+) create mode 100644 pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/ExecutionResult.java create mode 100644 pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/PMDCLI.java create mode 100644 pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDBaseCommand.java create mode 100644 pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDCommand.java create mode 100644 pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/mixins/SubCommandHelpMixin.java diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/ExecutionResult.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/ExecutionResult.java new file mode 100644 index 0000000000..4e6f890c11 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/ExecutionResult.java @@ -0,0 +1,17 @@ +package net.sourceforge.pmd.cli.internal; + +public enum ExecutionResult { + OK(0), + ERROR(1), + VIOLATIONS_FOUND(4); + + private final int exitStatusCode; + + private ExecutionResult(int exitStatusCode) { + this.exitStatusCode = exitStatusCode; + } + + public int getExitStatusCode() { + return exitStatusCode; + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/PMDCLI.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/PMDCLI.java new file mode 100644 index 0000000000..76ab3a0bfb --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/PMDCLI.java @@ -0,0 +1,38 @@ +package net.sourceforge.pmd.cli.internal; + +import net.sourceforge.pmd.cli.internal.commands.PMDBaseCommand; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; + +public class PMDCLI { + + public static void main(String[] args) { + tryPicoCli(); + } + + private static void tryPicoCli() { + new CommandLine(new PMDBaseCommand()) + .setCaseInsensitiveEnumValuesAllowed(true) +// .addSubcommand(new PMDCommand()) +// .addSubcommand(new CPDPicoCli()) + .execute("run", "-h"); + //.execute("run", "-t", "4", "-P", "foo=bar", "-R" , "foo,bar", "-R", "baz", "-d", "src/main/java", "-f", "xml"); + } + + @Command(name = "cpd", mixinStandardHelpOptions = true, description = "The Copy Paste Detector") + public static class CPDPicoCli implements Runnable { + @Option(names = "--minimum-tokens", + description = "The minimum token length which should be reported as a duplicate.", required = true) + private int minimumTileSize; + + @Option(names = "--skip-duplicate-files", + description = "Ignore multiple copies of files of the same name and length in comparison", required = false) + private boolean skipDuplicates; + + @Override + public void run() { + System.out.println("running cpd command"); + } + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDBaseCommand.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDBaseCommand.java new file mode 100644 index 0000000000..455efd8cf7 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDBaseCommand.java @@ -0,0 +1,21 @@ +package net.sourceforge.pmd.cli.internal.commands; + +import net.sourceforge.pmd.PMDVersion; +import net.sourceforge.pmd.cli.internal.PMDCLI.CPDPicoCli; +import picocli.CommandLine.Command; +import picocli.CommandLine.IVersionProvider; + +@Command(name = "pmd", mixinStandardHelpOptions = true, + versionProvider = PMDVersionProvider.class, + subcommands = { PMDCommand.class, CPDPicoCli.class }) +public class PMDBaseCommand { + +} + +class PMDVersionProvider implements IVersionProvider { + + @Override + public String[] getVersion() throws Exception { + return new String[] { "PMD " + PMDVersion.VERSION }; + } +} \ No newline at end of file diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDCommand.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDCommand.java new file mode 100644 index 0000000000..9be89d1891 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDCommand.java @@ -0,0 +1,476 @@ +package net.sourceforge.pmd.cli.internal.commands; + +import java.net.URI; +import java.nio.file.Path; +import java.util.Iterator; +import java.util.List; +import java.util.Properties; +import java.util.concurrent.Callable; +import java.util.stream.Collectors; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import net.sourceforge.pmd.PMDConfiguration; +import net.sourceforge.pmd.RulePriority; +import net.sourceforge.pmd.cli.internal.ExecutionResult; +import net.sourceforge.pmd.cli.internal.commands.mixins.SubCommandHelpMixin; +import net.sourceforge.pmd.lang.Language; +import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.lang.LanguageVersion; +import net.sourceforge.pmd.renderers.RendererFactory; +import picocli.CommandLine.Command; +import picocli.CommandLine.Mixin; +import picocli.CommandLine.Model.CommandSpec; +import picocli.CommandLine.Option; +import picocli.CommandLine.ParameterException; +import picocli.CommandLine.Spec; + +@Command(name = "analyze", aliases = {"analyse", "run" }, showDefaultValues = true, + description = "The PMD standard source code analyzer") +public class PMDCommand implements Callable { + + @Spec + private CommandSpec spec; // injected by PicoCli, needed for validations + + @Mixin + private SubCommandHelpMixin help; + + private List rulesets; + + private URI uri; + + private List inputPaths; + + private Path fileListPath; + + private Path ignoreListPath; + + private String format; // Enhance to support other usage + + private boolean debug; + + private String encoding; + + private int threads; + + private boolean benchmark; + + private boolean stress; + + private boolean shortnames; + + private boolean showSuppressed; + + private String suppressMarker; + + private RulePriority minimumPriority; + + private Properties properties; + + private Path reportFile; + + private String version; + + private String language; // TODO : Actually use Language and support suggestions + + private String forceLanguage; + + private String auxClasspath; + + private boolean failOnViolation; + + private boolean noRuleSetCompatibility; + + private Path cacheLocation; + + private boolean noCache; + + @Option(names = { "--rulesets", "-rulesets", "-R" }, + description = "Path to a ruleset xml file. " + + "The path may reference a resource on the classpath of the application, be a local file system path, or a URL. " + + "The option can be repeated, and multiple arguments separated by comma can be provided to a single occurrence of the option.", + required = true, split = ",", + arity = "1..*") + public void setRulesets(final List rulesets) { + this.rulesets = rulesets; + } + + @Option(names = { "--uri", "-uri", "-u" }, + description = "Database URI for sources. " + + "One of --dir, --file-list or --uri must be provided.") + public void setUri(final URI uri) { + this.uri = uri; + } + + @Option(names = { "--dir", "-dir", "-d" }, + description = "Path to a source file, or directory containing source files to analyze. " + // About the following line: + // In PMD 6, this is only the case for files found in directories. If you + // specify a file directly, and it is unknown, then the Java parser is used. + + "Note that a file is only effectively added if it matches a language known by PMD. " + + "Zip and Jar files are also supported, if they are specified directly " + + "(archive files found while exploring a directory are not recursively expanded). " + + "This option can be repeated, and multiple arguments can be provided to a single occurrence of the option. " + + "One of --dir, --file-list or --uri must be provided. ", + arity = "0..*") + public void setInputPaths(final List inputPaths) { + this.inputPaths = inputPaths; + } + + @Option(names = { "--file-list", "-filelist" }, + description = + "Path to a file containing a list of files to analyze, one path per line. " + + "One of --dir, --file-list or --uri must be provided.") + public void setFileListPath(final Path fileListPath) { + this.fileListPath = fileListPath; + } + + @Option(names = { "--ignore-list", "-ignorelist" }, + description = "Path to a file containing a list of files to exclude from the analysis, one path per line. " + + "This option can be combined with --dir and --file-list.") + public void setIgnoreListPath(final Path ignoreListPath) { + this.ignoreListPath = ignoreListPath; + } + + @Option(names = { "--format", "-format", "-f" }, + description = "Report format.%nValid values: ${COMPLETION-CANDIDATES}%n" + + "Alternatively, you can provide the fully qualified name of a custom Renderer in the classpath.", + defaultValue = "text", completionCandidates = PMDSupportedReportFormatsCandidates.class) + public void setFormat(final String format) { + this.format = format; + } + + @Option(names = { "--debug", "--verbose", "-debug", "-verbose", "-D", "-V" }, description = "Debug mode.") + public void setDebug(final boolean debug) { + this.debug = debug; + } + + @Option(names = { "--encoding", "-encoding", "-e" }, + description = "Specifies the character set encoding of the source code files PMD is reading (i.e., UTF-8).", + defaultValue = "UTF-8") + public void setEncoding(final String encoding) { + this.encoding = encoding; + } + + @Option(names = { "--benchmark", "-benchmark", "-b" }, + description = "Benchmark mode - output a benchmark report upon completion; default to System.err.") + public void setBenchmark(final boolean benchmark) { + this.benchmark = benchmark; + } + + @Option(names = { "--stress", "-stress", "-S" }, description = "Performs a stress test.") + public void setStress(final boolean stress) { + this.stress = stress; + } + + @Option(names = { "--short-names", "-shortnames" }, description = "Prints shortened filenames in the report.") + public void setShortnames(final boolean shortnames) { + this.shortnames = shortnames; + } + + @Option(names = { "--show-suppressed", "-showsuppressed" }, description = "Report should show suppressed rule violations.") + public void setShowSuppressed(final boolean showSuppressed) { + this.showSuppressed = showSuppressed; + } + + @Option(names = { "--suppress-marker", "-suppressmarker" }, + description = "Specifies the string that marks a line which PMD should ignore.", + defaultValue = "NOPMD") + public void setSuppressMarker(final String suppressMarker) { + this.suppressMarker = suppressMarker; + } + + @Option(names = { "--minimum-priority", "-minimumpriority", "-min" }, + description = "Rule priority threshold; rules with lower priority than configured here won't be used.%n" + + "Valid values (case insensitive): ${COMPLETION-CANDIDATES}", + defaultValue = "Low") + public void setMinimumPriority(final RulePriority priority) { + this.minimumPriority = priority; + } + + @Option(names = { "--property", "-property", "-P" }, description = "Key-value pair defining a property for the report format.") + public void setProperties(final Properties properties) { + this.properties = properties; + } + + @Option(names = { "--report-file", "-reportfile", "-r" }, + description = "Path to a file to which report output is written. " + + "The file is created if it does not exist. " + + "If this option is not specified, the report is rendered to standard output.") + public void setReportFile(final Path reportFile) { + this.reportFile = reportFile; + } + + @Option(names = { "-version", "-v" }, description = "Specify version of a language PMD should use.") + public void setVersion(final String version) { + this.version = version; + } + + @Option(names = { "-language", "-l" }, + description = "Specify a language PMD should use.%nValid values: ${COMPLETION-CANDIDATES}%n", + completionCandidates = PMDSupportedLanguagesCandidates.class) + public void setLanguage(final String language) { + this.language = language; + } + + @Option(names = { "--force-language", "-force-language" }, + description = "Force a language to be used for all input files, irrespective of file names. " + + "When using this option, the automatic language selection by extension is disabled, and PMD " + + "tries to parse all input files with the given language's parser. " + + "Parsing errors are ignored.%nValid values: ${COMPLETION-CANDIDATES}%n", + completionCandidates = PMDSupportedLanguagesCandidates.class) + public void setForceLanguage(final String forceLanguage) { + this.forceLanguage = forceLanguage; + } + + @Option(names = { "--aux-classpath", "-auxclasspath" }, + description = "Specifies the classpath for libraries used by the source code. " + + "This is used to resolve types in Java source files. The platform specific path delimiter " + + "(\":\" on Linux, \";\" on Windows) is used to separate the entries. " + + "Alternatively, a single 'file:' URL to a text file containing path elements on consecutive lines " + + "can be specified.") + public void setAuxClasspath(final String auxClasspath) { + this.auxClasspath = auxClasspath; + } + + @Option(names = { "--fail-on-violation", "--failOnViolation", "-failOnViolation"}, + description = "By default PMD exits with status 4 if violations are found. Disable this option with '-failOnViolation false' to exit with 0 instead and just write the report.", + defaultValue = "true") + public void setFailOnViolation(final boolean failOnViolation) { + this.failOnViolation = failOnViolation; + } + + @Option(names = { "--no-ruleset-compatibility", "-norulesetcompatibility" }, + description = "Disable the ruleset compatibility filter. The filter is active by default and tries automatically 'fix' old ruleset files with old rule names") + public void setNoRuleSetCompatibility(final boolean noRuleSetCompatibility) { + this.noRuleSetCompatibility = noRuleSetCompatibility; + } + + @Option(names = { "--cache", "-cache" }, + description = "Specify the location of the cache file for incremental analysis. " + + "This should be the full path to the file, including the desired file name (not just the parent directory). " + + "If the file doesn't exist, it will be created on the first run. The file will be overwritten on each run " + + "with the most up-to-date rule violations.") + public void setCacheLocation(final Path cacheLocation) { + this.cacheLocation = cacheLocation; + } + + @Option(names = { "--no-cache", "-no-cache" }, description = "Explicitly disable incremental analysis. The '-cache' option is ignored if this switch is present in the command line.") + public void setNoCache(final boolean noCache) { + this.noCache = noCache; + } + + @Option(names = { "--threads", "-threads", "-t" }, description = "Sets the number of threads used by PMD.", + defaultValue = "1") + public void setThreads(final int threads) { + if (threads < 0) { + throw new ParameterException(spec.commandLine(), "Thread count should be a positive number or zero, found " + threads + " instead."); + } + + this.threads = threads; + } + + /** + * Converts these parameters into a configuration. + * + * @return A new PMDConfiguration corresponding to these parameters + * + * @throws IllegalArgumentException if the parameters are inconsistent or incomplete + */ + public PMDConfiguration toConfiguration() { + PMDConfiguration configuration = new PMDConfiguration(); + configuration.setInputPaths(this.getInputPaths().stream().map(Path::toString).collect(Collectors.toList())); + configuration.setInputFilePath(this.getFileListPath().toString()); + configuration.setIgnoreFilePath(this.getIgnoreListPath().toString()); + configuration.setInputUri(this.getUri().toString()); + configuration.setReportFormat(this.getFormat()); + configuration.setBenchmark(this.isBenchmark()); + configuration.setDebug(this.isDebug()); + configuration.setMinimumPriority(this.getMinimumPriority()); + configuration.setReportFile(this.getReportFile().toString()); + configuration.setReportProperties(this.getProperties()); + configuration.setReportShortNames(this.isShortnames()); + configuration.setRuleSets(this.getRulesetRefs()); + configuration.setRuleSetFactoryCompatibilityEnabled(!this.noRuleSetCompatibility); + configuration.setShowSuppressedViolations(this.isShowSuppressed()); + configuration.setSourceEncoding(this.getEncoding()); + configuration.setStressTest(this.isStress()); + configuration.setSuppressMarker(this.getSuppressMarker()); + configuration.setThreads(this.getThreads()); + configuration.setFailOnViolation(this.isFailOnViolation()); + configuration.setAnalysisCacheLocation(this.cacheLocation.toString()); + configuration.setIgnoreIncrementalAnalysis(this.isIgnoreIncrementalAnalysis()); + + final LanguageVersion forceLangVersion = getForceLangVersion(); + if (forceLangVersion != null) { + configuration.setForceLanguageVersion(forceLangVersion); + } + + final LanguageVersion languageVersion = getLangVersion(); + if (languageVersion != null) { + configuration.getLanguageVersionDiscoverer().setDefaultLanguageVersion(languageVersion); + } + + try { + configuration.prependAuxClasspath(this.getAuxClasspath()); + } catch (IllegalArgumentException e) { + throw new ParameterException(spec.commandLine(), "Invalid auxiliary classpath: " + e.getMessage(), e); + } + return configuration; + } + + public boolean isIgnoreIncrementalAnalysis() { + return noCache; + } + + public boolean isDebug() { + return debug; + } + + public String getEncoding() { + return encoding; + } + + public Integer getThreads() { + return threads; + } + + public boolean isBenchmark() { + return benchmark; + } + + public boolean isStress() { + return stress; + } + + public boolean isShortnames() { + return shortnames; + } + + public boolean isShowSuppressed() { + return showSuppressed; + } + + public String getSuppressMarker() { + return suppressMarker; + } + + public RulePriority getMinimumPriority() { + return minimumPriority; + } + + public Properties getProperties() { + return properties; + } + + public Path getReportFile() { + return reportFile; + } + + public String getVersion() { + if (version != null) { + return version; + } + return LanguageRegistry.findLanguageByTerseName(getLanguage()).getDefaultVersion().getVersion(); + } + + public String getLanguage() { + return language != null ? language : LanguageRegistry.getDefaultLanguage().getTerseName(); + } + + public String getForceLanguage() { + return forceLanguage != null ? forceLanguage : ""; + } + + public String getAuxClasspath() { + return auxClasspath; + } + + public List getRulesetRefs() { + return rulesets; + } + + public List getInputPaths() { + return inputPaths; + } + + public Path getFileListPath() { + return fileListPath; + } + + public Path getIgnoreListPath() { + return ignoreListPath; + } + + public String getFormat() { + return format; + } + + public boolean isFailOnViolation() { + return failOnViolation; + } + + /** + * @return the uri alternative to source directory. + */ + public URI getUri() { + return uri; + } + + private @Nullable LanguageVersion getForceLangVersion() { + Language lang = forceLanguage != null ? LanguageRegistry.findLanguageByTerseName(forceLanguage) : null; + return lang != null ? lang.getDefaultVersion() : null; + } + + private @Nullable LanguageVersion getLangVersion() { + Language lang = language != null ? LanguageRegistry.findLanguageByTerseName(language) + : LanguageRegistry.getDefaultLanguage(); + + return version != null ? lang.getVersion(version) + : lang.getDefaultVersion(); + } + + @Override + public ExecutionResult call() throws Exception { + if ((inputPaths == null || inputPaths.isEmpty()) && uri == null && fileListPath == null) { + throw new ParameterException(spec.commandLine(), "One of --dir, --file-list or --uri must be provided."); + } + + System.out.println("threads: " + threads); + System.out.println("priority: " + minimumPriority); + System.out.println("properties: " + properties); + System.out.println("ruleset: " + rulesets); + System.out.println("format: " + format); + System.out.println("priority: " + minimumPriority); + + // TODO Auto-generated method stub + return ExecutionResult.OK; + } + + /** + * Provider of candidates for valid report formats. + */ + private static class PMDSupportedReportFormatsCandidates implements Iterable { + + @Override + public Iterator iterator() { + return RendererFactory.supportedRenderers().iterator(); + } + } + + /** + * Provider of candidates for valid languages. + * + * Beware, the help will report this on runtime, and be accurate to available modules in the classpath, + * but autocomplete will include all at build time. + */ + private static class PMDSupportedLanguagesCandidates implements Iterable { + + @Override + public Iterator iterator() { + return LanguageRegistry.getLanguages().stream() + .map(Language::getTerseName).collect(Collectors.toList()).iterator(); + } + + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/mixins/SubCommandHelpMixin.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/mixins/SubCommandHelpMixin.java new file mode 100644 index 0000000000..1607b11356 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/mixins/SubCommandHelpMixin.java @@ -0,0 +1,13 @@ +package net.sourceforge.pmd.cli.internal.commands.mixins; + +import picocli.CommandLine.Option; + +/** + * A mixin for subcommands that need to show help, + * but don't want to also display a version as done with {@code mixinStandardHelpOptions = true} + */ +public class SubCommandHelpMixin { + @Option(names = {"-h", "--help"}, usageHelp = true, descriptionKey = "mixinStandardHelpOptions.help", + description = "Show this help message and exit.") + private boolean helpRequested; +} From 0b032bef85f1ac7ac1ee0c8db93a3d892b97281d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Fri, 22 Jul 2022 10:58:52 -0300 Subject: [PATCH 057/254] Initial PoC of Autocomplete --- pmd-core/pom.xml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/pmd-core/pom.xml b/pmd-core/pom.xml index 08e5e4daaa..ec37a92263 100644 --- a/pmd-core/pom.xml +++ b/pmd-core/pom.xml @@ -51,6 +51,34 @@ + + + + org.codehaus.mojo + exec-maven-plugin + + + generate-autocompletion-script + package + + exec + + + + + java + + -Dpicocli.autocomplete.systemExitOnError + -cp + + picocli.AutoComplete + --force + --completionScript + ${project.build.directory}/pmd_completion.sh + net.sourceforge.pmd.cli.internal.commands.PMDBaseCommand + + + From 8587fc2a6ca2fd8efca6241ac12dc9c708f71794 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Fri, 22 Jul 2022 11:11:46 -0300 Subject: [PATCH 058/254] Fix whitespace --- .../pmd/cli/internal/ExecutionResult.java | 26 ++++----- .../sourceforge/pmd/cli/internal/PMDCLI.java | 54 ++++++++++--------- .../cli/internal/commands/PMDBaseCommand.java | 12 ++--- 3 files changed, 47 insertions(+), 45 deletions(-) diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/ExecutionResult.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/ExecutionResult.java index 4e6f890c11..4ca3ffa7b2 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/ExecutionResult.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/ExecutionResult.java @@ -1,17 +1,17 @@ package net.sourceforge.pmd.cli.internal; public enum ExecutionResult { - OK(0), - ERROR(1), - VIOLATIONS_FOUND(4); - - private final int exitStatusCode; - - private ExecutionResult(int exitStatusCode) { - this.exitStatusCode = exitStatusCode; - } - - public int getExitStatusCode() { - return exitStatusCode; - } + OK(0), + ERROR(1), + VIOLATIONS_FOUND(4); + + private final int exitStatusCode; + + private ExecutionResult(int exitStatusCode) { + this.exitStatusCode = exitStatusCode; + } + + public int getExitStatusCode() { + return exitStatusCode; + } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/PMDCLI.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/PMDCLI.java index 76ab3a0bfb..6d13e63ee5 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/PMDCLI.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/PMDCLI.java @@ -7,32 +7,34 @@ import picocli.CommandLine.Option; public class PMDCLI { - public static void main(String[] args) { - tryPicoCli(); - } + public static void main(String[] args) { + tryPicoCli(); + } - private static void tryPicoCli() { - new CommandLine(new PMDBaseCommand()) - .setCaseInsensitiveEnumValuesAllowed(true) -// .addSubcommand(new PMDCommand()) -// .addSubcommand(new CPDPicoCli()) - .execute("run", "-h"); - //.execute("run", "-t", "4", "-P", "foo=bar", "-R" , "foo,bar", "-R", "baz", "-d", "src/main/java", "-f", "xml"); - } - - @Command(name = "cpd", mixinStandardHelpOptions = true, description = "The Copy Paste Detector") - public static class CPDPicoCli implements Runnable { - @Option(names = "--minimum-tokens", - description = "The minimum token length which should be reported as a duplicate.", required = true) - private int minimumTileSize; + private static void tryPicoCli() { + new CommandLine(new PMDBaseCommand()) + .setCaseInsensitiveEnumValuesAllowed(true) +// .addSubcommand(new PMDCommand()) +// .addSubcommand(new CPDPicoCli()) + .execute("run", "-h"); +// .execute("run", "-t", "4", "-P", "foo=bar", "-R" , "foo,bar", "-R", "baz", "-d", "src/main/java", "-f", "xml"); + } - @Option(names = "--skip-duplicate-files", - description = "Ignore multiple copies of files of the same name and length in comparison", required = false) - private boolean skipDuplicates; - - @Override - public void run() { - System.out.println("running cpd command"); - } - } + @Command(name = "cpd", mixinStandardHelpOptions = true, description = "The Copy Paste Detector") + public static class CPDPicoCli implements Runnable { + @Option(names = "--minimum-tokens", + description = "The minimum token length which should be reported as a duplicate.", + required = true) + private int minimumTileSize; + + @Option(names = "--skip-duplicate-files", + description = "Ignore multiple copies of files of the same name and length in comparison", + required = false) + private boolean skipDuplicates; + + @Override + public void run() { + System.out.println("running cpd command"); + } + } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDBaseCommand.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDBaseCommand.java index 455efd8cf7..6a0e82e3d8 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDBaseCommand.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDBaseCommand.java @@ -6,16 +6,16 @@ import picocli.CommandLine.Command; import picocli.CommandLine.IVersionProvider; @Command(name = "pmd", mixinStandardHelpOptions = true, - versionProvider = PMDVersionProvider.class, - subcommands = { PMDCommand.class, CPDPicoCli.class }) + versionProvider = PMDVersionProvider.class, + subcommands = { PMDCommand.class, CPDPicoCli.class }) public class PMDBaseCommand { } class PMDVersionProvider implements IVersionProvider { - @Override - public String[] getVersion() throws Exception { - return new String[] { "PMD " + PMDVersion.VERSION }; - } + @Override + public String[] getVersion() throws Exception { + return new String[] { "PMD " + PMDVersion.VERSION }; + } } \ No newline at end of file From 311ca535a8628b27792daa04ae1c70ae0213863a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Mon, 25 Jul 2022 18:24:35 -0300 Subject: [PATCH 059/254] Cleanups --- .../sourceforge/pmd/cli/internal/ExecutionResult.java | 2 +- .../java/net/sourceforge/pmd/cli/internal/PMDCLI.java | 2 ++ .../pmd/cli/internal/commands/PMDCommand.java | 11 +++++++---- .../internal/commands/mixins/SubCommandHelpMixin.java | 1 + 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/ExecutionResult.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/ExecutionResult.java index 4ca3ffa7b2..c91b38c5c0 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/ExecutionResult.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/ExecutionResult.java @@ -7,7 +7,7 @@ public enum ExecutionResult { private final int exitStatusCode; - private ExecutionResult(int exitStatusCode) { + ExecutionResult(int exitStatusCode) { this.exitStatusCode = exitStatusCode; } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/PMDCLI.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/PMDCLI.java index 6d13e63ee5..73c4e80324 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/PMDCLI.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/PMDCLI.java @@ -22,11 +22,13 @@ public class PMDCLI { @Command(name = "cpd", mixinStandardHelpOptions = true, description = "The Copy Paste Detector") public static class CPDPicoCli implements Runnable { + @SuppressWarnings("unused") @Option(names = "--minimum-tokens", description = "The minimum token length which should be reported as a duplicate.", required = true) private int minimumTileSize; + @SuppressWarnings("unused") @Option(names = "--skip-duplicate-files", description = "Ignore multiple copies of files of the same name and length in comparison", required = false) diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDCommand.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDCommand.java index 9be89d1891..f79161aea1 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDCommand.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDCommand.java @@ -32,6 +32,7 @@ public class PMDCommand implements Callable { @Spec private CommandSpec spec; // injected by PicoCli, needed for validations + @SuppressWarnings("unused") @Mixin private SubCommandHelpMixin help; @@ -45,7 +46,7 @@ public class PMDCommand implements Callable { private Path ignoreListPath; - private String format; // Enhance to support other usage + private String format; private boolean debug; @@ -71,7 +72,7 @@ public class PMDCommand implements Callable { private String version; - private String language; // TODO : Actually use Language and support suggestions + private String language; private String forceLanguage; @@ -433,7 +434,9 @@ public class PMDCommand implements Callable { @Override public ExecutionResult call() throws Exception { if ((inputPaths == null || inputPaths.isEmpty()) && uri == null && fileListPath == null) { - throw new ParameterException(spec.commandLine(), "One of --dir, --file-list or --uri must be provided."); + throw new ParameterException(spec.commandLine(), + "Please provide a parameter for source root directory (--dir or -d), " + + "database URI (--uri or -u), or file list path (--file-list)"); } System.out.println("threads: " + threads); @@ -469,7 +472,7 @@ public class PMDCommand implements Callable { @Override public Iterator iterator() { return LanguageRegistry.getLanguages().stream() - .map(Language::getTerseName).collect(Collectors.toList()).iterator(); + .map(Language::getTerseName).iterator(); } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/mixins/SubCommandHelpMixin.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/mixins/SubCommandHelpMixin.java index 1607b11356..5eb2cc037c 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/mixins/SubCommandHelpMixin.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/mixins/SubCommandHelpMixin.java @@ -7,6 +7,7 @@ import picocli.CommandLine.Option; * but don't want to also display a version as done with {@code mixinStandardHelpOptions = true} */ public class SubCommandHelpMixin { + @SuppressWarnings("unused") @Option(names = {"-h", "--help"}, usageHelp = true, descriptionKey = "mixinStandardHelpOptions.help", description = "Show this help message and exit.") private boolean helpRequested; From 711e2c34dd2400b19bc1ecb23e29f3f3638387b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Mon, 25 Jul 2022 18:25:12 -0300 Subject: [PATCH 060/254] Remove deprecated options --- .../pmd/cli/internal/commands/PMDCommand.java | 51 ++++++++++--------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDCommand.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDCommand.java index f79161aea1..e9bfa6a1ed 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDCommand.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDCommand.java @@ -86,7 +86,7 @@ public class PMDCommand implements Callable { private boolean noCache; - @Option(names = { "--rulesets", "-rulesets", "-R" }, + @Option(names = { "--rulesets", "-R" }, description = "Path to a ruleset xml file. " + "The path may reference a resource on the classpath of the application, be a local file system path, or a URL. " + "The option can be repeated, and multiple arguments separated by comma can be provided to a single occurrence of the option.", @@ -96,14 +96,14 @@ public class PMDCommand implements Callable { this.rulesets = rulesets; } - @Option(names = { "--uri", "-uri", "-u" }, + @Option(names = { "--uri", "-u" }, description = "Database URI for sources. " + "One of --dir, --file-list or --uri must be provided.") public void setUri(final URI uri) { this.uri = uri; } - @Option(names = { "--dir", "-dir", "-d" }, + @Option(names = { "--dir", "-d" }, description = "Path to a source file, or directory containing source files to analyze. " // About the following line: // In PMD 6, this is only the case for files found in directories. If you @@ -118,7 +118,7 @@ public class PMDCommand implements Callable { this.inputPaths = inputPaths; } - @Option(names = { "--file-list", "-filelist" }, + @Option(names = { "--file-list" }, description = "Path to a file containing a list of files to analyze, one path per line. " + "One of --dir, --file-list or --uri must be provided.") @@ -126,14 +126,14 @@ public class PMDCommand implements Callable { this.fileListPath = fileListPath; } - @Option(names = { "--ignore-list", "-ignorelist" }, + @Option(names = { "--ignore-list" }, description = "Path to a file containing a list of files to exclude from the analysis, one path per line. " + "This option can be combined with --dir and --file-list.") public void setIgnoreListPath(final Path ignoreListPath) { this.ignoreListPath = ignoreListPath; } - @Option(names = { "--format", "-format", "-f" }, + @Option(names = { "--format", "-f" }, description = "Report format.%nValid values: ${COMPLETION-CANDIDATES}%n" + "Alternatively, you can provide the fully qualified name of a custom Renderer in the classpath.", defaultValue = "text", completionCandidates = PMDSupportedReportFormatsCandidates.class) @@ -141,47 +141,49 @@ public class PMDCommand implements Callable { this.format = format; } - @Option(names = { "--debug", "--verbose", "-debug", "-verbose", "-D", "-V" }, description = "Debug mode.") + @Option(names = { "--debug", "--verbose", "-D", "-V" }, description = "Debug mode.") public void setDebug(final boolean debug) { this.debug = debug; } - @Option(names = { "--encoding", "-encoding", "-e" }, + @Option(names = { "--encoding", "-e" }, description = "Specifies the character set encoding of the source code files PMD is reading (i.e., UTF-8).", defaultValue = "UTF-8") public void setEncoding(final String encoding) { this.encoding = encoding; } - @Option(names = { "--benchmark", "-benchmark", "-b" }, + @Option(names = { "--benchmark", "-b" }, description = "Benchmark mode - output a benchmark report upon completion; default to System.err.") public void setBenchmark(final boolean benchmark) { this.benchmark = benchmark; } - @Option(names = { "--stress", "-stress", "-S" }, description = "Performs a stress test.") + @Option(names = { "--stress", "-S" }, description = "Performs a stress test.") public void setStress(final boolean stress) { this.stress = stress; } - @Option(names = { "--short-names", "-shortnames" }, description = "Prints shortened filenames in the report.") + @Option(names = { "--short-names" }, description = "Prints shortened filenames in the report.") public void setShortnames(final boolean shortnames) { this.shortnames = shortnames; } - @Option(names = { "--show-suppressed", "-showsuppressed" }, description = "Report should show suppressed rule violations.") + @Option(names = { "--show-suppressed" }, description = "Report should show suppressed rule violations.") public void setShowSuppressed(final boolean showSuppressed) { this.showSuppressed = showSuppressed; } - @Option(names = { "--suppress-marker", "-suppressmarker" }, + @Option(names = { "--suppress-marker" }, description = "Specifies the string that marks a line which PMD should ignore.", defaultValue = "NOPMD") public void setSuppressMarker(final String suppressMarker) { this.suppressMarker = suppressMarker; } - @Option(names = { "--minimum-priority", "-minimumpriority", "-min" }, + // TODO : "-min" is not a single letter option, but was never deprecated in PMD + // 6.x + @Option(names = { "--minimum-priority", "-min" }, description = "Rule priority threshold; rules with lower priority than configured here won't be used.%n" + "Valid values (case insensitive): ${COMPLETION-CANDIDATES}", defaultValue = "Low") @@ -189,12 +191,12 @@ public class PMDCommand implements Callable { this.minimumPriority = priority; } - @Option(names = { "--property", "-property", "-P" }, description = "Key-value pair defining a property for the report format.") + @Option(names = { "--property", "-P" }, description = "Key-value pair defining a property for the report format.") public void setProperties(final Properties properties) { this.properties = properties; } - @Option(names = { "--report-file", "-reportfile", "-r" }, + @Option(names = { "--report-file", "-r" }, description = "Path to a file to which report output is written. " + "The file is created if it does not exist. " + "If this option is not specified, the report is rendered to standard output.") @@ -202,11 +204,14 @@ public class PMDCommand implements Callable { this.reportFile = reportFile; } + // TODO : -version was never deprecated, should be replaced with --version… but + // --version is used to display the PMD version… @Option(names = { "-version", "-v" }, description = "Specify version of a language PMD should use.") public void setVersion(final String version) { this.version = version; } + // TODO : -langugae was never deprecated, should be replaced with --language @Option(names = { "-language", "-l" }, description = "Specify a language PMD should use.%nValid values: ${COMPLETION-CANDIDATES}%n", completionCandidates = PMDSupportedLanguagesCandidates.class) @@ -214,7 +219,7 @@ public class PMDCommand implements Callable { this.language = language; } - @Option(names = { "--force-language", "-force-language" }, + @Option(names = { "--force-language" }, description = "Force a language to be used for all input files, irrespective of file names. " + "When using this option, the automatic language selection by extension is disabled, and PMD " + "tries to parse all input files with the given language's parser. " @@ -224,7 +229,7 @@ public class PMDCommand implements Callable { this.forceLanguage = forceLanguage; } - @Option(names = { "--aux-classpath", "-auxclasspath" }, + @Option(names = { "--aux-classpath" }, description = "Specifies the classpath for libraries used by the source code. " + "This is used to resolve types in Java source files. The platform specific path delimiter " + "(\":\" on Linux, \";\" on Windows) is used to separate the entries. " @@ -234,20 +239,20 @@ public class PMDCommand implements Callable { this.auxClasspath = auxClasspath; } - @Option(names = { "--fail-on-violation", "--failOnViolation", "-failOnViolation"}, + @Option(names = { "--fail-on-violation" }, description = "By default PMD exits with status 4 if violations are found. Disable this option with '-failOnViolation false' to exit with 0 instead and just write the report.", defaultValue = "true") public void setFailOnViolation(final boolean failOnViolation) { this.failOnViolation = failOnViolation; } - @Option(names = { "--no-ruleset-compatibility", "-norulesetcompatibility" }, + @Option(names = { "--no-ruleset-compatibility" }, description = "Disable the ruleset compatibility filter. The filter is active by default and tries automatically 'fix' old ruleset files with old rule names") public void setNoRuleSetCompatibility(final boolean noRuleSetCompatibility) { this.noRuleSetCompatibility = noRuleSetCompatibility; } - @Option(names = { "--cache", "-cache" }, + @Option(names = { "--cache" }, description = "Specify the location of the cache file for incremental analysis. " + "This should be the full path to the file, including the desired file name (not just the parent directory). " + "If the file doesn't exist, it will be created on the first run. The file will be overwritten on each run " @@ -256,12 +261,12 @@ public class PMDCommand implements Callable { this.cacheLocation = cacheLocation; } - @Option(names = { "--no-cache", "-no-cache" }, description = "Explicitly disable incremental analysis. The '-cache' option is ignored if this switch is present in the command line.") + @Option(names = { "--no-cache" }, description = "Explicitly disable incremental analysis. The '-cache' option is ignored if this switch is present in the command line.") public void setNoCache(final boolean noCache) { this.noCache = noCache; } - @Option(names = { "--threads", "-threads", "-t" }, description = "Sets the number of threads used by PMD.", + @Option(names = { "--threads", "-t" }, description = "Sets the number of threads used by PMD.", defaultValue = "1") public void setThreads(final int threads) { if (threads < 0) { From 58081947271d270ff72f4d401cb570235dc23994 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Mon, 25 Jul 2022 18:59:39 -0300 Subject: [PATCH 061/254] Rename base command to root command --- .../java/net/sourceforge/pmd/cli/internal/PMDCLI.java | 11 ++++------- .../{PMDBaseCommand.java => PMDRootCommand.java} | 5 ++++- 2 files changed, 8 insertions(+), 8 deletions(-) rename pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/{PMDBaseCommand.java => PMDRootCommand.java} (65%) diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/PMDCLI.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/PMDCLI.java index 73c4e80324..8f78023532 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/PMDCLI.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/PMDCLI.java @@ -1,6 +1,6 @@ package net.sourceforge.pmd.cli.internal; -import net.sourceforge.pmd.cli.internal.commands.PMDBaseCommand; +import net.sourceforge.pmd.cli.internal.commands.PMDRootCommand; import picocli.CommandLine; import picocli.CommandLine.Command; import picocli.CommandLine.Option; @@ -12,12 +12,9 @@ public class PMDCLI { } private static void tryPicoCli() { - new CommandLine(new PMDBaseCommand()) - .setCaseInsensitiveEnumValuesAllowed(true) -// .addSubcommand(new PMDCommand()) -// .addSubcommand(new CPDPicoCli()) - .execute("run", "-h"); -// .execute("run", "-t", "4", "-P", "foo=bar", "-R" , "foo,bar", "-R", "baz", "-d", "src/main/java", "-f", "xml"); + new CommandLine(new PMDRootCommand()).setCaseInsensitiveEnumValuesAllowed(true) + .execute("-h"); +// .execute("run", "-P", "foo=bar", "-R", "foo,bar", "-R", "baz", "-d", "src/main/java", "-f", "xml"); } @Command(name = "cpd", mixinStandardHelpOptions = true, description = "The Copy Paste Detector") diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDBaseCommand.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDRootCommand.java similarity index 65% rename from pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDBaseCommand.java rename to pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDRootCommand.java index 6a0e82e3d8..9006ba5ff5 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDBaseCommand.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDRootCommand.java @@ -7,8 +7,11 @@ import picocli.CommandLine.IVersionProvider; @Command(name = "pmd", mixinStandardHelpOptions = true, versionProvider = PMDVersionProvider.class, + exitCodeListHeading = "Exit Codes:%n", + exitCodeList = { "0:Succesful analysis, no violations found", "1:An unexpected error occurred during execution", + "2:Usage error, please refer to the command help", "4:Successful analysis, at least 1 violation found" }, subcommands = { PMDCommand.class, CPDPicoCli.class }) -public class PMDBaseCommand { +public class PMDRootCommand { } From ab035fa60d38dc837701ec53968c08e5fa633f06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 27 Jul 2022 12:00:40 -0300 Subject: [PATCH 062/254] Create an abstract PMD subcommand --- .../pmd/cli/internal/ExecutionResult.java | 13 ++++++------ .../commands/AbstractPMDSubcommand.java | 20 +++++++++++++++++++ .../pmd/cli/internal/commands/PMDCommand.java | 12 +++-------- 3 files changed, 30 insertions(+), 15 deletions(-) create mode 100644 pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/AbstractPMDSubcommand.java diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/ExecutionResult.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/ExecutionResult.java index c91b38c5c0..f0880b8ace 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/ExecutionResult.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/ExecutionResult.java @@ -1,17 +1,18 @@ package net.sourceforge.pmd.cli.internal; +// TODO : Unify with PMD.StatusCode / CPD.StatusCode public enum ExecutionResult { OK(0), ERROR(1), VIOLATIONS_FOUND(4); - private final int exitStatusCode; + private final int exitCode; - ExecutionResult(int exitStatusCode) { - this.exitStatusCode = exitStatusCode; + ExecutionResult(int exitCode) { + this.exitCode = exitCode; } - - public int getExitStatusCode() { - return exitStatusCode; + + public int getExitCode() { + return exitCode; } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/AbstractPMDSubcommand.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/AbstractPMDSubcommand.java new file mode 100644 index 0000000000..68794882e8 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/AbstractPMDSubcommand.java @@ -0,0 +1,20 @@ +package net.sourceforge.pmd.cli.internal.commands; + +import java.util.concurrent.Callable; + +import net.sourceforge.pmd.cli.internal.ExecutionResult; +import picocli.CommandLine.Model.CommandSpec; +import picocli.CommandLine.Spec; + +public abstract class AbstractPMDSubcommand implements Callable { + + @Spec + protected CommandSpec spec; // injected by PicoCli, needed for validations + + @Override + public final Integer call() throws Exception { + return execute().getExitCode(); + } + + protected abstract ExecutionResult execute(); +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDCommand.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDCommand.java index e9bfa6a1ed..1a04ed5c40 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDCommand.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDCommand.java @@ -5,7 +5,6 @@ import java.nio.file.Path; import java.util.Iterator; import java.util.List; import java.util.Properties; -import java.util.concurrent.Callable; import java.util.stream.Collectors; import org.checkerframework.checker.nullness.qual.Nullable; @@ -20,18 +19,13 @@ import net.sourceforge.pmd.lang.LanguageVersion; import net.sourceforge.pmd.renderers.RendererFactory; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; -import picocli.CommandLine.Model.CommandSpec; import picocli.CommandLine.Option; import picocli.CommandLine.ParameterException; -import picocli.CommandLine.Spec; @Command(name = "analyze", aliases = {"analyse", "run" }, showDefaultValues = true, description = "The PMD standard source code analyzer") -public class PMDCommand implements Callable { - - @Spec - private CommandSpec spec; // injected by PicoCli, needed for validations - +public class PMDCommand extends AbstractPMDSubcommand { + @SuppressWarnings("unused") @Mixin private SubCommandHelpMixin help; @@ -437,7 +431,7 @@ public class PMDCommand implements Callable { } @Override - public ExecutionResult call() throws Exception { + protected ExecutionResult execute() { if ((inputPaths == null || inputPaths.isEmpty()) && uri == null && fileListPath == null) { throw new ParameterException(spec.commandLine(), "Please provide a parameter for source root directory (--dir or -d), " From cfe307ef7b9f1b529e5c2b94bd573e0b227d5b2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 27 Jul 2022 15:50:03 -0300 Subject: [PATCH 063/254] Update autocomplete generation --- pmd-core/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pmd-core/pom.xml b/pmd-core/pom.xml index ec37a92263..7ce81c2c40 100644 --- a/pmd-core/pom.xml +++ b/pmd-core/pom.xml @@ -75,7 +75,7 @@ --force --completionScript ${project.build.directory}/pmd_completion.sh - net.sourceforge.pmd.cli.internal.commands.PMDBaseCommand + net.sourceforge.pmd.cli.internal.commands.PMDRootCommand From f1861b8a92b26463aeedc32fef12122973912c16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 27 Jul 2022 15:51:38 -0300 Subject: [PATCH 064/254] Remove the mixin in favor of the abstract subcommand --- .../internal/commands/AbstractPMDSubcommand.java | 4 ++++ .../pmd/cli/internal/commands/PMDCommand.java | 6 ------ .../commands/mixins/SubCommandHelpMixin.java | 14 -------------- 3 files changed, 4 insertions(+), 20 deletions(-) delete mode 100644 pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/mixins/SubCommandHelpMixin.java diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/AbstractPMDSubcommand.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/AbstractPMDSubcommand.java index 68794882e8..6708ce5ced 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/AbstractPMDSubcommand.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/AbstractPMDSubcommand.java @@ -4,6 +4,7 @@ import java.util.concurrent.Callable; import net.sourceforge.pmd.cli.internal.ExecutionResult; import picocli.CommandLine.Model.CommandSpec; +import picocli.CommandLine.Option; import picocli.CommandLine.Spec; public abstract class AbstractPMDSubcommand implements Callable { @@ -11,6 +12,9 @@ public abstract class AbstractPMDSubcommand implements Callable { @Spec protected CommandSpec spec; // injected by PicoCli, needed for validations + @Option(names = { "-h", "--help" }, usageHelp = true, description = "Show this help message and exit.") + protected boolean helpRequested; + @Override public final Integer call() throws Exception { return execute().getExitCode(); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDCommand.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDCommand.java index 1a04ed5c40..48f839a502 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDCommand.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDCommand.java @@ -12,13 +12,11 @@ import org.checkerframework.checker.nullness.qual.Nullable; import net.sourceforge.pmd.PMDConfiguration; import net.sourceforge.pmd.RulePriority; import net.sourceforge.pmd.cli.internal.ExecutionResult; -import net.sourceforge.pmd.cli.internal.commands.mixins.SubCommandHelpMixin; import net.sourceforge.pmd.lang.Language; import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.LanguageVersion; import net.sourceforge.pmd.renderers.RendererFactory; import picocli.CommandLine.Command; -import picocli.CommandLine.Mixin; import picocli.CommandLine.Option; import picocli.CommandLine.ParameterException; @@ -26,10 +24,6 @@ import picocli.CommandLine.ParameterException; description = "The PMD standard source code analyzer") public class PMDCommand extends AbstractPMDSubcommand { - @SuppressWarnings("unused") - @Mixin - private SubCommandHelpMixin help; - private List rulesets; private URI uri; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/mixins/SubCommandHelpMixin.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/mixins/SubCommandHelpMixin.java deleted file mode 100644 index 5eb2cc037c..0000000000 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/mixins/SubCommandHelpMixin.java +++ /dev/null @@ -1,14 +0,0 @@ -package net.sourceforge.pmd.cli.internal.commands.mixins; - -import picocli.CommandLine.Option; - -/** - * A mixin for subcommands that need to show help, - * but don't want to also display a version as done with {@code mixinStandardHelpOptions = true} - */ -public class SubCommandHelpMixin { - @SuppressWarnings("unused") - @Option(names = {"-h", "--help"}, usageHelp = true, descriptionKey = "mixinStandardHelpOptions.help", - description = "Show this help message and exit.") - private boolean helpRequested; -} From ef6c07c9fa9c30e9ff150bfe4b881a4a78ef1573 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Fri, 29 Jul 2022 22:17:42 -0300 Subject: [PATCH 065/254] Move everything to a separate pmd-cli module - Have the CLI separate from the standard analysis engine. - This allows for easier integration to other tools (ie: IDEs and build toolchains) - This allows a self-contained way to setup the commands and have autocomplete scripts include all languages - This easily allows to include the pmd designer to expose under a standard command --- pmd-cli/pom.xml | 291 ++++++++++++++++++ .../java/net/sourceforge/pmd/cli}/PMDCLI.java | 12 +- .../internal}/AbstractPMDSubcommand.java | 2 +- .../cli/commands/internal}/PMDCommand.java | 73 ++--- .../commands/internal}/PMDRootCommand.java | 4 +- .../pmd/cli/internal/ExecutionResult.java | 0 pom.xml | 1 + 7 files changed, 330 insertions(+), 53 deletions(-) create mode 100644 pmd-cli/pom.xml rename {pmd-core/src/main/java/net/sourceforge/pmd/cli/internal => pmd-cli/src/main/java/net/sourceforge/pmd/cli}/PMDCLI.java (76%) rename {pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands => pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal}/AbstractPMDSubcommand.java (93%) rename {pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands => pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal}/PMDCommand.java (89%) rename {pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands => pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal}/PMDRootCommand.java (87%) rename {pmd-core => pmd-cli}/src/main/java/net/sourceforge/pmd/cli/internal/ExecutionResult.java (100%) diff --git a/pmd-cli/pom.xml b/pmd-cli/pom.xml new file mode 100644 index 0000000000..fc2a623d9f --- /dev/null +++ b/pmd-cli/pom.xml @@ -0,0 +1,291 @@ + + + 4.0.0 + pmd-cli + PMD CLI + + + net.sourceforge.pmd + pmd + 7.0.0-SNAPSHOT + ../pom.xml + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + pmd-cli-checkstyle-suppressions.xml + + + + + + org.codehaus.mojo + exec-maven-plugin + + + generate-autocompletion-script + package + + exec + + + + + java + + -Dpicocli.autocomplete.systemExitOnError + -cp + + picocli.AutoComplete + --force + --completionScript + ${project.build.directory}/pmd_completion.sh + net.sourceforge.pmd.cli.internal.commands.PMDRootCommand + + + + + + + + net.sourceforge.pmd + pmd-core + ${project.version} + + + + + + net.sourceforge.pmd + pmd-apex + ${project.version} + runtime + + + net.sourceforge.pmd + pmd-cpp + ${project.version} + runtime + + + net.sourceforge.pmd + pmd-cs + ${project.version} + runtime + + + net.sourceforge.pmd + pmd-dart + ${project.version} + runtime + + + net.sourceforge.pmd + pmd-fortran + ${project.version} + runtime + + + net.sourceforge.pmd + pmd-gherkin + ${project.version} + runtime + + + net.sourceforge.pmd + pmd-go + ${project.version} + runtime + + + net.sourceforge.pmd + pmd-groovy + ${project.version} + runtime + + + net.sourceforge.pmd + pmd-html + ${project.version} + runtime + + + net.sourceforge.pmd + pmd-lua + ${project.version} + runtime + + + net.sourceforge.pmd + pmd-java + ${project.version} + runtime + + + net.sourceforge.pmd + pmd-javascript + ${project.version} + runtime + + + net.sourceforge.pmd + pmd-jsp + ${project.version} + runtime + + + net.sourceforge.pmd + pmd-kotlin + ${project.version} + runtime + + + net.sourceforge.pmd + pmd-matlab + ${project.version} + runtime + + + net.sourceforge.pmd + pmd-modelica + ${project.version} + runtime + + + net.sourceforge.pmd + pmd-perl + ${project.version} + runtime + + + net.sourceforge.pmd + pmd-objectivec + ${project.version} + runtime + + + net.sourceforge.pmd + pmd-php + ${project.version} + runtime + + + net.sourceforge.pmd + pmd-plsql + ${project.version} + runtime + + + net.sourceforge.pmd + pmd-python + ${project.version} + runtime + + + net.sourceforge.pmd + pmd-ruby + ${project.version} + runtime + + + net.sourceforge.pmd + pmd-scala_2.13 + ${project.version} + runtime + + + net.sourceforge.pmd + pmd-swift + ${project.version} + runtime + + + net.sourceforge.pmd + pmd-visualforce + ${project.version} + runtime + + + net.sourceforge.pmd + pmd-vm + ${project.version} + runtime + + + net.sourceforge.pmd + pmd-xml + ${project.version} + runtime + + + + + net.sourceforge.pmd + pmd-ui + ${pmd-designer.version} + + + + org.slf4j + slf4j-api + + + info.picocli + picocli + + + org.checkerframework + checker-qual + + + com.github.tomakehurst + wiremock-jre8 + test + + + org.hamcrest + hamcrest + test + + + org.junit.jupiter + junit-jupiter + test + + + org.junit.platform + junit-platform-suite + test + + + pl.pragmatists + JUnitParams + test + + + org.mockito + mockito-core + test + + + com.github.stefanbirkner + system-rules + test + + + com.github.stefanbirkner + system-lambda + test + + + + me.tongfei + progressbar + 0.9.3 + + + diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/PMDCLI.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/PMDCLI.java similarity index 76% rename from pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/PMDCLI.java rename to pmd-cli/src/main/java/net/sourceforge/pmd/cli/PMDCLI.java index 8f78023532..17054ea19b 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/PMDCLI.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/PMDCLI.java @@ -1,6 +1,6 @@ -package net.sourceforge.pmd.cli.internal; +package net.sourceforge.pmd.cli; -import net.sourceforge.pmd.cli.internal.commands.PMDRootCommand; +import net.sourceforge.pmd.cli.commands.internal.PMDRootCommand; import picocli.CommandLine; import picocli.CommandLine.Command; import picocli.CommandLine.Option; @@ -8,13 +8,9 @@ import picocli.CommandLine.Option; public class PMDCLI { public static void main(String[] args) { - tryPicoCli(); - } - - private static void tryPicoCli() { new CommandLine(new PMDRootCommand()).setCaseInsensitiveEnumValuesAllowed(true) - .execute("-h"); -// .execute("run", "-P", "foo=bar", "-R", "foo,bar", "-R", "baz", "-d", "src/main/java", "-f", "xml"); + .execute("run", "-h"); +// .execute("run", "-P", "foo=bar", "-R", "foo,bar", "-R", "baz", "-d", "src/main/java", "-f", "xml"); } @Command(name = "cpd", mixinStandardHelpOptions = true, description = "The Copy Paste Detector") diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/AbstractPMDSubcommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractPMDSubcommand.java similarity index 93% rename from pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/AbstractPMDSubcommand.java rename to pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractPMDSubcommand.java index 6708ce5ced..5d834367e4 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/AbstractPMDSubcommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractPMDSubcommand.java @@ -1,4 +1,4 @@ -package net.sourceforge.pmd.cli.internal.commands; +package net.sourceforge.pmd.cli.commands.internal; import java.util.concurrent.Callable; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PMDCommand.java similarity index 89% rename from pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDCommand.java rename to pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PMDCommand.java index 48f839a502..d337a20e9b 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PMDCommand.java @@ -1,7 +1,8 @@ -package net.sourceforge.pmd.cli.internal.commands; +package net.sourceforge.pmd.cli.commands.internal; import java.net.URI; import java.nio.file.Path; +import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Properties; @@ -58,10 +59,6 @@ public class PMDCommand extends AbstractPMDSubcommand { private Path reportFile; - private String version; - - private String language; - private String forceLanguage; private String auxClasspath; @@ -169,9 +166,7 @@ public class PMDCommand extends AbstractPMDSubcommand { this.suppressMarker = suppressMarker; } - // TODO : "-min" is not a single letter option, but was never deprecated in PMD - // 6.x - @Option(names = { "--minimum-priority", "-min" }, + @Option(names = { "--minimum-priority" }, description = "Rule priority threshold; rules with lower priority than configured here won't be used.%n" + "Valid values (case insensitive): ${COMPLETION-CANDIDATES}", defaultValue = "Low") @@ -192,19 +187,10 @@ public class PMDCommand extends AbstractPMDSubcommand { this.reportFile = reportFile; } - // TODO : -version was never deprecated, should be replaced with --version… but - // --version is used to display the PMD version… - @Option(names = { "-version", "-v" }, description = "Specify version of a language PMD should use.") - public void setVersion(final String version) { - this.version = version; - } - - // TODO : -langugae was never deprecated, should be replaced with --language - @Option(names = { "-language", "-l" }, - description = "Specify a language PMD should use.%nValid values: ${COMPLETION-CANDIDATES}%n", - completionCandidates = PMDSupportedLanguagesCandidates.class) - public void setLanguage(final String language) { - this.language = language; + @Option(names = { "--use-version" }, + description = "Sepcify the language and version PMD should use.%n Default: java latest", arity = "2", completionCandidates = LanguageVersionCandidates.class) + public void setLanguageVersion(String[] s) { + System.out.println(Arrays.asList(s)); } @Option(names = { "--force-language" }, @@ -361,17 +347,6 @@ public class PMDCommand extends AbstractPMDSubcommand { return reportFile; } - public String getVersion() { - if (version != null) { - return version; - } - return LanguageRegistry.findLanguageByTerseName(getLanguage()).getDefaultVersion().getVersion(); - } - - public String getLanguage() { - return language != null ? language : LanguageRegistry.getDefaultLanguage().getTerseName(); - } - public String getForceLanguage() { return forceLanguage != null ? forceLanguage : ""; } @@ -417,11 +392,12 @@ public class PMDCommand extends AbstractPMDSubcommand { } private @Nullable LanguageVersion getLangVersion() { - Language lang = language != null ? LanguageRegistry.findLanguageByTerseName(language) - : LanguageRegistry.getDefaultLanguage(); - - return version != null ? lang.getVersion(version) - : lang.getDefaultVersion(); + return null; +// Language lang = language != null ? LanguageRegistry.findLanguageByTerseName(language) +// : LanguageRegistry.getDefaultLanguage(); +// +// return version != null ? lang.getVersion(version) +// : lang.getDefaultVersion(); } @Override @@ -453,20 +429,33 @@ public class PMDCommand extends AbstractPMDSubcommand { return RendererFactory.supportedRenderers().iterator(); } } - + /** * Provider of candidates for valid languages. * - * Beware, the help will report this on runtime, and be accurate to available modules in the classpath, - * but autocomplete will include all at build time. + * Beware, the help will report this on runtime, and be accurate to available + * modules in the classpath, but autocomplete will include all at build time. */ private static class PMDSupportedLanguagesCandidates implements Iterable { @Override public Iterator iterator() { - return LanguageRegistry.getLanguages().stream() - .map(Language::getTerseName).iterator(); + return LanguageRegistry.getLanguages().stream().map(Language::getTerseName).iterator(); } + } + + /** + * Provider of candidates for valid language-version combinations. + * + * Beware, the help will report this on runtime, and be accurate to available + * modules in the classpath, but autocomplete will include all at build time. + */ + private static class LanguageVersionCandidates implements Iterable { + @Override + public Iterator iterator() { + return LanguageRegistry.getLanguages().stream().flatMap(l -> l.getVersions().stream()) + .map(LanguageVersion::getName).map(name -> name.replace(' ', '-')).iterator(); + } } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDRootCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PMDRootCommand.java similarity index 87% rename from pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDRootCommand.java rename to pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PMDRootCommand.java index 9006ba5ff5..9f7db5409e 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/commands/PMDRootCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PMDRootCommand.java @@ -1,7 +1,7 @@ -package net.sourceforge.pmd.cli.internal.commands; +package net.sourceforge.pmd.cli.commands.internal; import net.sourceforge.pmd.PMDVersion; -import net.sourceforge.pmd.cli.internal.PMDCLI.CPDPicoCli; +import net.sourceforge.pmd.cli.PMDCLI.CPDPicoCli; import picocli.CommandLine.Command; import picocli.CommandLine.IVersionProvider; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/ExecutionResult.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/internal/ExecutionResult.java similarity index 100% rename from pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/ExecutionResult.java rename to pmd-cli/src/main/java/net/sourceforge/pmd/cli/internal/ExecutionResult.java diff --git a/pom.xml b/pom.xml index 1de54f7139..09ee6a0f67 100644 --- a/pom.xml +++ b/pom.xml @@ -1218,6 +1218,7 @@ pmd-apex-jorje pmd-apex + pmd-cli pmd-core pmd-cpp pmd-cs From 172db11114b177b08758424e935f06108e4a7aee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Fri, 29 Jul 2022 22:25:09 -0300 Subject: [PATCH 066/254] Update pom autocomplete step --- pmd-cli/pom.xml | 2 +- pmd-core/pom.xml | 28 ---------------------------- 2 files changed, 1 insertion(+), 29 deletions(-) diff --git a/pmd-cli/pom.xml b/pmd-cli/pom.xml index fc2a623d9f..25fa8e82e9 100644 --- a/pmd-cli/pom.xml +++ b/pmd-cli/pom.xml @@ -44,7 +44,7 @@ --force --completionScript ${project.build.directory}/pmd_completion.sh - net.sourceforge.pmd.cli.internal.commands.PMDRootCommand + net.sourceforge.pmd.cli.commands.internal.PMDRootCommand diff --git a/pmd-core/pom.xml b/pmd-core/pom.xml index 7ce81c2c40..08e5e4daaa 100644 --- a/pmd-core/pom.xml +++ b/pmd-core/pom.xml @@ -51,34 +51,6 @@ - - - - org.codehaus.mojo - exec-maven-plugin - - - generate-autocompletion-script - package - - exec - - - - - java - - -Dpicocli.autocomplete.systemExitOnError - -cp - - picocli.AutoComplete - --force - --completionScript - ${project.build.directory}/pmd_completion.sh - net.sourceforge.pmd.cli.internal.commands.PMDRootCommand - - - From 34729c28e5515542fdf99a359931a8554bf31be5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Sat, 30 Jul 2022 01:45:10 -0300 Subject: [PATCH 067/254] Properly handle languages and versions - Add the candidates and converters for both objects - Properly validate the values and pass it to the configuration - `--use-version` can now be repeated to set multiple language versions - Revise arities of other options to avoid errors --- .../java/net/sourceforge/pmd/cli/PMDCLI.java | 6 +- .../pmd/cli/commands/internal/PMDCommand.java | 198 ++++++++++++------ 2 files changed, 140 insertions(+), 64 deletions(-) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/PMDCLI.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/PMDCLI.java index 17054ea19b..41a6cba0b3 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/PMDCLI.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/PMDCLI.java @@ -9,8 +9,10 @@ public class PMDCLI { public static void main(String[] args) { new CommandLine(new PMDRootCommand()).setCaseInsensitiveEnumValuesAllowed(true) - .execute("run", "-h"); -// .execute("run", "-P", "foo=bar", "-R", "foo,bar", "-R", "baz", "-d", "src/main/java", "-f", "xml"); +// .execute("run", "-h"); + .execute("run", "--use-version", "scala-2.11", "--use-version", "apex", "--use-version", + "ecmascript-latest", "-P", "foo=bar", "-R", "foo,bar", "-R", "baz", "-d", + "src/main/java", "-f", "xml"); } @Command(name = "cpd", mixinStandardHelpOptions = true, description = "The Copy Paste Detector") diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PMDCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PMDCommand.java index d337a20e9b..70295e4a80 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PMDCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PMDCommand.java @@ -2,13 +2,13 @@ package net.sourceforge.pmd.cli.commands.internal; import java.net.URI; import java.nio.file.Path; -import java.util.Arrays; import java.util.Iterator; import java.util.List; +import java.util.Optional; import java.util.Properties; +import java.util.TreeSet; import java.util.stream.Collectors; - -import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.stream.Stream; import net.sourceforge.pmd.PMDConfiguration; import net.sourceforge.pmd.RulePriority; @@ -18,8 +18,10 @@ import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.LanguageVersion; import net.sourceforge.pmd.renderers.RendererFactory; import picocli.CommandLine.Command; +import picocli.CommandLine.ITypeConverter; import picocli.CommandLine.Option; import picocli.CommandLine.ParameterException; +import picocli.CommandLine.TypeConversionException; @Command(name = "analyze", aliases = {"analyse", "run" }, showDefaultValues = true, description = "The PMD standard source code analyzer") @@ -39,6 +41,7 @@ public class PMDCommand extends AbstractPMDSubcommand { private boolean debug; + // TODO : Actually use an Charset instance? private String encoding; private int threads; @@ -59,7 +62,9 @@ public class PMDCommand extends AbstractPMDSubcommand { private Path reportFile; - private String forceLanguage; + private List languageVersion; + + private Language forceLanguage; private String auxClasspath; @@ -75,8 +80,7 @@ public class PMDCommand extends AbstractPMDSubcommand { description = "Path to a ruleset xml file. " + "The path may reference a resource on the classpath of the application, be a local file system path, or a URL. " + "The option can be repeated, and multiple arguments separated by comma can be provided to a single occurrence of the option.", - required = true, split = ",", - arity = "1..*") + required = true, split = ",") public void setRulesets(final List rulesets) { this.rulesets = rulesets; } @@ -98,7 +102,7 @@ public class PMDCommand extends AbstractPMDSubcommand { + "(archive files found while exploring a directory are not recursively expanded). " + "This option can be repeated, and multiple arguments can be provided to a single occurrence of the option. " + "One of --dir, --file-list or --uri must be provided. ", - arity = "0..*") + arity = "1..*") public void setInputPaths(final List inputPaths) { this.inputPaths = inputPaths; } @@ -174,6 +178,7 @@ public class PMDCommand extends AbstractPMDSubcommand { this.minimumPriority = priority; } + // TODO : Figure out how to surface the supported properties for each report format @Option(names = { "--property", "-P" }, description = "Key-value pair defining a property for the report format.") public void setProperties(final Properties properties) { this.properties = properties; @@ -187,10 +192,21 @@ public class PMDCommand extends AbstractPMDSubcommand { this.reportFile = reportFile; } - @Option(names = { "--use-version" }, - description = "Sepcify the language and version PMD should use.%n Default: java latest", arity = "2", completionCandidates = LanguageVersionCandidates.class) - public void setLanguageVersion(String[] s) { - System.out.println(Arrays.asList(s)); + @Option(names = { "--use-version" }, defaultValue = "java-latest", + description = "Sepcify the language and version PMD should use.%nValid values: ${COMPLETION-CANDIDATES}%n", + completionCandidates = PMDLanguageVersionCandidates.class, converter = PMDLanguageVersionConverter.class) + public void setLanguageVersion(final List languageVersion) { + // Make sure we only set 1 version per language + languageVersion.stream().collect(Collectors.groupingBy(LanguageVersion::getLanguage)) + .forEach((l, list) -> { + if (list.size() > 1) { + throw new ParameterException(spec.commandLine(), "Can only set one version per language, " + + "but for language " + l.getName() + " multiple versions were provided " + + list.stream().map(PMDCommand::normalizeName).collect(Collectors.joining("', '", "'", "'"))); + } + }); + + this.languageVersion = languageVersion; } @Option(names = { "--force-language" }, @@ -198,8 +214,8 @@ public class PMDCommand extends AbstractPMDSubcommand { + "When using this option, the automatic language selection by extension is disabled, and PMD " + "tries to parse all input files with the given language's parser. " + "Parsing errors are ignored.%nValid values: ${COMPLETION-CANDIDATES}%n", - completionCandidates = PMDSupportedLanguagesCandidates.class) - public void setForceLanguage(final String forceLanguage) { + completionCandidates = PMDLanguagesCandidates.class, converter = PMDLanguageConverter.class) + public void setForceLanguage(final Language forceLanguage) { this.forceLanguage = forceLanguage; } @@ -259,40 +275,41 @@ public class PMDCommand extends AbstractPMDSubcommand { */ public PMDConfiguration toConfiguration() { PMDConfiguration configuration = new PMDConfiguration(); - configuration.setInputPaths(this.getInputPaths().stream().map(Path::toString).collect(Collectors.toList())); - configuration.setInputFilePath(this.getFileListPath().toString()); - configuration.setIgnoreFilePath(this.getIgnoreListPath().toString()); - configuration.setInputUri(this.getUri().toString()); - configuration.setReportFormat(this.getFormat()); - configuration.setBenchmark(this.isBenchmark()); - configuration.setDebug(this.isDebug()); - configuration.setMinimumPriority(this.getMinimumPriority()); - configuration.setReportFile(this.getReportFile().toString()); - configuration.setReportProperties(this.getProperties()); - configuration.setReportShortNames(this.isShortnames()); - configuration.setRuleSets(this.getRulesetRefs()); + configuration.setInputPaths(inputPaths.stream().map(Path::toString).collect(Collectors.toList())); + configuration.setInputFilePath(fileListPath != null ? fileListPath.toString() : null); + configuration.setIgnoreFilePath(ignoreListPath != null ? ignoreListPath.toString() : null); + configuration.setInputUri(uri != null ? uri.toString() : null); + configuration.setReportFormat(format); + configuration.setBenchmark(benchmark); + configuration.setDebug(debug); + configuration.setMinimumPriority(minimumPriority); + configuration.setReportFile(reportFile != null ? reportFile.toString() : null); + configuration.setReportProperties(properties); + configuration.setReportShortNames(shortnames); + configuration.setRuleSets(rulesets); configuration.setRuleSetFactoryCompatibilityEnabled(!this.noRuleSetCompatibility); - configuration.setShowSuppressedViolations(this.isShowSuppressed()); - configuration.setSourceEncoding(this.getEncoding()); - configuration.setStressTest(this.isStress()); - configuration.setSuppressMarker(this.getSuppressMarker()); - configuration.setThreads(this.getThreads()); - configuration.setFailOnViolation(this.isFailOnViolation()); - configuration.setAnalysisCacheLocation(this.cacheLocation.toString()); - configuration.setIgnoreIncrementalAnalysis(this.isIgnoreIncrementalAnalysis()); + configuration.setShowSuppressedViolations(showSuppressed); + configuration.setSourceEncoding(encoding); + configuration.setStressTest(stress); + configuration.setSuppressMarker(suppressMarker); + configuration.setThreads(threads); + configuration.setFailOnViolation(failOnViolation); + configuration.setAnalysisCacheLocation(cacheLocation != null ? cacheLocation.toString() : null); + configuration.setIgnoreIncrementalAnalysis(noCache); - final LanguageVersion forceLangVersion = getForceLangVersion(); - if (forceLangVersion != null) { - configuration.setForceLanguageVersion(forceLangVersion); - } - - final LanguageVersion languageVersion = getLangVersion(); if (languageVersion != null) { - configuration.getLanguageVersionDiscoverer().setDefaultLanguageVersion(languageVersion); + configuration.setDefaultLanguageVersions(languageVersion); + } + + // Important: do this after setting default versions, so we can pick them up + if (forceLanguage != null) { + final LanguageVersion forcedLangVer = configuration.getLanguageVersionDiscoverer() + .getDefaultLanguageVersion(forceLanguage); + configuration.setForceLanguageVersion(forcedLangVer); } try { - configuration.prependAuxClasspath(this.getAuxClasspath()); + configuration.prependAuxClasspath(auxClasspath); } catch (IllegalArgumentException e) { throw new ParameterException(spec.commandLine(), "Invalid auxiliary classpath: " + e.getMessage(), e); } @@ -347,8 +364,8 @@ public class PMDCommand extends AbstractPMDSubcommand { return reportFile; } - public String getForceLanguage() { - return forceLanguage != null ? forceLanguage : ""; + public Language getForceLanguage() { + return forceLanguage; } public String getAuxClasspath() { @@ -385,20 +402,6 @@ public class PMDCommand extends AbstractPMDSubcommand { public URI getUri() { return uri; } - - private @Nullable LanguageVersion getForceLangVersion() { - Language lang = forceLanguage != null ? LanguageRegistry.findLanguageByTerseName(forceLanguage) : null; - return lang != null ? lang.getDefaultVersion() : null; - } - - private @Nullable LanguageVersion getLangVersion() { - return null; -// Language lang = language != null ? LanguageRegistry.findLanguageByTerseName(language) -// : LanguageRegistry.getDefaultLanguage(); -// -// return version != null ? lang.getVersion(version) -// : lang.getDefaultVersion(); - } @Override protected ExecutionResult execute() { @@ -408,12 +411,14 @@ public class PMDCommand extends AbstractPMDSubcommand { + "database URI (--uri or -u), or file list path (--file-list)"); } + System.out.println("language versions: " + languageVersion); System.out.println("threads: " + threads); System.out.println("priority: " + minimumPriority); System.out.println("properties: " + properties); System.out.println("ruleset: " + rulesets); System.out.println("format: " + format); System.out.println("priority: " + minimumPriority); + System.out.println("force lang ver: " + toConfiguration().getForceLanguageVersion()); // TODO Auto-generated method stub return ExecutionResult.OK; @@ -436,13 +441,29 @@ public class PMDCommand extends AbstractPMDSubcommand { * Beware, the help will report this on runtime, and be accurate to available * modules in the classpath, but autocomplete will include all at build time. */ - private static class PMDSupportedLanguagesCandidates implements Iterable { + private static class PMDLanguagesCandidates implements Iterable { @Override public Iterator iterator() { - return LanguageRegistry.getLanguages().stream().map(Language::getTerseName).iterator(); + return LanguageRegistry.getLanguages().stream().map(PMDCommand::normalizeName).iterator(); } } + + /** + * Maps a String back to a {@code Language} + * + * Effectively reverses the stringification done by {@code PMDLanguagesCandidates} + */ + private static class PMDLanguageConverter implements ITypeConverter { + + @Override + public Language convert(final String value) throws Exception { + return LanguageRegistry.getLanguages().stream() + .filter(l -> normalizeName(l).equals(value)).findFirst() + .orElseThrow(() -> new TypeConversionException("Unknown language: " + value)); + } + + } /** * Provider of candidates for valid language-version combinations. @@ -450,12 +471,65 @@ public class PMDCommand extends AbstractPMDSubcommand { * Beware, the help will report this on runtime, and be accurate to available * modules in the classpath, but autocomplete will include all at build time. */ - private static class LanguageVersionCandidates implements Iterable { + private static class PMDLanguageVersionCandidates implements Iterable { @Override public Iterator iterator() { - return LanguageRegistry.getLanguages().stream().flatMap(l -> l.getVersions().stream()) - .map(LanguageVersion::getName).map(name -> name.replace(' ', '-')).iterator(); + // Raw language names / -latest versions, such as "java" or "java-latest" + final Stream latestLangReferences = LanguageRegistry.getLanguages().stream() + .map(PMDCommand::normalizeName).flatMap(name -> Stream.of(name, name + "-latest")); + + // Explicit language-version pairs, such as "java-18" or "apex-54" + final Stream allLangVersionReferences = LanguageRegistry.getLanguages().stream() + .flatMap(l -> l.getVersions().stream()) + .map(PMDCommand::normalizeName); + + // Collect to a TreeSet to ensure alphabetical order + final TreeSet candidates = Stream.concat(latestLangReferences, allLangVersionReferences) + .collect(Collectors.toCollection(TreeSet::new)); + + return candidates.iterator(); } } + + /** + * Maps a String back to a {@code LanguageVersion} + * + * Effectively reverses the stringification done by {@code PMDLanguageVersionCandidates} + */ + private static class PMDLanguageVersionConverter implements ITypeConverter { + + @Override + public LanguageVersion convert(final String value) throws Exception { + // Is it an exact match? + final Optional langVer = LanguageRegistry.getLanguages().stream() + .flatMap(l -> l.getVersions().stream()) + .filter(lv -> normalizeName(lv).equals(value)).findFirst(); + + if (langVer.isPresent()) { + return langVer.get(); + } + + // This is either a -latest or standalone language name + final String langName; + if (value.endsWith("-latest")) { + langName = value.substring(0, value.length() - "-latest".length()); + } else { + langName = value; + } + + return LanguageRegistry.getLanguages().stream() + .filter(l -> normalizeName(l).equals(langName)) + .map(Language::getDefaultVersion).findFirst() + .orElseThrow(() -> new TypeConversionException("Unknown language version: " + value)); + } + } + + static String normalizeName(final LanguageVersion langVer) { + return langVer.getTerseName().replace(' ', '-'); + } + + static String normalizeName(final Language lang) { + return lang.getTerseName().replace(' ', '-'); + } } From adb08a983844283bc0fcd0dc53fc1dfbafe4735a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Tue, 2 Aug 2022 22:48:23 -0300 Subject: [PATCH 068/254] Rename classes --- pmd-cli/pom.xml | 2 +- .../sourceforge/pmd/cli/{PMDCLI.java => PmdCli.java} | 8 ++++---- ...ctPMDSubcommand.java => AbstractPmdSubcommand.java} | 2 +- .../internal/{PMDCommand.java => PmdCommand.java} | 10 +++++----- .../{PMDRootCommand.java => PmdRootCommand.java} | 6 +++--- 5 files changed, 14 insertions(+), 14 deletions(-) rename pmd-cli/src/main/java/net/sourceforge/pmd/cli/{PMDCLI.java => PmdCli.java} (85%) rename pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/{AbstractPMDSubcommand.java => AbstractPmdSubcommand.java} (91%) rename pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/{PMDCommand.java => PmdCommand.java} (98%) rename pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/{PMDRootCommand.java => PmdRootCommand.java} (84%) diff --git a/pmd-cli/pom.xml b/pmd-cli/pom.xml index 25fa8e82e9..39ecd6c824 100644 --- a/pmd-cli/pom.xml +++ b/pmd-cli/pom.xml @@ -44,7 +44,7 @@ --force --completionScript ${project.build.directory}/pmd_completion.sh - net.sourceforge.pmd.cli.commands.internal.PMDRootCommand + net.sourceforge.pmd.cli.commands.internal.PmdRootCommand diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/PMDCLI.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/PmdCli.java similarity index 85% rename from pmd-cli/src/main/java/net/sourceforge/pmd/cli/PMDCLI.java rename to pmd-cli/src/main/java/net/sourceforge/pmd/cli/PmdCli.java index 41a6cba0b3..c92fa1234f 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/PMDCLI.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/PmdCli.java @@ -1,14 +1,14 @@ package net.sourceforge.pmd.cli; -import net.sourceforge.pmd.cli.commands.internal.PMDRootCommand; +import net.sourceforge.pmd.cli.commands.internal.PmdRootCommand; import picocli.CommandLine; import picocli.CommandLine.Command; import picocli.CommandLine.Option; -public class PMDCLI { +public class PmdCli { public static void main(String[] args) { - new CommandLine(new PMDRootCommand()).setCaseInsensitiveEnumValuesAllowed(true) + new CommandLine(new PmdRootCommand()).setCaseInsensitiveEnumValuesAllowed(true) // .execute("run", "-h"); .execute("run", "--use-version", "scala-2.11", "--use-version", "apex", "--use-version", "ecmascript-latest", "-P", "foo=bar", "-R", "foo,bar", "-R", "baz", "-d", @@ -16,7 +16,7 @@ public class PMDCLI { } @Command(name = "cpd", mixinStandardHelpOptions = true, description = "The Copy Paste Detector") - public static class CPDPicoCli implements Runnable { + public static class CpdPicoCli implements Runnable { @SuppressWarnings("unused") @Option(names = "--minimum-tokens", description = "The minimum token length which should be reported as a duplicate.", diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractPMDSubcommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractPmdSubcommand.java similarity index 91% rename from pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractPMDSubcommand.java rename to pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractPmdSubcommand.java index 5d834367e4..17bac25525 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractPMDSubcommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractPmdSubcommand.java @@ -7,7 +7,7 @@ import picocli.CommandLine.Model.CommandSpec; import picocli.CommandLine.Option; import picocli.CommandLine.Spec; -public abstract class AbstractPMDSubcommand implements Callable { +public abstract class AbstractPmdSubcommand implements Callable { @Spec protected CommandSpec spec; // injected by PicoCli, needed for validations diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PMDCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java similarity index 98% rename from pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PMDCommand.java rename to pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java index 70295e4a80..e45df8ec6a 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PMDCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java @@ -25,7 +25,7 @@ import picocli.CommandLine.TypeConversionException; @Command(name = "analyze", aliases = {"analyse", "run" }, showDefaultValues = true, description = "The PMD standard source code analyzer") -public class PMDCommand extends AbstractPMDSubcommand { +public class PmdCommand extends AbstractPmdSubcommand { private List rulesets; @@ -202,7 +202,7 @@ public class PMDCommand extends AbstractPMDSubcommand { if (list.size() > 1) { throw new ParameterException(spec.commandLine(), "Can only set one version per language, " + "but for language " + l.getName() + " multiple versions were provided " - + list.stream().map(PMDCommand::normalizeName).collect(Collectors.joining("', '", "'", "'"))); + + list.stream().map(PmdCommand::normalizeName).collect(Collectors.joining("', '", "'", "'"))); } }); @@ -445,7 +445,7 @@ public class PMDCommand extends AbstractPMDSubcommand { @Override public Iterator iterator() { - return LanguageRegistry.getLanguages().stream().map(PMDCommand::normalizeName).iterator(); + return LanguageRegistry.getLanguages().stream().map(PmdCommand::normalizeName).iterator(); } } @@ -477,12 +477,12 @@ public class PMDCommand extends AbstractPMDSubcommand { public Iterator iterator() { // Raw language names / -latest versions, such as "java" or "java-latest" final Stream latestLangReferences = LanguageRegistry.getLanguages().stream() - .map(PMDCommand::normalizeName).flatMap(name -> Stream.of(name, name + "-latest")); + .map(PmdCommand::normalizeName).flatMap(name -> Stream.of(name, name + "-latest")); // Explicit language-version pairs, such as "java-18" or "apex-54" final Stream allLangVersionReferences = LanguageRegistry.getLanguages().stream() .flatMap(l -> l.getVersions().stream()) - .map(PMDCommand::normalizeName); + .map(PmdCommand::normalizeName); // Collect to a TreeSet to ensure alphabetical order final TreeSet candidates = Stream.concat(latestLangReferences, allLangVersionReferences) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PMDRootCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdRootCommand.java similarity index 84% rename from pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PMDRootCommand.java rename to pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdRootCommand.java index 9f7db5409e..e29e640142 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PMDRootCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdRootCommand.java @@ -1,7 +1,7 @@ package net.sourceforge.pmd.cli.commands.internal; import net.sourceforge.pmd.PMDVersion; -import net.sourceforge.pmd.cli.PMDCLI.CPDPicoCli; +import net.sourceforge.pmd.cli.PmdCli.CpdPicoCli; import picocli.CommandLine.Command; import picocli.CommandLine.IVersionProvider; @@ -10,8 +10,8 @@ import picocli.CommandLine.IVersionProvider; exitCodeListHeading = "Exit Codes:%n", exitCodeList = { "0:Succesful analysis, no violations found", "1:An unexpected error occurred during execution", "2:Usage error, please refer to the command help", "4:Successful analysis, at least 1 violation found" }, - subcommands = { PMDCommand.class, CPDPicoCli.class }) -public class PMDRootCommand { + subcommands = { PmdCommand.class, CpdPicoCli.class }) +public class PmdRootCommand { } From 7006d7fc3df27214ea89bc5da9cd16ca27aab1ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 3 Aug 2022 00:34:42 -0300 Subject: [PATCH 069/254] Move cache nagging into PmdAnalysis - It was already done as part of the execution, so have it self-contained - The method runAndReturnStats is open up as public --- .../main/java/net/sourceforge/pmd/PMD.java | 13 ------------- .../java/net/sourceforge/pmd/PmdAnalysis.java | 19 +++++++++++++++++-- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java b/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java index 086f4fd019..d526c1c45d 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java @@ -26,7 +26,6 @@ import net.sourceforge.pmd.benchmark.TextTimingReportRenderer; import net.sourceforge.pmd.benchmark.TimeTracker; import net.sourceforge.pmd.benchmark.TimingReport; import net.sourceforge.pmd.benchmark.TimingReportRenderer; -import net.sourceforge.pmd.cache.NoopAnalysisCache; import net.sourceforge.pmd.cli.PMDCommandLineInterface; import net.sourceforge.pmd.cli.PmdParametersParseResult; import net.sourceforge.pmd.cli.internal.CliMessages; @@ -72,18 +71,6 @@ public final class PMD { private PMD() { } - - static void encourageToUseIncrementalAnalysis(final PMDConfiguration configuration) { - if (!configuration.isIgnoreIncrementalAnalysis() - && configuration.getAnalysisCache() instanceof NoopAnalysisCache - && log.isWarnEnabled()) { - final String version = - PMDVersion.isUnknown() || PMDVersion.isSnapshot() ? "latest" : "pmd-" + PMDVersion.VERSION; - log.warn("This analysis could be faster, please consider using Incremental Analysis: " - + "https://pmd.github.io/{}/pmd_userdocs_incremental_analysis.html", version); - } - } - /** * Run PMD using the given configuration. This replaces the other overload. * diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/PmdAnalysis.java b/pmd-core/src/main/java/net/sourceforge/pmd/PmdAnalysis.java index ce84aa200c..e58a53d6d2 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/PmdAnalysis.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/PmdAnalysis.java @@ -16,12 +16,14 @@ import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.slf4j.event.Level; import net.sourceforge.pmd.Report.GlobalReportBuilderListener; import net.sourceforge.pmd.benchmark.TimeTracker; import net.sourceforge.pmd.benchmark.TimedOperation; import net.sourceforge.pmd.benchmark.TimedOperationCategory; import net.sourceforge.pmd.cache.AnalysisCacheListener; +import net.sourceforge.pmd.cache.NoopAnalysisCache; import net.sourceforge.pmd.cli.internal.CliMessages; import net.sourceforge.pmd.cli.internal.ProgressBarListener; import net.sourceforge.pmd.internal.util.AssertionUtil; @@ -301,7 +303,7 @@ public final class PmdAnalysis implements AutoCloseable { listener.onConfigError(new Report.ConfigurationError(rule, rule.dysfunctionReason())); } - PMD.encourageToUseIncrementalAnalysis(configuration); + encourageToUseIncrementalAnalysis(configuration); try (AbstractPMDProcessor processor = AbstractPMDProcessor.newFileProcessor(configuration)) { processor.processFiles(rulesets, textFiles, listener); } @@ -400,7 +402,7 @@ public final class PmdAnalysis implements AutoCloseable { } } - ReportStats runAndReturnStats() { + public ReportStats runAndReturnStats() { if (getRulesets().isEmpty()) { return ReportStats.empty(); } @@ -437,4 +439,17 @@ public final class PmdAnalysis implements AutoCloseable { void printErrorDetected(int errors) { printErrorDetected(getReporter(), errors); } + + private static void encourageToUseIncrementalAnalysis(final PMDConfiguration configuration) { + final MessageReporter reporter = configuration.getReporter(); + + if (!configuration.isIgnoreIncrementalAnalysis() + && configuration.getAnalysisCache() instanceof NoopAnalysisCache + && reporter.isLoggable(Level.WARN)) { + final String version = + PMDVersion.isUnknown() || PMDVersion.isSnapshot() ? "latest" : "pmd-" + PMDVersion.VERSION; + reporter.warn("This analysis could be faster, please consider using Incremental Analysis: " + + "https://pmd.github.io/{}/pmd_userdocs_incremental_analysis.html", version); + } + } } From 912db372e88d59f3429be9449879820826acf61e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 3 Aug 2022 00:37:58 -0300 Subject: [PATCH 070/254] Setup SLF4J for all subcommands - Have the debug flag shared between all commands and get SLF4J init --- pmd-cli/pom.xml | 4 ++++ .../internal/AbstractPmdSubcommand.java | 18 ++++++++++++++++++ .../pmd/cli/commands/internal/PmdCommand.java | 7 ------- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/pmd-cli/pom.xml b/pmd-cli/pom.xml index 39ecd6c824..9ccf08f90a 100644 --- a/pmd-cli/pom.xml +++ b/pmd-cli/pom.xml @@ -233,6 +233,10 @@ org.slf4j slf4j-api + + org.slf4j + slf4j-simple + info.picocli picocli diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractPmdSubcommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractPmdSubcommand.java index 17bac25525..73ab98ca9d 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractPmdSubcommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractPmdSubcommand.java @@ -2,7 +2,10 @@ package net.sourceforge.pmd.cli.commands.internal; import java.util.concurrent.Callable; +import org.slf4j.event.Level; + import net.sourceforge.pmd.cli.internal.ExecutionResult; +import net.sourceforge.pmd.internal.Slf4jSimpleConfiguration; import picocli.CommandLine.Model.CommandSpec; import picocli.CommandLine.Option; import picocli.CommandLine.Spec; @@ -15,10 +18,25 @@ public abstract class AbstractPmdSubcommand implements Callable { @Option(names = { "-h", "--help" }, usageHelp = true, description = "Show this help message and exit.") protected boolean helpRequested; + @Option(names = { "--debug", "--verbose", "-D", "-V" }, description = "Debug mode.") + protected boolean debug; + @Override public final Integer call() throws Exception { + setupCliLogger(); return execute().getExitCode(); } protected abstract ExecutionResult execute(); + + private void setupCliLogger() { + // only reconfigure logging, if debug flag was used on command line + // otherwise just use whatever is in conf/simplelogger.properties which happens automatically + if (debug) { + Slf4jSimpleConfiguration.reconfigureDefaultLogLevel(Level.TRACE); + } + + // always install java.util.logging to slf4j bridge + Slf4jSimpleConfiguration.installJulBridge(); + } } diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java index e45df8ec6a..c8df93803e 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java @@ -39,8 +39,6 @@ public class PmdCommand extends AbstractPmdSubcommand { private String format; - private boolean debug; - // TODO : Actually use an Charset instance? private String encoding; @@ -130,11 +128,6 @@ public class PmdCommand extends AbstractPmdSubcommand { this.format = format; } - @Option(names = { "--debug", "--verbose", "-D", "-V" }, description = "Debug mode.") - public void setDebug(final boolean debug) { - this.debug = debug; - } - @Option(names = { "--encoding", "-e" }, description = "Specifies the character set encoding of the source code files PMD is reading (i.e., UTF-8).", defaultValue = "UTF-8") From 40803d23171a478dd5d5f2073b1e050f802fd1dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 3 Aug 2022 00:39:22 -0300 Subject: [PATCH 071/254] Implement PMD Analysis - Do cleanup and actually implement the PMD Analysis logic --- .../pmd/cli/commands/internal/PmdCommand.java | 194 ++++++++---------- .../cli/commands/internal/PmdRootCommand.java | 1 + 2 files changed, 86 insertions(+), 109 deletions(-) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java index c8df93803e..d6058c8e0b 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java @@ -1,5 +1,8 @@ package net.sourceforge.pmd.cli.commands.internal; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; import java.net.URI; import java.nio.file.Path; import java.util.Iterator; @@ -10,13 +13,27 @@ import java.util.TreeSet; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.shaded.apache.commons.io.output.CloseShieldWriter; +import org.slf4j.LoggerFactory; +import org.slf4j.event.Level; + import net.sourceforge.pmd.PMDConfiguration; +import net.sourceforge.pmd.PmdAnalysis; import net.sourceforge.pmd.RulePriority; +import net.sourceforge.pmd.benchmark.TextTimingReportRenderer; +import net.sourceforge.pmd.benchmark.TimeTracker; +import net.sourceforge.pmd.benchmark.TimingReport; +import net.sourceforge.pmd.benchmark.TimingReportRenderer; +import net.sourceforge.pmd.cli.internal.CliMessages; import net.sourceforge.pmd.cli.internal.ExecutionResult; import net.sourceforge.pmd.lang.Language; import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.LanguageVersion; import net.sourceforge.pmd.renderers.RendererFactory; +import net.sourceforge.pmd.reporting.ReportStats; +import net.sourceforge.pmd.util.StringUtil; +import net.sourceforge.pmd.util.log.MessageReporter; +import net.sourceforge.pmd.util.log.internal.SimpleMessageReporter; import picocli.CommandLine.Command; import picocli.CommandLine.ITypeConverter; import picocli.CommandLine.Option; @@ -46,8 +63,6 @@ public class PmdCommand extends AbstractPmdSubcommand { private boolean benchmark; - private boolean stress; - private boolean shortnames; private boolean showSuppressed; @@ -141,11 +156,6 @@ public class PmdCommand extends AbstractPmdSubcommand { this.benchmark = benchmark; } - @Option(names = { "--stress", "-S" }, description = "Performs a stress test.") - public void setStress(final boolean stress) { - this.stress = stress; - } - @Option(names = { "--short-names" }, description = "Prints shortened filenames in the report.") public void setShortnames(final boolean shortnames) { this.shortnames = shortnames; @@ -273,7 +283,6 @@ public class PmdCommand extends AbstractPmdSubcommand { configuration.setIgnoreFilePath(ignoreListPath != null ? ignoreListPath.toString() : null); configuration.setInputUri(uri != null ? uri.toString() : null); configuration.setReportFormat(format); - configuration.setBenchmark(benchmark); configuration.setDebug(debug); configuration.setMinimumPriority(minimumPriority); configuration.setReportFile(reportFile != null ? reportFile.toString() : null); @@ -283,7 +292,6 @@ public class PmdCommand extends AbstractPmdSubcommand { configuration.setRuleSetFactoryCompatibilityEnabled(!this.noRuleSetCompatibility); configuration.setShowSuppressedViolations(showSuppressed); configuration.setSourceEncoding(encoding); - configuration.setStressTest(stress); configuration.setSuppressMarker(suppressMarker); configuration.setThreads(threads); configuration.setFailOnViolation(failOnViolation); @@ -301,6 +309,9 @@ public class PmdCommand extends AbstractPmdSubcommand { configuration.setForceLanguageVersion(forcedLangVer); } + // Setup CLI message reporter + configuration.setReporter(new SimpleMessageReporter(LoggerFactory.getLogger(PmdCommand.class))); + try { configuration.prependAuxClasspath(auxClasspath); } catch (IllegalArgumentException e) { @@ -309,93 +320,6 @@ public class PmdCommand extends AbstractPmdSubcommand { return configuration; } - public boolean isIgnoreIncrementalAnalysis() { - return noCache; - } - - public boolean isDebug() { - return debug; - } - - public String getEncoding() { - return encoding; - } - - public Integer getThreads() { - return threads; - } - - public boolean isBenchmark() { - return benchmark; - } - - public boolean isStress() { - return stress; - } - - public boolean isShortnames() { - return shortnames; - } - - public boolean isShowSuppressed() { - return showSuppressed; - } - - public String getSuppressMarker() { - return suppressMarker; - } - - public RulePriority getMinimumPriority() { - return minimumPriority; - } - - public Properties getProperties() { - return properties; - } - - public Path getReportFile() { - return reportFile; - } - - public Language getForceLanguage() { - return forceLanguage; - } - - public String getAuxClasspath() { - return auxClasspath; - } - - public List getRulesetRefs() { - return rulesets; - } - - public List getInputPaths() { - return inputPaths; - } - - public Path getFileListPath() { - return fileListPath; - } - - public Path getIgnoreListPath() { - return ignoreListPath; - } - - public String getFormat() { - return format; - } - - public boolean isFailOnViolation() { - return failOnViolation; - } - - /** - * @return the uri alternative to source directory. - */ - public URI getUri() { - return uri; - } - @Override protected ExecutionResult execute() { if ((inputPaths == null || inputPaths.isEmpty()) && uri == null && fileListPath == null) { @@ -403,20 +327,72 @@ public class PmdCommand extends AbstractPmdSubcommand { "Please provide a parameter for source root directory (--dir or -d), " + "database URI (--uri or -u), or file list path (--file-list)"); } - - System.out.println("language versions: " + languageVersion); - System.out.println("threads: " + threads); - System.out.println("priority: " + minimumPriority); - System.out.println("properties: " + properties); - System.out.println("ruleset: " + rulesets); - System.out.println("format: " + format); - System.out.println("priority: " + minimumPriority); - System.out.println("force lang ver: " + toConfiguration().getForceLanguageVersion()); - - // TODO Auto-generated method stub - return ExecutionResult.OK; + + if (benchmark) { + TimeTracker.startGlobalTracking(); + } + + final PMDConfiguration configuration = toConfiguration(); + final MessageReporter pmdReporter = configuration.getReporter(); + + try { + PmdAnalysis pmd = null; + try { + try { + pmd = PmdAnalysis.create(configuration); + } catch (final Exception e) { + pmdReporter.errorEx("Could not initialize analysis", e); + return ExecutionResult.ERROR; + } + + pmdReporter.log(Level.DEBUG, "Current classpath:\n{}", System.getProperty("java.class.path")); + final ReportStats stats = pmd.runAndReturnStats(); + if (pmdReporter.numErrors() > 0) { + // processing errors are ignored + return ExecutionResult.ERROR; + } else if (stats.getNumViolations() > 0 && configuration.isFailOnViolation()) { + return ExecutionResult.VIOLATIONS_FOUND; + } else { + return ExecutionResult.OK; + } + } finally { + if (pmd != null) { + pmd.close(); + } + } + + } catch (final Exception e) { + pmdReporter.errorEx("Exception while running PMD.", e); + printErrorDetected(pmdReporter, 1); + return ExecutionResult.ERROR; + } finally { + finishBenchmarker(pmdReporter); + } } - + + private void printErrorDetected(MessageReporter reporter, int errors) { + String msg = CliMessages.errorDetectedMessage(errors, "PMD"); + // note: using error level here increments the error count of the reporter, + // which we don't want. + reporter.info(StringUtil.quoteMessageFormat(msg)); + } + + private void finishBenchmarker(final MessageReporter pmdReporter) { + if (benchmark) { + final TimingReport timingReport = TimeTracker.stopGlobalTracking(); + + // TODO get specified report format from config + final TimingReportRenderer renderer = new TextTimingReportRenderer(); + + // Use a CloseShieldWriter to avoid closing STDERR + try (final Writer writer = new CloseShieldWriter(new OutputStreamWriter(System.err))) { + renderer.render(timingReport, writer); + } catch (final IOException e) { + pmdReporter.errorEx("Error producing benchmark report", e); + } + } + } + /** * Provider of candidates for valid report formats. */ diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdRootCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdRootCommand.java index e29e640142..ebda1ceb40 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdRootCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdRootCommand.java @@ -5,6 +5,7 @@ import net.sourceforge.pmd.cli.PmdCli.CpdPicoCli; import picocli.CommandLine.Command; import picocli.CommandLine.IVersionProvider; +// TODO : Status code 4 is actually contingent on using --fail-on-violation… we need to raise that to a common flag @Command(name = "pmd", mixinStandardHelpOptions = true, versionProvider = PMDVersionProvider.class, exitCodeListHeading = "Exit Codes:%n", From 924fa96c156444bc128728b6a5389b27070cd168 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 3 Aug 2022 00:57:41 -0300 Subject: [PATCH 072/254] Fix javadoc --- .../net/sourceforge/pmd/cli/commands/internal/PmdCommand.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java index d6058c8e0b..4bbffc25bd 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java @@ -274,10 +274,10 @@ public class PmdCommand extends AbstractPmdSubcommand { * * @return A new PMDConfiguration corresponding to these parameters * - * @throws IllegalArgumentException if the parameters are inconsistent or incomplete + * @throws ParameterException if the parameters are inconsistent or incomplete */ public PMDConfiguration toConfiguration() { - PMDConfiguration configuration = new PMDConfiguration(); + final PMDConfiguration configuration = new PMDConfiguration(); configuration.setInputPaths(inputPaths.stream().map(Path::toString).collect(Collectors.toList())); configuration.setInputFilePath(fileListPath != null ? fileListPath.toString() : null); configuration.setIgnoreFilePath(ignoreListPath != null ? ignoreListPath.toString() : null); From 844ae1fd7a757299b6d7ad34ce42b252506d50ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Tue, 9 Aug 2022 17:47:20 -0300 Subject: [PATCH 073/254] More class name changes --- .../pmd/cli/commands/internal/PmdCommand.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java index 4bbffc25bd..a5add7ede1 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java @@ -138,7 +138,7 @@ public class PmdCommand extends AbstractPmdSubcommand { @Option(names = { "--format", "-f" }, description = "Report format.%nValid values: ${COMPLETION-CANDIDATES}%n" + "Alternatively, you can provide the fully qualified name of a custom Renderer in the classpath.", - defaultValue = "text", completionCandidates = PMDSupportedReportFormatsCandidates.class) + defaultValue = "text", completionCandidates = PmdSupportedReportFormatsCandidates.class) public void setFormat(final String format) { this.format = format; } @@ -197,7 +197,7 @@ public class PmdCommand extends AbstractPmdSubcommand { @Option(names = { "--use-version" }, defaultValue = "java-latest", description = "Sepcify the language and version PMD should use.%nValid values: ${COMPLETION-CANDIDATES}%n", - completionCandidates = PMDLanguageVersionCandidates.class, converter = PMDLanguageVersionConverter.class) + completionCandidates = PmdLanguageVersionCandidates.class, converter = PmdLanguageVersionConverter.class) public void setLanguageVersion(final List languageVersion) { // Make sure we only set 1 version per language languageVersion.stream().collect(Collectors.groupingBy(LanguageVersion::getLanguage)) @@ -217,7 +217,7 @@ public class PmdCommand extends AbstractPmdSubcommand { + "When using this option, the automatic language selection by extension is disabled, and PMD " + "tries to parse all input files with the given language's parser. " + "Parsing errors are ignored.%nValid values: ${COMPLETION-CANDIDATES}%n", - completionCandidates = PMDLanguagesCandidates.class, converter = PMDLanguageConverter.class) + completionCandidates = PmdLanguageCandidates.class, converter = PmdLanguageConverter.class) public void setForceLanguage(final Language forceLanguage) { this.forceLanguage = forceLanguage; } @@ -396,7 +396,7 @@ public class PmdCommand extends AbstractPmdSubcommand { /** * Provider of candidates for valid report formats. */ - private static class PMDSupportedReportFormatsCandidates implements Iterable { + private static class PmdSupportedReportFormatsCandidates implements Iterable { @Override public Iterator iterator() { @@ -410,7 +410,7 @@ public class PmdCommand extends AbstractPmdSubcommand { * Beware, the help will report this on runtime, and be accurate to available * modules in the classpath, but autocomplete will include all at build time. */ - private static class PMDLanguagesCandidates implements Iterable { + private static class PmdLanguageCandidates implements Iterable { @Override public Iterator iterator() { @@ -423,7 +423,7 @@ public class PmdCommand extends AbstractPmdSubcommand { * * Effectively reverses the stringification done by {@code PMDLanguagesCandidates} */ - private static class PMDLanguageConverter implements ITypeConverter { + private static class PmdLanguageConverter implements ITypeConverter { @Override public Language convert(final String value) throws Exception { @@ -440,7 +440,7 @@ public class PmdCommand extends AbstractPmdSubcommand { * Beware, the help will report this on runtime, and be accurate to available * modules in the classpath, but autocomplete will include all at build time. */ - private static class PMDLanguageVersionCandidates implements Iterable { + private static class PmdLanguageVersionCandidates implements Iterable { @Override public Iterator iterator() { @@ -466,7 +466,7 @@ public class PmdCommand extends AbstractPmdSubcommand { * * Effectively reverses the stringification done by {@code PMDLanguageVersionCandidates} */ - private static class PMDLanguageVersionConverter implements ITypeConverter { + private static class PmdLanguageVersionConverter implements ITypeConverter { @Override public LanguageVersion convert(final String value) throws Exception { From ddfa945226d04a331f66b7ee7cc0074d392f4dae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Tue, 9 Aug 2022 17:47:33 -0300 Subject: [PATCH 074/254] Configure source encoding --- .../net/sourceforge/pmd/cli/commands/internal/PmdCommand.java | 1 + 1 file changed, 1 insertion(+) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java index a5add7ede1..ecc59f7528 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java @@ -284,6 +284,7 @@ public class PmdCommand extends AbstractPmdSubcommand { configuration.setInputUri(uri != null ? uri.toString() : null); configuration.setReportFormat(format); configuration.setDebug(debug); + configuration.setSourceEncoding(encoding); configuration.setMinimumPriority(minimumPriority); configuration.setReportFile(reportFile != null ? reportFile.toString() : null); configuration.setReportProperties(properties); From 9be4311a41797d895539a90725f850b3874f3d33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Tue, 9 Aug 2022 17:48:31 -0300 Subject: [PATCH 075/254] Deprecate CPD / PMD - The entry points are no more, as CLI now have a unified interface - Everyone should use PmdAnalysis anyway. For CPD, I've plans of creating a CpdAnalysis --- pmd-core/src/main/java/net/sourceforge/pmd/PMD.java | 2 ++ pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java b/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java index d526c1c45d..5320e5348c 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java @@ -46,6 +46,7 @@ import net.sourceforge.pmd.util.log.internal.SimpleMessageReporter; *

Warning: This class is not intended to be instantiated or subclassed. It will * be made final in PMD7. */ +@Deprecated public final class PMD { // not final, in order to re-initialize logging @@ -253,6 +254,7 @@ public final class PMD { * * @see #runPmd(String[]) */ + @Deprecated public enum StatusCode { /** No errors, no violations. This is exit code {@code 0}. */ OK(0), diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java index fb8c43b39d..3f3cb78167 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java @@ -32,6 +32,7 @@ import net.sourceforge.pmd.util.database.DBMSMetadata; import net.sourceforge.pmd.util.database.DBURI; import net.sourceforge.pmd.util.database.SourceObject; +@Deprecated public class CPD { // not final, in order to re-initialize logging private static Logger log = LoggerFactory.getLogger(CPD.class); @@ -267,6 +268,7 @@ public class CPD { return new CPDReport(matchAlgorithm.getMatches(), numberOfTokensPerFile); } + @Deprecated public enum StatusCode { OK(0), ERROR(1), From 9b5a9dae8e451b39cc72ee1f2641ec92402867c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Tue, 9 Aug 2022 17:50:32 -0300 Subject: [PATCH 076/254] Initial CpdCommand - lots of opportunities still to shape up the code and unify the way we invoke PMD / CPD --- .../java/net/sourceforge/pmd/cli/PmdCli.java | 30 +-- .../pmd/cli/commands/internal/CpdCommand.java | 212 ++++++++++++++++++ .../cli/commands/internal/PmdRootCommand.java | 3 +- 3 files changed, 217 insertions(+), 28 deletions(-) create mode 100644 pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/PmdCli.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/PmdCli.java index c92fa1234f..340a0229e7 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/PmdCli.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/PmdCli.java @@ -2,36 +2,14 @@ package net.sourceforge.pmd.cli; import net.sourceforge.pmd.cli.commands.internal.PmdRootCommand; import picocli.CommandLine; -import picocli.CommandLine.Command; -import picocli.CommandLine.Option; public class PmdCli { public static void main(String[] args) { new CommandLine(new PmdRootCommand()).setCaseInsensitiveEnumValuesAllowed(true) -// .execute("run", "-h"); - .execute("run", "--use-version", "scala-2.11", "--use-version", "apex", "--use-version", - "ecmascript-latest", "-P", "foo=bar", "-R", "foo,bar", "-R", "baz", "-d", - "src/main/java", "-f", "xml"); - } - - @Command(name = "cpd", mixinStandardHelpOptions = true, description = "The Copy Paste Detector") - public static class CpdPicoCli implements Runnable { - @SuppressWarnings("unused") - @Option(names = "--minimum-tokens", - description = "The minimum token length which should be reported as a duplicate.", - required = true) - private int minimumTileSize; - - @SuppressWarnings("unused") - @Option(names = "--skip-duplicate-files", - description = "Ignore multiple copies of files of the same name and length in comparison", - required = false) - private boolean skipDuplicates; - - @Override - public void run() { - System.out.println("running cpd command"); - } + .execute("cpd", "-h"); +// .execute("run", "--use-version", "scala-2.11", "--use-version", "apex", "--use-version", +// "ecmascript-latest", "-P", "foo=bar", "-R", "foo,bar", "-R", "baz", "-d", +// "src/main/java", "-f", "xml"); } } diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java new file mode 100644 index 0000000000..a7daa46106 --- /dev/null +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java @@ -0,0 +1,212 @@ +package net.sourceforge.pmd.cli.commands.internal; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.net.URI; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import net.sourceforge.pmd.cli.internal.ExecutionResult; +import net.sourceforge.pmd.cpd.CPD; +import net.sourceforge.pmd.cpd.CPDConfiguration; +import net.sourceforge.pmd.cpd.Language; +import net.sourceforge.pmd.cpd.LanguageFactory; +import net.sourceforge.pmd.cpd.Tokenizer; +import picocli.CommandLine.Command; +import picocli.CommandLine.ITypeConverter; +import picocli.CommandLine.Option; +import picocli.CommandLine.ParameterException; + +@Command(name = "cpd", showDefaultValues = true, + description = "Copy/Paste Detector - find duplicate code") +public class CpdCommand extends AbstractPmdSubcommand { + + @Option(names = "--language", description = "Source code language.%nValid values: ${COMPLETION-CANDIDATES}%n", + defaultValue = "java", converter = CpdLanguageConverter.class, completionCandidates = CpdLanguageCompletionCandidates.class) + private Language language; + + // TODO : Set a default for this value? + @Option(names = "--minimum-tokens", + description = "The minimum token length which should be reported as a duplicate.", required = true) + private int minimumTileSize; + + @Option(names = "--skip-duplicate-files", + description = "Ignore multiple copies of files of the same name and length in comparison.") + private boolean skipDuplicates; + + // TODO : Can we unify with PmdCommand but keep separate completion candidates? I think not… + @Option(names = { "--format", "-f" }, + description = "Report format.%nValid values: ${COMPLETION-CANDIDATES}%n" + + "Alternatively, you can provide the fully qualified name of a custom Renderer in the classpath.", + defaultValue = "text", completionCandidates = CpdSupportedReportFormatsCandidates.class) + private String rendererName; + + // TODO : Unify with PmdCommand + @Option(names = "--encoding", description = "Character encoding to use when processing files") + private String encoding; + + @Option(names = "--ignore-literals", + description = "Ignore literal values such as numbers and strings when comparing text.") + private boolean ignoreLiterals; + + @Option(names = "--ignore-identifiers", + description = "Ignore names of classes, methods, variables, constants, etc. when comparing text.") + private boolean ignoreIdentifiers; + + @Option(names = "--ignore-annotations", description = "Ignore language annotations when comparing text.") + private boolean ignoreAnnotations; + + @Option(names = "--ignore-usings", description = "Ignore using directives in C#") + private boolean ignoreUsings; + + @Option(names = "--ignore-literal-sequences", description = "Ignore sequences of literals such as list initializers.") + private boolean ignoreLiteralSequences; + + @Option(names = "--skip-lexical-errors", + description = "Skip files which can't be tokenized due to invalid characters, instead of aborting with an error.") + private boolean skipLexicalErrors; + + @Option(names = "--no-skip-blocks", + description = "Do not skip code blocks marked with --skip-blocks-pattern (e.g. #if 0 until #endif).") + private boolean noSkipBlocks; + + @Option(names = "--skip-blocks-pattern", + description = "Pattern to find the blocks to skip. Start and End pattern separated by |.", + defaultValue = Tokenizer.DEFAULT_SKIP_BLOCKS_PATTERN) + private String skipBlocksPattern; + + // TODO This should be --dir and -d for consistency with PMD, and unify with PmdCommand + @Option(names = "--files", arity = "1..*", description = "List of files and directories to analyze.") + private List files; + + // TODO Unify with PmdCommand + @Option(names = "--file-list", description = "Path to a file containing a list of files to analyze.") + private Path fileListPath; + + @Option(names = "--exclude", arity = "1..*", description = "Files to be excluded from the analysis") + private List excludes; + + @Option(names = "--non-recursive", description = "Don't scan subdirectiories.") + private boolean nonRecursive; + + // TODO : Improve this description + // TODO : Unify with PmdCommand + @Option(names = "--uri", description = "URI to process", required = false) + private URI uri; + + // TODO : Unify with PmdCommand + @Option(names = "--fail-on-violation", + description = "By default CPD exits with status 4 if code duplications are found. Disable this option with '-failOnViolation false' to exit with 0 instead and just write the report.") + private boolean failOnViolation = true; + + /** + * Converts these parameters into a configuration. + * + * @return A new CPDConfiguration corresponding to these parameters + * + * @throws ParameterException if the parameters are inconsistent or incomplete + */ + public CPDConfiguration toConfiguration() { + final CPDConfiguration configuration = new CPDConfiguration(); + configuration.setDebug(debug); + configuration.setExcludes(excludes); + configuration.setFailOnViolation(failOnViolation); + configuration.setFileListPath(fileListPath == null ? null : fileListPath.toString()); + configuration.setFiles(files); + configuration.setIgnoreAnnotations(ignoreAnnotations); + configuration.setIgnoreIdentifiers(ignoreIdentifiers); + configuration.setIgnoreLiterals(ignoreLiterals); + configuration.setIgnoreLiteralSequences(ignoreLiteralSequences); + configuration.setIgnoreUsings(ignoreUsings); + configuration.setLanguage(language); + configuration.setMinimumTileSize(minimumTileSize); + configuration.setNonRecursive(nonRecursive); + configuration.setNoSkipBlocks(noSkipBlocks); + configuration.setRendererName(null); + configuration.setSkipBlocksPattern(skipBlocksPattern); + configuration.setSkipDuplicates(skipDuplicates); + configuration.setSkipLexicalErrors(skipLexicalErrors); + configuration.setSourceEncoding(encoding); + configuration.setURI(uri == null ? null : uri.toString()); + + configuration.setCPDRenderer(CPDConfiguration.getCPDRendererFromString(rendererName, encoding)); + + // TODO + // Setup CLI message reporter + //configuration.setReporter(new SimpleMessageReporter(LoggerFactory.getLogger(CpdCommand.class))); + + return configuration; + } + + @Override + protected ExecutionResult execute() { + // TODO : This check should be shared with PmdCommand + if ((files == null || files.isEmpty()) && uri == null && fileListPath == null) { + throw new ParameterException(spec.commandLine(), + "Please provide a parameter for source files/directories (--files), " + + "database URI (--uri), or file list path (--file-list)"); + } + + // TODO : Create a new CpdAnalysis to match PmdAnalysis + final CPDConfiguration configuration = toConfiguration(); + final CPD cpd = new CPD(configuration); + + try { + // TODO : This should be done by CPD itself from the configuration… + //CPDCommandLineInterface.addSourceFilesToCPD(cpd, arguments); + + cpd.go(); + + configuration.getCPDRenderer().render(cpd.getMatches(), new BufferedWriter(new OutputStreamWriter(System.out))); + + if (cpd.getMatches().hasNext() && configuration.isFailOnViolation()) { + return ExecutionResult.VIOLATIONS_FOUND; + } + } catch (IOException | RuntimeException e) { + // TODO +// LOG.debug(e.toString(), e); +// LOG.error(CliMessages.errorDetectedMessage(1, CPDCommandLineInterface.PROGRAM_NAME)); + return ExecutionResult.ERROR; + } + + return ExecutionResult.OK; + } + + /** + * Provider of candidates for valid Languages. + */ + private static class CpdLanguageCompletionCandidates implements Iterable { + + @Override + public Iterator iterator() { + return Arrays.stream(LanguageFactory.supportedLanguages).iterator(); + } + } + + /** + * Maps a String back to a {@code Language}. + */ + private static class CpdLanguageConverter implements ITypeConverter { + + @Override + public Language convert(final String languageString) { + // TODO : If an unknown value is passed, AnyLanguage is returned silently… + return LanguageFactory.createLanguage(languageString); + } + } + + /** + * Provider of candidates for valid report formats. + */ + private static class CpdSupportedReportFormatsCandidates implements Iterable { + + @Override + public Iterator iterator() { + return Arrays.stream(CPDConfiguration.getRenderers()).iterator(); + } + } +} diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdRootCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdRootCommand.java index ebda1ceb40..6c5f03c346 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdRootCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdRootCommand.java @@ -1,7 +1,6 @@ package net.sourceforge.pmd.cli.commands.internal; import net.sourceforge.pmd.PMDVersion; -import net.sourceforge.pmd.cli.PmdCli.CpdPicoCli; import picocli.CommandLine.Command; import picocli.CommandLine.IVersionProvider; @@ -11,7 +10,7 @@ import picocli.CommandLine.IVersionProvider; exitCodeListHeading = "Exit Codes:%n", exitCodeList = { "0:Succesful analysis, no violations found", "1:An unexpected error occurred during execution", "2:Usage error, please refer to the command help", "4:Successful analysis, at least 1 violation found" }, - subcommands = { PmdCommand.class, CpdPicoCli.class }) + subcommands = { PmdCommand.class, CpdCommand.class }) public class PmdRootCommand { } From df4f1615f8450eece0967eb0af64ccd309e77f0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Tue, 9 Aug 2022 18:34:50 -0300 Subject: [PATCH 077/254] Unify several flags for PMD and CPD - start bringing together the way to invoke both processes --- .../java/net/sourceforge/pmd/cli/PmdCli.java | 2 +- .../AbstractAnalysisPmdSubcommand.java | 53 +++++++++++++++ .../internal/AbstractPmdSubcommand.java | 11 +++ .../pmd/cli/commands/internal/CpdCommand.java | 41 ++---------- .../pmd/cli/commands/internal/PmdCommand.java | 67 +------------------ 5 files changed, 73 insertions(+), 101 deletions(-) create mode 100644 pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractAnalysisPmdSubcommand.java diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/PmdCli.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/PmdCli.java index 340a0229e7..b19b854702 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/PmdCli.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/PmdCli.java @@ -7,7 +7,7 @@ public class PmdCli { public static void main(String[] args) { new CommandLine(new PmdRootCommand()).setCaseInsensitiveEnumValuesAllowed(true) - .execute("cpd", "-h"); + .execute("run", "-h"); // .execute("run", "--use-version", "scala-2.11", "--use-version", "apex", "--use-version", // "ecmascript-latest", "-P", "foo=bar", "-R", "foo,bar", "-R", "baz", "-d", // "src/main/java", "-f", "xml"); diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractAnalysisPmdSubcommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractAnalysisPmdSubcommand.java new file mode 100644 index 0000000000..f79268af5e --- /dev/null +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractAnalysisPmdSubcommand.java @@ -0,0 +1,53 @@ +package net.sourceforge.pmd.cli.commands.internal; + +import java.net.URI; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.util.List; + +import picocli.CommandLine.Option; +import picocli.CommandLine.ParameterException; + +public abstract class AbstractAnalysisPmdSubcommand extends AbstractPmdSubcommand { + + @Option(names = { "--encoding", "-e" }, description = "Specifies the character set encoding of the source code files", + defaultValue = "UTF-8") + protected Charset encoding; + + @Option(names = { "--dir", "-d" }, + description = "Path to a source file, or directory containing source files to analyze. " + + "Zip and Jar files are also supported, if they are specified directly " + + "(archive files found while exploring a directory are not recursively expanded). " + + "This option can be repeated, and multiple arguments can be provided to a single occurrence of the option. " + + "One of --dir, --file-list or --uri must be provided. ", + arity = "1..*") + protected List inputPaths; + + @Option(names = { "--file-list" }, + description = + "Path to a file containing a list of files to analyze, one path per line. " + + "One of --dir, --file-list or --uri must be provided.") + protected Path fileListPath; + + @Option(names = { "--uri", "-u" }, + description = "Database URI for sources. " + + "One of --dir, --file-list or --uri must be provided.") + protected URI uri; + + @Option(names = { "--fail-on-violation" }, + description = "By default PMD exits with status 4 if violations are found. " + + "Disable this option with '--fail-on-violation false' to exit with 0 instead and just write the report.", + defaultValue = "true", arity = "1") + protected boolean failOnViolation; + + @Override + protected final void validate() throws ParameterException { + super.validate(); + + if ((inputPaths == null || inputPaths.isEmpty()) && uri == null && fileListPath == null) { + throw new ParameterException(spec.commandLine(), + "Please provide a parameter for source root directory (--dir or -d), " + + "database URI (--uri or -u), or file list path (--file-list)"); + } + } +} diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractPmdSubcommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractPmdSubcommand.java index 73ab98ca9d..af938de078 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractPmdSubcommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractPmdSubcommand.java @@ -8,6 +8,7 @@ import net.sourceforge.pmd.cli.internal.ExecutionResult; import net.sourceforge.pmd.internal.Slf4jSimpleConfiguration; import picocli.CommandLine.Model.CommandSpec; import picocli.CommandLine.Option; +import picocli.CommandLine.ParameterException; import picocli.CommandLine.Spec; public abstract class AbstractPmdSubcommand implements Callable { @@ -24,9 +25,19 @@ public abstract class AbstractPmdSubcommand implements Callable { @Override public final Integer call() throws Exception { setupCliLogger(); + validate(); return execute().getExitCode(); } + /** + * Extension point to validate provided configuration. + * + * Implementations must throw {@code ParameterException} upon a violation. + * + * @throws ParameterException + */ + protected void validate() throws ParameterException { } + protected abstract ExecutionResult execute(); private void setupCliLogger() { diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java index a7daa46106..a9e7bb4c56 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java @@ -4,11 +4,11 @@ import java.io.BufferedWriter; import java.io.File; import java.io.IOException; import java.io.OutputStreamWriter; -import java.net.URI; import java.nio.file.Path; import java.util.Arrays; import java.util.Iterator; import java.util.List; +import java.util.stream.Collectors; import net.sourceforge.pmd.cli.internal.ExecutionResult; import net.sourceforge.pmd.cpd.CPD; @@ -23,7 +23,7 @@ import picocli.CommandLine.ParameterException; @Command(name = "cpd", showDefaultValues = true, description = "Copy/Paste Detector - find duplicate code") -public class CpdCommand extends AbstractPmdSubcommand { +public class CpdCommand extends AbstractAnalysisPmdSubcommand { @Option(names = "--language", description = "Source code language.%nValid values: ${COMPLETION-CANDIDATES}%n", defaultValue = "java", converter = CpdLanguageConverter.class, completionCandidates = CpdLanguageCompletionCandidates.class) @@ -41,13 +41,9 @@ public class CpdCommand extends AbstractPmdSubcommand { // TODO : Can we unify with PmdCommand but keep separate completion candidates? I think not… @Option(names = { "--format", "-f" }, description = "Report format.%nValid values: ${COMPLETION-CANDIDATES}%n" - + "Alternatively, you can provide the fully qualified name of a custom Renderer in the classpath.", + + "Alternatively, you can provide the fully qualified name of a custom CpdRenderer in the classpath.", defaultValue = "text", completionCandidates = CpdSupportedReportFormatsCandidates.class) private String rendererName; - - // TODO : Unify with PmdCommand - @Option(names = "--encoding", description = "Character encoding to use when processing files") - private String encoding; @Option(names = "--ignore-literals", description = "Ignore literal values such as numbers and strings when comparing text.") @@ -79,30 +75,12 @@ public class CpdCommand extends AbstractPmdSubcommand { defaultValue = Tokenizer.DEFAULT_SKIP_BLOCKS_PATTERN) private String skipBlocksPattern; - // TODO This should be --dir and -d for consistency with PMD, and unify with PmdCommand - @Option(names = "--files", arity = "1..*", description = "List of files and directories to analyze.") - private List files; - - // TODO Unify with PmdCommand - @Option(names = "--file-list", description = "Path to a file containing a list of files to analyze.") - private Path fileListPath; - @Option(names = "--exclude", arity = "1..*", description = "Files to be excluded from the analysis") private List excludes; @Option(names = "--non-recursive", description = "Don't scan subdirectiories.") private boolean nonRecursive; - // TODO : Improve this description - // TODO : Unify with PmdCommand - @Option(names = "--uri", description = "URI to process", required = false) - private URI uri; - - // TODO : Unify with PmdCommand - @Option(names = "--fail-on-violation", - description = "By default CPD exits with status 4 if code duplications are found. Disable this option with '-failOnViolation false' to exit with 0 instead and just write the report.") - private boolean failOnViolation = true; - /** * Converts these parameters into a configuration. * @@ -116,7 +94,7 @@ public class CpdCommand extends AbstractPmdSubcommand { configuration.setExcludes(excludes); configuration.setFailOnViolation(failOnViolation); configuration.setFileListPath(fileListPath == null ? null : fileListPath.toString()); - configuration.setFiles(files); + configuration.setFiles(inputPaths == null ? null : inputPaths.stream().map(Path::toFile).collect(Collectors.toList())); configuration.setIgnoreAnnotations(ignoreAnnotations); configuration.setIgnoreIdentifiers(ignoreIdentifiers); configuration.setIgnoreLiterals(ignoreLiterals); @@ -130,10 +108,10 @@ public class CpdCommand extends AbstractPmdSubcommand { configuration.setSkipBlocksPattern(skipBlocksPattern); configuration.setSkipDuplicates(skipDuplicates); configuration.setSkipLexicalErrors(skipLexicalErrors); - configuration.setSourceEncoding(encoding); + configuration.setSourceEncoding(encoding.name()); configuration.setURI(uri == null ? null : uri.toString()); - configuration.setCPDRenderer(CPDConfiguration.getCPDRendererFromString(rendererName, encoding)); + configuration.setCPDRenderer(CPDConfiguration.getCPDRendererFromString(rendererName, encoding.name())); // TODO // Setup CLI message reporter @@ -144,13 +122,6 @@ public class CpdCommand extends AbstractPmdSubcommand { @Override protected ExecutionResult execute() { - // TODO : This check should be shared with PmdCommand - if ((files == null || files.isEmpty()) && uri == null && fileListPath == null) { - throw new ParameterException(spec.commandLine(), - "Please provide a parameter for source files/directories (--files), " - + "database URI (--uri), or file list path (--file-list)"); - } - // TODO : Create a new CpdAnalysis to match PmdAnalysis final CPDConfiguration configuration = toConfiguration(); final CPD cpd = new CPD(configuration); diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java index ecc59f7528..dd10562da5 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java @@ -3,7 +3,6 @@ package net.sourceforge.pmd.cli.commands.internal; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; -import java.net.URI; import java.nio.file.Path; import java.util.Iterator; import java.util.List; @@ -42,23 +41,14 @@ import picocli.CommandLine.TypeConversionException; @Command(name = "analyze", aliases = {"analyse", "run" }, showDefaultValues = true, description = "The PMD standard source code analyzer") -public class PmdCommand extends AbstractPmdSubcommand { +public class PmdCommand extends AbstractAnalysisPmdSubcommand { private List rulesets; - private URI uri; - - private List inputPaths; - - private Path fileListPath; - private Path ignoreListPath; private String format; - // TODO : Actually use an Charset instance? - private String encoding; - private int threads; private boolean benchmark; @@ -81,8 +71,6 @@ public class PmdCommand extends AbstractPmdSubcommand { private String auxClasspath; - private boolean failOnViolation; - private boolean noRuleSetCompatibility; private Path cacheLocation; @@ -98,36 +86,6 @@ public class PmdCommand extends AbstractPmdSubcommand { this.rulesets = rulesets; } - @Option(names = { "--uri", "-u" }, - description = "Database URI for sources. " - + "One of --dir, --file-list or --uri must be provided.") - public void setUri(final URI uri) { - this.uri = uri; - } - - @Option(names = { "--dir", "-d" }, - description = "Path to a source file, or directory containing source files to analyze. " - // About the following line: - // In PMD 6, this is only the case for files found in directories. If you - // specify a file directly, and it is unknown, then the Java parser is used. - + "Note that a file is only effectively added if it matches a language known by PMD. " - + "Zip and Jar files are also supported, if they are specified directly " - + "(archive files found while exploring a directory are not recursively expanded). " - + "This option can be repeated, and multiple arguments can be provided to a single occurrence of the option. " - + "One of --dir, --file-list or --uri must be provided. ", - arity = "1..*") - public void setInputPaths(final List inputPaths) { - this.inputPaths = inputPaths; - } - - @Option(names = { "--file-list" }, - description = - "Path to a file containing a list of files to analyze, one path per line. " - + "One of --dir, --file-list or --uri must be provided.") - public void setFileListPath(final Path fileListPath) { - this.fileListPath = fileListPath; - } - @Option(names = { "--ignore-list" }, description = "Path to a file containing a list of files to exclude from the analysis, one path per line. " + "This option can be combined with --dir and --file-list.") @@ -143,13 +101,6 @@ public class PmdCommand extends AbstractPmdSubcommand { this.format = format; } - @Option(names = { "--encoding", "-e" }, - description = "Specifies the character set encoding of the source code files PMD is reading (i.e., UTF-8).", - defaultValue = "UTF-8") - public void setEncoding(final String encoding) { - this.encoding = encoding; - } - @Option(names = { "--benchmark", "-b" }, description = "Benchmark mode - output a benchmark report upon completion; default to System.err.") public void setBenchmark(final boolean benchmark) { @@ -232,13 +183,6 @@ public class PmdCommand extends AbstractPmdSubcommand { this.auxClasspath = auxClasspath; } - @Option(names = { "--fail-on-violation" }, - description = "By default PMD exits with status 4 if violations are found. Disable this option with '-failOnViolation false' to exit with 0 instead and just write the report.", - defaultValue = "true") - public void setFailOnViolation(final boolean failOnViolation) { - this.failOnViolation = failOnViolation; - } - @Option(names = { "--no-ruleset-compatibility" }, description = "Disable the ruleset compatibility filter. The filter is active by default and tries automatically 'fix' old ruleset files with old rule names") public void setNoRuleSetCompatibility(final boolean noRuleSetCompatibility) { @@ -284,7 +228,7 @@ public class PmdCommand extends AbstractPmdSubcommand { configuration.setInputUri(uri != null ? uri.toString() : null); configuration.setReportFormat(format); configuration.setDebug(debug); - configuration.setSourceEncoding(encoding); + configuration.setSourceEncoding(encoding.name()); configuration.setMinimumPriority(minimumPriority); configuration.setReportFile(reportFile != null ? reportFile.toString() : null); configuration.setReportProperties(properties); @@ -292,7 +236,6 @@ public class PmdCommand extends AbstractPmdSubcommand { configuration.setRuleSets(rulesets); configuration.setRuleSetFactoryCompatibilityEnabled(!this.noRuleSetCompatibility); configuration.setShowSuppressedViolations(showSuppressed); - configuration.setSourceEncoding(encoding); configuration.setSuppressMarker(suppressMarker); configuration.setThreads(threads); configuration.setFailOnViolation(failOnViolation); @@ -323,12 +266,6 @@ public class PmdCommand extends AbstractPmdSubcommand { @Override protected ExecutionResult execute() { - if ((inputPaths == null || inputPaths.isEmpty()) && uri == null && fileListPath == null) { - throw new ParameterException(spec.commandLine(), - "Please provide a parameter for source root directory (--dir or -d), " - + "database URI (--uri or -u), or file list path (--file-list)"); - } - if (benchmark) { TimeTracker.startGlobalTracking(); } From 83418404c5c828f097e71fff01f8c216126d8eab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Tue, 9 Aug 2022 19:06:18 -0300 Subject: [PATCH 078/254] Draft DesignerCommand to identify gaps --- .../java/net/sourceforge/pmd/cli/PmdCli.java | 2 +- .../internal/AbstractPmdSubcommand.java | 2 +- .../commands/internal/DesignerCommand.java | 71 +++++++++++++++++++ .../cli/commands/internal/PmdRootCommand.java | 3 +- 4 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/DesignerCommand.java diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/PmdCli.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/PmdCli.java index b19b854702..fec55153fa 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/PmdCli.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/PmdCli.java @@ -7,7 +7,7 @@ public class PmdCli { public static void main(String[] args) { new CommandLine(new PmdRootCommand()).setCaseInsensitiveEnumValuesAllowed(true) - .execute("run", "-h"); + .execute("designer", "--version"); // .execute("run", "--use-version", "scala-2.11", "--use-version", "apex", "--use-version", // "ecmascript-latest", "-P", "foo=bar", "-R", "foo,bar", "-R", "baz", "-d", // "src/main/java", "-f", "xml"); diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractPmdSubcommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractPmdSubcommand.java index af938de078..6e33803a03 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractPmdSubcommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractPmdSubcommand.java @@ -19,7 +19,7 @@ public abstract class AbstractPmdSubcommand implements Callable { @Option(names = { "-h", "--help" }, usageHelp = true, description = "Show this help message and exit.") protected boolean helpRequested; - @Option(names = { "--debug", "--verbose", "-D", "-V" }, description = "Debug mode.") + @Option(names = { "--debug", "--verbose", "-D", "-v" }, description = "Debug mode.") protected boolean debug; @Override diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/DesignerCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/DesignerCommand.java new file mode 100644 index 0000000000..8d45c9c495 --- /dev/null +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/DesignerCommand.java @@ -0,0 +1,71 @@ +package net.sourceforge.pmd.cli.commands.internal; + +import javax.swing.JOptionPane; + +import net.sourceforge.pmd.cli.internal.ExecutionResult; +import net.sourceforge.pmd.util.fxdesigner.Designer; +import net.sourceforge.pmd.util.fxdesigner.DesignerStarter; +import picocli.CommandLine.Command; +import picocli.CommandLine.IVersionProvider; +import picocli.CommandLine.Option; + +@Command(name = "designer", showDefaultValues = true, + versionProvider = DesignerVersionProvider.class, + description = "The PMD visual rule designer") +public class DesignerCommand extends AbstractPmdSubcommand { + + @Option(names = {"-V", "--version"}, versionHelp = true, description = "Print version information and exit.") + private boolean versionRequested; + + // TODO : Until a Designer version is released making DesignerStarter.launchGui() public we need to copy these… + // launchGui should probably be changed to take no arguments, and return an int with the exit status code + private static final String MISSING_JAVAFX = + "You seem to be missing the JavaFX runtime." + System.lineSeparator() + + " Please install JavaFX on your system and try again." + System.lineSeparator() + + " See https://gluonhq.com/products/javafx/"; + + private static final int ERROR_EXIT = 1; + private static final int OK = 0; + + private static boolean isJavaFxAvailable() { + try { + DesignerStarter.class.getClassLoader().loadClass("javafx.application.Application"); + return true; + } catch (ClassNotFoundException | LinkageError e) { + return false; + } + } + + private static int launchGui() { + String message = null; + if (!isJavaFxAvailable()) { + message = MISSING_JAVAFX; + } + + if (message != null) { + System.err.println(message); + JOptionPane.showMessageDialog(null, message); + return ERROR_EXIT; + } + + // TODO : add JavaFX to manually launch app… + //Application.launch(Designer.class, args); + return OK; + } + + @Override + protected ExecutionResult execute() { + final int status = launchGui(); + return status == OK ? ExecutionResult.OK : ExecutionResult.ERROR; + } +} + +class DesignerVersionProvider implements IVersionProvider { + + // TODO : Since getCurrentVersion is within Designer, we can't ask for the version without JavaFX or we will face a NoClassDefFoundError + @Override + public String[] getVersion() throws Exception { + return new String[] { "PMD Rule Designer " + Designer.getCurrentVersion() }; + } + +} \ No newline at end of file diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdRootCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdRootCommand.java index 6c5f03c346..aabf9f16a6 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdRootCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdRootCommand.java @@ -4,13 +4,12 @@ import net.sourceforge.pmd.PMDVersion; import picocli.CommandLine.Command; import picocli.CommandLine.IVersionProvider; -// TODO : Status code 4 is actually contingent on using --fail-on-violation… we need to raise that to a common flag @Command(name = "pmd", mixinStandardHelpOptions = true, versionProvider = PMDVersionProvider.class, exitCodeListHeading = "Exit Codes:%n", exitCodeList = { "0:Succesful analysis, no violations found", "1:An unexpected error occurred during execution", "2:Usage error, please refer to the command help", "4:Successful analysis, at least 1 violation found" }, - subcommands = { PmdCommand.class, CpdCommand.class }) + subcommands = { PmdCommand.class, CpdCommand.class, DesignerCommand.class }) public class PmdRootCommand { } From f2a2eee71bdae0ff76ee7bc2939f14c5e9295a08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 10 Aug 2022 19:30:40 -0300 Subject: [PATCH 079/254] Update DesignerCommand using enhanced pmd-ui - this depends on https://github.com/pmd/pmd-designer/pull/52 being merged into master and then master merged onto compat-7.0 --- .../commands/internal/DesignerCommand.java | 45 +++---------------- 1 file changed, 5 insertions(+), 40 deletions(-) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/DesignerCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/DesignerCommand.java index 8d45c9c495..21b3d4482d 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/DesignerCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/DesignerCommand.java @@ -1,10 +1,8 @@ package net.sourceforge.pmd.cli.commands.internal; -import javax.swing.JOptionPane; - import net.sourceforge.pmd.cli.internal.ExecutionResult; -import net.sourceforge.pmd.util.fxdesigner.Designer; import net.sourceforge.pmd.util.fxdesigner.DesignerStarter; +import net.sourceforge.pmd.util.fxdesigner.DesignerVersion; import picocli.CommandLine.Command; import picocli.CommandLine.IVersionProvider; import picocli.CommandLine.Option; @@ -17,55 +15,22 @@ public class DesignerCommand extends AbstractPmdSubcommand { @Option(names = {"-V", "--version"}, versionHelp = true, description = "Print version information and exit.") private boolean versionRequested; - // TODO : Until a Designer version is released making DesignerStarter.launchGui() public we need to copy these… - // launchGui should probably be changed to take no arguments, and return an int with the exit status code - private static final String MISSING_JAVAFX = - "You seem to be missing the JavaFX runtime." + System.lineSeparator() - + " Please install JavaFX on your system and try again." + System.lineSeparator() - + " See https://gluonhq.com/products/javafx/"; - - private static final int ERROR_EXIT = 1; private static final int OK = 0; - private static boolean isJavaFxAvailable() { - try { - DesignerStarter.class.getClassLoader().loadClass("javafx.application.Application"); - return true; - } catch (ClassNotFoundException | LinkageError e) { - return false; - } - } - - private static int launchGui() { - String message = null; - if (!isJavaFxAvailable()) { - message = MISSING_JAVAFX; - } - - if (message != null) { - System.err.println(message); - JOptionPane.showMessageDialog(null, message); - return ERROR_EXIT; - } - - // TODO : add JavaFX to manually launch app… - //Application.launch(Designer.class, args); - return OK; - } - @Override protected ExecutionResult execute() { - final int status = launchGui(); + final String[] rawArgs = spec.commandLine().getParseResult().expandedArgs().toArray(new String[0]); + final int status = DesignerStarter.launchGui(rawArgs); + return status == OK ? ExecutionResult.OK : ExecutionResult.ERROR; } } class DesignerVersionProvider implements IVersionProvider { - // TODO : Since getCurrentVersion is within Designer, we can't ask for the version without JavaFX or we will face a NoClassDefFoundError @Override public String[] getVersion() throws Exception { - return new String[] { "PMD Rule Designer " + Designer.getCurrentVersion() }; + return new String[] { "PMD Rule Designer " + DesignerVersion.getCurrentVersion() }; } } \ No newline at end of file From aceec768bc0041758a2f3ae9c6d0239a15680b2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 10 Aug 2022 19:32:37 -0300 Subject: [PATCH 080/254] Use proper import, no shaded classes --- .../net/sourceforge/pmd/cli/commands/internal/PmdCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java index dd10562da5..27fe33595b 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java @@ -12,7 +12,7 @@ import java.util.TreeSet; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.shaded.apache.commons.io.output.CloseShieldWriter; +import org.apache.commons.io.output.CloseShieldWriter; import org.slf4j.LoggerFactory; import org.slf4j.event.Level; From 7f98592467007701d344aec7c560ba1a112bc723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Thu, 11 Aug 2022 15:07:12 -0300 Subject: [PATCH 081/254] Accomodate DesignerSubcommand to pmd-ui --- .../pmd/cli/commands/internal/DesignerCommand.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/DesignerCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/DesignerCommand.java index 21b3d4482d..a19ecf7f71 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/DesignerCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/DesignerCommand.java @@ -2,6 +2,7 @@ package net.sourceforge.pmd.cli.commands.internal; import net.sourceforge.pmd.cli.internal.ExecutionResult; import net.sourceforge.pmd.util.fxdesigner.DesignerStarter; +import net.sourceforge.pmd.util.fxdesigner.DesignerStarter.ExitStatus; import net.sourceforge.pmd.util.fxdesigner.DesignerVersion; import picocli.CommandLine.Command; import picocli.CommandLine.IVersionProvider; @@ -15,14 +16,12 @@ public class DesignerCommand extends AbstractPmdSubcommand { @Option(names = {"-V", "--version"}, versionHelp = true, description = "Print version information and exit.") private boolean versionRequested; - private static final int OK = 0; - @Override protected ExecutionResult execute() { final String[] rawArgs = spec.commandLine().getParseResult().expandedArgs().toArray(new String[0]); - final int status = DesignerStarter.launchGui(rawArgs); + final ExitStatus status = DesignerStarter.launchGui(rawArgs); - return status == OK ? ExecutionResult.OK : ExecutionResult.ERROR; + return status == ExitStatus.OK ? ExecutionResult.OK : ExecutionResult.ERROR; } } From 774f58f657fbfbbc3e39b3e4843266c08c01ab43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Tue, 16 Aug 2022 19:00:16 -0300 Subject: [PATCH 082/254] Make CPD easier to invoke MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - We should probably introduce a CpdAnalysis and get rid of CPD altogether… --- .../java/net/sourceforge/pmd/cpd/CPD.java | 73 +++++++++++++++- .../pmd/cpd/CPDCommandLineInterface.java | 83 +++---------------- 2 files changed, 81 insertions(+), 75 deletions(-) diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java index 3f3cb78167..bd6e121dc1 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java @@ -7,7 +7,9 @@ package net.sourceforge.pmd.cpd; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.net.URISyntaxException; import java.nio.charset.Charset; +import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -17,6 +19,7 @@ import java.util.Map; import java.util.Set; import java.util.TreeMap; +import org.checkerframework.checker.nullness.qual.NonNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.event.Level; @@ -27,6 +30,7 @@ import net.sourceforge.pmd.cpd.renderer.CPDReportRenderer; import net.sourceforge.pmd.internal.Slf4jSimpleConfiguration; import net.sourceforge.pmd.lang.ast.TokenMgrError; import net.sourceforge.pmd.util.FileFinder; +import net.sourceforge.pmd.util.FileUtil; import net.sourceforge.pmd.util.IOUtil; import net.sourceforge.pmd.util.database.DBMSMetadata; import net.sourceforge.pmd.util.database.DBURI; @@ -52,6 +56,71 @@ public class CPD { // before we start any tokenizing (add(File...)), we need to reset the // static TokenEntry status TokenEntry.clearImages(); + + // Add all sources + extractAllSources(); + } + + private void extractAllSources() { + // Add files + if (null != configuration.getFiles() && !configuration.getFiles().isEmpty()) { + addSourcesFilesToCPD(configuration.getFiles()); + } + + // Add Database URIS + if (null != configuration.getURI() && !"".equals(configuration.getURI())) { + addSourceURIToCPD(configuration.getURI()); + } + + if (null != configuration.getFileListPath() && !"".equals(configuration.getFileListPath())) { + addFilesFromFilelist(configuration.getFileListPath()); + } + } + + private void addSourcesFilesToCPD(List files) { + try { + for (File file : files) { + if (!file.exists()) { + throw new FileNotFoundException("Couldn't find directory/file '" + file + "'"); + } else if (file.isDirectory()) { + if (configuration.isNonRecursive()) { + addAllInDirectory(file); + } else { + addRecursively(file); + } + } else { + add(file); + } + } + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + private void addFilesFromFilelist(String inputFilePath) { + List files = new ArrayList<>(); + try { + Path file = FileUtil.toExistingPath(inputFilePath); + for (String param : FileUtil.readFilelistEntries(file)) { + @NonNull Path fileToAdd = FileUtil.toExistingPath(param); + files.add(fileToAdd.toFile()); + } + addSourcesFilesToCPD(files); + } catch (IOException ex) { + throw new IllegalStateException(ex); + } + } + + private void addSourceURIToCPD(String uri) { + try { + log.debug("Attempting DBURI={}", uri); + DBURI dburi = new DBURI(uri); + log.debug("Initialised DBURI={}", dburi); + log.debug("Adding DBURI={} with DBType={}", dburi, dburi.getDbType()); + add(dburi); + } catch (IOException | URISyntaxException e) { + throw new IllegalStateException("uri=" + uri, e); + } } public void setCpdListener(CPDListener cpdListener) { @@ -120,7 +189,7 @@ public class CPD { add(sourceCode); } - public void add(DBURI dburi) throws IOException { + public void add(DBURI dburi) { try { DBMSMetadata dbmsmetadata = new DBMSMetadata(dburi); @@ -236,8 +305,6 @@ public class CPD { CPD cpd = new CPD(arguments); try { - CPDCommandLineInterface.addSourceFilesToCPD(cpd, arguments); - cpd.go(); final CPDReportRenderer renderer = arguments.getCPDReportRenderer(); if (renderer == null) { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDCommandLineInterface.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDCommandLineInterface.java index 919d02cb45..d05eac13e4 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDCommandLineInterface.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDCommandLineInterface.java @@ -4,34 +4,24 @@ package net.sourceforge.pmd.cpd; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.net.URISyntaxException; -import java.nio.file.Path; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import java.util.Map.Entry; -import org.checkerframework.checker.nullness.qual.NonNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.beust.jcommander.JCommander; +import com.beust.jcommander.ParameterException; + import net.sourceforge.pmd.PMD; import net.sourceforge.pmd.PMDVersion; import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.cli.internal.CliMessages; import net.sourceforge.pmd.cpd.CPD.StatusCode; -import net.sourceforge.pmd.util.FileUtil; -import net.sourceforge.pmd.util.database.DBURI; - -import com.beust.jcommander.JCommander; -import com.beust.jcommander.ParameterException; /** * @deprecated Internal API. Use {@link CPD#runCpd(String...)} or {@link CPD#main(String[])} @@ -141,66 +131,15 @@ public final class CPDCommandLineInterface { SUGGESTED_REPLACEMENT = Collections.unmodifiableMap(m); } + /** + * {@code CPD} now takes the sources from the {@code CPDConfiguration} itslef, + * this method is now an noop and will be removed. + * + * @deprecated This method is now a noop and will be removed, CPD does this itself. + */ + @Deprecated public static void addSourceFilesToCPD(CPD cpd, CPDConfiguration arguments) { - // Add files - if (null != arguments.getFiles() && !arguments.getFiles().isEmpty()) { - addSourcesFilesToCPD(arguments.getFiles(), cpd, !arguments.isNonRecursive()); - } - - // Add Database URIS - if (null != arguments.getURI() && !"".equals(arguments.getURI())) { - addSourceURIToCPD(arguments.getURI(), cpd); - } - - if (null != arguments.getFileListPath() && !"".equals(arguments.getFileListPath())) { - addFilesFromFilelist(arguments.getFileListPath(), cpd, !arguments.isNonRecursive()); - } - } - - private static void addSourcesFilesToCPD(List files, CPD cpd, boolean recursive) { - try { - for (File file : files) { - if (!file.exists()) { - throw new FileNotFoundException("Couldn't find directory/file '" + file + "'"); - } else if (file.isDirectory()) { - if (recursive) { - cpd.addRecursively(file); - } else { - cpd.addAllInDirectory(file); - } - } else { - cpd.add(file); - } - } - } catch (IOException e) { - throw new IllegalStateException(e); - } - } - - private static void addFilesFromFilelist(String inputFilePath, CPD cpd, boolean recursive) { - List files = new ArrayList<>(); - try { - Path file = FileUtil.toExistingPath(inputFilePath); - for (String param : FileUtil.readFilelistEntries(file)) { - @NonNull Path fileToAdd = FileUtil.toExistingPath(param); - files.add(fileToAdd.toFile()); - } - addSourcesFilesToCPD(files, cpd, recursive); - } catch (IOException ex) { - throw new IllegalStateException(ex); - } - } - - private static void addSourceURIToCPD(String uri, CPD cpd) { - try { - LOG.debug("Attempting DBURI={}", uri); - DBURI dburi = new DBURI(uri); - LOG.debug("Initialised DBURI={}", dburi); - LOG.debug("Adding DBURI={} with DBType={}", dburi, dburi.getDbType()); - cpd.add(dburi); - } catch (IOException | URISyntaxException e) { - throw new IllegalStateException("uri=" + uri, e); - } + // noop } @Deprecated From ae7ce6b1cb072412a46599d78eb83390c85f5e5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Tue, 16 Aug 2022 22:53:20 -0300 Subject: [PATCH 083/254] Remove usage of CloseShieldWriter - commons-io was a test dependency, not worth including it for a single use of a single class --- .../sourceforge/pmd/cli/commands/internal/PmdCommand.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java index 27fe33595b..1cbe8f9377 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java @@ -12,7 +12,6 @@ import java.util.TreeSet; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.apache.commons.io.output.CloseShieldWriter; import org.slf4j.LoggerFactory; import org.slf4j.event.Level; @@ -322,8 +321,9 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { // TODO get specified report format from config final TimingReportRenderer renderer = new TextTimingReportRenderer(); - // Use a CloseShieldWriter to avoid closing STDERR - try (final Writer writer = new CloseShieldWriter(new OutputStreamWriter(System.err))) { + try { + // No try-with-resources, do not want to close STDERR + final Writer writer = new OutputStreamWriter(System.err); renderer.render(timingReport, writer); } catch (final IOException e) { pmdReporter.errorEx("Error producing benchmark report", e); From ad3aea173f97e7114a2125fb43991043c94bf8b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Tue, 16 Aug 2022 22:59:52 -0300 Subject: [PATCH 084/254] PMD fixes --- .../pmd/cli/commands/internal/AbstractPmdSubcommand.java | 4 +++- .../pmd/cli/commands/internal/DesignerCommand.java | 1 + .../net/sourceforge/pmd/cli/commands/internal/PmdCommand.java | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractPmdSubcommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractPmdSubcommand.java index 6e33803a03..9cdf45315a 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractPmdSubcommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractPmdSubcommand.java @@ -36,7 +36,9 @@ public abstract class AbstractPmdSubcommand implements Callable { * * @throws ParameterException */ - protected void validate() throws ParameterException { } + protected void validate() throws ParameterException { + // no-op, children may override + } protected abstract ExecutionResult execute(); diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/DesignerCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/DesignerCommand.java index a19ecf7f71..d8c66b75a2 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/DesignerCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/DesignerCommand.java @@ -13,6 +13,7 @@ import picocli.CommandLine.Option; description = "The PMD visual rule designer") public class DesignerCommand extends AbstractPmdSubcommand { + @SuppressWarnings("unused") @Option(names = {"-V", "--version"}, versionHelp = true, description = "Print version information and exit.") private boolean versionRequested; diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java index 1cbe8f9377..535d64c6eb 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java @@ -323,6 +323,7 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { try { // No try-with-resources, do not want to close STDERR + @SuppressWarnings("PMD.CloseResource") final Writer writer = new OutputStreamWriter(System.err); renderer.render(timingReport, writer); } catch (final IOException e) { From 2c6cd6413c9db14a5228f893a6de5534bb8fa3f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Tue, 16 Aug 2022 23:16:31 -0300 Subject: [PATCH 085/254] Checkstyle fixes --- pmd-cli/pom.xml | 1 - .../java/net/sourceforge/pmd/cli/PmdCli.java | 17 +++++--- .../AbstractAnalysisPmdSubcommand.java | 8 +++- .../internal/AbstractPmdSubcommand.java | 5 +++ .../pmd/cli/commands/internal/CpdCommand.java | 9 +++- .../commands/internal/DesignerCommand.java | 7 +++- .../pmd/cli/commands/internal/PmdCommand.java | 41 +++++++++++-------- .../cli/commands/internal/PmdRootCommand.java | 9 +++- .../pmd/cli/internal/ExecutionResult.java | 4 ++ 9 files changed, 70 insertions(+), 31 deletions(-) diff --git a/pmd-cli/pom.xml b/pmd-cli/pom.xml index 9ccf08f90a..d8edbdc297 100644 --- a/pmd-cli/pom.xml +++ b/pmd-cli/pom.xml @@ -21,7 +21,6 @@ - org.codehaus.mojo exec-maven-plugin diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/PmdCli.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/PmdCli.java index fec55153fa..59370bed38 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/PmdCli.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/PmdCli.java @@ -1,15 +1,22 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + package net.sourceforge.pmd.cli; import net.sourceforge.pmd.cli.commands.internal.PmdRootCommand; + import picocli.CommandLine; -public class PmdCli { +public final class PmdCli { + + private PmdCli() { } public static void main(String[] args) { new CommandLine(new PmdRootCommand()).setCaseInsensitiveEnumValuesAllowed(true) - .execute("designer", "--version"); -// .execute("run", "--use-version", "scala-2.11", "--use-version", "apex", "--use-version", -// "ecmascript-latest", "-P", "foo=bar", "-R", "foo,bar", "-R", "baz", "-d", -// "src/main/java", "-f", "xml"); + .execute("designer", "-v"); + // .execute("run", "--use-version", "scala-2.11", "--use-version", "apex", "--use-version", + // "ecmascript-latest", "-P", "foo=bar", "-R", "foo,bar", "-R", "baz", "-d", + // "src/main/java", "-f", "xml"); } } diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractAnalysisPmdSubcommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractAnalysisPmdSubcommand.java index f79268af5e..2b413d1e9c 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractAnalysisPmdSubcommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractAnalysisPmdSubcommand.java @@ -1,3 +1,7 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + package net.sourceforge.pmd.cli.commands.internal; import java.net.URI; @@ -23,7 +27,7 @@ public abstract class AbstractAnalysisPmdSubcommand extends AbstractPmdSubcomman arity = "1..*") protected List inputPaths; - @Option(names = { "--file-list" }, + @Option(names = "--file-list", description = "Path to a file containing a list of files to analyze, one path per line. " + "One of --dir, --file-list or --uri must be provided.") @@ -34,7 +38,7 @@ public abstract class AbstractAnalysisPmdSubcommand extends AbstractPmdSubcomman + "One of --dir, --file-list or --uri must be provided.") protected URI uri; - @Option(names = { "--fail-on-violation" }, + @Option(names = "--fail-on-violation", description = "By default PMD exits with status 4 if violations are found. " + "Disable this option with '--fail-on-violation false' to exit with 0 instead and just write the report.", defaultValue = "true", arity = "1") diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractPmdSubcommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractPmdSubcommand.java index 9cdf45315a..9adb9a040b 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractPmdSubcommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractPmdSubcommand.java @@ -1,3 +1,7 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + package net.sourceforge.pmd.cli.commands.internal; import java.util.concurrent.Callable; @@ -6,6 +10,7 @@ import org.slf4j.event.Level; import net.sourceforge.pmd.cli.internal.ExecutionResult; import net.sourceforge.pmd.internal.Slf4jSimpleConfiguration; + import picocli.CommandLine.Model.CommandSpec; import picocli.CommandLine.Option; import picocli.CommandLine.ParameterException; diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java index a9e7bb4c56..667f66a5d8 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java @@ -1,3 +1,7 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + package net.sourceforge.pmd.cli.commands.internal; import java.io.BufferedWriter; @@ -16,6 +20,7 @@ import net.sourceforge.pmd.cpd.CPDConfiguration; import net.sourceforge.pmd.cpd.Language; import net.sourceforge.pmd.cpd.LanguageFactory; import net.sourceforge.pmd.cpd.Tokenizer; + import picocli.CommandLine.Command; import picocli.CommandLine.ITypeConverter; import picocli.CommandLine.Option; @@ -139,8 +144,8 @@ public class CpdCommand extends AbstractAnalysisPmdSubcommand { } } catch (IOException | RuntimeException e) { // TODO -// LOG.debug(e.toString(), e); -// LOG.error(CliMessages.errorDetectedMessage(1, CPDCommandLineInterface.PROGRAM_NAME)); + //LOG.debug(e.toString(), e); + //LOG.error(CliMessages.errorDetectedMessage(1, CPDCommandLineInterface.PROGRAM_NAME)); return ExecutionResult.ERROR; } diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/DesignerCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/DesignerCommand.java index d8c66b75a2..68aabc4b37 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/DesignerCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/DesignerCommand.java @@ -1,9 +1,14 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + package net.sourceforge.pmd.cli.commands.internal; import net.sourceforge.pmd.cli.internal.ExecutionResult; import net.sourceforge.pmd.util.fxdesigner.DesignerStarter; import net.sourceforge.pmd.util.fxdesigner.DesignerStarter.ExitStatus; import net.sourceforge.pmd.util.fxdesigner.DesignerVersion; + import picocli.CommandLine.Command; import picocli.CommandLine.IVersionProvider; import picocli.CommandLine.Option; @@ -33,4 +38,4 @@ class DesignerVersionProvider implements IVersionProvider { return new String[] { "PMD Rule Designer " + DesignerVersion.getCurrentVersion() }; } -} \ No newline at end of file +} diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java index 535d64c6eb..2e7b312ec9 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java @@ -1,3 +1,7 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + package net.sourceforge.pmd.cli.commands.internal; import java.io.IOException; @@ -32,6 +36,7 @@ import net.sourceforge.pmd.reporting.ReportStats; import net.sourceforge.pmd.util.StringUtil; import net.sourceforge.pmd.util.log.MessageReporter; import net.sourceforge.pmd.util.log.internal.SimpleMessageReporter; + import picocli.CommandLine.Command; import picocli.CommandLine.ITypeConverter; import picocli.CommandLine.Option; @@ -85,7 +90,7 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { this.rulesets = rulesets; } - @Option(names = { "--ignore-list" }, + @Option(names = "--ignore-list", description = "Path to a file containing a list of files to exclude from the analysis, one path per line. " + "This option can be combined with --dir and --file-list.") public void setIgnoreListPath(final Path ignoreListPath) { @@ -106,24 +111,24 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { this.benchmark = benchmark; } - @Option(names = { "--short-names" }, description = "Prints shortened filenames in the report.") + @Option(names = "--short-names", description = "Prints shortened filenames in the report.") public void setShortnames(final boolean shortnames) { this.shortnames = shortnames; } - @Option(names = { "--show-suppressed" }, description = "Report should show suppressed rule violations.") + @Option(names = "--show-suppressed", description = "Report should show suppressed rule violations.") public void setShowSuppressed(final boolean showSuppressed) { this.showSuppressed = showSuppressed; } - @Option(names = { "--suppress-marker" }, + @Option(names = "--suppress-marker", description = "Specifies the string that marks a line which PMD should ignore.", defaultValue = "NOPMD") public void setSuppressMarker(final String suppressMarker) { this.suppressMarker = suppressMarker; } - @Option(names = { "--minimum-priority" }, + @Option(names = "--minimum-priority", description = "Rule priority threshold; rules with lower priority than configured here won't be used.%n" + "Valid values (case insensitive): ${COMPLETION-CANDIDATES}", defaultValue = "Low") @@ -145,24 +150,24 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { this.reportFile = reportFile; } - @Option(names = { "--use-version" }, defaultValue = "java-latest", + @Option(names = "--use-version", defaultValue = "java-latest", description = "Sepcify the language and version PMD should use.%nValid values: ${COMPLETION-CANDIDATES}%n", completionCandidates = PmdLanguageVersionCandidates.class, converter = PmdLanguageVersionConverter.class) public void setLanguageVersion(final List languageVersion) { // Make sure we only set 1 version per language languageVersion.stream().collect(Collectors.groupingBy(LanguageVersion::getLanguage)) - .forEach((l, list) -> { - if (list.size() > 1) { - throw new ParameterException(spec.commandLine(), "Can only set one version per language, " - + "but for language " + l.getName() + " multiple versions were provided " - + list.stream().map(PmdCommand::normalizeName).collect(Collectors.joining("', '", "'", "'"))); - } - }); + .forEach((l, list) -> { + if (list.size() > 1) { + throw new ParameterException(spec.commandLine(), "Can only set one version per language, " + + "but for language " + l.getName() + " multiple versions were provided " + + list.stream().map(PmdCommand::normalizeName).collect(Collectors.joining("', '", "'", "'"))); + } + }); this.languageVersion = languageVersion; } - @Option(names = { "--force-language" }, + @Option(names = "--force-language", description = "Force a language to be used for all input files, irrespective of file names. " + "When using this option, the automatic language selection by extension is disabled, and PMD " + "tries to parse all input files with the given language's parser. " @@ -172,7 +177,7 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { this.forceLanguage = forceLanguage; } - @Option(names = { "--aux-classpath" }, + @Option(names = "--aux-classpath", description = "Specifies the classpath for libraries used by the source code. " + "This is used to resolve types in Java source files. The platform specific path delimiter " + "(\":\" on Linux, \";\" on Windows) is used to separate the entries. " @@ -182,13 +187,13 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { this.auxClasspath = auxClasspath; } - @Option(names = { "--no-ruleset-compatibility" }, + @Option(names = "--no-ruleset-compatibility", description = "Disable the ruleset compatibility filter. The filter is active by default and tries automatically 'fix' old ruleset files with old rule names") public void setNoRuleSetCompatibility(final boolean noRuleSetCompatibility) { this.noRuleSetCompatibility = noRuleSetCompatibility; } - @Option(names = { "--cache" }, + @Option(names = "--cache", description = "Specify the location of the cache file for incremental analysis. " + "This should be the full path to the file, including the desired file name (not just the parent directory). " + "If the file doesn't exist, it will be created on the first run. The file will be overwritten on each run " @@ -197,7 +202,7 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { this.cacheLocation = cacheLocation; } - @Option(names = { "--no-cache" }, description = "Explicitly disable incremental analysis. The '-cache' option is ignored if this switch is present in the command line.") + @Option(names = "--no-cache", description = "Explicitly disable incremental analysis. The '-cache' option is ignored if this switch is present in the command line.") public void setNoCache(final boolean noCache) { this.noCache = noCache; } diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdRootCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdRootCommand.java index aabf9f16a6..18c7c53c12 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdRootCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdRootCommand.java @@ -1,6 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + package net.sourceforge.pmd.cli.commands.internal; import net.sourceforge.pmd.PMDVersion; + import picocli.CommandLine.Command; import picocli.CommandLine.IVersionProvider; @@ -8,7 +13,7 @@ import picocli.CommandLine.IVersionProvider; versionProvider = PMDVersionProvider.class, exitCodeListHeading = "Exit Codes:%n", exitCodeList = { "0:Succesful analysis, no violations found", "1:An unexpected error occurred during execution", - "2:Usage error, please refer to the command help", "4:Successful analysis, at least 1 violation found" }, + "2:Usage error, please refer to the command help", "4:Successful analysis, at least 1 violation found" }, subcommands = { PmdCommand.class, CpdCommand.class, DesignerCommand.class }) public class PmdRootCommand { @@ -20,4 +25,4 @@ class PMDVersionProvider implements IVersionProvider { public String[] getVersion() throws Exception { return new String[] { "PMD " + PMDVersion.VERSION }; } -} \ No newline at end of file +} diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/internal/ExecutionResult.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/internal/ExecutionResult.java index f0880b8ace..3c813c9bdb 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/internal/ExecutionResult.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/internal/ExecutionResult.java @@ -1,3 +1,7 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + package net.sourceforge.pmd.cli.internal; // TODO : Unify with PMD.StatusCode / CPD.StatusCode From 1d8c8cbe1760c4ba19d2f68ec522b53b5bf1706e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Tue, 16 Aug 2022 23:53:27 -0300 Subject: [PATCH 086/254] Add Checkstyle suppressions --- pmd-cli/pmd-cli-checkstyle-suppressions.xml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 pmd-cli/pmd-cli-checkstyle-suppressions.xml diff --git a/pmd-cli/pmd-cli-checkstyle-suppressions.xml b/pmd-cli/pmd-cli-checkstyle-suppressions.xml new file mode 100644 index 0000000000..03b468029b --- /dev/null +++ b/pmd-cli/pmd-cli-checkstyle-suppressions.xml @@ -0,0 +1,9 @@ + + + + + + + From 4ae3e993b95ee5d5683278923dc775d8ee5f620e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Tue, 16 Aug 2022 23:54:17 -0300 Subject: [PATCH 087/254] Add CPD GUI command --- .../cli/commands/internal/CpdGuiCommand.java | 20 +++++++++++++++++++ .../cli/commands/internal/PmdRootCommand.java | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdGuiCommand.java diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdGuiCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdGuiCommand.java new file mode 100644 index 0000000000..f6da7bc988 --- /dev/null +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdGuiCommand.java @@ -0,0 +1,20 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cli.commands.internal; + +import net.sourceforge.pmd.cpd.GUI; + +import picocli.CommandLine.Command; + +@Command(name = "cpd-gui", + description = "GUI for the Copy/Paste Detector%n Warning: May not support the full CPD feature set") +public class CpdGuiCommand implements Runnable { + + @Override + public void run() { + new GUI(); + } + +} diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdRootCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdRootCommand.java index 18c7c53c12..b21c7b72cc 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdRootCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdRootCommand.java @@ -14,7 +14,7 @@ import picocli.CommandLine.IVersionProvider; exitCodeListHeading = "Exit Codes:%n", exitCodeList = { "0:Succesful analysis, no violations found", "1:An unexpected error occurred during execution", "2:Usage error, please refer to the command help", "4:Successful analysis, at least 1 violation found" }, - subcommands = { PmdCommand.class, CpdCommand.class, DesignerCommand.class }) + subcommands = { PmdCommand.class, CpdCommand.class, DesignerCommand.class, CpdGuiCommand.class }) public class PmdRootCommand { } From 7da46685329584408e1db847852685339a362a4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 17 Aug 2022 01:01:03 -0300 Subject: [PATCH 088/254] Fix message format --- pmd-core/src/main/java/net/sourceforge/pmd/PmdAnalysis.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/PmdAnalysis.java b/pmd-core/src/main/java/net/sourceforge/pmd/PmdAnalysis.java index e58a53d6d2..9486c223ec 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/PmdAnalysis.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/PmdAnalysis.java @@ -449,7 +449,7 @@ public final class PmdAnalysis implements AutoCloseable { final String version = PMDVersion.isUnknown() || PMDVersion.isSnapshot() ? "latest" : "pmd-" + PMDVersion.VERSION; reporter.warn("This analysis could be faster, please consider using Incremental Analysis: " - + "https://pmd.github.io/{}/pmd_userdocs_incremental_analysis.html", version); + + "https://pmd.github.io/{1}/pmd_userdocs_incremental_analysis.html", version); } } } From a0dcf384bf9de1f9d2d0223b1ba3ac591353719a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 17 Aug 2022 10:50:24 -0300 Subject: [PATCH 089/254] Add report properties to help message --- .../pmd/cli/commands/internal/PmdCommand.java | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java index 2e7b312ec9..ad0d7a710a 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java @@ -31,6 +31,8 @@ import net.sourceforge.pmd.cli.internal.ExecutionResult; import net.sourceforge.pmd.lang.Language; import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.LanguageVersion; +import net.sourceforge.pmd.properties.PropertyDescriptor; +import net.sourceforge.pmd.renderers.Renderer; import net.sourceforge.pmd.renderers.RendererFactory; import net.sourceforge.pmd.reporting.ReportStats; import net.sourceforge.pmd.util.StringUtil; @@ -47,6 +49,31 @@ import picocli.CommandLine.TypeConversionException; description = "The PMD standard source code analyzer") public class PmdCommand extends AbstractAnalysisPmdSubcommand { + static { + final Properties emptyProps = new Properties(); + final StringBuilder reportPropertiesHelp = new StringBuilder(); + + for (final String rendererName : RendererFactory.supportedRenderers()) { + final Renderer renderer = RendererFactory.createRenderer(rendererName, emptyProps); + + if (renderer.getPropertyDescriptors().size() > 0) { + reportPropertiesHelp.append(rendererName + ":" + System.getProperty("line.separator")); + for (final PropertyDescriptor property : renderer.getPropertyDescriptors()) { + reportPropertiesHelp.append(" ").append(property.name()).append(" - ") + .append(property.description()).append(System.getProperty("line.separator")); + final Object deflt = property.defaultValue(); + if (deflt != null && !"".equals(deflt)) { + reportPropertiesHelp.append(" Default: ").append(deflt) + .append(System.getProperty("line.separator")); + } + } + } + } + + // System Properties are the easier way to inject dynamically computed values into the help of an option + System.setProperty("pmd-cli.pmd.report.properties.help", reportPropertiesHelp.toString()); + } + private List rulesets; private Path ignoreListPath; @@ -136,8 +163,9 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { this.minimumPriority = priority; } - // TODO : Figure out how to surface the supported properties for each report format - @Option(names = { "--property", "-P" }, description = "Key-value pair defining a property for the report format.") + // TODO : Autocomplete candidates? + @Option(names = { "--property", "-P" }, description = "Key-value pair defining a property for the report format.%n" + + "Supported values for each report format:%n${sys:pmd-cli.pmd.report.properties.help}") public void setProperties(final Properties properties) { this.properties = properties; } From 6d35c9189ec79dc663fffdf7b7a9ad2d23fa3a3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 17 Aug 2022 10:51:49 -0300 Subject: [PATCH 090/254] Property descriptions should not include defaults - The default is properly modeled and is up to the consumer to choose where / when to render it fopr consistency --- .../main/java/net/sourceforge/pmd/renderers/HTMLRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/HTMLRenderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/HTMLRenderer.java index aaba3b065b..ac15a315e7 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/HTMLRenderer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/HTMLRenderer.java @@ -40,7 +40,7 @@ public class HTMLRenderer extends AbstractIncrementingRenderer { public static final PropertyDescriptor HTML_EXTENSION = PropertyFactory.booleanProperty("htmlExtension") - .desc("Replace file extension with .html for the links (default: false)") + .desc("Replace file extension with .html for the links.") // default value is false - to have the old (pre 6.23.0) behavior, this needs to be set to true. .defaultValue(false) .build(); From af40487933e5cdfeec14aa356541dd384905ad00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 17 Aug 2022 11:09:04 -0300 Subject: [PATCH 091/254] Add missing --no-progress flag --- .../pmd/cli/commands/internal/PmdCommand.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java index ad0d7a710a..59f114dadf 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java @@ -108,6 +108,8 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { private boolean noCache; + private boolean noProgressBar; + @Option(names = { "--rulesets", "-R" }, description = "Path to a ruleset xml file. " + "The path may reference a resource on the classpath of the application, be a local file system path, or a URL. " @@ -154,7 +156,7 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { public void setSuppressMarker(final String suppressMarker) { this.suppressMarker = suppressMarker; } - + @Option(names = "--minimum-priority", description = "Rule priority threshold; rules with lower priority than configured here won't be used.%n" + "Valid values (case insensitive): ${COMPLETION-CANDIDATES}", @@ -234,7 +236,7 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { public void setNoCache(final boolean noCache) { this.noCache = noCache; } - + @Option(names = { "--threads", "-t" }, description = "Sets the number of threads used by PMD.", defaultValue = "1") public void setThreads(final int threads) { @@ -245,6 +247,11 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { this.threads = threads; } + @Option(names = "--no-progress", description = "Disables progress bar indicator of live analysis progress.") + public void setNoProgressBar(final boolean noProgressBar) { + this.noProgressBar = noProgressBar; + } + /** * Converts these parameters into a configuration. * @@ -273,6 +280,7 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { configuration.setFailOnViolation(failOnViolation); configuration.setAnalysisCacheLocation(cacheLocation != null ? cacheLocation.toString() : null); configuration.setIgnoreIncrementalAnalysis(noCache); + configuration.setProgressBar(!noProgressBar); if (languageVersion != null) { configuration.setDefaultLanguageVersions(languageVersion); From ccf6adfcd66afbfb0ab157047f98dc3d8e22c81d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 17 Aug 2022 11:20:00 -0300 Subject: [PATCH 092/254] Remove fixed TODO --- .../net/sourceforge/pmd/cli/commands/internal/CpdCommand.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java index 667f66a5d8..40f42e6b2a 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java @@ -132,9 +132,6 @@ public class CpdCommand extends AbstractAnalysisPmdSubcommand { final CPD cpd = new CPD(configuration); try { - // TODO : This should be done by CPD itself from the configuration… - //CPDCommandLineInterface.addSourceFilesToCPD(cpd, arguments); - cpd.go(); configuration.getCPDRenderer().render(cpd.getMatches(), new BufferedWriter(new OutputStreamWriter(System.out))); From 52d2616181a63328b40655c4477a0c774e8da7d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 17 Aug 2022 14:47:31 -0300 Subject: [PATCH 093/254] Add basic CLI test --- pmd-cli/pom.xml | 11 + .../net/sourceforge/pmd/cli/PmdCliTest.java | 251 ++++++++++++++++++ 2 files changed, 262 insertions(+) create mode 100644 pmd-cli/src/test/java/net/sourceforge/pmd/cli/PmdCliTest.java diff --git a/pmd-cli/pom.xml b/pmd-cli/pom.xml index d8edbdc297..2a9b53f3c1 100644 --- a/pmd-cli/pom.xml +++ b/pmd-cli/pom.xml @@ -284,6 +284,17 @@ system-lambda test + + + + net.sourceforge.pmd + pmd-core + ${project.version} + test + tests + test-jar + + me.tongfei diff --git a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/PmdCliTest.java b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/PmdCliTest.java new file mode 100644 index 0000000000..143d1eb7dd --- /dev/null +++ b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/PmdCliTest.java @@ -0,0 +1,251 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cli; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.containsStringIgnoringCase; +import static org.hamcrest.Matchers.not; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import net.sourceforge.pmd.cli.internal.ExecutionResult; +import net.sourceforge.pmd.internal.Slf4jSimpleConfiguration; + +import com.github.stefanbirkner.systemlambda.SystemLambda; + +class PmdCliTest { + + @TempDir + private Path tempDir; + + private static final String DUMMY_RULESET = "net/sourceforge/pmd/cli/FakeRuleset.xml"; + private static final String STRING_TO_REPLACE = "__should_be_replaced__"; + + private Path srcDir; + + @AfterAll + static void resetLogging() { + // reset logging in case "--debug" changed the logging properties + // See also Slf4jSimpleConfigurationForAnt + Slf4jSimpleConfiguration.reconfigureDefaultLogLevel(null); + } + + @BeforeEach + void setup() throws IOException { + // set current directory to wd + Path root = tempRoot(); + System.setProperty("user.dir", root.toString()); + + // create a few files + srcDir = Files.createDirectories(root.resolve("src")); + writeString(srcDir.resolve("someSource.dummy"), "dummy text"); + } + + + @Test + void testPreExistingReportFile() throws Exception { + Path reportFile = tempRoot().resolve("out/reportFile.txt"); + // now we create the file + Files.createDirectories(reportFile.getParent()); + writeString(reportFile, STRING_TO_REPLACE); + + assertTrue(Files.exists(reportFile), "Report file should exist"); + + runPmdSuccessfully("-d", srcDir.toString(), "-R", DUMMY_RULESET, "-r", reportFile.toString()); + + assertNotEquals(readString(reportFile), STRING_TO_REPLACE); + } + + @Test + void testPreExistingReportFileLongOption() throws Exception { + Path reportFile = tempRoot().resolve("out/reportFile.txt"); + // now we create the file + Files.createDirectories(reportFile.getParent()); + writeString(reportFile, STRING_TO_REPLACE); + + assertTrue(Files.exists(reportFile), "Report file should exist"); + + runPmdSuccessfully("--dir", srcDir.toString(), "--rulesets", DUMMY_RULESET, "--report-file", reportFile.toString()); + + assertNotEquals(readString(reportFile), STRING_TO_REPLACE, "Report file should have been overwritten"); + } + + @Test + void testNonExistentReportFile() throws Exception { + Path reportFile = tempRoot().resolve("out/reportFile.txt"); + + assertFalse(Files.exists(reportFile), "Report file should not exist"); + + try { + runPmdSuccessfully("-d", srcDir.toString(), "-R", DUMMY_RULESET, "-r", reportFile.toString()); + assertTrue(Files.exists(reportFile), "Report file should have been created"); + } finally { + Files.deleteIfExists(reportFile); + } + } + + @Test + void testNonExistentReportFileLongOption() throws Exception { + Path reportFile = tempRoot().resolve("out/reportFile.txt"); + + assertFalse(Files.exists(reportFile), "Report file should not exist"); + + runPmdSuccessfully("--dir", srcDir.toString(), "--rulesets", DUMMY_RULESET, "--report-file", reportFile.toString()); + + assertTrue(Files.exists(reportFile), "Report file should have been created"); + } + + @Test + void testFileCollectionWithUnknownFiles() throws Exception { + Path reportFile = tempRoot().resolve("out/reportFile.txt"); + Files.createFile(srcDir.resolve("foo.not_analysable")); + assertFalse(Files.exists(reportFile), "Report file should not exist"); + + // restoring system properties: --debug might change logging properties + SystemLambda.restoreSystemProperties(() -> { + runPmdSuccessfully("--dir", srcDir.toString(), "--rulesets", DUMMY_RULESET, "--report-file", reportFile.toString(), "--debug"); + }); + + assertTrue(Files.exists(reportFile), "Report file should have been created"); + String reportText = readString(reportFile); + assertThat(reportText, not(containsStringIgnoringCase("error"))); + } + + /** + * This tests to create the report file in the current working directory. + * + *

Note: We can't change the cwd in the running VM, so the file will not be created + * in the temporary folder, but really in the cwd. The test fails if a file already exists + * and makes sure to cleanup the file afterwards. + */ + @Test + void testRelativeReportFile() throws Exception { + String reportFile = "reportFile.txt"; + Path absoluteReportFile = FileSystems.getDefault().getPath(reportFile).toAbsolutePath(); + // verify the file doesn't exist yet - we will delete the file at the end! + assertFalse(Files.exists(absoluteReportFile), "Report file must not exist yet! " + absoluteReportFile); + + try { + runPmdSuccessfully("-d", srcDir.toString(), "-R", DUMMY_RULESET, "-r", reportFile.toString()); + assertTrue(Files.exists(absoluteReportFile), "Report file should have been created"); + } finally { + Files.deleteIfExists(absoluteReportFile); + } + } + + @Test + void testRelativeReportFileLongOption() throws Exception { + String reportFile = "reportFile.txt"; + Path absoluteReportFile = FileSystems.getDefault().getPath(reportFile).toAbsolutePath(); + // verify the file doesn't exist yet - we will delete the file at the end! + assertFalse(Files.exists(absoluteReportFile), "Report file must not exist yet!"); + + try { + runPmdSuccessfully("--dir", srcDir.toString(), "--rulesets", DUMMY_RULESET, "--report-file", reportFile.toString()); + assertTrue(Files.exists(absoluteReportFile), "Report file should have been created"); + } finally { + Files.deleteIfExists(absoluteReportFile); + } + } + + @Test + void debugLogging() throws Exception { + // restoring system properties: --debug might change logging properties + SystemLambda.restoreSystemProperties(() -> { + String log = runPmdSuccessfully("--debug", "--dir", srcDir.toString(), "--rulesets", DUMMY_RULESET); + assertThat(log, containsString("[main] INFO net.sourceforge.pmd.cli.commands.internal.AbstractPmdSubcommand - Log level is at TRACE")); + }); + } + + @Test + void defaultLogging() throws Exception { + String log = runPmdSuccessfully("--dir", srcDir.toString(), "--rulesets", DUMMY_RULESET); + assertThat(log, containsString("[main] INFO net.sourceforge.pmd.cli.commands.internal.AbstractPmdSubcommand - Log level is at INFO")); + } + + @Test + void testDeprecatedRulesetSyntaxOnCommandLine() throws Exception { + String log = SystemLambda.tapSystemErrAndOut(() -> { + runPmd(ExecutionResult.VIOLATIONS_FOUND, "--dir", srcDir.toString(), "--rulesets", "dummy-basic"); + }); + assertThat(log, containsString("Ruleset reference 'dummy-basic' uses a deprecated form, use 'rulesets/dummy/basic.xml' instead")); + } + + + @Test + void testWrongCliOptionsDoNotPrintUsage() throws Exception { + final String log = SystemLambda.tapSystemErrAndOut(() -> { + final int actualExitCode = SystemLambda.catchSystemExit(() -> { + PmdCli.main(new String[] { "run", "--invalid" }); + }); + assertEquals(2, actualExitCode); + }); + assertThat(log, not(containsStringIgnoringCase("Available report formats and"))); + } + + // utilities + private Path tempRoot() { + return tempDir; + } + + + private static String runPmdSuccessfully(String... args) throws Exception { + return SystemLambda.tapSystemErrAndOut(() -> { + runPmd(ExecutionResult.OK, args); + }); + } + + // available in Files on java 11+ + private static void writeString(Path path, String text) throws IOException { + ByteBuffer encoded = StandardCharsets.UTF_8.encode(text); + Files.write(path, encoded.array()); + } + + + // available in Files on java 11+ + private static String readString(Path path) throws IOException { + byte[] bytes = Files.readAllBytes(path); + ByteBuffer buf = ByteBuffer.wrap(bytes); + return StandardCharsets.UTF_8.decode(buf).toString(); + } + + private static void runPmd(ExecutionResult expectedExitCode, String... args) throws Exception { + final int actualExitCode = SystemLambda.catchSystemExit(() -> { + final List argList = new ArrayList<>(); + + // Always run against dummy language without logging not cache to remove all logging noise + argList.add("run"); + argList.add("--use-version"); + argList.add("dummy"); + argList.add("--no-cache"); + argList.add("--no-progress"); + + argList.addAll(Arrays.asList(args)); + + PmdCli.main(argList.toArray(new String[0])); + }); + assertEquals(expectedExitCode.getExitCode(), actualExitCode, "Exit code"); + } + + +} From 57701e679c2a06b3900cfff429d4d81192ca580c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 17 Aug 2022 14:48:18 -0300 Subject: [PATCH 094/254] Make tests successful --- pmd-cli/src/main/java/net/sourceforge/pmd/cli/PmdCli.java | 7 +++++-- .../pmd/cli/commands/internal/AbstractPmdSubcommand.java | 5 +++++ .../sourceforge/pmd/cli/commands/internal/PmdCommand.java | 2 +- .../src/main/java/net/sourceforge/pmd/PmdAnalysis.java | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/PmdCli.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/PmdCli.java index 59370bed38..f77aaf5588 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/PmdCli.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/PmdCli.java @@ -13,10 +13,13 @@ public final class PmdCli { private PmdCli() { } public static void main(String[] args) { - new CommandLine(new PmdRootCommand()).setCaseInsensitiveEnumValuesAllowed(true) - .execute("designer", "-v"); + final int exitCode = new CommandLine(new PmdRootCommand()) + .setCaseInsensitiveEnumValuesAllowed(true) + .execute(args); + // .execute("run", "-h"); // .execute("run", "--use-version", "scala-2.11", "--use-version", "apex", "--use-version", // "ecmascript-latest", "-P", "foo=bar", "-R", "foo,bar", "-R", "baz", "-d", // "src/main/java", "-f", "xml"); + System.exit(exitCode); } } diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractPmdSubcommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractPmdSubcommand.java index 9adb9a040b..f3385db824 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractPmdSubcommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractPmdSubcommand.java @@ -6,6 +6,7 @@ package net.sourceforge.pmd.cli.commands.internal; import java.util.concurrent.Callable; +import org.slf4j.LoggerFactory; import org.slf4j.event.Level; import net.sourceforge.pmd.cli.internal.ExecutionResult; @@ -56,5 +57,9 @@ public abstract class AbstractPmdSubcommand implements Callable { // always install java.util.logging to slf4j bridge Slf4jSimpleConfiguration.installJulBridge(); + + // logging, mostly for testing purposes + Level defaultLogLevel = Slf4jSimpleConfiguration.getDefaultLogLevel(); + LoggerFactory.getLogger(AbstractPmdSubcommand.class).info("Log level is at {}", defaultLogLevel); } } diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java index 59f114dadf..2d677b427d 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java @@ -323,7 +323,7 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { return ExecutionResult.ERROR; } - pmdReporter.log(Level.DEBUG, "Current classpath:\n{}", System.getProperty("java.class.path")); + pmdReporter.log(Level.DEBUG, "Current classpath:\n{0}", System.getProperty("java.class.path")); final ReportStats stats = pmd.runAndReturnStats(); if (pmdReporter.numErrors() > 0) { // processing errors are ignored diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/PmdAnalysis.java b/pmd-core/src/main/java/net/sourceforge/pmd/PmdAnalysis.java index 9486c223ec..a4375770e2 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/PmdAnalysis.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/PmdAnalysis.java @@ -449,7 +449,7 @@ public final class PmdAnalysis implements AutoCloseable { final String version = PMDVersion.isUnknown() || PMDVersion.isSnapshot() ? "latest" : "pmd-" + PMDVersion.VERSION; reporter.warn("This analysis could be faster, please consider using Incremental Analysis: " - + "https://pmd.github.io/{1}/pmd_userdocs_incremental_analysis.html", version); + + "https://pmd.github.io/{0}/pmd_userdocs_incremental_analysis.html", version); } } } From a1359755a8c39772a8c3819ecf6c1889c164c1c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 17 Aug 2022 15:09:35 -0300 Subject: [PATCH 095/254] Parse test for PmdCommand --- .../cli/commands/internal/PmdCommandTest.java | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/PmdCommandTest.java diff --git a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/PmdCommandTest.java b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/PmdCommandTest.java new file mode 100644 index 0000000000..8621f352d4 --- /dev/null +++ b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/PmdCommandTest.java @@ -0,0 +1,110 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cli.commands.internal; + +import static net.sourceforge.pmd.util.CollectionUtil.listOf; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import net.sourceforge.pmd.PMDConfiguration; +import net.sourceforge.pmd.cli.PmdParametersParseResult; + +import picocli.CommandLine; +import picocli.CommandLine.ParseResult; + +class PmdCommandTest { + +// @Test +// void testVersion() throws Exception { +// PMDParameters parameters = new PMDParameters(); +// // no language set, uses default language +// assertEquals("1.7", parameters.getVersion()); +// +// // now set language +// FieldUtils.writeDeclaredField(parameters, "language", "dummy2", true); +// assertEquals("1.0", parameters.getVersion()); +// } + + @Test + void testMultipleDirsAndRuleSets() { + final PmdCommand cmd = setupAndParse( + "-d", "a", "b", "-R", "x.xml", "y.xml" + ); + assertMultipleDirsAndRulesets(cmd); + } + + @Test + void testMultipleDirsAndRuleSetsWithCommas() { + final PmdCommand cmd = setupAndParse( + "-d", "a,b", "-R", "x.xml,y.xml" + ); + assertMultipleDirsAndRulesets(cmd); + } + + @Test + void testMultipleDirsAndRuleSetsWithRepeatedOption() { + final PmdCommand cmd = setupAndParse( + "-d", "a", "-d", "b", "-R", "x.xml", "-R", "y.xml" + ); + assertMultipleDirsAndRulesets(cmd); + } + + @Test + void testNoPositionalParametersAllowed() { + assertError( + // vvvv + "-R", "x.xml", "-d", "a", "--", "-d", "b" + ); + } + + + private void assertMultipleDirsAndRulesets(final PmdCommand result) { + final PMDConfiguration config = result.toConfiguration(); + assertEquals(listOf("a", "b"), config.getAllInputPaths()); + assertEquals(listOf("x.xml", "y.xml"), config.getRuleSetPaths()); + } + + @Test + void testEmptyDirOption() { + assertError("-d", "-R", "y.xml"); + } + + @Test + void testEmptyRulesetOption() { + assertError("-R", "-d", "something"); + } + + private void assertError(String... params) { + // TODO + PmdParametersParseResult result = PmdParametersParseResult.extractParameters(params); + assertTrue(result.isError()); + } + + private PmdCommand setupAndParse(final String... params) { + final PmdCommand cmd = new PmdCommand(); + + // Always run against dummy language + final List argList = new ArrayList<>(); + argList.add("--use-version"); + argList.add("dummy"); + argList.addAll(Arrays.asList(params)); + + ParseResult parseResult = new CommandLine(cmd) + .setCaseInsensitiveEnumValuesAllowed(true) + .parseArgs(argList.toArray(new String[0])); + + assertThat(parseResult.errors(), Matchers.empty()); + + return cmd; + } +} From 5651d772d8d30466f0cff8425cc10b99361cfaa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 17 Aug 2022 15:10:05 -0300 Subject: [PATCH 096/254] Improve compatibility with PMD 6 CLI --- .../cli/commands/internal/AbstractAnalysisPmdSubcommand.java | 2 +- .../net/sourceforge/pmd/cli/commands/internal/PmdCommand.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractAnalysisPmdSubcommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractAnalysisPmdSubcommand.java index 2b413d1e9c..c42dc45a01 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractAnalysisPmdSubcommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractAnalysisPmdSubcommand.java @@ -24,7 +24,7 @@ public abstract class AbstractAnalysisPmdSubcommand extends AbstractPmdSubcomman + "(archive files found while exploring a directory are not recursively expanded). " + "This option can be repeated, and multiple arguments can be provided to a single occurrence of the option. " + "One of --dir, --file-list or --uri must be provided. ", - arity = "1..*") + arity = "1..*", split = ",") protected List inputPaths; @Option(names = "--file-list", diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java index 2d677b427d..1cec851066 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java @@ -114,7 +114,7 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { description = "Path to a ruleset xml file. " + "The path may reference a resource on the classpath of the application, be a local file system path, or a URL. " + "The option can be repeated, and multiple arguments separated by comma can be provided to a single occurrence of the option.", - required = true, split = ",") + required = true, split = ",", arity = "1..*") public void setRulesets(final List rulesets) { this.rulesets = rulesets; } From 15a654e2a6439b222fef7264871f0a8dd90d151b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 17 Aug 2022 15:22:34 -0300 Subject: [PATCH 097/254] Finalize unit test of PmdCommand --- .../cli/commands/internal/PmdCommandTest.java | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/PmdCommandTest.java b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/PmdCommandTest.java index 8621f352d4..bbe0f0d768 100644 --- a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/PmdCommandTest.java +++ b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/PmdCommandTest.java @@ -7,7 +7,6 @@ package net.sourceforge.pmd.cli.commands.internal; import static net.sourceforge.pmd.util.CollectionUtil.listOf; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.ArrayList; import java.util.Arrays; @@ -17,7 +16,6 @@ import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; import net.sourceforge.pmd.PMDConfiguration; -import net.sourceforge.pmd.cli.PmdParametersParseResult; import picocli.CommandLine; import picocli.CommandLine.ParseResult; @@ -84,27 +82,34 @@ class PmdCommandTest { assertError("-R", "-d", "something"); } - private void assertError(String... params) { - // TODO - PmdParametersParseResult result = PmdParametersParseResult.extractParameters(params); - assertTrue(result.isError()); + private void assertError(final String... params) { + final PmdCommand cmd = new PmdCommand(); + final ParseResult parseResult = parseCommand(cmd, params); + assertThat(parseResult.errors(), Matchers.not(Matchers.empty())); } private PmdCommand setupAndParse(final String... params) { final PmdCommand cmd = new PmdCommand(); - - // Always run against dummy language - final List argList = new ArrayList<>(); - argList.add("--use-version"); - argList.add("dummy"); - argList.addAll(Arrays.asList(params)); - - ParseResult parseResult = new CommandLine(cmd) - .setCaseInsensitiveEnumValuesAllowed(true) - .parseArgs(argList.toArray(new String[0])); + final ParseResult parseResult = parseCommand(cmd, params); assertThat(parseResult.errors(), Matchers.empty()); return cmd; } + + private ParseResult parseCommand(final Object cmd, final String... params) { + // Always run against dummy language + final List argList = new ArrayList<>(); + argList.add("--use-version"); + argList.add("dummy"); + argList.addAll(Arrays.asList(params)); + + final CommandLine commandLine = new CommandLine(cmd) + .setCaseInsensitiveEnumValuesAllowed(true); + + // Collect errors instead of simply throwing during parsing + commandLine.getCommandSpec().parser().collectErrors(true); + + return commandLine.parseArgs(argList.toArray(new String[0])); + } } From 8e0473771547e00b4142d0afe03391efbcf220dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 17 Aug 2022 19:14:52 -0300 Subject: [PATCH 098/254] Add --use-version tests --- .../cli/commands/internal/PmdCommandTest.java | 59 +++++++++++++------ 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/PmdCommandTest.java b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/PmdCommandTest.java index bbe0f0d768..a751a12e9c 100644 --- a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/PmdCommandTest.java +++ b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/PmdCommandTest.java @@ -16,22 +16,40 @@ import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; import net.sourceforge.pmd.PMDConfiguration; +import net.sourceforge.pmd.lang.DummyLanguageModule; +import net.sourceforge.pmd.lang.LanguageVersion; import picocli.CommandLine; import picocli.CommandLine.ParseResult; class PmdCommandTest { -// @Test -// void testVersion() throws Exception { -// PMDParameters parameters = new PMDParameters(); -// // no language set, uses default language -// assertEquals("1.7", parameters.getVersion()); -// -// // now set language -// FieldUtils.writeDeclaredField(parameters, "language", "dummy2", true); -// assertEquals("1.0", parameters.getVersion()); -// } + @Test + void testVersionDefault() throws Exception { + final PmdCommand cmd = setupAndParse("--use-version", "dummy", "-d", "a", "-R", "x.xml"); + final LanguageVersion dummyLatest = cmd.toConfiguration().getLanguageVersionOfFile("foo.dummy"); + + // LanguageVersion do not implement equals, but we can check their string representations + assertEquals(DummyLanguageModule.getInstance().getDefaultVersion().toString(), dummyLatest.toString()); + } + + @Test + void testVersionLatest() throws Exception { + final PmdCommand cmd = setupAndParse("--use-version", "dummy-latest", "-d", "a", "-R", "x.xml"); + final LanguageVersion dummyLatest = cmd.toConfiguration().getLanguageVersionOfFile("foo.dummy"); + + // LanguageVersion do not implement equals, but we can check their string representations + assertEquals(DummyLanguageModule.getInstance().getDefaultVersion().toString(), dummyLatest.toString()); + } + + @Test + void testVersionGiven() throws Exception { + final PmdCommand cmd = setupAndParse("--use-version", "dummy-1.2", "-d", "a", "-R", "x.xml"); + final LanguageVersion dummyLatest = cmd.toConfiguration().getLanguageVersionOfFile("foo.dummy"); + + // LanguageVersion do not implement equals, but we can check their string representations + assertEquals(DummyLanguageModule.getInstance().getVersion("1.2").toString(), dummyLatest.toString()); + } @Test void testMultipleDirsAndRuleSets() { @@ -98,18 +116,21 @@ class PmdCommandTest { } private ParseResult parseCommand(final Object cmd, final String... params) { - // Always run against dummy language final List argList = new ArrayList<>(); - argList.add("--use-version"); - argList.add("dummy"); + + // If no language provided, set dummy latest + if (!Arrays.stream(params).anyMatch(s -> s.equals("--use-version"))) { + argList.add("--use-version"); + argList.add("dummy"); + } argList.addAll(Arrays.asList(params)); - final CommandLine commandLine = new CommandLine(cmd) + final CommandLine commandLine = new CommandLine(cmd) .setCaseInsensitiveEnumValuesAllowed(true); - - // Collect errors instead of simply throwing during parsing - commandLine.getCommandSpec().parser().collectErrors(true); - - return commandLine.parseArgs(argList.toArray(new String[0])); + + // Collect errors instead of simply throwing during parsing + commandLine.getCommandSpec().parser().collectErrors(true); + + return commandLine.parseArgs(argList.toArray(new String[0])); } } From 5d7e2473f46fe9895692026749ddfcde51ecf8d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 17 Aug 2022 22:54:56 -0300 Subject: [PATCH 099/254] Add cpd CLI test --- .../net/sourceforge/pmd/cli/BaseCliTest.java | 33 ++++++++ .../net/sourceforge/pmd/cli/CpdCliTest.java | 76 +++++++++++++++++++ .../net/sourceforge/pmd/cli/PmdCliTest.java | 61 ++++++--------- .../net/sourceforge/pmd/cpd/files/dup1.java | 18 +++++ .../net/sourceforge/pmd/cpd/files/dup2.java | 18 +++++ .../files/file_with_ISO-8859-1_encoding.java | 8 ++ .../pmd/cpd/files/file_with_utf8_bom.java | 8 ++ .../sourceforge/pmd/cpd/files/real-file.txt | 0 8 files changed, 185 insertions(+), 37 deletions(-) create mode 100644 pmd-cli/src/test/java/net/sourceforge/pmd/cli/BaseCliTest.java create mode 100644 pmd-cli/src/test/java/net/sourceforge/pmd/cli/CpdCliTest.java create mode 100644 pmd-cli/src/test/resources/net/sourceforge/pmd/cpd/files/dup1.java create mode 100644 pmd-cli/src/test/resources/net/sourceforge/pmd/cpd/files/dup2.java create mode 100644 pmd-cli/src/test/resources/net/sourceforge/pmd/cpd/files/file_with_ISO-8859-1_encoding.java create mode 100644 pmd-cli/src/test/resources/net/sourceforge/pmd/cpd/files/file_with_utf8_bom.java create mode 100644 pmd-cli/src/test/resources/net/sourceforge/pmd/cpd/files/real-file.txt diff --git a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/BaseCliTest.java b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/BaseCliTest.java new file mode 100644 index 0000000000..4d1522b76e --- /dev/null +++ b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/BaseCliTest.java @@ -0,0 +1,33 @@ +package net.sourceforge.pmd.cli; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import net.sourceforge.pmd.cli.internal.ExecutionResult; + +import com.github.stefanbirkner.systemlambda.SystemLambda; + +abstract class BaseCliTest { + + protected String runCliSuccessfully(String... args) throws Exception { + return SystemLambda.tapSystemErrAndOut(() -> { + runCli(ExecutionResult.OK, args); + }); + } + + protected void runCli(ExecutionResult expectedExitCode, String... args) throws Exception { + final List argList = new ArrayList<>(); + argList.addAll(cliStandardArgs()); + argList.addAll(Arrays.asList(args)); + + final int actualExitCode = SystemLambda.catchSystemExit(() -> { + PmdCli.main(argList.toArray(new String[0])); + }); + assertEquals(expectedExitCode.getExitCode(), actualExitCode, "Exit code"); + } + + abstract protected List cliStandardArgs(); +} \ No newline at end of file diff --git a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/CpdCliTest.java b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/CpdCliTest.java new file mode 100644 index 0000000000..09c104bc0b --- /dev/null +++ b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/CpdCliTest.java @@ -0,0 +1,76 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cli; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import net.sourceforge.pmd.cli.internal.ExecutionResult; +import net.sourceforge.pmd.internal.Slf4jSimpleConfiguration; + +import com.github.stefanbirkner.systemlambda.SystemLambda; + +class CpdCliTest extends BaseCliTest { + private static final String SRC_DIR = "src/test/resources/net/sourceforge/pmd/cpd/files/"; + + @TempDir + private Path tempDir; + + @AfterAll + static void resetLogging() { + // reset logging in case "--debug" changed the logging properties + // See also Slf4jSimpleConfigurationForAnt + Slf4jSimpleConfiguration.reconfigureDefaultLogLevel(null); + } + + @Test + void debugLogging() throws Exception { + // restoring system properties: --debug might change logging properties + SystemLambda.restoreSystemProperties(() -> { + String log = runCliSuccessfully("--debug", "--minimum-tokens", "340", "--dir", SRC_DIR); + assertThat(log, containsString("[main] INFO net.sourceforge.pmd.cli.commands.internal.AbstractPmdSubcommand - Log level is at TRACE")); + }); + } + + @Test + void defaultLogging() throws Exception { + String log = runCliSuccessfully("--minimum-tokens", "340", "--dir", SRC_DIR); + assertThat(log, containsString("[main] INFO net.sourceforge.pmd.cli.commands.internal.AbstractPmdSubcommand - Log level is at INFO")); + } + + @Test + void testEmptyResultRendering() throws Exception { + final String stdout = SystemLambda.tapSystemErrAndOut(() -> { + SystemLambda.tapSystemErr(() -> { + final int statusCode = SystemLambda.catchSystemExit(() -> { + PmdCli.main(new String[] { + "cpd", "--minimum-tokens", "340", "--language", "java", "--dir", + SRC_DIR, "--format", "xml" + }); + }); + assertEquals(ExecutionResult.OK.getExitCode(), statusCode); + }); + }); + assertEquals("" + "\n" + "", stdout.trim()); + } + + @Override + protected List cliStandardArgs() { + final List argList = new ArrayList<>(); + + argList.add("cpd"); + + return argList; + } +} diff --git a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/PmdCliTest.java b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/PmdCliTest.java index 143d1eb7dd..b221993880 100644 --- a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/PmdCliTest.java +++ b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/PmdCliTest.java @@ -20,7 +20,6 @@ import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.AfterAll; @@ -33,7 +32,7 @@ import net.sourceforge.pmd.internal.Slf4jSimpleConfiguration; import com.github.stefanbirkner.systemlambda.SystemLambda; -class PmdCliTest { +class PmdCliTest extends BaseCliTest { @TempDir private Path tempDir; @@ -71,7 +70,7 @@ class PmdCliTest { assertTrue(Files.exists(reportFile), "Report file should exist"); - runPmdSuccessfully("-d", srcDir.toString(), "-R", DUMMY_RULESET, "-r", reportFile.toString()); + runCliSuccessfully("-d", srcDir.toString(), "-R", DUMMY_RULESET, "-r", reportFile.toString()); assertNotEquals(readString(reportFile), STRING_TO_REPLACE); } @@ -85,7 +84,7 @@ class PmdCliTest { assertTrue(Files.exists(reportFile), "Report file should exist"); - runPmdSuccessfully("--dir", srcDir.toString(), "--rulesets", DUMMY_RULESET, "--report-file", reportFile.toString()); + runCliSuccessfully("--dir", srcDir.toString(), "--rulesets", DUMMY_RULESET, "--report-file", reportFile.toString()); assertNotEquals(readString(reportFile), STRING_TO_REPLACE, "Report file should have been overwritten"); } @@ -97,7 +96,7 @@ class PmdCliTest { assertFalse(Files.exists(reportFile), "Report file should not exist"); try { - runPmdSuccessfully("-d", srcDir.toString(), "-R", DUMMY_RULESET, "-r", reportFile.toString()); + runCliSuccessfully("-d", srcDir.toString(), "-R", DUMMY_RULESET, "-r", reportFile.toString()); assertTrue(Files.exists(reportFile), "Report file should have been created"); } finally { Files.deleteIfExists(reportFile); @@ -110,7 +109,7 @@ class PmdCliTest { assertFalse(Files.exists(reportFile), "Report file should not exist"); - runPmdSuccessfully("--dir", srcDir.toString(), "--rulesets", DUMMY_RULESET, "--report-file", reportFile.toString()); + runCliSuccessfully("--dir", srcDir.toString(), "--rulesets", DUMMY_RULESET, "--report-file", reportFile.toString()); assertTrue(Files.exists(reportFile), "Report file should have been created"); } @@ -123,7 +122,7 @@ class PmdCliTest { // restoring system properties: --debug might change logging properties SystemLambda.restoreSystemProperties(() -> { - runPmdSuccessfully("--dir", srcDir.toString(), "--rulesets", DUMMY_RULESET, "--report-file", reportFile.toString(), "--debug"); + runCliSuccessfully("--dir", srcDir.toString(), "--rulesets", DUMMY_RULESET, "--report-file", reportFile.toString(), "--debug"); }); assertTrue(Files.exists(reportFile), "Report file should have been created"); @@ -146,7 +145,7 @@ class PmdCliTest { assertFalse(Files.exists(absoluteReportFile), "Report file must not exist yet! " + absoluteReportFile); try { - runPmdSuccessfully("-d", srcDir.toString(), "-R", DUMMY_RULESET, "-r", reportFile.toString()); + runCliSuccessfully("-d", srcDir.toString(), "-R", DUMMY_RULESET, "-r", reportFile.toString()); assertTrue(Files.exists(absoluteReportFile), "Report file should have been created"); } finally { Files.deleteIfExists(absoluteReportFile); @@ -161,7 +160,7 @@ class PmdCliTest { assertFalse(Files.exists(absoluteReportFile), "Report file must not exist yet!"); try { - runPmdSuccessfully("--dir", srcDir.toString(), "--rulesets", DUMMY_RULESET, "--report-file", reportFile.toString()); + runCliSuccessfully("--dir", srcDir.toString(), "--rulesets", DUMMY_RULESET, "--report-file", reportFile.toString()); assertTrue(Files.exists(absoluteReportFile), "Report file should have been created"); } finally { Files.deleteIfExists(absoluteReportFile); @@ -172,21 +171,21 @@ class PmdCliTest { void debugLogging() throws Exception { // restoring system properties: --debug might change logging properties SystemLambda.restoreSystemProperties(() -> { - String log = runPmdSuccessfully("--debug", "--dir", srcDir.toString(), "--rulesets", DUMMY_RULESET); + String log = runCliSuccessfully("--debug", "--dir", srcDir.toString(), "--rulesets", DUMMY_RULESET); assertThat(log, containsString("[main] INFO net.sourceforge.pmd.cli.commands.internal.AbstractPmdSubcommand - Log level is at TRACE")); }); } @Test void defaultLogging() throws Exception { - String log = runPmdSuccessfully("--dir", srcDir.toString(), "--rulesets", DUMMY_RULESET); + String log = runCliSuccessfully("--dir", srcDir.toString(), "--rulesets", DUMMY_RULESET); assertThat(log, containsString("[main] INFO net.sourceforge.pmd.cli.commands.internal.AbstractPmdSubcommand - Log level is at INFO")); } @Test void testDeprecatedRulesetSyntaxOnCommandLine() throws Exception { String log = SystemLambda.tapSystemErrAndOut(() -> { - runPmd(ExecutionResult.VIOLATIONS_FOUND, "--dir", srcDir.toString(), "--rulesets", "dummy-basic"); + runCli(ExecutionResult.VIOLATIONS_FOUND, "--dir", srcDir.toString(), "--rulesets", "dummy-basic"); }); assertThat(log, containsString("Ruleset reference 'dummy-basic' uses a deprecated form, use 'rulesets/dummy/basic.xml' instead")); } @@ -209,12 +208,6 @@ class PmdCliTest { } - private static String runPmdSuccessfully(String... args) throws Exception { - return SystemLambda.tapSystemErrAndOut(() -> { - runPmd(ExecutionResult.OK, args); - }); - } - // available in Files on java 11+ private static void writeString(Path path, String text) throws IOException { ByteBuffer encoded = StandardCharsets.UTF_8.encode(text); @@ -228,24 +221,18 @@ class PmdCliTest { ByteBuffer buf = ByteBuffer.wrap(bytes); return StandardCharsets.UTF_8.decode(buf).toString(); } - - private static void runPmd(ExecutionResult expectedExitCode, String... args) throws Exception { - final int actualExitCode = SystemLambda.catchSystemExit(() -> { - final List argList = new ArrayList<>(); - - // Always run against dummy language without logging not cache to remove all logging noise - argList.add("run"); - argList.add("--use-version"); - argList.add("dummy"); - argList.add("--no-cache"); - argList.add("--no-progress"); - - argList.addAll(Arrays.asList(args)); - - PmdCli.main(argList.toArray(new String[0])); - }); - assertEquals(expectedExitCode.getExitCode(), actualExitCode, "Exit code"); + + @Override + protected List cliStandardArgs() { + final List argList = new ArrayList<>(); + + // Always run against dummy language without logging not cache to remove all logging noise + argList.add("run"); + argList.add("--use-version"); + argList.add("dummy"); + argList.add("--no-cache"); + argList.add("--no-progress"); + + return argList; } - - } diff --git a/pmd-cli/src/test/resources/net/sourceforge/pmd/cpd/files/dup1.java b/pmd-cli/src/test/resources/net/sourceforge/pmd/cpd/files/dup1.java new file mode 100644 index 0000000000..2fde07a30a --- /dev/null +++ b/pmd-cli/src/test/resources/net/sourceforge/pmd/cpd/files/dup1.java @@ -0,0 +1,18 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +public class dup1 { + + public static void main(String[] args) { + System.out.println("Test1"); + System.out.println("Test2"); + System.out.println("Test3"); + System.out.println("Test4"); + System.out.println("Test5"); + System.out.println("Test6"); + System.out.println("Test7"); + System.out.println("Test8"); + System.out.println("Test9"); + } +} \ No newline at end of file diff --git a/pmd-cli/src/test/resources/net/sourceforge/pmd/cpd/files/dup2.java b/pmd-cli/src/test/resources/net/sourceforge/pmd/cpd/files/dup2.java new file mode 100644 index 0000000000..91779c045b --- /dev/null +++ b/pmd-cli/src/test/resources/net/sourceforge/pmd/cpd/files/dup2.java @@ -0,0 +1,18 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +public class dup2 { + + public static void main(String[] args) { + System.out.println("Test1"); + System.out.println("Test2"); + System.out.println("Test3"); + System.out.println("Test4"); + System.out.println("Test5"); + System.out.println("Test6"); + System.out.println("Test7"); + System.out.println("Test8"); + System.out.println("Test9"); + } +} \ No newline at end of file diff --git a/pmd-cli/src/test/resources/net/sourceforge/pmd/cpd/files/file_with_ISO-8859-1_encoding.java b/pmd-cli/src/test/resources/net/sourceforge/pmd/cpd/files/file_with_ISO-8859-1_encoding.java new file mode 100644 index 0000000000..d7f62ea9ed --- /dev/null +++ b/pmd-cli/src/test/resources/net/sourceforge/pmd/cpd/files/file_with_ISO-8859-1_encoding.java @@ -0,0 +1,8 @@ +/** + * This file is using ISO-8859-1 (Latin-1) encoding. + * + * ä + */ +public class FileWith_ISO8859-1_Encoding { + +} diff --git a/pmd-cli/src/test/resources/net/sourceforge/pmd/cpd/files/file_with_utf8_bom.java b/pmd-cli/src/test/resources/net/sourceforge/pmd/cpd/files/file_with_utf8_bom.java new file mode 100644 index 0000000000..566bf55d83 --- /dev/null +++ b/pmd-cli/src/test/resources/net/sourceforge/pmd/cpd/files/file_with_utf8_bom.java @@ -0,0 +1,8 @@ +/** + * This file is using UTF-8 with BOM encoding. + * + * ä + */ +public class FileWith_UTF-8-BOM_Encoding { + +} diff --git a/pmd-cli/src/test/resources/net/sourceforge/pmd/cpd/files/real-file.txt b/pmd-cli/src/test/resources/net/sourceforge/pmd/cpd/files/real-file.txt new file mode 100644 index 0000000000..e69de29bb2 From 301cd0ef34b71a9280960bc662ca29365a6fafd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 17 Aug 2022 23:30:14 -0300 Subject: [PATCH 100/254] Even better tests --- .../pmd/cli/commands/internal/CpdCommand.java | 4 +-- .../pmd/cli/internal/ExecutionResult.java | 1 + .../net/sourceforge/pmd/cli/BaseCliTest.java | 14 ++++----- .../net/sourceforge/pmd/cli/CpdCliTest.java | 19 ++++++++++++ .../net/sourceforge/pmd/cli/PmdCliTest.java | 30 +++++++++++-------- 5 files changed, 46 insertions(+), 22 deletions(-) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java index 40f42e6b2a..311a1a7397 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java @@ -37,7 +37,7 @@ public class CpdCommand extends AbstractAnalysisPmdSubcommand { // TODO : Set a default for this value? @Option(names = "--minimum-tokens", description = "The minimum token length which should be reported as a duplicate.", required = true) - private int minimumTileSize; + private int minimumTokens; @Option(names = "--skip-duplicate-files", description = "Ignore multiple copies of files of the same name and length in comparison.") @@ -106,7 +106,7 @@ public class CpdCommand extends AbstractAnalysisPmdSubcommand { configuration.setIgnoreLiteralSequences(ignoreLiteralSequences); configuration.setIgnoreUsings(ignoreUsings); configuration.setLanguage(language); - configuration.setMinimumTileSize(minimumTileSize); + configuration.setMinimumTileSize(minimumTokens); configuration.setNonRecursive(nonRecursive); configuration.setNoSkipBlocks(noSkipBlocks); configuration.setRendererName(null); diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/internal/ExecutionResult.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/internal/ExecutionResult.java index 3c813c9bdb..4c60674fde 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/internal/ExecutionResult.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/internal/ExecutionResult.java @@ -8,6 +8,7 @@ package net.sourceforge.pmd.cli.internal; public enum ExecutionResult { OK(0), ERROR(1), + USAGE_ERROR(2), VIOLATIONS_FOUND(4); private final int exitCode; diff --git a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/BaseCliTest.java b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/BaseCliTest.java index 4d1522b76e..3821f19044 100644 --- a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/BaseCliTest.java +++ b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/BaseCliTest.java @@ -13,20 +13,20 @@ import com.github.stefanbirkner.systemlambda.SystemLambda; abstract class BaseCliTest { protected String runCliSuccessfully(String... args) throws Exception { - return SystemLambda.tapSystemErrAndOut(() -> { - runCli(ExecutionResult.OK, args); - }); + return runCli(ExecutionResult.OK, args); } - protected void runCli(ExecutionResult expectedExitCode, String... args) throws Exception { + protected String runCli(ExecutionResult expectedExitCode, String... args) throws Exception { final List argList = new ArrayList<>(); argList.addAll(cliStandardArgs()); argList.addAll(Arrays.asList(args)); - final int actualExitCode = SystemLambda.catchSystemExit(() -> { - PmdCli.main(argList.toArray(new String[0])); + return SystemLambda.tapSystemErrAndOut(() -> { + final int actualExitCode = SystemLambda.catchSystemExit(() -> { + PmdCli.main(argList.toArray(new String[0])); + }); + assertEquals(expectedExitCode.getExitCode(), actualExitCode, "Exit code"); }); - assertEquals(expectedExitCode.getExitCode(), actualExitCode, "Exit code"); } abstract protected List cliStandardArgs(); diff --git a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/CpdCliTest.java b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/CpdCliTest.java index 09c104bc0b..98db2f6277 100644 --- a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/CpdCliTest.java +++ b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/CpdCliTest.java @@ -49,6 +49,25 @@ class CpdCliTest extends BaseCliTest { assertThat(log, containsString("[main] INFO net.sourceforge.pmd.cli.commands.internal.AbstractPmdSubcommand - Log level is at INFO")); } + @Test + void testMissingminimumTokens() throws Exception { + final String log = runCli(ExecutionResult.USAGE_ERROR); + assertThat(log, containsString("Missing required option: '--minimum-tokens='")); + } + + @Test + void testMissingSource() throws Exception { + final String log = runCli(ExecutionResult.USAGE_ERROR, "--minimum-tokens", "340"); + assertThat(log, containsString("Please provide a parameter for source root directory")); + } + + @Test + void testWrongCliOptionsDoPrintUsage() throws Exception { + final String log = runCli(ExecutionResult.USAGE_ERROR, "--invalid", "--minimum-tokens", "340", "-d", SRC_DIR); + assertThat(log, containsString("Unknown option: '--invalid'")); + assertThat(log, containsString("Usage: pmd cpd")); + } + @Test void testEmptyResultRendering() throws Exception { final String stdout = SystemLambda.tapSystemErrAndOut(() -> { diff --git a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/PmdCliTest.java b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/PmdCliTest.java index b221993880..ebfa0ba0aa 100644 --- a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/PmdCliTest.java +++ b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/PmdCliTest.java @@ -8,7 +8,6 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsStringIgnoringCase; import static org.hamcrest.Matchers.not; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -184,22 +183,27 @@ class PmdCliTest extends BaseCliTest { @Test void testDeprecatedRulesetSyntaxOnCommandLine() throws Exception { - String log = SystemLambda.tapSystemErrAndOut(() -> { - runCli(ExecutionResult.VIOLATIONS_FOUND, "--dir", srcDir.toString(), "--rulesets", "dummy-basic"); - }); + String log = runCli(ExecutionResult.VIOLATIONS_FOUND, "--dir", srcDir.toString(), "--rulesets", "dummy-basic"); assertThat(log, containsString("Ruleset reference 'dummy-basic' uses a deprecated form, use 'rulesets/dummy/basic.xml' instead")); } - @Test - void testWrongCliOptionsDoNotPrintUsage() throws Exception { - final String log = SystemLambda.tapSystemErrAndOut(() -> { - final int actualExitCode = SystemLambda.catchSystemExit(() -> { - PmdCli.main(new String[] { "run", "--invalid" }); - }); - assertEquals(2, actualExitCode); - }); - assertThat(log, not(containsStringIgnoringCase("Available report formats and"))); + void testMissingRuleset() throws Exception { + final String log = runCli(ExecutionResult.USAGE_ERROR); + assertThat(log, containsString("Missing required option: '--rulesets='")); + } + + @Test + void testMissingSource() throws Exception { + final String log = runCli(ExecutionResult.USAGE_ERROR, "--rulesets", DUMMY_RULESET); + assertThat(log, containsString("Please provide a parameter for source root directory")); + } + + @Test + void testWrongCliOptionsDoPrintUsage() throws Exception { + final String log = runCli(ExecutionResult.USAGE_ERROR, "--invalid", "--rulesets", DUMMY_RULESET, "-d", srcDir.toString()); + assertThat(log, containsString("Unknown option: '--invalid'")); + assertThat(log, containsString("Usage: pmd analyze")); } // utilities From 4a0c735506d9fecb9850e0426528f977f33dd559 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Thu, 18 Aug 2022 00:05:51 -0300 Subject: [PATCH 101/254] Add CpdCommandTest and refactor PmdCommandTest --- .../pmd/cli/commands/internal/CpdCommand.java | 1 - .../commands/internal/BaseCommandTest.java | 50 +++++++++++++ .../cli/commands/internal/CpdCommandTest.java | 75 +++++++++++++++++++ .../cli/commands/internal/PmdCommandTest.java | 50 +++---------- 4 files changed, 136 insertions(+), 40 deletions(-) create mode 100644 pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/BaseCommandTest.java create mode 100644 pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/CpdCommandTest.java diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java index 311a1a7397..011407c62c 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java @@ -43,7 +43,6 @@ public class CpdCommand extends AbstractAnalysisPmdSubcommand { description = "Ignore multiple copies of files of the same name and length in comparison.") private boolean skipDuplicates; - // TODO : Can we unify with PmdCommand but keep separate completion candidates? I think not… @Option(names = { "--format", "-f" }, description = "Report format.%nValid values: ${COMPLETION-CANDIDATES}%n" + "Alternatively, you can provide the fully qualified name of a custom CpdRenderer in the classpath.", diff --git a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/BaseCommandTest.java b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/BaseCommandTest.java new file mode 100644 index 0000000000..34f3d63dcf --- /dev/null +++ b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/BaseCommandTest.java @@ -0,0 +1,50 @@ +package net.sourceforge.pmd.cli.commands.internal; + +import static org.hamcrest.MatcherAssert.assertThat; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.hamcrest.Matchers; + +import picocli.CommandLine; +import picocli.CommandLine.ParseResult; + +abstract class BaseCommandTest { + + protected abstract T createCommand(); + + protected abstract void addStandardParams(List argList); + + protected void assertError(final String... params) { + final T cmd = createCommand(); + final ParseResult parseResult = parseCommand(cmd, params); + assertThat(parseResult.errors(), Matchers.not(Matchers.empty())); + } + + protected T setupAndParse(final String... params) { + final T cmd = createCommand(); + final ParseResult parseResult = parseCommand(cmd, params); + + assertThat(parseResult.errors(), Matchers.empty()); + + return cmd; + } + + private ParseResult parseCommand(final Object cmd, final String... params) { + final List argList = new ArrayList<>(); + argList.addAll(Arrays.asList(params)); + + addStandardParams(argList); + + final CommandLine commandLine = new CommandLine(cmd) + .setCaseInsensitiveEnumValuesAllowed(true); + + // Collect errors instead of simply throwing during parsing + commandLine.getCommandSpec().parser().collectErrors(true); + + return commandLine.parseArgs(argList.toArray(new String[0])); + } + +} diff --git a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/CpdCommandTest.java b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/CpdCommandTest.java new file mode 100644 index 0000000000..b261d5664a --- /dev/null +++ b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/CpdCommandTest.java @@ -0,0 +1,75 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cli.commands.internal; + +import static net.sourceforge.pmd.util.CollectionUtil.listOf; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.File; +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; + +import net.sourceforge.pmd.cpd.CPDConfiguration; + +class CpdCommandTest extends BaseCommandTest { + + @Test + void testMultipleDirs() { + final CpdCommand cmd = setupAndParse( + "-d", "a", "b" + ); + assertMultipleDirs(cmd); + } + + @Test + void testMultipleDirsWithCommas() { + final CpdCommand cmd = setupAndParse( + "-d", "a,b" + ); + assertMultipleDirs(cmd); + } + + @Test + void testMultipleDirsWithRepeatedOption() { + final CpdCommand cmd = setupAndParse( + "-d", "a", "-d", "b" + ); + assertMultipleDirs(cmd); + } + + @Test + void testNoPositionalParametersAllowed() { + assertError( + // vvvv + "-d", "a", "--", "-d", "b" + ); + } + + @Test + void testEmptyDirOption() { + assertError("-d", "-f", "text"); + } + + private void assertMultipleDirs(final CpdCommand result) { + final CPDConfiguration config = result.toConfiguration(); + assertEquals(listOf("a", "b"), config.getFiles().stream().map(File::toString).collect(Collectors.toList())); + } + + @Override + protected CpdCommand createCommand() { + return new CpdCommand(); + } + + @Override + protected void addStandardParams(final List argList) { + // If no minimum tokens provided, set default value + if (!argList.contains("--minimum-tokens")) { + argList.add("--minimum-tokens"); + argList.add("100"); + } + } +} diff --git a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/PmdCommandTest.java b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/PmdCommandTest.java index a751a12e9c..b30c009871 100644 --- a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/PmdCommandTest.java +++ b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/PmdCommandTest.java @@ -5,24 +5,17 @@ package net.sourceforge.pmd.cli.commands.internal; import static net.sourceforge.pmd.util.CollectionUtil.listOf; -import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; import net.sourceforge.pmd.PMDConfiguration; import net.sourceforge.pmd.lang.DummyLanguageModule; import net.sourceforge.pmd.lang.LanguageVersion; -import picocli.CommandLine; -import picocli.CommandLine.ParseResult; - -class PmdCommandTest { +class PmdCommandTest extends BaseCommandTest { @Test void testVersionDefault() throws Exception { @@ -83,13 +76,6 @@ class PmdCommandTest { ); } - - private void assertMultipleDirsAndRulesets(final PmdCommand result) { - final PMDConfiguration config = result.toConfiguration(); - assertEquals(listOf("a", "b"), config.getAllInputPaths()); - assertEquals(listOf("x.xml", "y.xml"), config.getRuleSetPaths()); - } - @Test void testEmptyDirOption() { assertError("-d", "-R", "y.xml"); @@ -100,37 +86,23 @@ class PmdCommandTest { assertError("-R", "-d", "something"); } - private void assertError(final String... params) { - final PmdCommand cmd = new PmdCommand(); - final ParseResult parseResult = parseCommand(cmd, params); - assertThat(parseResult.errors(), Matchers.not(Matchers.empty())); + private void assertMultipleDirsAndRulesets(final PmdCommand result) { + final PMDConfiguration config = result.toConfiguration(); + assertEquals(listOf("a", "b"), config.getAllInputPaths()); + assertEquals(listOf("x.xml", "y.xml"), config.getRuleSetPaths()); } - private PmdCommand setupAndParse(final String... params) { - final PmdCommand cmd = new PmdCommand(); - final ParseResult parseResult = parseCommand(cmd, params); - - assertThat(parseResult.errors(), Matchers.empty()); - - return cmd; + @Override + protected PmdCommand createCommand() { + return new PmdCommand(); } - - private ParseResult parseCommand(final Object cmd, final String... params) { - final List argList = new ArrayList<>(); + @Override + protected void addStandardParams(final List argList) { // If no language provided, set dummy latest - if (!Arrays.stream(params).anyMatch(s -> s.equals("--use-version"))) { + if (!argList.contains("--use-version")) { argList.add("--use-version"); argList.add("dummy"); } - argList.addAll(Arrays.asList(params)); - - final CommandLine commandLine = new CommandLine(cmd) - .setCaseInsensitiveEnumValuesAllowed(true); - - // Collect errors instead of simply throwing during parsing - commandLine.getCommandSpec().parser().collectErrors(true); - - return commandLine.parseArgs(argList.toArray(new String[0])); } } From 40f43954c52f098d5a3b2760e46e984be45b5562 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Thu, 18 Aug 2022 00:16:35 -0300 Subject: [PATCH 102/254] Deprecate BaseCLITest from pmd-test - Only the pmd-cli module should deal with the CLI anymore --- pmd-test/src/main/java/net/sourceforge/pmd/cli/BaseCLITest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pmd-test/src/main/java/net/sourceforge/pmd/cli/BaseCLITest.java b/pmd-test/src/main/java/net/sourceforge/pmd/cli/BaseCLITest.java index 9fede03758..b199a22742 100644 --- a/pmd-test/src/main/java/net/sourceforge/pmd/cli/BaseCLITest.java +++ b/pmd-test/src/main/java/net/sourceforge/pmd/cli/BaseCLITest.java @@ -31,7 +31,9 @@ import net.sourceforge.pmd.internal.util.AssertionUtil; /** * @author Romain Pelisse <belaran@gmail.com> * + * @deprecated Only pmd-cli module should use / test the CLI. */ +@Deprecated public abstract class BaseCLITest { protected static final String TEST_OUPUT_DIRECTORY = "target/cli-tests/"; From f250701c73ba64e1976737a5107ab15adea070a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Thu, 18 Aug 2022 00:18:43 -0300 Subject: [PATCH 103/254] Add completion candidates for PMD report properties - The help is still the only source of truth for which ones are valid --- .../pmd/cli/commands/internal/PmdCommand.java | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java index 1cec851066..ce6eff9b31 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java @@ -8,6 +8,7 @@ import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Optional; @@ -165,9 +166,9 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { this.minimumPriority = priority; } - // TODO : Autocomplete candidates? @Option(names = { "--property", "-P" }, description = "Key-value pair defining a property for the report format.%n" - + "Supported values for each report format:%n${sys:pmd-cli.pmd.report.properties.help}") + + "Supported values for each report format:%n${sys:pmd-cli.pmd.report.properties.help}", + completionCandidates = PmdReportPropertiesCandidates.class) public void setProperties(final Properties properties) { this.properties = properties; } @@ -384,6 +385,29 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { } } + /** + * Provider of candidates for valid report properties. + * + * Check the help for which ones are supported by each report format and possible values. + */ + private static class PmdReportPropertiesCandidates implements Iterable { + + @Override + public Iterator iterator() { + final List propertyNames = new ArrayList<>(); + final Properties emptyProps = new Properties(); + for (final String rendererName : RendererFactory.supportedRenderers()) { + final Renderer renderer = RendererFactory.createRenderer(rendererName, emptyProps); + + for (final PropertyDescriptor property : renderer.getPropertyDescriptors()) { + propertyNames.add(property.name()); + } + } + return propertyNames.iterator(); + } + + } + /** * Provider of candidates for valid languages. * From 85e45dcfefdc0b4b0fe27bc6a7f4230f46faa249 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Thu, 18 Aug 2022 00:27:32 -0300 Subject: [PATCH 104/254] Cleanup code --- .../test/java/net/sourceforge/pmd/cli/BaseCliTest.java | 8 ++++++-- .../src/test/java/net/sourceforge/pmd/cli/CpdCliTest.java | 4 ++-- .../pmd/cli/commands/internal/BaseCommandTest.java | 4 ++++ .../net/sourceforge/pmd/cpd/CPDCommandLineInterface.java | 6 +++--- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/BaseCliTest.java b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/BaseCliTest.java index 3821f19044..3c6f4bd1ac 100644 --- a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/BaseCliTest.java +++ b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/BaseCliTest.java @@ -1,3 +1,7 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + package net.sourceforge.pmd.cli; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -29,5 +33,5 @@ abstract class BaseCliTest { }); } - abstract protected List cliStandardArgs(); -} \ No newline at end of file + protected abstract List cliStandardArgs(); +} diff --git a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/CpdCliTest.java b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/CpdCliTest.java index 98db2f6277..c1ee5c5152 100644 --- a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/CpdCliTest.java +++ b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/CpdCliTest.java @@ -70,12 +70,12 @@ class CpdCliTest extends BaseCliTest { @Test void testEmptyResultRendering() throws Exception { - final String stdout = SystemLambda.tapSystemErrAndOut(() -> { + final String stdout = SystemLambda.tapSystemOut(() -> { SystemLambda.tapSystemErr(() -> { final int statusCode = SystemLambda.catchSystemExit(() -> { PmdCli.main(new String[] { "cpd", "--minimum-tokens", "340", "--language", "java", "--dir", - SRC_DIR, "--format", "xml" + SRC_DIR, "--format", "xml", }); }); assertEquals(ExecutionResult.OK.getExitCode(), statusCode); diff --git a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/BaseCommandTest.java b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/BaseCommandTest.java index 34f3d63dcf..6057a549b9 100644 --- a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/BaseCommandTest.java +++ b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/BaseCommandTest.java @@ -1,3 +1,7 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + package net.sourceforge.pmd.cli.commands.internal; import static org.hamcrest.MatcherAssert.assertThat; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDCommandLineInterface.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDCommandLineInterface.java index d05eac13e4..bb9a5d50d4 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDCommandLineInterface.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDCommandLineInterface.java @@ -14,15 +14,15 @@ import java.util.Map.Entry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.beust.jcommander.JCommander; -import com.beust.jcommander.ParameterException; - import net.sourceforge.pmd.PMD; import net.sourceforge.pmd.PMDVersion; import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.cli.internal.CliMessages; import net.sourceforge.pmd.cpd.CPD.StatusCode; +import com.beust.jcommander.JCommander; +import com.beust.jcommander.ParameterException; + /** * @deprecated Internal API. Use {@link CPD#runCpd(String...)} or {@link CPD#main(String[])} * in order to execute CPD. From 01781a3fa8b536607661b79773427cc98f57a13f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Fri, 19 Aug 2022 19:18:50 -0300 Subject: [PATCH 105/254] Rename CliMessages - It was never used exclusively for CLI - It no longer references the CLI command --- .../pmd/cli/commands/internal/PmdCommand.java | 4 ++-- pmd-core/src/main/java/net/sourceforge/pmd/PMD.java | 4 ++-- .../src/main/java/net/sourceforge/pmd/PmdAnalysis.java | 4 ++-- pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java | 4 ++-- .../net/sourceforge/pmd/cpd/CPDCommandLineInterface.java | 4 ++-- .../CliMessages.java => internal/LogMessages.java} | 9 +++++---- 6 files changed, 15 insertions(+), 14 deletions(-) rename pmd-core/src/main/java/net/sourceforge/pmd/{cli/internal/CliMessages.java => internal/LogMessages.java} (82%) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java index ce6eff9b31..d0c70bbcf8 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java @@ -27,8 +27,8 @@ import net.sourceforge.pmd.benchmark.TextTimingReportRenderer; import net.sourceforge.pmd.benchmark.TimeTracker; import net.sourceforge.pmd.benchmark.TimingReport; import net.sourceforge.pmd.benchmark.TimingReportRenderer; -import net.sourceforge.pmd.cli.internal.CliMessages; import net.sourceforge.pmd.cli.internal.ExecutionResult; +import net.sourceforge.pmd.internal.LogMessages; import net.sourceforge.pmd.lang.Language; import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.LanguageVersion; @@ -350,7 +350,7 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { } private void printErrorDetected(MessageReporter reporter, int errors) { - String msg = CliMessages.errorDetectedMessage(errors, "PMD"); + String msg = LogMessages.errorDetectedMessage(errors, "PMD"); // note: using error level here increments the error count of the reporter, // which we don't want. reporter.info(StringUtil.quoteMessageFormat(msg)); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java b/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java index 5320e5348c..288f8872fa 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java @@ -28,7 +28,7 @@ import net.sourceforge.pmd.benchmark.TimingReport; import net.sourceforge.pmd.benchmark.TimingReportRenderer; import net.sourceforge.pmd.cli.PMDCommandLineInterface; import net.sourceforge.pmd.cli.PmdParametersParseResult; -import net.sourceforge.pmd.cli.internal.CliMessages; +import net.sourceforge.pmd.internal.LogMessages; import net.sourceforge.pmd.internal.Slf4jSimpleConfiguration; import net.sourceforge.pmd.lang.document.TextFile; import net.sourceforge.pmd.renderers.Renderer; @@ -149,7 +149,7 @@ public final class PMD { return StatusCode.OK; } else if (parseResult.isError()) { System.err.println(parseResult.getError().getMessage()); - System.err.println(CliMessages.runWithHelpFlagMessage()); + System.err.println(LogMessages.runWithHelpFlagMessage()); return StatusCode.ERROR; } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/PmdAnalysis.java b/pmd-core/src/main/java/net/sourceforge/pmd/PmdAnalysis.java index a4375770e2..7416e8244f 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/PmdAnalysis.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/PmdAnalysis.java @@ -24,8 +24,8 @@ import net.sourceforge.pmd.benchmark.TimedOperation; import net.sourceforge.pmd.benchmark.TimedOperationCategory; import net.sourceforge.pmd.cache.AnalysisCacheListener; import net.sourceforge.pmd.cache.NoopAnalysisCache; -import net.sourceforge.pmd.cli.internal.CliMessages; import net.sourceforge.pmd.cli.internal.ProgressBarListener; +import net.sourceforge.pmd.internal.LogMessages; import net.sourceforge.pmd.internal.util.AssertionUtil; import net.sourceforge.pmd.internal.util.FileCollectionUtil; import net.sourceforge.pmd.lang.Language; @@ -430,7 +430,7 @@ public final class PmdAnalysis implements AutoCloseable { } static void printErrorDetected(MessageReporter reporter, int errors) { - String msg = CliMessages.errorDetectedMessage(errors, "PMD"); + String msg = LogMessages.errorDetectedMessage(errors, "PMD"); // note: using error level here increments the error count of the reporter, // which we don't want. reporter.info(StringUtil.quoteMessageFormat(msg)); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java index bd6e121dc1..60214b1442 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java @@ -25,8 +25,8 @@ import org.slf4j.LoggerFactory; import org.slf4j.event.Level; import net.sourceforge.pmd.annotation.Experimental; -import net.sourceforge.pmd.cli.internal.CliMessages; import net.sourceforge.pmd.cpd.renderer.CPDReportRenderer; +import net.sourceforge.pmd.internal.LogMessages; import net.sourceforge.pmd.internal.Slf4jSimpleConfiguration; import net.sourceforge.pmd.lang.ast.TokenMgrError; import net.sourceforge.pmd.util.FileFinder; @@ -325,7 +325,7 @@ public class CPD { } } catch (IOException | RuntimeException e) { log.debug(e.toString(), e); - log.error(CliMessages.errorDetectedMessage(1, CPDCommandLineInterface.PROGRAM_NAME)); + log.error(LogMessages.errorDetectedMessage(1, CPDCommandLineInterface.PROGRAM_NAME)); statusCode = StatusCode.ERROR; } return statusCode; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDCommandLineInterface.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDCommandLineInterface.java index bb9a5d50d4..b0e8bb5a18 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDCommandLineInterface.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDCommandLineInterface.java @@ -17,8 +17,8 @@ import org.slf4j.LoggerFactory; import net.sourceforge.pmd.PMD; import net.sourceforge.pmd.PMDVersion; import net.sourceforge.pmd.annotation.InternalApi; -import net.sourceforge.pmd.cli.internal.CliMessages; import net.sourceforge.pmd.cpd.CPD.StatusCode; +import net.sourceforge.pmd.internal.LogMessages; import com.beust.jcommander.JCommander; import com.beust.jcommander.ParameterException; @@ -86,7 +86,7 @@ public final class CPDCommandLineInterface { } } catch (ParameterException e) { System.err.println(e.getMessage()); - System.err.println(CliMessages.runWithHelpFlagMessage()); + System.err.println(LogMessages.runWithHelpFlagMessage()); return StatusCode.ERROR; } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/CliMessages.java b/pmd-core/src/main/java/net/sourceforge/pmd/internal/LogMessages.java similarity index 82% rename from pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/CliMessages.java rename to pmd-core/src/main/java/net/sourceforge/pmd/internal/LogMessages.java index 56a8b349d3..b4911afbea 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cli/internal/CliMessages.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/internal/LogMessages.java @@ -2,27 +2,28 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.cli.internal; +package net.sourceforge.pmd.internal; /** * @author Clément Fournier */ -public final class CliMessages { +public final class LogMessages { - private CliMessages() { + private LogMessages() { // utility class } public static String errorDetectedMessage(int errors, String program) { String anError = errors == 1 ? "An error" : errors + " errors"; return anError + " occurred while executing " + program + ".\n" - + "Run with --debug to see a stack-trace.\n" + + "Run in verbose mode to see a stack-trace.\n" + "If you think this is a bug in " + program + ", please report this issue at https://github.com/pmd/pmd/issues/new/choose\n" + "If you do so, please include a stack-trace, the code sample\n" + " causing the issue, and details about your run configuration."; } + @Deprecated public static String runWithHelpFlagMessage() { return "Run with --help for command line help."; } From 90138b00c648c8d90dea234631a6e292cb0b1afc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Mon, 22 Aug 2022 10:12:20 -0300 Subject: [PATCH 106/254] Extract encoding to a mixin --- .../AbstractAnalysisPmdSubcommand.java | 9 ++++---- .../pmd/cli/commands/internal/CpdCommand.java | 4 ++-- .../pmd/cli/commands/internal/PmdCommand.java | 2 +- .../mixins/internal/EncodingMixin.java | 23 +++++++++++++++++++ 4 files changed, 31 insertions(+), 7 deletions(-) create mode 100644 pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/mixins/internal/EncodingMixin.java diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractAnalysisPmdSubcommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractAnalysisPmdSubcommand.java index c42dc45a01..de9ccd8eac 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractAnalysisPmdSubcommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractAnalysisPmdSubcommand.java @@ -5,18 +5,19 @@ package net.sourceforge.pmd.cli.commands.internal; import java.net.URI; -import java.nio.charset.Charset; import java.nio.file.Path; import java.util.List; +import net.sourceforge.pmd.cli.commands.mixins.internal.EncodingMixin; + +import picocli.CommandLine.Mixin; import picocli.CommandLine.Option; import picocli.CommandLine.ParameterException; public abstract class AbstractAnalysisPmdSubcommand extends AbstractPmdSubcommand { - @Option(names = { "--encoding", "-e" }, description = "Specifies the character set encoding of the source code files", - defaultValue = "UTF-8") - protected Charset encoding; + @Mixin + protected EncodingMixin encoding; @Option(names = { "--dir", "-d" }, description = "Path to a source file, or directory containing source files to analyze. " diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java index 011407c62c..54fcc04b3a 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java @@ -112,10 +112,10 @@ public class CpdCommand extends AbstractAnalysisPmdSubcommand { configuration.setSkipBlocksPattern(skipBlocksPattern); configuration.setSkipDuplicates(skipDuplicates); configuration.setSkipLexicalErrors(skipLexicalErrors); - configuration.setSourceEncoding(encoding.name()); + configuration.setSourceEncoding(encoding.getEncoding().name()); configuration.setURI(uri == null ? null : uri.toString()); - configuration.setCPDRenderer(CPDConfiguration.getCPDRendererFromString(rendererName, encoding.name())); + configuration.setCPDRenderer(CPDConfiguration.getCPDRendererFromString(rendererName, encoding.getEncoding().name())); // TODO // Setup CLI message reporter diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java index d0c70bbcf8..1ce72fe46c 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java @@ -268,7 +268,7 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { configuration.setInputUri(uri != null ? uri.toString() : null); configuration.setReportFormat(format); configuration.setDebug(debug); - configuration.setSourceEncoding(encoding.name()); + configuration.setSourceEncoding(encoding.getEncoding().name()); configuration.setMinimumPriority(minimumPriority); configuration.setReportFile(reportFile != null ? reportFile.toString() : null); configuration.setReportProperties(properties); diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/mixins/internal/EncodingMixin.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/mixins/internal/EncodingMixin.java new file mode 100644 index 0000000000..e6086bb46f --- /dev/null +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/mixins/internal/EncodingMixin.java @@ -0,0 +1,23 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cli.commands.mixins.internal; + +import java.nio.charset.Charset; + +import picocli.CommandLine.Option; + +/** + * A mixin for source code encoding. Used to ensure consistency among commands. + */ +public class EncodingMixin { + + @Option(names = { "--encoding", "-e" }, description = "Specifies the character set encoding of the source code files", + defaultValue = "UTF-8") + private Charset encoding; + + public Charset getEncoding() { + return encoding; + } +} From 4d083c1d2b5c5cfd0d02d9c5be1497b305aab68a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Mon, 22 Aug 2022 10:12:43 -0300 Subject: [PATCH 107/254] Improve on help message for --ignore-list --- .../net/sourceforge/pmd/cli/commands/internal/PmdCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java index 1ce72fe46c..ab5a6fee10 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java @@ -122,7 +122,7 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { @Option(names = "--ignore-list", description = "Path to a file containing a list of files to exclude from the analysis, one path per line. " - + "This option can be combined with --dir and --file-list.") + + "This option can be combined with --dir, --file-list and --uri.") public void setIgnoreListPath(final Path ignoreListPath) { this.ignoreListPath = ignoreListPath; } From ae1d00d5133ce4a198f7506b97f292003dcaa1b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Mon, 22 Aug 2022 11:09:09 -0300 Subject: [PATCH 108/254] Extract converter / iterable pairs - This will allow better reuse / have consistency by keeping completion candidates and convertion tied together --- .../pmd/cli/commands/internal/PmdCommand.java | 113 +----------------- .../internal/PmdLanguageTypeSupport.java | 38 ++++++ .../PmdLanguageVersionTypeSupport.java | 74 ++++++++++++ 3 files changed, 117 insertions(+), 108 deletions(-) create mode 100644 pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/typesupport/internal/PmdLanguageTypeSupport.java create mode 100644 pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/typesupport/internal/PmdLanguageVersionTypeSupport.java diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java index ab5a6fee10..c7f94176e2 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java @@ -11,11 +11,8 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.Iterator; import java.util.List; -import java.util.Optional; import java.util.Properties; -import java.util.TreeSet; import java.util.stream.Collectors; -import java.util.stream.Stream; import org.slf4j.LoggerFactory; import org.slf4j.event.Level; @@ -27,10 +24,11 @@ import net.sourceforge.pmd.benchmark.TextTimingReportRenderer; import net.sourceforge.pmd.benchmark.TimeTracker; import net.sourceforge.pmd.benchmark.TimingReport; import net.sourceforge.pmd.benchmark.TimingReportRenderer; +import net.sourceforge.pmd.cli.commands.typesupport.internal.PmdLanguageTypeSupport; +import net.sourceforge.pmd.cli.commands.typesupport.internal.PmdLanguageVersionTypeSupport; import net.sourceforge.pmd.cli.internal.ExecutionResult; import net.sourceforge.pmd.internal.LogMessages; import net.sourceforge.pmd.lang.Language; -import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.LanguageVersion; import net.sourceforge.pmd.properties.PropertyDescriptor; import net.sourceforge.pmd.renderers.Renderer; @@ -41,10 +39,8 @@ import net.sourceforge.pmd.util.log.MessageReporter; import net.sourceforge.pmd.util.log.internal.SimpleMessageReporter; import picocli.CommandLine.Command; -import picocli.CommandLine.ITypeConverter; import picocli.CommandLine.Option; import picocli.CommandLine.ParameterException; -import picocli.CommandLine.TypeConversionException; @Command(name = "analyze", aliases = {"analyse", "run" }, showDefaultValues = true, description = "The PMD standard source code analyzer") @@ -183,7 +179,7 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { @Option(names = "--use-version", defaultValue = "java-latest", description = "Sepcify the language and version PMD should use.%nValid values: ${COMPLETION-CANDIDATES}%n", - completionCandidates = PmdLanguageVersionCandidates.class, converter = PmdLanguageVersionConverter.class) + completionCandidates = PmdLanguageVersionTypeSupport.class, converter = PmdLanguageVersionTypeSupport.class) public void setLanguageVersion(final List languageVersion) { // Make sure we only set 1 version per language languageVersion.stream().collect(Collectors.groupingBy(LanguageVersion::getLanguage)) @@ -191,7 +187,7 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { if (list.size() > 1) { throw new ParameterException(spec.commandLine(), "Can only set one version per language, " + "but for language " + l.getName() + " multiple versions were provided " - + list.stream().map(PmdCommand::normalizeName).collect(Collectors.joining("', '", "'", "'"))); + + list.stream().map(PmdLanguageVersionTypeSupport::normalizeName).collect(Collectors.joining("', '", "'", "'"))); } }); @@ -203,7 +199,7 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { + "When using this option, the automatic language selection by extension is disabled, and PMD " + "tries to parse all input files with the given language's parser. " + "Parsing errors are ignored.%nValid values: ${COMPLETION-CANDIDATES}%n", - completionCandidates = PmdLanguageCandidates.class, converter = PmdLanguageConverter.class) + completionCandidates = PmdLanguageTypeSupport.class, converter = PmdLanguageTypeSupport.class) public void setForceLanguage(final Language forceLanguage) { this.forceLanguage = forceLanguage; } @@ -405,104 +401,5 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { } return propertyNames.iterator(); } - - } - - /** - * Provider of candidates for valid languages. - * - * Beware, the help will report this on runtime, and be accurate to available - * modules in the classpath, but autocomplete will include all at build time. - */ - private static class PmdLanguageCandidates implements Iterable { - - @Override - public Iterator iterator() { - return LanguageRegistry.getLanguages().stream().map(PmdCommand::normalizeName).iterator(); - } - } - - /** - * Maps a String back to a {@code Language} - * - * Effectively reverses the stringification done by {@code PMDLanguagesCandidates} - */ - private static class PmdLanguageConverter implements ITypeConverter { - - @Override - public Language convert(final String value) throws Exception { - return LanguageRegistry.getLanguages().stream() - .filter(l -> normalizeName(l).equals(value)).findFirst() - .orElseThrow(() -> new TypeConversionException("Unknown language: " + value)); - } - - } - - /** - * Provider of candidates for valid language-version combinations. - * - * Beware, the help will report this on runtime, and be accurate to available - * modules in the classpath, but autocomplete will include all at build time. - */ - private static class PmdLanguageVersionCandidates implements Iterable { - - @Override - public Iterator iterator() { - // Raw language names / -latest versions, such as "java" or "java-latest" - final Stream latestLangReferences = LanguageRegistry.getLanguages().stream() - .map(PmdCommand::normalizeName).flatMap(name -> Stream.of(name, name + "-latest")); - - // Explicit language-version pairs, such as "java-18" or "apex-54" - final Stream allLangVersionReferences = LanguageRegistry.getLanguages().stream() - .flatMap(l -> l.getVersions().stream()) - .map(PmdCommand::normalizeName); - - // Collect to a TreeSet to ensure alphabetical order - final TreeSet candidates = Stream.concat(latestLangReferences, allLangVersionReferences) - .collect(Collectors.toCollection(TreeSet::new)); - - return candidates.iterator(); - } - } - - /** - * Maps a String back to a {@code LanguageVersion} - * - * Effectively reverses the stringification done by {@code PMDLanguageVersionCandidates} - */ - private static class PmdLanguageVersionConverter implements ITypeConverter { - - @Override - public LanguageVersion convert(final String value) throws Exception { - // Is it an exact match? - final Optional langVer = LanguageRegistry.getLanguages().stream() - .flatMap(l -> l.getVersions().stream()) - .filter(lv -> normalizeName(lv).equals(value)).findFirst(); - - if (langVer.isPresent()) { - return langVer.get(); - } - - // This is either a -latest or standalone language name - final String langName; - if (value.endsWith("-latest")) { - langName = value.substring(0, value.length() - "-latest".length()); - } else { - langName = value; - } - - return LanguageRegistry.getLanguages().stream() - .filter(l -> normalizeName(l).equals(langName)) - .map(Language::getDefaultVersion).findFirst() - .orElseThrow(() -> new TypeConversionException("Unknown language version: " + value)); - } - } - - static String normalizeName(final LanguageVersion langVer) { - return langVer.getTerseName().replace(' ', '-'); - } - - static String normalizeName(final Language lang) { - return lang.getTerseName().replace(' ', '-'); } } diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/typesupport/internal/PmdLanguageTypeSupport.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/typesupport/internal/PmdLanguageTypeSupport.java new file mode 100644 index 0000000000..e57d346733 --- /dev/null +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/typesupport/internal/PmdLanguageTypeSupport.java @@ -0,0 +1,38 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cli.commands.typesupport.internal; + +import java.util.Iterator; + +import net.sourceforge.pmd.lang.Language; +import net.sourceforge.pmd.lang.LanguageRegistry; + +import picocli.CommandLine.ITypeConverter; +import picocli.CommandLine.TypeConversionException; + +/** + * Provider of candidates / conversion support for supported PMD languages. + * + * Beware, the help will report this on runtime, and be accurate to available + * modules in the classpath, but autocomplete will include all available at build time. + */ +public class PmdLanguageTypeSupport implements ITypeConverter, Iterable { + + @Override + public Language convert(final String value) throws Exception { + return LanguageRegistry.getLanguages().stream() + .filter(l -> normalizeName(l).equals(value)).findFirst() + .orElseThrow(() -> new TypeConversionException("Unknown language: " + value)); + } + + @Override + public Iterator iterator() { + return LanguageRegistry.getLanguages().stream().map(PmdLanguageTypeSupport::normalizeName).iterator(); + } + + public static String normalizeName(final Language lang) { + return lang.getTerseName().replace(' ', '-'); + } +} diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/typesupport/internal/PmdLanguageVersionTypeSupport.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/typesupport/internal/PmdLanguageVersionTypeSupport.java new file mode 100644 index 0000000000..04a7157648 --- /dev/null +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/typesupport/internal/PmdLanguageVersionTypeSupport.java @@ -0,0 +1,74 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cli.commands.typesupport.internal; + +import java.util.Iterator; +import java.util.Optional; +import java.util.TreeSet; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import net.sourceforge.pmd.lang.Language; +import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.lang.LanguageVersion; + +import picocli.CommandLine.ITypeConverter; +import picocli.CommandLine.TypeConversionException; + +/** + * Provider of candidates for valid language-version combinations. + * + * Beware, the help will report this on runtime, and be accurate to available + * modules in the classpath, but autocomplete will include all available at build time. + */ +public class PmdLanguageVersionTypeSupport implements ITypeConverter, Iterable { + + @Override + public Iterator iterator() { + // Raw language names / -latest versions, such as "java" or "java-latest" + final Stream latestLangReferences = LanguageRegistry.getLanguages().stream() + .map(PmdLanguageTypeSupport::normalizeName).flatMap(name -> Stream.of(name, name + "-latest")); + + // Explicit language-version pairs, such as "java-18" or "apex-54" + final Stream allLangVersionReferences = LanguageRegistry.getLanguages().stream() + .flatMap(l -> l.getVersions().stream()) + .map(PmdLanguageVersionTypeSupport::normalizeName); + + // Collect to a TreeSet to ensure alphabetical order + final TreeSet candidates = Stream.concat(latestLangReferences, allLangVersionReferences) + .collect(Collectors.toCollection(TreeSet::new)); + + return candidates.iterator(); + } + + @Override + public LanguageVersion convert(final String value) throws Exception { + // Is it an exact match? + final Optional langVer = LanguageRegistry.getLanguages().stream() + .flatMap(l -> l.getVersions().stream()) + .filter(lv -> normalizeName(lv).equals(value)).findFirst(); + + if (langVer.isPresent()) { + return langVer.get(); + } + + // This is either a -latest or standalone language name + final String langName; + if (value.endsWith("-latest")) { + langName = value.substring(0, value.length() - "-latest".length()); + } else { + langName = value; + } + + return LanguageRegistry.getLanguages().stream() + .filter(l -> PmdLanguageTypeSupport.normalizeName(l).equals(langName)) + .map(Language::getDefaultVersion).findFirst() + .orElseThrow(() -> new TypeConversionException("Unknown language version: " + value)); + } + + public static String normalizeName(final LanguageVersion langVer) { + return langVer.getTerseName().replace(' ', '-'); + } +} From f66ccd4bfd2c8429cc54876854952e9ccd3027d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Mon, 22 Aug 2022 13:16:22 -0300 Subject: [PATCH 109/254] Rework TreeExportCli - Extract a TreeExportConfiguration object - Move actual logic to a TreeExporter - TreeExportCli remains as a JCommander based CLI (to be deprecated), and usage from pmd-cli is now easier to achieeve --- .../pmd/util/treeexport/TreeExportCli.java | 120 +++--------------- .../treeexport/TreeExportConfiguration.java | 61 +++++++++ .../pmd/util/treeexport/TreeExporter.java | 116 +++++++++++++++++ 3 files changed, 192 insertions(+), 105 deletions(-) create mode 100644 pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExportConfiguration.java create mode 100644 pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExporter.java diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExportCli.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExportCli.java index 7192067590..ffd408d828 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExportCli.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExportCli.java @@ -4,35 +4,21 @@ package net.sourceforge.pmd.util.treeexport; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStreamReader; -import java.io.Reader; -import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Paths; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Properties; import org.apache.commons.lang3.StringEscapeUtils; import org.checkerframework.checker.nullness.qual.Nullable; import net.sourceforge.pmd.annotation.Experimental; -import net.sourceforge.pmd.internal.Slf4jSimpleConfiguration; import net.sourceforge.pmd.lang.Language; import net.sourceforge.pmd.lang.LanguageRegistry; -import net.sourceforge.pmd.lang.LanguageVersion; -import net.sourceforge.pmd.lang.LanguageVersionHandler; -import net.sourceforge.pmd.lang.ast.Parser; -import net.sourceforge.pmd.lang.ast.Parser.ParserTask; -import net.sourceforge.pmd.lang.ast.RootNode; -import net.sourceforge.pmd.lang.ast.SemanticErrorReporter; -import net.sourceforge.pmd.lang.document.TextDocument; -import net.sourceforge.pmd.lang.document.TextFile; -import net.sourceforge.pmd.lang.rule.xpath.Attribute; import net.sourceforge.pmd.properties.PropertyDescriptor; -import net.sourceforge.pmd.properties.PropertySource; import com.beust.jcommander.DynamicParameter; import com.beust.jcommander.JCommander; @@ -71,7 +57,7 @@ public class TreeExportCli { public int runMain(String... args) { try { return runMainOrThrow(args); - } catch (AbortedError e) { + } catch (RuntimeException unused) { return 1; } catch (IOException e) { io.stderr.println("Error: " + e); @@ -97,33 +83,23 @@ public class TreeExportCli { return 0; } + TreeExportConfiguration configuration = new TreeExportConfiguration(); + configuration.setFile(file == null ? null : Paths.get(file)); + configuration.setFormat(format); + configuration.setLanguage(LanguageRegistry.findLanguageByTerseName(language)); + configuration.setReadStdin(readStdin); + configuration.setSourceEncoding(encoding); + + Properties props = new Properties(); + props.putAll(this.properties); + configuration.setProperties(props); - TreeRendererDescriptor descriptor = TreeRenderers.findById(this.format); - if (descriptor == null) { - throw this.bail("Unknown format '" + this.format + "'"); - } - - PropertySource bundle = parseProperties(this, descriptor); - - run(descriptor.produceRenderer(bundle)); + TreeExporter exporter = new TreeExporter(configuration, io); + exporter.export(); + return 0; } - public static PropertySource parseProperties(TreeExportCli cli, TreeRendererDescriptor descriptor) { - PropertySource bundle = descriptor.newPropertyBundle(); - - for (String key : cli.properties.keySet()) { - PropertyDescriptor d = bundle.getPropertyDescriptor(key); - if (d == null) { - throw cli.bail("Unknown property '" + key + "'"); - } - - setProperty(d, bundle, cli.properties.get(key)); - } - return bundle; - } - - private void usage(JCommander commander) { StringBuilder sb = new StringBuilder(); commander.setProgramName("ast-dump"); @@ -175,70 +151,4 @@ public class TreeExportCli { private String getDefault(PropertyDescriptor prop) { return StringEscapeUtils.escapeJava(prop.asDelimitedString(prop.defaultValue())); } - - private void run(TreeRenderer renderer) throws IOException { - run(LanguageRegistry.PMD, renderer); - } - - private void run(LanguageRegistry registry, TreeRenderer renderer) throws IOException { - printWarning(); - - Language lang = registry.getLanguageById(language); - if (lang == null) { - throw bail("Unknown language '" + language + "', one of [" - + registry.commaSeparatedList(Language::getId) - + "] was expected"); - } - - LanguageVersion langVersion = lang.getDefaultVersion(); - LanguageVersionHandler languageHandler = langVersion.getLanguageVersionHandler(); - Parser parser = languageHandler.getParser(); - - @SuppressWarnings("PMD.CloseResource") TextFile textFile; - if (file == null && !readStdin) { - throw bail("One of --file or --read-stdin must be mentioned"); - } else if (readStdin) { - io.stderr.println("Reading from stdin..."); - textFile = TextFile.forReader(readFromSystemIn(), "stdin", langVersion); - } else { - textFile = TextFile.forPath(Paths.get(file), Charset.forName(encoding), langVersion); - } - - // disable warnings for deprecated attributes - Slf4jSimpleConfiguration.disableLogging(Attribute.class); - - try (TextDocument textDocument = TextDocument.create(textFile)) { - - ParserTask task = new ParserTask(textDocument, SemanticErrorReporter.noop(), TreeExportCli.class.getClassLoader()); - RootNode root = parser.parse(task); - - renderer.renderSubtree(root, io.stdout); - } - } - - private Reader readFromSystemIn() { - return new BufferedReader(new InputStreamReader(io.stdin)); - } - - private void printWarning() { - io.stderr.println("-------------------------------------------------------------------------------"); - io.stderr.println("This command line utility is experimental. It might change at any time without"); - io.stderr.println("prior notice."); - io.stderr.println("-------------------------------------------------------------------------------"); - } - - private static void setProperty(PropertyDescriptor descriptor, PropertySource bundle, String value) { - bundle.setProperty(descriptor, descriptor.valueFrom(value)); - } - - - private AbortedError bail(String message) { - io.stderr.println(message); - io.stderr.println("Use --help for usage information"); - return new AbortedError(); - } - - private static final class AbortedError extends Error { - - } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExportConfiguration.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExportConfiguration.java new file mode 100644 index 0000000000..d7cc078cdc --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExportConfiguration.java @@ -0,0 +1,61 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.treeexport; + +import java.nio.file.Path; +import java.util.Properties; + +import net.sourceforge.pmd.AbstractConfiguration; +import net.sourceforge.pmd.lang.Language; +import net.sourceforge.pmd.lang.LanguageRegistry; + +public class TreeExportConfiguration extends AbstractConfiguration { + + private String format = "xml"; + private Language language = LanguageRegistry.getDefaultLanguage(); + private Properties properties = new Properties(); + private Path file; + private boolean readStdin; + + public String getFormat() { + return format; + } + + public Language getLanguage() { + return language; + } + + public Properties getProperties() { + return properties; + } + + public Path getFile() { + return file; + } + + public boolean isReadStdin() { + return readStdin; + } + + public void setFormat(String format) { + this.format = format; + } + + public void setLanguage(Language language) { + this.language = language; + } + + public void setProperties(Properties properties) { + this.properties = properties; + } + + public void setFile(Path file) { + this.file = file; + } + + public void setReadStdin(boolean readStdin) { + this.readStdin = readStdin; + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExporter.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExporter.java new file mode 100644 index 0000000000..04fc874a77 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExporter.java @@ -0,0 +1,116 @@ +package net.sourceforge.pmd.util.treeexport; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.Map.Entry; + +import net.sourceforge.pmd.annotation.Experimental; +import net.sourceforge.pmd.internal.Slf4jSimpleConfiguration; +import net.sourceforge.pmd.lang.LanguageVersion; +import net.sourceforge.pmd.lang.LanguageVersionHandler; +import net.sourceforge.pmd.lang.ast.Parser; +import net.sourceforge.pmd.lang.ast.Parser.ParserTask; +import net.sourceforge.pmd.lang.ast.RootNode; +import net.sourceforge.pmd.lang.ast.SemanticErrorReporter; +import net.sourceforge.pmd.lang.document.TextDocument; +import net.sourceforge.pmd.lang.document.TextFile; +import net.sourceforge.pmd.lang.rule.xpath.Attribute; +import net.sourceforge.pmd.properties.PropertyDescriptor; +import net.sourceforge.pmd.properties.PropertySource; + +@Experimental +public class TreeExporter { + + private final TreeExportConfiguration configuration; + private final Io io; + + public TreeExporter(final TreeExportConfiguration configuration) { + this(configuration, Io.SYSTEM); + } + + TreeExporter(final TreeExportConfiguration configuration, final Io io) { + this.configuration = configuration; + this.io = io; + } + + public void export() throws IOException { + TreeRendererDescriptor descriptor = TreeRenderers.findById(configuration.getFormat()); + if (descriptor == null) { + throw this.bail("Unknown format '" + configuration.getFormat() + "'"); + } + + PropertySource bundle = parseProperties(descriptor); + + run(descriptor.produceRenderer(bundle)); + } + + private void run(final TreeRenderer renderer) throws IOException { + printWarning(); + + LanguageVersion langVersion = configuration.getLanguage().getDefaultVersion(); + LanguageVersionHandler languageHandler = langVersion.getLanguageVersionHandler(); + Parser parser = languageHandler.getParser(); + + @SuppressWarnings("PMD.CloseResource") + TextFile textFile; + if (configuration.isReadStdin()) { + io.stderr.println("Reading from stdin..."); + textFile = TextFile.forReader(readFromSystemIn(), "stdin", langVersion); + } else { + textFile = TextFile.forPath(configuration.getFile(), configuration.getSourceEncoding(), langVersion); + } + + // disable warnings for deprecated attributes + Slf4jSimpleConfiguration.disableLogging(Attribute.class); + + try (TextDocument textDocument = TextDocument.create(textFile)) { + + ParserTask task = new ParserTask(textDocument, SemanticErrorReporter.noop(), TreeExportCli.class.getClassLoader()); + RootNode root = parser.parse(task); + + renderer.renderSubtree(root, io.stdout); + } + } + + private Reader readFromSystemIn() { + return new BufferedReader(new InputStreamReader(io.stdin)); + } + + private void printWarning() { + io.stderr.println("-------------------------------------------------------------------------------"); + io.stderr.println("This command line utility is experimental. It might change at any time without"); + io.stderr.println("prior notice."); + io.stderr.println("-------------------------------------------------------------------------------"); + } + + private PropertySource parseProperties(TreeRendererDescriptor descriptor) { + PropertySource bundle = descriptor.newPropertyBundle(); + + for (Entry prop : configuration.getProperties().entrySet()) { + PropertyDescriptor d = bundle.getPropertyDescriptor(prop.getKey().toString()); + if (d == null) { + throw bail("Unknown property '" + prop.getKey() + "'"); + } + + setProperty(d, bundle, prop.getValue().toString()); + } + return bundle; + } + + private void setProperty(PropertyDescriptor descriptor, PropertySource bundle, String value) { + bundle.setProperty(descriptor, descriptor.valueFrom(value)); + } + + private AbortedException bail(String message) { + io.stderr.println(message); + io.stderr.println("Use --help for usage information"); + return new AbortedException(); + } + + private static final class AbortedException extends RuntimeException { + + private static final long serialVersionUID = -1925142332978792215L; + } +} From 3689456ebcbe6d80c56aa24486dbb9b44256de81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Mon, 22 Aug 2022 13:18:09 -0300 Subject: [PATCH 110/254] Implement TreeExportCommand --- .../cli/commands/internal/PmdRootCommand.java | 2 +- .../commands/internal/TreeExportCommand.java | 152 ++++++++++++++++++ 2 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/TreeExportCommand.java diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdRootCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdRootCommand.java index b21c7b72cc..380b46929a 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdRootCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdRootCommand.java @@ -14,7 +14,7 @@ import picocli.CommandLine.IVersionProvider; exitCodeListHeading = "Exit Codes:%n", exitCodeList = { "0:Succesful analysis, no violations found", "1:An unexpected error occurred during execution", "2:Usage error, please refer to the command help", "4:Successful analysis, at least 1 violation found" }, - subcommands = { PmdCommand.class, CpdCommand.class, DesignerCommand.class, CpdGuiCommand.class }) + subcommands = { PmdCommand.class, CpdCommand.class, DesignerCommand.class, CpdGuiCommand.class, TreeExportCommand.class }) public class PmdRootCommand { } diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/TreeExportCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/TreeExportCommand.java new file mode 100644 index 0000000000..c61b6ef35b --- /dev/null +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/TreeExportCommand.java @@ -0,0 +1,152 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cli.commands.internal; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Properties; + +import org.slf4j.LoggerFactory; + +import net.sourceforge.pmd.cli.commands.mixins.internal.EncodingMixin; +import net.sourceforge.pmd.cli.commands.typesupport.internal.PmdLanguageTypeSupport; +import net.sourceforge.pmd.cli.internal.ExecutionResult; +import net.sourceforge.pmd.internal.LogMessages; +import net.sourceforge.pmd.lang.Language; +import net.sourceforge.pmd.properties.PropertyDescriptor; +import net.sourceforge.pmd.properties.PropertySource; +import net.sourceforge.pmd.util.StringUtil; +import net.sourceforge.pmd.util.log.internal.SimpleMessageReporter; +import net.sourceforge.pmd.util.treeexport.TreeExportConfiguration; +import net.sourceforge.pmd.util.treeexport.TreeExporter; +import net.sourceforge.pmd.util.treeexport.TreeRendererDescriptor; +import net.sourceforge.pmd.util.treeexport.TreeRenderers; + +import picocli.CommandLine.Command; +import picocli.CommandLine.Mixin; +import picocli.CommandLine.Option; +import picocli.CommandLine.ParameterException; + +@Command(name = "ast-dump", description = "Experimental: dumps the AST of parsing source code") +public class TreeExportCommand extends AbstractPmdSubcommand { + + static { + final StringBuilder reportPropertiesHelp = new StringBuilder(); + + for (final TreeRendererDescriptor renderer : TreeRenderers.registeredRenderers()) { + final PropertySource propertyBundle = renderer.newPropertyBundle(); + if (propertyBundle.getPropertyDescriptors().size() > 0) { + reportPropertiesHelp.append(renderer.id() + ":" + System.getProperty("line.separator")); + for (final PropertyDescriptor property : propertyBundle.getPropertyDescriptors()) { + reportPropertiesHelp.append(" ").append(property.name()).append(" - ") + .append(property.description()).append(System.getProperty("line.separator")); + final Object deflt = property.defaultValue(); + if (deflt != null && !"".equals(deflt)) { + reportPropertiesHelp.append(" Default: ").append(StringUtil.escapeWhitespace(deflt)) + .append(System.getProperty("line.separator")); + } + } + } + } + + // System Properties are the easier way to inject dynamically computed values into the help of an option + System.setProperty("pmd-cli.tree-export.report.properties.help", reportPropertiesHelp.toString()); + } + + @Mixin + private EncodingMixin encoding; + + @Option(names = { "--format", "-f" }, defaultValue = "xml", + description = "The output format.%nValid values: ${COMPLETION-CANDIDATES}", + completionCandidates = TreeRenderersCandidates.class) + private String format; + + @Option(names = { "--language", "-l" }, defaultValue = "java", + description = "The source code language.%nValid values: ${COMPLETION-CANDIDATES}", + completionCandidates = PmdLanguageTypeSupport.class, converter = PmdLanguageTypeSupport.class) + private Language language; + + @Option(names = "-P", description = "Key-value pair defining a property for the report format.%n" + + "Supported values for each report format:%n${sys:pmd-cli.tree-export.report.properties.help}", + completionCandidates = TreeExportReportPropertiesCandidates.class) + private Properties properties; + + @Option(names = "--file", description = "The file to parse and dump.") + private Path file; + + @Option(names = { "--read-stdin", "-i" }, description = "Read source from standard input.") + private boolean readStdin; + + public TreeExportConfiguration toConfiguration() { + final TreeExportConfiguration configuration = new TreeExportConfiguration(); + configuration.setDebug(debug); + configuration.setFile(file); + configuration.setFormat(format); + configuration.setLanguage(language); + configuration.setProperties(properties); + configuration.setReadStdin(readStdin); + configuration.setSourceEncoding(encoding.getEncoding().name()); + + return configuration; + } + + @Override + protected void validate() throws ParameterException { + super.validate(); + + if (file == null && !readStdin) { + throw new ParameterException(spec.commandLine(), "One of --file or --read-stdin must be used."); + } + } + + @Override + protected ExecutionResult execute() { + final TreeExporter exporter = new TreeExporter(toConfiguration()); + try { + exporter.export(); + return ExecutionResult.OK; + } catch (final IOException e) { + final SimpleMessageReporter reporter = new SimpleMessageReporter(LoggerFactory.getLogger(TreeExportCommand.class)); + reporter.error(e, LogMessages.errorDetectedMessage(1, "ast-dump")); + + return ExecutionResult.ERROR; + } + } + + /** + * Provides completion candidates for the report format. + */ + private static class TreeRenderersCandidates implements Iterable { + + @Override + public Iterator iterator() { + return TreeRenderers.registeredRenderers().stream().map(TreeRendererDescriptor::id).iterator(); + } + } + + /** + * Provider of candidates for valid report properties. + * + * Check the help for which ones are supported by each report format and possible values. + */ + private static class TreeExportReportPropertiesCandidates implements Iterable { + + @Override + public Iterator iterator() { + final List propertyNames = new ArrayList<>(); + for (final TreeRendererDescriptor renderer : TreeRenderers.registeredRenderers()) { + final PropertySource propertyBundle = renderer.newPropertyBundle(); + + for (final PropertyDescriptor property : propertyBundle.getPropertyDescriptors()) { + propertyNames.add(property.name()); + } + } + return propertyNames.iterator(); + } + } +} From 0641a9c3164bd52aabf9edb2ee5d2e895462fbf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Mon, 22 Aug 2022 13:18:41 -0300 Subject: [PATCH 111/254] Fix help text newlines --- .../net/sourceforge/pmd/cli/commands/internal/CpdCommand.java | 2 +- .../net/sourceforge/pmd/cli/commands/internal/PmdCommand.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java index 54fcc04b3a..1ffe325438 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java @@ -30,7 +30,7 @@ import picocli.CommandLine.ParameterException; description = "Copy/Paste Detector - find duplicate code") public class CpdCommand extends AbstractAnalysisPmdSubcommand { - @Option(names = "--language", description = "Source code language.%nValid values: ${COMPLETION-CANDIDATES}%n", + @Option(names = { "--language", "-l" }, description = "The source code language.%nValid values: ${COMPLETION-CANDIDATES}", defaultValue = "java", converter = CpdLanguageConverter.class, completionCandidates = CpdLanguageCompletionCandidates.class) private Language language; diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java index c7f94176e2..b6cb76b090 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java @@ -178,7 +178,7 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { } @Option(names = "--use-version", defaultValue = "java-latest", - description = "Sepcify the language and version PMD should use.%nValid values: ${COMPLETION-CANDIDATES}%n", + description = "The language version PMD should use when parsing source code.%nValid values: ${COMPLETION-CANDIDATES}", completionCandidates = PmdLanguageVersionTypeSupport.class, converter = PmdLanguageVersionTypeSupport.class) public void setLanguageVersion(final List languageVersion) { // Make sure we only set 1 version per language @@ -198,7 +198,7 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { description = "Force a language to be used for all input files, irrespective of file names. " + "When using this option, the automatic language selection by extension is disabled, and PMD " + "tries to parse all input files with the given language's parser. " - + "Parsing errors are ignored.%nValid values: ${COMPLETION-CANDIDATES}%n", + + "Parsing errors are ignored.%nValid values: ${COMPLETION-CANDIDATES}", completionCandidates = PmdLanguageTypeSupport.class, converter = PmdLanguageTypeSupport.class) public void setForceLanguage(final Language forceLanguage) { this.forceLanguage = forceLanguage; From 029602abac8af2bc1be90ba7d3da57ba722a0a0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Mon, 22 Aug 2022 13:21:34 -0300 Subject: [PATCH 112/254] Extract CpdLanguageTypeSupport --- .../pmd/cli/commands/internal/CpdCommand.java | 28 ++-------------- .../internal/CpdLanguageTypeSupport.java | 33 +++++++++++++++++++ 2 files changed, 35 insertions(+), 26 deletions(-) create mode 100644 pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/typesupport/internal/CpdLanguageTypeSupport.java diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java index 1ffe325438..717f9b660f 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java @@ -14,15 +14,14 @@ import java.util.Iterator; import java.util.List; import java.util.stream.Collectors; +import net.sourceforge.pmd.cli.commands.typesupport.internal.CpdLanguageTypeSupport; import net.sourceforge.pmd.cli.internal.ExecutionResult; import net.sourceforge.pmd.cpd.CPD; import net.sourceforge.pmd.cpd.CPDConfiguration; import net.sourceforge.pmd.cpd.Language; -import net.sourceforge.pmd.cpd.LanguageFactory; import net.sourceforge.pmd.cpd.Tokenizer; import picocli.CommandLine.Command; -import picocli.CommandLine.ITypeConverter; import picocli.CommandLine.Option; import picocli.CommandLine.ParameterException; @@ -31,7 +30,7 @@ import picocli.CommandLine.ParameterException; public class CpdCommand extends AbstractAnalysisPmdSubcommand { @Option(names = { "--language", "-l" }, description = "The source code language.%nValid values: ${COMPLETION-CANDIDATES}", - defaultValue = "java", converter = CpdLanguageConverter.class, completionCandidates = CpdLanguageCompletionCandidates.class) + defaultValue = "java", converter = CpdLanguageTypeSupport.class, completionCandidates = CpdLanguageTypeSupport.class) private Language language; // TODO : Set a default for this value? @@ -148,29 +147,6 @@ public class CpdCommand extends AbstractAnalysisPmdSubcommand { return ExecutionResult.OK; } - /** - * Provider of candidates for valid Languages. - */ - private static class CpdLanguageCompletionCandidates implements Iterable { - - @Override - public Iterator iterator() { - return Arrays.stream(LanguageFactory.supportedLanguages).iterator(); - } - } - - /** - * Maps a String back to a {@code Language}. - */ - private static class CpdLanguageConverter implements ITypeConverter { - - @Override - public Language convert(final String languageString) { - // TODO : If an unknown value is passed, AnyLanguage is returned silently… - return LanguageFactory.createLanguage(languageString); - } - } - /** * Provider of candidates for valid report formats. */ diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/typesupport/internal/CpdLanguageTypeSupport.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/typesupport/internal/CpdLanguageTypeSupport.java new file mode 100644 index 0000000000..29868b8da7 --- /dev/null +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/typesupport/internal/CpdLanguageTypeSupport.java @@ -0,0 +1,33 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cli.commands.typesupport.internal; + +import java.util.Arrays; +import java.util.Iterator; + +import net.sourceforge.pmd.cpd.Language; +import net.sourceforge.pmd.cpd.LanguageFactory; + +import picocli.CommandLine.ITypeConverter; + +/** + * Provider of candidates / conversion support for supported CPD languages. + * + * Beware, the help will report this on runtime, and be accurate to available + * modules in the classpath, but autocomplete will include all available at build time. + */ +public class CpdLanguageTypeSupport implements ITypeConverter, Iterable { + + @Override + public Iterator iterator() { + return Arrays.stream(LanguageFactory.supportedLanguages).iterator(); + } + + @Override + public Language convert(final String languageString) { + // TODO : If an unknown value is passed, AnyLanguage is returned silently… + return LanguageFactory.createLanguage(languageString); + } +} From 939369ecd724caa5f05610aa85491a00beed7ee4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Mon, 22 Aug 2022 14:32:57 -0300 Subject: [PATCH 113/254] Add missing license --- .../net/sourceforge/pmd/util/treeexport/TreeExporter.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExporter.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExporter.java index 04fc874a77..f47d87c488 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExporter.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExporter.java @@ -1,3 +1,7 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + package net.sourceforge.pmd.util.treeexport; import java.io.BufferedReader; From 3230038bc2023732bddff91fdfd5b957875c58ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Mon, 22 Aug 2022 18:24:46 -0300 Subject: [PATCH 114/254] Fix broken build --- .../java/net/sourceforge/pmd/it/BinaryDistributionIT.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java b/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java index a011878546..d39dd7965d 100644 --- a/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java +++ b/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java @@ -18,7 +18,7 @@ import java.util.zip.ZipFile; import org.junit.Test; import net.sourceforge.pmd.PMDVersion; -import net.sourceforge.pmd.cli.internal.CliMessages; +import net.sourceforge.pmd.internal.LogMessages; public class BinaryDistributionIT extends AbstractBinaryDistributionTest { @@ -98,7 +98,7 @@ public class BinaryDistributionIT extends AbstractBinaryDistributionTest { @Test public void testPmdNoArgs() throws Exception { ExecutionResult result = PMDExecutor.runPMD(tempDir); // without any argument, display usage help and error - result.assertExecutionResultErrOutput(1, CliMessages.runWithHelpFlagMessage()); + result.assertExecutionResultErrOutput(1, LogMessages.runWithHelpFlagMessage()); } @Test @@ -135,7 +135,7 @@ public class BinaryDistributionIT extends AbstractBinaryDistributionTest { ExecutionResult result; result = CpdExecutor.runCpd(tempDir); // without any argument, display usage help and error - result.assertExecutionResultErrOutput(1, CliMessages.runWithHelpFlagMessage()); + result.assertExecutionResultErrOutput(1, LogMessages.runWithHelpFlagMessage()); result = CpdExecutor.runCpd(tempDir, "-h"); result.assertExecutionResult(0, SUPPORTED_LANGUAGES_CPD); From 78d046c7d18fdb8d382abd1639b0397b75984995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Mon, 22 Aug 2022 18:27:44 -0300 Subject: [PATCH 115/254] Make fail on violation negatable - Instead of doing `--fail-on-violation false` we now do `--no-fail-on-violation` or `--fail-on-violation` to be explicit about the default --- .../commands/internal/AbstractAnalysisPmdSubcommand.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractAnalysisPmdSubcommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractAnalysisPmdSubcommand.java index de9ccd8eac..c89336d460 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractAnalysisPmdSubcommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractAnalysisPmdSubcommand.java @@ -39,10 +39,10 @@ public abstract class AbstractAnalysisPmdSubcommand extends AbstractPmdSubcomman + "One of --dir, --file-list or --uri must be provided.") protected URI uri; - @Option(names = "--fail-on-violation", + @Option(names = "--no-fail-on-violation", description = "By default PMD exits with status 4 if violations are found. " - + "Disable this option with '--fail-on-violation false' to exit with 0 instead and just write the report.", - defaultValue = "true", arity = "1") + + "Disable this option with '--no-fail-on-violation' to exit with 0 instead and just write the report.", + defaultValue = "true", negatable = true) protected boolean failOnViolation; @Override From e898993ccfb906f6cdc006ff8ddc891b6eb1b651 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Mon, 22 Aug 2022 18:28:58 -0300 Subject: [PATCH 116/254] Deprecate the TreeExportCli - This is an experimental class, so we may as well remove it, but will confer with other maintainers first --- .../java/net/sourceforge/pmd/util/treeexport/TreeExportCli.java | 1 + 1 file changed, 1 insertion(+) diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExportCli.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExportCli.java index ffd408d828..ea67b514de 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExportCli.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExportCli.java @@ -25,6 +25,7 @@ import com.beust.jcommander.JCommander; import com.beust.jcommander.Parameter; import com.beust.jcommander.ParameterException; +@Deprecated @Experimental public class TreeExportCli { From 6afe892073f598be144a51c25bef4353d14b4349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Tue, 23 Aug 2022 18:39:56 -0300 Subject: [PATCH 117/254] Refactor Io.SYSTEM to avoid sticking - This will help unit testing, as we get references to the streams when the process starts vs when the class loads, so different tests can tap / mock STD* without forking for each test. --- .../main/java/net/sourceforge/pmd/util/treeexport/Io.java | 5 ++++- .../net/sourceforge/pmd/util/treeexport/TreeExportCli.java | 2 +- .../net/sourceforge/pmd/util/treeexport/TreeExporter.java | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/Io.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/Io.java index 74380498f3..2ff20bac74 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/Io.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/Io.java @@ -14,7 +14,6 @@ import java.io.PrintStream; */ final class Io { - public static final Io SYSTEM = new Io(System.out, System.err, System.in); public final PrintStream stdout; public final PrintStream stderr; public final InputStream stdin; @@ -24,4 +23,8 @@ final class Io { this.stderr = stderr; this.stdin = stdin; } + + public static Io system() { + return new Io(System.out, System.err, System.in); + } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExportCli.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExportCli.java index ea67b514de..92d62e3b21 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExportCli.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExportCli.java @@ -51,7 +51,7 @@ public class TreeExportCli { } public static void main(String... args) { - TreeExportCli cli = new TreeExportCli(Io.SYSTEM); + TreeExportCli cli = new TreeExportCli(Io.system()); System.exit(cli.runMain(args)); } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExporter.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExporter.java index f47d87c488..b1c4bf23cd 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExporter.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExporter.java @@ -31,7 +31,7 @@ public class TreeExporter { private final Io io; public TreeExporter(final TreeExportConfiguration configuration) { - this(configuration, Io.SYSTEM); + this(configuration, Io.system()); } TreeExporter(final TreeExportConfiguration configuration, final Io io) { From 7b416bc12d5c3cd1bfc6a7503e81dfd3c5569243 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Tue, 23 Aug 2022 18:41:59 -0300 Subject: [PATCH 118/254] Clean up todos --- .../net/sourceforge/pmd/cli/commands/internal/CpdCommand.java | 1 - .../net/sourceforge/pmd/cli/internal/ExecutionResult.java | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java index 717f9b660f..349b3c09b9 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java @@ -33,7 +33,6 @@ public class CpdCommand extends AbstractAnalysisPmdSubcommand { defaultValue = "java", converter = CpdLanguageTypeSupport.class, completionCandidates = CpdLanguageTypeSupport.class) private Language language; - // TODO : Set a default for this value? @Option(names = "--minimum-tokens", description = "The minimum token length which should be reported as a duplicate.", required = true) private int minimumTokens; diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/internal/ExecutionResult.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/internal/ExecutionResult.java index 4c60674fde..8b0da06648 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/internal/ExecutionResult.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/internal/ExecutionResult.java @@ -4,7 +4,9 @@ package net.sourceforge.pmd.cli.internal; -// TODO : Unify with PMD.StatusCode / CPD.StatusCode +/** + * The execution result of any given command. + */ public enum ExecutionResult { OK(0), ERROR(1), From afde91247fdd131c500e8e1f13ea5fca29c9e47e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Tue, 23 Aug 2022 18:55:20 -0300 Subject: [PATCH 119/254] Make --no-progress negatable --- .../pmd/cli/commands/internal/PmdCommand.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java index b6cb76b090..31320899e1 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java @@ -105,7 +105,7 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { private boolean noCache; - private boolean noProgressBar; + private boolean showProgressBar; @Option(names = { "--rulesets", "-R" }, description = "Path to a ruleset xml file. " @@ -244,9 +244,10 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { this.threads = threads; } - @Option(names = "--no-progress", description = "Disables progress bar indicator of live analysis progress.") - public void setNoProgressBar(final boolean noProgressBar) { - this.noProgressBar = noProgressBar; + @Option(names = "--no-progress", negatable = true, defaultValue = "true", + description = "Enables / disables progress bar indicator of live analysis progress.") + public void setShowProgressBar(final boolean showProgressBar) { + this.showProgressBar = showProgressBar; } /** @@ -277,7 +278,7 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { configuration.setFailOnViolation(failOnViolation); configuration.setAnalysisCacheLocation(cacheLocation != null ? cacheLocation.toString() : null); configuration.setIgnoreIncrementalAnalysis(noCache); - configuration.setProgressBar(!noProgressBar); + configuration.setProgressBar(showProgressBar); if (languageVersion != null) { configuration.setDefaultLanguageVersions(languageVersion); From 782459560358c2bca47e17c45fa0a230ef0c7f44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Tue, 23 Aug 2022 18:58:21 -0300 Subject: [PATCH 120/254] Logging cleanups --- .../pmd/cli/commands/internal/CpdCommand.java | 15 ++++++++------- .../pmd/cli/commands/internal/PmdCommand.java | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java index 349b3c09b9..98ed9ed1a6 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java @@ -14,12 +14,16 @@ import java.util.Iterator; import java.util.List; import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import net.sourceforge.pmd.cli.commands.typesupport.internal.CpdLanguageTypeSupport; import net.sourceforge.pmd.cli.internal.ExecutionResult; import net.sourceforge.pmd.cpd.CPD; import net.sourceforge.pmd.cpd.CPDConfiguration; import net.sourceforge.pmd.cpd.Language; import net.sourceforge.pmd.cpd.Tokenizer; +import net.sourceforge.pmd.internal.LogMessages; import picocli.CommandLine.Command; import picocli.CommandLine.Option; @@ -115,15 +119,13 @@ public class CpdCommand extends AbstractAnalysisPmdSubcommand { configuration.setCPDRenderer(CPDConfiguration.getCPDRendererFromString(rendererName, encoding.getEncoding().name())); - // TODO - // Setup CLI message reporter - //configuration.setReporter(new SimpleMessageReporter(LoggerFactory.getLogger(CpdCommand.class))); - return configuration; } @Override protected ExecutionResult execute() { + final Logger logger = LoggerFactory.getLogger(CpdCommand.class); + // TODO : Create a new CpdAnalysis to match PmdAnalysis final CPDConfiguration configuration = toConfiguration(); final CPD cpd = new CPD(configuration); @@ -137,9 +139,8 @@ public class CpdCommand extends AbstractAnalysisPmdSubcommand { return ExecutionResult.VIOLATIONS_FOUND; } } catch (IOException | RuntimeException e) { - // TODO - //LOG.debug(e.toString(), e); - //LOG.error(CliMessages.errorDetectedMessage(1, CPDCommandLineInterface.PROGRAM_NAME)); + logger.debug(e.toString(), e); + logger.error(LogMessages.errorDetectedMessage(1, "cpd")); return ExecutionResult.ERROR; } diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java index 31320899e1..1d3bcb6d03 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java @@ -347,7 +347,7 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { } private void printErrorDetected(MessageReporter reporter, int errors) { - String msg = LogMessages.errorDetectedMessage(errors, "PMD"); + String msg = LogMessages.errorDetectedMessage(errors, "pmd"); // note: using error level here increments the error count of the reporter, // which we don't want. reporter.info(StringUtil.quoteMessageFormat(msg)); From 3a9cf2032376a4923b6b18091ac196fb3af71b8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Tue, 23 Aug 2022 23:18:31 -0300 Subject: [PATCH 121/254] Fix build --- .../test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java b/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java index d39dd7965d..2114dc4697 100644 --- a/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java +++ b/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java @@ -125,7 +125,7 @@ public class BinaryDistributionIT extends AbstractBinaryDistributionTest { String srcDir = new File(".", "src/test/resources/sample-source/unparsable/").getAbsolutePath(); ExecutionResult result = PMDExecutor.runPMDRules(folder.newFile().toPath(), tempDir, srcDir, "src/test/resources/rulesets/sample-ruleset.xml"); - result.assertExecutionResultErrOutput(0, "Run with --debug to see a stack-trace."); + result.assertExecutionResultErrOutput(0, "Run in verbose mode to see a stack-trace."); } @Test From f60fd97ee4216f9a42e1b36bd5166d5064b052f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Tue, 23 Aug 2022 23:18:48 -0300 Subject: [PATCH 122/254] Bring on pmd-cli into pmd-dist --- pmd-dist/pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pmd-dist/pom.xml b/pmd-dist/pom.xml index a774170524..5d482c5a87 100644 --- a/pmd-dist/pom.xml +++ b/pmd-dist/pom.xml @@ -129,6 +129,11 @@ pmd-apex ${project.version} + + net.sourceforge.pmd + pmd-cli + ${project.version} + net.sourceforge.pmd pmd-core From a4a5affe83600d08f49609ebb681122a33147d0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Tue, 23 Aug 2022 23:52:19 -0300 Subject: [PATCH 123/254] Include everything in pmd-dist - Include both the pmd-cli module and the completion script --- pmd-cli/pom.xml | 22 +++++++++++++++++++ pmd-dist/pom.xml | 8 +++++++ .../src/main/resources/assemblies/pmd-bin.xml | 17 ++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/pmd-cli/pom.xml b/pmd-cli/pom.xml index 2a9b53f3c1..0773366c89 100644 --- a/pmd-cli/pom.xml +++ b/pmd-cli/pom.xml @@ -47,6 +47,28 @@ + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-completion-artifact + + attach-artifact + + + + + ${project.build.directory}/pmd_completion.sh + sh + completion + + + + + + diff --git a/pmd-dist/pom.xml b/pmd-dist/pom.xml index 5d482c5a87..8bd93b1d3d 100644 --- a/pmd-dist/pom.xml +++ b/pmd-dist/pom.xml @@ -134,6 +134,14 @@ pmd-cli ${project.version} + + + net.sourceforge.pmd + pmd-cli + ${project.version} + sh + completion + net.sourceforge.pmd pmd-core diff --git a/pmd-dist/src/main/resources/assemblies/pmd-bin.xml b/pmd-dist/src/main/resources/assemblies/pmd-bin.xml index acd37c2cfe..c783dc530f 100644 --- a/pmd-dist/src/main/resources/assemblies/pmd-bin.xml +++ b/pmd-dist/src/main/resources/assemblies/pmd-bin.xml @@ -54,8 +54,25 @@ + runtime + + net.sourceforge.pmd:pmd-cli:sh:completion:* + + pmd-completion.sh + shell + 0755 + 0644 + false + + + + + runtime + + net.sourceforge.pmd:pmd-cli:sh:completion:* + lib 0755 0644 From 924ca85ff545fde1b7aa707c72ed1063fb1a95e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Tue, 23 Aug 2022 23:53:06 -0300 Subject: [PATCH 124/254] Add ast-dump CLI test --- .../pmd/cli/TreeExportCliTest.java | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 pmd-cli/src/test/java/net/sourceforge/pmd/cli/TreeExportCliTest.java diff --git a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/TreeExportCliTest.java b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/TreeExportCliTest.java new file mode 100644 index 0000000000..47cab84e42 --- /dev/null +++ b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/TreeExportCliTest.java @@ -0,0 +1,74 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cli; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import com.github.stefanbirkner.systemlambda.SystemLambda; + +class TreeExportCliTest extends BaseCliTest { + + @TempDir + private Path tmp; + + @Test + void testReadStandardInput() throws Exception { + SystemLambda.withTextFromSystemIn("(a(b))").execute(() -> { + final String output = runCliSuccessfully("-i", "-f", "xml", "-PlineSeparator=LF"); + + assertThat(output, containsString("\n" + + "\n" + + " \n" + + " \n" + + " \n" + + "")); + }); + } + + @Test + void testReadFile() throws Exception { + File file = newFileWithContents("(a(b))"); + final String output = runCliSuccessfully("--file", file.getAbsolutePath(), "-f", "xml", "-PlineSeparator=LF"); + assertThat(output, containsString("\n" + + "\n" + + " \n" + + " \n" + + " \n" + + "")); + } + + private File newFileWithContents(String data) throws IOException { + File file = Files.createTempFile(tmp, "TreeExportCliTest", "data").toFile(); + try (BufferedWriter br = Files.newBufferedWriter(file.toPath(), StandardCharsets.UTF_8)) { + br.write(data); + } + return file; + } + + @Override + protected List cliStandardArgs() { + final List argList = new ArrayList<>(); + + // Set program name and set dummy language + argList.add("ast-dump"); + argList.add("-l"); + argList.add("dummy"); + + return argList; + } +} From e49e5d2ddbe4340d9d455e60e88ab7433d98eebb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 24 Aug 2022 15:02:17 -0300 Subject: [PATCH 125/254] Update run.sh - This is now plainly "pmd" and can drop lots of logic that is now part of the standard CLI --- .../src/main/resources/assemblies/pmd-bin.xml | 2 +- .../main/resources/scripts/{run.sh => pmd} | 41 +------------------ 2 files changed, 2 insertions(+), 41 deletions(-) rename pmd-dist/src/main/resources/scripts/{run.sh => pmd} (85%) diff --git a/pmd-dist/src/main/resources/assemblies/pmd-bin.xml b/pmd-dist/src/main/resources/assemblies/pmd-bin.xml index c783dc530f..2215aa3265 100644 --- a/pmd-dist/src/main/resources/assemblies/pmd-bin.xml +++ b/pmd-dist/src/main/resources/assemblies/pmd-bin.xml @@ -23,7 +23,7 @@ - run.sh + pmd target/extra-resources/scripts bin diff --git a/pmd-dist/src/main/resources/scripts/run.sh b/pmd-dist/src/main/resources/scripts/pmd similarity index 85% rename from pmd-dist/src/main/resources/scripts/run.sh rename to pmd-dist/src/main/resources/scripts/pmd index fc0a407fad..c879341bdf 100755 --- a/pmd-dist/src/main/resources/scripts/run.sh +++ b/pmd-dist/src/main/resources/scripts/pmd @@ -1,18 +1,5 @@ #!/bin/bash -usage() { - echo "Usage:" - echo " $(basename "$0") [-h|-v] ..." - echo "" - echo "application-name: valid options are: $(valid_app_options)" - echo "-h print this help" - echo "-v display PMD's version" -} - -valid_app_options () { - echo "pmd, cpd, cpdgui, designer, ast-dump" -} - is_cygwin() { case "$(uname)" in CYGWIN*|MINGW*) @@ -191,32 +178,6 @@ function add_openjfx_classpath() { } readonly APPNAME="${1}" -if [ -z "${APPNAME}" ]; then - usage - exit 1 -fi -shift - -case "${APPNAME}" in - "pmd") - readonly CLASSNAME="net.sourceforge.pmd.PMD" - ;; - "cpd") - readonly CLASSNAME="net.sourceforge.pmd.cpd.CPD" - ;; - "designer") - readonly CLASSNAME="net.sourceforge.pmd.util.fxdesigner.DesignerStarter" - ;; - "cpdgui") - readonly CLASSNAME="net.sourceforge.pmd.cpd.GUI" - ;; - "ast-dump") - readonly CLASSNAME="net.sourceforge.pmd.util.treeexport.TreeExportCli" - ;; - *) - echo "${APPNAME} is NOT a valid application name, valid options are: $(valid_app_options)" - ;; -esac is_cygwin @@ -237,4 +198,4 @@ cygwin_paths java_heapsize_settings -java ${HEAPSIZE} ${PMD_JAVA_OPTS} $(jre_specific_vm_options) -cp "${classpath}" "${CLASSNAME}" "$@" +java ${HEAPSIZE} ${PMD_JAVA_OPTS} $(jre_specific_vm_options) -cp "${classpath}" net.sourceforge.pmd.cli.PmdCli "$@" From 5ed8fc81e895c8409e2fc99e21dac1a3c9e1a30c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 24 Aug 2022 15:14:05 -0300 Subject: [PATCH 126/254] Unify all bat files under a single one --- .../src/main/resources/scripts/ast-dump.bat | 6 -- pmd-dist/src/main/resources/scripts/cpd.bat | 6 -- .../src/main/resources/scripts/cpdgui.bat | 6 -- .../src/main/resources/scripts/designer.bat | 57 ------------------- pmd-dist/src/main/resources/scripts/pmd.bat | 52 ++++++++++++++++- 5 files changed, 50 insertions(+), 77 deletions(-) delete mode 100644 pmd-dist/src/main/resources/scripts/ast-dump.bat delete mode 100644 pmd-dist/src/main/resources/scripts/cpd.bat delete mode 100755 pmd-dist/src/main/resources/scripts/cpdgui.bat delete mode 100644 pmd-dist/src/main/resources/scripts/designer.bat mode change 100755 => 100644 pmd-dist/src/main/resources/scripts/pmd.bat diff --git a/pmd-dist/src/main/resources/scripts/ast-dump.bat b/pmd-dist/src/main/resources/scripts/ast-dump.bat deleted file mode 100644 index a7edf7aca8..0000000000 --- a/pmd-dist/src/main/resources/scripts/ast-dump.bat +++ /dev/null @@ -1,6 +0,0 @@ -@echo off -set TOPDIR="%~dp0.." -set OPTS= -set MAIN_CLASS=net.sourceforge.pmd.util.treeexport.TreeExportCli - -java %PMD_JAVA_OPTS% -classpath %TOPDIR%\conf;%TOPDIR%\lib\* %OPTS% %MAIN_CLASS% %* diff --git a/pmd-dist/src/main/resources/scripts/cpd.bat b/pmd-dist/src/main/resources/scripts/cpd.bat deleted file mode 100644 index 5b33818843..0000000000 --- a/pmd-dist/src/main/resources/scripts/cpd.bat +++ /dev/null @@ -1,6 +0,0 @@ -@echo off -set TOPDIR="%~dp0.." -set OPTS= -set MAIN_CLASS=net.sourceforge.pmd.cpd.CPD - -java %PMD_JAVA_OPTS% -classpath %TOPDIR%\conf;%TOPDIR%\lib\* %OPTS% %MAIN_CLASS% %* diff --git a/pmd-dist/src/main/resources/scripts/cpdgui.bat b/pmd-dist/src/main/resources/scripts/cpdgui.bat deleted file mode 100755 index e9bf4f91fe..0000000000 --- a/pmd-dist/src/main/resources/scripts/cpdgui.bat +++ /dev/null @@ -1,6 +0,0 @@ -@echo off -set TOPDIR="%~dp0.." -set OPTS= -set MAIN_CLASS=net.sourceforge.pmd.cpd.GUI - -java %PMD_JAVA_OPTS% -classpath %TOPDIR%\conf;%TOPDIR%\lib\* %OPTS% %MAIN_CLASS% %* diff --git a/pmd-dist/src/main/resources/scripts/designer.bat b/pmd-dist/src/main/resources/scripts/designer.bat deleted file mode 100644 index 9c4dd9af3e..0000000000 --- a/pmd-dist/src/main/resources/scripts/designer.bat +++ /dev/null @@ -1,57 +0,0 @@ -@echo off -set TOPDIR="%~dp0.." -set OPTS= -set MAIN_CLASS=net.sourceforge.pmd.util.fxdesigner.DesignerStarter - - -:: sets the jver variable to the java version, eg 90 for 9.0.1+x or 80 for 1.8.0_171-b11 or 110 for 11.0.6.1 -:: sets the jvendor variable to either java (oracle) or openjdk -for /f tokens^=1^,3^,4^,5^ delims^=.-_+^"^ %%j in ('java -version 2^>^&1 ^| find "version"') do ( - set jvendor=%%j - if %%l EQU ea ( - set /A "jver=%%k0" - ) else ( - if %%k EQU 1 ( - :: for java version 1.7.x, 1.8.x, ignore the first 1. - set /A "jver=%%l%%m" - ) else ( - set /A "jver=%%k%%l" - ) - ) -) - - -Set "jreopts=" -:: oracle java 9 and 10 has javafx included as a module -if /I %jvendor% == java ( - if %jver% GEQ 90 ( - if %jver% LSS 110 ( - :: enable reflection - set jreopts=--add-opens javafx.controls/javafx.scene.control.skin=ALL-UNNAMED - ) - ) -) - -set "_needjfxlib=0" -if /I %jvendor% == openjdk set _needjfxlib=1 -if /I %jvendor% == java ( - if %jver% GEQ 110 set _needjfxlib=1 -) -if %_needjfxlib% EQU 1 ( - if %jver% LSS 100 ( - echo For openjfx at least java 10 is required. - pause - exit - ) - if [%JAVAFX_HOME%] EQU [] ( - echo The environment variable JAVAFX_HOME is missing. - pause - exit - ) - set classpath=%TOPDIR%\conf;%TOPDIR%\lib\*;%JAVAFX_HOME%\lib\* -) else ( - set classpath=%TOPDIR%\conf;%TOPDIR%\lib\* -) - - -java %PMD_JAVA_OPTS% %jreopts% -classpath %classpath% %OPTS% %MAIN_CLASS% %* diff --git a/pmd-dist/src/main/resources/scripts/pmd.bat b/pmd-dist/src/main/resources/scripts/pmd.bat old mode 100755 new mode 100644 index a2e0b32956..e8b47ba903 --- a/pmd-dist/src/main/resources/scripts/pmd.bat +++ b/pmd-dist/src/main/resources/scripts/pmd.bat @@ -1,6 +1,54 @@ @echo off set TOPDIR="%~dp0.." set OPTS= -set MAIN_CLASS=net.sourceforge.pmd.PMD +set COMMAND=%1 +set MAIN_CLASS=net.sourceforge.pmd.cli.PmdCli -java %PMD_JAVA_OPTS% -classpath %TOPDIR%\conf;%TOPDIR%\lib\* %OPTS% %MAIN_CLASS% %* + +:: sets the jver variable to the java version, eg 901 for 9.0.1+x or 180 for 1.8.0_171-b11 +:: sets the jvendor variable to either java (oracle) or openjdk +for /f tokens^=1^,3^,4^,5^ delims^=.-_+^"^ %%j in ('java -version 2^>^&1 ^| find "version"') do ( + set jvendor=%%j + if %%l EQU ea ( + set /A "jver=%%k00" + ) else ( + set /A jver=%%k%%l%%m + ) +) + +Set "jreopts=" +:: oracle java 9 and 10 has javafx included as a module +if /I "%jvendor%" EQU "java" ( + if %jver% GEQ 900 ( + if %jver% LSS 1100 ( + :: enable reflection + Set jreopts=--add-opens javafx.controls/javafx.scene.control.skin=ALL-UNNAMED + ) + ) +) + +set "_needjfxlib=0" +if %COMMAND% EQU "designer" ( + if /I "%jvendor%" EQU "openjdk" set _needjfxlib=1 + if /I "%jvendor%" EQU "java" ( + if %jver% GEQ 1100 set _needjfxlib=1 + ) +) +if %_needjfxlib% EQU 1 ( + if %jver% LSS 1000 ( + echo For openjfx at least java 10 is required. + pause + exit + ) + if [%JAVAFX_HOME%] EQU [] ( + echo The environment variable JAVAFX_HOME is missing. + pause + exit + ) + set classpath=%TOPDIR%\conf;%TOPDIR%\lib\*;%JAVAFX_HOME%\lib\* +) else ( + set classpath=%TOPDIR%\conf;%TOPDIR%\lib\* +) + + +java %PMD_JAVA_OPTS% %jreopts% -classpath %classpath% %OPTS% %MAIN_CLASS% %* From 24bbf538b7f772deea80e1d72d38ee15abc338bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 24 Aug 2022 16:24:58 -0300 Subject: [PATCH 127/254] Fix NPE --- .../net/sourceforge/pmd/cli/commands/internal/PmdCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java index 1d3bcb6d03..f3a3ec8b48 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java @@ -89,7 +89,7 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { private RulePriority minimumPriority; - private Properties properties; + private Properties properties = new Properties(); private Path reportFile; From e567de3872c3a494df54b6c302754f894e4162b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 24 Aug 2022 16:25:12 -0300 Subject: [PATCH 128/254] Update pmd bin IT --- .../pmd/it/BinaryDistributionIT.java | 40 +++++++++++++------ .../net/sourceforge/pmd/it/CpdExecutor.java | 8 ++-- .../net/sourceforge/pmd/it/PMDExecutor.java | 11 +++-- 3 files changed, 37 insertions(+), 22 deletions(-) diff --git a/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java b/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java index 2114dc4697..451cb53031 100644 --- a/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java +++ b/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java @@ -18,7 +18,6 @@ import java.util.zip.ZipFile; import org.junit.Test; import net.sourceforge.pmd.PMDVersion; -import net.sourceforge.pmd.internal.LogMessages; public class BinaryDistributionIT extends AbstractBinaryDistributionTest { @@ -26,8 +25,24 @@ public class BinaryDistributionIT extends AbstractBinaryDistributionTest { private static final String SUPPORTED_LANGUAGES_PMD; static { - SUPPORTED_LANGUAGES_CPD = "Supported languages: [apex, cpp, cs, dart, ecmascript, fortran, gherkin, go, groovy, html, java, jsp, kotlin, lua, matlab, modelica, objectivec, perl, php, plsql, python, ruby, scala, swift, vf, xml]"; - SUPPORTED_LANGUAGES_PMD = "apex, ecmascript, html, java, jsp, kotlin, modelica, plsql, pom, scala, swift, vf, vm, wsdl, xml, xsl"; + SUPPORTED_LANGUAGES_CPD = "Valid values: apex, cpp, cs, dart, ecmascript,\n" + + " fortran, gherkin, go, groovy, html, java, jsp,\n" + + " kotlin, lua, matlab, modelica, objectivec, perl,\n" + + " php, plsql, python, ruby, scala, swift, vf, xml"; + SUPPORTED_LANGUAGES_PMD = "Valid values: apex, apex-54, apex-latest,\n" + + " ecmascript, ecmascript-ES6, ecmascript-latest,\n" + + " html, html-latest, java, java-1.3, java-1.4,\n" + + " java-1.5, java-1.6, java-1.7, java-1.8, java-10,\n" + + " java-11, java-12, java-13, java-14, java-15,\n" + + " java-16, java-17, java-18, java-18-preview,\n" + + " java-19, java-19-preview, java-9, java-latest,\n" + + " jsp, jsp-latest, kotlin, kotlin-1.6-rfc+0.1,\n" + + " kotlin-latest, modelica, modelica-latest, plsql,\n" + + " plsql-latest, pom, pom-latest, scala, scala-2.10,\n" + + " scala-2.11, scala-2.12, scala-2.13, scala-latest,\n" + + " swift, swift-latest, vf, vf-latest, vm,\n" + + " vm-latest, wsdl, wsdl-latest, xml, xml-latest,\n" + + " xsl, xsl-latest"; } private final String srcDir = new File(".", "src/test/resources/sample-source/java/").getAbsolutePath(); @@ -42,10 +57,9 @@ public class BinaryDistributionIT extends AbstractBinaryDistributionTest { String basedir = "pmd-bin-" + PMDVersion.VERSION + "/"; result.add(basedir); result.add(basedir + "LICENSE"); - result.add(basedir + "bin/run.sh"); + result.add(basedir + "bin/pmd"); result.add(basedir + "bin/pmd.bat"); - result.add(basedir + "bin/cpd.bat"); - result.add(basedir + "bin/ast-dump.bat"); + result.add(basedir + "conf/simplelogger.properties"); result.add(basedir + "lib/pmd-core-" + PMDVersion.VERSION + ".jar"); result.add(basedir + "lib/pmd-java-" + PMDVersion.VERSION + ".jar"); return result; @@ -98,7 +112,7 @@ public class BinaryDistributionIT extends AbstractBinaryDistributionTest { @Test public void testPmdNoArgs() throws Exception { ExecutionResult result = PMDExecutor.runPMD(tempDir); // without any argument, display usage help and error - result.assertExecutionResultErrOutput(1, LogMessages.runWithHelpFlagMessage()); + result.assertExecutionResultErrOutput(2, "Usage: pmd analyze "); } @Test @@ -110,14 +124,14 @@ public class BinaryDistributionIT extends AbstractBinaryDistributionTest { result = PMDExecutor.runPMD(tempDir, "-d", srcDir, "-R", "src/test/resources/rulesets/sample-ruleset.xml", "-r", folder.newFile().toString()); result.assertExecutionResult(4); - result.assertErrorOutputContains("[main] INFO net.sourceforge.pmd.PMD - Log level is at INFO"); + result.assertErrorOutputContains("[main] INFO net.sourceforge.pmd.cli.commands.internal.AbstractPmdSubcommand - Log level is at INFO"); // now with debug result = PMDExecutor.runPMD(tempDir, "-d", srcDir, "-R", "src/test/resources/rulesets/sample-ruleset.xml", "-r", folder.newFile().toString(), "--debug"); result.assertExecutionResult(4); - result.assertErrorOutputContains("[main] INFO net.sourceforge.pmd.PMD - Log level is at TRACE"); + result.assertErrorOutputContains("[main] INFO net.sourceforge.pmd.cli.commands.internal.AbstractPmdSubcommand - Log level is at TRACE"); } @Test @@ -135,22 +149,22 @@ public class BinaryDistributionIT extends AbstractBinaryDistributionTest { ExecutionResult result; result = CpdExecutor.runCpd(tempDir); // without any argument, display usage help and error - result.assertExecutionResultErrOutput(1, LogMessages.runWithHelpFlagMessage()); + result.assertExecutionResultErrOutput(2, "Usage: pmd cpd "); result = CpdExecutor.runCpd(tempDir, "-h"); result.assertExecutionResult(0, SUPPORTED_LANGUAGES_CPD); - result = CpdExecutor.runCpd(tempDir, "--minimum-tokens", "10", "--format", "text", "--files", srcDir); + result = CpdExecutor.runCpd(tempDir, "--minimum-tokens", "10", "--format", "text", "--dir", srcDir); result.assertExecutionResult(4, "Found a 10 line (55 tokens) duplication in the following files:"); result.assertExecutionResult(4, "Class1.java"); result.assertExecutionResult(4, "Class2.java"); - result = CpdExecutor.runCpd(tempDir, "--minimum-tokens", "10", "--format", "xml", "--files", srcDir); + result = CpdExecutor.runCpd(tempDir, "--minimum-tokens", "10", "--format", "xml", "--dir", srcDir); result.assertExecutionResult(4, ""); result.assertExecutionResult(4, "Class1.java\"/>"); result.assertExecutionResult(4, "Class2.java\"/>"); - result = CpdExecutor.runCpd(tempDir, "--minimum-tokens", "1000", "--format", "text", "--files", srcDir); + result = CpdExecutor.runCpd(tempDir, "--minimum-tokens", "1000", "--format", "text", "--dir", srcDir); result.assertExecutionResult(0); } } diff --git a/pmd-dist/src/test/java/net/sourceforge/pmd/it/CpdExecutor.java b/pmd-dist/src/test/java/net/sourceforge/pmd/it/CpdExecutor.java index 8dc812c278..0948d9ff28 100644 --- a/pmd-dist/src/test/java/net/sourceforge/pmd/it/CpdExecutor.java +++ b/pmd-dist/src/test/java/net/sourceforge/pmd/it/CpdExecutor.java @@ -7,7 +7,6 @@ package net.sourceforge.pmd.it; import static net.sourceforge.pmd.util.CollectionUtil.listOf; import java.nio.file.Path; -import java.util.Arrays; import java.util.List; import org.apache.commons.lang3.SystemUtils; @@ -37,12 +36,11 @@ public class CpdExecutor { String cmd; List args; if (SystemUtils.IS_OS_WINDOWS) { - cmd = "/bin/cpd.bat"; - args = Arrays.asList(arguments); + cmd = "/bin/pmd.bat"; } else { - cmd = "/bin/run.sh"; - args = listOf("cpd", arguments); + cmd = "/bin/pmd"; } + args = listOf("cpd", arguments); cmd = tempDir.resolve(PMD_BIN_PREFIX + PMDVersion.VERSION + cmd).toAbsolutePath().toString(); return PMDExecutor.runCommand(cmd, args, null); } diff --git a/pmd-dist/src/test/java/net/sourceforge/pmd/it/PMDExecutor.java b/pmd-dist/src/test/java/net/sourceforge/pmd/it/PMDExecutor.java index b61e94d7a7..6e87ce3a13 100644 --- a/pmd-dist/src/test/java/net/sourceforge/pmd/it/PMDExecutor.java +++ b/pmd-dist/src/test/java/net/sourceforge/pmd/it/PMDExecutor.java @@ -37,16 +37,19 @@ public class PMDExecutor { } private static ExecutionResult runPMDUnix(Path tempDir, Path reportFile, String... arguments) throws Exception { - String cmd = tempDir.resolve(AbstractBinaryDistributionTest.PMD_BIN_PREFIX + PMDVersion.VERSION + "/bin/run.sh").toAbsolutePath().toString(); + String cmd = tempDir.resolve(AbstractBinaryDistributionTest.PMD_BIN_PREFIX + PMDVersion.VERSION + "/bin/pmd").toAbsolutePath().toString(); List args = new ArrayList<>(); - args.add("pmd"); + args.add("run"); args.addAll(Arrays.asList(arguments)); return runCommand(cmd, args, reportFile); } private static ExecutionResult runPMDWindows(Path tempDir, Path reportFile, String... arguments) throws Exception { String cmd = tempDir.resolve(AbstractBinaryDistributionTest.PMD_BIN_PREFIX + PMDVersion.VERSION + "/bin/pmd.bat").toAbsolutePath().toString(); - return runCommand(cmd, Arrays.asList(arguments), reportFile); + List args = new ArrayList<>(); + args.add("run"); + args.addAll(Arrays.asList(arguments)); + return runCommand(cmd, args, reportFile); } static ExecutionResult runCommand(String cmd, List arguments, Path reportFile) throws Exception { @@ -116,7 +119,7 @@ public class PMDExecutor { FORMAT_FLAG, formatter, REPORTFILE_FLAG, reportFile.toAbsolutePath().toString(), NO_PROGRESSBAR_FLAG); } else { return runPMDUnix(tempDir, reportFile, SOURCE_DIRECTORY_FLAG, sourceDirectory, RULESET_FLAG, ruleset, - FORMAT_FLAG, formatter, REPORTFILE_FLAG, reportFile.toAbsolutePath().toString(), NO_PROGRESSBAR_FLAG); + FORMAT_FLAG, formatter, REPORTFILE_FLAG, reportFile.toAbsolutePath().toString(), NO_PROGRESSBAR_FLAG, "--verbose"); } } From 4a6f838d6751721b1bc0a7bc65c78591a9398c10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 24 Aug 2022 16:49:09 -0300 Subject: [PATCH 129/254] Cleanup code --- pmd-cli/src/main/java/net/sourceforge/pmd/cli/PmdCli.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/PmdCli.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/PmdCli.java index f77aaf5588..1de2edc64a 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/PmdCli.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/PmdCli.java @@ -16,10 +16,6 @@ public final class PmdCli { final int exitCode = new CommandLine(new PmdRootCommand()) .setCaseInsensitiveEnumValuesAllowed(true) .execute(args); - // .execute("run", "-h"); - // .execute("run", "--use-version", "scala-2.11", "--use-version", "apex", "--use-version", - // "ecmascript-latest", "-P", "foo=bar", "-R", "foo,bar", "-R", "baz", "-d", - // "src/main/java", "-f", "xml"); System.exit(exitCode); } } From 411a0034d6730162150934518947fcf66a9693f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Thu, 25 Aug 2022 20:11:19 -0300 Subject: [PATCH 130/254] Update CLI docs --- docs/pages/pmd/userdocs/cli_reference.md | 109 ++++++++++-------- docs/pages/pmd/userdocs/cpd/cpd.md | 105 ++++++++++------- .../userdocs/extending/designer_reference.md | 4 +- docs/pages/pmd/userdocs/installation.md | 29 ++--- 4 files changed, 142 insertions(+), 105 deletions(-) diff --git a/docs/pages/pmd/userdocs/cli_reference.md b/docs/pages/pmd/userdocs/cli_reference.md index 8c1f59df60..5f6855dc01 100644 --- a/docs/pages/pmd/userdocs/cli_reference.md +++ b/docs/pages/pmd/userdocs/cli_reference.md @@ -22,13 +22,24 @@ The tool comes with a rather extensive help text, simply running with `--help`! {% include custom/cli_option_row.html options="--rulesets,-R" option_arg="refs" - description="Comma-separated list of ruleset or rule references." + description="Path to a ruleset xml file. The path may reference + a resource on the classpath of the application, + be a local file system path, or a URL. The option + can be repeated, and multiple arguments separated + by comma can be provided to a single occurrence + of the option." required="yes" %} {% include custom/cli_option_row.html options="--dir,-d" option_arg="path" - description="Root directory for the analyzed sources." - required="yes" + description="Path to a source file, or directory containing + source files to analyze. Zip and Jar files are + also supported, if they are specified directly + (archive files found while exploring a directory + are not recursively expanded). This option can be + repeated, and multiple arguments can be provided + to a single occurrence of the option. One of + `--dir`, `--file-list` or `--uri` must be provided." %} {% include custom/cli_option_row.html options="--format,-f" option_arg="format" @@ -58,7 +69,7 @@ The tool comes with a rather extensive help text, simply running with `--help`! with the most up-to-date rule violations. This can greatly improve analysis performance and is **highly recommended**." %} - {% include custom/cli_option_row.html options="--debug,--verbose,-D,-V" + {% include custom/cli_option_row.html options="--debug,--verbose,-D,-v" description="Debug mode. Prints more log output. See also [Logging](#logging)." %} {% include custom/cli_option_row.html options="--encoding,-e" @@ -67,17 +78,16 @@ The tool comes with a rather extensive help text, simply running with `--help`! The valid values are the standard character sets of `java.nio.charset.Charset`." default="UTF-8" %} - {% include custom/cli_option_row.html options="--fail-on-violation" - option_arg="bool" + {% include custom/cli_option_row.html options="--[no-]fail-on-violation" description="Specifies whether PMD exits with non-zero status if violations are found. By default PMD exits with status 4 if violations are found. - Disable this feature with `--fail-on-violation false` to exit with 0 instead and just output the report." - default="true" + Disable this feature with `--no-fail-on-violation` to exit with 0 instead and just output the report." %} {% include custom/cli_option_row.html options="--file-list" option_arg="filepath" - description="Path to file containing a list of files to analyze, one path per line. - If this is given, then you don't need to provide `--dir`." + description="Path to a file containing a list of files to + analyze, one path per line. One of `--dir`, + `--file-list` or `--uri` must be provided." %} {% include custom/cli_option_row.html options="--force-language" option_arg="lang" @@ -87,26 +97,35 @@ The tool comes with a rather extensive help text, simply running with `--help`! the given language `<lang>`. Parsing errors are ignored and unparsable files are skipped. +

`--use-version` is still needed to specify the version of the language to use

+

This option allows to use the xml language for files, that don't use xml as extension. See [example](#analyze-other-xml-formats) below.

" %} {% include custom/cli_option_row.html options="--ignore-list" option_arg="filepath" description="Path to file containing a list of files to ignore, one path per line. - This option can be combined with `--dir` and `--file-list`. - This ignore list takes precedence over any files in the file-list." + This option overrides files included by any of `--dir`, `--file-list` and `--uri`." %} - {% include custom/cli_option_row.html options="--help,-h,-H" + {% include custom/cli_option_row.html options="--help,-h" description="Display help on usage." %} - {% include custom/cli_option_row.html options="-language,-l" - option_arg="lang" - description="Specify the language PMD should use. Used together with `-version`. See also [Supported Languages](#supported-languages)." + {% include custom/cli_option_row.html options="--use-version" + option_arg="lang-version" + description="The language version PMD should use when parsing source code. +

Values are in the format of *language[-version]*. If version is omitted, + or *-latest* is used the latest supported version is used.

+

This option can be repeated to configure multiple languages to be analyzed during a single run

+

See also [Supported Languages](#supported-languages). + Using `--help` will display a full list of supported languages and versions.

" %} - {% include custom/cli_option_row.html options="--minimum-priority,-min" - option_arg="num" - description="Rule priority threshold; rules with lower priority than configured here won't be used." - default="5" + {% include custom/cli_option_row.html options="--minimum-priority" + option_arg="priority" + description="Rule priority threshold; rules with lower priority + than configured here won't be used. + Valid values (case insensitive): High, Medium High, + Medium, Medium Low, Low" + default="Low" %} {% include custom/cli_option_row.html options="--no-ruleset-compatibility" description='Disable automatic fixing of invalid rule references. Without the switch, PMD tries to automatically replace rule references that point to moved or renamed rules with the newer location if possible. Disabling it is not recommended.' @@ -115,13 +134,13 @@ The tool comes with a rather extensive help text, simply running with `--help`! description="Explicitly disables incremental analysis. This switch turns off suggestions to use Incremental Analysis, and causes the `--cache` option to be discarded if it is provided." %} - {% include custom/cli_option_row.html options="--no-progress" - description="Disable progress bar indicator of live analysis progress." + {% include custom/cli_option_row.html options="--[no-]progress" + description="Enables / disable progress bar indicator of live analysis progress." %} {% include custom/cli_option_row.html options="--property,-P" option_arg="name>= ## Additional Java Runtime Options @@ -169,7 +178,7 @@ if you want to analyze a project, that uses one of OpenJDK's [Preview Language F Just set the environment variable `PMD_JAVA_OPTS` before executing PMD, e.g. export PMD_JAVA_OPTS="--enable-preview" - ./run.sh pmd -d ../../../src/main/java/ -f text -R rulesets/java/quickstart.xml + ./pmd analyze -d ../../../src/main/java/ -f text -R rulesets/java/quickstart.xml ## Exit Status @@ -177,9 +186,10 @@ Please note that if PMD detects any violations, it will exit with status 4 (sinc This behavior has been introduced to ease PMD integration into scripts or hooks, such as SVN hooks. - - - + + + +
0Everything is fine, no violations found
1Couldn't understand command-line parameters or PMD exited with an exception
4At least one violation has been detected, unless --fail-on-violation false is set.
0Everything is fine, no violations found.
1PMD exited with an exception.
2Usage error. Command-line parameters are invalid or missing.
4At least one violation has been detected, unless --no-fail-on-violation is set.
## Logging @@ -197,29 +207,34 @@ to be "debug". The language is determined automatically by PMD from the file extensions. Some languages such as "Java" however support multiple versions. The default version will be used, which is usually the latest supported version. If you want to use an older version, so that e.g. rules, that suggest usage of language features, -that are not available yet, won't be executed, you need to specify a specific version via the `-language` -and `-version` parameter. +that are not available yet, won't be executed, you need to specify a specific version via the `--use-version` +parameter. These parameters are irrelevant for languages that don't support different versions. Example: ``` shell -./run.sh pmd -d src/main/java -f text -R rulesets/java/quickstart.xml -language java -version 8 +./pmd analyze -d src/main/java -f text -R rulesets/java/quickstart.xml --use-version java-1.8 ``` * [apex](pmd_rules_apex.html) (Salesforce Apex) +* [ecmascript](pmd_rules_ecmascript.html) (JavaScript) +* [html](pmd_rules_html.html) * [java](pmd_rules_java.html) * [Supported Versions](pmd_languages_java_versions.html) -* [ecmascript](pmd_rules_ecmascript.html) (JavaScript) * [jsp](pmd_rules_jsp.html) +* [kotlin](pmd_rules_kotlin.html) * [modelica](pmd_rules_modelica.html) * [plsql](pmd_rules_plsql.html) +* [pom](pmd_rules_pom.html) (Maven POM) * [scala](pmd_rules_scala.html) * Supported Versions: 2.10, 2.11, 2.12, 2.13 (default) +* [swift](pmd_rules_swift.html) * [vf](pmd_rules_vf.html) (Salesforce VisualForce) * [vm](pmd_rules_vm.html) (Apache Velocity) -* [xml and xsl](pmd_rules_xml.html) +* [xml](pmd_rules_xml.html) +* [xsl](pmd_rules_xsl.html) ## Available Report Formats @@ -234,19 +249,19 @@ All formats are described at [PMD Report formats](pmd_userdocs_report_formats.ht If your xml language doesn't use `xml` as file extension, you can still use PMD with `--force-language`: ``` -$ ./run.sh pmd -d /home/me/src/xml-file.ext -f text -R ruleset.xml --force-language xml +$ ./pmd analyze -d /home/me/src/xml-file.ext -f text -R ruleset.xml --force-language xml ``` You can also specify a directory instead of a single file. Then all files are analyzed. In that case, parse errors are suppressed in order to reduce irrelevant noise: ``` -$ ./run.sh pmd -d /home/me/src/ -f text -R ruleset.xml --force-language xml +$ ./pmd analyze -d /home/me/src/ -f text -R ruleset.xml --force-language xml ``` Alternatively, you can create a filelist to only analyze files with a given extension: ``` $ find /home/me/src -name "*.ext" > /home/me/src/filelist.txt -$ ./run.sh pmd --file-list /home/me/src/filelist.txt -f text -R ruleset.xml --force-language xml +$ ./pmd analyze --file-list /home/me/src/filelist.txt -f text -R ruleset.xml --force-language xml ``` diff --git a/docs/pages/pmd/userdocs/cpd/cpd.md b/docs/pages/pmd/userdocs/cpd/cpd.md index eea7213483..482c71bf34 100644 --- a/docs/pages/pmd/userdocs/cpd/cpd.md +++ b/docs/pages/pmd/userdocs/cpd/cpd.md @@ -59,32 +59,48 @@ Novice as much as advanced readers may want to [read on on Refactoring Guru](htt Applies to {% include custom/cli_option_row.html options="--minimum-tokens" + option_arg="count" description="The minimum token length which should be reported as a duplicate." required="yes" %} - {% include custom/cli_option_row.html options="--files" - description="List of files and directories to process" - required="yes" + {% include custom/cli_option_row.html options="--dir,-d" + option_arg="path" + description="Path to a source file, or directory containing + source files to analyze. Zip and Jar files are + also supported, if they are specified directly + (archive files found while exploring a directory + are not recursively expanded). This option can + be repeated, and multiple arguments can be + provided to a single occurrence of the option. + One of `--dir`, `--file-list` or `--uri` must be + provided." %} {% include custom/cli_option_row.html options="--file-list" - description="Path to file containing a comma delimited list of files to analyze. If this is given, then you don't need to provide `--files`." + option_arg="filepath" + description="Path to a file containing a list of files to + analyze, one path per line. One of `--dir`, + `--file-list` or `--uri` must be provided." %} - {% include custom/cli_option_row.html options="--language" - description="Sources code language." + {% include custom/cli_option_row.html options="--language,-l" + option_arg="lang" + description="The source code language. +

See also [Supported Languages](#supported-languages). + Using `--help` will display a full list of supported languages.

" default="java" %} - {% include custom/cli_option_row.html options="--debug,--verbose" - description="Debug mode. Prints more log output." - %} - {% include custom/cli_option_row.html options="--encoding" - description="Character encoding to use when processing files. If not specified, CPD uses the system default encoding." + {% include custom/cli_option_row.html options="--encoding,-e" + option_arg="charset" + description="Specifies the character set encoding of the source code files PMD is reading. + The valid values are the standard character sets of `java.nio.charset.Charset`." + default="UTF-8" %} {% include custom/cli_option_row.html options="--skip-duplicate-files" description="Ignore multiple copies of files of the same name and length in comparison." default="false" %} {% include custom/cli_option_row.html options="--exclude" - description="Files to be excluded from CPD check" + option_arg="path" + description="Files to be excluded from the analysis" %} {% include custom/cli_option_row.html options="--non-recursive" description="Don't scan subdirectories" @@ -94,15 +110,16 @@ Novice as much as advanced readers may want to [read on on Refactoring Guru](htt description="Skip files which can't be tokenized due to invalid characters instead of aborting CPD" default="false" %} - {% include custom/cli_option_row.html options="--format" - description="Report format." + {% include custom/cli_option_row.html options="--format,-f" + option_arg="format" + description="Output format of the analysis report. The available formats + are described [here](#available-report-formats)." default="text" %} - {% include custom/cli_option_row.html options="--fail-on-violation" - option_arg="bool" - description="By default CPD exits with status 4 if code duplications are found. - Disable this option with `--fail-on-violation false` to exit with 0 instead and just write the report." - default="true" + {% include custom/cli_option_row.html options="--[no-]fail-on-violation" + description="Specifies whether CPD exits with non-zero status if violations are found. + By default CPD exits with status 4 if violations are found. + Disable this feature with `--no-fail-on-violation` to exit with 0 instead and just output the report." %} {% include custom/cli_option_row.html options="--ignore-literals" description="Ignore number values and string contents when comparing text" @@ -140,66 +157,67 @@ Novice as much as advanced readers may want to [read on on Refactoring Guru](htt default="#if 0|#endif" languages="C++" %} - {% include custom/cli_option_row.html options="--uri" - description="URI to process" + {% include custom/cli_option_row.html options="--uri,-u" + option_arg="uri" + description="Database URI for sources. One of `--dir`, + `--file-list` or `--uri` must be provided." languages="PLSQL" %} {% include custom/cli_option_row.html options="--help,-h" - default="false" description="Print help text" %} ### Examples -_Note:_ The following example use the Linux start script. For Windows, just replace "./run.sh cpd" by "cpd.bat". +_Note:_ The following example use the Linux start script. For Windows, just replace "./pmd cpd" by "pmd.bat cpd". Minimum required options: Just give it the minimum duplicate size and the source directory: - $ ./run.sh cpd --minimum-tokens 100 --files /usr/local/java/src/java + $ ./pmd cpd --minimum-tokens 100 --dir /usr/local/java/src/java You can also specify the language: - $ ./run.sh cpd --minimum-tokens 100 --files /path/to/c/source --language cpp + $ ./pmd cpd --minimum-tokens 100 --dir /path/to/c/source --language cpp You may wish to check sources that are stored in different directories: - $ ./run.sh cpd --minimum-tokens 100 --files /path/to/other/source --files /path/to/other/source --files /path/to/other/source --language fortran + $ ./pmd cpd --minimum-tokens 100 --dir /path/to/other/source --dir /path/to/other/source --dir /path/to/other/source --language fortran -There should be no limit to the number of '--files', you may add... But if you stumble one, please tell us ! +There should be no limit to the number of `--dir`, you may add... But if you stumble one, please tell us ! And if you're checking a C source tree with duplicate files in different architecture directories -you can skip those using --skip-duplicate-files: +you can skip those using `--skip-duplicate-files`: - $ ./run.sh cpd --minimum-tokens 100 --files /path/to/c/source --language cpp --skip-duplicate-files + $ ./pmd cpd --minimum-tokens 100 --dir /path/to/c/source --language cpp --skip-duplicate-files You can also specify the encoding to use when parsing files: - $ ./run.sh cpd --minimum-tokens 100 --files /usr/local/java/src/java --encoding utf-16le + $ ./pmd cpd --minimum-tokens 100 --dir /usr/local/java/src/java --encoding utf-16le You can also specify a report format - here we're using the XML report: - $ ./run.sh cpd --minimum-tokens 100 --files /usr/local/java/src/java --format xml + $ ./pmd cpd --minimum-tokens 100 --dir /usr/local/java/src/java --format xml -The default format is a text report, and there's also a `csv` report. +The default format is a text report, but there are [other supported formats](#available-report-formats) Note that CPD is pretty memory-hungry; you may need to give Java more memory to run it, like this: $ export PMD_JAVA_OPTS=-Xmx512m - $ ./run.sh cpd --minimum-tokens 100 --files /usr/local/java/src/java + $ ./pmd cpd --minimum-tokens 100 --dir /usr/local/java/src/java -In order to change the heap size under Windows, you'll need to edit the batch file `cpd.bat` or +In order to change the heap size under Windows, you'll need to edit the batch file `pmd.bat` or set the environment variable `PMD_JAVA_OPTS` prior to starting CPD: C:\ > cd C:\pmd-bin-{{site.pmd.version}}\bin C:\...\bin > set PMD_JAVA_OPTS=-Xmx512m - C:\...\bin > .\cpd.bat --minimum-tokens 100 --files c:\temp\src + C:\...\bin > .\pmd.bat cpd --minimum-tokens 100 --dir c:\temp\src If you specify a source directory but don't want to scan the sub-directories, you can use the non-recursive option: - $ ./run.sh cpd --minimum-tokens 100 --non-recursive --files /usr/local/java/src/java + $ ./pmd cpd --minimum-tokens 100 --non-recursive --dir /usr/local/java/src/java ### Exit status @@ -207,9 +225,10 @@ Please note that if CPD detects duplicated source code, it will exit with status This behavior has been introduced to ease CPD integration into scripts or hooks, such as SVN hooks. - - - + + + +
0Everything is fine, no code duplications found
1Couldn't understand command line parameters or CPD exited with an exception
4At least one code duplication has been detected unless '--fail-on-violation false' is used.
0Everything is fine, no code duplications found.
1CPD exited with an exception.
2Usage error. Command-line parameters are invalid or missing.
4At least one code duplication has been detected unless --no-fail-on-violation is set.
@@ -360,15 +379,15 @@ the CPD task as usual and right after it invoke the Ant XSLT script like this: ## GUI -CPD also comes with a simple GUI. You can start it via some scripts in the `bin` folder: +CPD also comes with a simple GUI. You can start it through the unified CLI interface provided in the `bin` folder: For Windows: - cpdgui.bat + pmd.bat cpd-gui For Linux: - ./run.sh cpdgui + ./pmd cpd-gui Here's a screenshot of CPD after running on the JDK 8 java.lang package: @@ -405,7 +424,7 @@ Additionally, **Java** allows to toggle suppression by adding the annotations all code within will be ignored by CPD. This approach however, is limited to the locations were `@SuppressWarnings` is accepted. -It's legacy and the new comment's based approach should be favored. +It is legacy and the new comment based approach should be favored. ```java //enable suppression diff --git a/docs/pages/pmd/userdocs/extending/designer_reference.md b/docs/pages/pmd/userdocs/extending/designer_reference.md index de1293707f..188c60cb57 100644 --- a/docs/pages/pmd/userdocs/extending/designer_reference.md +++ b/docs/pages/pmd/userdocs/extending/designer_reference.md @@ -16,8 +16,8 @@ The app needs JRE 1.8 or above to run. Be aware that on JRE 11+, the JavaFX dist If the bin directory of your PMD distribution is on your shell's path, then you can **launch the app** with - run.sh designer on Linux/ OSX - designer.bat on Windows + pmd designer on Linux/ OSX + pmd.bat designer on Windows {% include note.html content="pmd-ui.jar is not a runnable jar, because it doesn't include any PMD language module, or PMD Core. " %} diff --git a/docs/pages/pmd/userdocs/installation.md b/docs/pages/pmd/userdocs/installation.md index c50dabc63d..32868d5d66 100644 --- a/docs/pages/pmd/userdocs/installation.md +++ b/docs/pages/pmd/userdocs/installation.md @@ -23,7 +23,7 @@ sidebar: pmd_sidebar * For Windows: [Winzip](http://winzip.com) or the free [7-zip](http://www.7-zip.org/) * For Linux / Unix: [InfoZip](http://infozip.sourceforge.net/) -{% include note.html content="For executing the Designer (./run.sh designer) using [OpenJDK](http://jdk.java.net) or Java 11, you need additionally [OpenJFX](http://jdk.java.net). Download it, extract it and set the environment variable JAVAFX_HOME." %} +{% include note.html content="For executing the Designer (./pmd designer) using [OpenJDK](http://jdk.java.net) or Java 11+, you need additionally [OpenJFX](http://jdk.java.net). Download it, extract it and set the environment variable JAVAFX_HOME." %} ### Installation @@ -31,21 +31,24 @@ sidebar: pmd_sidebar PMD is distributed as a zip archive, which includes both [PMD](#running-pmd-via-command-line) and [CPD](pmd_userdocs_cpd.html). You can download the latest binary distribution from [the github releases page](https://github.com/pmd/pmd/releases). -Unzip it into any directory, optionally add the `bin` subdirectory in your `PATH`, and you're good to go! +Unzip it into any directory, optionally add the `bin` subdirectory in your `PATH`, and you're good to go! - +#### Shell completion + +PMD ships with built-in completion support for bash / zsh. + +To enable it, simply add `source path_to_pmd/shell/pmd-completion.sh` to your `~/.bashrc` / `~/.zshrc` file. ## Running PMD via command line {% include callout.html type="primary" content="PMD comes with several command line utilities, like CPD, the rule designer or PMD itself. - On Unix, you can run any of them using the script `run.sh`, located inside the `bin/` + You can run any of them using the script `pmd` (`pmd.bat` under Windows), located inside the `bin/` directory of the PMD distribution. The first argument is the name of the utility you want - to execute ('pmd', 'designer', ...), e.g. PMD is launched via `run.sh pmd`. The rest of - the arguments are specific to the utility used.

- On Windows, each utility has its own startup script, e.g. `pmd.bat`, `cpd.bat`." %} + to execute ('analyze', 'designer', ...), e.g. PMD is launched via `pmd analyze`. The rest of + the arguments are specific to the utility used.

" %} -The PMD command (`pmd.bat` or `run.sh pmd`) requires two options: +Running a PMD analysis (`pmd analyze` or `pmd.bat analyze`) requires at least two options: * `-d `: path to the sources to analyse. This can be a file name, a directory, or a jar or zip file containing the sources. @@ -89,7 +92,7 @@ Additionally, the following options, are specified most of the time even though
~ $ cd ~/bin/pmd-bin-{{site.pmd.version}}/bin
-~/.../bin $ ./run.sh pmd -d ../../../src/main/java/ -f text -R rulesets/java/quickstart.xml
+~/.../bin $ ./pmd analyze -d ../../../src/main/java/ -f text -R rulesets/java/quickstart.xml
   
   .../src/main/java/com/me/RuleSet.java:123  These nested if statements could be combined
   .../src/main/java/com/me/RuleSet.java:231  Useless parentheses.
@@ -99,7 +102,7 @@ Additionally, the following options, are specified most of the time even though
     
C:\ > cd C:\pmd-bin-{{site.pmd.version}}\bin
-C:\...\bin > .\pmd.bat -d ..\..\src\main\java\ -f text -R rulesets/java/quickstart.xml
+C:\...\bin > .\pmd.bat analyze -d ..\..\src\main\java\ -f text -R rulesets/java/quickstart.xml
       
   .../src/main/java/com/me/RuleSet.java:123  These nested if statements could be combined
   .../src/main/java/com/me/RuleSet.java:231  Useless parentheses.
@@ -117,7 +120,7 @@ Additionally, the following options, are specified most of the time even though
    content="CPD supports Java, JSP, C, C++, C#, Fortran and PHP source code, among other languages.
             For the full list, see [Supported Languages](pmd_userdocs_cpd.html#supported-languages)." %}
 
-Like for PMD, CPD is started on Unix by `run.sh cpd` and on Windows by `cpd.bat`.
+Like for PMD, CPD is started on Unix by `pmd cpd` and on Windows by `pmd.bat cpd`.
 
 There are two required parameters:
 * `--files `: path to the sources to analyse. This can be a file name, a
@@ -146,7 +149,7 @@ There are two required parameters:
   
~ $ cd ~/bin/pmd-bin-{{site.pmd.version}}/bin
-~/.../bin $ ./run.sh cpd --minimum-tokens 100 --files /home/me/src
+~/.../bin $ ./pmd cpd --minimum-tokens 100 --files /home/me/src
 
   Found a 7 line (110 tokens) duplication in the following files:
   Starting at line 579 of /home/me/src/test/java/foo/FooTypeTest.java
@@ -162,7 +165,7 @@ There are two required parameters:
     
C:\ > cd C:\pmd-bin-{{site.pmd.version}}\bin
-C:\...\bin > .\cpd.bat --minimum-tokens 100 --files c:\temp\src
+C:\...\bin > .\pmd.bat cpd --minimum-tokens 100 --files c:\temp\src
 
   Found a 7 line (110 tokens) duplication in the following files:
   Starting at line 579 of c:\temp\src\test\java\foo\FooTypeTest.java

From a2ce1334fb7aee98798084cdaff89d8bfb01b10d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?=
 
Date: Thu, 25 Aug 2022 20:14:10 -0300
Subject: [PATCH 131/254] Update docs

---
 docs/index.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/index.md b/docs/index.md
index 134e96f9f3..10f3edb3cc 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -26,7 +26,7 @@ additional_js:
 
 **PMD** is a static source code analyzer. It finds common programming flaws like
 unused variables, empty catch blocks, unnecessary object creation, and
-so forth. It's mainly concerned with **Java and Apex**, but **supports six other
+so forth. It's mainly concerned with **Java and Apex**, but **supports 13 other
 languages**.
 
 PMD features many **built-in checks** (in PMD lingo, *rules*), which are documented

From ecbd2d51f9771bde30213c9603774c827b64a541 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?=
 
Date: Thu, 25 Aug 2022 20:46:49 -0300
Subject: [PATCH 132/254] Remove all pending references to the old scripts

---
 docs/pages/next_major_development.md          | 33 ++++++++
 .../pmd/devdocs/experimental/ast_dump.md      | 82 ++++++++++---------
 docs/pages/pmd/languages/java.md              |  2 +-
 docs/pages/pmd/languages/java_versions.md     | 39 ---------
 docs/pages/pmd/languages/visualforce.md       |  2 +-
 docs/pages/pmd/languages/xml.md               |  2 +-
 .../pmd/userdocs/extending/your_first_rule.md |  4 +-
 docs/pages/pmd/userdocs/installation.md       |  4 +-
 docs/pages/pmd/userdocs/pmd_report_formats.md | 10 +--
 .../pmd/userdocs/suppressing_warnings.md      |  2 +-
 10 files changed, 91 insertions(+), 89 deletions(-)
 delete mode 100644 docs/pages/pmd/languages/java_versions.md

diff --git a/docs/pages/next_major_development.md b/docs/pages/next_major_development.md
index 3a4f63d24b..8cdead8413 100644
--- a/docs/pages/next_major_development.md
+++ b/docs/pages/next_major_development.md
@@ -20,6 +20,39 @@ The current tasks are listed here: [Integrate new PMD logo #1931](https://github
 
 The new logo is available from the [Logo Project Page](pmd_projectdocs_logo.html).
 
+### Revamped Command Line Interface
+
+PMD now ships with a unified Command Line Interface for both Linux/Unix and Windows. Under the `pmd` executable (`pmd.bat` for Windows),
+all utilities can be launched similar to how git is normally used. All commands and options are thoroughly documented in the help,
+with full color support where available. Moreover, efforts were made to provide consistency in the usage of all PMD utilities.
+
+```
+$ pmd --help    
+Usage: pmd [-hV] [COMMAND]
+  -h, --help      Show this help message and exit.
+  -V, --version   Print version information and exit.
+Commands:
+  analyze, analyse, run  The PMD standard source code analyzer
+  cpd                    Copy/Paste Detector - find duplicate code
+  designer               The PMD visual rule designer
+  cpd-gui                GUI for the Copy/Paste Detector
+                           Warning: May not support the full CPD feature set
+  ast-dump               Experimental: dumps the AST of parsing source code
+Exit Codes:
+  0   Succesful analysis, no violations found
+  1   An unexpected error occurred during execution
+  2   Usage error, please refer to the command help
+  4   Successful analysis, at least 1 violation found
+```
+
+Additionally, we now provide a completion script for Bash/Zsh to further help daily usage.
+This script can be found under `shell/pmd-completion.sh` in the binary distribution.
+To use it, edit your `~/.bashrc` / `~/.zshrc` file and add the following line:
+
+```
+source *path_to_pmd*/shell/pmd-completion.sh
+```
+
 ### API
 
 The API of PMD has been growing over the years and needs to be cleaned up. The goal is, to
diff --git a/docs/pages/pmd/devdocs/experimental/ast_dump.md b/docs/pages/pmd/devdocs/experimental/ast_dump.md
index eca9fe9ca9..6f85de5f63 100644
--- a/docs/pages/pmd/devdocs/experimental/ast_dump.md
+++ b/docs/pages/pmd/devdocs/experimental/ast_dump.md
@@ -9,37 +9,50 @@ permalink: pmd_devdocs_experimental_ast_dump.html
 ## Command line usage
 
 ```shell
-$ run.sh ast-dump --help
-Usage: ast-dump [options]
-  Options:
-    --encoding, -e
-      Encoding of the source file.
-      Default: UTF-8
-    --file
-      The file to dump
-    --format, -f
-      The output format.
-      Default: xml
-    --help, -h
-      Display usage.
-    --language, -l
-      Specify the language to use.
-      Default: java
-    --read-stdin, -i
-      Read source from standard input
-      Default: false
-    -P
-      Properties for the renderer.
-      Syntax: -Pkey=value
-      Default: {}
-
-Available languages: apex ecmascript java jsp modelica plsql pom scala text vf vm wsdl xml xsl 
-Available formats: xml                           XML format with the same structure as the one used in XPath
-+ Properties                  
-  + singleQuoteAttributes         Use single quotes to delimit attribute values (default true)
-  + lineSeparator                 Line separator to use. The default is platform-specific. (default \n)
-  + renderProlog                  True to output a prolog (default true)
-  + renderCommonAttributes        True to render attributes like BeginLine, EndLine, etc. (default false)
+$ pmd ast-dump --help
+Usage: pmd ast-dump [-Dhi] [-e=] [-f=] [--file=]
+                    [-l=] [-P=]...
+Experimental: dumps the AST of parsing source code
+  -D, -v, --debug, --verbose
+                          Debug mode.
+  -e, --encoding=
+                          Specifies the character set encoding of the source
+                            code files
+  -f, --format=   The output format.
+                          Valid values: xml, text
+      --file=       The file to parse and dump.
+  -h, --help              Show this help message and exit.
+  -i, --read-stdin        Read source from standard input.
+  -l, --language=
+                          The source code language.
+                          Valid values: apex, ecmascript, html, java, jsp,
+                            kotlin, modelica, plsql, pom, scala, swift, vf, vm,
+                            wsdl, xml, xsl
+  -P=      Key-value pair defining a property for the report
+                            format.
+                          Supported values for each report format:
+                          xml:
+                            singleQuoteAttributes - Use single quotes to
+                            delimit attribute values
+                              Default: true
+                            lineSeparator - Line separator to use. The default
+                            is platform-specific. The values 'CR', 'CRLF',
+                            'LF', '\r', '\r\n' and '\n' can be used to
+                            represent a carriage return, line feed and their
+                            combination more easily.
+                              Default: \n
+                            renderProlog - True to output a prolog
+                              Default: true
+                            renderCommonAttributes - True to render attributes
+                            like BeginLine, EndLine, etc.
+                              Default: false
+                          text:
+                            onlyAsciiChars - Use only ASCII characters in the
+                            structure
+                              Default: false
+                            maxLevel - Max level on which to recurse. Negative
+                            means unbounded
+                              Default: -1
 ```
 
 ## Example
@@ -50,12 +63,7 @@ public class Foo {
   int a;
 }
 
-$ run.sh ast-dump --format xml --language java --file Foo.java > Foo.xml
--------------------------------------------------------------------------------
-This command line utility is experimental. It might change at any time without
-prior notice.
--------------------------------------------------------------------------------
-
+$ pmd ast-dump --format xml --language java --file Foo.java > Foo.xml
 $ cat Foo.xml
 
 
diff --git a/docs/pages/pmd/languages/java.md b/docs/pages/pmd/languages/java.md
index a1e9d6c54d..32eff4a6ba 100644
--- a/docs/pages/pmd/languages/java.md
+++ b/docs/pages/pmd/languages/java.md
@@ -41,7 +41,7 @@ In order to analyze a project with PMD that uses preview language features, you'
 it via the environment variable `PMD_JAVA_OPTS` and select the new language version, e.g. `19-preview`:
 
     export PMD_JAVA_OPTS=--enable-preview
-    ./run.sh pmd -language java -version 19-preview ...
+    ./pmd run --use-version java-19-preview ...
 
 Note: we only support preview language features for the latest two java versions.
 
diff --git a/docs/pages/pmd/languages/java_versions.md b/docs/pages/pmd/languages/java_versions.md
deleted file mode 100644
index 627868ace0..0000000000
--- a/docs/pages/pmd/languages/java_versions.md
+++ /dev/null
@@ -1,39 +0,0 @@
----
-title: Supported Java Versions
-permalink: pmd_languages_java_versions.html
----
-
-## Overview of supported Java language versions
-
-Usually the latest non-preview Java Version is the default version.
-
-Java Version |Alias | Supported by PMD since |
--------------|------|------------------------|
-18-preview   |      | 6.44.0 |
-18 (default) |      | 6.44.0 |
-17-preview   |      | 6.37.0 |
-17           |      | 6.37.0 |
-16           |      | 6.32.0 |
-15           |      | 6.27.0 |
-14           |      | 6.22.0 |
-13           |      | 6.18.0 |
-12           |      | 6.13.0 |
-11           |      | 6.6.0 |
-10           | 1.10 | 6.4.0 |
-9            | 1.9  | 6.0.0 |
-8            | 1.8  | 5.1.0 |
-7            | 1.7  | 5.0.0 |
-6            | 1.6  | 3.9   |
-5            | 1.5  | 3.0   |
-1.4          |      | 1.2.2 |
-1.3          |      | 1.0.0 |
-
-## Using Java preview features
-
-In order to analyze a project with PMD that uses preview language features, you'll need to enable
-it via the environment variable `PMD_JAVA_OPTS` and select the new language version, e.g. `18-preview`:
-
-    export PMD_JAVA_OPTS=--enable-preview
-    ./run.sh pmd -language java -version 18-preview ...
-
-Note: we only support preview language features for the latest two java versions.
diff --git a/docs/pages/pmd/languages/visualforce.md b/docs/pages/pmd/languages/visualforce.md
index cc48cf3f79..cdd5d856fa 100644
--- a/docs/pages/pmd/languages/visualforce.md
+++ b/docs/pages/pmd/languages/visualforce.md
@@ -31,7 +31,7 @@ We'll probably extend the CLI instead of relying on environment variables in a f
 ```
 PMD_VF_APEXDIRECTORIES=../classes \
 PMD_VF_OBJECTSDIRECTORIES=../objects \
-run.sh pmd -d $GITHUB_WORKSPACE/force-app/main/default/pages \
+pmd run -d $GITHUB_WORKSPACE/force-app/main/default/pages \
     -R category/vf/security.xml/VfUnescapeEl -f text
 ```
 
diff --git a/docs/pages/pmd/languages/xml.md b/docs/pages/pmd/languages/xml.md
index ddd7d34a6d..cb0213663f 100644
--- a/docs/pages/pmd/languages/xml.md
+++ b/docs/pages/pmd/languages/xml.md
@@ -31,7 +31,7 @@ Some XML-based file formats do not conventionally use a `.xml` extension. To ass
 these files with the XML language, you need to use the `--force-language xml` command-line
 arguments, for instance:
 ```
-$ ./run.sh pmd -d /home/me/src/xml-file.ext -f text -R ruleset.xml --force-language xml
+$ ./pmd run -d /home/me/src/xml-file.ext -f text -R ruleset.xml --force-language xml
 ```
 Please refer to [PMD CLI reference](pmd_userdocs_cli_reference.html#analyze-other-xml-formats)
 for more examples.
diff --git a/docs/pages/pmd/userdocs/extending/your_first_rule.md b/docs/pages/pmd/userdocs/extending/your_first_rule.md
index d559fcd6ad..de3a127f81 100644
--- a/docs/pages/pmd/userdocs/extending/your_first_rule.md
+++ b/docs/pages/pmd/userdocs/extending/your_first_rule.md
@@ -24,8 +24,8 @@ The rule designer is a tool that packs a lot of features to help you develop XPa
 rules quickly and painlessly. Basically, it allows you to examine the AST of a code
 snippet and evaluate an XPath expression against it.
 
-Like for PMD and CPD, you can launch it using `run.sh designer` on Linux/Unix
-and `designer.bat` on Windows. The interface looks like the following:
+Like for PMD and CPD, you can launch it using `pmd designer` on Linux/Unix
+and `pmd.bat designer` on Windows. The interface looks like the following:
 
 {% include image.html file="userdocs/designer-overview-with-nums.png" alt="Designer overview" %}
 
diff --git a/docs/pages/pmd/userdocs/installation.md b/docs/pages/pmd/userdocs/installation.md
index 32868d5d66..32520d96ee 100644
--- a/docs/pages/pmd/userdocs/installation.md
+++ b/docs/pages/pmd/userdocs/installation.md
@@ -35,9 +35,9 @@ Unzip it into any directory, optionally add the `bin` subdirectory in your `PATH
 
 #### Shell completion
 
-PMD ships with built-in completion support for bash / zsh.
+PMD ships with built-in completion support for Bash / Zsh.
 
-To enable it, simply add `source path_to_pmd/shell/pmd-completion.sh` to your `~/.bashrc` / `~/.zshrc` file.
+To enable it, simply add `source *path_to_pmd*/shell/pmd-completion.sh` to your `~/.bashrc` / `~/.zshrc` file.
 
 ## Running PMD via command line
 
diff --git a/docs/pages/pmd/userdocs/pmd_report_formats.md b/docs/pages/pmd/userdocs/pmd_report_formats.md
index 8f34c1192e..f49aca34ba 100644
--- a/docs/pages/pmd/userdocs/pmd_report_formats.md
+++ b/docs/pages/pmd/userdocs/pmd_report_formats.md
@@ -14,10 +14,10 @@ be customized further via properties. Violations might also be suppressed and th
 be processing errors or configuration errors. Not all report formats display all information.
 
 The header of the sections below are used to select the format on the command line, as
-arguments to the `-format` option. When a format accepts *properties*,
-those can be specified with the `-property` / `-P` option on the command-line.
+arguments to the `--format` option. When a format accepts *properties*,
+those can be specified with the `--property` / `-P` option on the command-line.
 
-{% include note.html content="Suppressed violations are only reported, if the CLI parameter `-showsuppressed` is set." %}
+{% include note.html content="Suppressed violations are only reported, if the CLI parameter `--show-suppressed` is set." %}
 
 ## sarif
 
@@ -122,11 +122,11 @@ It has two ways of calling:
 
 1. For a single file: then all three properties need to be provided
 
-`run.sh pmd -d src/Foo.java -R rulesets/java/quickstart.xml -f ideaj -P fileName=src/Foo.java -P sourcePath=/home/pmd/src -P classAndMethodName=Foo`
+`pmd run -d src/Foo.java -R rulesets/java/quickstart.xml -f ideaj -P fileName=src/Foo.java -P sourcePath=/home/pmd/src -P classAndMethodName=Foo`
 
 2. For a directory: then the fileName property can be omitted
 
-`run.sh pmd -d src -R rulesets/java/quickstart.xml -f ideaj -P sourcePath=/home/pmd/src -P classAndMethodName=.method`
+`pmd run -d src -R rulesets/java/quickstart.xml -f ideaj -P sourcePath=/home/pmd/src -P classAndMethodName=.method`
 
 Example:
 
diff --git a/docs/pages/pmd/userdocs/suppressing_warnings.md b/docs/pages/pmd/userdocs/suppressing_warnings.md
index 320fb9b5b7..1ab582ac38 100644
--- a/docs/pages/pmd/userdocs/suppressing_warnings.md
+++ b/docs/pages/pmd/userdocs/suppressing_warnings.md
@@ -116,7 +116,7 @@ public class Foo {
     }
 }
 
-$ ./run.sh pmd -d Foo.java -f text -R java-unusedcode -suppressmarker TURN_OFF_WARNINGS
+$ ./pmd run -d Foo.java -f text -R java-unusedcode --suppress-marker TURN_OFF_WARNINGS
 No problems found!
 UnusedLocalVariable rule violation suppressed by //NOPMD in /home/tom/pmd/pmd/bin/Foo.java
 ```

From 366769cc813b977762711a69adbe25e905a3d1ee Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?=
 
Date: Thu, 25 Aug 2022 21:57:23 -0300
Subject: [PATCH 133/254] Support languiage aliases in PMD CLI

---
 .../PmdLanguageVersionTypeSupport.java        | 27 +++++++++++++------
 .../pmd/lang/BaseLanguageModule.java          |  5 ++++
 .../net/sourceforge/pmd/lang/Language.java    |  7 +++++
 .../pmd/it/BinaryDistributionIT.java          | 25 ++++++++---------
 4 files changed, 44 insertions(+), 20 deletions(-)

diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/typesupport/internal/PmdLanguageVersionTypeSupport.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/typesupport/internal/PmdLanguageVersionTypeSupport.java
index 04a7157648..fd8a3721fa 100644
--- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/typesupport/internal/PmdLanguageVersionTypeSupport.java
+++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/typesupport/internal/PmdLanguageVersionTypeSupport.java
@@ -24,17 +24,18 @@ import picocli.CommandLine.TypeConversionException;
  * modules in the classpath, but autocomplete will include all available at build time.
  */
 public class PmdLanguageVersionTypeSupport implements ITypeConverter, Iterable {
+
+    private static final String LATEST_SUFFIX = "-latest";
     
     @Override
     public Iterator iterator() {
         // Raw language names / -latest versions, such as "java" or "java-latest"
         final Stream latestLangReferences = LanguageRegistry.getLanguages().stream()
-                .map(PmdLanguageTypeSupport::normalizeName).flatMap(name -> Stream.of(name, name + "-latest"));
+                .map(PmdLanguageTypeSupport::normalizeName).flatMap(name -> Stream.of(name, name + LATEST_SUFFIX));
 
         // Explicit language-version pairs, such as "java-18" or "apex-54"
         final Stream allLangVersionReferences = LanguageRegistry.getLanguages().stream()
-                .flatMap(l -> l.getVersions().stream())
-                .map(PmdLanguageVersionTypeSupport::normalizeName);
+                .flatMap(PmdLanguageVersionTypeSupport::getNormalizedLangVerStream);
         
         // Collect to a TreeSet to ensure alphabetical order
         final TreeSet candidates = Stream.concat(latestLangReferences, allLangVersionReferences)
@@ -47,8 +48,10 @@ public class PmdLanguageVersionTypeSupport implements ITypeConverter langVer = LanguageRegistry.getLanguages().stream()
-                .flatMap(l -> l.getVersions().stream())
-                .filter(lv -> normalizeName(lv).equals(value)).findFirst();
+                .flatMap(l ->
+                    getNormalizedLangVerStream(l).filter(lv -> lv.equals(value))
+                        .map(lv -> l.getVersion(lv.substring(l.getTerseName().length() + 1))))
+                .findFirst();
 
         if (langVer.isPresent()) {
             return langVer.get();
@@ -56,8 +59,8 @@ public class PmdLanguageVersionTypeSupport implements ITypeConverter new TypeConversionException("Unknown language version: " + value));
     }
+
+    private static Stream getNormalizedLangVerStream(final Language lang) {
+        return lang.getVersionNamesAndAliases().stream().map(v -> normalizeName(lang.getTerseName() + " " + v));
+    }
+
+    public static String normalizeName(final String langVer) {
+        return langVer.trim().replace(' ', '-');
+    }
     
     public static String normalizeName(final LanguageVersion langVer) {
-        return langVer.getTerseName().replace(' ', '-');
+        return normalizeName(langVer.getTerseName());
     }
 }
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/BaseLanguageModule.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/BaseLanguageModule.java
index 1303795db5..77663e99c8 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/BaseLanguageModule.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/BaseLanguageModule.java
@@ -140,6 +140,11 @@ public abstract class BaseLanguageModule implements Language {
         return new ArrayList<>(distinctVersions);
     }
 
+    @Override
+    public List getVersionNamesAndAliases() {
+        return new ArrayList<>(versions.keySet());
+    }
+
     @Override
     public boolean hasVersion(String version) {
         return versions != null && versions.containsKey(version);
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/Language.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/Language.java
index c2ebbc1729..2e9b4ff378 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/Language.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/Language.java
@@ -100,6 +100,13 @@ public interface Language extends Comparable {
      */
     List getVersions();
 
+    /**
+     * Returns a complete list of supported version names for this language including all aliases.
+     *
+     * @return All supported language version names and aliases.
+     */
+    List getVersionNamesAndAliases();
+
     /**
      * Returns true if a language version with the given {@linkplain LanguageVersion#getVersion() version string}
      * is registered. Then, {@link #getVersion(String) getVersion} will return a non-null value.
diff --git a/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java b/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java
index 451cb53031..e426cab036 100644
--- a/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java
+++ b/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java
@@ -31,18 +31,19 @@ public class BinaryDistributionIT extends AbstractBinaryDistributionTest {
                 + "                               php, plsql, python, ruby, scala, swift, vf, xml";
         SUPPORTED_LANGUAGES_PMD = "Valid values: apex, apex-54, apex-latest,\n"
                 + "                              ecmascript, ecmascript-ES6, ecmascript-latest,\n"
-                + "                              html, html-latest, java, java-1.3, java-1.4,\n"
-                + "                              java-1.5, java-1.6, java-1.7, java-1.8, java-10,\n"
-                + "                              java-11, java-12, java-13, java-14, java-15,\n"
-                + "                              java-16, java-17, java-18, java-18-preview,\n"
-                + "                              java-19, java-19-preview, java-9, java-latest,\n"
-                + "                              jsp, jsp-latest, kotlin, kotlin-1.6-rfc+0.1,\n"
-                + "                              kotlin-latest, modelica, modelica-latest, plsql,\n"
-                + "                              plsql-latest, pom, pom-latest, scala, scala-2.10,\n"
-                + "                              scala-2.11, scala-2.12, scala-2.13, scala-latest,\n"
-                + "                              swift, swift-latest, vf, vf-latest, vm,\n"
-                + "                              vm-latest, wsdl, wsdl-latest, xml, xml-latest,\n"
-                + "                              xsl, xsl-latest";
+                + "                              html, html-latest, java, java-1.10, java-1.3,\n"
+                + "                              java-1.4, java-1.5, java-1.6, java-1.7, java-1.8,\n"
+                + "                              java-1.9, java-10, java-11, java-12, java-13,\n"
+                + "                              java-14, java-15, java-16, java-17, java-18,\n"
+                + "                              java-18-preview, java-19, java-19-preview,\n"
+                + "                              java-5, java-6, java-7, java-8, java-9,\n"
+                + "                              java-latest, jsp, jsp-latest, kotlin, kotlin-1.6,\n"
+                + "                              kotlin-1.6-rfc+0.1, kotlin-latest, modelica,\n"
+                + "                              modelica-latest, plsql, plsql-latest, pom,\n"
+                + "                              pom-latest, scala, scala-2.10, scala-2.11,\n"
+                + "                              scala-2.12, scala-2.13, scala-latest, swift,\n"
+                + "                              swift-latest, vf, vf-latest, vm, vm-latest, wsdl,\n"
+                + "                              wsdl-latest, xml, xml-latest, xsl, xsl-latest";
     }
 
     private final String srcDir = new File(".", "src/test/resources/sample-source/java/").getAbsolutePath();

From 4beef145f6bf450e756fb46db4234f580ee5597c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?=
 
Date: Fri, 26 Aug 2022 14:57:10 -0300
Subject: [PATCH 134/254] Update docs/pages/pmd/userdocs/cli_reference.md
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: Clément Fournier 
---
 docs/pages/pmd/userdocs/cli_reference.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/pages/pmd/userdocs/cli_reference.md b/docs/pages/pmd/userdocs/cli_reference.md
index 5f6855dc01..b8c165e0d2 100644
--- a/docs/pages/pmd/userdocs/cli_reference.md
+++ b/docs/pages/pmd/userdocs/cli_reference.md
@@ -97,7 +97,7 @@ The tool comes with a rather extensive help text, simply running with `--help`!
                             the given language `<lang>`. Parsing errors are ignored and unparsable files
                             are skipped.
                             
-                            

`--use-version` is still needed to specify the version of the language to use

+

Use `--use-version` to specify the language version to use, if it is not the default.

This option allows to use the xml language for files, that don't use xml as extension. See [example](#analyze-other-xml-formats) below.

" From cd7e41a9cbc04c486826bd57641056eca492c86e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Mon, 29 Aug 2022 18:45:14 -0300 Subject: [PATCH 135/254] Remove name normalization - Terse names are becoming more strictly defined soon --- .../typesupport/internal/PmdLanguageTypeSupport.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/typesupport/internal/PmdLanguageTypeSupport.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/typesupport/internal/PmdLanguageTypeSupport.java index e57d346733..a250f61b5c 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/typesupport/internal/PmdLanguageTypeSupport.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/typesupport/internal/PmdLanguageTypeSupport.java @@ -23,16 +23,12 @@ public class PmdLanguageTypeSupport implements ITypeConverter, Iterabl @Override public Language convert(final String value) throws Exception { return LanguageRegistry.getLanguages().stream() - .filter(l -> normalizeName(l).equals(value)).findFirst() + .filter(l -> l.getTerseName().equals(value)).findFirst() .orElseThrow(() -> new TypeConversionException("Unknown language: " + value)); } @Override public Iterator iterator() { - return LanguageRegistry.getLanguages().stream().map(PmdLanguageTypeSupport::normalizeName).iterator(); - } - - public static String normalizeName(final Language lang) { - return lang.getTerseName().replace(' ', '-'); + return LanguageRegistry.getLanguages().stream().map(Language::getTerseName).iterator(); } } From 90e5c52295c86b9b8965fa7441eb3091e95816b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 14 Sep 2022 16:16:20 -0300 Subject: [PATCH 136/254] Use only lang-version pairs - no more default/ latest versions - this assumes all versions have names, which is till pending, but agreed on --- .../pmd/cli/commands/internal/PmdCommand.java | 4 +- .../PmdLanguageVersionTypeSupport.java | 62 +++---------------- .../net/sourceforge/pmd/cli/PmdCliTest.java | 2 +- .../cli/commands/internal/PmdCommandTest.java | 20 +----- .../pmd/it/BinaryDistributionIT.java | 25 +++----- 5 files changed, 24 insertions(+), 89 deletions(-) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java index f3a3ec8b48..64e29b3bdc 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java @@ -177,7 +177,7 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { this.reportFile = reportFile; } - @Option(names = "--use-version", defaultValue = "java-latest", + @Option(names = "--use-version", defaultValue = "java-19", description = "The language version PMD should use when parsing source code.%nValid values: ${COMPLETION-CANDIDATES}", completionCandidates = PmdLanguageVersionTypeSupport.class, converter = PmdLanguageVersionTypeSupport.class) public void setLanguageVersion(final List languageVersion) { @@ -187,7 +187,7 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { if (list.size() > 1) { throw new ParameterException(spec.commandLine(), "Can only set one version per language, " + "but for language " + l.getName() + " multiple versions were provided " - + list.stream().map(PmdLanguageVersionTypeSupport::normalizeName).collect(Collectors.joining("', '", "'", "'"))); + + list.stream().map(LanguageVersion::getTerseName).collect(Collectors.joining("', '", "'", "'"))); } }); diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/typesupport/internal/PmdLanguageVersionTypeSupport.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/typesupport/internal/PmdLanguageVersionTypeSupport.java index fd8a3721fa..97c31b6975 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/typesupport/internal/PmdLanguageVersionTypeSupport.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/typesupport/internal/PmdLanguageVersionTypeSupport.java @@ -5,12 +5,9 @@ package net.sourceforge.pmd.cli.commands.typesupport.internal; import java.util.Iterator; -import java.util.Optional; import java.util.TreeSet; import java.util.stream.Collectors; -import java.util.stream.Stream; -import net.sourceforge.pmd.lang.Language; import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.LanguageVersion; @@ -25,61 +22,22 @@ import picocli.CommandLine.TypeConversionException; */ public class PmdLanguageVersionTypeSupport implements ITypeConverter, Iterable { - private static final String LATEST_SUFFIX = "-latest"; - @Override public Iterator iterator() { - // Raw language names / -latest versions, such as "java" or "java-latest" - final Stream latestLangReferences = LanguageRegistry.getLanguages().stream() - .map(PmdLanguageTypeSupport::normalizeName).flatMap(name -> Stream.of(name, name + LATEST_SUFFIX)); - - // Explicit language-version pairs, such as "java-18" or "apex-54" - final Stream allLangVersionReferences = LanguageRegistry.getLanguages().stream() - .flatMap(PmdLanguageVersionTypeSupport::getNormalizedLangVerStream); - - // Collect to a TreeSet to ensure alphabetical order - final TreeSet candidates = Stream.concat(latestLangReferences, allLangVersionReferences) - .collect(Collectors.toCollection(TreeSet::new)); - - return candidates.iterator(); + // Explicit language-version pairs, such as "java-18" or "apex-54". + // We build these directly to retain aliases. "java-8" works, but the canonical name for the LanguageVersion is java-1.8 + return LanguageRegistry.getLanguages().stream() + .flatMap(l -> l.getVersionNamesAndAliases().stream().map(v -> l.getTerseName() + "-" + v)) + .collect(Collectors.toCollection(TreeSet::new)).iterator(); } @Override public LanguageVersion convert(final String value) throws Exception { - // Is it an exact match? - final Optional langVer = LanguageRegistry.getLanguages().stream() - .flatMap(l -> - getNormalizedLangVerStream(l).filter(lv -> lv.equals(value)) - .map(lv -> l.getVersion(lv.substring(l.getTerseName().length() + 1)))) - .findFirst(); - - if (langVer.isPresent()) { - return langVer.get(); - } - - // This is either a -latest or standalone language name - final String langName; - if (value.endsWith(LATEST_SUFFIX)) { - langName = value.substring(0, value.length() - LATEST_SUFFIX.length()); - } else { - langName = value; - } - return LanguageRegistry.getLanguages().stream() - .filter(l -> PmdLanguageTypeSupport.normalizeName(l).equals(langName)) - .map(Language::getDefaultVersion).findFirst() - .orElseThrow(() -> new TypeConversionException("Unknown language version: " + value)); - } - - private static Stream getNormalizedLangVerStream(final Language lang) { - return lang.getVersionNamesAndAliases().stream().map(v -> normalizeName(lang.getTerseName() + " " + v)); - } - - public static String normalizeName(final String langVer) { - return langVer.trim().replace(' ', '-'); - } - - public static String normalizeName(final LanguageVersion langVer) { - return normalizeName(langVer.getTerseName()); + .filter(l -> value.startsWith(l.getTerseName() + "-")) + .map(l -> l.getVersion(value.substring(l.getTerseName().length() + 1))) + .filter(lv -> lv != null) + .findFirst() + .orElseThrow(() -> new TypeConversionException("Unknown language version: " + value)); } } diff --git a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/PmdCliTest.java b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/PmdCliTest.java index ebfa0ba0aa..00b9ac1e5e 100644 --- a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/PmdCliTest.java +++ b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/PmdCliTest.java @@ -233,7 +233,7 @@ class PmdCliTest extends BaseCliTest { // Always run against dummy language without logging not cache to remove all logging noise argList.add("run"); argList.add("--use-version"); - argList.add("dummy"); + argList.add("dummy-1.0"); argList.add("--no-cache"); argList.add("--no-progress"); diff --git a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/PmdCommandTest.java b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/PmdCommandTest.java index b30c009871..c25f9be1d5 100644 --- a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/PmdCommandTest.java +++ b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/PmdCommandTest.java @@ -17,24 +17,6 @@ import net.sourceforge.pmd.lang.LanguageVersion; class PmdCommandTest extends BaseCommandTest { - @Test - void testVersionDefault() throws Exception { - final PmdCommand cmd = setupAndParse("--use-version", "dummy", "-d", "a", "-R", "x.xml"); - final LanguageVersion dummyLatest = cmd.toConfiguration().getLanguageVersionOfFile("foo.dummy"); - - // LanguageVersion do not implement equals, but we can check their string representations - assertEquals(DummyLanguageModule.getInstance().getDefaultVersion().toString(), dummyLatest.toString()); - } - - @Test - void testVersionLatest() throws Exception { - final PmdCommand cmd = setupAndParse("--use-version", "dummy-latest", "-d", "a", "-R", "x.xml"); - final LanguageVersion dummyLatest = cmd.toConfiguration().getLanguageVersionOfFile("foo.dummy"); - - // LanguageVersion do not implement equals, but we can check their string representations - assertEquals(DummyLanguageModule.getInstance().getDefaultVersion().toString(), dummyLatest.toString()); - } - @Test void testVersionGiven() throws Exception { final PmdCommand cmd = setupAndParse("--use-version", "dummy-1.2", "-d", "a", "-R", "x.xml"); @@ -102,7 +84,7 @@ class PmdCommandTest extends BaseCommandTest { // If no language provided, set dummy latest if (!argList.contains("--use-version")) { argList.add("--use-version"); - argList.add("dummy"); + argList.add("dummy-1.0"); } } } diff --git a/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java b/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java index e426cab036..d411da1179 100644 --- a/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java +++ b/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java @@ -29,21 +29,16 @@ public class BinaryDistributionIT extends AbstractBinaryDistributionTest { + " fortran, gherkin, go, groovy, html, java, jsp,\n" + " kotlin, lua, matlab, modelica, objectivec, perl,\n" + " php, plsql, python, ruby, scala, swift, vf, xml"; - SUPPORTED_LANGUAGES_PMD = "Valid values: apex, apex-54, apex-latest,\n" - + " ecmascript, ecmascript-ES6, ecmascript-latest,\n" - + " html, html-latest, java, java-1.10, java-1.3,\n" - + " java-1.4, java-1.5, java-1.6, java-1.7, java-1.8,\n" - + " java-1.9, java-10, java-11, java-12, java-13,\n" - + " java-14, java-15, java-16, java-17, java-18,\n" - + " java-18-preview, java-19, java-19-preview,\n" - + " java-5, java-6, java-7, java-8, java-9,\n" - + " java-latest, jsp, jsp-latest, kotlin, kotlin-1.6,\n" - + " kotlin-1.6-rfc+0.1, kotlin-latest, modelica,\n" - + " modelica-latest, plsql, plsql-latest, pom,\n" - + " pom-latest, scala, scala-2.10, scala-2.11,\n" - + " scala-2.12, scala-2.13, scala-latest, swift,\n" - + " swift-latest, vf, vf-latest, vm, vm-latest, wsdl,\n" - + " wsdl-latest, xml, xml-latest, xsl, xsl-latest"; + SUPPORTED_LANGUAGES_PMD = "Valid values: apex-54, ecmascript-ES6, html-,\n" + + " java-1.10, java-1.3, java-1.4, java-1.5, java-1.\n" + + " 6, java-1.7, java-1.8, java-1.9, java-10,\n" + + " java-11, java-12, java-13, java-14, java-15,\n" + + " java-16, java-17, java-18, java-18-preview,\n" + + " java-19, java-19-preview, java-5, java-6, java-7,\n" + + " java-8, java-9, jsp-, kotlin-1.6, kotlin-1.\n" + + " 6-rfc+0.1, modelica-, plsql-, pom-, scala-2.10,\n" + + " scala-2.11, scala-2.12, scala-2.13, swift-, vf-,\n" + + " vm-, wsdl-, xml-, xsl-"; } private final String srcDir = new File(".", "src/test/resources/sample-source/java/").getAbsolutePath(); From 51c0891e494729582177300dc667eb95a7703d5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 14 Sep 2022 16:22:12 -0300 Subject: [PATCH 137/254] Update docs --- docs/pages/pmd/userdocs/cli_reference.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/pages/pmd/userdocs/cli_reference.md b/docs/pages/pmd/userdocs/cli_reference.md index b8c165e0d2..1bcd3a9758 100644 --- a/docs/pages/pmd/userdocs/cli_reference.md +++ b/docs/pages/pmd/userdocs/cli_reference.md @@ -113,8 +113,7 @@ The tool comes with a rather extensive help text, simply running with `--help`! {% include custom/cli_option_row.html options="--use-version" option_arg="lang-version" description="The language version PMD should use when parsing source code. -

Values are in the format of *language[-version]*. If version is omitted, - or *-latest* is used the latest supported version is used.

+

Values are in the format of *language-version*.

This option can be repeated to configure multiple languages to be analyzed during a single run

See also [Supported Languages](#supported-languages). Using `--help` will display a full list of supported languages and versions.

" @@ -206,12 +205,10 @@ to be "debug". The language is determined automatically by PMD from the file extensions. Some languages such as "Java" however support multiple versions. The default version will be used, which is usually the latest supported -version. If you want to use an older version, so that e.g. rules, that suggest usage of language features, +non-preview version. If you want to use an older version, so that e.g. rules, that suggest usage of language features, that are not available yet, won't be executed, you need to specify a specific version via the `--use-version` parameter. -These parameters are irrelevant for languages that don't support different versions. - Example: ``` shell From b7533169bf1e0d3e441868a0723cda803f4fb4fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 14 Sep 2022 16:26:34 -0300 Subject: [PATCH 138/254] Update pmd-dist/src/main/resources/scripts/pmd.bat Add all needed `--add-opens` for javaFX Co-authored-by: Andreas Dangel --- pmd-dist/src/main/resources/scripts/pmd.bat | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/pmd-dist/src/main/resources/scripts/pmd.bat b/pmd-dist/src/main/resources/scripts/pmd.bat index e8b47ba903..ef2a6c7efc 100644 --- a/pmd-dist/src/main/resources/scripts/pmd.bat +++ b/pmd-dist/src/main/resources/scripts/pmd.bat @@ -22,7 +22,18 @@ if /I "%jvendor%" EQU "java" ( if %jver% GEQ 900 ( if %jver% LSS 1100 ( :: enable reflection - Set jreopts=--add-opens javafx.controls/javafx.scene.control.skin=ALL-UNNAMED + SETLOCAL EnableDelayedExpansion + rem java9 and java10 from oracle contain javafx as a module + rem open internal module of javafx to reflection (for our TreeViewWrapper) + set "jreopts=--add-opens javafx.controls/javafx.scene.control.skin=ALL-UNNAMED" + rem The rest here is for RichtextFX + set "jreopts=!jreopts! --add-opens javafx.graphics/javafx.scene.text=ALL-UNNAMED" + set "jreopts=!jreopts! --add-opens javafx.graphics/com.sun.javafx.scene.text=ALL-UNNAMED" + set "jreopts=!jreopts! --add-opens javafx.graphics/com.sun.javafx.text=ALL-UNNAMED" + set "jreopts=!jreopts! --add-opens javafx.graphics/com.sun.javafx.geom=ALL-UNNAMED" + rem Warn of remaining illegal accesses + set "jreopts=!jreopts! --illegal-access=warn" + ) ) ) From 20e441f87d015d1b265c79b740b8289a066ac8fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 14 Sep 2022 16:30:10 -0300 Subject: [PATCH 139/254] Improve unified pmd.bat Co-authored-by: Andreas Dangel --- pmd-dist/src/main/resources/scripts/pmd.bat | 27 ++++++++++++--------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/pmd-dist/src/main/resources/scripts/pmd.bat b/pmd-dist/src/main/resources/scripts/pmd.bat index ef2a6c7efc..b441962598 100644 --- a/pmd-dist/src/main/resources/scripts/pmd.bat +++ b/pmd-dist/src/main/resources/scripts/pmd.bat @@ -5,22 +5,27 @@ set COMMAND=%1 set MAIN_CLASS=net.sourceforge.pmd.cli.PmdCli -:: sets the jver variable to the java version, eg 901 for 9.0.1+x or 180 for 1.8.0_171-b11 +:: sets the jver variable to the java version, eg 90 for 9.0.1+x or 80 for 1.8.0_171-b11 or 110 for 11.0.6.1 :: sets the jvendor variable to either java (oracle) or openjdk for /f tokens^=1^,3^,4^,5^ delims^=.-_+^"^ %%j in ('java -version 2^>^&1 ^| find "version"') do ( set jvendor=%%j if %%l EQU ea ( - set /A "jver=%%k00" + set /A "jver=%%k0" ) else ( - set /A jver=%%k%%l%%m + if %%k EQU 1 ( + :: for java version 1.7.x, 1.8.x, ignore the first 1. + set /A "jver=%%l%%m" + ) else ( + set /A "jver=%%k%%l" + ) ) ) Set "jreopts=" :: oracle java 9 and 10 has javafx included as a module -if /I "%jvendor%" EQU "java" ( - if %jver% GEQ 900 ( - if %jver% LSS 1100 ( +if /I %jvendor% == java ( + if %jver% GEQ 90 ( + if %jver% LSS 110 ( :: enable reflection SETLOCAL EnableDelayedExpansion rem java9 and java10 from oracle contain javafx as a module @@ -39,14 +44,14 @@ if /I "%jvendor%" EQU "java" ( ) set "_needjfxlib=0" -if %COMMAND% EQU "designer" ( - if /I "%jvendor%" EQU "openjdk" set _needjfxlib=1 - if /I "%jvendor%" EQU "java" ( - if %jver% GEQ 1100 set _needjfxlib=1 +if [%COMMAND%] == [designer] ( + if /I %jvendor% == openjdk set _needjfxlib=1 + if /I %jvendor% == java ( + if %jver% GEQ 110 set _needjfxlib=1 ) ) if %_needjfxlib% EQU 1 ( - if %jver% LSS 1000 ( + if %jver% LSS 100 ( echo For openjfx at least java 10 is required. pause exit From 90fba7a19ca8523e08d7e8fb54592ed2685ec75e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 14 Sep 2022 16:38:36 -0300 Subject: [PATCH 140/254] Add debug / verbose docs for CPD --- docs/pages/pmd/userdocs/cpd/cpd.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/pages/pmd/userdocs/cpd/cpd.md b/docs/pages/pmd/userdocs/cpd/cpd.md index 482c71bf34..ce9d3d77d9 100644 --- a/docs/pages/pmd/userdocs/cpd/cpd.md +++ b/docs/pages/pmd/userdocs/cpd/cpd.md @@ -88,6 +88,9 @@ Novice as much as advanced readers may want to [read on on Refactoring Guru](htt Using `--help` will display a full list of supported languages.

" default="java" %} + {% include custom/cli_option_row.html options="--debug,--verbose,-D,-v" + description="Debug mode. Prints more log output. See also [Logging](#logging)." + %} {% include custom/cli_option_row.html options="--encoding,-e" option_arg="charset" description="Specifies the character set encoding of the source code files PMD is reading. @@ -231,6 +234,15 @@ This behavior has been introduced to ease CPD integration into scripts or hooks, 4At least one code duplication has been detected unless --no-fail-on-violation is set. +## Logging + +PMD internally uses [slf4j](https://www.slf4j.org/) and ships with slf4j-simple as the logging implementation. +Logging messages are printed to System.err. + +The configuration for slf4j-simple is in the file `conf/simplelogger.properties`. There you can enable +logging of specific classes if needed. The `--debug` command line option configures the default log level +to be "debug". + ## Supported Languages From 6c87c704c769be8e2896e72428d4ddd18de48498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 14 Sep 2022 18:29:25 -0300 Subject: [PATCH 141/254] Adapt to latest changes from upstream --- .../pmd/cli/commands/internal/CpdCommand.java | 19 ++++++++++++------- .../internal/PmdLanguageTypeSupport.java | 4 ++-- .../PmdLanguageVersionTypeSupport.java | 4 ++-- .../sourceforge/pmd/cpd/CPDConfiguration.java | 2 +- .../treeexport/TreeExportConfiguration.java | 2 +- 5 files changed, 18 insertions(+), 13 deletions(-) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java index 98ed9ed1a6..90d5dfc2b1 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java @@ -4,10 +4,9 @@ package net.sourceforge.pmd.cli.commands.internal; -import java.io.BufferedWriter; import java.io.File; import java.io.IOException; -import java.io.OutputStreamWriter; +import java.nio.charset.Charset; import java.nio.file.Path; import java.util.Arrays; import java.util.Iterator; @@ -21,9 +20,11 @@ import net.sourceforge.pmd.cli.commands.typesupport.internal.CpdLanguageTypeSupp import net.sourceforge.pmd.cli.internal.ExecutionResult; import net.sourceforge.pmd.cpd.CPD; import net.sourceforge.pmd.cpd.CPDConfiguration; +import net.sourceforge.pmd.cpd.CPDReport; import net.sourceforge.pmd.cpd.Language; import net.sourceforge.pmd.cpd.Tokenizer; import net.sourceforge.pmd.internal.LogMessages; +import net.sourceforge.pmd.util.IOUtil; import picocli.CommandLine.Command; import picocli.CommandLine.Option; @@ -34,7 +35,7 @@ import picocli.CommandLine.ParameterException; public class CpdCommand extends AbstractAnalysisPmdSubcommand { @Option(names = { "--language", "-l" }, description = "The source code language.%nValid values: ${COMPLETION-CANDIDATES}", - defaultValue = "java", converter = CpdLanguageTypeSupport.class, completionCandidates = CpdLanguageTypeSupport.class) + defaultValue = CPDConfiguration.DEFAULT_LANGUAGE, converter = CpdLanguageTypeSupport.class, completionCandidates = CpdLanguageTypeSupport.class) private Language language; @Option(names = "--minimum-tokens", @@ -48,7 +49,7 @@ public class CpdCommand extends AbstractAnalysisPmdSubcommand { @Option(names = { "--format", "-f" }, description = "Report format.%nValid values: ${COMPLETION-CANDIDATES}%n" + "Alternatively, you can provide the fully qualified name of a custom CpdRenderer in the classpath.", - defaultValue = "text", completionCandidates = CpdSupportedReportFormatsCandidates.class) + defaultValue = CPDConfiguration.DEFAULT_RENDERER, completionCandidates = CpdSupportedReportFormatsCandidates.class) private String rendererName; @Option(names = "--ignore-literals", @@ -110,14 +111,17 @@ public class CpdCommand extends AbstractAnalysisPmdSubcommand { configuration.setMinimumTileSize(minimumTokens); configuration.setNonRecursive(nonRecursive); configuration.setNoSkipBlocks(noSkipBlocks); - configuration.setRendererName(null); + configuration.setRendererName(rendererName); configuration.setSkipBlocksPattern(skipBlocksPattern); configuration.setSkipDuplicates(skipDuplicates); configuration.setSkipLexicalErrors(skipLexicalErrors); configuration.setSourceEncoding(encoding.getEncoding().name()); configuration.setURI(uri == null ? null : uri.toString()); - configuration.setCPDRenderer(CPDConfiguration.getCPDRendererFromString(rendererName, encoding.getEncoding().name())); + configuration.postContruct(); + // Pass extra parameters as System properties to allow language + // implementation to retrieve their associate values... + CPDConfiguration.setSystemProperties(configuration); return configuration; } @@ -133,7 +137,8 @@ public class CpdCommand extends AbstractAnalysisPmdSubcommand { try { cpd.go(); - configuration.getCPDRenderer().render(cpd.getMatches(), new BufferedWriter(new OutputStreamWriter(System.out))); + final CPDReport report = cpd.toReport(); + configuration.getCPDReportRenderer().render(report, IOUtil.createWriter(Charset.defaultCharset(), null)); if (cpd.getMatches().hasNext() && configuration.isFailOnViolation()) { return ExecutionResult.VIOLATIONS_FOUND; diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/typesupport/internal/PmdLanguageTypeSupport.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/typesupport/internal/PmdLanguageTypeSupport.java index a250f61b5c..a84dfcff24 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/typesupport/internal/PmdLanguageTypeSupport.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/typesupport/internal/PmdLanguageTypeSupport.java @@ -22,13 +22,13 @@ public class PmdLanguageTypeSupport implements ITypeConverter, Iterabl @Override public Language convert(final String value) throws Exception { - return LanguageRegistry.getLanguages().stream() + return LanguageRegistry.PMD.getLanguages().stream() .filter(l -> l.getTerseName().equals(value)).findFirst() .orElseThrow(() -> new TypeConversionException("Unknown language: " + value)); } @Override public Iterator iterator() { - return LanguageRegistry.getLanguages().stream().map(Language::getTerseName).iterator(); + return LanguageRegistry.PMD.getLanguages().stream().map(Language::getTerseName).iterator(); } } diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/typesupport/internal/PmdLanguageVersionTypeSupport.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/typesupport/internal/PmdLanguageVersionTypeSupport.java index 97c31b6975..f935056259 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/typesupport/internal/PmdLanguageVersionTypeSupport.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/typesupport/internal/PmdLanguageVersionTypeSupport.java @@ -26,14 +26,14 @@ public class PmdLanguageVersionTypeSupport implements ITypeConverter iterator() { // Explicit language-version pairs, such as "java-18" or "apex-54". // We build these directly to retain aliases. "java-8" works, but the canonical name for the LanguageVersion is java-1.8 - return LanguageRegistry.getLanguages().stream() + return LanguageRegistry.PMD.getLanguages().stream() .flatMap(l -> l.getVersionNamesAndAliases().stream().map(v -> l.getTerseName() + "-" + v)) .collect(Collectors.toCollection(TreeSet::new)).iterator(); } @Override public LanguageVersion convert(final String value) throws Exception { - return LanguageRegistry.getLanguages().stream() + return LanguageRegistry.PMD.getLanguages().stream() .filter(l -> value.startsWith(l.getTerseName() + "-")) .map(l -> l.getVersion(value.substring(l.getTerseName().length() + 1))) .filter(lv -> lv != null) diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java index 0b322797d4..ab6134cf2d 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java @@ -344,7 +344,7 @@ public class CPDConfiguration extends AbstractConfiguration { return cpdRenderer; } - CPDReportRenderer getCPDReportRenderer() { + public CPDReportRenderer getCPDReportRenderer() { return cpdReportRenderer; } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExportConfiguration.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExportConfiguration.java index d7cc078cdc..a36f8f0106 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExportConfiguration.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/treeexport/TreeExportConfiguration.java @@ -14,7 +14,7 @@ import net.sourceforge.pmd.lang.LanguageRegistry; public class TreeExportConfiguration extends AbstractConfiguration { private String format = "xml"; - private Language language = LanguageRegistry.getDefaultLanguage(); + private Language language = LanguageRegistry.PMD.getLanguageById("java"); private Properties properties = new Properties(); private Path file; private boolean readStdin; From ee652286c69ae14c2150730ef219e3450c798445 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 14 Sep 2022 18:43:34 -0300 Subject: [PATCH 142/254] Update test to latest changes --- .../java/net/sourceforge/pmd/cli/CpdCliTest.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/CpdCliTest.java b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/CpdCliTest.java index c1ee5c5152..14713414f8 100644 --- a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/CpdCliTest.java +++ b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/CpdCliTest.java @@ -69,7 +69,7 @@ class CpdCliTest extends BaseCliTest { } @Test - void testEmptyResultRendering() throws Exception { + void testNoDuplicatesResultRendering() throws Exception { final String stdout = SystemLambda.tapSystemOut(() -> { SystemLambda.tapSystemErr(() -> { final int statusCode = SystemLambda.catchSystemExit(() -> { @@ -81,7 +81,17 @@ class CpdCliTest extends BaseCliTest { assertEquals(ExecutionResult.OK.getExitCode(), statusCode); }); }); - assertEquals("" + "\n" + "", stdout.trim()); + assertEquals("\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + "", stdout.trim()); } @Override From e093f19577e1e76b83807bc1a7dc70e66b06bb8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Mon, 19 Sep 2022 18:06:00 -0300 Subject: [PATCH 143/254] Unwire absolute paths --- .../test/java/net/sourceforge/pmd/cli/CpdCliTest.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/CpdCliTest.java b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/CpdCliTest.java index 14713414f8..d311d2b099 100644 --- a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/CpdCliTest.java +++ b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/CpdCliTest.java @@ -9,6 +9,7 @@ import static org.hamcrest.Matchers.containsString; import static org.junit.jupiter.api.Assertions.assertEquals; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; @@ -81,15 +82,16 @@ class CpdCliTest extends BaseCliTest { assertEquals(ExecutionResult.OK.getExitCode(), statusCode); }); }); + final String absoluteSrcDir = Paths.get(SRC_DIR).toAbsolutePath().toString(); assertEquals("\n" + "\n" - + " \n" - + " \n" - + " \n" - + " \n" + "", stdout.trim()); } From f1cdacf200b7f8513664fd64cea145545a9fc020 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Mon, 19 Sep 2022 18:33:53 -0300 Subject: [PATCH 144/254] Unwire file separator --- .../test/java/net/sourceforge/pmd/cli/CpdCliTest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/CpdCliTest.java b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/CpdCliTest.java index d311d2b099..eee70a4104 100644 --- a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/CpdCliTest.java +++ b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/CpdCliTest.java @@ -82,16 +82,16 @@ class CpdCliTest extends BaseCliTest { assertEquals(ExecutionResult.OK.getExitCode(), statusCode); }); }); - final String absoluteSrcDir = Paths.get(SRC_DIR).toAbsolutePath().toString(); + final Path absoluteSrcDir = Paths.get(SRC_DIR).toAbsolutePath(); assertEquals("\n" + "\n" - + " \n" - + " \n" - + " \n" - + " \n" + "", stdout.trim()); } From 50ae4ddeac6fdb6acef41cd5b142cb3f6d9f4d60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Mon, 19 Sep 2022 22:26:09 -0300 Subject: [PATCH 145/254] Try to forcefully disable Picocli ANSI - This is a blind test to see if this is the cause for Windows' failing the build --- .../java/net/sourceforge/pmd/cli/BaseCliTest.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/BaseCliTest.java b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/BaseCliTest.java index 3c6f4bd1ac..89d16c0996 100644 --- a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/BaseCliTest.java +++ b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/BaseCliTest.java @@ -10,12 +10,25 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; + import net.sourceforge.pmd.cli.internal.ExecutionResult; import com.github.stefanbirkner.systemlambda.SystemLambda; abstract class BaseCliTest { + @BeforeAll + static void disablePicocliAnsi() { + System.setProperty("picocli.ansi", "false"); + } + + @AfterAll + static void resetPicocliAnsi() { + System.clearProperty("picocli.ansi"); + } + protected String runCliSuccessfully(String... args) throws Exception { return runCli(ExecutionResult.OK, args); } From fdf76f22a33af29ac88a191f547fa13b1373cbfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Mon, 19 Sep 2022 23:41:43 -0300 Subject: [PATCH 146/254] Remove verbose flag for IT --- pmd-dist/src/test/java/net/sourceforge/pmd/it/PMDExecutor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pmd-dist/src/test/java/net/sourceforge/pmd/it/PMDExecutor.java b/pmd-dist/src/test/java/net/sourceforge/pmd/it/PMDExecutor.java index 6e87ce3a13..24d793cf24 100644 --- a/pmd-dist/src/test/java/net/sourceforge/pmd/it/PMDExecutor.java +++ b/pmd-dist/src/test/java/net/sourceforge/pmd/it/PMDExecutor.java @@ -119,7 +119,7 @@ public class PMDExecutor { FORMAT_FLAG, formatter, REPORTFILE_FLAG, reportFile.toAbsolutePath().toString(), NO_PROGRESSBAR_FLAG); } else { return runPMDUnix(tempDir, reportFile, SOURCE_DIRECTORY_FLAG, sourceDirectory, RULESET_FLAG, ruleset, - FORMAT_FLAG, formatter, REPORTFILE_FLAG, reportFile.toAbsolutePath().toString(), NO_PROGRESSBAR_FLAG, "--verbose"); + FORMAT_FLAG, formatter, REPORTFILE_FLAG, reportFile.toAbsolutePath().toString(), NO_PROGRESSBAR_FLAG); } } From 4568c94f9e9f6e1cc4cfaecf1535de9077bc2378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Tue, 20 Sep 2022 14:52:14 -0300 Subject: [PATCH 147/254] Use findstr instead of find --- pmd-dist/src/main/resources/scripts/pmd.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pmd-dist/src/main/resources/scripts/pmd.bat b/pmd-dist/src/main/resources/scripts/pmd.bat index b441962598..ecac530712 100644 --- a/pmd-dist/src/main/resources/scripts/pmd.bat +++ b/pmd-dist/src/main/resources/scripts/pmd.bat @@ -7,7 +7,7 @@ set MAIN_CLASS=net.sourceforge.pmd.cli.PmdCli :: sets the jver variable to the java version, eg 90 for 9.0.1+x or 80 for 1.8.0_171-b11 or 110 for 11.0.6.1 :: sets the jvendor variable to either java (oracle) or openjdk -for /f tokens^=1^,3^,4^,5^ delims^=.-_+^"^ %%j in ('java -version 2^>^&1 ^| find "version"') do ( +for /f tokens^=1^,3^,4^,5^ delims^=.-_+^"^ %%j in ('java -version 2^>^&1 ^| findstr /c:"version"') do ( set jvendor=%%j if %%l EQU ea ( set /A "jver=%%k0" From caa3295511c2414cbd1bd07de4e971bb3c866db1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Tue, 20 Sep 2022 16:09:56 -0300 Subject: [PATCH 148/254] No need to have a default --use-version --- .../net/sourceforge/pmd/cli/commands/internal/PmdCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java index 64e29b3bdc..7cf8a36dd0 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java @@ -177,7 +177,7 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { this.reportFile = reportFile; } - @Option(names = "--use-version", defaultValue = "java-19", + @Option(names = "--use-version", description = "The language version PMD should use when parsing source code.%nValid values: ${COMPLETION-CANDIDATES}", completionCandidates = PmdLanguageVersionTypeSupport.class, converter = PmdLanguageVersionTypeSupport.class) public void setLanguageVersion(final List languageVersion) { From 9567a439fbcbac9e3768167fcdf13614de5d0172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Tue, 20 Sep 2022 16:20:01 -0300 Subject: [PATCH 149/254] Fix integration tests for Windows --- .../src/test/java/net/sourceforge/pmd/it/PMDExecutor.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pmd-dist/src/test/java/net/sourceforge/pmd/it/PMDExecutor.java b/pmd-dist/src/test/java/net/sourceforge/pmd/it/PMDExecutor.java index 24d793cf24..a4e4baa70b 100644 --- a/pmd-dist/src/test/java/net/sourceforge/pmd/it/PMDExecutor.java +++ b/pmd-dist/src/test/java/net/sourceforge/pmd/it/PMDExecutor.java @@ -56,6 +56,10 @@ public class PMDExecutor { ProcessBuilder pb = new ProcessBuilder(cmd); pb.command().addAll(arguments); pb.redirectErrorStream(false); + + // Ensure no ANSI output so tests can properly look at it + pb.environment().put("PMD_JAVA_OPTS", "-Dpicocli.ansi=false"); + final Process process = pb.start(); final ExecutionResult.Builder result = new ExecutionResult.Builder(); From 3b42cf6ea3c0e4b5cd2944633546e692dd285a13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Tue, 20 Sep 2022 19:16:45 -0300 Subject: [PATCH 150/254] Use system-dependent line separator --- .../pmd/it/BinaryDistributionIT.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java b/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java index d411da1179..877c750052 100644 --- a/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java +++ b/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java @@ -25,19 +25,19 @@ public class BinaryDistributionIT extends AbstractBinaryDistributionTest { private static final String SUPPORTED_LANGUAGES_PMD; static { - SUPPORTED_LANGUAGES_CPD = "Valid values: apex, cpp, cs, dart, ecmascript,\n" - + " fortran, gherkin, go, groovy, html, java, jsp,\n" - + " kotlin, lua, matlab, modelica, objectivec, perl,\n" + SUPPORTED_LANGUAGES_CPD = "Valid values: apex, cpp, cs, dart, ecmascript," + System.lineSeparator() + + " fortran, gherkin, go, groovy, html, java, jsp," + System.lineSeparator() + + " kotlin, lua, matlab, modelica, objectivec, perl," + System.lineSeparator() + " php, plsql, python, ruby, scala, swift, vf, xml"; - SUPPORTED_LANGUAGES_PMD = "Valid values: apex-54, ecmascript-ES6, html-,\n" - + " java-1.10, java-1.3, java-1.4, java-1.5, java-1.\n" - + " 6, java-1.7, java-1.8, java-1.9, java-10,\n" - + " java-11, java-12, java-13, java-14, java-15,\n" - + " java-16, java-17, java-18, java-18-preview,\n" - + " java-19, java-19-preview, java-5, java-6, java-7,\n" - + " java-8, java-9, jsp-, kotlin-1.6, kotlin-1.\n" - + " 6-rfc+0.1, modelica-, plsql-, pom-, scala-2.10,\n" - + " scala-2.11, scala-2.12, scala-2.13, swift-, vf-,\n" + SUPPORTED_LANGUAGES_PMD = "Valid values: apex-54, ecmascript-ES6, html-," + System.lineSeparator() + + " java-1.10, java-1.3, java-1.4, java-1.5, java-1." + System.lineSeparator() + + " 6, java-1.7, java-1.8, java-1.9, java-10," + System.lineSeparator() + + " java-11, java-12, java-13, java-14, java-15," + System.lineSeparator() + + " java-16, java-17, java-18, java-18-preview," + System.lineSeparator() + + " java-19, java-19-preview, java-5, java-6, java-7," + System.lineSeparator() + + " java-8, java-9, jsp-, kotlin-1.6, kotlin-1." + System.lineSeparator() + + " 6-rfc+0.1, modelica-, plsql-, pom-, scala-2.10," + System.lineSeparator() + + " scala-2.11, scala-2.12, scala-2.13, swift-, vf-," + System.lineSeparator() + " vm-, wsdl-, xml-, xsl-"; } From 0b7a4274b8231a70dd7e0cb10cd89b9e2772eaea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 21 Sep 2022 12:21:40 -0300 Subject: [PATCH 151/254] Ensure the new designer can find the properties - JavaFX 17 at least has the properties file laying bare --- pmd-dist/src/main/resources/scripts/pmd | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pmd-dist/src/main/resources/scripts/pmd b/pmd-dist/src/main/resources/scripts/pmd index c879341bdf..6407edb1e0 100755 --- a/pmd-dist/src/main/resources/scripts/pmd +++ b/pmd-dist/src/main/resources/scripts/pmd @@ -167,10 +167,12 @@ function add_openjfx_classpath() { then script_exit "The environment variable JAVAFX_HOME is missing." else + # The wildcard will include only jar files, but we need to access also + # property files such as javafx.properties that lay bare in the dir if [ -n "$classpath" ]; then - classpath="$classpath:${JAVAFX_HOME}/lib/*" + classpath="$classpath:${JAVAFX_HOME}/lib/*:${JAVAFX_HOME}/lib/" else - classpath="${JAVAFX_HOME}/lib/*" + classpath="${JAVAFX_HOME}/lib/*:${JAVAFX_HOME}/lib/" fi fi fi From 2b3d517581ddbf62981da456a377f123a30a86c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 21 Sep 2022 14:43:51 -0300 Subject: [PATCH 152/254] Fix PMD warnings --- .../sourceforge/pmd/cli/commands/internal/CpdCommand.java | 2 +- .../sourceforge/pmd/cli/commands/internal/PmdCommand.java | 6 +++--- .../pmd/cli/commands/internal/TreeExportCommand.java | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java index 90d5dfc2b1..6c1a8acc7a 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java @@ -155,7 +155,7 @@ public class CpdCommand extends AbstractAnalysisPmdSubcommand { /** * Provider of candidates for valid report formats. */ - private static class CpdSupportedReportFormatsCandidates implements Iterable { + private final static class CpdSupportedReportFormatsCandidates implements Iterable { @Override public Iterator iterator() { diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java index 7cf8a36dd0..94d6d3507b 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java @@ -53,7 +53,7 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { for (final String rendererName : RendererFactory.supportedRenderers()) { final Renderer renderer = RendererFactory.createRenderer(rendererName, emptyProps); - if (renderer.getPropertyDescriptors().size() > 0) { + if (!renderer.getPropertyDescriptors().isEmpty()) { reportPropertiesHelp.append(rendererName + ":" + System.getProperty("line.separator")); for (final PropertyDescriptor property : renderer.getPropertyDescriptors()) { reportPropertiesHelp.append(" ").append(property.name()).append(" - ") @@ -374,7 +374,7 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { /** * Provider of candidates for valid report formats. */ - private static class PmdSupportedReportFormatsCandidates implements Iterable { + private final static class PmdSupportedReportFormatsCandidates implements Iterable { @Override public Iterator iterator() { @@ -387,7 +387,7 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { * * Check the help for which ones are supported by each report format and possible values. */ - private static class PmdReportPropertiesCandidates implements Iterable { + private final static class PmdReportPropertiesCandidates implements Iterable { @Override public Iterator iterator() { diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/TreeExportCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/TreeExportCommand.java index c61b6ef35b..f3aa2705ad 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/TreeExportCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/TreeExportCommand.java @@ -40,7 +40,7 @@ public class TreeExportCommand extends AbstractPmdSubcommand { for (final TreeRendererDescriptor renderer : TreeRenderers.registeredRenderers()) { final PropertySource propertyBundle = renderer.newPropertyBundle(); - if (propertyBundle.getPropertyDescriptors().size() > 0) { + if (!propertyBundle.getPropertyDescriptors().isEmpty()) { reportPropertiesHelp.append(renderer.id() + ":" + System.getProperty("line.separator")); for (final PropertyDescriptor property : propertyBundle.getPropertyDescriptors()) { reportPropertiesHelp.append(" ").append(property.name()).append(" - ") @@ -121,7 +121,7 @@ public class TreeExportCommand extends AbstractPmdSubcommand { /** * Provides completion candidates for the report format. */ - private static class TreeRenderersCandidates implements Iterable { + private final static class TreeRenderersCandidates implements Iterable { @Override public Iterator iterator() { @@ -134,7 +134,7 @@ public class TreeExportCommand extends AbstractPmdSubcommand { * * Check the help for which ones are supported by each report format and possible values. */ - private static class TreeExportReportPropertiesCandidates implements Iterable { + private final static class TreeExportReportPropertiesCandidates implements Iterable { @Override public Iterator iterator() { From 0de5b8fb039489aa46b9e7177d30653833cab30b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 21 Sep 2022 15:00:03 -0300 Subject: [PATCH 153/254] Fix checkstyle warnings --- .../net/sourceforge/pmd/cli/commands/internal/CpdCommand.java | 2 +- .../net/sourceforge/pmd/cli/commands/internal/PmdCommand.java | 4 ++-- .../pmd/cli/commands/internal/TreeExportCommand.java | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java index 6c1a8acc7a..2b809050db 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java @@ -155,7 +155,7 @@ public class CpdCommand extends AbstractAnalysisPmdSubcommand { /** * Provider of candidates for valid report formats. */ - private final static class CpdSupportedReportFormatsCandidates implements Iterable { + private static final class CpdSupportedReportFormatsCandidates implements Iterable { @Override public Iterator iterator() { diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java index 94d6d3507b..f7969691a2 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java @@ -374,7 +374,7 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { /** * Provider of candidates for valid report formats. */ - private final static class PmdSupportedReportFormatsCandidates implements Iterable { + private static final class PmdSupportedReportFormatsCandidates implements Iterable { @Override public Iterator iterator() { @@ -387,7 +387,7 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { * * Check the help for which ones are supported by each report format and possible values. */ - private final static class PmdReportPropertiesCandidates implements Iterable { + private static final class PmdReportPropertiesCandidates implements Iterable { @Override public Iterator iterator() { diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/TreeExportCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/TreeExportCommand.java index f3aa2705ad..84b55270d3 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/TreeExportCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/TreeExportCommand.java @@ -121,7 +121,7 @@ public class TreeExportCommand extends AbstractPmdSubcommand { /** * Provides completion candidates for the report format. */ - private final static class TreeRenderersCandidates implements Iterable { + private static final class TreeRenderersCandidates implements Iterable { @Override public Iterator iterator() { @@ -134,7 +134,7 @@ public class TreeExportCommand extends AbstractPmdSubcommand { * * Check the help for which ones are supported by each report format and possible values. */ - private final static class TreeExportReportPropertiesCandidates implements Iterable { + private static final class TreeExportReportPropertiesCandidates implements Iterable { @Override public Iterator iterator() { From c093ce73acad9d6571ddcb59211d7b771df98b3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 21 Sep 2022 18:23:51 -0300 Subject: [PATCH 154/254] Cleanup module dependencies --- pmd-cli/pom.xml | 7 ------- pmd-core/pom.xml | 4 ---- 2 files changed, 11 deletions(-) diff --git a/pmd-cli/pom.xml b/pmd-cli/pom.xml index 0773366c89..52256cd444 100644 --- a/pmd-cli/pom.xml +++ b/pmd-cli/pom.xml @@ -316,12 +316,5 @@ tests test-jar - - - - me.tongfei - progressbar - 0.9.3 - diff --git a/pmd-core/pom.xml b/pmd-core/pom.xml index 08e5e4daaa..8a20b776cd 100644 --- a/pmd-core/pom.xml +++ b/pmd-core/pom.xml @@ -76,10 +76,6 @@ com.beust jcommander - - info.picocli - picocli - net.sf.saxon From e590f40d5aa7587b798844cfc53402780f4ff259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 21 Sep 2022 18:29:25 -0300 Subject: [PATCH 155/254] Ensure Windows can also find javafx.properties --- pmd-dist/src/main/resources/scripts/pmd.bat | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pmd-dist/src/main/resources/scripts/pmd.bat b/pmd-dist/src/main/resources/scripts/pmd.bat index ecac530712..9c5b3cdf75 100644 --- a/pmd-dist/src/main/resources/scripts/pmd.bat +++ b/pmd-dist/src/main/resources/scripts/pmd.bat @@ -61,7 +61,9 @@ if %_needjfxlib% EQU 1 ( pause exit ) - set classpath=%TOPDIR%\conf;%TOPDIR%\lib\*;%JAVAFX_HOME%\lib\* + :: The wildcard will include only jar files, but we need to access also + :: property files such as javafx.properties that lay bare in the dir + set classpath=%TOPDIR%\conf;%TOPDIR%\lib\*;%JAVAFX_HOME%\lib\*;%JAVAFX_HOME%\lib\ ) else ( set classpath=%TOPDIR%\conf;%TOPDIR%\lib\* ) From e7c8d5e7d650c5f563e6d1ea06e934aa8ed8b2f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Wed, 21 Sep 2022 18:31:15 -0300 Subject: [PATCH 156/254] Ensure bash completion is part of the bin dist --- .../test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java | 1 + 1 file changed, 1 insertion(+) diff --git a/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java b/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java index 877c750052..65d2912f14 100644 --- a/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java +++ b/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java @@ -56,6 +56,7 @@ public class BinaryDistributionIT extends AbstractBinaryDistributionTest { result.add(basedir + "bin/pmd"); result.add(basedir + "bin/pmd.bat"); result.add(basedir + "conf/simplelogger.properties"); + result.add(basedir + "shell/pmd-completion.sh"); result.add(basedir + "lib/pmd-core-" + PMDVersion.VERSION + ".jar"); result.add(basedir + "lib/pmd-java-" + PMDVersion.VERSION + ".jar"); return result; From a4d029f2af47432af51f504ca6f9502d0f3203cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Tue, 27 Sep 2022 10:58:29 -0300 Subject: [PATCH 157/254] Remove ./ from commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Clément Fournier --- docs/pages/pmd/languages/java.md | 2 +- docs/pages/pmd/languages/xml.md | 2 +- docs/pages/pmd/userdocs/cli_reference.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/pages/pmd/languages/java.md b/docs/pages/pmd/languages/java.md index 32eff4a6ba..77e8e6665b 100644 --- a/docs/pages/pmd/languages/java.md +++ b/docs/pages/pmd/languages/java.md @@ -41,7 +41,7 @@ In order to analyze a project with PMD that uses preview language features, you' it via the environment variable `PMD_JAVA_OPTS` and select the new language version, e.g. `19-preview`: export PMD_JAVA_OPTS=--enable-preview - ./pmd run --use-version java-19-preview ... + pmd run --use-version java-19-preview ... Note: we only support preview language features for the latest two java versions. diff --git a/docs/pages/pmd/languages/xml.md b/docs/pages/pmd/languages/xml.md index cb0213663f..e367ae720d 100644 --- a/docs/pages/pmd/languages/xml.md +++ b/docs/pages/pmd/languages/xml.md @@ -31,7 +31,7 @@ Some XML-based file formats do not conventionally use a `.xml` extension. To ass these files with the XML language, you need to use the `--force-language xml` command-line arguments, for instance: ``` -$ ./pmd run -d /home/me/src/xml-file.ext -f text -R ruleset.xml --force-language xml +$ pmd run -d /home/me/src/xml-file.ext -f text -R ruleset.xml --force-language xml ``` Please refer to [PMD CLI reference](pmd_userdocs_cli_reference.html#analyze-other-xml-formats) for more examples. diff --git a/docs/pages/pmd/userdocs/cli_reference.md b/docs/pages/pmd/userdocs/cli_reference.md index 1bcd3a9758..a90d671921 100644 --- a/docs/pages/pmd/userdocs/cli_reference.md +++ b/docs/pages/pmd/userdocs/cli_reference.md @@ -246,7 +246,7 @@ All formats are described at [PMD Report formats](pmd_userdocs_report_formats.ht If your xml language doesn't use `xml` as file extension, you can still use PMD with `--force-language`: ``` -$ ./pmd analyze -d /home/me/src/xml-file.ext -f text -R ruleset.xml --force-language xml +$ pmd analyze -d /home/me/src/xml-file.ext -f text -R ruleset.xml --force-language xml ``` You can also specify a directory instead of a single file. Then all files are analyzed. In that case, From db480352b51aae755d53a045ebecd728447400c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Tue, 27 Sep 2022 10:59:01 -0300 Subject: [PATCH 158/254] Improve wording MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Clément Fournier --- docs/pages/pmd/userdocs/cli_reference.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/pages/pmd/userdocs/cli_reference.md b/docs/pages/pmd/userdocs/cli_reference.md index a90d671921..82611a1010 100644 --- a/docs/pages/pmd/userdocs/cli_reference.md +++ b/docs/pages/pmd/userdocs/cli_reference.md @@ -205,8 +205,8 @@ to be "debug". The language is determined automatically by PMD from the file extensions. Some languages such as "Java" however support multiple versions. The default version will be used, which is usually the latest supported -non-preview version. If you want to use an older version, so that e.g. rules, that suggest usage of language features, -that are not available yet, won't be executed, you need to specify a specific version via the `--use-version` +non-preview version. If you want to use an older version, so that e.g. rules that suggest usage of language features +that are not available yet won't be executed, you need to specify a specific version via the `--use-version` parameter. Example: From 333b9c8d6d64c0dd6ec2482d072fd3ae802b04e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Tue, 27 Sep 2022 11:00:07 -0300 Subject: [PATCH 159/254] Typo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Clément Fournier --- docs/pages/next_major_development.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pages/next_major_development.md b/docs/pages/next_major_development.md index 8cdead8413..9bee004c54 100644 --- a/docs/pages/next_major_development.md +++ b/docs/pages/next_major_development.md @@ -39,7 +39,7 @@ Commands: Warning: May not support the full CPD feature set ast-dump Experimental: dumps the AST of parsing source code Exit Codes: - 0 Succesful analysis, no violations found + 0 Successful analysis, no violations found 1 An unexpected error occurred during execution 2 Usage error, please refer to the command help 4 Successful analysis, at least 1 violation found From efc4144e2c60c0a449c5fc4157c2bcf32a12c092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Tue, 27 Sep 2022 11:03:49 -0300 Subject: [PATCH 160/254] Improve changelog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Clément Fournier --- docs/pages/next_major_development.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/pages/next_major_development.md b/docs/pages/next_major_development.md index 9bee004c54..e8f570679d 100644 --- a/docs/pages/next_major_development.md +++ b/docs/pages/next_major_development.md @@ -22,8 +22,9 @@ The new logo is available from the [Logo Project Page](pmd_projectdocs_logo.html ### Revamped Command Line Interface -PMD now ships with a unified Command Line Interface for both Linux/Unix and Windows. Under the `pmd` executable (`pmd.bat` for Windows), -all utilities can be launched similar to how git is normally used. All commands and options are thoroughly documented in the help, +PMD now ships with a unified Command Line Interface for both Linux/Unix and Windows. Instead of having a collection of scripts +for the different utilities shipped with PMD, a single script `pmd` (`pmd.bat` for Windows) can now launch all +utilities using subcommands, e.g. `pmd run`, `pmd designer`. All commands and options are thoroughly documented in the help, with full color support where available. Moreover, efforts were made to provide consistency in the usage of all PMD utilities. ``` @@ -45,6 +46,14 @@ Exit Codes: 4 Successful analysis, at least 1 violation found ``` +For instance, where you previously would have run +```shell +run.sh pmd -d src -R ruleset.xml +``` +you should now use +```shell +pmd run -d src -R ruleset.xml +``` Additionally, we now provide a completion script for Bash/Zsh to further help daily usage. This script can be found under `shell/pmd-completion.sh` in the binary distribution. To use it, edit your `~/.bashrc` / `~/.zshrc` file and add the following line: From 5ede9ee45fb7654c7cfb0339a059c23105def9cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Thu, 29 Sep 2022 18:28:32 -0300 Subject: [PATCH 161/254] Create an include for command snippets - This should improve how we give examples for different platforms throughout the docs. - I also improved the doc around adding PMD to PATH --- docs/_includes/cli_example.html | 24 ++++++++ docs/pages/pmd/userdocs/installation.md | 79 +++++++++---------------- 2 files changed, 52 insertions(+), 51 deletions(-) create mode 100644 docs/_includes/cli_example.html diff --git a/docs/_includes/cli_example.html b/docs/_includes/cli_example.html new file mode 100644 index 0000000000..936f5d146b --- /dev/null +++ b/docs/_includes/cli_example.html @@ -0,0 +1,24 @@ +{% assign linux_cmd = include.linux | split: ' ' | first %} +{% assign linux_tail = include.linux | remove_first: linux_cmd %} +{% assign windows_cmd = include.windows | split: ' ' | first %} +{% assign windows_tail = include.windows | remove_first: windows_cmd %} + +
+ + +
+
+
~ $ {{linux_cmd}}{{linux_tail}}
+
+
+
C:\> {{windows_cmd}}{{windows_tail}}
+
+
+
diff --git a/docs/pages/pmd/userdocs/installation.md b/docs/pages/pmd/userdocs/installation.md index 32520d96ee..b3f283924e 100644 --- a/docs/pages/pmd/userdocs/installation.md +++ b/docs/pages/pmd/userdocs/installation.md @@ -31,7 +31,20 @@ sidebar: pmd_sidebar PMD is distributed as a zip archive, which includes both [PMD](#running-pmd-via-command-line) and [CPD](pmd_userdocs_cpd.html). You can download the latest binary distribution from [the github releases page](https://github.com/pmd/pmd/releases). -Unzip it into any directory, optionally add the `bin` subdirectory in your `PATH`, and you're good to go! +It's highly recommended (but not required) to include it to your `PATH`. + +On Linux you can do this by adding `PATH=$PATH:*path_to_pmd*/bin/` to your `~/.bashrc` / `~/.zshrc` file. + +On Windows this is achieved by: +1. On the **Start menu**, right-click **Computer**. +2. On the context menu, click **Properties**. +3. In the **System** dialog box, click **Advanced system settings**. +4. On the **Advanced** tab of the **System Properties** dialog box, click **Environment Variables** +5. In the **System Variables** box of the **Environment Variables** dialog box, scroll to **Path** and select it. +6. Click the lower of the two **Edit** buttons in the dialog box. +7. In the **Edit System Variable** dialog box, scroll to the end of the string in the **Variable value** box and add a semicolon (;). +8. Add the proper value for `*path_to_pmd*/bin/` after the semicolon. +9. Click **OK** in three successive dialog boxes, and then close the **System** dialog box. #### Shell completion @@ -78,41 +91,22 @@ Additionally, the following options, are specified most of the time even though The following shows a sample run of PMD with the `text` format: +{% include cli_example.html + id="pmd" + linux="pmd analyze -d ../../../src/main/java/ -f text -R rulesets/java/quickstart.xml -
- - -
-
-
~ $ cd ~/bin/pmd-bin-{{site.pmd.version}}/bin
-~/.../bin $ ./pmd analyze -d ../../../src/main/java/ -f text -R rulesets/java/quickstart.xml
-  
   .../src/main/java/com/me/RuleSet.java:123  These nested if statements could be combined
   .../src/main/java/com/me/RuleSet.java:231  Useless parentheses.
   .../src/main/java/com/me/RuleSet.java:232  Useless parentheses.
   .../src/main/java/com/me/RuleSet.java:357  These nested if statements could be combined
-  .../src/main/java/com/me/RuleSetWriter.java:66     Avoid empty catch blocks
-
-
-
C:\ > cd C:\pmd-bin-{{site.pmd.version}}\bin
-C:\...\bin > .\pmd.bat analyze -d ..\..\src\main\java\ -f text -R rulesets/java/quickstart.xml
-      
+  .../src/main/java/com/me/RuleSetWriter.java:66     Avoid empty catch blocks"
+   windows="pmd.bat analyze -d ..\..\src\main\java\ -f text -R rulesets/java/quickstart.xml
+
   .../src/main/java/com/me/RuleSet.java:123  These nested if statements could be combined
   .../src/main/java/com/me/RuleSet.java:231  Useless parentheses.
   .../src/main/java/com/me/RuleSet.java:232  Useless parentheses.
   .../src/main/java/com/me/RuleSet.java:357  These nested if statements could be combined
-  .../src/main/java/com/me/RuleSetWriter.java:66     Avoid empty catch blocks
-
-
-
- + .../src/main/java/com/me/RuleSetWriter.java:66 Avoid empty catch blocks" %} ## Running CPD via command line @@ -135,21 +129,9 @@ There are two required parameters: The following shows a sample run of CPD with the `text` format: - -
- - -
-
-
~ $ cd ~/bin/pmd-bin-{{site.pmd.version}}/bin
-~/.../bin $ ./pmd cpd --minimum-tokens 100 --files /home/me/src
+{% include cli_example.html
+   id="cpd"
+   linux="pmd cpd --minimum-tokens 100 --files /home/me/src
 
   Found a 7 line (110 tokens) duplication in the following files:
   Starting at line 579 of /home/me/src/test/java/foo/FooTypeTest.java
@@ -161,11 +143,8 @@ There are two required parameters:
           assertEquals(Boolean.TYPE, expressions.get(index++).getType());
           assertEquals(Boolean.TYPE, expressions.get(index++).getType());
           assertEquals(Boolean.TYPE, expressions.get(index++).getType());
-          assertEquals(Boolean.TYPE, expressions.get(index++).getType());
-
-
-
C:\ > cd C:\pmd-bin-{{site.pmd.version}}\bin
-C:\...\bin > .\pmd.bat cpd --minimum-tokens 100 --files c:\temp\src
+          assertEquals(Boolean.TYPE, expressions.get(index++).getType());"
+    windows="pmd.bat cpd --minimum-tokens 100 --files /home/me/src
 
   Found a 7 line (110 tokens) duplication in the following files:
   Starting at line 579 of c:\temp\src\test\java\foo\FooTypeTest.java
@@ -177,7 +156,5 @@ There are two required parameters:
           assertEquals(Boolean.TYPE, expressions.get(index++).getType());
           assertEquals(Boolean.TYPE, expressions.get(index++).getType());
           assertEquals(Boolean.TYPE, expressions.get(index++).getType());
-          assertEquals(Boolean.TYPE, expressions.get(index++).getType());
-
-
-
+ assertEquals(Boolean.TYPE, expressions.get(index++).getType());" %} + From 80f784edc7771dc61634d99f05c9ec526429fe16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Fri, 30 Sep 2022 11:32:18 -0300 Subject: [PATCH 162/254] Revamp all CLI examples --- docs/_includes/cli_example.html | 28 +++++--- docs/pages/pmd/userdocs/cpd/cpd.md | 70 +++++++++++-------- .../userdocs/extending/designer_reference.md | 6 +- .../pmd/userdocs/extending/your_first_rule.md | 11 ++- 4 files changed, 72 insertions(+), 43 deletions(-) diff --git a/docs/_includes/cli_example.html b/docs/_includes/cli_example.html index 936f5d146b..6694924a5f 100644 --- a/docs/_includes/cli_example.html +++ b/docs/_includes/cli_example.html @@ -1,12 +1,24 @@ -{% assign linux_cmd = include.linux | split: ' ' | first %} -{% assign linux_tail = include.linux | remove_first: linux_cmd %} -{% assign windows_cmd = include.windows | split: ' ' | first %} -{% assign windows_tail = include.windows | remove_first: windows_cmd %} +{% assign linux_cmd_blob = '' %} +{% assign linux_split_cmds = include.linux | newline_to_br | strip_newlines | split: '
' %} +{% for linux_raw_cmd in linux_split_cmds %} + {% assign linux_cmd = linux_raw_cmd | strip | split: ' ' | first %} + {% assign linux_tail = linux_raw_cmd | strip | remove_first: linux_cmd %} + {% capture linux_cmd_blob %}{{linux_cmd_blob}}~ $ {{linux_cmd}}{{linux_tail}} +{% endcapture %} +{% endfor %} +{% assign windows_cmd_blob = '' %} +{% assign windows_split_cmds = include.windows | newline_to_br | strip_newlines | split: '
' %} +{% for windows_raw_cmd in windows_split_cmds %} + {% assign windows_cmd = windows_raw_cmd | strip | split: ' ' | first %} + {% assign windows_tail = windows_raw_cmd | strip | remove_first: windows_cmd %} + {% capture windows_cmd_blob %}{{windows_cmd_blob}}C:\> {{windows_cmd}}{{windows_tail}} +{% endcapture %} +{% endfor %}
-
diff --git a/docs/pages/pmd/userdocs/cpd/cpd.md b/docs/pages/pmd/userdocs/cpd/cpd.md index ce9d3d77d9..51c951ebbf 100644 --- a/docs/pages/pmd/userdocs/cpd/cpd.md +++ b/docs/pages/pmd/userdocs/cpd/cpd.md @@ -173,54 +173,67 @@ Novice as much as advanced readers may want to [read on on Refactoring Guru](htt ### Examples -_Note:_ The following example use the Linux start script. For Windows, just replace "./pmd cpd" by "pmd.bat cpd". - - Minimum required options: Just give it the minimum duplicate size and the source directory: - $ ./pmd cpd --minimum-tokens 100 --dir /usr/local/java/src/java +{% include cli_example.html + id="basic" + linux="pmd cpd --minimum-tokens 100 --dir src/main/java" + windows="pmd.bat cpd --minimum-tokens 100 --dir src\main\java" %} You can also specify the language: - $ ./pmd cpd --minimum-tokens 100 --dir /path/to/c/source --language cpp +{% include cli_example.html + id="lang" + linux="pmd cpd --minimum-tokens 100 --dir src/main/cpp --language cpp" + windows="pmd.bat cpd --minimum-tokens 100 --dir src\main\cpp --language cpp" %} You may wish to check sources that are stored in different directories: - $ ./pmd cpd --minimum-tokens 100 --dir /path/to/other/source --dir /path/to/other/source --dir /path/to/other/source --language fortran +{% include cli_example.html + id="multiple" + linux="pmd cpd --minimum-tokens 100 --dir src/main/java --dir src/test/java" + windows="pmd.bat cpd --minimum-tokens 100 --dir src\main\java --dir src\test\java" %} -There should be no limit to the number of `--dir`, you may add... But if you stumble one, please tell us ! +There is no limit to the number of `--dir`, you may add. And if you're checking a C source tree with duplicate files in different architecture directories you can skip those using `--skip-duplicate-files`: - $ ./pmd cpd --minimum-tokens 100 --dir /path/to/c/source --language cpp --skip-duplicate-files +{% include cli_example.html + id="duplicates" + linux="pmd cpd --minimum-tokens 100 --dir src/main/cpp --language cpp --skip-duplicate-files" + windows="pmd.bat cpd --minimum-tokens 100 --dir src\main\cpp --language cpp --skip-duplicate-files" %} You can also specify the encoding to use when parsing files: - $ ./pmd cpd --minimum-tokens 100 --dir /usr/local/java/src/java --encoding utf-16le +{% include cli_example.html + id="encoding" + linux="pmd cpd --minimum-tokens 100 --dir src/main/java --encoding utf-16le" + windows="pmd.bat cpd --minimum-tokens 100 --dir src\main\java --encoding utf-16le" %} You can also specify a report format - here we're using the XML report: - $ ./pmd cpd --minimum-tokens 100 --dir /usr/local/java/src/java --format xml +{% include cli_example.html + id="report" + linux="pmd cpd --minimum-tokens 100 --dir src/main/java --format xml" + windows="pmd.bat cpd --minimum-tokens 100 --dir src\main\java --format xml" %} The default format is a text report, but there are [other supported formats](#available-report-formats) -Note that CPD is pretty memory-hungry; you may need to give Java more memory to run it, like this: - - $ export PMD_JAVA_OPTS=-Xmx512m - $ ./pmd cpd --minimum-tokens 100 --dir /usr/local/java/src/java - -In order to change the heap size under Windows, you'll need to edit the batch file `pmd.bat` or -set the environment variable `PMD_JAVA_OPTS` prior to starting CPD: - - C:\ > cd C:\pmd-bin-{{site.pmd.version}}\bin - C:\...\bin > set PMD_JAVA_OPTS=-Xmx512m - C:\...\bin > .\pmd.bat cpd --minimum-tokens 100 --dir c:\temp\src - +Note that CPD's memory usage increases linearly with the size of the analyzed source code; you may need to give Java more memory to run it, like this: +{% include cli_example.html + id="memchange" + linux="export PMD_JAVA_OPTS=-Xmx512m + pmd cpd --minimum-tokens 100 --dir src/main/java" + windows="set PMD_JAVA_OPTS=-Xmx512m + pmd.bat cpd --minimum-tokens 100 --dir src\main\java" %} If you specify a source directory but don't want to scan the sub-directories, you can use the non-recursive option: - $ ./pmd cpd --minimum-tokens 100 --non-recursive --dir /usr/local/java/src/java +{% include cli_example.html + id="nonrecursive" + linux="pmd cpd --minimum-tokens 100 --dir src/main/java --non-recursive" + windows="pmd.bat cpd --minimum-tokens 100 --dir src\main\java --non-recursive" %} ### Exit status @@ -393,13 +406,10 @@ the CPD task as usual and right after it invoke the Ant XSLT script like this: CPD also comes with a simple GUI. You can start it through the unified CLI interface provided in the `bin` folder: -For Windows: - - pmd.bat cpd-gui - -For Linux: - - ./pmd cpd-gui +{% include cli_example.html + id="gui" + linux="pmd cpd-gui" + windows="pmd.bat cpd-gui" %} Here's a screenshot of CPD after running on the JDK 8 java.lang package: diff --git a/docs/pages/pmd/userdocs/extending/designer_reference.md b/docs/pages/pmd/userdocs/extending/designer_reference.md index 188c60cb57..6c9383f9a2 100644 --- a/docs/pages/pmd/userdocs/extending/designer_reference.md +++ b/docs/pages/pmd/userdocs/extending/designer_reference.md @@ -16,8 +16,10 @@ The app needs JRE 1.8 or above to run. Be aware that on JRE 11+, the JavaFX dist If the bin directory of your PMD distribution is on your shell's path, then you can **launch the app** with - pmd designer on Linux/ OSX - pmd.bat designer on Windows +{% include cli_example.html + id="pmd" + linux="pmd designer" + windows="pmd.bat designer" %} {% include note.html content="pmd-ui.jar is not a runnable jar, because it doesn't include any PMD language module, or PMD Core. " %} diff --git a/docs/pages/pmd/userdocs/extending/your_first_rule.md b/docs/pages/pmd/userdocs/extending/your_first_rule.md index de3a127f81..d9ff74802b 100644 --- a/docs/pages/pmd/userdocs/extending/your_first_rule.md +++ b/docs/pages/pmd/userdocs/extending/your_first_rule.md @@ -22,10 +22,15 @@ expressions. The rule designer is a tool that packs a lot of features to help you develop XPath rules quickly and painlessly. Basically, it allows you to examine the AST of a code -snippet and evaluate an XPath expression against it. +snippet and evaluate an XPath expression against it. You can launch it from Command Line as follows: -Like for PMD and CPD, you can launch it using `pmd designer` on Linux/Unix -and `pmd.bat designer` on Windows. The interface looks like the following: +{% include cli_example.html + id="designer" + linux="pmd designer" + windows="pmd.bat designer" %} + + +The interface looks like the following: {% include image.html file="userdocs/designer-overview-with-nums.png" alt="Designer overview" %} From 65476303b39fbfbc0cf92334b8d199bdf5d97a79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Fri, 30 Sep 2022 11:35:20 -0300 Subject: [PATCH 163/254] Explicit default behavior --- docs/pages/pmd/userdocs/cli_reference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pages/pmd/userdocs/cli_reference.md b/docs/pages/pmd/userdocs/cli_reference.md index 82611a1010..a3a9276e39 100644 --- a/docs/pages/pmd/userdocs/cli_reference.md +++ b/docs/pages/pmd/userdocs/cli_reference.md @@ -134,7 +134,7 @@ The tool comes with a rather extensive help text, simply running with `--help`! and causes the `--cache` option to be discarded if it is provided." %} {% include custom/cli_option_row.html options="--[no-]progress" - description="Enables / disable progress bar indicator of live analysis progress." + description="Enables / disable progress bar indicator of live analysis progress. This ie enabled by default." %} {% include custom/cli_option_row.html options="--property,-P" option_arg="name>= Date: Thu, 6 Oct 2022 14:17:29 -0300 Subject: [PATCH 164/254] Review feedback --- docs/_data/sidebars/pmd_sidebar.yml | 13 ++------ docs/index.md | 2 +- docs/pages/pmd/userdocs/cli_reference.md | 33 ++++++++++++------- docs/pages/pmd/userdocs/installation.md | 2 +- .../pmd/userdocs/suppressing_warnings.md | 2 +- .../pmd/cli/commands/internal/PmdCommand.java | 7 ++-- .../commands/internal/TreeExportCommand.java | 7 ++-- .../main/java/net/sourceforge/pmd/PMD.java | 4 +++ .../java/net/sourceforge/pmd/cpd/CPD.java | 5 +++ 9 files changed, 43 insertions(+), 32 deletions(-) diff --git a/docs/_data/sidebars/pmd_sidebar.yml b/docs/_data/sidebars/pmd_sidebar.yml index fef62bf958..25344731ec 100644 --- a/docs/_data/sidebars/pmd_sidebar.yml +++ b/docs/_data/sidebars/pmd_sidebar.yml @@ -385,18 +385,9 @@ entries: - title: Apex url: /pmd_languages_apex.html output: web, pdf - - title: null + - title: Java + url: /pmd_languages_java.html output: web, pdf - subfolders: - - title: Java - output: web, pdf - subfolderitems: - - title: Java Support - url: /pmd_languages_java.html - output: web, pdf - - title: Java Versions - url: /pmd_languages_java_versions.html - output: web, pdf - title: JSP url: /pmd_languages_jsp.html output: web, pdf diff --git a/docs/index.md b/docs/index.md index 10f3edb3cc..29f3b3d346 100644 --- a/docs/index.md +++ b/docs/index.md @@ -26,7 +26,7 @@ additional_js: **PMD** is a static source code analyzer. It finds common programming flaws like unused variables, empty catch blocks, unnecessary object creation, and -so forth. It's mainly concerned with **Java and Apex**, but **supports 13 other +so forth. It's mainly concerned with **Java and Apex**, but **supports 14 other languages**. PMD features many **built-in checks** (in PMD lingo, *rules*), which are documented diff --git a/docs/pages/pmd/userdocs/cli_reference.md b/docs/pages/pmd/userdocs/cli_reference.md index a3a9276e39..ec32d13ac7 100644 --- a/docs/pages/pmd/userdocs/cli_reference.md +++ b/docs/pages/pmd/userdocs/cli_reference.md @@ -176,8 +176,12 @@ if you want to analyze a project, that uses one of OpenJDK's [Preview Language F Just set the environment variable `PMD_JAVA_OPTS` before executing PMD, e.g. - export PMD_JAVA_OPTS="--enable-preview" - ./pmd analyze -d ../../../src/main/java/ -f text -R rulesets/java/quickstart.xml +{% include cli_example.html + id="preview" + linux="export PMD_JAVA_OPTS=\"--enable-preview\" + pmd analyze -d src/main/java/ -f text -R rulesets/java/quickstart.xml" + windows="set \"PMD_JAVA_OPTS=--enable-preview\" + pmd.bat analyze -d src\main\java\ -f text -R rulesets/java/quickstart.xml" %} ## Exit Status @@ -245,20 +249,25 @@ All formats are described at [PMD Report formats](pmd_userdocs_report_formats.ht If your xml language doesn't use `xml` as file extension, you can still use PMD with `--force-language`: -``` -$ pmd analyze -d /home/me/src/xml-file.ext -f text -R ruleset.xml --force-language xml -``` +{% include cli_example.html + id="force" + linux="pmd analyze -d src/xml-file.ext -f text -R ruleset.xml --force-language xml" + windows="pmd.bat analyze -d src\xml-file.ext -f text -R ruleset.xml --force-language xml" %} You can also specify a directory instead of a single file. Then all files are analyzed. In that case, parse errors are suppressed in order to reduce irrelevant noise: -``` -$ ./pmd analyze -d /home/me/src/ -f text -R ruleset.xml --force-language xml -``` +{% include cli_example.html + id="force-dir" + linux="pmd analyze -d src/ -f text -R ruleset.xml --force-language xml" + windows="pmd.bat analyze -d src\ -f text -R ruleset.xml --force-language xml" %} Alternatively, you can create a filelist to only analyze files with a given extension: -``` -$ find /home/me/src -name "*.ext" > /home/me/src/filelist.txt -$ ./pmd analyze --file-list /home/me/src/filelist.txt -f text -R ruleset.xml --force-language xml -``` +{% include cli_example.html + id="file-list" + linux="find src/ -name \"*.ext\" > filelist.txt + pmd analyze --file-list filelist.txt -f text -R ruleset.xml --force-language xml" + windows="for /r src/ %i in (*.ext) do echo %i >> filelist.txt + pmd.bat analyze --file-list filelist.txt -f text -R ruleset.xml --force-language xml" %} + diff --git a/docs/pages/pmd/userdocs/installation.md b/docs/pages/pmd/userdocs/installation.md index b3f283924e..6292786a0e 100644 --- a/docs/pages/pmd/userdocs/installation.md +++ b/docs/pages/pmd/userdocs/installation.md @@ -23,7 +23,7 @@ sidebar: pmd_sidebar * For Windows: [Winzip](http://winzip.com) or the free [7-zip](http://www.7-zip.org/) * For Linux / Unix: [InfoZip](http://infozip.sourceforge.net/) -{% include note.html content="For executing the Designer (./pmd designer) using [OpenJDK](http://jdk.java.net) or Java 11+, you need additionally [OpenJFX](http://jdk.java.net). Download it, extract it and set the environment variable JAVAFX_HOME." %} +{% include note.html content="For executing the Designer (`pmd designer`) using [OpenJDK](http://jdk.java.net) or Java 11+, you need additionally [OpenJFX](http://jdk.java.net). Download it, extract it and set the environment variable JAVAFX_HOME." %} ### Installation diff --git a/docs/pages/pmd/userdocs/suppressing_warnings.md b/docs/pages/pmd/userdocs/suppressing_warnings.md index 1ab582ac38..c4adcb4695 100644 --- a/docs/pages/pmd/userdocs/suppressing_warnings.md +++ b/docs/pages/pmd/userdocs/suppressing_warnings.md @@ -116,7 +116,7 @@ public class Foo { } } -$ ./pmd run -d Foo.java -f text -R java-unusedcode --suppress-marker TURN_OFF_WARNINGS +$ pmd analyze -d Foo.java -f text -R java-unusedcode --suppress-marker TURN_OFF_WARNINGS No problems found! UnusedLocalVariable rule violation suppressed by //NOPMD in /home/tom/pmd/pmd/bin/Foo.java ``` diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java index f7969691a2..77b1fe3a01 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java @@ -49,19 +49,20 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { static { final Properties emptyProps = new Properties(); final StringBuilder reportPropertiesHelp = new StringBuilder(); + final String lineSeparator = System.lineSeparator(); for (final String rendererName : RendererFactory.supportedRenderers()) { final Renderer renderer = RendererFactory.createRenderer(rendererName, emptyProps); if (!renderer.getPropertyDescriptors().isEmpty()) { - reportPropertiesHelp.append(rendererName + ":" + System.getProperty("line.separator")); + reportPropertiesHelp.append(rendererName + ":" + lineSeparator); for (final PropertyDescriptor property : renderer.getPropertyDescriptors()) { reportPropertiesHelp.append(" ").append(property.name()).append(" - ") - .append(property.description()).append(System.getProperty("line.separator")); + .append(property.description()).append(lineSeparator); final Object deflt = property.defaultValue(); if (deflt != null && !"".equals(deflt)) { reportPropertiesHelp.append(" Default: ").append(deflt) - .append(System.getProperty("line.separator")); + .append(lineSeparator); } } } diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/TreeExportCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/TreeExportCommand.java index 84b55270d3..7b51057dd5 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/TreeExportCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/TreeExportCommand.java @@ -37,18 +37,19 @@ public class TreeExportCommand extends AbstractPmdSubcommand { static { final StringBuilder reportPropertiesHelp = new StringBuilder(); + final String lineSeparator = System.lineSeparator(); for (final TreeRendererDescriptor renderer : TreeRenderers.registeredRenderers()) { final PropertySource propertyBundle = renderer.newPropertyBundle(); if (!propertyBundle.getPropertyDescriptors().isEmpty()) { - reportPropertiesHelp.append(renderer.id() + ":" + System.getProperty("line.separator")); + reportPropertiesHelp.append(renderer.id() + ":" + lineSeparator); for (final PropertyDescriptor property : propertyBundle.getPropertyDescriptors()) { reportPropertiesHelp.append(" ").append(property.name()).append(" - ") - .append(property.description()).append(System.getProperty("line.separator")); + .append(property.description()).append(lineSeparator); final Object deflt = property.defaultValue(); if (deflt != null && !"".equals(deflt)) { reportPropertiesHelp.append(" Default: ").append(StringUtil.escapeWhitespace(deflt)) - .append(System.getProperty("line.separator")); + .append(lineSeparator); } } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java b/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java index 288f8872fa..ff10365c07 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java @@ -22,6 +22,7 @@ import org.slf4j.LoggerFactory; import org.slf4j.event.Level; import net.sourceforge.pmd.Report.GlobalReportBuilderListener; +import net.sourceforge.pmd.annotation.DeprecatedUntil700; import net.sourceforge.pmd.benchmark.TextTimingReportRenderer; import net.sourceforge.pmd.benchmark.TimeTracker; import net.sourceforge.pmd.benchmark.TimingReport; @@ -45,7 +46,10 @@ import net.sourceforge.pmd.util.log.internal.SimpleMessageReporter; * *

Warning: This class is not intended to be instantiated or subclassed. It will * be made final in PMD7. + * + * @deprecated Use either {@link PmdAnalysis} or {@link PmdCli} under the pmd-cli module. */ +@DeprecatedUntil700 @Deprecated public final class PMD { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java index 60214b1442..808b8eafbb 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java @@ -24,6 +24,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.event.Level; +import net.sourceforge.pmd.annotation.DeprecatedUntil700; import net.sourceforge.pmd.annotation.Experimental; import net.sourceforge.pmd.cpd.renderer.CPDReportRenderer; import net.sourceforge.pmd.internal.LogMessages; @@ -36,6 +37,10 @@ import net.sourceforge.pmd.util.database.DBMSMetadata; import net.sourceforge.pmd.util.database.DBURI; import net.sourceforge.pmd.util.database.SourceObject; +/** + * @deprecated {@link PmdCli} under the pmd-cli offers CLI support. + */ +@DeprecatedUntil700 @Deprecated public class CPD { // not final, in order to re-initialize logging From 805864dc028cc1d83338e81f8ed9f26c357fd139 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Thu, 6 Oct 2022 14:27:13 -0300 Subject: [PATCH 165/254] Improve cli example --- docs/pages/pmd/userdocs/cli_reference.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/pages/pmd/userdocs/cli_reference.md b/docs/pages/pmd/userdocs/cli_reference.md index ec32d13ac7..8a10c9131e 100644 --- a/docs/pages/pmd/userdocs/cli_reference.md +++ b/docs/pages/pmd/userdocs/cli_reference.md @@ -215,9 +215,10 @@ parameter. Example: -``` shell -./pmd analyze -d src/main/java -f text -R rulesets/java/quickstart.xml --use-version java-1.8 -``` +{% include cli_example.html + id="lang-ver" + linux="pmd analyze -d src/main/java -f text -R rulesets/java/quickstart.xml --use-version java-1.8" + windows="pmd.bat analyze -d src\main\java -f text -R rulesets/java/quickstart.xml --use-version java-1.8" %} * [apex](pmd_rules_apex.html) (Salesforce Apex) * [ecmascript](pmd_rules_ecmascript.html) (JavaScript) From cedafa9e0d690b38b31dc5147c1fd96b502d944a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Thu, 6 Oct 2022 15:23:15 -0300 Subject: [PATCH 166/254] Allow positional parameters as equivalent to -d --- .../internal/AbstractAnalysisPmdSubcommand.java | 14 +++++++++++++- .../pmd/cli/commands/internal/CpdCommandTest.java | 10 +++++----- .../pmd/cli/commands/internal/PmdCommandTest.java | 10 +++++----- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractAnalysisPmdSubcommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractAnalysisPmdSubcommand.java index c89336d460..4f1cf5c263 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractAnalysisPmdSubcommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/AbstractAnalysisPmdSubcommand.java @@ -6,6 +6,7 @@ package net.sourceforge.pmd.cli.commands.internal; import java.net.URI; import java.nio.file.Path; +import java.util.ArrayList; import java.util.List; import net.sourceforge.pmd.cli.commands.mixins.internal.EncodingMixin; @@ -13,6 +14,7 @@ import net.sourceforge.pmd.cli.commands.mixins.internal.EncodingMixin; import picocli.CommandLine.Mixin; import picocli.CommandLine.Option; import picocli.CommandLine.ParameterException; +import picocli.CommandLine.Parameters; public abstract class AbstractAnalysisPmdSubcommand extends AbstractPmdSubcommand { @@ -24,7 +26,7 @@ public abstract class AbstractAnalysisPmdSubcommand extends AbstractPmdSubcomman + "Zip and Jar files are also supported, if they are specified directly " + "(archive files found while exploring a directory are not recursively expanded). " + "This option can be repeated, and multiple arguments can be provided to a single occurrence of the option. " - + "One of --dir, --file-list or --uri must be provided. ", + + "One of --dir, --file-list or --uri must be provided.", arity = "1..*", split = ",") protected List inputPaths; @@ -44,6 +46,16 @@ public abstract class AbstractAnalysisPmdSubcommand extends AbstractPmdSubcomman + "Disable this option with '--no-fail-on-violation' to exit with 0 instead and just write the report.", defaultValue = "true", negatable = true) protected boolean failOnViolation; + + @Parameters(arity = "*", description = "Path to a source file, or directory containing source files to analyze. " + + "Equivalent to using --dir.") + public void setInputPaths(final List inputPaths) { + if (this.inputPaths == null) { + this.inputPaths = new ArrayList<>(); + } + + this.inputPaths.addAll(inputPaths); + } @Override protected final void validate() throws ParameterException { diff --git a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/CpdCommandTest.java b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/CpdCommandTest.java index b261d5664a..53f98ccbad 100644 --- a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/CpdCommandTest.java +++ b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/CpdCommandTest.java @@ -43,10 +43,10 @@ class CpdCommandTest extends BaseCommandTest { @Test void testNoPositionalParametersAllowed() { - assertError( - // vvvv - "-d", "a", "--", "-d", "b" + final CpdCommand cmd = setupAndParse( + "-d", "a", "--", "b" ); + assertMultipleDirs(cmd); } @Test @@ -68,8 +68,8 @@ class CpdCommandTest extends BaseCommandTest { protected void addStandardParams(final List argList) { // If no minimum tokens provided, set default value if (!argList.contains("--minimum-tokens")) { - argList.add("--minimum-tokens"); - argList.add("100"); + argList.add(0, "--minimum-tokens"); + argList.add(1, "100"); } } } diff --git a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/PmdCommandTest.java b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/PmdCommandTest.java index c25f9be1d5..37f4e75059 100644 --- a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/PmdCommandTest.java +++ b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/commands/internal/PmdCommandTest.java @@ -52,10 +52,10 @@ class PmdCommandTest extends BaseCommandTest { @Test void testNoPositionalParametersAllowed() { - assertError( - // vvvv - "-R", "x.xml", "-d", "a", "--", "-d", "b" + final PmdCommand cmd = setupAndParse( + "-R", "x.xml", "-R", "y.xml", "-d", "a", "--", "b" ); + assertMultipleDirsAndRulesets(cmd); } @Test @@ -83,8 +83,8 @@ class PmdCommandTest extends BaseCommandTest { protected void addStandardParams(final List argList) { // If no language provided, set dummy latest if (!argList.contains("--use-version")) { - argList.add("--use-version"); - argList.add("dummy-1.0"); + argList.add(0, "--use-version"); + argList.add(1, "dummy-1.0"); } } } From 1bcc39d6a34f61841fc37b5bd56666fba97974ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Sat, 15 Oct 2022 00:29:05 -0300 Subject: [PATCH 167/254] PMDConfiguration to use stricter types - No more raw Strings going around --- .../pmd/cli/commands/internal/PmdCommand.java | 10 +- .../net/sourceforge/pmd/PMDConfiguration.java | 178 ++++++++++++++++-- 2 files changed, 164 insertions(+), 24 deletions(-) diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java index 77b1fe3a01..a5b4ad9fb4 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java @@ -260,15 +260,15 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { */ public PMDConfiguration toConfiguration() { final PMDConfiguration configuration = new PMDConfiguration(); - configuration.setInputPaths(inputPaths.stream().map(Path::toString).collect(Collectors.toList())); - configuration.setInputFilePath(fileListPath != null ? fileListPath.toString() : null); - configuration.setIgnoreFilePath(ignoreListPath != null ? ignoreListPath.toString() : null); - configuration.setInputUri(uri != null ? uri.toString() : null); + configuration.setInputPathList(inputPaths); + configuration.setInputFilePath(fileListPath); + configuration.setIgnoreFilePath(ignoreListPath); + configuration.setInputUri(uri); configuration.setReportFormat(format); configuration.setDebug(debug); configuration.setSourceEncoding(encoding.getEncoding().name()); configuration.setMinimumPriority(minimumPriority); - configuration.setReportFile(reportFile != null ? reportFile.toString() : null); + configuration.setReportFile(reportFile); configuration.setReportProperties(properties); configuration.setReportShortNames(shortnames); configuration.setRuleSets(rulesets); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/PMDConfiguration.java b/pmd-core/src/main/java/net/sourceforge/pmd/PMDConfiguration.java index 01ed36d138..5ccef52bb3 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/PMDConfiguration.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/PMDConfiguration.java @@ -6,12 +6,16 @@ package net.sourceforge.pmd; import java.io.File; import java.io.IOException; +import java.net.URI; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Properties; +import java.util.stream.Collectors; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -103,28 +107,30 @@ public class PMDConfiguration extends AbstractConfiguration { private String suppressMarker = DEFAULT_SUPPRESS_MARKER; private int threads = Runtime.getRuntime().availableProcessors(); private ClassLoader classLoader = getClass().getClassLoader(); - private LanguageVersionDiscoverer languageVersionDiscoverer; + private final LanguageVersionDiscoverer languageVersionDiscoverer; private LanguageVersion forceLanguageVersion; private MessageReporter reporter = new SimpleMessageReporter(LoggerFactory.getLogger(PMD.class)); // Rule and source file options private List ruleSets = new ArrayList<>(); private RulePriority minimumPriority = RulePriority.LOW; - private @NonNull List inputPaths = Collections.emptyList(); - private String inputUri; - private String inputFilePath; - private String ignoreFilePath; + private @NonNull List inputPaths = new ArrayList<>(); + private URI inputUri; + private Path inputFilePath; + private Path ignoreFilePath; private boolean ruleSetFactoryCompatibilityEnabled = true; // Reporting options private String reportFormat; - private String reportFile; + private Path reportFile; private boolean reportShortNames = false; private Properties reportProperties = new Properties(); private boolean showSuppressedViolations = false; private boolean failOnViolation = true; + @Deprecated private boolean stressTest; + @Deprecated private boolean benchmark; private AnalysisCache analysisCache = new NoopAnalysisCache(); private boolean ignoreIncrementalAnalysis; @@ -473,13 +479,30 @@ public class PMDConfiguration extends AbstractConfiguration { */ @Deprecated public @Nullable String getInputPaths() { - return inputPaths.isEmpty() ? null : String.join(",", inputPaths); + return inputPaths.isEmpty() ? null : inputPaths.stream().map(Path::toString).collect(Collectors.joining(",")); } /** * Returns an unmodifiable list. + * + * @deprecated Use {@link #getInputPathList()} */ + @Deprecated public @NonNull List getAllInputPaths() { + final List ret = new ArrayList<>(inputPaths.size()); + for (final Path p : inputPaths) { + ret.add(p.toString()); + } + + return Collections.unmodifiableList(ret); + } + + /** + * Returns an unmodifiable list. + * + * @throws NullPointerException If the parameter is null + */ + public @NonNull List getInputPathList() { return Collections.unmodifiableList(inputPaths); } @@ -493,8 +516,24 @@ public class PMDConfiguration extends AbstractConfiguration { */ @Deprecated public void setInputPaths(String inputPaths) { - List paths = new ArrayList<>(); - Collections.addAll(paths, inputPaths.split(",")); + List paths = new ArrayList<>(); + for (String s : inputPaths.split(",")) { + paths.add(Paths.get(s)); + } + this.inputPaths = paths; + } + + /** + * Set the input paths to the given list of paths. + * @throws NullPointerException If the parameter is null + * @deprecated Use {@link #setInputPathList(List)} + */ + @Deprecated + public void setInputPaths(List inputPaths) { + final List paths = new ArrayList<>(inputPaths.size()); + for (String s : inputPaths) { + paths.add(Paths.get(s)); + } this.inputPaths = paths; } @@ -502,7 +541,7 @@ public class PMDConfiguration extends AbstractConfiguration { * Set the input paths to the given list of paths. * @throws NullPointerException If the parameter is null */ - public void setInputPaths(List inputPaths) { + public void setInputPathList(final @NonNull List inputPaths) { this.inputPaths = new ArrayList<>(inputPaths); } @@ -510,17 +549,45 @@ public class PMDConfiguration extends AbstractConfiguration { * Add an input path. It is not split on commas. * * @throws NullPointerException If the parameter is null + * @deprecated Use {@link #addInputPath(Path)} */ + @Deprecated public void addInputPath(String inputPath) { + Objects.requireNonNull(inputPath); + this.inputPaths.add(Paths.get(inputPath)); + } + + /** + * Add an input path. It is not split on commas. + * + * @throws NullPointerException If the parameter is null + */ + public void addInputPath(@NonNull Path inputPath) { Objects.requireNonNull(inputPath); this.inputPaths.add(inputPath); } + /** + * @deprecated Use {@link #getInputFile()} + */ + @Deprecated public String getInputFilePath() { + return inputFilePath == null ? null : inputFilePath.toString(); + } + + public Path getInputFile() { return inputFilePath; } + /** + * @deprecated Use {@link #getIgnoreFile()} + */ + @Deprecated public String getIgnoreFilePath() { + return ignoreFilePath == null ? null : ignoreFilePath.toString(); + } + + public Path getIgnoreFile() { return ignoreFilePath; } @@ -528,10 +595,21 @@ public class PMDConfiguration extends AbstractConfiguration { * The input file path points to a single file, which contains a * comma-separated list of source file names to process. * - * @param inputFilePath - * path to the file + * @param inputFilePath path to the file + * @deprecated Use {@link #setInputFilePath(Path)} */ + @Deprecated public void setInputFilePath(String inputFilePath) { + this.inputFilePath = inputFilePath == null ? null : Paths.get(inputFilePath); + } + + /** + * The input file path points to a single file, which contains a + * comma-separated list of source file names to process. + * + * @param inputFilePath path to the file + */ + public void setInputFilePath(@Nullable Path inputFilePath) { this.inputFilePath = inputFilePath; } @@ -539,10 +617,21 @@ public class PMDConfiguration extends AbstractConfiguration { * The input file path points to a single file, which contains a * comma-separated list of source file names to ignore. * - * @param ignoreFilePath - * path to the file + * @param ignoreFilePath path to the file + * @deprecated Use {@link #setIgnoreFilePath(Path)} */ + @Deprecated public void setIgnoreFilePath(String ignoreFilePath) { + this.ignoreFilePath = ignoreFilePath == null ? null : Paths.get(ignoreFilePath); + } + + /** + * The input file path points to a single file, which contains a + * comma-separated list of source file names to ignore. + * + * @param ignoreFilePath path to the file + */ + public void setIgnoreFilePath(@Nullable Path ignoreFilePath) { this.ignoreFilePath = ignoreFilePath; } @@ -550,18 +639,39 @@ public class PMDConfiguration extends AbstractConfiguration { * Get the input URI to process for source code objects. * * @return URI + * @deprecated Use {@link #getUri} */ + @Deprecated public String getInputUri() { + return inputUri == null ? null : inputUri.toString(); + } + + /** + * Get the input URI to process for source code objects. + * + * @return URI + */ + public URI getUri() { return inputUri; } /** * Set the input URI to process for source code objects. * - * @param inputUri - * a single URI + * @param inputUri a single URI + * @deprecated Use {@link PMDConfiguration#setInputUri(URI)} */ + @Deprecated public void setInputUri(String inputUri) { + this.inputUri = inputUri == null ? null : URI.create(inputUri); + } + + /** + * Set the input URI to process for source code objects. + * + * @param inputUri a single URI + */ + public void setInputUri(URI inputUri) { this.inputUri = inputUri; } @@ -607,7 +717,7 @@ public class PMDConfiguration extends AbstractConfiguration { Renderer renderer = RendererFactory.createRenderer(reportFormat, reportProperties); renderer.setShowSuppressedViolations(showSuppressedViolations); if (withReportWriter) { - renderer.setReportFile(reportFile); + renderer.setReportFile(reportFile == null ? null : reportFile.toString()); } return renderer; } @@ -637,18 +747,39 @@ public class PMDConfiguration extends AbstractConfiguration { * 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 + * @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; } @@ -699,7 +830,10 @@ public class PMDConfiguration extends AbstractConfiguration { * * @return true if stress test is enbaled, false * otherwise. + * + * @deprecated For removal */ + @Deprecated public boolean isStressTest() { return stressTest; } @@ -710,7 +844,9 @@ public class PMDConfiguration extends AbstractConfiguration { * @param stressTest * The stree test indicator to set. * @see #isStressTest() + * @deprecated For removal. */ + @Deprecated public void setStressTest(boolean stressTest) { this.stressTest = stressTest; } @@ -721,7 +857,9 @@ public class PMDConfiguration extends AbstractConfiguration { * * @return true if benchmark logging is enbaled, * false otherwise. + * @deprecated This behavior is down to CLI, not part of the core analysis. */ + @Deprecated public boolean isBenchmark() { return benchmark; } @@ -732,7 +870,9 @@ public class PMDConfiguration extends AbstractConfiguration { * @param benchmark * The benchmark indicator to set. * @see #isBenchmark() + * @deprecated This behavior is down to CLI, not part of the core analysis. */ + @Deprecated public void setBenchmark(boolean benchmark) { this.benchmark = benchmark; } From c506444043ba53fcecf508a24c3a3e1094a64751 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Sat, 15 Oct 2022 00:44:05 -0300 Subject: [PATCH 168/254] Use bleeding edge pmdtester --- Gemfile | 4 ++-- Gemfile.lock | 17 +++++++++++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 031779070e..fb54e76c21 100644 --- a/Gemfile +++ b/Gemfile @@ -1,9 +1,9 @@ source 'https://rubygems.org/' # bleeding edge from git -#gem 'pmdtester', :git => 'https://github.com/pmd/pmd-regression-tester.git', branch: 'master' +gem 'pmdtester', :git => 'https://github.com/pmd/pmd-regression-tester.git', branch: 'master' -gem 'pmdtester' +#gem 'pmdtester' gem 'danger' # This group is only needed for rendering release notes (docs/render_release_notes.rb) diff --git a/Gemfile.lock b/Gemfile.lock index f47c7c5067..51f431b572 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,16 @@ +GIT + remote: https://github.com/pmd/pmd-regression-tester.git + revision: a69aa251e8893b0c3af2c16917430cdc81c70574 + branch: master + specs: + pmdtester (1.5.2.pre.SNAPSHOT) + differ (~> 0.1) + liquid (~> 5.2) + logger-colors (~> 1.0) + nokogiri (~> 1.13) + rufus-scheduler (~> 3.8) + slop (~> 4.6) + GEM remote: https://rubygems.org/ specs: @@ -96,7 +109,7 @@ GEM sawyer (0.9.2) addressable (>= 2.3.5) faraday (>= 0.17.3, < 3) - slop (4.9.2) + slop (4.9.3) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) tzinfo (2.0.5) @@ -109,7 +122,7 @@ PLATFORMS DEPENDENCIES danger liquid - pmdtester + pmdtester! rouge safe_yaml From 1f43af7d831e5b2d63b13d5b2c8f22916609379a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Sat, 15 Oct 2022 01:02:01 -0300 Subject: [PATCH 169/254] Change `pmd run` to `pmd check` with no aliases --- docs/pages/7_0_0_release_notes.md | 45 ++++++++++++++++++- docs/pages/next_major_development.md | 42 ----------------- docs/pages/pmd/languages/java.md | 2 +- docs/pages/pmd/languages/visualforce.md | 2 +- docs/pages/pmd/languages/xml.md | 2 +- docs/pages/pmd/userdocs/cli_reference.md | 20 ++++----- docs/pages/pmd/userdocs/installation.md | 8 ++-- docs/pages/pmd/userdocs/pmd_report_formats.md | 4 +- .../pmd/userdocs/suppressing_warnings.md | 2 +- .../pmd/cli/commands/internal/PmdCommand.java | 2 +- .../net/sourceforge/pmd/cli/PmdCliTest.java | 4 +- .../pmd/it/BinaryDistributionIT.java | 2 +- 12 files changed, 67 insertions(+), 68 deletions(-) diff --git a/docs/pages/7_0_0_release_notes.md b/docs/pages/7_0_0_release_notes.md index 4aff611397..157b1dadbd 100644 --- a/docs/pages/7_0_0_release_notes.md +++ b/docs/pages/7_0_0_release_notes.md @@ -19,15 +19,56 @@ This is a {{ site.pmd.release_type }} release. ### New and noteworthy -#### CLI improvements +#### Revamped Command Line Interface -The PMD CLI has been enhanced with a progress bar, which interactively displays the +PMD now ships with a unified Command Line Interface for both Linux/Unix and Windows. Instead of having a collection of scripts +for the different utilities shipped with PMD, a single script `pmd` (`pmd.bat` for Windows) can now launch all +utilities using subcommands, e.g. `pmd check`, `pmd designer`. All commands and options are thoroughly documented in the help, +with full color support where available. Moreover, efforts were made to provide consistency in the usage of all PMD utilities. + +```shell +$ Usage: pmd [-hV] [COMMAND] + -h, --help Show this help message and exit. + -V, --version Print version information and exit. +Commands: + check The PMD standard source code analyzer + cpd Copy/Paste Detector - find duplicate code + designer The PMD visual rule designer + cpd-gui GUI for the Copy/Paste Detector + Warning: May not support the full CPD feature set + ast-dump Experimental: dumps the AST of parsing source code +Exit Codes: + 0 Succesful analysis, no violations found + 1 An unexpected error occurred during execution + 2 Usage error, please refer to the command help + 4 Successful analysis, at least 1 violation found +``` + +For instance, where you previously would have run +```shell +run.sh pmd -d src -R ruleset.xml +``` +you should now use +```shell +pmd check -d src -R ruleset.xml +``` + +Additionally, the CLI has been enhanced with a progress bar, which interactively displays the current progress of the analysis. TODO screenshot (take it right before releasing, because other changes to the CLI will occur until then) This can be disabled with the `--no-progress` flag. + +Finally, we now provide a completion script for Bash/Zsh to further help daily usage. +This script can be found under `shell/pmd-completion.sh` in the binary distribution. +To use it, edit your `~/.bashrc` / `~/.zshrc` file and add the following line: + +``` +source *path_to_pmd*/shell/pmd-completion.sh +``` + #### Full Antlr support Languages backed by an Antlr grammar are now fully supported. This means, it's now possible not only to use Antlr grammars for CPD, diff --git a/docs/pages/next_major_development.md b/docs/pages/next_major_development.md index e8f570679d..3a4f63d24b 100644 --- a/docs/pages/next_major_development.md +++ b/docs/pages/next_major_development.md @@ -20,48 +20,6 @@ The current tasks are listed here: [Integrate new PMD logo #1931](https://github The new logo is available from the [Logo Project Page](pmd_projectdocs_logo.html). -### Revamped Command Line Interface - -PMD now ships with a unified Command Line Interface for both Linux/Unix and Windows. Instead of having a collection of scripts -for the different utilities shipped with PMD, a single script `pmd` (`pmd.bat` for Windows) can now launch all -utilities using subcommands, e.g. `pmd run`, `pmd designer`. All commands and options are thoroughly documented in the help, -with full color support where available. Moreover, efforts were made to provide consistency in the usage of all PMD utilities. - -``` -$ pmd --help -Usage: pmd [-hV] [COMMAND] - -h, --help Show this help message and exit. - -V, --version Print version information and exit. -Commands: - analyze, analyse, run The PMD standard source code analyzer - cpd Copy/Paste Detector - find duplicate code - designer The PMD visual rule designer - cpd-gui GUI for the Copy/Paste Detector - Warning: May not support the full CPD feature set - ast-dump Experimental: dumps the AST of parsing source code -Exit Codes: - 0 Successful analysis, no violations found - 1 An unexpected error occurred during execution - 2 Usage error, please refer to the command help - 4 Successful analysis, at least 1 violation found -``` - -For instance, where you previously would have run -```shell -run.sh pmd -d src -R ruleset.xml -``` -you should now use -```shell -pmd run -d src -R ruleset.xml -``` -Additionally, we now provide a completion script for Bash/Zsh to further help daily usage. -This script can be found under `shell/pmd-completion.sh` in the binary distribution. -To use it, edit your `~/.bashrc` / `~/.zshrc` file and add the following line: - -``` -source *path_to_pmd*/shell/pmd-completion.sh -``` - ### API The API of PMD has been growing over the years and needs to be cleaned up. The goal is, to diff --git a/docs/pages/pmd/languages/java.md b/docs/pages/pmd/languages/java.md index 77e8e6665b..e17bd5797e 100644 --- a/docs/pages/pmd/languages/java.md +++ b/docs/pages/pmd/languages/java.md @@ -41,7 +41,7 @@ In order to analyze a project with PMD that uses preview language features, you' it via the environment variable `PMD_JAVA_OPTS` and select the new language version, e.g. `19-preview`: export PMD_JAVA_OPTS=--enable-preview - pmd run --use-version java-19-preview ... + pmd check --use-version java-19-preview ... Note: we only support preview language features for the latest two java versions. diff --git a/docs/pages/pmd/languages/visualforce.md b/docs/pages/pmd/languages/visualforce.md index cdd5d856fa..8699883564 100644 --- a/docs/pages/pmd/languages/visualforce.md +++ b/docs/pages/pmd/languages/visualforce.md @@ -31,7 +31,7 @@ We'll probably extend the CLI instead of relying on environment variables in a f ``` PMD_VF_APEXDIRECTORIES=../classes \ PMD_VF_OBJECTSDIRECTORIES=../objects \ -pmd run -d $GITHUB_WORKSPACE/force-app/main/default/pages \ +pmd check -d $GITHUB_WORKSPACE/force-app/main/default/pages \ -R category/vf/security.xml/VfUnescapeEl -f text ``` diff --git a/docs/pages/pmd/languages/xml.md b/docs/pages/pmd/languages/xml.md index e367ae720d..409194aa63 100644 --- a/docs/pages/pmd/languages/xml.md +++ b/docs/pages/pmd/languages/xml.md @@ -31,7 +31,7 @@ Some XML-based file formats do not conventionally use a `.xml` extension. To ass these files with the XML language, you need to use the `--force-language xml` command-line arguments, for instance: ``` -$ pmd run -d /home/me/src/xml-file.ext -f text -R ruleset.xml --force-language xml +$ pmd check -d /home/me/src/xml-file.ext -f text -R ruleset.xml --force-language xml ``` Please refer to [PMD CLI reference](pmd_userdocs_cli_reference.html#analyze-other-xml-formats) for more examples. diff --git a/docs/pages/pmd/userdocs/cli_reference.md b/docs/pages/pmd/userdocs/cli_reference.md index 8a10c9131e..85b2600726 100644 --- a/docs/pages/pmd/userdocs/cli_reference.md +++ b/docs/pages/pmd/userdocs/cli_reference.md @@ -179,9 +179,9 @@ Just set the environment variable `PMD_JAVA_OPTS` before executing PMD, e.g. {% include cli_example.html id="preview" linux="export PMD_JAVA_OPTS=\"--enable-preview\" - pmd analyze -d src/main/java/ -f text -R rulesets/java/quickstart.xml" + pmd check -d src/main/java/ -f text -R rulesets/java/quickstart.xml" windows="set \"PMD_JAVA_OPTS=--enable-preview\" - pmd.bat analyze -d src\main\java\ -f text -R rulesets/java/quickstart.xml" %} + pmd.bat check -d src\main\java\ -f text -R rulesets/java/quickstart.xml" %} ## Exit Status @@ -217,8 +217,8 @@ Example: {% include cli_example.html id="lang-ver" - linux="pmd analyze -d src/main/java -f text -R rulesets/java/quickstart.xml --use-version java-1.8" - windows="pmd.bat analyze -d src\main\java -f text -R rulesets/java/quickstart.xml --use-version java-1.8" %} + linux="pmd check -d src/main/java -f text -R rulesets/java/quickstart.xml --use-version java-1.8" + windows="pmd.bat check -d src\main\java -f text -R rulesets/java/quickstart.xml --use-version java-1.8" %} * [apex](pmd_rules_apex.html) (Salesforce Apex) * [ecmascript](pmd_rules_ecmascript.html) (JavaScript) @@ -252,23 +252,23 @@ If your xml language doesn't use `xml` as file extension, you can still use PMD {% include cli_example.html id="force" - linux="pmd analyze -d src/xml-file.ext -f text -R ruleset.xml --force-language xml" - windows="pmd.bat analyze -d src\xml-file.ext -f text -R ruleset.xml --force-language xml" %} + linux="pmd check -d src/xml-file.ext -f text -R ruleset.xml --force-language xml" + windows="pmd.bat check -d src\xml-file.ext -f text -R ruleset.xml --force-language xml" %} You can also specify a directory instead of a single file. Then all files are analyzed. In that case, parse errors are suppressed in order to reduce irrelevant noise: {% include cli_example.html id="force-dir" - linux="pmd analyze -d src/ -f text -R ruleset.xml --force-language xml" - windows="pmd.bat analyze -d src\ -f text -R ruleset.xml --force-language xml" %} + linux="pmd check -d src/ -f text -R ruleset.xml --force-language xml" + windows="pmd.bat check -d src\ -f text -R ruleset.xml --force-language xml" %} Alternatively, you can create a filelist to only analyze files with a given extension: {% include cli_example.html id="file-list" linux="find src/ -name \"*.ext\" > filelist.txt - pmd analyze --file-list filelist.txt -f text -R ruleset.xml --force-language xml" + pmd check --file-list filelist.txt -f text -R ruleset.xml --force-language xml" windows="for /r src/ %i in (*.ext) do echo %i >> filelist.txt - pmd.bat analyze --file-list filelist.txt -f text -R ruleset.xml --force-language xml" %} + pmd.bat check --file-list filelist.txt -f text -R ruleset.xml --force-language xml" %} diff --git a/docs/pages/pmd/userdocs/installation.md b/docs/pages/pmd/userdocs/installation.md index 6292786a0e..59584ef74d 100644 --- a/docs/pages/pmd/userdocs/installation.md +++ b/docs/pages/pmd/userdocs/installation.md @@ -58,10 +58,10 @@ To enable it, simply add `source *path_to_pmd*/shell/pmd-completion.sh` to your content="PMD comes with several command line utilities, like CPD, the rule designer or PMD itself. You can run any of them using the script `pmd` (`pmd.bat` under Windows), located inside the `bin/` directory of the PMD distribution. The first argument is the name of the utility you want - to execute ('analyze', 'designer', ...), e.g. PMD is launched via `pmd analyze`. The rest of + to execute ('check', 'designer', ...), e.g. PMD is launched via `pmd check`. The rest of the arguments are specific to the utility used.

" %} -Running a PMD analysis (`pmd analyze` or `pmd.bat analyze`) requires at least two options: +Running a PMD analysis (`pmd check` or `pmd.bat check`) requires at least two options: * `-d `: path to the sources to analyse. This can be a file name, a directory, or a jar or zip file containing the sources. @@ -93,14 +93,14 @@ Additionally, the following options, are specified most of the time even though {% include cli_example.html id="pmd" - linux="pmd analyze -d ../../../src/main/java/ -f text -R rulesets/java/quickstart.xml + linux="pmd check -d ../../../src/main/java/ -f text -R rulesets/java/quickstart.xml .../src/main/java/com/me/RuleSet.java:123 These nested if statements could be combined .../src/main/java/com/me/RuleSet.java:231 Useless parentheses. .../src/main/java/com/me/RuleSet.java:232 Useless parentheses. .../src/main/java/com/me/RuleSet.java:357 These nested if statements could be combined .../src/main/java/com/me/RuleSetWriter.java:66 Avoid empty catch blocks" - windows="pmd.bat analyze -d ..\..\src\main\java\ -f text -R rulesets/java/quickstart.xml + windows="pmd.bat check -d ..\..\src\main\java\ -f text -R rulesets/java/quickstart.xml .../src/main/java/com/me/RuleSet.java:123 These nested if statements could be combined .../src/main/java/com/me/RuleSet.java:231 Useless parentheses. diff --git a/docs/pages/pmd/userdocs/pmd_report_formats.md b/docs/pages/pmd/userdocs/pmd_report_formats.md index f49aca34ba..6428b7b1d1 100644 --- a/docs/pages/pmd/userdocs/pmd_report_formats.md +++ b/docs/pages/pmd/userdocs/pmd_report_formats.md @@ -122,11 +122,11 @@ It has two ways of calling: 1. For a single file: then all three properties need to be provided -`pmd run -d src/Foo.java -R rulesets/java/quickstart.xml -f ideaj -P fileName=src/Foo.java -P sourcePath=/home/pmd/src -P classAndMethodName=Foo` +`pmd check -d src/Foo.java -R rulesets/java/quickstart.xml -f ideaj -P fileName=src/Foo.java -P sourcePath=/home/pmd/src -P classAndMethodName=Foo` 2. For a directory: then the fileName property can be omitted -`pmd run -d src -R rulesets/java/quickstart.xml -f ideaj -P sourcePath=/home/pmd/src -P classAndMethodName=.method` +`pmd check -d src -R rulesets/java/quickstart.xml -f ideaj -P sourcePath=/home/pmd/src -P classAndMethodName=.method` Example: diff --git a/docs/pages/pmd/userdocs/suppressing_warnings.md b/docs/pages/pmd/userdocs/suppressing_warnings.md index c4adcb4695..68bf5e4379 100644 --- a/docs/pages/pmd/userdocs/suppressing_warnings.md +++ b/docs/pages/pmd/userdocs/suppressing_warnings.md @@ -116,7 +116,7 @@ public class Foo { } } -$ pmd analyze -d Foo.java -f text -R java-unusedcode --suppress-marker TURN_OFF_WARNINGS +$ pmd check -d Foo.java -f text -R java-unusedcode --suppress-marker TURN_OFF_WARNINGS No problems found! UnusedLocalVariable rule violation suppressed by //NOPMD in /home/tom/pmd/pmd/bin/Foo.java ``` diff --git a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java index a5b4ad9fb4..87fcc9f557 100644 --- a/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java +++ b/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java @@ -42,7 +42,7 @@ import picocli.CommandLine.Command; import picocli.CommandLine.Option; import picocli.CommandLine.ParameterException; -@Command(name = "analyze", aliases = {"analyse", "run" }, showDefaultValues = true, +@Command(name = "check", showDefaultValues = true, description = "The PMD standard source code analyzer") public class PmdCommand extends AbstractAnalysisPmdSubcommand { diff --git a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/PmdCliTest.java b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/PmdCliTest.java index 00b9ac1e5e..6679d8d8d5 100644 --- a/pmd-cli/src/test/java/net/sourceforge/pmd/cli/PmdCliTest.java +++ b/pmd-cli/src/test/java/net/sourceforge/pmd/cli/PmdCliTest.java @@ -203,7 +203,7 @@ class PmdCliTest extends BaseCliTest { void testWrongCliOptionsDoPrintUsage() throws Exception { final String log = runCli(ExecutionResult.USAGE_ERROR, "--invalid", "--rulesets", DUMMY_RULESET, "-d", srcDir.toString()); assertThat(log, containsString("Unknown option: '--invalid'")); - assertThat(log, containsString("Usage: pmd analyze")); + assertThat(log, containsString("Usage: pmd check")); } // utilities @@ -231,7 +231,7 @@ class PmdCliTest extends BaseCliTest { final List argList = new ArrayList<>(); // Always run against dummy language without logging not cache to remove all logging noise - argList.add("run"); + argList.add("check"); argList.add("--use-version"); argList.add("dummy-1.0"); argList.add("--no-cache"); diff --git a/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java b/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java index 65d2912f14..6397e56050 100644 --- a/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java +++ b/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java @@ -109,7 +109,7 @@ public class BinaryDistributionIT extends AbstractBinaryDistributionTest { @Test public void testPmdNoArgs() throws Exception { ExecutionResult result = PMDExecutor.runPMD(tempDir); // without any argument, display usage help and error - result.assertExecutionResultErrOutput(2, "Usage: pmd analyze "); + result.assertExecutionResultErrOutput(2, "Usage: pmd check "); } @Test From 08e63b670e9f7b8fab622dc3a063ca38b5a641f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Sat, 15 Oct 2022 01:09:35 -0300 Subject: [PATCH 170/254] Fix JavaFX link --- docs/pages/pmd/userdocs/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pages/pmd/userdocs/installation.md b/docs/pages/pmd/userdocs/installation.md index 59584ef74d..23f4b5d6a8 100644 --- a/docs/pages/pmd/userdocs/installation.md +++ b/docs/pages/pmd/userdocs/installation.md @@ -23,7 +23,7 @@ sidebar: pmd_sidebar * For Windows: [Winzip](http://winzip.com) or the free [7-zip](http://www.7-zip.org/) * For Linux / Unix: [InfoZip](http://infozip.sourceforge.net/) -{% include note.html content="For executing the Designer (`pmd designer`) using [OpenJDK](http://jdk.java.net) or Java 11+, you need additionally [OpenJFX](http://jdk.java.net). Download it, extract it and set the environment variable JAVAFX_HOME." %} +{% include note.html content="For executing the Designer (`pmd designer`) using [OpenJDK](http://jdk.java.net) or Java 11+, you need additionally [JavaFX](https://gluonhq.com/products/javafx/). Download it, extract it and set the environment variable JAVAFX_HOME pointing at that directory." %} ### Installation From aaa392b9845017eabfd6dda10d759000f446a854 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Sat, 15 Oct 2022 01:24:48 -0300 Subject: [PATCH 171/254] Improve release notes --- docs/pages/7_0_0_release_notes.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/pages/7_0_0_release_notes.md b/docs/pages/7_0_0_release_notes.md index 157b1dadbd..c78d824e23 100644 --- a/docs/pages/7_0_0_release_notes.md +++ b/docs/pages/7_0_0_release_notes.md @@ -52,6 +52,11 @@ you should now use ```shell pmd check -d src -R ruleset.xml ``` +or even better, omit using `-d` / `--dir` and simply pass the sources as positional parameters + +```shell +pmd check -R ruleset.xml src +``` Additionally, the CLI has been enhanced with a progress bar, which interactively displays the current progress of the analysis. From 75666d9c2d5b32cc94ec2670e73d21780db66c7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Sat, 15 Oct 2022 01:31:55 -0300 Subject: [PATCH 172/254] Rename command to check --- .../src/test/java/net/sourceforge/pmd/it/PMDExecutor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pmd-dist/src/test/java/net/sourceforge/pmd/it/PMDExecutor.java b/pmd-dist/src/test/java/net/sourceforge/pmd/it/PMDExecutor.java index a4e4baa70b..7998ae0652 100644 --- a/pmd-dist/src/test/java/net/sourceforge/pmd/it/PMDExecutor.java +++ b/pmd-dist/src/test/java/net/sourceforge/pmd/it/PMDExecutor.java @@ -39,7 +39,7 @@ public class PMDExecutor { private static ExecutionResult runPMDUnix(Path tempDir, Path reportFile, String... arguments) throws Exception { String cmd = tempDir.resolve(AbstractBinaryDistributionTest.PMD_BIN_PREFIX + PMDVersion.VERSION + "/bin/pmd").toAbsolutePath().toString(); List args = new ArrayList<>(); - args.add("run"); + args.add("check"); args.addAll(Arrays.asList(arguments)); return runCommand(cmd, args, reportFile); } @@ -47,7 +47,7 @@ public class PMDExecutor { private static ExecutionResult runPMDWindows(Path tempDir, Path reportFile, String... arguments) throws Exception { String cmd = tempDir.resolve(AbstractBinaryDistributionTest.PMD_BIN_PREFIX + PMDVersion.VERSION + "/bin/pmd.bat").toAbsolutePath().toString(); List args = new ArrayList<>(); - args.add("run"); + args.add("check"); args.addAll(Arrays.asList(arguments)); return runCommand(cmd, args, reportFile); } From 33ede7fbc264c0abcdf1123f29a7095167262d84 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Sat, 15 Oct 2022 18:11:09 +0200 Subject: [PATCH 173/254] [java] Improve TestClassWithoutTestCases - Added new property "testClassPatterns" to property detect empty test classes - Added support for different JUnit5 annotations (like ParameterizedTest) - Added support for TestNG - Fixes #929 - Fixes #2636 --- docs/pages/release_notes.md | 10 ++ .../pmd/lang/java/rule/AbstractJUnitRule.java | 73 ++++++++-- .../TestClassWithoutTestCasesRule.java | 44 +++++- .../resources/category/java/errorprone.xml | 8 +- .../xml/TestClassWithoutTestCases.xml | 132 ++++++++++++++++-- 5 files changed, 243 insertions(+), 24 deletions(-) diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index 2ac4d95984..203298d13e 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -25,6 +25,13 @@ This is a {{ site.pmd.release_type }} release. The rule is part of the quickstart.xml ruleset. +#### Modified Rules + +* The Java rule {% rule java/errorprone/TestClassWithoutTestCases %} has a new property `testClassPattern`. This is + used to detect empty test classes by name. Previously this rule could only detect empty JUnit3 test cases + properly. To switch back to the old behavior, this property can be set to an empty value which disables the + test class detection by pattern. + ### Fixed Issues * apex * [#4149](https://github.com/pmd/pmd/issues/4149): \[apex] New rule: ApexUnitTestClassShouldHaveRunAs @@ -32,6 +39,9 @@ The rule is part of the quickstart.xml ruleset. * [#4144](https://github.com/pmd/pmd/pull/4144) \[doc] Update docs to reflect supported languages * java-documentation * [#4141](https://github.com/pmd/pmd/issues/4141): \[java] UncommentedEmptyConstructor FP when constructor annotated with @Autowired +* java-errorprone + * [#929](https://github.com/pmd/pmd/issues/929): \[java] Inconsistent results with TestClassWithoutTestCases + * [#2636](https://github.com/pmd/pmd/issues/2636): \[java] TestClassWithoutTestCases false positive with JUnit5 ParameterizedTest ### API Changes diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractJUnitRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractJUnitRule.java index 10b5bfeb6d..4bd020f007 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractJUnitRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractJUnitRule.java @@ -4,7 +4,11 @@ package net.sourceforge.pmd.lang.java.rule; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.annotation.InternalApi; @@ -30,6 +34,14 @@ public abstract class AbstractJUnitRule extends AbstractJavaRule { protected static final String JUNIT3_CLASS_NAME = "junit.framework.TestCase"; protected static final String JUNIT4_CLASS_NAME = "org.junit.Test"; protected static final String JUNIT5_CLASS_NAME = "org.junit.jupiter.api.Test"; + private static final Set JUNIT5_TEST_ANNOTATIONS = new HashSet<>(Arrays.asList( + JUNIT5_CLASS_NAME, + "org.junit.jupiter.api.RepeatedTest", + "org.junit.jupiter.api.TestFactory", + "org.junit.jupiter.api.TestTemplate", + "org.junit.jupiter.params.ParameterizedTest")); + + private static final String TESTNG_ANNOTATION = "org.testng.annotations.Test"; protected boolean isJUnit3Class; protected boolean isJUnit4Class; @@ -92,12 +104,12 @@ public abstract class AbstractJUnitRule extends AbstractJavaRule { if (isJUnit4Class && isJUnit5Class) { isJUnit4Class &= hasImports(node, JUNIT4_CLASS_NAME); - isJUnit5Class &= hasImports(node, JUNIT5_CLASS_NAME); + isJUnit5Class &= hasImports(node, JUNIT5_TEST_ANNOTATIONS); } } public static boolean isTestClass(ASTClassOrInterfaceBody node) { - return !isAbstractClass(node) + return !isAbstractClass(node) && node.getParent() instanceof ASTClassOrInterfaceDeclaration && (isTestClassJUnit3(node) || isTestClassJUnit4(node) || isTestClassJUnit5(node)); } @@ -131,7 +143,7 @@ public abstract class AbstractJUnitRule extends AbstractJavaRule { Node parent = node.getParent(); if (parent instanceof TypeNode) { TypeNode type = (TypeNode) parent; - return isJUnit5Class(type) && hasImports(type, JUNIT5_CLASS_NAME); + return isJUnit5Class(type) && hasImports(type, JUNIT5_TEST_ANNOTATIONS); } return false; } @@ -145,7 +157,8 @@ public abstract class AbstractJUnitRule extends AbstractJavaRule { ASTClassOrInterfaceBody type = method.getFirstParentOfType(ASTClassOrInterfaceBody.class); return isTestClassJUnit3(type) && method.isPublic() && method.isVoid() && method.getName().startsWith("test") || isTestClassJUnit4(type) && method.isPublic() && doesNodeContainJUnitAnnotation(method.getParent(), JUNIT4_CLASS_NAME) - || isTestClassJUnit5(type) && doesNodeContainJUnitAnnotation(method.getParent(), JUNIT5_CLASS_NAME); + || isTestClassJUnit5(type) && doesNodeContainJUnitAnnotation(method.getParent(), JUNIT5_TEST_ANNOTATIONS) + || doesNodeContainJUnitAnnotation(method.getParent(), TESTNG_ANNOTATION); } public boolean isJUnitMethod(ASTMethodDeclaration method, Object data) { @@ -170,7 +183,7 @@ public abstract class AbstractJUnitRule extends AbstractJavaRule { } private boolean isJUnit5Method(ASTMethodDeclaration method) { - return isJUnit5Class && doesNodeContainJUnitAnnotation(method.getParent(), JUNIT5_CLASS_NAME); + return isJUnit5Class && doesNodeContainJUnitAnnotation(method.getParent(), JUNIT5_TEST_ANNOTATIONS); } private boolean isJUnit3Method(ASTMethodDeclaration method) { @@ -186,34 +199,72 @@ public abstract class AbstractJUnitRule extends AbstractJavaRule { } private static boolean isJUnit5Class(JavaNode node) { - return doesNodeContainJUnitAnnotation(node, JUNIT5_CLASS_NAME); + return doesNodeContainJUnitAnnotation(node, JUNIT5_TEST_ANNOTATIONS); } private static boolean doesNodeContainJUnitAnnotation(JavaNode node, String annotationTypeClassName) { + return doesNodeContainJUnitAnnotation(node, Collections.singleton(annotationTypeClassName)); + } + + private static boolean doesNodeContainJUnitAnnotation(JavaNode node, Set annotationTypeClassNames) { List annotations = node.findDescendantsOfType(ASTAnnotation.class); for (ASTAnnotation annotation : annotations) { Node annotationTypeNode = annotation.getChild(0); TypeNode annotationType = (TypeNode) annotationTypeNode; if (annotationType.getType() == null) { ASTName name = annotationTypeNode.getFirstChildOfType(ASTName.class); - if (name != null && (name.hasImageEqualTo("Test") || name.hasImageEqualTo(annotationTypeClassName))) { - return true; + if (name != null) { + for (String annotationTypeName : annotationTypeClassNames) { + if (areNamesEqual(name, annotationTypeName)) { + return true; + } + } + } + } else { + for (String annotationTypeClassName : annotationTypeClassNames) { + if (TypeTestUtil.isA(annotationTypeClassName, annotationType)) { + return true; + } } - } else if (TypeTestUtil.isA(annotationTypeClassName, annotationType)) { - return true; } } return false; } private static boolean hasImports(JavaNode node, String className) { + return hasImports(node, Collections.singleton(className)); + } + + private static boolean hasImports(JavaNode node, Set classNames) { List imports = node.getRoot().findDescendantsOfType(ASTImportDeclaration.class); for (ASTImportDeclaration importDeclaration : imports) { ASTName name = importDeclaration.getFirstChildOfType(ASTName.class); - if (name != null && name.hasImageEqualTo(className)) { + // imports are always fully qualified + if (name != null && classNames.contains(name.getImage())) { return true; } } return false; } + + /** + * Compares the name against the given fully qualified name. If a direct comparison doesn't + * work, try to compare as a simple name. + * + *

Note: This method is only used, if the auxclasspath is incomplete.

+ */ + private static boolean areNamesEqual(ASTName node, String fullyQualifiedAnnotationName) { + String simpleName = node.getImage(); + if (simpleName.equals(fullyQualifiedAnnotationName)) { + return true; + } + + String simpleAnnotationName = fullyQualifiedAnnotationName; + int lastDot = fullyQualifiedAnnotationName.lastIndexOf('.'); + if (lastDot != -1) { + simpleAnnotationName = fullyQualifiedAnnotationName.substring(lastDot + 1); + } + // when comparing by simple name, double check whether we have a import to avoid false positives + return simpleName.equals(simpleAnnotationName) && hasImports(node, fullyQualifiedAnnotationName); + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/TestClassWithoutTestCasesRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/TestClassWithoutTestCasesRule.java index acce9a621f..796540b4e1 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/TestClassWithoutTestCasesRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/TestClassWithoutTestCasesRule.java @@ -5,23 +5,35 @@ package net.sourceforge.pmd.lang.java.rule.errorprone; import java.util.List; +import java.util.regex.Pattern; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBody; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBodyDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTPackageDeclaration; import net.sourceforge.pmd.lang.java.ast.JavaNode; import net.sourceforge.pmd.lang.java.rule.AbstractJUnitRule; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; +import net.sourceforge.pmd.properties.PropertyDescriptor; +import net.sourceforge.pmd.properties.PropertyFactory; public class TestClassWithoutTestCasesRule extends AbstractJavaRule { + private static final PropertyDescriptor TEST_CLASS_PATTERN = PropertyFactory.regexProperty("testClassPattern") + .defaultValue("^(.*\\.)?Test.*$|^(.*\\.)?.*Tests?$|^(.*\\.)?.*TestCase$") + .desc("Test class name pattern to identify test classes by their fully qualified name. " + + "A empty pattern disables test class detection by name. Since PMD 6.51.0.") + .build(); + public TestClassWithoutTestCasesRule() { addRuleChainVisit(ASTClassOrInterfaceBody.class); + definePropertyDescriptor(TEST_CLASS_PATTERN); } @Override public Object visit(ASTClassOrInterfaceBody node, Object data) { - if (AbstractJUnitRule.isTestClass(node)) { + if (AbstractJUnitRule.isTestClass(node) || isTestClassByPattern(node)) { List declarations = node.findChildrenOfType(ASTClassOrInterfaceBodyDeclaration.class); int testMethods = 0; for (ASTClassOrInterfaceBodyDeclaration decl : declarations) { @@ -36,6 +48,36 @@ public class TestClassWithoutTestCasesRule extends AbstractJavaRule { return data; } + private boolean isTestClassByPattern(ASTClassOrInterfaceBody node) { + Pattern testClassPattern = getProperty(TEST_CLASS_PATTERN); + if (testClassPattern.pattern().isEmpty()) { + // detection by pattern is disabled + return false; + } + + ASTClassOrInterfaceDeclaration classDecl = null; + if (node.getParent() instanceof ASTClassOrInterfaceDeclaration) { + classDecl = (ASTClassOrInterfaceDeclaration) node.getParent(); + } + + // classDecl can be null in case of anonymous classes or enum constants + if (classDecl == null || classDecl.isAbstract() || classDecl.isInterface()) { + return false; + } + + StringBuilder fullyQualifiedName = new StringBuilder(); + ASTPackageDeclaration packageDeclaration = classDecl.getRoot().getPackageDeclaration(); + if (packageDeclaration != null) { + fullyQualifiedName.append(packageDeclaration.getName()).append('.'); + } + List parentClasses = classDecl.getParentsOfType(ASTClassOrInterfaceDeclaration.class); + for (int i = parentClasses.size() - 1; i >= 0; i--) { + fullyQualifiedName.append(parentClasses.get(i).getSimpleName()).append('.'); + } + fullyQualifiedName.append(classDecl.getSimpleName()); + return testClassPattern.matcher(fullyQualifiedName).find(); + } + private boolean isTestMethod(ASTClassOrInterfaceBodyDeclaration decl) { JavaNode node = decl.getDeclarationNode(); if (node instanceof ASTMethodDeclaration) { diff --git a/pmd-java/src/main/resources/category/java/errorprone.xml b/pmd-java/src/main/resources/category/java/errorprone.xml index 77c809b50d..f8340365ef 100644 --- a/pmd-java/src/main/resources/category/java/errorprone.xml +++ b/pmd-java/src/main/resources/category/java/errorprone.xml @@ -3336,8 +3336,12 @@ public void foo() { class="net.sourceforge.pmd.lang.java.rule.errorprone.TestClassWithoutTestCasesRule" externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_errorprone.html#testclasswithouttestcases"> -Test classes end with the suffix Test. Having a non-test class with that name is not a good practice, -since most people will assume it is a test case. Test classes have test methods named testXXX. +Test classes end with the suffix "Test" or "TestCase". Having a non-test class with that name is not a good practice, +since most people will assume it is a test case. Test classes have test methods named "testXXX" (JUnit3) or use +annotations (e.g. `@Test`). + +The suffix can be configured using the property `testClassPattern`. To disable the detection of possible test classes +by name, set this property to an empty string. 3 diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/TestClassWithoutTestCases.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/TestClassWithoutTestCases.xml index 28b7f3d2dd..e9a6207eae 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/TestClassWithoutTestCases.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/TestClassWithoutTestCases.xml @@ -5,7 +5,7 @@ xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> - failure case + JUnit3: failure case 1 - test method should be public + JUnit3: test method should be public 1 - inner class should get checked + JUnit3: inner class should get checked 1 2 - test method in inner class not valid + JUnit3: test method in inner class not valid + 1 + 2 - abstract classes are ok + JUnit3: abstract classes are ok 0 - failure case does not extend TestCase + failure case does not extend TestCase and testClassPattern is not used + 0 - #1453 False positive when the test class extends an other. + failure case does not extend TestCase and default testClassPattern + 1 + 1 + + + + + JUnit4: #1453 False positive when the test class extends an other. 0 - #428 [java] PMD requires public modifier on JUnit 5 test + JUnit5: #428 [java] PMD requires public modifier on JUnit 5 test 0 - false positive with anonymous class inside test class + JUnit4: false positive with anonymous class inside test class 0 - [java] TestClassWithoutTestCases reports wrong classes in a file #3624 + JUnit4: [java] TestClassWithoutTestCases reports wrong classes in a file #3624 0 + + + JUnit5: [java] TestClassWithoutTestCases false positive with JUnit5 parameterized test #2636 + 0 + 0); + } +} +]]> + + + + JUnit5: [java] TestClassWithoutTestCases false positive with JUnit5 normal test #2636 + 0 + + + + + empty package-private test class identified by default testClassPattern + 1 + 1 + + + + + empty test class identified by special testClassPattern + my\.pkg\..*Case + 1 + 2 + + + + + empty test class identified by special testClassPattern and nested classes + my\.pkg\..*Case$ + 2 + 2,3 + + + + + empty test class identified by special testClassPattern - other package + my\.pkg\..*Case + 0 + + + + + TestNG based test class without import + 0 + + + + + TestNG based test class with import + 0 + + From c8eecb53a490366b438858ce28c883136ebfda04 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Sun, 16 Oct 2022 11:37:57 +0200 Subject: [PATCH 174/254] [doc] Fix broken links for "Architecture Decisions" Fixes #4163 --- docs/pages/pmd/projectdocs/decisions.md | 2 +- docs/pages/release_notes.md | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/pages/pmd/projectdocs/decisions.md b/docs/pages/pmd/projectdocs/decisions.md index af0258c29c..b8f813bad7 100644 --- a/docs/pages/pmd/projectdocs/decisions.md +++ b/docs/pages/pmd/projectdocs/decisions.md @@ -8,7 +8,7 @@ last_updated: July 2022
    {% for page in site.pages %} {% if page.adr == true and page.adr_status != "" %} -
  • {{ page.title }} ({{ page.adr_status }})
  • +
  • {{ page.title }} ({{ page.adr_status }})
  • {% endif %} {% endfor %}
diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index 2ac4d95984..687f3714a1 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -29,7 +29,8 @@ The rule is part of the quickstart.xml ruleset. * apex * [#4149](https://github.com/pmd/pmd/issues/4149): \[apex] New rule: ApexUnitTestClassShouldHaveRunAs * doc - * [#4144](https://github.com/pmd/pmd/pull/4144) \[doc] Update docs to reflect supported languages + * [#4144](https://github.com/pmd/pmd/pull/4144): \[doc] Update docs to reflect supported languages + * [#4163](https://github.com/pmd/pmd/issues/4163): \[doc] Broken links on page "Architecture Decisions" * java-documentation * [#4141](https://github.com/pmd/pmd/issues/4141): \[java] UncommentedEmptyConstructor FP when constructor annotated with @Autowired From 75450c7e9f688aba9384db903e5d9838e01cdf5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Sun, 16 Oct 2022 23:33:07 -0300 Subject: [PATCH 175/254] Bump pmdtester --- Gemfile.lock | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 51f431b572..f51b7a997a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/pmd/pmd-regression-tester.git - revision: a69aa251e8893b0c3af2c16917430cdc81c70574 + revision: 3b26e7fc28203f5ea4f1cb840d990ad7f28b5491 branch: master specs: pmdtester (1.5.2.pre.SNAPSHOT) @@ -89,13 +89,6 @@ GEM faraday (>= 1, < 3) sawyer (~> 0.9) open4 (1.3.4) - pmdtester (1.5.1) - differ (~> 0.1) - liquid (~> 5.2) - logger-colors (~> 1.0) - nokogiri (~> 1.13) - rufus-scheduler (~> 3.8) - slop (~> 4.6) public_suffix (5.0.0) raabro (1.4.0) racc (1.6.0) From 7ed57a7198d2f74b9d18f741c77579212e221553 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Sun, 16 Oct 2022 23:39:09 -0300 Subject: [PATCH 176/254] Don't use the -d in basic examples --- docs/pages/7_0_0_release_notes.md | 2 +- docs/pages/pmd/userdocs/installation.md | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/pages/7_0_0_release_notes.md b/docs/pages/7_0_0_release_notes.md index c78d824e23..8c918fb25c 100644 --- a/docs/pages/7_0_0_release_notes.md +++ b/docs/pages/7_0_0_release_notes.md @@ -52,7 +52,7 @@ you should now use ```shell pmd check -d src -R ruleset.xml ``` -or even better, omit using `-d` / `--dir` and simply pass the sources as positional parameters +or even better, omit using `-d` / `--dir` and simply pass the sources at the end of the parameter list ```shell pmd check -R ruleset.xml src diff --git a/docs/pages/pmd/userdocs/installation.md b/docs/pages/pmd/userdocs/installation.md index 23f4b5d6a8..325fa7503e 100644 --- a/docs/pages/pmd/userdocs/installation.md +++ b/docs/pages/pmd/userdocs/installation.md @@ -61,14 +61,14 @@ To enable it, simply add `source *path_to_pmd*/shell/pmd-completion.sh` to your to execute ('check', 'designer', ...), e.g. PMD is launched via `pmd check`. The rest of the arguments are specific to the utility used.

" %} -Running a PMD analysis (`pmd check` or `pmd.bat check`) requires at least two options: +Running a PMD analysis (`pmd check` or `pmd.bat check`) requires at least one option and a list of sources: -* `-d `: path to the sources to analyse. This can be a file name, a directory, or a jar or zip file containing the -sources. * `-R `: the ruleset file you want to use. PMD uses xml configuration files, called *rulesets*, which specify which rules to execute on your sources. You can also run a single rule by referencing it using its *category* and name (more details [here](pmd_userdocs_making_rulesets.html#referencing-a-single-rule)). For example, you can check for unnecessary modifiers on Java sources with `-R category/java/codestyle.xml/UnnecessaryModifier`. +* ` …`: path to the sources to analyse. This can be a file name, a directory, or a jar or zip file containing the +sources. Alternatively You can use the `-d` or `--dir` flag, which is equivalent. {% include note.html content="At the moment the formerly provided rulesets (eg `rulesets/java/basic.xml`) are deprecated, @@ -93,7 +93,7 @@ Additionally, the following options, are specified most of the time even though {% include cli_example.html id="pmd" - linux="pmd check -d ../../../src/main/java/ -f text -R rulesets/java/quickstart.xml + linux="pmd check -f text -R rulesets/java/quickstart.xml src/main/java .../src/main/java/com/me/RuleSet.java:123 These nested if statements could be combined .../src/main/java/com/me/RuleSet.java:231 Useless parentheses. From 25ffe7a2c2b866750ed52b2fd1addef1652f1fc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Sun, 16 Oct 2022 23:45:27 -0300 Subject: [PATCH 177/254] Make sure cpd docs reflect this too --- docs/pages/7_0_0_release_notes.md | 12 +++++++++++- docs/pages/pmd/userdocs/installation.md | 13 ++++++------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/docs/pages/7_0_0_release_notes.md b/docs/pages/7_0_0_release_notes.md index 8c918fb25c..ba6d3a91fe 100644 --- a/docs/pages/7_0_0_release_notes.md +++ b/docs/pages/7_0_0_release_notes.md @@ -58,7 +58,17 @@ or even better, omit using `-d` / `--dir` and simply pass the sources at the end pmd check -R ruleset.xml src ``` -Additionally, the CLI has been enhanced with a progress bar, which interactively displays the +Multiple source directories can passed, such as: +```shell +pmd check -R ruleset.xml src/main/java src/test/java +``` + +And the exact same applies to CPD: +```shell +pmd cpd --minimum-tokens 100 src/main/java +``` + +Additionally, the CLI for the `check` command has been enhanced with a progress bar, which interactively displays the current progress of the analysis. TODO screenshot (take it right before releasing, because other changes to the CLI will occur until then) diff --git a/docs/pages/pmd/userdocs/installation.md b/docs/pages/pmd/userdocs/installation.md index 325fa7503e..7d4d8c8d4c 100644 --- a/docs/pages/pmd/userdocs/installation.md +++ b/docs/pages/pmd/userdocs/installation.md @@ -100,7 +100,7 @@ Additionally, the following options, are specified most of the time even though .../src/main/java/com/me/RuleSet.java:232 Useless parentheses. .../src/main/java/com/me/RuleSet.java:357 These nested if statements could be combined .../src/main/java/com/me/RuleSetWriter.java:66 Avoid empty catch blocks" - windows="pmd.bat check -d ..\..\src\main\java\ -f text -R rulesets/java/quickstart.xml + windows="pmd.bat check -f text -R rulesets/java/quickstart.xml ..\..\src\main\java\ .../src/main/java/com/me/RuleSet.java:123 These nested if statements could be combined .../src/main/java/com/me/RuleSet.java:231 Useless parentheses. @@ -114,12 +114,11 @@ Additionally, the following options, are specified most of the time even though content="CPD supports Java, JSP, C, C++, C#, Fortran and PHP source code, among other languages. For the full list, see [Supported Languages](pmd_userdocs_cpd.html#supported-languages)." %} -Like for PMD, CPD is started on Unix by `pmd cpd` and on Windows by `pmd.bat cpd`. +Like for PMD, CPD is started on Unix by `pmd cpd` and on Windows by `pmd.bat cpd`, and it requires one option and a list of sources: -There are two required parameters: -* `--files `: path to the sources to analyse. This can be a file name, a - directory or a jar or zip file containing the sources. * `--minimum-tokens `: the minimum token length which should be reported as a duplicate. +* ` …`: path to the sources to analyse. This can be a file name, a directory, or a jar or zip file containing the +sources. Alternatively You can use the `-d` or `--dir` flag, which is equivalent. {% include tip.html content="CPD's command-line reference, Ant task usage, and many examples are documented in the @@ -131,7 +130,7 @@ There are two required parameters: {% include cli_example.html id="cpd" - linux="pmd cpd --minimum-tokens 100 --files /home/me/src + linux="pmd cpd --minimum-tokens 100 /home/me/src Found a 7 line (110 tokens) duplication in the following files: Starting at line 579 of /home/me/src/test/java/foo/FooTypeTest.java @@ -144,7 +143,7 @@ There are two required parameters: assertEquals(Boolean.TYPE, expressions.get(index++).getType()); assertEquals(Boolean.TYPE, expressions.get(index++).getType()); assertEquals(Boolean.TYPE, expressions.get(index++).getType());" - windows="pmd.bat cpd --minimum-tokens 100 --files /home/me/src + windows="pmd.bat cpd --minimum-tokens 100 /home/me/src Found a 7 line (110 tokens) duplication in the following files: Starting at line 579 of c:\temp\src\test\java\foo\FooTypeTest.java From 074217a57011eec854300d936ee66185bc505eff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Mon, 17 Oct 2022 00:11:21 -0300 Subject: [PATCH 178/254] Allow an empty line to indicate the start of output --- docs/_includes/cli_example.html | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/docs/_includes/cli_example.html b/docs/_includes/cli_example.html index 6694924a5f..edf1c5c166 100644 --- a/docs/_includes/cli_example.html +++ b/docs/_includes/cli_example.html @@ -1,19 +1,37 @@ {% assign linux_cmd_blob = '' %} {% assign linux_split_cmds = include.linux | newline_to_br | strip_newlines | split: '
' %} +{% assign raw_output = false %} {% for linux_raw_cmd in linux_split_cmds %} {% assign linux_cmd = linux_raw_cmd | strip | split: ' ' | first %} {% assign linux_tail = linux_raw_cmd | strip | remove_first: linux_cmd %} - {% capture linux_cmd_blob %}{{linux_cmd_blob}}~ $ {{linux_cmd}}{{linux_tail}} + {% if linux_cmd == nil %} + {% assign raw_output = true %} + {% endif %} + {% if raw_output %} + {% capture linux_cmd_blob %}{{linux_cmd_blob}}{{linux_cmd}}{{linux_tail}} {% endcapture %} + {% else %} + {% capture linux_cmd_blob %}{{linux_cmd_blob}}~ $ {{linux_cmd}}{{linux_tail}} +{% endcapture %} + {% endif %} {% endfor %} {% assign windows_cmd_blob = '' %} {% assign windows_split_cmds = include.windows | newline_to_br | strip_newlines | split: '
' %} +{% assign raw_output = false %} {% for windows_raw_cmd in windows_split_cmds %} {% assign windows_cmd = windows_raw_cmd | strip | split: ' ' | first %} {% assign windows_tail = windows_raw_cmd | strip | remove_first: windows_cmd %} - {% capture windows_cmd_blob %}{{windows_cmd_blob}}C:\> {{windows_cmd}}{{windows_tail}} + {% if windows_cmd == nil %} + {% assign raw_output = true %} + {% endif %} + {% if raw_output == true %} + {% capture windows_cmd_blob %}{{windows_cmd_blob}}{{windows_cmd}}{{windows_tail}} {% endcapture %} + {% else %} + {% capture windows_cmd_blob %}{{windows_cmd_blob}}C:\> {{windows_cmd}}{{windows_tail}} +{% endcapture %} + {% endif %} {% endfor %}