Add displaying configuration and processing errors

This commit is contained in:
Erik C. Thauvin 2025-05-31 01:45:44 -07:00
parent 71e9ab7f98
commit 630f3fa8d2
Signed by: erik
GPG key ID: 776702A6A2DA330E
3 changed files with 208 additions and 131 deletions

View file

@ -0,0 +1,47 @@
/*
* Copyright 2023-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package rife.bld.extension;
/**
* Represents the results of a PMD analysis, containing various counts
* related to violations, errors, and rules.
*
* @param violations The number of violations found during the analysis
* @param suppressedViolations The number of suppressed violations found during the analysis
* @param errors The number of errors returned during the analysis
* @param processingErrors The number of processing errors returned during the analysis
* @param configurationErrors The number of processing errors returning during the analysis
* @param rulesChecked The number of rules checked during the analysis
* @since 1.2.4
*/
public record PmdAnalysisResults(
int violations,
int suppressedViolations,
int errors,
int processingErrors,
int configurationErrors,
int rulesChecked) {
/**
* Checks if the analysis results indicate no errors of any type.
*
* @return {@code true} if there are no errors, {@code false} otherwise.
*/
public boolean hasNoErrors() {
return errors == 0 && processingErrors == 0 && configurationErrors == 0;
}
}

View file

