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