diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/SemanticErrorReporter.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/SemanticErrorReporter.java index 9d5c96b9e5..88183eb65d 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/SemanticErrorReporter.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/SemanticErrorReporter.java @@ -6,6 +6,7 @@ package net.sourceforge.pmd.lang.ast; import java.text.MessageFormat; +import org.checkerframework.checker.nullness.qual.Nullable; import org.slf4j.event.Level; import net.sourceforge.pmd.util.StringUtil; @@ -45,14 +46,17 @@ public interface SemanticErrorReporter { /** - * Returns true if at least one error has been reported. + * If {@link #error(Node, String, Object...)} has been called, return + * a semantic exception instance with the correct message. If it has been + * called more than once, return the first exception, possibly with suppressed + * exceptions for subsequent calls to {@link #error(Node, String, Object...)}. */ - boolean hasError(); + @Nullable SemanticException getFirstError(); static SemanticErrorReporter noop() { return new SemanticErrorReporter() { - private boolean hasError = false; + private SemanticException exception; @Override public void warning(Node location, String message, Object... formatArgs) { @@ -61,13 +65,18 @@ public interface SemanticErrorReporter { @Override public SemanticException error(Node location, String message, Object... formatArgs) { - hasError = true; - return new SemanticException(MessageFormat.format(message, formatArgs)); + SemanticException ex = new SemanticException(MessageFormat.format(message, formatArgs)); + if (this.exception == null) { + this.exception = ex; + } else { + this.exception.addSuppressed(ex); + } + return ex; } @Override - public boolean hasError() { - return hasError; + public @Nullable SemanticException getFirstError() { + return exception; } }; } @@ -81,6 +90,8 @@ public interface SemanticErrorReporter { return new SemanticErrorReporter() { private boolean hasError = false; + private SemanticException exception = null; + private String locPrefix(Node loc) { return "at " + loc.getAstInfo().getFileName() + " :" + loc.getBeginLine() + ":" + loc.getBeginColumn() + ": "; @@ -105,12 +116,18 @@ public interface SemanticErrorReporter { public SemanticException error(Node location, String message, Object... args) { hasError = true; String fullMessage = logMessage(Level.ERROR, location, message, args); - return new SemanticException(fullMessage); + SemanticException ex = new SemanticException(fullMessage); + if (this.exception == null) { + this.exception = ex; + } else { + this.exception.addSuppressed(ex); + } + return ex; } @Override - public boolean hasError() { - return hasError; + public @Nullable SemanticException getFirstError() { + return exception; } }; } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/processor/PmdRunnable.java b/pmd-core/src/main/java/net/sourceforge/pmd/processor/PmdRunnable.java index 292461a225..c86caaa29e 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/processor/PmdRunnable.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/processor/PmdRunnable.java @@ -25,6 +25,7 @@ import net.sourceforge.pmd.lang.ast.Parser; import net.sourceforge.pmd.lang.ast.Parser.ParserTask; import net.sourceforge.pmd.lang.ast.RootNode; import net.sourceforge.pmd.lang.ast.SemanticErrorReporter; +import net.sourceforge.pmd.lang.ast.SemanticException; import net.sourceforge.pmd.reporting.FileAnalysisListener; import net.sourceforge.pmd.reporting.GlobalAnalysisListener; import net.sourceforge.pmd.util.datasource.DataSource; @@ -148,9 +149,10 @@ abstract class PmdRunnable implements Runnable { RootNode rootNode = parse(parser, task); - if (reporter.hasError()) { - configuration.getReporter().info("Errors occurred in file, skipping rule analysis: {0}", filename); - return; + SemanticException semanticError = reporter.getFirstError(); + if (semanticError != null) { + // cause a processing error to be reported and rule analysis to be skipped + throw semanticError; } ruleSets.apply(rootNode, listener); diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/SemanticErrorReporterTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/SemanticErrorReporterTest.java index c6ca31b805..b4c45d6f21 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/SemanticErrorReporterTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/SemanticErrorReporterTest.java @@ -4,8 +4,8 @@ package net.sourceforge.pmd.lang.ast; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.mockito.Mockito.contains; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; @@ -48,7 +48,7 @@ public class SemanticErrorReporterTest { SemanticErrorReporter reporter = SemanticErrorReporter.reportToLogger(mockReporter); RootNode node = parseMockNode(reporter); - assertFalse(reporter.hasError()); + assertNull(reporter.getFirstError()); String message = "an error occurred"; reporter.error(node, message); @@ -56,7 +56,7 @@ public class SemanticErrorReporterTest { verify(mockReporter).log(eq(Level.ERROR), contains(message)); verifyNoMoreInteractions(mockLogger); - assertTrue(reporter.hasError()); + assertNotNull(reporter.getFirstError()); } @Test diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/processor/PmdRunnableTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/processor/PmdRunnableTest.java index e6f8cde7e0..aa778a5cae 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/processor/PmdRunnableTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/processor/PmdRunnableTest.java @@ -132,8 +132,11 @@ public class PmdRunnableTest { pmdRunnable.run(); - verify(reporter).log(eq(Level.INFO), contains("skipping rule analysis")); + verify(reporter, times(1)).log(eq(Level.ERROR), eq("at test.dummy :1:1: " + TEST_MESSAGE_SEMANTIC_ERROR)); verify(rule, never()).apply(Mockito.any(), Mockito.any()); + + reportBuilder.close(); + Assert.assertEquals(1, reportBuilder.getResult().getProcessingErrors().size()); } @Test @@ -144,6 +147,9 @@ public class PmdRunnableTest { verify(reporter, times(1)).log(eq(Level.ERROR), contains(TEST_MESSAGE_SEMANTIC_ERROR)); verify(rule, never()).apply(Mockito.any(), Mockito.any()); + + reportBuilder.close(); + Assert.assertEquals(1, reportBuilder.getResult().getProcessingErrors().size()); } public static void registerCustomVersions(BiConsumer addVersion) { diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/JavaParsingHelper.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/JavaParsingHelper.java index 3e2028b96e..0643500672 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/JavaParsingHelper.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/JavaParsingHelper.java @@ -15,6 +15,7 @@ import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -144,8 +145,8 @@ public class JavaParsingHelper extends BaseParsingHelper