languageVersions;
/**
* The path to the report page.
*/
Path reportFile;
/**
* The report format.
*/
String reportFormat = "text";
/**
* The show suppressed flag.
*/
boolean showSuppressed;
/**
* THe suppressed marker.
*/
String suppressedMarker = "NOPMD";
/**
* The number of threads.
*/
int threads = 1;
/**
* The project reference.
*/
private BaseProject project;
/**
* Adds paths to source files, or directories containing source files to analyze.
*
* @see #inputPaths(Path...)
*/
public PmdOperation addInputPath(Path... inputPath) {
inputPaths.addAll(List.of(inputPath));
return this;
}
/**
* Adds several paths to shorten paths that are output in the report.
*
* @see #addRelativizeRoot(Path...)
*/
public PmdOperation addRelativizeRoot(Path... relativeRoot) {
this.relativizeRoots.addAll(List.of(relativeRoot));
return this;
}
/**
* Adds new rule set paths.
*
* The built-in rule set paths are:
*
* - {@code rulesets/java/quickstart.xml}
* - {@code category/java/bestpractices.xml}
* - {@code category/java/codestyle.xml}
* - {@code category/java/design.xml}
* - {@code category/java/documentation.xml}
* - {@code category/java/errorprone.xml}
* - {@code category/java/multithreading.xml}
* - {@code category/java/performance.xml}
* - {@code category/java/security.xml}
*
*
* @see #ruleSets(String...)
*/
public PmdOperation addRuleSet(String... ruleSet) {
ruleSets.addAll(List.of(ruleSet));
return this;
}
/**
* Sets the location of the cache file for incremental analysis.
*/
public PmdOperation cache(Path cache) {
this.cache = cache;
return this;
}
/**
* Enables or disables debug logging mode.
*/
public PmdOperation debug(boolean debug) {
this.debug = debug;
return this;
}
/**
* Sets the default language to be used for all input files.
*/
public PmdOperation defaultLanguage(LanguageVersion... languageVersion) {
this.languageVersions.addAll(List.of(languageVersion));
return this;
}
/**
* Specifies the character set encoding of the source code files. The default is {@code UTF-8}.
*
* The valid values are the standard character sets of {@link java.nio.charset.Charset Charset}.
*/
public PmdOperation encoding(String encoding) {
this.encoding = encoding;
return this;
}
/**
* Performs the PMD code analysis operation.
*
* @throws Exception when an exception occurs during the execution
*/
@Override
public void execute() throws Exception {
if (project == null) {
throw new IllegalArgumentException("ERROR: project required.");
}
var commandName = project.getCurrentCommandName();
performPmdAnalysis(commandName, initConfiguration(commandName));
}
/**
* Sets whether the build will continue on warnings.
*/
public PmdOperation failOnViolation(boolean failOnViolation) {
this.failOnViolation = failOnViolation;
return this;
}
/**
* Forces a language to be used for all input files, irrespective of file names.
*/
public PmdOperation forceVersion(LanguageVersion languageVersion) {
this.forcedLanguageVersion = languageVersion;
return this;
}
/**
* Configures a PMD operation from a {@link BaseProject}.
*
*
* The defaults are:
*
* - cache={@code build/pmd/pmd-cache}
* - encoding={@code UTF-9}
* - incrementAnalysis={@code true}
* - inputPaths={@code [src/main, src/test]}
* - reportFile={@code build/pmd/pmd-report-txt}
* - reportFormat={@code text}
* - rulePriority={@code LOW}
* - ruleSets={@code [rulesets/java/quickstart.xml]}
* - suppressedMarker={@code NOPMD}
*
*/
public PmdOperation fromProject(BaseProject project) {
this.project = project;
inputPaths.add(project.srcMainDirectory().toPath());
inputPaths.add(project.srcTestDirectory().toPath());
ruleSets.add(RULE_SET_DEFAULT);
return this;
}
/**
* Sets the path to the file containing a list of files to ignore, one path per line.
*/
public PmdOperation ignoreFile(Path ignoreFile) {
this.ignoreFile = ignoreFile;
return this;
}
/**
* Enables or disables incremental analysis.
*/
public PmdOperation incrementalAnalysis(boolean incrementalAnalysis) {
this.incrementalAnalysis = incrementalAnalysis;
return this;
}
/**
* Creates a new initialized configuration.
*/
public PMDConfiguration initConfiguration(String commandName) {
PMDConfiguration config = new PMDConfiguration();
if (cache == null && project != null && incrementalAnalysis) {
config.setAnalysisCacheLocation(
Paths.get(project.buildDirectory().getPath(), PMD_DIR, PMD_DIR + "-cache").toFile().getAbsolutePath());
} else if (cache != null) {
config.setAnalysisCacheLocation(cache.toFile().getAbsolutePath());
}
config.setDebug(debug);
config.setFailOnViolation(failOnViolation);
if (languageVersions != null) {
config.setDefaultLanguageVersions(languageVersions);
}
if (forcedLanguageVersion != null) {
config.setForceLanguageVersion(forcedLanguageVersion);
}
if (ignoreFile != null) {
config.setIgnoreFilePath(ignoreFile);
}
config.setIgnoreIncrementalAnalysis(!incrementalAnalysis);
if (inputPaths.isEmpty()) {
throw new IllegalArgumentException(commandName + ": InputPaths required.");
} else {
config.setInputPathList(inputPaths);
}
if (inputUri != null) {
config.setInputUri(inputUri);
}
config.setMinimumPriority(rulePriority);
if (project != null) {
config.setReportFile(Objects.requireNonNullElseGet(reportFile,
() -> Paths.get(project.buildDirectory().getPath(), PMD_DIR, PMD_DIR + "-report." + reportFormat)));
} else {
config.setReportFile(reportFile);
}
config.addRelativizeRoots(relativizeRoots);
config.setReportFormat(reportFormat);
config.setRuleSets(ruleSets);
config.setShowSuppressedViolations(showSuppressed);
config.setSourceEncoding(encoding);
config.setSuppressMarker(suppressedMarker);
config.setThreads(threads);
return config;
}
/**
* Sets the to source files, or directories containing source files to analyze.
* Previously set paths will be disregarded.
*
* @see #addInputPath(Path...)
*/
public PmdOperation inputPaths(Path... inputPath) {
inputPaths.clear();
inputPaths.addAll(List.of(inputPath));
return this;
}
/**
* Performs the PMD analysis with the given config.
*/
public int performPmdAnalysis(String commandName, PMDConfiguration config) throws RuntimeException {
var pmd = PmdAnalysis.create(config);
var report = pmd.performAnalysisAndCollectReport();
if (LOGGER.isLoggable(Level.INFO)) {
LOGGER.log(Level.INFO, "[{0}] inputPaths{1}", new Object[]{commandName, inputPaths});
LOGGER.log(Level.INFO, "[{0}] ruleSets{1}", new Object[]{commandName, ruleSets});
}
var numErrors = report.getViolations().size();
if (numErrors > 0) {
var msg = String.format(
"[%s] %d rule violations were found. See the report at: %s", commandName, numErrors,
config.getReportFilePath().toUri());
for (var v : report.getViolations()) {
if (LOGGER.isLoggable(Level.WARNING)) {
LOGGER.log(Level.WARNING, "[{0}] {1}:{2}:\n\t{3} ({4})\n\t\t--> {5}",
new Object[]{commandName, Paths.get(v.getFilename()).toUri(), v.getBeginLine(),
v.getRule().getName(),
v.getRule().getExternalInfoUrl() //TODO bug in PMD?
.replace("${pmd.website.baseurl}",
"https://docs.pmd-code.org/pmd-doc-7.0.0-rc1"),
v.getDescription()});
}
}
if (config.isFailOnViolation()) {
throw new RuntimeException(msg); // NOPMD
} else {
if (LOGGER.isLoggable(Level.WARNING)) {
LOGGER.warning(msg);
}
}
} else {
var rules = pmd.getRulesets();
if (!rules.isEmpty()) {
int count = 0;
for (var rule : rules) {
count += rule.getRules().size();
}
if (LOGGER.isLoggable(Level.INFO)) {
LOGGER.info(String.format("[%s] %d rules were checked.", commandName, count));
}
}
}
return numErrors;
}
/**
* Sets several paths to shorten paths that are output in the report. Previous relative paths will be disregarded.
*
* @see #addRelativizeRoot(Path...)
*/
public PmdOperation relativizeRoots(Path... relativeRoot) {
this.relativizeRoots.clear();
this.relativizeRoots.addAll(List.of(relativeRoot));
return this;
}
/**
* Sets the output format of the analysis report. The default is {@code text}.
*/
public PmdOperation reportFormat(String reportFormat) {
this.reportFormat = reportFormat;
return this;
}
/**
* Sets the rule set path(s), disregarding any previously set paths.
*
* The built-in rule set paths are:
*
* - {@code rulesets/java/quickstart.xml}
* - {@code category/java/bestpractices.xml}
* - {@code category/java/codestyle.xml}
* - {@code category/java/design.xml}
* - {@code category/java/documentation.xml}
* - {@code category/java/errorprone.xml}
* - {@code category/java/multithreading.xml}
* - {@code category/java/performance.xml}
* - {@code category/java/security.xml}
*
*
* @see #addRuleSet(String...)
*/
public PmdOperation ruleSets(String... ruleSet) {
ruleSets.clear();
ruleSets.addAll(Arrays.asList(ruleSet));
return this;
}
/**
* Enables or disables adding the suppressed rule violations to the report.
*/
public PmdOperation showSuppressed(boolean showSuppressed) {
this.showSuppressed = showSuppressed;
return this;
}
/**
* Specifies the comment token that marks lines which should be ignored. The default is {@code NOPMD}.
*/
public PmdOperation suppressedMarker(String suppressedMarker) {
this.suppressedMarker = suppressedMarker;
return this;
}
/**
* Sets the number of threads to be used. The default is {code 1}.
*/
public PmdOperation threads(int threads) {
this.threads = threads;
return this;
}
/**
* Sets the input URI to process for source code objects.
*/
public PmdOperation uri(URI inputUri) {
this.inputUri = inputUri;
return this;
}
}