@ -20,6 +20,7 @@ import net.sourceforge.pmd.PMDConfiguration;
import net.sourceforge.pmd.PmdAnalysis;
import net.sourceforge.pmd.lang.LanguageVersion;
import net.sourceforge.pmd.lang.rule.RulePriority;
import net.sourceforge.pmd.reporting.Report;
import rife.bld.BaseProject;
import rife.bld.operations.AbstractOperation;
import rife.bld.operations.exceptions.ExitStatusException;
@ -41,105 +42,41 @@ import java.util.logging.Logger;
* @since 1.0
*/
public class PmdOperation extends AbstractOperation<PmdOperation> {
/**
* The default logger.
*/
public static final Logger LOGGER = Logger.getLogger(PmdOperation.class.getName());
/**
* The default rule set.
* <p>
* Set to: {@code rulesets/java/quickstart.xml}
*/
public static final String RULE_SET_DEFAULT = "rulesets/java/quickstart.xml";
private static final Logger LOGGER = Logger.getLogger(PmdOperation.class.getName());
private static final String PMD_DIR = "pmd";
/**
* The list of paths to exclude.
*/
private final Collection<Path> excludes_ = new ArrayList<>();
/**
* The input paths (source) list.
*/
private final Collection<Path> inputPaths_ = new ArrayList<>();
/**
* The relative roots paths.
*/
private final Collection<Path> relativizeRoots_ = new ArrayList<>();
/**
* The rule sets list.
*/
private final Collection<String> ruleSets_ = new ArrayList<>();
/**
* The cache location.
*/
private Path cache_;
/**
* The encoding.
*/
private boolean collectFilesRecursively_ = true;
private Charset encoding_ = StandardCharsets.UTF_8;
/**
* The fail on error toggle.
*/
private boolean failOnError_ = true;
/**
* The fail on violation toggle.
*/
private boolean failOnViolation_;
/**
* The forced language.
*/
private LanguageVersion forcedLanguageVersion_;
/**
* The path of the ignore file
*/
private Path ignoreFile_;
/**
* The include line number toggle.
*/
private boolean includeLineNumber_ = true;
/**
* The incremental analysis toggle.
*/
private boolean incrementalAnalysis_ = true;
/**
* The input URI.
*/
private URI inputUri_;
/**
* The default language version(s).
*/
private Collection<LanguageVersion> languageVersions_ = new ArrayList<>();
/**
* The classpath to prepend.
*/
private String prependClasspath_;
/**
* The project reference.
*/
private BaseProject project_;
/**
* The path to the report page.
*/
private Path reportFile_;
/**
* The report format.
*/
private String reportFormat_ = "text";
/**
* The report properties.
*/
private Properties reportProperties_;
/**
* The rule priority.
*/
private RulePriority rulePriority_ = RulePriority.LOW;
/**
* The show suppressed flag.
*/
private boolean showSuppressed_;
/**
* THe suppressed marker.
*/
private String suppressedMarker_ = "NOPMD";
/**
* The number of threads.
*/
private int threads_ = 1;
/**
@ -216,18 +153,18 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
}
/**
* Adds paths to source files, or directories containing source files to analyze.\
* Adds paths to source files or directories containing source files to analyze.\
*
* @param inputPath one or more paths
* @return this operation
* @see #inputPaths(Path...)
*/
public PmdOperation addInputPaths(Path... inputPath) {
return inputPaths(List.of(inputPath));
return addInputPaths(List.of(inputPath));
}
/**
* Adds paths to source files, or directories containing source files to analyze.
* Adds paths to source files or directories containing source files to analyze.
*
* @param inputPath one or more paths
* @return this operation
@ -238,7 +175,7 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
}
/**
* Adds paths to source files, or directories containing source files to analyze.
* Adds paths to source files or directories containing source files to analyze.
*
* @param inputPath one or more paths
* @return this operation
@ -249,7 +186,7 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
}
/**
* Adds paths to source files, or directories containing source files to analyze.
* Adds paths to source files or directories containing source files to analyze.
*
* @param inputPath a collection of input paths
* @return this operation
@ -261,7 +198,7 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
}
/**
* Adds paths to source files, or directories containing source files to analyze.
* Adds paths to source files or directories containing source files to analyze.
*
* @param inputPath a collection of input paths
* @return this operation
@ -272,7 +209,7 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
}
/**
* Adds paths to source files, or directories containing source files to analyze.
* Adds paths to source files or directories containing source files to analyze.
*
* @param inputPath a collection of input paths
* @return this operation
@ -379,7 +316,7 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
/**
* Sets the default language version to be used for all input files.
*
* @param languageVersion a collection language versions
* @param languageVersion a collection language version
* @return this operation
*/
public PmdOperation defaultLanguageVersions(Collection<LanguageVersion> languageVersion) {
@ -733,7 +670,7 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
}
/**
* Sets paths to source files, or directories containing source files to analyze.
* Sets paths to source files or directories containing source files to analyze.
*
* @param inputPath one or more paths
* @return this operation
@ -744,7 +681,7 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
}
/**
* Sets paths to source files, or directories containing source files to analyze.
* Sets paths to source files or directories containing source files to analyze.
* <p>
* Previous entries are disregarded.
*
@ -757,7 +694,7 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
}
/**
* Sets paths to source files, or directories containing source files to analyze.
* Sets paths to source files or directories containing source files to analyze.
* <p>
* Previous entries are disregarded.
*
@ -770,7 +707,7 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
}
/**
* Sets paths to source files, or directories containing source files to analyze.
* Sets paths to source files or directories containing source files to analyze.
* <p>
* Previous entries are disregarded.
*
@ -785,7 +722,7 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
}
/**
* Returns paths to source files, or directories containing source files to analyze.
* Returns paths to source files or directories containing source files to analyze.
*
* @return the input paths
*/
@ -794,7 +731,7 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
}
/**
* Sets paths to source files, or directories containing source files to analyze.
* Sets paths to source files or directories containing source files to analyze.
* <p>
* Previous entries are disregarded.
*
@ -807,7 +744,7 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
}
/**
* Sets paths to source files, or directories containing source files to analyze.
* Sets paths to source files or directories containing source files to analyze.
* <p>
* Previous entries are disregarded.
*
@ -832,7 +769,7 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
/**
* Sets the default language versions.
*
* @param languageVersions a collection language versions
* @param languageVersions a collection language version
* @return this operation
*/
public PmdOperation languageVersions(Collection<LanguageVersion> languageVersions) {
@ -867,10 +804,12 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
* @return the number of violations
* @throws ExitStatusException if an error occurs
*/
@SuppressWarnings({"PMD.CloseResource", "PMD.AvoidInstantiatingObjectsInLoops"})
public int performPmdAnalysis(String commandName, PMDConfiguration config) throws ExitStatusException {
@SuppressWarnings("PMD.CloseResource")
public PmdAnalysisResults performPmdAnalysis(String commandName, PMDConfiguration config)
throws ExitStatusException {
var pmd = PmdAnalysis.create(config);
var report = pmd.performAnalysisAndCollectReport();
if (LOGGER.isLoggable(Level.INFO) && !silent()) {
LOGGER.log(Level.INFO, "[{0}] inputPaths{1}", new Object[]{commandName, inputPaths_});
LOGGER.log(Level.INFO, "[{0}] ruleSets{1}", new Object[]{commandName, ruleSets_});
@ -878,52 +817,48 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
var numViolations = report.getViolations().size();
if (numViolations > 0) {
for (var v : report.getViolations()) {
if (LOGGER.isLoggable(Level.WARNING) && !silent()) {
final String msg;
if (includeLineNumber_) {
msg = "[{0}] {1}:{2}\n\t{3} ({4})\n\t\t--> {5}";
} else {
msg = "[{0}] {1} (line: {2})\n\t{3} ({4})\n\t\t--> {5}";
}
LOGGER.log(Level.WARNING, msg,
new Object[]{commandName,
v.getFileId().getUriString(),
v.getBeginLine(),
v.getRule().getName(),
v.getRule().getExternalInfoUrl() //TODO bug in PMD?
.replace("${pmd.website.baseurl}",
"https://docs.pmd-code.org/latest"),
v.getDescription()});
}
}
var violations = String.format(
"[%s] %d rule violations were found. See the report at: %s", commandName, numViolations,
config.getReportFilePath().toUri());
if (config.isFailOnViolation()) {
if (LOGGER.isLoggable(Level.SEVERE) && !silent()) {
LOGGER.log(Level.SEVERE, violations);
}
throw new ExitStatusException(ExitStatusException.EXIT_FAILURE);
} else if (LOGGER.isLoggable(Level.WARNING) && !silent()) {
LOGGER.warning(violations);
}
printViolations(commandName, config, report);
} else if (pmd.getReporter().numErrors() > 0 && failOnError_) {
throw new ExitStatusException(ExitStatusException.EXIT_FAILURE);
} else {
var rules = pmd.getRulesets();
if (!rules.isEmpty()) {
int count = 0;
for (var rule : rules) {
count += rule.getRules().size();
}
if (LOGGER.isLoggable(Level.INFO) && !silent()) {
LOGGER.info(String.format("[%s] %d rules were checked.", commandName, count));
}
}
int rulesChecked = 0;
var rules = pmd.getRulesets();
if (!rules.isEmpty()) {
for (var rule : rules) {
rulesChecked += rule.getRules().size();
}
if (LOGGER.isLoggable(Level.INFO) && !silent()) {
LOGGER.info(String.format("[%s] %d rules were checked.", commandName, rulesChecked));
}
}
return numViolations;
var result = new PmdAnalysisResults(
numViolations,
report.getSuppressedViolations().size(),
pmd.getReporter().numErrors(),
report.getProcessingErrors().size(),
report.getConfigurationErrors().size(),
rulesChecked
);
if (result.processingErrors() > 0 && LOGGER.isLoggable(Level.WARNING) && !silent()) {
for (var err : report.getProcessingErrors()) {
LOGGER.warning(String.format("[%s] %s", commandName, err.getMsg()));
}
}
if (result.configurationErrors() > 0 && LOGGER.isLoggable(Level.WARNING) && !silent()) {
for (var err : report.getConfigurationErrors()) {
LOGGER.warning(String.format("[%s] %s", commandName, err.issue()));
}
}
if (LOGGER.isLoggable(Level.FINEST) && !silent()) {
LOGGER.finest(result.toString());
}
return result;
}
/**
@ -931,7 +866,7 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
* is currently configured, the ClassLoader used to load the PMDConfiguration class will be used as the parent
* ClassLoader of the created ClassLoader.
* <p>
* If the classpath String looks like a URL to a file (i.e. starts with {@code file://}) the file will be read with
* If the classpath String looks like a URL to a file (i.e., starts with {@code file://}) the file will be read with
* each line representing an entry on the classpath.
*
* @param classpath one or more classpath
@ -951,6 +886,44 @@ public class PmdOperation extends AbstractOperation<PmdOperation> {
return prependClasspath_;
}
private void printViolations(String commandName, PMDConfiguration config, Report report)
throws ExitStatusException {
for (var v : report.getViolations()) {
if (LOGGER.isLoggable(Level.WARNING) && !silent()) {
final String msg;
if (includeLineNumber_) {
msg = "[%s] %s:%d\n\t%s (%s)\n\t\t--> %s";
} else {
msg = "[%s] %s (line: %d)\n\t%s (%s)\n\t\t--> %s";
}
LOGGER.log(Level.WARNING,
String.format(msg,
commandName,
v.getFileId().getUriString(),
v.getBeginLine(),
v.getRule().getName(),
v.getRule().getExternalInfoUrl(),
v.getDescription()));
}
}
var violations = new StringBuilder(
String.format("[%s] %d rule violations were found.", commandName, report.getViolations().size()));
if (config.getReportFilePath() != null) {
violations.append(" See the report at: ").append(config.getReportFilePath().toUri());
}
if (config.isFailOnViolation()) {
if (LOGGER.isLoggable(Level.SEVERE) && !silent()) {
LOGGER.log(Level.SEVERE, violations.toString());
}
throw new ExitStatusException(ExitStatusException.EXIT_FAILURE);
} else if (LOGGER.isLoggable(Level.WARNING) && !silent()) {
LOGGER.warning(violations.toString());
}
}
/**
* Adds several paths to shorten paths that are output in the report.
*

View file

@ -0,0 +1,57 @@
/*
* Copyright 2023-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package rife.bld.extension;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
class PmdAnalysisResultsTests {
@Nested
@DisplayName("Errors Tests")
class ErrorsTests {
@Test
void hasConfigurationErrors() {
var results = new PmdAnalysisResults(
0, 0, 0, 0, 1, 0);
assertThat(results.hasNoErrors()).isFalse();
}
@Test
void hasErrors() {
var results = new PmdAnalysisResults(
0, 0, 1, 0, 0, 0);
assertThat(results.hasNoErrors()).isFalse();
}
@Test
void hasNoErrors() {
var results = new PmdAnalysisResults(
0, 0, 0, 0, 0, 0);
assertThat(results.hasNoErrors()).isTrue();
}
@Test
void hasProcessingErrors() {
var results = new PmdAnalysisResults(
0, 0, 0, 1, 0, 0);
assertThat(results.hasNoErrors()).isFalse();
}
}
}