forked from phoedos/pmd
Merge branch 'analysis-listener' into text-utils-simple
This commit is contained in:
@ -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());
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -141,6 +141,7 @@ public final class RuleContext {
|
||||
*
|
||||
* @param rv A violation
|
||||
*/
|
||||
@InternalApi
|
||||
public void addViolationNoSuppress(RuleViolation rv) {
|
||||
listener.onRuleViolation(rv);
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -20,6 +20,7 @@ import net.sourceforge.pmd.util.document.TextFile;
|
||||
*
|
||||
* @author Romain Pelisse <belaran@gmail.com>
|
||||
*/
|
||||
@InternalApi
|
||||
public abstract class AbstractPMDProcessor implements AutoCloseable {
|
||||
|
||||
protected final PMDConfiguration configuration;
|
||||
|
@ -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)) {
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -81,6 +81,9 @@ public interface GlobalAnalysisListener extends AutoCloseable {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* A listener that does nothing.
|
||||
*/
|
||||
static GlobalAnalysisListener noop() {
|
||||
return new GlobalAnalysisListener() {
|
||||
@Override
|
||||
|
@ -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 {
|
||||
|
@ -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<>(
|
||||
|
@ -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);
|
||||
}
|
||||
|
Reference in New Issue
Block a user