Make semantic errors report processing errors

This commit is contained in:
Clément Fournier
2022-05-15 14:23:35 +02:00
parent bd86027d90
commit e759069956
5 changed files with 48 additions and 22 deletions

View File

@ -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;
}
};
}

View File

@ -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);

View File

@ -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

View File

@ -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<String, Handler> addVersion) {

View File

@ -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<JavaParsingHelper, ASTC
}
@Override
public boolean hasError() {
return baseLogger.hasError();
public @Nullable SemanticException getFirstError() {
return baseLogger.getFirstError();
}
}
@ -181,8 +182,8 @@ public class JavaParsingHelper extends BaseParsingHelper<JavaParsingHelper, ASTC
}
@Override
public boolean hasError() {
return false;
public @Nullable SemanticException getFirstError() {
return null;
}
}
}