Add utility to build a report, make report immutable

This commit is contained in:
Clément Fournier committed 2021-07-08 16:01:34 +02:00
1 parent 92fbaee6ac
commit 243ab00229
10 files changed
+98 -75

No files matched your search

@@ -12,6 +12,7 @@ import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import net.sourceforge.pmd.renderers.AbstractAccumulatingRenderer;
import net.sourceforge.pmd.reporting.FileAnalysisListener;
@@ -23,6 +24,10 @@ import net.sourceforge.pmd.util.datasource.DataSource;
* A {@link Report} collects all informations during a PMD execution. This
* includes violations, suppressed violations, metrics, error during processing
* and configuration errors.
*
* <p>A report may be created by a {@link GlobalReportBuilderListener} that you
* use as the {@link GlobalAnalysisListener} in {@linkplain PMD#processFiles(PMDConfiguration, List, List, GlobalAnalysisListener) PMD's entry point}.
* You can also create one manually with {@link #buildReport(Consumer)}.
*/
public final class Report {
// todo move to package reporting
@@ -32,9 +37,8 @@ public final class Report {
private final List<ProcessingError> errors = synchronizedList(new ArrayList<>());
private final List<ConfigurationError> configErrors = synchronizedList(new ArrayList<>());
/** Package-private, now you must use a listener to build a report. */
Report() {
// package-private, you have to use a listener to build a report.
}
/**
@@ -162,7 +166,7 @@ public final class Report {
*
* @param violation the violation to add
*/
public void addRuleViolation(RuleViolation violation) {
private void addRuleViolation(RuleViolation violation) {
synchronized (violations) {
int index = Collections.binarySearch(violations, violation, RuleViolation.DEFAULT_COMPARATOR);
violations.add(index < 0 ? -index - 1 : index, violation);
@@ -172,7 +176,7 @@ public final class Report {
/**
* Adds a new suppressed violation.
*/
public void addSuppressedViolation(SuppressedViolation sv) {
private void addSuppressedViolation(SuppressedViolation sv) {
suppressedRuleViolations.add(sv);
}
@@ -181,7 +185,7 @@ public final class Report {
*
* @param error the error to add
*/
public void addConfigError(ConfigurationError error) {
private void addConfigError(ConfigurationError error) {
configErrors.add(error);
}
@@ -191,7 +195,7 @@ public final class Report {
* @param error
* the error to add
*/
public void addError(ProcessingError error) {
private void addError(ProcessingError error) {
errors.add(error);
}
@@ -206,7 +210,10 @@ public final class Report {
* @param r the report to be merged into this.
*
* @see AbstractAccumulatingRenderer
*
* @deprecated Convert Renderer to use the reports.
*/
@Deprecated
public void merge(Report r) {
errors.addAll(r.errors);
configErrors.addAll(r.configErrors);
@@ -221,7 +228,7 @@ public final class Report {
/**
* Returns an unmodifiable list of violations that were suppressed.
*/
public final List<SuppressedViolation> getSuppressedViolations() {
public List<SuppressedViolation> getSuppressedViolations() {
return Collections.unmodifiableList(suppressedRuleViolations);
}
@@ -231,7 +238,7 @@ public final class Report {
*
* <p>The violations list is sorted with {@link RuleViolation#DEFAULT_COMPARATOR}.
*/
public final List<RuleViolation> getViolations() {
public List<RuleViolation> getViolations() {
return Collections.unmodifiableList(violations);
}
@@ -240,7 +247,7 @@ public final class Report {
* Returns an unmodifiable list of processing errors that have been
* recorded until now.
*/
public final List<ProcessingError> getProcessingErrors() {
public List<ProcessingError> getProcessingErrors() {
return Collections.unmodifiableList(errors);
}
@@ -249,10 +256,17 @@ public final class Report {
* Returns an unmodifiable list of configuration errors that have
* been recorded until now.
*/
public final List<ConfigurationError> getConfigurationErrors() {
public List<ConfigurationError> getConfigurationErrors() {
return Collections.unmodifiableList(configErrors);
}
/**
* Create a report by making side effects on a {@link FileAnalysisListener}.
* This wraps a {@link ReportBuilderListener}.
*/
public static Report buildReport(Consumer<? super FileAnalysisListener> lambda) {
return BaseResultProducingCloseable.using(new ReportBuilderListener(), lambda);
}
/**
* A {@link FileAnalysisListener} that accumulates events into a
@@ -4,6 +4,8 @@
package net.sourceforge.pmd.util;
import java.util.function.Consumer;
/**
* Base class for an autocloseable that produce a result once it has
* been closed.
@@ -43,4 +45,14 @@ public abstract class BaseResultProducingCloseable<T> implements AutoCloseable {
public void close() {
closed = true;
}
public static <U, C extends BaseResultProducingCloseable<U>> U using(C closeable, Consumer<? super C> it) {
try {
it.accept(closeable);
} finally {
closeable.close();
}
return closeable.getResult();
}
}
@@ -25,41 +25,42 @@ public class ReportTest {
// Files are grouped together now.
@Test
public void testSortedReportFile() throws IOException {
Report r = new Report();
Node s = getNode(10, 5).withFileName("foo");
Rule rule1 = new MockRule("name", "desc", "msg", "rulesetname");
r.addRuleViolation(new ParametricRuleViolation<>(rule1, s, rule1.getMessage()));
Node s1 = getNode(10, 5).withFileName("bar");
Rule rule2 = new MockRule("name", "desc", "msg", "rulesetname");
r.addRuleViolation(new ParametricRuleViolation<>(rule2, s1, rule2.getMessage()));
Renderer rend = new XMLRenderer();
String result = render(rend, r);
String result = render(rend, Report.buildReport(r -> {
Node s = getNode(10, 5).withFileName("foo");
Rule rule1 = new MockRule("name", "desc", "msg", "rulesetname");
r.onRuleViolation(new ParametricRuleViolation<>(rule1, s, rule1.getMessage()));
Node s1 = getNode(10, 5).withFileName("bar");
Rule rule2 = new MockRule("name", "desc", "msg", "rulesetname");
r.onRuleViolation(new ParametricRuleViolation<>(rule2, s1, rule2.getMessage()));
}));
assertTrue("sort order wrong", result.indexOf("bar") < result.indexOf("foo"));
}
@Test
public void testSortedReportLine() throws IOException {
Report r = new Report();
Node node1 = getNode(20, 5).withFileName("foo1"); // line 20: after rule2 violation
Rule rule1 = new MockRule("rule1", "rule1", "msg", "rulesetname");
r.addRuleViolation(new ParametricRuleViolation<>(rule1, node1, rule1.getMessage()));
Node node2 = getNode(10, 5).withFileName("foo1"); // line 10: before rule1 violation
Rule rule2 = new MockRule("rule2", "rule2", "msg", "rulesetname");
r.addRuleViolation(new ParametricRuleViolation<>(rule2, node2, rule2.getMessage())); // same file!!
Renderer rend = new XMLRenderer();
String result = render(rend, r);
String result = render(rend, Report.buildReport(r -> {
Node node1 = getNode(20, 5).withFileName("foo1"); // line 20: after rule2 violation
Rule rule1 = new MockRule("rule1", "rule1", "msg", "rulesetname");
r.onRuleViolation(new ParametricRuleViolation<>(rule1, node1, rule1.getMessage()));
Node node2 = getNode(10, 5).withFileName("foo1"); // line 10: before rule1 violation
Rule rule2 = new MockRule("rule2", "rule2", "msg", "rulesetname");
r.onRuleViolation(new ParametricRuleViolation<>(rule2, node2, rule2.getMessage())); // same file!!
}));
assertTrue("sort order wrong", result.indexOf("rule2") < result.indexOf("rule1"));
}
@Test
public void testIterator() {
Report r = new Report();
Rule rule = new MockRule("name", "desc", "msg", "rulesetname");
Node node1 = getNode(5, 5, true);
r.addRuleViolation(new ParametricRuleViolation<>(rule, node1, rule.getMessage()));
Node node2 = getNode(5, 6, true);
r.addRuleViolation(new ParametricRuleViolation<>(rule, node2, rule.getMessage()));
Report r = Report.buildReport(it -> {
it.onRuleViolation(new ParametricRuleViolation<>(rule, node1, rule.getMessage()));
it.onRuleViolation(new ParametricRuleViolation<>(rule, node2, rule.getMessage()));
});
assertEquals(2, r.getViolations().size());
}
@@ -74,7 +75,7 @@ public class ReportTest {
}
private static Node getNode(int line, int column, boolean nextLine) {
DummyNode s = (DummyNode) getNode(line, column);
DummyNode s = getNode(line, column);
if (nextLine) {
s.setCoords(line + 1, column + 4, line + 4, 1);
}
@@ -16,6 +16,7 @@ import org.junit.Test;
import net.sourceforge.pmd.FooRule;
import net.sourceforge.pmd.Report;
import net.sourceforge.pmd.Report.ConfigurationError;
import net.sourceforge.pmd.Report.GlobalReportBuilderListener;
import net.sourceforge.pmd.Report.ProcessingError;
import net.sourceforge.pmd.ReportTest;
import net.sourceforge.pmd.RulePriority;
@@ -25,6 +26,7 @@ import net.sourceforge.pmd.lang.ast.DummyNode;
import net.sourceforge.pmd.lang.ast.DummyRoot;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.rule.ParametricRuleViolation;
import net.sourceforge.pmd.util.BaseResultProducingCloseable;
public abstract class AbstractRendererTest {
@@ -66,20 +68,18 @@ public abstract class AbstractRendererTest {
}
protected Report reportOneViolation() {
Report report = new Report();
report.addRuleViolation(newRuleViolation(1));
return report;
return Report.buildReport(it -> it.onRuleViolation(newRuleViolation(1)));
}
private Report reportTwoViolations() {
Report report = new Report();
RuleViolation informationalRuleViolation = newRuleViolation(1);
informationalRuleViolation.getRule().setPriority(RulePriority.LOW);
report.addRuleViolation(informationalRuleViolation);
RuleViolation severeRuleViolation = newRuleViolation(2);
severeRuleViolation.getRule().setPriority(RulePriority.HIGH);
report.addRuleViolation(severeRuleViolation);
return report;
return Report.buildReport(it -> {
RuleViolation informationalRuleViolation = newRuleViolation(1);
informationalRuleViolation.getRule().setPriority(RulePriority.LOW);
it.onRuleViolation(informationalRuleViolation);
RuleViolation severeRuleViolation = newRuleViolation(2);
severeRuleViolation.getRule().setPriority(RulePriority.HIGH);
it.onRuleViolation(severeRuleViolation);
});
}
protected RuleViolation newRuleViolation(int endColumn) {
@@ -113,11 +113,10 @@ public abstract class AbstractRendererTest {
@Test
public void testRuleWithProperties() throws Exception {
DummyNode node = createNode(1);
Report report = new Report();
RuleWithProperties theRule = new RuleWithProperties();
theRule.setProperty(RuleWithProperties.STRING_PROPERTY_DESCRIPTOR,
"the string value\nsecond line with \"quotes\"");
report.addRuleViolation(new ParametricRuleViolation<Node>(theRule, node, "blah"));
Report report = Report.buildReport(it -> it.onRuleViolation(new ParametricRuleViolation<Node>(theRule, node, "blah")));
String rendered = ReportTest.render(getRenderer(), report);
assertEquals(filter(getExpectedWithProperties()), filter(rendered));
}
@@ -131,7 +130,7 @@ public abstract class AbstractRendererTest {
@Test
public void testRendererEmpty() throws Exception {
Report rep = new Report();
Report rep = Report.buildReport(it -> {});
String actual = ReportTest.render(getRenderer(), rep);
assertEquals(filter(getExpectedEmpty()), filter(actual));
}
@@ -145,27 +144,24 @@ public abstract class AbstractRendererTest {
@Test
public void testError() throws Exception {
Report rep = new Report();
Report.ProcessingError err = new Report.ProcessingError(new RuntimeException("Error"), "file");
rep.addError(err);
Report rep = Report.buildReport(it -> it.onError(err));
String actual = ReportTest.render(getRenderer(), rep);
assertEquals(filter(getExpectedError(err)), filter(actual));
}
@Test
public void testErrorWithoutMessage() throws Exception {
Report rep = new Report();
Report.ProcessingError err = new Report.ProcessingError(new NullPointerException(), "file");
rep.addError(err);
Report rep = Report.buildReport(it -> it.onError(err));
String actual = ReportTest.render(getRenderer(), rep);
assertEquals(filter(getExpectedErrorWithoutMessage(err)), filter(actual));
}
@Test
public void testConfigError() throws Exception {
Report rep = new Report();
Report.ConfigurationError err = new Report.ConfigurationError(new FooRule(), "a configuration error");
rep.addConfigError(err);
Report rep = BaseResultProducingCloseable.using(new GlobalReportBuilderListener(), it -> it.onConfigError(err));
String actual = ReportTest.render(getRenderer(), rep);
assertEquals(filter(getExpectedError(err)), filter(actual));
}
@@ -89,14 +89,13 @@ public class CodeClimateRendererTest extends AbstractRendererTest {
@Test
public void testXPathRule() throws Exception {
DummyNode node = createNode(1);
Report report = new Report();
XPathRule theRule = new XPathRule(XPathVersion.XPATH_3_1, "//dummyNode");
// Setup as FooRule
theRule.setDescription("desc");
theRule.setName("Foo");
report.addRuleViolation(new ParametricRuleViolation<Node>(theRule, node, "blah"));
Report report = Report.buildReport(it -> it.onRuleViolation(new ParametricRuleViolation<Node>(theRule, node, "blah")));
String rendered = ReportTest.render(getRenderer(), report);
// Output should be the exact same as for non xpath rules
@@ -79,10 +79,12 @@ public class JsonRendererTest extends AbstractRendererTest {
@Test
public void suppressedViolations() throws IOException {
Report rep = new Report();
SuppressedViolation suppressed = new SuppressedViolation(newRuleViolation(1),
ViolationSuppressor.NOPMD_COMMENT_SUPPRESSOR, "test");
rep.addSuppressedViolation(suppressed);
SuppressedViolation suppressed = new SuppressedViolation(
newRuleViolation(1),
ViolationSuppressor.NOPMD_COMMENT_SUPPRESSOR,
"test"
);
Report rep = Report.buildReport(it -> it.onSuppressedRuleViolation(suppressed));
String actual = ReportTest.render(getRenderer(), rep);
String expected = readFile("expected-suppressed.json");
Assert.assertEquals(filter(expected), filter(actual));
@@ -84,14 +84,14 @@ public class SarifRendererTest extends AbstractRendererTest {
}
private Report reportTwoViolations() {
Report report = new Report();
RuleViolation informationalRuleViolation = newRuleViolation(1, "Foo");
informationalRuleViolation.getRule().setPriority(RulePriority.LOW);
report.addRuleViolation(informationalRuleViolation);
RuleViolation severeRuleViolation = newRuleViolation(2, "Boo");
severeRuleViolation.getRule().setPriority(RulePriority.HIGH);
report.addRuleViolation(severeRuleViolation);
return report;
return Report.buildReport(reportBuilder -> {
RuleViolation informationalRuleViolation = newRuleViolation(1, "Foo");
informationalRuleViolation.getRule().setPriority(RulePriority.LOW);
reportBuilder.onRuleViolation(informationalRuleViolation);
RuleViolation severeRuleViolation = newRuleViolation(2, "Boo");
severeRuleViolation.getRule().setPriority(RulePriority.HIGH);
reportBuilder.onRuleViolation(severeRuleViolation);
});
}
protected String readFile(String relativePath) {
@@ -98,10 +98,9 @@ public class XMLRendererTest extends AbstractRendererTest {
private void verifyXmlEscaping(Renderer renderer, String shouldContain, Charset charset) throws Exception {
renderer.setProperty(XMLRenderer.ENCODING, charset.name());
Report report = new Report();
String surrogatePair = "\ud801\udc1c";
String msg = "The String 'literal' \"TokénizĀr " + surrogatePair + "\" appears...";
report.addRuleViolation(createRuleViolation(msg));
Report report = Report.buildReport(it -> it.onRuleViolation(createRuleViolation(msg)));
String actual = renderTempFile(renderer, report, charset);
Assert.assertTrue(actual.contains(shouldContain));
Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder()
@@ -143,14 +142,13 @@ public class XMLRendererTest extends AbstractRendererTest {
Renderer renderer = getRenderer();
Report report = new Report();
String formFeed = "\u000C";
// é = U+00E9 : can be represented in ISO-8859-1 as is
// Ā = U+0100 : cannot be represented in ISO-8859-1 -> would be a unmappable character, needs to be escaped
String specialChars = "éĀ";
String originalChars = formFeed + specialChars; // u000C should be removed, é should be encoded correctly as UTF-8
String msg = "The String literal \"" + originalChars + "\" appears...";
report.addRuleViolation(createRuleViolation(msg));
Report report = Report.buildReport(it -> it.onRuleViolation(createRuleViolation(msg)));
String actual = renderTempFile(renderer, report, StandardCharsets.UTF_8);
Assert.assertTrue(actual.contains(specialChars));
Assert.assertFalse(actual.contains(formFeed));
@@ -21,11 +21,10 @@ public class XSLTRendererTest {
@Test
public void testDefaultStylesheet() throws Exception {
XSLTRenderer renderer = new XSLTRenderer();
Report report = new Report();
DummyNode node = new DummyRoot().withFileName("file");
node.setCoords(1, 1, 1, 2);
RuleViolation rv = new ParametricRuleViolation<Node>(new FooRule(), node, "violation message");
report.addRuleViolation(rv);
Report report = Report.buildReport(it -> it.onRuleViolation(rv));
String result = ReportTest.render(renderer, report);
Assert.assertTrue(result.contains("violation message"));
}
@@ -60,10 +60,12 @@ public class YAHTMLRendererTest extends AbstractRendererTest {
@Test
public void testReportMultipleViolations() throws Exception {
Report report = new Report();
report.addRuleViolation(newRuleViolation(1, "net.sf.pmd.test", "YAHTMLSampleClass1"));
report.addRuleViolation(newRuleViolation(2, "net.sf.pmd.test", "YAHTMLSampleClass1"));
report.addRuleViolation(newRuleViolation(1, "net.sf.pmd.other", "YAHTMLSampleClass2"));
Report report = Report.buildReport(it -> {
it.onRuleViolation(newRuleViolation(1, "net.sf.pmd.test", "YAHTMLSampleClass1"));
it.onRuleViolation(newRuleViolation(2, "net.sf.pmd.test", "YAHTMLSampleClass1"));
it.onRuleViolation(newRuleViolation(1, "net.sf.pmd.other", "YAHTMLSampleClass2"));
});
String actual = ReportTest.render(getRenderer(), report);
assertEquals(filter(getExpected()), filter(actual));