diff --git a/docs/pages/pmd/userdocs/cli_reference.md b/docs/pages/pmd/userdocs/cli_reference.md index a60683d345..ba0353d144 100644 --- a/docs/pages/pmd/userdocs/cli_reference.md +++ b/docs/pages/pmd/userdocs/cli_reference.md @@ -122,7 +122,8 @@ The tool comes with a rather extensive help text, simply running with `--help`! %} {% 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)." + description="Specify the language PMD should use. Used together with `-version`. See also [Supported Languages](#supported-languages). +

Deprecated since PMD 6.52.0. Use `--use-version` instead.

" %} {% include custom/cli_option_row.html options="--minimum-priority" option_arg="priority" @@ -147,13 +148,17 @@ The tool comes with a rather extensive help text, simply running with `--help`! description="Specifies a property for the report renderer. The option can be specified several times.

Using `--help` will provide a complete list of supported properties for each report format

" %} + {% include custom/cli_option_row.html options="--relativize-paths-with,-z" + option_arg="path" + description="Path relative to which directories are rendered in the report. This option allows + shortening directories in the report; without it, paths are rendered as mentioned in the source directory (option \"--dir\"). + The option can be repeated, in which case the shortest relative path will be used. + If the root path is mentioned (e.g. \"/\" or \"C:\\\"), then the paths will be rendered as absolute." + %} {% include custom/cli_option_row.html options="--report-file,-r" option_arg="path" 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." %} - {% include custom/cli_option_row.html options="--short-names" - description="Prints shortened filenames in the report." - %} {% include custom/cli_option_row.html options="--show-suppressed" description="Causes the suppressed rule violations to be added to the report." %} diff --git a/docs/pages/pmd/userdocs/tools/ant.md b/docs/pages/pmd/userdocs/tools/ant.md index 9212ac552c..c0637db94e 100644 --- a/docs/pages/pmd/userdocs/tools/ant.md +++ b/docs/pages/pmd/userdocs/tools/ant.md @@ -77,11 +77,6 @@ The examples below won't repeat this taskdef element, as this is always required The rule priority threshold; rules with lower priority than they will not be used No - - shortFilenames - Places truncated filenames in the report. This can reduce your report file size by 15%-20%. - No - failuresPropertyName A property name to plug the number of rule violations into when the task finishes @@ -187,7 +182,7 @@ automatically and the latest language version is used. - + rulesets/java/quickstart.xml config/my-ruleset.xml @@ -199,6 +194,11 @@ automatically and the latest language version is used. `fileset` nested element - specify the actual java source files, that PMD should analyze. You can use multiple fileset elements. See [FileSet](https://ant.apache.org/manual/Types/fileset.html) for the syntax and usage. +`relativizePathsWith` nested element - configures the paths relative to which directories are rendered in the report. +This option allows shortening directories in the report; without it, paths are rendered as absolute paths. +The option can be repeated, in which case the shortest relative path will be used. +It is a [path-like structure](https://ant.apache.org/manual/using.html#path). + ### Language version selection PMD selects the language automatically using the file extension. If multiple versions of a language are @@ -410,7 +410,7 @@ An HTML report with the "linkPrefix" and "linePrefix" properties: - + @@ -418,6 +418,9 @@ An HTML report with the "linkPrefix" and "linePrefix" properties: + + + diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index 9efe9f225c..fab1ba9cf8 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -30,6 +30,7 @@ See [the example report](report-examples/html-report-v2.html). * apex-bestpractices * [#2669](https://github.com/pmd/pmd/issues/2669): \[apex] UnusedLocalVariable false positive in dynamic SOQL * core + * [#4026](https://github.com/pmd/pmd/issues/4026): \[cli] Filenames printed as absolute paths in the report despite parameter `--short-names` * [#4279](https://github.com/pmd/pmd/issues/4279): \[core] Can not set ruleset property value to empty * [#4329](https://github.com/pmd/pmd/pull/4329): \[core] Refactor usage of snakeyaml * [#4340](https://github.com/pmd/pmd/issues/4340): \[core] Allow to filter found matches in CPDReport @@ -38,6 +39,13 @@ See [the example report](report-examples/html-report-v2.html). ### API Changes +#### PMD CLI + +* PMD now supports a new `--relativize-paths-with` flag (or short `-z`), which replaces `--short-names`. + It serves the same purpose: Shortening the pathnames in the reports. However, with the new flag it's possible + to explicitly define one or more pathnames that should be used as the base when creating relative paths. + The old flag `--short-names` is deprecated. + #### Deprecated APIs ##### For removal @@ -57,6 +65,14 @@ See [the example report](report-examples/html-report-v2.html). functionality as unit tests. * {% jdoc core::cpd.CPDConfiguration.LanguageConverter %} +* {% jdoc !!core::lang.document.FileCollector#addZipFile(java.nio.file.Path) %} has been deprecated. It is replaced + by {% jdoc !!core::lang.document.FileCollector#addZipFileWithContent(java.nio.file.Path) %} which directly adds the + content of the zip file for analysis. + +* {% jdoc !!core::PMDConfiguration#setReportShortNames(boolean) %} and + {% jdoc !!core::PMDConfiguration#isReportShortNames() %} have been deprecated for removal. + Use {% jdoc !!core::PMDConfiguration#addRelativizeRoot(java.nio.file.Path) %} instead. + ##### Internal APIs * {% jdoc core::renderers.CSVWriter %} 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 2f60dade65..578db83ca5 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 @@ -4,6 +4,7 @@ package net.sourceforge.pmd.ant; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; @@ -14,8 +15,10 @@ import org.apache.tools.ant.Task; import org.apache.tools.ant.types.FileSet; import org.apache.tools.ant.types.Path; import org.apache.tools.ant.types.Reference; +import org.apache.tools.ant.types.Resource; import net.sourceforge.pmd.RulePriority; +import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.ant.internal.PMDTaskImpl; /** @@ -30,7 +33,7 @@ public class PMDTask extends Task { private final List filesets = new ArrayList<>(); private boolean failOnError; private boolean failOnRuleViolation; - private boolean shortFilenames; + private final List relativizePathsWith = new ArrayList<>(); private String suppressMarker; private String rulesetFiles; private boolean noRuleSetCompatibility; @@ -94,10 +97,6 @@ public class PMDTask extends Task { return sb.toString(); } - public void setShortFilenames(boolean reportShortNames) { - this.shortFilenames = reportShortNames; - } - public void setSuppressMarker(String suppressMarker) { this.suppressMarker = suppressMarker; } @@ -207,10 +206,6 @@ public class PMDTask extends Task { return failOnRuleViolation; } - public boolean isShortFilenames() { - return shortFilenames; - } - public String getSuppressMarker() { return suppressMarker; } @@ -271,4 +266,23 @@ public class PMDTask extends Task { public void setNoCache(boolean noCache) { this.noCache = noCache; } + + public void addRelativizePathsWith(Path relativizePathsWith) { + this.relativizePathsWith.add(relativizePathsWith); + } + + public List getRelativizePathsWith() { + return relativizePathsWith; + } + + @InternalApi + public List getRelativizeRoots() { + List paths = new ArrayList<>(); + for (Path path : getRelativizePathsWith()) { + for (Resource resource : path) { + paths.add(Paths.get(resource.toString())); + } + } + return paths; + } } diff --git a/pmd-ant/src/main/java/net/sourceforge/pmd/ant/internal/PMDTaskImpl.java b/pmd-ant/src/main/java/net/sourceforge/pmd/ant/internal/PMDTaskImpl.java index de1ec6b952..a39fd1abfd 100644 --- a/pmd-ant/src/main/java/net/sourceforge/pmd/ant/internal/PMDTaskImpl.java +++ b/pmd-ant/src/main/java/net/sourceforge/pmd/ant/internal/PMDTaskImpl.java @@ -53,7 +53,7 @@ public class PMDTaskImpl { private Project project; public PMDTaskImpl(PMDTask task) { - configuration.setReportShortNames(task.isShortFilenames()); + configuration.addRelativizeRoots(task.getRelativizeRoots()); if (task.getSuppressMarker() != null) { configuration.setSuppressMarker(task.getSuppressMarker()); } @@ -103,7 +103,6 @@ public class PMDTaskImpl { project.log("Setting suppress marker to be " + configuration.getSuppressMarker(), Project.MSG_VERBOSE); } - List ruleSetPaths = expandRuleSetPaths(configuration.getRuleSetPaths()); // don't let PmdAnalysis.create create rulesets itself. configuration.setRuleSets(Collections.emptyList()); @@ -116,9 +115,6 @@ public class PMDTaskImpl { for (FileSet fileset : filesets) { DirectoryScanner ds = fileset.getDirectoryScanner(project); - if (configuration.isReportShortNames()) { - pmd.files().relativizeWith(ds.getBasedir().getPath()); - } for (String srcFile : ds.getIncludedFiles()) { pmd.files().addFile(ds.getBasedir().toPath().resolve(srcFile)); } 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 5623c23284..d3ea8f2072 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,6 +9,7 @@ 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.File; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; @@ -69,14 +70,14 @@ class PMDTaskTest extends AbstractAntTest { } @Test - void testWithShortFilenames() throws IOException { - executeTarget("testWithShortFilenames"); + void testRelativizeWith() throws IOException { + executeTarget("testRelativizeWith"); 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(); - assertThat(actual, containsString("sample.dummy:1:\tSampleXPathRule:\tTest Rule 2")); + actual = actual.replaceAll("\n|\r", ""); + assertThat(actual, containsString("src" + File.separator + "sample.dummy:1:\tSampleXPathRule:\tTest Rule 2")); } } 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 da96098178..fe82ff049a 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 @@ -25,25 +25,31 @@ - - + + rulesets/dummy/basic.xml - + + + + - + rulesets/dummy/basic.xml + + + 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 073e9dd564..e2da3c1af7 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 @@ -7,6 +7,7 @@ package net.sourceforge.pmd.cli.commands.internal; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; +import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Iterator; @@ -84,7 +85,7 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { private boolean benchmark; - private boolean shortnames; + private List relativizeRootPaths; private boolean showSuppressed; @@ -140,9 +141,21 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { this.benchmark = benchmark; } - @Option(names = "--short-names", description = "Prints shortened filenames in the report.") - public void setShortnames(final boolean shortnames) { - this.shortnames = shortnames; + @Option(names = { "--relativize-paths-with", "-z"}, description = "Path relative to which directories are rendered in the report. " + + "This option allows shortening directories in the report; " + + "without it, paths are rendered as mentioned in the source directory (option \"--dir\"). " + + "The option can be repeated, in which case the shortest relative path will be used. " + + "If the root path is mentioned (e.g. \"/\" or \"C:\\\"), then the paths will be rendered as absolute.", + arity = "1..*", split = ",") + public void setRelativizePathsWith(List rootPaths) { + this.relativizeRootPaths = rootPaths; + + for (Path path : this.relativizeRootPaths) { + if (Files.isRegularFile(path)) { + throw new ParameterException(spec.commandLine(), + "Expected a directory path for option '--relativize-paths-with', found a file: " + path); + } + } } @Option(names = "--show-suppressed", description = "Report should show suppressed rule violations.") @@ -272,7 +285,9 @@ public class PmdCommand extends AbstractAnalysisPmdSubcommand { configuration.setMinimumPriority(minimumPriority); configuration.setReportFile(reportFile); configuration.setReportProperties(properties); - configuration.setReportShortNames(shortnames); + if (relativizeRootPaths != null) { + configuration.addRelativizeRoots(relativizeRootPaths); + } configuration.setRuleSets(rulesets); configuration.setRuleSetFactoryCompatibilityEnabled(!this.noRuleSetCompatibility); configuration.setShowSuppressedViolations(showSuppressed); 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 1b52f4b580..f95c39afc7 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 @@ -11,13 +11,16 @@ import static net.sourceforge.pmd.util.CollectionUtil.listOf; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsStringIgnoringCase; +import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.startsWith; 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 static org.junit.jupiter.api.Assertions.fail; +import java.io.File; import java.io.FilterOutputStream; import java.io.IOException; import java.io.PrintStream; @@ -26,23 +29,28 @@ import java.nio.charset.StandardCharsets; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; -import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.cli.internal.CliExitCode; import net.sourceforge.pmd.internal.Slf4jSimpleConfiguration; +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.rule.MockRule; import net.sourceforge.pmd.util.IOUtil; import com.github.stefanbirkner.systemlambda.SystemLambda; class PmdCliTest extends BaseCliTest { + private static final String DUMMY_RULESET_WITH_VIOLATIONS = "net/sourceforge/pmd/cli/FakeRuleset2.xml"; static final String RULESET_WITH_VIOLATION = "net/sourceforge/pmd/cli/RuleSetWithViolations.xml"; private static final String RULESET_NO_VIOLATIONS = "net/sourceforge/pmd/cli/FakeRuleset.xml"; private static final String NOT_A_RULESET = "ThisRuleSetDoesNotExist.xml"; @@ -52,8 +60,8 @@ class PmdCliTest extends BaseCliTest { private Path tempDir; private Path srcDir; - @AfterAll - static void resetLogging() { + @AfterEach + void resetLogging() { // reset logging in case "--debug" changed the logging properties // See also Slf4jSimpleConfigurationForAnt Slf4jSimpleConfiguration.reconfigureDefaultLogLevel(null); @@ -61,12 +69,8 @@ class PmdCliTest extends BaseCliTest { @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")); + srcDir = Files.createDirectories(tempRoot().resolve("src")); writeString(srcDir.resolve("someSource.dummy"), "dummy text"); } @@ -311,7 +315,7 @@ class PmdCliTest extends BaseCliTest { Path zipArchive = createTemporaryZipArchive("sources.zip"); CliExecutionResult result = runCli(VIOLATIONS_FOUND, "--dir", zipArchive.toString(), "--rulesets", "rulesets/dummy/basic.xml"); result.checkStdErr(not(containsStringIgnoringCase("Cannot open zip file"))); - String reportPath = IOUtil.normalizePath(zipArchive + "!/someSource.dummy"); + String reportPath = IOUtil.normalizePath(zipArchive.toString()) + "!/someSource.dummy"; result.checkStdOut(containsString(reportPath + ":1:\tSampleXPathRule:\tTest Rule 2")); } @@ -320,7 +324,7 @@ class PmdCliTest extends BaseCliTest { Path jarArchive = createTemporaryZipArchive("sources.jar"); CliExecutionResult result = runCli(VIOLATIONS_FOUND, "--dir", jarArchive.toString(), "--rulesets", "rulesets/dummy/basic.xml"); result.checkStdErr(not(containsStringIgnoringCase("Cannot open zip file"))); - String reportPath = IOUtil.normalizePath(jarArchive + "!/someSource.dummy"); + String reportPath = IOUtil.normalizePath(jarArchive.toString()) + "!/someSource.dummy"; result.checkStdOut(containsString(reportPath + ":1:\tSampleXPathRule:\tTest Rule 2")); } @@ -335,6 +339,131 @@ class PmdCliTest extends BaseCliTest { return zipArchive; } + @Test + void testNoRelativizeWithAbsoluteSrcDir() throws Exception { + assertTrue(srcDir.isAbsolute(), "srcDir should be absolute"); + runCli(CliExitCode.VIOLATIONS_FOUND, "--dir", srcDir.toString(), "--rulesets", + DUMMY_RULESET_WITH_VIOLATIONS) + .verify(result -> result.checkStdOut( + containsString(srcDir.resolve("someSource.dummy").toString()))); + } + + @Test + void testNoRelativizeWithRelativeSrcDir() throws Exception { + // Note, that we can't reliably change the current working directory for the current java process + // therefore we use the current directory and make sure, we are at the correct place - in pmd-core + Path cwd = Paths.get(".").toRealPath(); + assertThat(cwd.toString(), endsWith("pmd-cli")); + String relativeSrcDir = "src/test/resources/net/sourceforge/pmd/cli/src"; + assertTrue(Files.isDirectory(cwd.resolve(relativeSrcDir))); + + runCli(CliExitCode.VIOLATIONS_FOUND, "--dir", relativeSrcDir, "--rulesets", + DUMMY_RULESET_WITH_VIOLATIONS) + .verify(result -> result.checkStdOut( + containsString("\n" + IOUtil.normalizePath(relativeSrcDir + "/somefile.dummy")))); + } + + @Test + void testNoRelativizeWithRelativeSrcDirParent() throws Exception { + // Note, that we can't reliably change the current working directory for the current java process + // therefore we use the current directory and make sure, we are at the correct place - in pmd-core + Path cwd = Paths.get(".").toRealPath(); + assertThat(cwd.toString(), endsWith("pmd-cli")); + String relativeSrcDir = IOUtil.normalizePath("src/test/resources/net/sourceforge/pmd/cli/src"); + assertTrue(Files.isDirectory(cwd.resolve(relativeSrcDir))); + + // use the parent directory + String relativeSrcDirWithParent = relativeSrcDir + File.separator + ".."; + + runCli(CliExitCode.VIOLATIONS_FOUND, "--dir", relativeSrcDirWithParent, "--rulesets", + DUMMY_RULESET_WITH_VIOLATIONS) + .verify(result -> result.checkStdOut( + containsString("\n" + relativeSrcDirWithParent + IOUtil.normalizePath("/src/somefile.dummy")))); + } + + @Test + void testRelativizeWithRootRelativeSrcDir() throws Exception { + // Note, that we can't reliably change the current working directory for the current java process + // therefore we use the current directory and make sure, we are at the correct place - in pmd-core + Path cwd = Paths.get(".").toRealPath(); + assertThat(cwd.toString(), endsWith("pmd-cli")); + String relativeSrcDir = "src/test/resources/net/sourceforge/pmd/cli/src"; + assertTrue(Files.isDirectory(cwd.resolve(relativeSrcDir))); + + String root = cwd.getRoot().toString(); + String absoluteSrcPath = cwd.resolve(relativeSrcDir).resolve("somefile.dummy").toString(); + + runCli(CliExitCode.VIOLATIONS_FOUND, "--dir", relativeSrcDir, "--rulesets", + DUMMY_RULESET_WITH_VIOLATIONS, "--relativize-paths-with", root) + .verify(result -> result.checkStdOut( + containsString("\n" + absoluteSrcPath)) + ); + } + + @Test + void testRelativizeWith() throws Exception { + runCli(CliExitCode.VIOLATIONS_FOUND, "--dir", srcDir.toString(), "--rulesets", + DUMMY_RULESET_WITH_VIOLATIONS, "-z", srcDir.getParent().toString()) + .verify(result -> { + result.checkStdOut(not(containsString(srcDir.resolve("someSource.dummy").toString()))); + result.checkStdOut(startsWith(IOUtil.normalizePath("src/someSource.dummy"))); + }); + } + + @Test + void testRelativizeWithSymLink() throws Exception { + // srcDir = /tmp/junit123/src + // symlinkedSrcDir = /tmp/junit123/sources -> /tmp/junit123/src + Path symlinkedSrcDir = Files.createSymbolicLink(tempRoot().resolve("sources"), srcDir); + runCli(CliExitCode.VIOLATIONS_FOUND, "--dir", symlinkedSrcDir.toString(), "--rulesets", + DUMMY_RULESET_WITH_VIOLATIONS, "-z", symlinkedSrcDir.toString()) + .verify(result -> { + result.checkStdOut(not(containsString(srcDir.resolve("someSource.dummy").toString()))); + result.checkStdOut(not(containsString(symlinkedSrcDir.resolve("someSource.dummy").toString()))); + result.checkStdOut(startsWith("someSource.dummy")); + }); + } + + @Test + void testRelativizeWithSymLinkParent() throws Exception { + // srcDir = /tmp/junit123/src + // symlinkedSrcDir = /tmp/junit-relativize-with-123 -> /tmp/junit123/src + Path tempPath = Files.createTempDirectory("junit-relativize-with-"); + Files.delete(tempPath); + Path symlinkedSrcDir = Files.createSymbolicLink(tempPath, srcDir); + // relativizing against parent of symlinkedSrcDir: /tmp + runCli(CliExitCode.VIOLATIONS_FOUND, "--dir", symlinkedSrcDir.toString(), "--rulesets", + DUMMY_RULESET_WITH_VIOLATIONS, "-z", symlinkedSrcDir.getParent().toString()) + .verify(result -> { + result.checkStdOut(not(containsString(srcDir.resolve("someSource.dummy").toString()))); + result.checkStdOut(not(containsString(symlinkedSrcDir.resolve("someSource.dummy").toString()))); + // base path is symlinkedSrcDir without /tmp: e.g. junit-relativize-with-123 + String basePath = symlinkedSrcDir.getParent().relativize(symlinkedSrcDir).toString(); + result.checkStdOut(startsWith(basePath + File.separator + "someSource.dummy")); + }); + } + + @Test + void testRelativizeWithMultiple() throws Exception { + runCli(CliExitCode.VIOLATIONS_FOUND, "--dir", srcDir.toString(), "--rulesets", + DUMMY_RULESET_WITH_VIOLATIONS, "-z", srcDir.getParent().toString() + "," + srcDir.toString()) + .verify(result -> { + result.checkStdOut(not(containsString(srcDir.resolve("someSource.dummy").toString()))); + result.checkStdOut(startsWith("someSource.dummy")); + }); + } + + @Test + void testRelativizeWithFileIsError() throws Exception { + runCli(CliExitCode.USAGE_ERROR, "--dir", srcDir.toString(), "--rulesets", + DUMMY_RULESET_WITH_VIOLATIONS, "-z", srcDir.resolve("someSource.dummy").toString()) + .verify(result -> result.checkStdErr( + containsString( + "Expected a directory path for option '--relativize-paths-with', found a file: " + + srcDir.resolve("someSource.dummy")) + )); + } + // utilities private Path tempRoot() { return tempDir; @@ -361,4 +490,11 @@ class PmdCliTest extends BaseCliTest { "check", "--no-cache", "--no-progress" ); } + + public static class FooRule extends MockRule { + @Override + public void apply(Node node, RuleContext ctx) { + ctx.addViolation(node); + } + } } diff --git a/pmd-cli/src/test/resources/net/sourceforge/pmd/cli/FakeRuleset2.xml b/pmd-cli/src/test/resources/net/sourceforge/pmd/cli/FakeRuleset2.xml new file mode 100644 index 0000000000..e09f6ff039 --- /dev/null +++ b/pmd-cli/src/test/resources/net/sourceforge/pmd/cli/FakeRuleset2.xml @@ -0,0 +1,21 @@ + + + + + Ruleset used by net.sourceforge.pmd.cli.PmdCliTest + + + + +Just for test + + 3 + + + + + + diff --git a/pmd-cli/src/test/resources/net/sourceforge/pmd/cli/src/anotherfile.dummy b/pmd-cli/src/test/resources/net/sourceforge/pmd/cli/src/anotherfile.dummy new file mode 100644 index 0000000000..c3d7ac9496 --- /dev/null +++ b/pmd-cli/src/test/resources/net/sourceforge/pmd/cli/src/anotherfile.dummy @@ -0,0 +1 @@ +Another file for testing diff --git a/pmd-cli/src/test/resources/net/sourceforge/pmd/cli/src/somefile.dummy b/pmd-cli/src/test/resources/net/sourceforge/pmd/cli/src/somefile.dummy new file mode 100644 index 0000000000..901f2e4bd0 --- /dev/null +++ b/pmd-cli/src/test/resources/net/sourceforge/pmd/cli/src/somefile.dummy @@ -0,0 +1 @@ +Some file for testing diff --git a/pmd-cli/src/test/resources/net/sourceforge/pmd/cli/src/somefile1.dummy b/pmd-cli/src/test/resources/net/sourceforge/pmd/cli/src/somefile1.dummy new file mode 100644 index 0000000000..901f2e4bd0 --- /dev/null +++ b/pmd-cli/src/test/resources/net/sourceforge/pmd/cli/src/somefile1.dummy @@ -0,0 +1 @@ +Some file for testing diff --git a/pmd-cli/src/test/resources/net/sourceforge/pmd/cli/src/somefile2.dummy b/pmd-cli/src/test/resources/net/sourceforge/pmd/cli/src/somefile2.dummy new file mode 100644 index 0000000000..901f2e4bd0 --- /dev/null +++ b/pmd-cli/src/test/resources/net/sourceforge/pmd/cli/src/somefile2.dummy @@ -0,0 +1 @@ +Some file for testing diff --git a/pmd-cli/src/test/resources/net/sourceforge/pmd/cli/src/somefile3.dummy b/pmd-cli/src/test/resources/net/sourceforge/pmd/cli/src/somefile3.dummy new file mode 100644 index 0000000000..901f2e4bd0 --- /dev/null +++ b/pmd-cli/src/test/resources/net/sourceforge/pmd/cli/src/somefile3.dummy @@ -0,0 +1 @@ +Some file for testing diff --git a/pmd-cli/src/test/resources/net/sourceforge/pmd/cli/src/somefile4.dummy b/pmd-cli/src/test/resources/net/sourceforge/pmd/cli/src/somefile4.dummy new file mode 100644 index 0000000000..901f2e4bd0 --- /dev/null +++ b/pmd-cli/src/test/resources/net/sourceforge/pmd/cli/src/somefile4.dummy @@ -0,0 +1 @@ +Some file for testing 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 acf5818af5..d60d2dec0e 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java @@ -16,6 +16,7 @@ import java.util.Map.Entry; import java.util.Objects; import java.util.stream.Collectors; +import org.apache.commons.lang3.exception.ExceptionUtils; import org.checkerframework.checker.nullness.qual.NonNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -155,9 +156,17 @@ public final class PMD { return StatusCode.ERROR; } - PMDConfiguration configuration = Objects.requireNonNull( - parseResult.toConfiguration() - ); + PMDConfiguration configuration = null; + try { + configuration = Objects.requireNonNull( + parseResult.toConfiguration() + ); + } catch (IllegalArgumentException e) { + System.err.println("Cannot start analysis: " + e); + log.debug(ExceptionUtils.getStackTrace(e)); + return StatusCode.ERROR; + } + MessageReporter pmdReporter = setupMessageReporter(configuration); configuration.setReporter(pmdReporter); 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 052c0c3a22..1c8549fed6 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/PMDConfiguration.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/PMDConfiguration.java @@ -7,6 +7,7 @@ package net.sourceforge.pmd; import java.io.File; import java.io.IOException; import java.net.URI; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; @@ -78,8 +79,8 @@ import net.sourceforge.pmd.util.log.internal.SimpleMessageReporter; *
    *
  • The renderer format to use for Reports. {@link #getReportFormat()}
  • *
  • The file to which the Report should render. {@link #getReportFile()}
  • - *
  • An indicator of whether to use File short names in Reports, defaults to - * false. {@link #isReportShortNames()}
  • + *
  • Configure the root paths that are used to relativize file names in reports via {@link #addRelativizeRoot(Path)}. + * This enables to get short names in reports.
  • *
  • The initialization properties to use when creating a Renderer instance. * {@link #getReportProperties()}
  • *
  • An indicator of whether to show suppressed Rule violations in Reports. @@ -122,7 +123,6 @@ public class PMDConfiguration extends AbstractConfiguration { // Reporting options private String reportFormat; private Path reportFile; - private boolean reportShortNames = false; private Properties reportProperties = new Properties(); private boolean showSuppressedViolations = false; private boolean failOnViolation = true; @@ -134,6 +134,7 @@ public class PMDConfiguration extends AbstractConfiguration { private AnalysisCache analysisCache = new NoopAnalysisCache(); private boolean ignoreIncrementalAnalysis; private final LanguageRegistry langRegistry; + private final List relativizeRoots = new ArrayList<>(); public PMDConfiguration() { this(DEFAULT_REGISTRY); @@ -483,7 +484,7 @@ public class PMDConfiguration extends AbstractConfiguration { * @param inputPaths The comma separated list. * * @throws NullPointerException If the parameter is null - * @deprecated Use {@link #setInputPaths(List)} or {@link #addInputPath(String)} + * @deprecated Use {@link #setInputPathList(List)} or {@link #addInputPath(Path)} */ @Deprecated public void setInputPaths(String inputPaths) { @@ -600,25 +601,6 @@ public class PMDConfiguration extends AbstractConfiguration { this.inputUri = inputUri; } - /** - * Get whether to use File short names in Reports. - * - * @return true when using short names in reports. - */ - public boolean isReportShortNames() { - return reportShortNames; - } - - /** - * Set whether to use File short names in Reports. - * - * @param reportShortNames - * true when using short names in reports. - */ - public void setReportShortNames(boolean reportShortNames) { - this.reportShortNames = reportShortNames; - } - /** * Create a Renderer instance based upon the configured reporting options. * No writer is created. @@ -882,8 +864,8 @@ public class PMDConfiguration extends AbstractConfiguration { */ public void setAnalysisCacheLocation(final String cacheLocation) { setAnalysisCache(cacheLocation == null - ? new NoopAnalysisCache() - : new FileAnalysisCache(new File(cacheLocation))); + ? new NoopAnalysisCache() + : new FileAnalysisCache(new File(cacheLocation))); } @@ -909,4 +891,56 @@ public class PMDConfiguration extends AbstractConfiguration { public boolean isIgnoreIncrementalAnalysis() { return ignoreIncrementalAnalysis; } + + /** + * Set the path used to shorten paths output in the report. + * The path does not need to exist. If it exists, it must point + * to a directory and not a file. See {@link #getRelativizeRoots()} + * for the interpretation. + * + *

    If several paths are added, the shortest paths possible are + * built. + * + * @param path A path + * + * @throws IllegalArgumentException If the path points to a file, and not a directory + * @throws NullPointerException If the path is null + */ + public void addRelativizeRoot(Path path) { + // Note: the given path is not further modified or resolved. E.g. there is no special handling for symlinks. + // The goal is, that if the user inputs a path, PMD should output in terms of that path, not it's resolution. + this.relativizeRoots.add(Objects.requireNonNull(path)); + + if (Files.isRegularFile(path)) { + throw new IllegalArgumentException("Relativize root should be a directory: " + path); + } + } + + + /** + * Add several paths to shorten paths that are output in the report. + * See {@link #addRelativizeRoot(Path)}. + * + * @param paths A list of non-null paths + * + * @throws IllegalArgumentException If any path points to a file, and not a directory + * @throws NullPointerException If the list, or any path in the list is null + */ + public void addRelativizeRoots(List paths) { + for (Path path : paths) { + addRelativizeRoot(path); + } + } + + /** + * Returns the paths used to shorten paths output in the report. + *

      + *
    • If the list is empty, then paths are not touched + *
    • If the list is non-empty, then source file paths are relativized with all the items in the list. + * The shortest of these relative paths is taken as the display name of the file. + *
    + */ + public List getRelativizeRoots() { + return Collections.unmodifiableList(relativizeRoots); + } } 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 32a4025700..f9a42d0b88 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/PmdAnalysis.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/PmdAnalysis.java @@ -6,6 +6,7 @@ package net.sourceforge.pmd; import static net.sourceforge.pmd.util.CollectionUtil.listOf; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -99,6 +100,10 @@ public final class PmdAnalysis implements AutoCloseable { config.getLanguageVersionDiscoverer(), reporter ); + + for (Path path : config.getRelativizeRoots()) { + this.collector.relativizeWith(path); + } } /** 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 bd12ed5177..2c96b29c8b 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDParameters.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDParameters.java @@ -4,6 +4,9 @@ package net.sourceforge.pmd.cli; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -35,6 +38,7 @@ import com.beust.jcommander.validators.PositiveInteger; @InternalApi public class PMDParameters { + static final String RELATIVIZE_PATHS_WITH = "--relativize-paths-with"; @Parameter(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. " @@ -99,9 +103,6 @@ public class PMDParameters { @Parameter(names = { "--stress", "-stress", "-S" }, description = "Performs a stress test.") private boolean stress = false; - @Parameter(names = { "--short-names", "-shortnames" }, description = "Prints shortened filenames in the report.") - private boolean shortnames = false; - @Parameter(names = { "--show-suppressed", "-showsuppressed" }, description = "Report should show suppressed rule violations.") private boolean showsuppressed = false; @@ -125,6 +126,17 @@ public class PMDParameters { + "If this option is not specified, the report is rendered to standard output.") private String reportfile = null; + @Parameter(names = { RELATIVIZE_PATHS_WITH, "-z" }, + variableArity = true, + description = "Path relative to which directories are rendered in the report. " + + "This option allows shortening directories in the report; " + + "without it, paths are rendered as mentioned in the source directory (option \"--dir\"). " + + "The option can be repeated, in which case the shortest relative path will be used. " + + "If the root path is mentioned (e.g. \"/\" or \"C:\\\"), then the paths will be rendered as absolute.", + validateValueWith = PathToRelativizeRootValidator.class, + converter = StringToPathConverter.class) + private List relativizePathRoot = new ArrayList<>(); + @Parameter(names = { "-version", "-v" }, description = "Specify version of a language PMD should use.") private String version = null; @@ -203,6 +215,23 @@ public class PMDParameters { } } + public static class PathToRelativizeRootValidator implements IValueValidator> { + @Override + public void validate(String name, List value) throws ParameterException { + for (Path p : value) { + if (Files.isRegularFile(p)) { + throw new ParameterException("Expected a directory path for option " + name + ", found a file: " + p); + } + } + } + } + + public static class StringToPathConverter implements IStringConverter { + @Override + public Path convert(String value) { + return Paths.get(value); + } + } /** * Converts these parameters into a configuration. @@ -236,10 +265,10 @@ public class PMDParameters { configuration.setReportFormat(this.getFormat()); configuration.setBenchmark(this.isBenchmark()); configuration.setDebug(this.isDebug()); + configuration.addRelativizeRoots(this.relativizePathRoot); configuration.setMinimumPriority(this.getMinimumPriority()); configuration.setReportFile(this.getReportfile()); configuration.setReportProperties(this.getProperties()); - configuration.setReportShortNames(this.isShortnames()); configuration.setRuleSets(Arrays.asList(this.getRulesets().split(","))); configuration.setRuleSetFactoryCompatibilityEnabled(!this.noRuleSetCompatibility); configuration.setShowSuppressedViolations(this.isShowsuppressed()); @@ -321,10 +350,6 @@ public class PMDParameters { return stress; } - public boolean isShortnames() { - return shortnames; - } - public boolean isShowsuppressed() { return showsuppressed; } 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 aa593b633a..8989f59a38 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 @@ -143,6 +143,7 @@ public final class PmdParametersParseResult { /** Map of deprecated option to suggested replacement. */ private static final Map SUGGESTED_REPLACEMENT; + static { Map m = new LinkedHashMap<>(); @@ -159,7 +160,8 @@ public final class PmdParametersParseResult { m.put("-threads", "--threads"); m.put("-benchmark", "--benchmark"); m.put("-stress", "--stress"); - m.put("-shortnames", "--short-names"); + m.put("-shortnames", PMDParameters.RELATIVIZE_PATHS_WITH); + m.put("--short-names", PMDParameters.RELATIVIZE_PATHS_WITH); m.put("-showsuppressed", "--show-suppressed"); m.put("-suppressmarker", "--suppress-marker"); m.put("-minimumpriority", "--minimum-priority"); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/internal/util/FileCollectionUtil.java b/pmd-core/src/main/java/net/sourceforge/pmd/internal/util/FileCollectionUtil.java index dc3df472b0..f06a227c31 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/internal/util/FileCollectionUtil.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/internal/util/FileCollectionUtil.java @@ -7,7 +7,6 @@ package net.sourceforge.pmd.internal.util; import java.io.IOException; import java.io.Reader; import java.net.URI; -import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.sql.SQLException; @@ -42,12 +41,14 @@ public final class FileCollectionUtil { collector.setCharset(configuration.getSourceEncoding()); } + collectFiles(collector, configuration.getInputPathList()); if (configuration.getUri() != null) { collectDB(collector, configuration.getUri()); } + if (configuration.getInputFile() != null) { collectFileList(collector, configuration.getInputFile()); } @@ -104,15 +105,7 @@ public final class FileCollectionUtil { LOG.debug("Adding directory {}.", path); collector.addDirectory(path); } else if (pathStr.endsWith(".zip") || pathStr.endsWith(".jar")) { - LOG.debug("Adding zip file {}.", path); - @SuppressWarnings("PMD.CloseResource") - FileSystem fs = collector.addZipFile(path); - if (fs == null) { - return; - } - for (Path zipRoot : fs.getRootDirectories()) { - collector.addFileOrDirectory(zipRoot); - } + collector.addZipFileWithContent(path); } else if (Files.isRegularFile(path)) { LOG.debug("Adding regular file {}.", path); collector.addFile(path); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/document/FileCollector.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/document/FileCollector.java index dab049f24a..e3ca07677a 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/document/FileCollector.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/document/FileCollector.java @@ -12,7 +12,9 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.FileSystem; import java.nio.file.FileSystemAlreadyExistsException; +import java.nio.file.FileSystemNotFoundException; import java.nio.file.FileSystems; +import java.nio.file.FileVisitOption; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; @@ -22,8 +24,10 @@ import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.EnumSet; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; import java.util.Objects; import java.util.Set; @@ -32,6 +36,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.sourceforge.pmd.PmdAnalysis; +import net.sourceforge.pmd.annotation.Experimental; import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.internal.util.AssertionUtil; import net.sourceforge.pmd.lang.Language; @@ -51,19 +56,23 @@ public final class FileCollector implements AutoCloseable { private static final Logger LOG = LoggerFactory.getLogger(FileCollector.class); - private final List allFilesToProcess = new ArrayList<>(); + private final Set allFilesToProcess = new LinkedHashSet<>(); private final List resourcesToClose = new ArrayList<>(); private Charset charset = StandardCharsets.UTF_8; private final LanguageVersionDiscoverer discoverer; private final MessageReporter reporter; - private final List relativizeRoots = new ArrayList<>(); + private final String outerFsDisplayName; + @Deprecated + private final List legacyRelativizeRoots = new ArrayList<>(); + private final List relativizeRootPaths = new ArrayList<>(); private boolean closed; // construction - private FileCollector(LanguageVersionDiscoverer discoverer, MessageReporter reporter) { + private FileCollector(LanguageVersionDiscoverer discoverer, MessageReporter reporter, String outerFsDisplayName) { this.discoverer = discoverer; this.reporter = reporter; + this.outerFsDisplayName = outerFsDisplayName; } /** @@ -72,7 +81,7 @@ public final class FileCollector implements AutoCloseable { */ @InternalApi public static FileCollector newCollector(LanguageVersionDiscoverer discoverer, MessageReporter reporter) { - return new FileCollector(discoverer, reporter); + return new FileCollector(discoverer, reporter, null); } /** @@ -80,9 +89,9 @@ public final class FileCollector implements AutoCloseable { */ @InternalApi public FileCollector newCollector(MessageReporter logger) { - FileCollector fileCollector = new FileCollector(discoverer, logger); + FileCollector fileCollector = new FileCollector(discoverer, logger, null); fileCollector.charset = this.charset; - fileCollector.relativizeRoots.addAll(this.relativizeRoots); + fileCollector.relativizeRootPaths.addAll(this.relativizeRootPaths); return fileCollector; } @@ -98,6 +107,7 @@ public final class FileCollector implements AutoCloseable { if (closed) { throw new IllegalStateException("Collector was closed!"); } + List allFilesToProcess = new ArrayList<>(this.allFilesToProcess); allFilesToProcess.sort(Comparator.comparing(TextFile::getPathId)); return Collections.unmodifiableList(allFilesToProcess); } @@ -143,13 +153,10 @@ public final class FileCollector implements AutoCloseable { return false; } LanguageVersion languageVersion = discoverLanguage(file.toString()); - if (languageVersion != null) { - addFileImpl(TextFile.builderForPath(file, charset, languageVersion) - .withDisplayName(getDisplayName(file)) - .build()); - return true; - } - return false; + return languageVersion != null + && addFileImpl(TextFile.builderForPath(file, charset, languageVersion) + .withDisplayName(getDisplayName(file)) + .build()); } /** @@ -170,10 +177,9 @@ public final class FileCollector implements AutoCloseable { } LanguageVersion lv = discoverer.getDefaultLanguageVersion(language); Objects.requireNonNull(lv); - addFileImpl(TextFile.builderForPath(file, charset, lv) + return addFileImpl(TextFile.builderForPath(file, charset, lv) .withDisplayName(getDisplayName(file)) .build()); - return true; } /** @@ -185,11 +191,7 @@ public final class FileCollector implements AutoCloseable { */ public boolean addFile(TextFile textFile) { AssertionUtil.requireParamNotNull("textFile", textFile); - if (checkContextualVersion(textFile)) { - addFileImpl(textFile); - return true; - } - return false; + return checkContextualVersion(textFile) && addFileImpl(textFile); } /** @@ -203,17 +205,19 @@ public final class FileCollector implements AutoCloseable { AssertionUtil.requireParamNotNull("pathId", pathId); LanguageVersion version = discoverLanguage(pathId); - if (version != null) { - addFileImpl(TextFile.builderForCharSeq(sourceContents, pathId, version).build()); - return true; - } - - return false; + return version != null + && addFileImpl(TextFile.builderForCharSeq(sourceContents, pathId, version) + .withDisplayName(pathId) + .build()); } - private void addFileImpl(TextFile textFile) { + private boolean addFileImpl(TextFile textFile) { LOG.trace("Adding file {} (lang: {}) ", textFile.getPathId(), textFile.getLanguageVersion().getTerseName()); - allFilesToProcess.add(textFile); + if (allFilesToProcess.add(textFile)) { + return true; + } + LOG.trace("File was already collected, skipping"); + return false; } private LanguageVersion discoverLanguage(String file) { @@ -255,18 +259,28 @@ public final class FileCollector implements AutoCloseable { } private String getDisplayName(Path file) { - return getDisplayName(file, relativizeRoots); + String localDisplayName = getLocalDisplayName(file); + if (outerFsDisplayName != null) { + return outerFsDisplayName + "!" + localDisplayName; + } + return localDisplayName; + } + + private String getLocalDisplayName(Path file) { + if (!relativizeRootPaths.isEmpty()) { + // takes precedence over legacy behavior + return getDisplayName(file, relativizeRootPaths); + } + return getDisplayNameLegacy(file, legacyRelativizeRoots); } /** * Return the textfile's display name. - * test only + * + *

    package private for test only

    */ - static String getDisplayName(Path file, List relativizeRoots) { + static String getDisplayNameLegacy(Path file, List relativizeRoots) { String fileName = file.toString(); - if ("jar".equals(file.toUri().getScheme())) { - fileName = new File(URI.create(file.toUri().getSchemeSpecificPart()).getPath()).toString(); - } for (String root : relativizeRoots) { if (file.startsWith(root)) { if (fileName.startsWith(File.separator, root.length())) { @@ -279,6 +293,46 @@ public final class FileCollector implements AutoCloseable { return fileName; } + /** + * Return the textfile's display name. Takes the shortest path we + * can construct from the relativize roots. + * + *

    package private for test only

    + */ + static String getDisplayName(Path file, List relativizeRoots) { + Path best = file; + for (Path root : relativizeRoots) { + Path candidate; + if (isFileSystemRoot(root)) { + // Absolutize the path. Since the relativize roots are + // sorted by ascending length, this should be the first in the list + // (so another root can override it). + best = file.toAbsolutePath(); + continue; + } else { + if (!root.getFileSystem().equals(file.getFileSystem())) { + // maybe the file is in a zip + root = file.getFileSystem().getPath(root.toString()); // SUPPRESS CHECKSTYLE ModifiedControlVariable + } + if (root.isAbsolute() != file.isAbsolute()) { // this causes IllegalArgumentException + root = root.toAbsolutePath(); // SUPPRESS CHECKSTYLE ModifiedControlVariable + file = file.toAbsolutePath(); + } + candidate = root.relativize(file); + } + // take the shortest path. + if (candidate.getNameCount() < best.getNameCount()) { + best = candidate; + } + } + return best.toString(); + } + + /** Return whether the path is the root path (/). */ + private static boolean isFileSystemRoot(Path root) { + return root.isAbsolute() && root.getNameCount() == 0; + } + /** * Add a directory recursively using {@link #addFile(Path)} on @@ -293,7 +347,7 @@ public final class FileCollector implements AutoCloseable { reporter.error("Not a directory {0}", dir); return false; } - Files.walkFileTree(dir, new SimpleFileVisitor() { + Files.walkFileTree(dir, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { if (attrs.isRegularFile()) { @@ -328,7 +382,10 @@ public final class FileCollector implements AutoCloseable { * it can be explored with the {@link Path} API. You can then call * {@link #addFile(Path)} and such. The zip file is registered as * a resource to close at the end of analysis. + * + * @deprecated Use {@link #addZipFileWithContent(Path)} instead. */ + @Deprecated public FileSystem addZipFile(Path zipFile) { if (!Files.isRegularFile(zipFile)) { throw new IllegalArgumentException("Not a regular file: " + zipFile); @@ -344,6 +401,60 @@ public final class FileCollector implements AutoCloseable { } } + /** + * Opens a zip file and adds all files of the zip file to the list + * of files to be processed. + * + *

    The zip file is registered as a resource to close at the end of analysis.

    + * + * @return True if the zip file including its content has been added without errors + */ + @Experimental + public boolean addZipFileWithContent(Path zipFile) throws IOException { + if (!Files.isRegularFile(zipFile)) { + throw new IllegalArgumentException("Not a regular file: " + zipFile); + } + URI zipUri = URI.create("jar:" + zipFile.toUri()); + FileSystem fs; + boolean isNewFileSystem = false; + try { + // find an existing file system, may fail + fs = FileSystems.getFileSystem(zipUri); + } catch (FileSystemNotFoundException ignored) { + // if it fails, try to create it. + try { + fs = FileSystems.newFileSystem(zipUri, Collections.emptyMap()); + isNewFileSystem = true; + } catch (ProviderNotFoundException | IOException e) { + reporter.errorEx("Cannot open zip file " + zipFile, e); + return false; + } + } + try (FileCollector zipCollector = newZipCollector(zipFile)) { + for (Path zipRoot : fs.getRootDirectories()) { + zipCollector.addFileOrDirectory(zipRoot); + } + this.absorb(zipCollector); + if (isNewFileSystem) { + resourcesToClose.add(fs); + } + + } catch (IOException ioe) { + reporter.errorEx("Error reading zip file " + zipFile + ", will be skipped", ioe); + fs.close(); + return false; + } + return true; + } + + + /** A collector that prefixes the display name of the files it will contain with the path of the zip. */ + @Experimental + private FileCollector newZipCollector(Path zipFilePath) { + String zipDisplayName = getDisplayName(zipFilePath); + return new FileCollector(discoverer, reporter, zipDisplayName); + } + // configuration /** @@ -363,12 +474,37 @@ public final class FileCollector implements AutoCloseable { * will have a path id of {@code /tmp/src/main/java/org/foo.java}, and a * display name of {@code main/java/org/foo.java}. * - * This only matters for files added from a {@link Path} object. + *

    This only matters for files added from a {@link Path} object. * * @param prefix Prefix to relativize (if a directory, include a trailing slash) + * + * @deprecated Use {@link #relativizeWith(Path)} */ + @Deprecated public void relativizeWith(String prefix) { - this.relativizeRoots.add(Objects.requireNonNull(prefix)); + this.legacyRelativizeRoots.add(Objects.requireNonNull(prefix)); + } + + /** + * Add a prefix that is used to relativize file paths as their display name. + * For instance, when adding a file {@code /tmp/src/main/java/org/foo.java}, + * and relativizing with {@code /tmp/src/}, the registered {@link TextFile} + * will have a path id of {@code /tmp/src/main/java/org/foo.java}, and a + * display name of {@code main/java/org/foo.java}. + * + *

    This only matters for files added from a {@link Path} object. + * + * @param path Path with which to relativize + */ + public void relativizeWith(Path path) { + this.relativizeRootPaths.add(Objects.requireNonNull(path)); + Collections.sort(relativizeRootPaths, new Comparator() { + @Override + public int compare(Path o1, Path o2) { + int lengthCmp = Integer.compare(o1.getNameCount(), o2.getNameCount()); + return lengthCmp == 0 ? o1.compareTo(o2) : lengthCmp; + } + }); } // filtering @@ -387,6 +523,17 @@ public final class FileCollector implements AutoCloseable { } } + /** + * Add all files collected in the other collector into this one. + * Transfers resources to close as well. The parameter is left empty. + */ + public void absorb(FileCollector otherCollector) { + this.allFilesToProcess.addAll(otherCollector.allFilesToProcess); + this.resourcesToClose.addAll(otherCollector.resourcesToClose); + otherCollector.allFilesToProcess.clear(); + otherCollector.resourcesToClose.clear(); + } + /** * Exclude all collected files whose language is not part of the given * collection. @@ -402,6 +549,7 @@ public final class FileCollector implements AutoCloseable { } } + @Override public String toString() { return "FileCollector{filesToProcess=" + allFilesToProcess + '}'; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/document/NioTextFile.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/document/NioTextFile.java index b6b74edaf3..1b51bd59bb 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/document/NioTextFile.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/document/NioTextFile.java @@ -5,16 +5,13 @@ package net.sourceforge.pmd.lang.document; import java.io.BufferedWriter; -import java.io.File; import java.io.IOException; -import java.net.URI; import java.nio.charset.Charset; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; import net.sourceforge.pmd.internal.util.AssertionUtil; import net.sourceforge.pmd.internal.util.BaseCloseable; @@ -28,18 +25,19 @@ class NioTextFile extends BaseCloseable implements TextFile { private final Path path; private final Charset charset; private final LanguageVersion languageVersion; - private final @Nullable String displayName; + private final String displayName; private final String pathId; private boolean readOnly; NioTextFile(Path path, Charset charset, LanguageVersion languageVersion, - @Nullable String displayName, + String displayName, boolean readOnly) { AssertionUtil.requireParamNotNull("path", path); AssertionUtil.requireParamNotNull("charset", charset); AssertionUtil.requireParamNotNull("language version", languageVersion); + AssertionUtil.requireParamNotNull("display name", displayName); this.displayName = displayName; this.readOnly = readOnly; @@ -57,13 +55,7 @@ class NioTextFile extends BaseCloseable implements TextFile { @Override public @NonNull String getDisplayName() { - if (displayName != null) { - return displayName; - } - if ("jar".equals(path.toUri().getScheme())) { - return new File(URI.create(path.toUri().getSchemeSpecificPart()).getPath()).toString(); - } - return path.toString(); + return displayName; } @Override diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/document/TextFile.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/document/TextFile.java index 76a715cd66..6e0af1e1b4 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/document/TextFile.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/document/TextFile.java @@ -160,7 +160,9 @@ public interface TextFile extends Closeable { * @throws NullPointerException If any parameter is null */ static TextFile forPath(Path path, Charset charset, LanguageVersion languageVersion) { - return builderForPath(path, charset, languageVersion).build(); + return builderForPath(path, charset, languageVersion) + .withDisplayName(path.toString()) + .build(); } /** @@ -194,7 +196,9 @@ public interface TextFile extends Closeable { * @throws NullPointerException If any parameter is null */ static TextFile forCharSeq(CharSequence charseq, String pathId, LanguageVersion languageVersion) { - return builderForCharSeq(charseq, pathId, languageVersion).build(); + return builderForCharSeq(charseq, pathId, languageVersion) + .withDisplayName(pathId) + .build(); } /** @@ -226,7 +230,9 @@ public interface TextFile extends Closeable { * @throws NullPointerException If any parameter is null */ static TextFile forReader(Reader reader, String pathId, LanguageVersion languageVersion) { - return builderForReader(reader, pathId, languageVersion).build(); + return builderForReader(reader, pathId, languageVersion) + .withDisplayName(pathId) + .build(); } /** @@ -277,7 +283,7 @@ public interface TextFile extends Closeable { @Override public @NonNull String getDisplayName() { - return ds.getNiceFileName(config.isReportShortNames(), shortPaths); + return ds.getNiceFileName(false, shortPaths); } @Override diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/datasource/FileDataSource.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/datasource/FileDataSource.java index 81a2f66cad..2e6688d3ec 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/datasource/FileDataSource.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/datasource/FileDataSource.java @@ -15,10 +15,9 @@ import net.sourceforge.pmd.util.datasource.internal.PathDataSource; public class FileDataSource extends PathDataSource { /** - * @param file - * the file to read + * @param file the file to read */ public FileDataSource(File file) { - super(file.toPath()); + super(file.toPath(), null); } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/datasource/internal/PathDataSource.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/datasource/internal/PathDataSource.java index 1fec19b8fd..4aced4a853 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/datasource/internal/PathDataSource.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/datasource/internal/PathDataSource.java @@ -21,13 +21,23 @@ import net.sourceforge.pmd.internal.util.ShortFilenameUtil; */ //TODO This class (and all other DataSources) can be probably removed with PMD 7 in favor of TextFile public class PathDataSource extends AbstractDataSource { + private final String displayName; + private final Path path; /** * @param path the file to read */ public PathDataSource(Path path) { + this(path, null); + } + + /** + * @param path the file to read + */ + public PathDataSource(Path path, String displayName) { this.path = path; + this.displayName = displayName; } @Override @@ -56,6 +66,9 @@ public class PathDataSource extends AbstractDataSource { } private String glomName(boolean shortNames, String inputPaths) { + if (displayName != null) { + return displayName; + } if (shortNames) { if (inputPaths != null) { List inputPathPrefixes = Arrays.asList(inputPaths.split(",")); diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/PmdConfigurationTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/PmdConfigurationTest.java index 772e481c9c..127fea8da0 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/PmdConfigurationTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/PmdConfigurationTest.java @@ -167,14 +167,6 @@ class PmdConfigurationTest { assertEquals(expected, configuration.getInputPathList(), "Changed input paths"); } - @Test - void testReportShortNames() { - PMDConfiguration configuration = new PMDConfiguration(); - assertEquals(false, configuration.isReportShortNames(), "Default report short names"); - configuration.setReportShortNames(true); - assertEquals(true, configuration.isReportShortNames(), "Changed report short names"); - } - @Test void testReportFormat() { PMDConfiguration configuration = new PMDConfiguration(); diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/cli/PMDFilelistTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/cli/PMDFilelistTest.java index 9f35ca4ec5..ddce7c5cf2 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/cli/PMDFilelistTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/cli/PMDFilelistTest.java @@ -6,6 +6,7 @@ package net.sourceforge.pmd.cli; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; import java.nio.file.Paths; @@ -15,14 +16,17 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.junit.jupiter.api.Test; import net.sourceforge.pmd.PMDConfiguration; +import net.sourceforge.pmd.PmdAnalysis; import net.sourceforge.pmd.internal.util.FileCollectionUtil; import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.LanguageVersionDiscoverer; import net.sourceforge.pmd.lang.document.FileCollector; import net.sourceforge.pmd.lang.document.TextFile; +import net.sourceforge.pmd.util.IOUtil; import net.sourceforge.pmd.util.log.internal.NoopReporter; class PMDFilelistTest { + private static final String RESOURCE_PREFIX = "src/test/resources/net/sourceforge/pmd/cli/"; private @NonNull FileCollector newCollector() { return FileCollector.newCollector(new LanguageVersionDiscoverer(LanguageRegistry.PMD), new NoopReporter()); @@ -36,7 +40,7 @@ class PMDFilelistTest { void testGetApplicableFiles() { FileCollector collector = newCollector(); - collectFileList(collector, "src/test/resources/net/sourceforge/pmd/cli/filelist.txt"); + collectFileList(collector, RESOURCE_PREFIX + "filelist.txt"); List applicableFiles = collector.getCollectedFiles(); assertThat(applicableFiles, hasSize(2)); @@ -48,13 +52,13 @@ class PMDFilelistTest { void testGetApplicableFilesMultipleLines() { FileCollector collector = newCollector(); - collectFileList(collector, "src/test/resources/net/sourceforge/pmd/cli/filelist2.txt"); + collectFileList(collector, RESOURCE_PREFIX + "filelist2.txt"); List applicableFiles = collector.getCollectedFiles(); - assertThat(applicableFiles, hasSize(3)); + // note: the file has 3 entries, but one is duplicated, resulting in 2 individual files + assertThat(applicableFiles, hasSize(2)); assertThat(applicableFiles.get(0).getPathId(), endsWith("anotherfile.dummy")); assertThat(applicableFiles.get(1).getPathId(), endsWith("somefile.dummy")); - assertThat(applicableFiles.get(2).getPathId(), endsWith("somefile.dummy")); } @Test @@ -62,8 +66,8 @@ class PMDFilelistTest { FileCollector collector = newCollector(); PMDConfiguration configuration = new PMDConfiguration(); - configuration.setInputFilePath("src/test/resources/net/sourceforge/pmd/cli/filelist3.txt"); - configuration.setIgnoreFilePath("src/test/resources/net/sourceforge/pmd/cli/ignorelist.txt"); + configuration.setInputFilePath(RESOURCE_PREFIX + "filelist3.txt"); + configuration.setIgnoreFilePath(RESOURCE_PREFIX + "ignorelist.txt"); FileCollectionUtil.collectFiles(configuration, collector); List applicableFiles = collector.getCollectedFiles(); @@ -73,11 +77,67 @@ class PMDFilelistTest { } @Test - void testGetApplicableFilesWithDirAndIgnores() { + void testRelativizeWith() { + PMDConfiguration conf = new PMDConfiguration(); + conf.setInputFilePath(Paths.get(RESOURCE_PREFIX + "filelist2.txt")); + conf.addRelativizeRoot(Paths.get("src/test/resources")); + try (PmdAnalysis pmd = PmdAnalysis.create(conf)) { + List files = pmd.files().getCollectedFiles(); + assertThat(files, hasSize(2)); + assertThat(files.get(0).getDisplayName(), equalTo(IOUtil.normalizePath("net/sourceforge/pmd/cli/src/anotherfile.dummy"))); + assertThat(files.get(1).getDisplayName(), equalTo(IOUtil.normalizePath("net/sourceforge/pmd/cli/src/somefile.dummy"))); + } + } + @Test + void testRelativizeWithOtherDir() { + PMDConfiguration conf = new PMDConfiguration(); + conf.setInputFilePath(Paths.get(RESOURCE_PREFIX + "filelist4.txt")); + conf.addRelativizeRoot(Paths.get(RESOURCE_PREFIX + "src")); + try (PmdAnalysis pmd = PmdAnalysis.create(conf)) { + List files = pmd.files().getCollectedFiles(); + assertThat(files, hasSize(3)); + assertThat(files.get(0).getDisplayName(), equalTo(".." + IOUtil.normalizePath("/otherSrc/somefile.dummy"))); + assertThat(files.get(1).getDisplayName(), equalTo("anotherfile.dummy")); + assertThat(files.get(2).getDisplayName(), equalTo("somefile.dummy")); + } + } + + @Test + void testRelativizeWithSeveralDirs() { + PMDConfiguration conf = new PMDConfiguration(); + conf.setInputFilePath(Paths.get(RESOURCE_PREFIX + "filelist4.txt")); + conf.addRelativizeRoot(Paths.get(RESOURCE_PREFIX + "src")); + conf.addRelativizeRoot(Paths.get(RESOURCE_PREFIX + "otherSrc")); + try (PmdAnalysis pmd = PmdAnalysis.create(conf)) { + List files = pmd.files().getCollectedFiles(); + assertThat(files, hasSize(3)); + assertThat(files.get(0).getDisplayName(), equalTo("somefile.dummy")); + assertThat(files.get(1).getDisplayName(), equalTo("anotherfile.dummy")); + assertThat(files.get(2).getDisplayName(), equalTo("somefile.dummy")); + } + } + + @Test + void testUseAbsolutePaths() { + PMDConfiguration conf = new PMDConfiguration(); + conf.setInputFilePath(Paths.get(RESOURCE_PREFIX + "filelist4.txt")); + conf.addRelativizeRoot(Paths.get(RESOURCE_PREFIX).toAbsolutePath().getRoot()); + try (PmdAnalysis pmd = PmdAnalysis.create(conf)) { + List files = pmd.files().getCollectedFiles(); + assertThat(files, hasSize(3)); + assertThat(files.get(0).getDisplayName(), equalTo(Paths.get(RESOURCE_PREFIX, "otherSrc", "somefile.dummy").toAbsolutePath().toString())); + assertThat(files.get(1).getDisplayName(), equalTo(Paths.get(RESOURCE_PREFIX, "src", "anotherfile.dummy").toAbsolutePath().toString())); + assertThat(files.get(2).getDisplayName(), equalTo(Paths.get(RESOURCE_PREFIX, "src", "somefile.dummy").toAbsolutePath().toString())); + } + } + + + @Test + void testGetApplicableFilesWithDirAndIgnores() { PMDConfiguration configuration = new PMDConfiguration(); - configuration.setInputPaths("src/test/resources/net/sourceforge/pmd/cli/src"); - configuration.setIgnoreFilePath("src/test/resources/net/sourceforge/pmd/cli/ignorelist.txt"); + configuration.setInputPaths(RESOURCE_PREFIX + "src"); + configuration.setIgnoreFilePath(RESOURCE_PREFIX + "ignorelist.txt"); FileCollector collector = newCollector(); FileCollectionUtil.collectFiles(configuration, collector); diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/cli/ZipFileTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/cli/ZipFileTest.java new file mode 100644 index 0000000000..e260f65f9d --- /dev/null +++ b/pmd-core/src/test/java/net/sourceforge/pmd/cli/ZipFileTest.java @@ -0,0 +1,73 @@ +/** + * 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.equalTo; +import static org.hamcrest.Matchers.hasSize; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; + +import org.junit.Test; + +import net.sourceforge.pmd.PMDConfiguration; +import net.sourceforge.pmd.PmdAnalysis; +import net.sourceforge.pmd.lang.document.TextFile; +import net.sourceforge.pmd.util.IOUtil; + +public class ZipFileTest { + + private static final String ZIP_PATH = "src/test/resources/net/sourceforge/pmd/cli/zipWithSources.zip"; + private final Path zipPath = Paths.get(ZIP_PATH); + + @Test + public void testZipFile() { + PMDConfiguration conf = new PMDConfiguration(); + conf.addInputPath(zipPath); + // no relativizeRoot paths configured -> we use the relative path + String reportPath = zipPath.toString(); + try (PmdAnalysis pmd = PmdAnalysis.create(conf)) { + List files = pmd.files().getCollectedFiles(); + assertThat(files, hasSize(3)); + assertThat(files.get(0).getDisplayName(), equalTo(reportPath + "!/otherSrc/somefile.dummy")); + assertThat(files.get(1).getDisplayName(), equalTo(reportPath + "!/src/somefile.dummy")); + assertThat(files.get(2).getDisplayName(), equalTo(reportPath + "!/src/somefile1.dummy")); + } + } + + @Test + public void testZipFileRelativizeWith() { + PMDConfiguration conf = new PMDConfiguration(); + conf.addInputPath(zipPath); + conf.addRelativizeRoot(Paths.get("src/test/resources")); + try (PmdAnalysis pmd = PmdAnalysis.create(conf)) { + List files = pmd.files().getCollectedFiles(); + assertThat(files, hasSize(3)); + String baseZipPath = IOUtil.normalizePath("net/sourceforge/pmd/cli/zipWithSources.zip"); + assertThat(files.get(0).getDisplayName(), equalTo(baseZipPath + "!/otherSrc/somefile.dummy")); + assertThat(files.get(1).getDisplayName(), equalTo(baseZipPath + "!/src/somefile.dummy")); + assertThat(files.get(2).getDisplayName(), equalTo(baseZipPath + "!/src/somefile1.dummy")); + } + } + + @Test + public void testZipFileRelativizeWithRoot() { + PMDConfiguration conf = new PMDConfiguration(); + conf.addInputPath(zipPath); + // this configures "/" as the relativizeRoot -> result are absolute paths + conf.addRelativizeRoot(zipPath.toAbsolutePath().getRoot()); + String reportPath = zipPath.toAbsolutePath().toString(); + try (PmdAnalysis pmd = PmdAnalysis.create(conf)) { + List files = pmd.files().getCollectedFiles(); + assertThat(files, hasSize(3)); + assertThat(files.get(0).getDisplayName(), equalTo(reportPath + "!/otherSrc/somefile.dummy")); + assertThat(files.get(1).getDisplayName(), equalTo(reportPath + "!/src/somefile.dummy")); + assertThat(files.get(2).getDisplayName(), equalTo(reportPath + "!/src/somefile1.dummy")); + } + } + +} diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/document/FileCollectorTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/document/FileCollectorTest.java index db4eef2608..f9879c92b3 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/document/FileCollectorTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/document/FileCollectorTest.java @@ -95,8 +95,14 @@ class FileCollectorTest { } @Test - void testRelativize() throws IOException { - String displayName = FileCollector.getDisplayName(Paths.get("a", "b", "c"), listOf(Paths.get("a").toString())); + void testRelativizeLegacy() { + String displayName = FileCollector.getDisplayNameLegacy(Paths.get("a", "b", "c"), listOf(Paths.get("a").toString())); + assertEquals(displayName, Paths.get("b", "c").toString()); + } + + @Test + void testRelativize() { + String displayName = FileCollector.getDisplayName(Paths.get("a", "b", "c"), listOf(Paths.get("a"))); assertEquals(displayName, Paths.get("b", "c").toString()); } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/document/NioTextFileTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/document/NioTextFileTest.java index 597d09f6d8..6638422a3a 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/document/NioTextFileTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/document/NioTextFileTest.java @@ -7,22 +7,17 @@ package net.sourceforge.pmd.lang.document; import static org.junit.Assert.assertEquals; import java.io.FileOutputStream; -import java.net.URI; import java.nio.charset.StandardCharsets; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; import java.nio.file.Path; -import java.util.Collections; +import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import net.sourceforge.pmd.lang.DummyLanguageModule; import net.sourceforge.pmd.lang.LanguageRegistry; -import net.sourceforge.pmd.lang.LanguageVersion; -import net.sourceforge.pmd.util.IOUtil; +import net.sourceforge.pmd.lang.LanguageVersionDiscoverer; class NioTextFileTest { @@ -38,12 +33,14 @@ class NioTextFileTest { zipOutputStream.write("dummy text".getBytes(StandardCharsets.UTF_8)); zipOutputStream.closeEntry(); } - try (FileSystem fileSystem = FileSystems.newFileSystem(URI.create("jar:" + zipArchive.toUri()), Collections.emptyMap())) { - Path path = fileSystem.getPath("path/inside/someSource.dummy"); - LanguageRegistry.PMD.getLanguageById("dummy"); - LanguageVersion languageVersion = DummyLanguageModule.getInstance().getDefaultVersion(); - TextFile textFile = TextFile.builderForPath(path, StandardCharsets.UTF_8, languageVersion).build(); - assertEquals(zipArchive.toAbsolutePath() + "!" + IOUtil.normalizePath("/path/inside/someSource.dummy"), + + LanguageVersionDiscoverer discoverer = new LanguageVersionDiscoverer(LanguageRegistry.PMD, null); + try (FileCollector collector = FileCollector.newCollector(discoverer, new TestMessageReporter())) { + collector.addZipFileWithContent(zipArchive); + List collectedFiles = collector.getCollectedFiles(); + assertEquals(1, collectedFiles.size()); + TextFile textFile = collectedFiles.get(0); + assertEquals(zipArchive.toAbsolutePath() + "!/path/inside/someSource.dummy", textFile.getDisplayName()); } } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/document/TextFilesTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/document/TextFilesTest.java index 16dbe22bd3..ed0721292d 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/document/TextFilesTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/document/TextFilesTest.java @@ -144,6 +144,7 @@ class TextFilesTest { void testNioFileExplicitReadOnly() throws IOException { Path file = makeTmpFile(StandardCharsets.UTF_8, "some content"); try (TextFile tf = TextFile.builderForPath(file, StandardCharsets.UTF_8, dummyVersion()) + .withDisplayName(file.toString()) .asReadOnly().build()) { assertTrue(tf.isReadOnly(), "readonly"); diff --git a/pmd-core/src/test/resources/net/sourceforge/pmd/cli/filelist4.txt b/pmd-core/src/test/resources/net/sourceforge/pmd/cli/filelist4.txt new file mode 100644 index 0000000000..9f7ddef003 --- /dev/null +++ b/pmd-core/src/test/resources/net/sourceforge/pmd/cli/filelist4.txt @@ -0,0 +1,4 @@ +src/test/resources/net/sourceforge/pmd/cli/src/somefile.dummy, +src/test/resources/net/sourceforge/pmd/cli/otherSrc/somefile.dummy, +src/test/resources/net/sourceforge/pmd/cli/src/anotherfile.dummy +src/test/resources/net/sourceforge/pmd/cli/src/somefile.dummy diff --git a/pmd-core/src/test/resources/net/sourceforge/pmd/cli/otherSrc/somefile.dummy b/pmd-core/src/test/resources/net/sourceforge/pmd/cli/otherSrc/somefile.dummy new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pmd-core/src/test/resources/net/sourceforge/pmd/cli/zipWithSources.zip b/pmd-core/src/test/resources/net/sourceforge/pmd/cli/zipWithSources.zip new file mode 100644 index 0000000000..877cbdbe2c Binary files /dev/null and b/pmd-core/src/test/resources/net/sourceforge/pmd/cli/zipWithSources.zip differ