Merge branch 'analysis-listener' into text-utils-simple

This commit is contained in:
Clément Fournier
2022-02-06 16:57:51 +01:00
12 changed files with 65 additions and 55 deletions

View File

@ -110,18 +110,17 @@ You can also provide your own custom renderers.
List<DataSource> files = Arrays.asList(new FileDataSource(new File("/path/to/src/MyClass.java")));
```
5. For reporting, you can use a built-in renderer, e.g. `XMLRenderer` or a custom renderer implementing
`Renderer`. Note, that you must manually initialize
the renderer by setting a suitable `Writer` and calling `start()`. After the PMD run, you need to call
`end()` and `flush()`. Then your writer should have received all output.
5. For reporting, you can use `GlobalAnalysisListener`, which receives events like violations and errors.
Useful implementations are provided by `Renderer` instances. To use a renderer, eg the built-in `XMLRenderer`,
create it and configure it with a suitable `Writer`.
```java
StringWriter rendererOutput = new StringWriter();
Renderer xmlRenderer = new XMLRenderer("UTF-8");
xmlRenderer.setWriter(rendererOutput);
xmlRenderer.start();
// The listener is created from the renderer in the next listing
```
6. Now, all the preparations are done, and PMD can be executed. This is done by calling
`PMD.processFiles(...)`. This method call takes the configuration, the rulesets, the files
to process, and the list of renderers. Provide an empty list, if you don't want to use
@ -129,8 +128,8 @@ You can also provide your own custom renderers.
remain open and file resources are leaked.
```java
try {
PMD.processFiles(configuration, ruleSets, files, Collections.singletonList(renderer));
try (GlobalAnalysisListener listener = xmlRenderer.newListener()) {
PMD.processFiles(configuration, ruleSets, files, listener);
} finally {
ClassLoader auxiliaryClassLoader = configuration.getClassLoader();
if (auxiliaryClassLoader instanceof ClasspathClassLoader) {
@ -139,12 +138,10 @@ You can also provide your own custom renderers.
}
```
7. After the call, you need to finish the renderer via `end()` and `flush()`.
7. After the call, the renderer will have been flushed by PMD (through its `GlobalAnalysisListener`).
Then you can check the rendered output.
``` java
renderer.end();
renderer.flush();
System.out.println("Rendered Report:");
System.out.println(rendererOutput.toString());
```
@ -192,10 +189,9 @@ public class PmdExample2 {
Writer rendererOutput = new StringWriter();
Renderer renderer = createRenderer(rendererOutput);
renderer.start();
try {
PMD.processFiles(configuration, ruleSets, files, Collections.singletonList(renderer));
try (GlobalAnalysisListener listener = renderer.newListener()) {
PMD.processFiles(configuration, ruleSets, files, listener);
} finally {
ClassLoader auxiliaryClassLoader = configuration.getClassLoader();
if (auxiliaryClassLoader instanceof ClasspathClassLoader) {
@ -203,8 +199,6 @@ public class PmdExample2 {
}
}
renderer.end();
renderer.flush();
System.out.println("Rendered Report:");
System.out.println(rendererOutput.toString());
}

View File

@ -102,7 +102,7 @@ public final class PMD {
violationCounter))) {
try (TimedOperation to = TimeTracker.startOperation(TimedOperationCategory.FILE_PROCESSING)) {
try (TimedOperation ignored = TimeTracker.startOperation(TimedOperationCategory.FILE_PROCESSING)) {
processTextFiles(configuration, ruleSets, files, listener);
}
}

View File

@ -141,6 +141,7 @@ public final class RuleContext {
*
* @param rv A violation
*/
@InternalApi
public void addViolationNoSuppress(RuleViolation rv) {
listener.onRuleViolation(rv);
}

View File

@ -27,12 +27,14 @@ import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.Parameter;
import net.sourceforge.pmd.Report;
import net.sourceforge.pmd.annotation.InternalApi;
import net.sourceforge.pmd.renderers.Renderer;
import net.sourceforge.pmd.renderers.RendererFactory;
import net.sourceforge.pmd.reporting.FileAnalysisListener;
import net.sourceforge.pmd.reporting.GlobalAnalysisListener;
import net.sourceforge.pmd.util.document.TextFile;
@InternalApi
public class Formatter {
private File toFile;
@ -236,6 +238,7 @@ public class Formatter {
return null;
}
@InternalApi
public GlobalAnalysisListener newListener(Project project) throws IOException {
start(project.getBaseDir().toString());
Renderer renderer = getRenderer();

View File

@ -20,6 +20,7 @@ import net.sourceforge.pmd.util.document.TextFile;
*
* @author Romain Pelisse &lt;belaran@gmail.com&gt;
*/
@InternalApi
public abstract class AbstractPMDProcessor implements AutoCloseable {
protected final PMDConfiguration configuration;

View File

@ -31,7 +31,7 @@ import net.sourceforge.pmd.util.document.TextFile;
abstract class PmdRunnable implements Runnable {
private final TextFile textFile;
private final GlobalAnalysisListener ruleContext;
private final GlobalAnalysisListener globalListener;
private final AnalysisCache analysisCache;
/** @deprecated Get rid of this */
@ -41,10 +41,10 @@ abstract class PmdRunnable implements Runnable {
private final RulesetStageDependencyHelper dependencyHelper;
PmdRunnable(TextFile textFile,
GlobalAnalysisListener ruleContext,
GlobalAnalysisListener globalListener,
PMDConfiguration configuration) {
this.textFile = textFile;
this.ruleContext = ruleContext;
this.globalListener = globalListener;
this.analysisCache = configuration.getAnalysisCache();
this.configuration = configuration;
this.dependencyHelper = new RulesetStageDependencyHelper(configuration);
@ -63,7 +63,7 @@ abstract class PmdRunnable implements Runnable {
RuleSets ruleSets = getRulesets();
try (FileAnalysisListener listener = ruleContext.startFileAnalysis(textFile)) {
try (FileAnalysisListener listener = globalListener.startFileAnalysis(textFile)) {
// Coarse check to see if any RuleSet applies to file, will need to do a finer RuleSet specific check later
if (ruleSets.applies(textFile)) {

View File

@ -44,8 +44,7 @@ public abstract class AbstractAccumulatingRenderer extends AbstractRenderer {
}
@Override
public final void startFileAnalysis(TextFile dataSource) {
// do nothing, final because it will never be called by the listener
public void startFileAnalysis(TextFile dataSource) {
Objects.requireNonNull(dataSource);
}
@ -65,7 +64,7 @@ public abstract class AbstractAccumulatingRenderer extends AbstractRenderer {
@Override
public GlobalAnalysisListener newListener() throws IOException {
try (TimedOperation to = TimeTracker.startOperation(TimedOperationCategory.REPORTING)) {
try (TimedOperation ignored = TimeTracker.startOperation(TimedOperationCategory.REPORTING)) {
this.start();
}
@ -74,6 +73,7 @@ public abstract class AbstractAccumulatingRenderer extends AbstractRenderer {
@Override
public FileAnalysisListener startFileAnalysis(TextFile file) {
AbstractAccumulatingRenderer.this.startFileAnalysis(file);
return reportBuilder.startFileAnalysis(file);
}
@ -85,7 +85,7 @@ public abstract class AbstractAccumulatingRenderer extends AbstractRenderer {
@Override
public void close() throws Exception {
reportBuilder.close();
try (TimedOperation to = TimeTracker.startOperation(TimedOperationCategory.REPORTING)) {
try (TimedOperation ignored = TimeTracker.startOperation(TimedOperationCategory.REPORTING)) {
outputReport(reportBuilder.getResult());
end();
flush();

View File

@ -87,13 +87,15 @@ public interface FileAnalysisListener extends AutoCloseable {
AssertionUtil.requireNotEmpty("Listeners", listeners);
AssertionUtil.requireContainsNoNullValue("Listeners", listeners);
if (listeners.size() == 1) {
return listeners.iterator().next();
}
List<FileAnalysisListener> list = new ArrayList<>(listeners);
list.removeIf(it -> it == NoopFileListener.INSTANCE);
if (listeners.isEmpty()) {
return noop();
} else if (listeners.size() == 1) {
return listeners.iterator().next();
}
class TeeListener implements FileAnalysisListener {
@Override

View File

@ -81,6 +81,9 @@ public interface GlobalAnalysisListener extends AutoCloseable {
// do nothing
}
/**
* A listener that does nothing.
*/
static GlobalAnalysisListener noop() {
return new GlobalAnalysisListener() {
@Override

View File

@ -4,11 +4,12 @@
package net.sourceforge.pmd.util;
import java.io.Closeable;
import java.util.function.Consumer;
/**
* Base class for an autocloseable that produce a result once it has
* been closed.
* been closed. None of the methods of this class are synchronized.
*
* @param <T> Type of the result
*/
@ -39,13 +40,25 @@ public abstract class BaseResultProducingCloseable<T> implements AutoCloseable {
protected abstract T getResultImpl();
/**
* @implNote Call super
* Close this object. Idempotent.
*
* @implNote Override {@link #closeImpl()} instead.
*/
@Override
public void close() {
closed = true;
public final void close() {
if (!closed) {
closed = true;
closeImpl();
}
}
/**
* Close this closeable as per the contract of {@link Closeable#close()}.
* Called exactly once. By default does nothing.
*/
protected void closeImpl() {
// override
}
public static <U, C extends BaseResultProducingCloseable<U>> U using(C closeable, Consumer<? super C> it) {
try {

View File

@ -65,6 +65,12 @@ public class DummyLanguageModule extends BaseLanguageModule {
this.languageVersion = languageVersion;
}
public DummyRootNode withCoords(int bline, int bcol, int eline, int ecol) {
super.setCoords(bline, bcol, eline, ecol);
return this;
}
@Override
public AstInfo<DummyRootNode> getAstInfo() {
return new AstInfo<>(

View File

@ -14,19 +14,14 @@ import java.util.Arrays;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import net.sourceforge.pmd.Rule;
import net.sourceforge.pmd.RuleContext;
import net.sourceforge.pmd.RuleViolation;
import net.sourceforge.pmd.lang.LanguageRegistry;
import net.sourceforge.pmd.lang.LanguageVersion;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.rule.ParametricRuleViolation;
import net.sourceforge.pmd.lang.rule.RuleTargetSelector;
import net.sourceforge.pmd.test.lang.DummyLanguageModule.DummyRootNode;
import net.sourceforge.pmd.test.lang.ast.DummyNode;
public class RuleTstTest {
private LanguageVersion dummyLanguage = LanguageRegistry.findLanguageByTerseName("dummy").getDefaultVersion();
@ -60,29 +55,21 @@ public class RuleTstTest {
public void shouldAssertLinenumbersSorted() {
when(rule.getLanguage()).thenReturn(dummyLanguage.getLanguage());
when(rule.getName()).thenReturn("test rule");
when(rule.getMessage()).thenReturn("test rule");
when(rule.getTargetSelector()).thenReturn(RuleTargetSelector.forRootOnly());
when(rule.deepCopy()).thenReturn(rule);
Mockito.doAnswer(new Answer<Void>() {
private RuleViolation createViolation(int beginLine, String message) {
DummyNode node = new DummyRootNode();
node.setCoords(beginLine, 1, beginLine + 1, 2);
return new ParametricRuleViolation(rule, node, message);
}
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
RuleContext context = invocation.getArgument(1, RuleContext.class);
// the violations are reported out of order
context.addViolationNoSuppress(createViolation(15, "first reported violation"));
context.addViolationNoSuppress(createViolation(5, "second reported violation"));
return null;
}
Mockito.doAnswer(invocation -> {
RuleContext context = invocation.getArgument(1, RuleContext.class);
// the violations are reported out of order
context.addViolation(new DummyRootNode().withCoords(15, 1, 15, 5));
context.addViolation(new DummyRootNode().withCoords(1, 1, 2, 5));
return null;
}).when(rule).apply(any(Node.class), Mockito.any(RuleContext.class));
TestDescriptor testDescriptor = new TestDescriptor("the code", "sample test", 2, rule, dummyLanguage);
testDescriptor.setReinitializeRule(false);
testDescriptor.setExpectedLineNumbers(Arrays.asList(5, 15));
testDescriptor.setExpectedLineNumbers(Arrays.asList(1, 15));
ruleTester.runTest(testDescriptor);
}