Change StaticError to represent a single error for a single front end.

Before, the data model collapsed errors for different front-ends at the
same location into a single StaticError object which tracked different
messages for each front end. The idea was to move towards a world where
they really are the "same" error with eventually the same message.

But this adds a lot of complexity with things like merging errors and
doesn't reflect the reality that each error from each front end is
basically its own thing. Also, critically, it makes it much harder to
attach context messages to a specific front end's error object.

This changes it so that an instance of StaticError represents a single
error for a single front end. The test file syntax is unchanged and the
updated tool behaves the same. In a static error test, multiple
expectations can still share the same "//   ^^^" marker line. They are
just expanded to multiple StaticError objects at parse time.

This eliminates all of the complexity around merging and simplifying
errors.

Change-Id: I1d55a6e885e12cc9c438f928297fc0db7dd5ce85
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/193280
Auto-Submit: Bob Nystrom <rnystrom@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Paul Berry <paulberry@google.com>
This commit is contained in:
Robert Nystrom 2021-03-30 16:30:21 +00:00 committed by commit-bot@chromium.org
parent 0a1a5276ba
commit 073eb9b806
9 changed files with 418 additions and 1242 deletions

View file

@ -552,7 +552,7 @@ class AnalysisCommandOutput extends CommandOutput with _StaticErrorOutput {
static void parseErrors(String stderr, List<StaticError> errors,
[List<StaticError> warnings]) {
for (var error in AnalyzerError.parseStderr(stderr)) {
var staticError = StaticError({ErrorSource.analyzer: error.errorCode},
var staticError = StaticError(ErrorSource.analyzer, error.errorCode,
line: error.line, column: error.column, length: error.length);
if (error.severity == 'ERROR') {
@ -1315,8 +1315,7 @@ mixin _StaticErrorOutput on CommandOutput {
var line = int.parse(match.group(2));
var column = int.parse(match.group(3));
var message = match.group(4);
errors
.add(StaticError({errorSource: message}, line: line, column: column));
errors.add(StaticError(errorSource, message, line: line, column: column));
}
}
@ -1417,7 +1416,7 @@ mixin _StaticErrorOutput on CommandOutput {
assert(errorSource != null);
var expected = testCase.testFile.expectedErrors
.where((error) => error.hasError(errorSource));
.where((error) => error.source == errorSource);
var validation = StaticError.validateExpectations(
expected,

View file

@ -36,7 +36,7 @@ class ErrorSource {
const ErrorSource._(this.name);
}
/// Describes one or more static errors that should be reported at a specific
/// Describes a single static error reported by a single front end at a specific
/// location.
///
/// These can be parsed from comments in [TestFile]s, in which case they
@ -45,11 +45,6 @@ class ErrorSource {
/// produces the expected compile-time errors. This same class is also used for
/// *reported* errors when parsing the output of a front end.
///
/// Because there are multiple front ends that each report errors somewhat
/// differently, each [StaticError] has a map to associate an error message
/// with each front end. If there is no message for a given front end, it means
/// the error is not reported by that front end.
///
/// For analyzer errors, the error "message" is actually the constant name for
/// the error code, like "CompileTimeErrorCode.WRONG_TYPE".
class StaticError implements Comparable<StaticError> {
@ -98,85 +93,6 @@ class StaticError implements Comparable<StaticError> {
static List<StaticError> parseExpectations(String source) =>
_ErrorExpectationParser(source)._parse();
/// Collapses overlapping [errors] into a shorter list of errors where
/// possible.
///
/// Errors on the same location can be collapsed if none of them both define
/// a message for the same front end.
static List<StaticError> simplify(List<StaticError> errors) {
var result = errors.toList();
result.sort();
for (var i = 0; i < result.length - 1; i++) {
var a = result[i];
// Look for a later error we can merge with this one. Usually, it will be
// adjacent to this one, but if there are multiple errors with no length
// on the same location, those will all be next to each other and their
// merge targets will come later. This happens when CFE reports multiple
// errors at the same location (messages but no length) and analyzer does
// too (codes and lengths but no messages).
for (var j = i + 1; j < result.length; j++) {
var b = result[j];
// Position must be the same. If the position is different, we can
// stop looking because all same-position errors will be adjacent.
if (a.line != b.line) break;
if (a.column != b.column) break;
// If they both have lengths that are different, we can't discard that
// information.
if (a.length != null && b.length != null && a.length != b.length) {
continue;
}
// Can't lose any messages.
if (ErrorSource.all
.any((source) => a.hasError(source) && b.hasError(source))) {
continue;
}
// TODO(rnystrom): Now that there are more than two front ends, this
// isn't as smart as it could be. It could try to pack all of the
// messages in a given location into as few errors as possible by
// taking only the non-colliding messages from one error. But that's
// weird.
//
// A cleaner model is to have each StaticError represent a unique error
// location. It would have an open-ended list of every message that
// occurs at that location, across the various front-ends, including
// multiple messages for the same front end. But that would change how
// the existing static error tests look since something like:
//
// // ^^^
// // [cfe] Message 1.
// // ^^^
// // [cfe] Message 2.
//
// Would turn into:
//
// // ^^^
// // [cfe] Message 1.
// // [cfe] Message 2.
//
// That's a good change to do, but should probably wait until after
// NNBD.
// Merge the two errors.
result[i] = StaticError({...a._errors, ...b._errors},
line: a.line, column: a.column, length: a.length ?? b.length);
// Continue trying to merge this merged error with more since multiple
// errors might collapse into a single one.
result.removeAt(j);
a = result[i];
j--;
}
}
return result;
}
/// Determines whether all [actualErrors] match the given [expectedErrors].
///
/// If they match, returns `null`. Otherwise returns a string describing the
@ -185,88 +101,118 @@ class StaticError implements Comparable<StaticError> {
/// An expected error that is completely identical to an actual error is
/// treated as a match. Everything else is a failure.
///
/// It treats line number as the "identity" of an error. So if there are two
/// errors on the same line that differ in other properties, it reports that
/// as a "wrong" error. Any expected error on a line containing no actual
/// error is reported as a "missing" error. Conversely, an actual error on a
/// line containing no expected error is an "unexpected" error.
/// It has a few heuristics to try to determine what the discrepancies mean,
/// which it applies in order:
///
/// By not treating the error's index in the list to be its identity, we
/// gracefully handle extra or missing errors without causing cascading
/// failures for later errors in the lists.
/// * If it sees an actual errors with the same message but different
/// location as expected ones, it considers those to be the same error
/// but with the wrong location.
///
/// * If it sees an actual errors at the same location as expected ones,
/// it considers those to be wrong messages.
///
/// * Otherwise, any remaining expected errors are considered missing
/// errors and remaining actual errors are considered unexpected.
static String validateExpectations(Iterable<StaticError> expectedErrors,
Iterable<StaticError> actualErrors) {
// Don't require the test or front end to output in any specific order.
var sortedExpected = expectedErrors.toList();
var sortedActual = actualErrors.toList();
sortedExpected.sort();
sortedActual.sort();
var expected = expectedErrors.toList();
var actual = actualErrors.toList();
// Put them in a deterministic order.
expected.sort();
actual.sort();
// Discard matched errors.
for (var i = 0; i < expected.length; i++) {
var matchedExpected = false;
for (var j = 0; j < actual.length; j++) {
if (actual[j] == null) continue;
if (expected[i]._matchLocation(actual[j]) == null &&
(expected[i].message == actual[j].message ||
!expected[i].isSpecified)) {
actual[j] = null;
matchedExpected = true;
// If the expected error is unspecified, keep going so that it can
// consume multiple errors on the same line.
if (expected[i].isSpecified) break;
}
}
if (matchedExpected) expected[i] = null;
}
expected.removeWhere((error) => error == null);
actual.removeWhere((error) => error == null);
// If everything matched, we're done.
if (expected.isEmpty && actual.isEmpty) return null;
var buffer = StringBuffer();
describeError(String adjective, StaticError error, String verb) {
buffer.writeln("$adjective static error at ${error.location}:");
for (var source in ErrorSource.all) {
var sourceError = error._errors[source];
if (sourceError == _unspecified) {
buffer.writeln("- $verb unspecified ${source.name} error.");
} else if (sourceError != null) {
buffer.writeln("- $verb ${source.name} error '$sourceError'.");
}
void fail(StaticError error, String label, [String secondary]) {
if (error.isSpecified) {
buffer.writeln("- $label ${error.location}: ${error.message}");
} else {
label = label.replaceAll("error", "unspecified error");
buffer.writeln("- $label ${error.location}.");
}
if (secondary != null) buffer.writeln(" $secondary");
buffer.writeln();
}
var expectedIndex = 0;
var actualIndex = 0;
for (;
expectedIndex < sortedExpected.length &&
actualIndex < sortedActual.length;) {
var expected = sortedExpected[expectedIndex];
var actual = sortedActual[actualIndex];
// Look for matching messages, which means a wrong location.
for (var i = 0; i < expected.length; i++) {
if (expected[i] == null) continue;
var differences = expected.describeDifferences(actual);
if (differences == null) {
// Consume this actual error.
actualIndex++;
for (var j = 0; j < actual.length; j++) {
if (actual[j] == null) continue;
// Consume the expectation, unless it's an unspecified error that can
// match more actual errors.
if (expected.isSpecifiedFor(actual) ||
actualIndex == sortedActual.length ||
sortedActual[actualIndex].line != expected.line) {
expectedIndex++;
if (expected[i].message == actual[j].message) {
fail(expected[i], "Wrong error location",
expected[i]._matchLocation(actual[j]));
// Only report this mismatch once.
expected[i] = null;
actual[j] = null;
break;
}
} else if (expected.line == actual.line) {
buffer.writeln("Wrong static error at ${expected.location}:");
for (var difference in differences) {
buffer.writeln("- $difference");
}
buffer.writeln();
expectedIndex++;
actualIndex++;
} else if (expected.line < actual.line) {
describeError("Missing", expected, "Expected");
expectedIndex++;
} else {
describeError("Unexpected", actual, "Had");
actualIndex++;
}
}
// Output any trailing expected or actual errors if the lengths of the
// lists differ.
for (; expectedIndex < sortedExpected.length; expectedIndex++) {
describeError("Missing", sortedExpected[expectedIndex], "Expected");
// Look for matching locations, which means a wrong message.
for (var i = 0; i < expected.length; i++) {
if (expected[i] == null) continue;
for (var j = 0; j < actual.length; j++) {
if (actual[j] == null) continue;
if (expected[i]._matchLocation(actual[j]) == null) {
fail(actual[j], "Wrong message at",
"Expected: ${expected[i].message}");
// Only report this mismatch once.
expected[i] = null;
actual[j] = null;
break;
}
}
}
for (; actualIndex < sortedActual.length; actualIndex++) {
describeError("Unexpected", sortedActual[actualIndex], "Had");
// Any remaining expected errors are missing.
for (var i = 0; i < expected.length; i++) {
if (expected[i] == null) continue;
fail(expected[i], "Missing expected error at");
}
// Any remaining actual errors are unexpected.
for (var j = 0; j < actual.length; j++) {
if (actual[j] == null) continue;
fail(actual[j], "Unexpected error at");
}
if (buffer.isEmpty) return null;
return buffer.toString().trimRight();
}
@ -281,27 +227,18 @@ class StaticError implements Comparable<StaticError> {
/// This is optional. The CFE only reports error location, but not length.
final int length;
/// The error messages that should be or were reported by each front end.
final Map<ErrorSource, String> _errors;
/// The front end this error is for.
final ErrorSource source;
/// Whether this static error exists for [source].
bool hasError(ErrorSource source) => _errors.containsKey(source);
final String message;
/// The error for [source] or `null` if this error isn't expected to
/// reported by that source.
String errorFor(ErrorSource source) => _errors[source];
/// The zero-based index of the first line in the [TestFile] containing the
/// marker comments that define this error.
/// The zero-based numbers of the lines in the [TestFile] containing comments
/// that were parsed to produce this error.
///
/// If this error was not parsed from a file, this may be `null`.
final int markerStartLine;
/// The zero-based index of the last line in the [TestFile] containing the
/// marker comments that define this error, inclusive.
///
/// If this error was not parsed from a file, this may be `null`.
final int markerEndLine;
/// This includes a line for the location comment, as well as lines for the
/// error message. Note that lines may not be contiguous and multiple errors
/// may share the same line number for a shared location marker.
final Set<int> sourceLines;
/// Creates a new StaticError at the given location with the given expected
/// error code and message.
@ -312,19 +249,12 @@ class StaticError implements Comparable<StaticError> {
/// code or message be the special string "unspecified". When an unspecified
/// error is tested, a front end is expected to report *some* error on that
/// error's line, but it can be any location, error code, or message.
StaticError(Map<ErrorSource, String> errors,
{this.line,
this.column,
this.length,
this.markerStartLine,
this.markerEndLine})
: _errors = errors {
StaticError(this.source, this.message,
{this.line, this.column, this.length, Set<int> sourceLines})
: sourceLines = {...?sourceLines} {
// Must have a location.
assert(line != null);
assert(column != null);
// Must have at least one piece of description.
assert(_errors.isNotEmpty);
}
/// A textual description of this error's location.
@ -334,37 +264,33 @@ class StaticError implements Comparable<StaticError> {
return result;
}
/// True if this error has a specific expected message and location.
///
/// Otherwise, it is an "unspecified error", which means that as long as some
/// actual error is reported on this error's line, then the expectation is
/// met.
bool get isSpecified => message != _unspecified;
/// Whether this error is only considered a warning on all front ends that
/// report it.
bool get isWarning {
var analyzer = _errors[ErrorSource.analyzer];
if (analyzer != null && !_analyzerWarningCodes.contains(analyzer)) {
return false;
switch (source) {
case ErrorSource.analyzer:
return _analyzerWarningCodes.contains(message);
case ErrorSource.cfe:
// TODO(42787): Once CFE starts reporting warnings, encode that in the
// message somehow and then look for it here.
return false;
case ErrorSource.web:
// TODO(rnystrom): If the web compilers report warnings, encode that in
// the message somehow and then look for it here.
return false;
}
// TODO(42787): Once CFE starts reporting warnings, encode that in the
// message somehow and then look for it here.
if (hasError(ErrorSource.cfe)) return false;
// TODO(rnystrom): If the web compilers report warnings, encode that in the
// message somehow and then look for it here.
if (hasError(ErrorSource.web)) return false;
return true;
throw FallThroughError();
}
String toString() {
var result = "Error at $location";
for (var source in ErrorSource.all) {
var sourceError = _errors[source];
if (sourceError != null) {
result += "\n[${source.name.toLowerCase()}] $sourceError";
}
}
return result;
}
String toString() => "[${source.marker} $location] $message";
/// Orders errors primarily by location, then by other fields if needed.
@override
@ -377,15 +303,11 @@ class StaticError implements Comparable<StaticError> {
if (length != null && other.length == null) return -1;
if (length != other.length) return length.compareTo(other.length);
for (var source in ErrorSource.all) {
var thisError = _errors[source] ?? "";
var otherError = other._errors[source] ?? "";
if (thisError != otherError) {
return thisError.compareTo(otherError);
}
if (source != other.source) {
return source.marker.compareTo(other.source.marker);
}
return 0;
return message.compareTo(other.message);
}
@override
@ -396,73 +318,43 @@ class StaticError implements Comparable<StaticError> {
3 * line.hashCode +
5 * column.hashCode +
7 * (length ?? 0).hashCode +
11 * (_errors[ErrorSource.analyzer] ?? "").hashCode +
13 * (_errors[ErrorSource.cfe] ?? "").hashCode +
17 * (_errors[ErrorSource.web] ?? "").hashCode;
11 * source.hashCode +
13 * message.hashCode;
/// Whether this error expectation is a specified error for the front end
/// reported by [actual].
bool isSpecifiedFor(StaticError actual) {
assert(actual._errors.length == 1,
"Actual error should only have one source.");
for (var source in ErrorSource.all) {
if (actual.hasError(source)) {
return hasError(source) && _errors[source] != _unspecified;
}
}
return false;
}
/// Compares this error expectation to [actual].
/// Returns a string describing how this error's expected location differs
/// from [actual], or `null` if [actual]'s location matches this one.
///
/// If this error correctly matches [actual], returns `null`. Otherwise
/// returns a list of strings describing the mismatch.
///
/// Note that this does *not* check to see that [actual] matches the front
/// ends that this error expects. For example, if [actual] only reports an
/// analyzer error and this error only specifies a CFE error, this will still
/// report differences in location information. This method expects that error
/// expectations have already been filtered by platform so this will only be
/// called in cases where the platforms do match.
List<String> describeDifferences(StaticError actual) {
var differences = <String>[];
/// Takes into account unspecified errors and errors without lengths.
String _matchLocation(StaticError actual) {
var expectedMismatches = <String>[];
var actualMismatches = <String>[];
if (line != actual.line) {
differences.add("Expected on line $line but was on ${actual.line}.");
expectedMismatches.add("line $line");
actualMismatches.add("line ${actual.line}");
}
// If the error is unspecified on the front end being tested, the column
// and length can be any values.
if (isSpecifiedFor(actual)) {
// Ignore column and length for unspecified errors.
if (isSpecified) {
if (column != actual.column) {
differences
.add("Expected on column $column but was on ${actual.column}.");
expectedMismatches.add("column $column");
actualMismatches.add("column ${actual.column}");
}
// This error represents an expectation, so should have a length.
assert(length != null);
if (actual.length != null && length != actual.length) {
differences.add("Expected length $length but was ${actual.length}.");
expectedMismatches.add("length $column");
actualMismatches.add("length ${actual.column}");
}
}
for (var source in ErrorSource.all) {
var error = _errors[source];
var actualError = actual._errors[source];
if (error != null &&
error != _unspecified &&
actualError != null &&
error != actualError) {
differences.add("Expected ${source.name} error '$error' "
"but was '$actualError'.");
}
if (expectedMismatches.isEmpty) {
// Everything matches.
return null;
}
if (differences.isNotEmpty) return differences;
return null;
var expectedList = expectedMismatches.join(", ");
var actualList = actualMismatches.join(", ");
return "Expected $expectedList but was $actualList.";
}
}
@ -522,7 +414,7 @@ class _ErrorExpectationParser {
_fail("An error expectation must follow some code.");
}
_parseErrorMessages(
_parseErrors(
line: _lastRealLine,
column: sourceLine.indexOf("^") + 1,
length: match.group(1).length);
@ -532,7 +424,7 @@ class _ErrorExpectationParser {
match = _explicitLocationAndLengthRegExp.firstMatch(sourceLine);
if (match != null) {
_parseErrorMessages(
_parseErrors(
line: int.parse(match.group(1)),
column: int.parse(match.group(2)),
length: int.parse(match.group(3)));
@ -542,7 +434,7 @@ class _ErrorExpectationParser {
match = _explicitLocationRegExp.firstMatch(sourceLine);
if (match != null) {
_parseErrorMessages(
_parseErrors(
line: int.parse(match.group(1)), column: int.parse(match.group(2)));
_advance();
continue;
@ -555,12 +447,12 @@ class _ErrorExpectationParser {
return _errors;
}
/// Finishes parsing an error expectation after parsing the location.
void _parseErrorMessages({int line, int column, int length}) {
var errors = <ErrorSource, String>{};
var startLine = _currentLine;
/// Finishes parsing a series of error expectations after parsing a location.
void _parseErrors({int line, int column, int length}) {
var locationLine = _currentLine;
var parsedError = false;
// Allow errors for multiple front-ends to share the same location marker.
while (_canPeek(1)) {
var match = _errorMessageRegExp.firstMatch(_peek(1));
if (match == null) break;
@ -571,6 +463,7 @@ class _ErrorExpectationParser {
var message = match.group(2);
_advance();
var sourceLines = {locationLine, _currentLine};
// Consume as many additional error message lines as we find.
while (_canPeek(1)) {
@ -589,6 +482,7 @@ class _ErrorExpectationParser {
message += "\n" + messageMatch.group(1);
_advance();
sourceLines.add(_currentLine);
}
if (source == ErrorSource.analyzer &&
@ -596,35 +490,28 @@ class _ErrorExpectationParser {
_fail("An analyzer error expectation should be a dotted identifier.");
}
if (errors.containsKey(source)) {
_fail("Already have an error for ${source.name}:\n${errors[source]}");
// Hack: If the error is CFE-only and the length is one, treat it as no
// length. The CFE does not output length information, and when the update
// tool writes a CFE-only error, it implicitly uses a length of one. Thus,
// when we parse back in a length one CFE error, we ignore the length so
// that the error round-trips correctly.
// TODO(rnystrom): Stop doing this when the CFE reports error lengths.
var errorLength = length;
if (errorLength == 1 && source == ErrorSource.cfe) {
errorLength = null;
}
errors[source] = message;
_errors.add(StaticError(source, message,
line: line,
column: column,
length: errorLength,
sourceLines: sourceLines));
parsedError = true;
}
if (errors.isEmpty) {
if (!parsedError) {
_fail("An error expectation must specify at least one error message.");
}
// Hack: If the error is CFE-only and the length is one, treat it as no
// length. The CFE does not output length information, and when the update
// tool writes a CFE-only error, it implicitly uses a length of one. Thus,
// when we parse back in a length one CFE error, we ignore the length so
// that the error round-trips correctly.
// TODO(rnystrom): Stop doing this when the CFE reports error lengths.
if (length == 1 &&
errors.length == 1 &&
errors.containsKey(ErrorSource.cfe)) {
length = null;
}
_errors.add(StaticError(errors,
line: line,
column: column,
length: length,
markerStartLine: startLine,
markerEndLine: _currentLine));
}
bool _canPeek(int offset) => _currentLine + offset < _lines.length;

View file

@ -78,7 +78,7 @@ abstract class _TestFileBase {
/// These tests exist to validate that a Dart web compiler reports the right
/// expected errors.
bool get isWebStaticErrorTest =>
expectedErrors.any((error) => error.hasError(ErrorSource.web));
expectedErrors.any((error) => error.source == ErrorSource.web);
/// If the tests has no static error expectations, or all of the expectations
/// are warnings, then the test tests runtime semantics.

View file

@ -18,39 +18,43 @@ String updateErrorExpectations(String source, List<StaticError> errors,
{Set<ErrorSource> remove}) {
remove ??= {};
// Split the existing errors into kept and deleted lists.
var existingErrors = StaticError.parseExpectations(source);
var keptErrors = <StaticError>[];
var removedErrors = <StaticError>[];
for (var error in existingErrors) {
if (remove.contains(error.source)) {
removedErrors.add(error);
} else {
keptErrors.add(error);
}
}
var lines = source.split("\n");
// Keep track of the indentation on any existing expectation markers. If
// found, it will try to preserve that indentation.
var indentation = <int, int>{};
// Remove existing markers that should be removed.
var preservedErrors = <StaticError>[];
// Remove all existing marker comments in the file, even for errors we are
// preserving. We will regenerate marker comments for those errors too so
// they can properly share location comments with new errors if needed.
for (var error in existingErrors) {
for (var i = error.markerStartLine; i <= error.markerEndLine; i++) {
indentation[i] = _countIndentation(lines[i]);
for (var line in error.sourceLines) {
if (lines[line] == null) continue;
indentation[line] = _countIndentation(lines[line]);
// Null the line instead of removing it so that line numbers in the
// reported errors are still correct.
lines[i] = null;
}
// Re-add errors for the portions we intend to preserve.
var keptErrors = {
for (var source in ErrorSource.all.toSet().difference(remove))
if (error.hasError(source)) source: error.errorFor(source)
};
if (keptErrors.isNotEmpty) {
preservedErrors.add(StaticError(keptErrors,
line: error.line, column: error.column, length: error.length));
lines[line] = null;
}
}
// Merge the new errors with the preserved ones.
errors = StaticError.simplify([...errors, ...preservedErrors]);
errors = [...errors, ...keptErrors];
// Group errors by the line where they appear.
var errorMap = <int, List<StaticError>>{};
for (var error in errors) {
// -1 to translate from one-based to zero-based index.
@ -63,6 +67,7 @@ String updateErrorExpectations(String source, List<StaticError> errors,
errorList.sort();
}
// Rebuild the source file a line at a time.
var previousIndent = 0;
var codeLine = 1;
var result = <String>[];
@ -82,6 +87,10 @@ String updateErrorExpectations(String source, List<StaticError> errors,
// Add expectations for any errors reported on this line.
var errorsHere = errorMap[i];
if (errorsHere == null) continue;
var previousColumn = -1;
var previousLength = -1;
for (var error in errorsHere) {
// Try to indent the line nicely to match either the existing expectation
// that is being regenerated, or, barring that, the previous line of code.
@ -90,44 +99,50 @@ String updateErrorExpectations(String source, List<StaticError> errors,
// If the error is to the left of the indent and the "//", sacrifice the
// indentation.
if (error.column - 1 < indent + 2) indent = 0;
var comment = (" " * indent) + "//";
// If the error can't fit in a line comment, or no source location is
// sepcified, use an explicit location.
if (error.column <= 2 || error.length == 0) {
if (error.length == null) {
result.add("$comment [error line $codeLine, column "
"${error.column}]");
// Write the location line, unless we already have an identical one. Allow
// sharing locations between errors with and without explicit lengths.
if (error.column != previousColumn ||
(previousLength != null &&
error.length != null &&
error.length != previousLength)) {
// If the error can't fit in a line comment, or no source location is
// sepcified, use an explicit location.
if (error.column <= 2 || error.length == 0) {
if (error.length == null) {
result.add("$comment [error line $codeLine, column "
"${error.column}]");
} else {
result.add("$comment [error line $codeLine, column "
"${error.column}, length ${error.length}]");
}
} else {
result.add("$comment [error line $codeLine, column "
"${error.column}, length ${error.length}]");
var spacing = " " * (error.column - 1 - 2 - indent);
// A CFE-only error may not have a length, so treat it as length 1.
var carets = "^" * (error.length ?? 1);
result.add("$comment$spacing$carets");
}
} else {
var spacing = " " * (error.column - 1 - 2 - indent);
// A CFE-only error may not have a length, so treat it as length 1.
var carets = "^" * (error.length ?? 1);
result.add("$comment$spacing$carets");
}
for (var source in ErrorSource.all) {
var sourceError = error.errorFor(source);
if (sourceError == null) continue;
// If multiple errors share the same location, let them share a location
// marker.
previousColumn = error.column;
previousLength = error.length;
var errorLines = sourceError.split("\n");
result.add("$comment [${source.marker}] ${errorLines[0]}");
for (var errorLine in errorLines.skip(1)) {
result.add("$comment $errorLine");
}
var errorLines = error.message.split("\n");
result.add("$comment [${error.source.marker}] ${errorLines[0]}");
for (var errorLine in errorLines.skip(1)) {
result.add("$comment $errorLine");
}
// If the very next line in the source is a line comment, it would
// become part of the inserted message. To prevent that, insert a blank
// line.
if (i < lines.length - 1 &&
lines[i + 1] != null &&
_lineCommentRegExp.hasMatch(lines[i + 1])) {
result.add("");
}
// If the very next line in the source is a line comment, it would
// become part of the inserted message. To prevent that, insert a blank
// line.
if (i < lines.length - 1 &&
lines[i + 1] != null &&
_lineCommentRegExp.hasMatch(lines[i + 1])) {
result.add("");
}
}
}

View file

@ -9,74 +9,36 @@ import 'package:test_runner/src/static_error.dart';
import 'utils.dart';
void main() {
testHasError();
testErrorFor();
testProperties();
testIsWarning();
testIsSpecifiedFor();
testCompareTo();
testDescribeDifferences();
testSimplify();
// testDescribeDifferences();
testValidate();
}
void testHasError() {
var analyzer =
makeError(line: 1, column: 2, length: 3, analyzerError: "E.CODE");
var cfe = makeError(line: 1, column: 2, length: 3, cfeError: "Error.");
var web = makeError(line: 1, column: 2, length: 3, webError: "Web.");
var all = makeError(
line: 1,
column: 2,
length: 3,
analyzerError: "E.CODE",
cfeError: "Error.",
webError: "Web.");
void testProperties() {
var analyzer = StaticError(ErrorSource.analyzer, "E.CODE",
line: 1, column: 2, length: 3, sourceLines: {1, 3, 5});
Expect.equals(analyzer.source, ErrorSource.analyzer);
Expect.equals(analyzer.message, "E.CODE");
Expect.equals(analyzer.line, 1);
Expect.equals(analyzer.column, 2);
Expect.equals(analyzer.length, 3);
Expect.isTrue(analyzer.isSpecified);
Expect.setEquals({1, 3, 5}, analyzer.sourceLines);
Expect.isTrue(analyzer.hasError(ErrorSource.analyzer));
Expect.isFalse(analyzer.hasError(ErrorSource.cfe));
Expect.isFalse(analyzer.hasError(ErrorSource.web));
var cfe = StaticError(ErrorSource.cfe, "Error.", line: 4, column: 5);
Expect.equals(cfe.source, ErrorSource.cfe);
Expect.equals(cfe.message, "Error.");
Expect.equals(cfe.line, 4);
Expect.equals(cfe.column, 5);
Expect.isNull(cfe.length);
Expect.isTrue(cfe.isSpecified);
Expect.isTrue(cfe.sourceLines.isEmpty);
Expect.isFalse(cfe.hasError(ErrorSource.analyzer));
Expect.isTrue(cfe.hasError(ErrorSource.cfe));
Expect.isFalse(cfe.hasError(ErrorSource.web));
Expect.isFalse(web.hasError(ErrorSource.analyzer));
Expect.isFalse(web.hasError(ErrorSource.cfe));
Expect.isTrue(web.hasError(ErrorSource.web));
Expect.isTrue(all.hasError(ErrorSource.analyzer));
Expect.isTrue(all.hasError(ErrorSource.cfe));
Expect.isTrue(all.hasError(ErrorSource.web));
}
void testErrorFor() {
var analyzer =
makeError(line: 1, column: 2, length: 3, analyzerError: "E.CODE");
var cfe = makeError(line: 1, column: 2, length: 3, cfeError: "Error.");
var web = makeError(line: 1, column: 2, length: 3, webError: "Web.");
var all = makeError(
line: 1,
column: 2,
length: 3,
analyzerError: "E.CODE",
cfeError: "Error.",
webError: "Web.");
Expect.equals("E.CODE", analyzer.errorFor(ErrorSource.analyzer));
Expect.isNull(analyzer.errorFor(ErrorSource.cfe));
Expect.isNull(analyzer.errorFor(ErrorSource.web));
Expect.isNull(cfe.errorFor(ErrorSource.analyzer));
Expect.equals("Error.", cfe.errorFor(ErrorSource.cfe));
Expect.isNull(cfe.errorFor(ErrorSource.web));
Expect.isNull(web.errorFor(ErrorSource.analyzer));
Expect.isNull(web.errorFor(ErrorSource.cfe));
Expect.equals("Web.", web.errorFor(ErrorSource.web));
Expect.equals("E.CODE", all.errorFor(ErrorSource.analyzer));
Expect.equals("Error.", all.errorFor(ErrorSource.cfe));
Expect.equals("Web.", all.errorFor(ErrorSource.web));
var unspecified =
StaticError(ErrorSource.web, "unspecified", line: 1, column: 2);
Expect.isFalse(unspecified.isSpecified);
}
void testIsWarning() {
@ -95,180 +57,29 @@ void testIsWarning() {
// Web only.
Expect.isFalse(makeError(webError: "Any error message.").isWarning);
// Multiple front ends.
Expect.isFalse(makeError(
analyzerError: "STATIC_WARNING.INVALID_OPTION",
cfeError: "Any error message.")
.isWarning);
Expect.isFalse(
makeError(cfeError: "Any error message.", webError: "Any error message.")
.isWarning);
Expect.isFalse(makeError(
analyzerError: "STATIC_WARNING.INVALID_OPTION",
webError: "Any error message.")
.isWarning);
Expect.isFalse(makeError(
analyzerError: "STATIC_WARNING.INVALID_OPTION",
cfeError: "Any error message.",
webError: "Any error message.")
.isWarning);
}
void testIsSpecifiedFor() {
var specifiedAll = makeError(
line: 1,
column: 2,
length: 3,
analyzerError: "ERR.CODE",
cfeError: "Message.",
webError: "Web.");
var unspecifiedAll = makeError(
line: 1,
column: 2,
length: 3,
analyzerError: "unspecified",
cfeError: "unspecified",
webError: "unspecified");
var specifiedAnalyzer = makeError(
line: 1,
column: 2,
length: 3,
analyzerError: "ERR.CODE",
cfeError: "unspecified",
webError: "unspecified");
var specifiedCfe = makeError(
line: 1,
column: 2,
length: 3,
analyzerError: "unspecified",
cfeError: "Message.",
webError: "unspecified");
var specifiedWeb = makeError(
line: 1,
column: 2,
length: 3,
analyzerError: "unspecified",
cfeError: "unspecified",
webError: "Web.");
var specifiedAnalyzerOnly =
makeError(line: 1, column: 2, length: 3, analyzerError: "ERR.CODE");
var specifiedCfeOnly =
makeError(line: 1, column: 2, length: 3, cfeError: "Message.");
var specifiedWebOnly =
makeError(line: 1, column: 2, length: 3, webError: "Web.");
var unspecifiedAnalyzerOnly =
makeError(line: 1, column: 2, length: 3, analyzerError: "unspecified");
var unspecifiedCfeOnly =
makeError(line: 1, column: 2, length: 3, cfeError: "unspecified");
var unspecifiedWebOnly =
makeError(line: 1, column: 2, length: 3, webError: "unspecified");
var analyzer =
makeError(line: 1, column: 2, length: 3, analyzerError: "E.CODE");
var cfe = makeError(line: 1, column: 2, length: 3, cfeError: "E.");
var web = makeError(line: 1, column: 2, length: 3, webError: "E.");
// isSpecifiedFor().
Expect.isTrue(specifiedAll.isSpecifiedFor(analyzer));
Expect.isTrue(specifiedAll.isSpecifiedFor(cfe));
Expect.isTrue(specifiedAll.isSpecifiedFor(web));
Expect.isFalse(unspecifiedAll.isSpecifiedFor(analyzer));
Expect.isFalse(unspecifiedAll.isSpecifiedFor(cfe));
Expect.isFalse(unspecifiedAll.isSpecifiedFor(web));
Expect.isTrue(specifiedAnalyzer.isSpecifiedFor(analyzer));
Expect.isFalse(specifiedAnalyzer.isSpecifiedFor(cfe));
Expect.isFalse(specifiedAnalyzer.isSpecifiedFor(web));
Expect.isFalse(specifiedCfe.isSpecifiedFor(analyzer));
Expect.isTrue(specifiedCfe.isSpecifiedFor(cfe));
Expect.isFalse(specifiedCfe.isSpecifiedFor(web));
Expect.isFalse(specifiedWeb.isSpecifiedFor(analyzer));
Expect.isFalse(specifiedWeb.isSpecifiedFor(cfe));
Expect.isTrue(specifiedWeb.isSpecifiedFor(web));
Expect.isTrue(specifiedAnalyzerOnly.isSpecifiedFor(analyzer));
Expect.isFalse(specifiedAnalyzerOnly.isSpecifiedFor(cfe));
Expect.isFalse(specifiedAnalyzerOnly.isSpecifiedFor(web));
Expect.isFalse(specifiedCfeOnly.isSpecifiedFor(analyzer));
Expect.isTrue(specifiedCfeOnly.isSpecifiedFor(cfe));
Expect.isFalse(specifiedCfeOnly.isSpecifiedFor(web));
Expect.isFalse(specifiedWebOnly.isSpecifiedFor(analyzer));
Expect.isFalse(specifiedWebOnly.isSpecifiedFor(cfe));
Expect.isTrue(specifiedWebOnly.isSpecifiedFor(web));
Expect.isFalse(unspecifiedAnalyzerOnly.isSpecifiedFor(analyzer));
Expect.isFalse(unspecifiedAnalyzerOnly.isSpecifiedFor(cfe));
Expect.isFalse(unspecifiedAnalyzerOnly.isSpecifiedFor(web));
Expect.isFalse(unspecifiedCfeOnly.isSpecifiedFor(analyzer));
Expect.isFalse(unspecifiedCfeOnly.isSpecifiedFor(cfe));
Expect.isFalse(unspecifiedCfeOnly.isSpecifiedFor(web));
Expect.isFalse(unspecifiedWebOnly.isSpecifiedFor(analyzer));
Expect.isFalse(unspecifiedWebOnly.isSpecifiedFor(cfe));
Expect.isFalse(unspecifiedWebOnly.isSpecifiedFor(web));
}
void testCompareTo() {
var errors = [
// Order by line.
makeError(
line: 1, column: 2, length: 2, analyzerError: "E.CODE", cfeError: "E."),
makeError(
line: 2, column: 1, length: 1, analyzerError: "E.CODE", cfeError: "E."),
makeError(line: 1, column: 2, length: 2, cfeError: "E."),
makeError(line: 2, column: 1, length: 1, cfeError: "E."),
// Then column.
makeError(
line: 3, column: 1, length: 2, analyzerError: "E.CODE", cfeError: "E."),
makeError(
line: 3,
column: 2,
length: 1,
analyzerError: "Error.CODE",
cfeError: "E."),
makeError(line: 3, column: 1, length: 2, cfeError: "E."),
makeError(line: 3, column: 2, length: 1, cfeError: "E."),
// Then length.
makeError(
line: 4, column: 1, length: 1, analyzerError: "Z.CODE", cfeError: "Z."),
makeError(
line: 4, column: 1, length: 2, analyzerError: "A.CODE", cfeError: "A."),
makeError(line: 4, column: 1, length: 1, cfeError: "Z."),
makeError(line: 4, column: 1, length: 2, cfeError: "A."),
// Then analyzer error.
makeError(line: 5, column: 1, length: 1, cfeError: "Z."),
makeError(
line: 5, column: 1, length: 1, analyzerError: "A.CODE", cfeError: "Z."),
makeError(
line: 5, column: 1, length: 1, analyzerError: "Z.CODE", cfeError: "Z."),
// Then source.
makeError(line: 5, column: 1, length: 1, analyzerError: "Z.CODE"),
makeError(line: 5, column: 1, length: 1, cfeError: "A."),
// Then CFE error.
makeError(line: 6, column: 1, length: 1, analyzerError: "E.CODE"),
makeError(
line: 6,
column: 1,
length: 1,
analyzerError: "E.CODE",
cfeError: "A.",
webError: "Z."),
makeError(
line: 6,
column: 1,
length: 1,
analyzerError: "E.CODE",
cfeError: "Z.",
webError: "A."),
// Then web error.
makeError(line: 7, column: 1, length: 1, cfeError: "E."),
makeError(line: 7, column: 1, length: 1, cfeError: "E.", webError: "A."),
makeError(line: 7, column: 1, length: 1, cfeError: "E.", webError: "Z."),
// Then message.
makeError(line: 6, column: 1, length: 1, cfeError: "A."),
makeError(line: 6, column: 1, length: 1, cfeError: "Z."),
];
// Every pair of errors in the array should be ordered correctly.
@ -281,347 +92,15 @@ void testCompareTo() {
}
}
void testDescribeDifferences() {
var precise = makeError(
line: 2,
column: 3,
length: 4,
analyzerError: "Error.CODE",
cfeError: "Error message.",
webError: "Web error.");
// Perfect match.
expectNoDifferences(precise,
makeError(line: 2, column: 3, length: 4, analyzerError: "Error.CODE"));
expectNoDifferences(precise,
makeError(line: 2, column: 3, length: 4, cfeError: "Error message."));
expectNoDifferences(precise,
makeError(line: 2, column: 3, length: 4, webError: "Web error."));
// Ignore null analyzer error.
expectNoDifferences(
makeError(
line: 2,
column: 3,
length: 4,
cfeError: "Error message.",
webError: "Web error."),
makeError(line: 2, column: 3, length: 4, cfeError: "Error message."));
// Ignore null CFE error.
expectNoDifferences(
makeError(
line: 2,
column: 3,
length: 4,
analyzerError: "Error.CODE",
webError: "Web error."),
makeError(line: 2, column: 3, length: 4, analyzerError: "Error.CODE"));
// Ignore null web error.
expectNoDifferences(
makeError(
line: 2,
column: 3,
length: 4,
analyzerError: "Error.CODE",
cfeError: "Error message."),
makeError(line: 2, column: 3, length: 4, cfeError: "Error message."));
// Different line.
expectDifferences(precise,
makeError(line: 4, column: 3, length: 4, analyzerError: "Error.CODE"), """
Expected on line 2 but was on 4.
""");
// Different column.
expectDifferences(precise,
makeError(line: 2, column: 5, length: 4, cfeError: "Error message."), """
Expected on column 3 but was on 5.
""");
// Different length.
expectDifferences(precise,
makeError(line: 2, column: 3, length: 6, webError: "Web error."), """
Expected length 4 but was 6.
""");
// Different analyzer error.
expectDifferences(
precise,
makeError(line: 2, column: 3, length: 4, analyzerError: "Weird.ERROR"),
"""
Expected analyzer error 'Error.CODE' but was 'Weird.ERROR'.
""");
// Different CFE error.
expectDifferences(precise,
makeError(line: 2, column: 3, length: 4, cfeError: "Funny story."), """
Expected CFE error 'Error message.' but was 'Funny story.'.
""");
// Different web error.
expectDifferences(precise,
makeError(line: 2, column: 3, length: 4, webError: "Funny story."), """
Expected web error 'Web error.' but was 'Funny story.'.
""");
// Multiple differences.
expectDifferences(
precise,
makeError(line: 4, column: 3, length: 6, analyzerError: "Weird.ERROR"),
"""
Expected on line 2 but was on 4.
Expected length 4 but was 6.
Expected analyzer error 'Error.CODE' but was 'Weird.ERROR'.
""");
// Unspecified errors.
var unspecified = makeError(
line: 2,
column: 3,
length: 4,
analyzerError: "unspecified",
cfeError: "unspecified",
webError: "unspecified");
var specifiedAnalyzer = makeError(
line: 2,
column: 3,
length: 4,
analyzerError: "Error.CODE",
cfeError: "unspecified",
webError: "unspecified");
var specifiedCfe = makeError(
line: 2,
column: 3,
length: 4,
analyzerError: "unspecified",
cfeError: "Error message.",
webError: "unspecified");
var specifiedWeb = makeError(
line: 2,
column: 3,
length: 4,
analyzerError: "unspecified",
cfeError: "unspecified",
webError: "Web error.");
// Matches if line is right.
expectNoDifferences(unspecified,
makeError(line: 2, column: 3, length: 4, analyzerError: "Error.CODE"));
// Does not match if lines differ.
expectDifferences(unspecified,
makeError(line: 3, column: 3, length: 4, cfeError: "Error message."), """
Expected on line 2 but was on 3.
""");
// If error is specified on analyzer, must match fields when actual is
// analyzer error.
expectDifferences(
specifiedAnalyzer,
makeError(line: 2, column: 5, length: 6, analyzerError: "Weird.ERROR"),
"""
Expected on column 3 but was on 5.
Expected length 4 but was 6.
Expected analyzer error 'Error.CODE' but was 'Weird.ERROR'.
""");
expectNoDifferences(specifiedAnalyzer,
makeError(line: 2, column: 3, length: 4, analyzerError: "Error.CODE"));
// If error is specified on CFE, must match fields when actual is
// CFE error.
expectDifferences(
specifiedCfe,
makeError(line: 2, column: 5, length: 6, cfeError: "Different message."),
"""
Expected on column 3 but was on 5.
Expected length 4 but was 6.
Expected CFE error 'Error message.' but was 'Different message.'.
""");
expectNoDifferences(specifiedCfe,
makeError(line: 2, column: 3, length: 4, cfeError: "Error message."));
// If error is specified on web, must match fields when actual is web error.
expectDifferences(
specifiedWeb,
makeError(line: 2, column: 5, length: 6, webError: "Different message."),
"""
Expected on column 3 but was on 5.
Expected length 4 but was 6.
Expected web error 'Web error.' but was 'Different message.'.
""");
expectNoDifferences(specifiedWeb,
makeError(line: 2, column: 3, length: 4, webError: "Web error."));
}
void testSimplify() {
// Merges errors if each has only one error.
expectSimplify([
makeError(line: 1, column: 2, length: 3, analyzerError: "Weird.ERROR"),
makeError(line: 1, column: 2, length: 3, cfeError: "Message."),
makeError(line: 1, column: 2, length: 3, webError: "Web.")
], [
makeError(
line: 1,
column: 2,
length: 3,
analyzerError: "Weird.ERROR",
cfeError: "Message.",
webError: "Web.")
]);
// Merges if length is null.
expectSimplify([
makeError(line: 1, column: 1, analyzerError: "A.ERR"),
makeError(line: 1, column: 1, length: 3, cfeError: "A."),
makeError(line: 2, column: 1, length: 4, analyzerError: "B.ERR"),
makeError(line: 2, column: 1, webError: "B."),
makeError(line: 3, column: 1, analyzerError: "C.ERR"),
makeError(line: 3, column: 1, cfeError: "C."),
], [
makeError(
line: 1, column: 1, length: 3, analyzerError: "A.ERR", cfeError: "A."),
makeError(
line: 2, column: 1, length: 4, analyzerError: "B.ERR", webError: "B."),
makeError(line: 3, column: 1, analyzerError: "C.ERR", cfeError: "C."),
]);
// Merges multiple errors with no length with errors that have length.
expectSimplify([
makeError(line: 1, column: 2, length: 3, analyzerError: "ERROR.A"),
makeError(line: 1, column: 4, length: 3, analyzerError: "ERROR.C"),
makeError(line: 1, column: 2, length: 5, analyzerError: "ERROR.B"),
makeError(line: 1, column: 2, cfeError: "One."),
makeError(line: 1, column: 4, cfeError: "Three."),
makeError(line: 1, column: 2, cfeError: "Two."),
makeError(line: 1, column: 2, webError: "Web 1."),
makeError(line: 1, column: 2, webError: "Web 2."),
], [
makeError(
line: 1,
column: 2,
length: 3,
analyzerError: "ERROR.A",
cfeError: "One.",
webError: "Web 1."),
makeError(
line: 1,
column: 2,
length: 5,
analyzerError: "ERROR.B",
cfeError: "Two.",
webError: "Web 2."),
makeError(
line: 1,
column: 4,
length: 3,
analyzerError: "ERROR.C",
cfeError: "Three."),
]);
// Merges even if not adjacent in input array.
expectSimplify([
makeError(line: 1, column: 2, length: 3, analyzerError: "Some.ERROR"),
makeError(line: 10, column: 2, length: 3, analyzerError: "Other.ERROR"),
makeError(line: 1, column: 2, length: 3, cfeError: "Message."),
makeError(line: 10, column: 2, length: 3, webError: "Web two."),
makeError(line: 1, column: 2, length: 3, webError: "Web."),
], [
makeError(
line: 1,
column: 2,
length: 3,
analyzerError: "Some.ERROR",
cfeError: "Message.",
webError: "Web."),
makeError(
line: 10,
column: 2,
length: 3,
analyzerError: "Other.ERROR",
webError: "Web two.")
]);
// Does not merge if positions differ.
expectSimplify([
makeError(line: 1, column: 1, length: 1, analyzerError: "A.ERR"),
makeError(line: 2, column: 1, length: 1, cfeError: "A."),
], [
makeError(line: 1, column: 1, length: 1, analyzerError: "A.ERR"),
makeError(line: 2, column: 1, length: 1, cfeError: "A."),
]);
expectSimplify([
makeError(line: 1, column: 1, length: 1, analyzerError: "A.ERR"),
makeError(line: 1, column: 2, length: 1, webError: "A."),
], [
makeError(line: 1, column: 1, length: 1, analyzerError: "A.ERR"),
makeError(line: 1, column: 2, length: 1, webError: "A."),
]);
expectSimplify([
makeError(line: 1, column: 1, length: 1, cfeError: "A."),
makeError(line: 1, column: 1, length: 2, webError: "W."),
], [
makeError(line: 1, column: 1, length: 1, cfeError: "A."),
makeError(line: 1, column: 1, length: 2, webError: "W."),
]);
// Does not merge if it would lose a message.
expectSimplify([
makeError(line: 1, column: 1, length: 1, analyzerError: "ERR.ONE"),
makeError(line: 1, column: 1, length: 1, analyzerError: "ERR.TWO"),
makeError(line: 2, column: 1, length: 1, cfeError: "One."),
makeError(line: 2, column: 1, length: 1, cfeError: "Two."),
makeError(line: 3, column: 1, length: 1, webError: "One."),
makeError(line: 3, column: 1, length: 1, webError: "Two."),
], [
makeError(line: 1, column: 1, length: 1, analyzerError: "ERR.ONE"),
makeError(line: 1, column: 1, length: 1, analyzerError: "ERR.TWO"),
makeError(line: 2, column: 1, length: 1, cfeError: "One."),
makeError(line: 2, column: 1, length: 1, cfeError: "Two."),
makeError(line: 3, column: 1, length: 1, webError: "One."),
makeError(line: 3, column: 1, length: 1, webError: "Two."),
]);
// Orders output.
expectSimplify([
makeError(line: 2, column: 1, length: 1, cfeError: "Two."),
makeError(line: 3, column: 1, length: 1, cfeError: "Three."),
makeError(line: 1, column: 1, length: 1, cfeError: "One."),
], [
makeError(line: 1, column: 1, length: 1, cfeError: "One."),
makeError(line: 2, column: 1, length: 1, cfeError: "Two."),
makeError(line: 3, column: 1, length: 1, cfeError: "Three."),
]);
}
void testValidate() {
// No errors.
expectValidate([], [], null);
// Same errors.
expectValidate([
makeError(
line: 1,
column: 2,
length: 3,
analyzerError: "ERR.A",
cfeError: "One.",
webError: "Web 1."),
makeError(
line: 2,
column: 2,
length: 3,
analyzerError: "ERR.B",
cfeError: "Two.",
webError: "Web 2."),
makeError(
line: 3,
column: 2,
length: 3,
analyzerError: "ERR.C",
cfeError: "Tres.",
webError: "Web 3."),
makeError(line: 1, column: 2, length: 3, analyzerError: "ERR.A"),
makeError(line: 2, column: 2, length: 3, analyzerError: "ERR.B"),
makeError(line: 3, column: 2, length: 3, analyzerError: "ERR.C"),
], [
// Order doesn't matter.
makeError(line: 3, column: 2, length: 3, analyzerError: "ERR.C"),
@ -629,35 +108,6 @@ void testValidate() {
makeError(line: 2, column: 2, length: 3, analyzerError: "ERR.B"),
], null);
// Ignore fields that aren't in actual errors.
expectValidate([
makeError(
line: 1,
column: 2,
length: 3,
analyzerError: "ERR.A",
cfeError: "One.",
webError: "Web 1."),
makeError(
line: 2,
column: 2,
length: 3,
analyzerError: "ERR.B",
cfeError: "Two.",
webError: "Web 2."),
makeError(
line: 3,
column: 2,
length: 3,
analyzerError: "ERR.C",
cfeError: "Tres.",
webError: "Web 3."),
], [
makeError(line: 1, column: 2, cfeError: "One."),
makeError(line: 2, column: 2, length: 3, cfeError: "Two."),
makeError(line: 3, column: 2, length: 3, cfeError: "Tres."),
], null);
// Catches differences in any field.
expectValidate([
makeError(line: 1, column: 2, length: 3, analyzerError: "ERR.A"),
@ -668,52 +118,36 @@ void testValidate() {
makeError(line: 2, column: 2, length: 9, analyzerError: "ERR.B"),
makeError(line: 3, column: 2, length: 3, analyzerError: "ERR.Z"),
], """
Wrong static error at line 1, column 2, length 3:
- Expected on column 2 but was on 9.
- Wrong error location line 1, column 2, length 3: ERR.A
Expected column 2 but was column 9.
Wrong static error at line 2, column 2, length 3:
- Expected length 3 but was 9.
- Wrong error location line 2, column 2, length 3: ERR.B
Expected length 2 but was length 2.
Wrong static error at line 3, column 2, length 3:
- Expected analyzer error 'ERR.C' but was 'ERR.Z'.""");
- Wrong message at line 3, column 2, length 3: ERR.Z
Expected: ERR.C""");
expectValidate([
makeError(line: 4, column: 2, length: 3, cfeError: "Four."),
], [
makeError(line: 4, column: 2, length: 3, cfeError: "Zzz."),
], """
Wrong static error at line 4, column 2, length 3:
- Expected CFE error 'Four.' but was 'Zzz.'.""");
- Wrong message at line 4, column 2, length 3: Zzz.
Expected: Four.""");
expectValidate([
makeError(line: 5, column: 2, length: 3, webError: "Web 5."),
], [
makeError(line: 5, column: 2, length: 3, webError: "Web Z."),
], """
Wrong static error at line 5, column 2, length 3:
- Expected web error 'Web 5.' but was 'Web Z.'.""");
- Wrong message at line 5, column 2, length 3: Web Z.
Expected: Web 5.""");
// Unexpected errors.
expectValidate([
makeError(
line: 2,
column: 2,
length: 3,
analyzerError: "ERR.A",
cfeError: "One."),
makeError(
line: 4,
column: 2,
length: 3,
analyzerError: "ERR.B",
cfeError: "Two.",
webError: "Web 2."),
makeError(
line: 6,
column: 2,
length: 3,
analyzerError: "ERR.C",
cfeError: "Tres."),
makeError(line: 2, column: 2, length: 3, cfeError: "One."),
makeError(line: 4, column: 2, length: 3, cfeError: "Two."),
makeError(line: 6, column: 2, length: 3, cfeError: "Tres."),
], [
makeError(line: 1, column: 2, length: 3, cfeError: "1."),
makeError(line: 2, column: 2, length: 3, cfeError: "One."),
@ -723,17 +157,13 @@ Wrong static error at line 5, column 2, length 3:
makeError(line: 6, column: 2, length: 3, cfeError: "Tres."),
makeError(line: 7, column: 2, length: 3, cfeError: "7."),
], """
Unexpected static error at line 1, column 2, length 3:
- Had CFE error '1.'.
- Unexpected error at line 1, column 2, length 3: 1.
Unexpected static error at line 3, column 2, length 3:
- Had CFE error '3.'.
- Unexpected error at line 3, column 2, length 3: 3.
Unexpected static error at line 5, column 2, length 3:
- Had CFE error '5.'.
- Unexpected error at line 5, column 2, length 3: 5.
Unexpected static error at line 7, column 2, length 3:
- Had CFE error '7.'.""");
- Unexpected error at line 7, column 2, length 3: 7.""");
// Missing errors.
expectValidate([
@ -746,14 +176,11 @@ Unexpected static error at line 7, column 2, length 3:
makeError(line: 2, column: 2, length: 3, analyzerError: "ERR.B"),
makeError(line: 4, column: 2, length: 3, analyzerError: "ERR.D"),
], """
Missing static error at line 1, column 2, length 3:
- Expected analyzer error 'ERR.A'.
- Missing expected error at line 1, column 2, length 3: ERR.A
Missing static error at line 3, column 2, length 3:
- Expected analyzer error 'ERR.C'.
- Missing expected error at line 3, column 2, length 3: ERR.C
Missing static error at line 5, column 2, length 3:
- Expected analyzer error 'ERR.E'.""");
- Missing expected error at line 5, column 2, length 3: ERR.E""");
// Unspecified errors.
expectValidate([
@ -768,161 +195,34 @@ Missing static error at line 5, column 2, length 3:
// Unexpected.
makeError(line: 9, column: 9, length: 3, cfeError: "Actual 2."),
], """
Missing static error at line 2, column 2, length 3:
- Expected unspecified CFE error.
- Missing expected unspecified error at line 2, column 2, length 3.
Unexpected static error at line 9, column 9, length 3:
- Had CFE error 'Actual 2.'.""");
- Unexpected error at line 9, column 9, length 3: Actual 2.""");
// Unspecified errors can match multiple errors on the same line.
var actualAnalyzer = [
expectValidate([
makeError(line: 1, column: 2, length: 3, analyzerError: "unspecified"),
], [
makeError(line: 1, column: 1, length: 3, analyzerError: "ERROR.CODE1"),
makeError(line: 1, column: 2, length: 3, analyzerError: "ERROR.CODE2"),
makeError(line: 1, column: 3, length: 3, analyzerError: "ERROR.CODE3"),
];
var actualCfe = [
makeError(line: 1, column: 1, length: 3, cfeError: "Actual 1."),
makeError(line: 1, column: 2, length: 3, cfeError: "Actual 2."),
makeError(line: 1, column: 3, length: 3, cfeError: "Actual 3."),
];
var actualWeb = [
makeError(line: 1, column: 1, length: 3, webError: "Web 1."),
makeError(line: 1, column: 2, length: 3, webError: "Web 2."),
makeError(line: 1, column: 3, length: 3, webError: "Web 3."),
];
// Unspecified error specific to one front end.
expectValidate([
makeError(line: 1, column: 2, length: 3, analyzerError: "unspecified"),
], actualAnalyzer, null);
], null);
expectValidate([
makeError(line: 1, column: 2, length: 3, cfeError: "unspecified"),
], actualCfe, null);
], [
makeError(line: 1, column: 1, length: 3, cfeError: "Actual 1."),
makeError(line: 1, column: 2, length: 3, cfeError: "Actual 2."),
makeError(line: 1, column: 3, length: 3, cfeError: "Actual 3."),
], null);
expectValidate([
makeError(line: 1, column: 2, length: 3, webError: "unspecified"),
], actualWeb, null);
// Unspecified error on multiple front ends.
expectValidate([
makeError(
line: 1,
column: 2,
length: 3,
analyzerError: "unspecified",
cfeError: "unspecified"),
], actualAnalyzer, null);
expectValidate([
makeError(
line: 1,
column: 2,
length: 3,
cfeError: "unspecified",
webError: "unspecified"),
], actualCfe, null);
expectValidate([
makeError(
line: 1,
column: 2,
length: 3,
analyzerError: "unspecified",
webError: "unspecified"),
], actualWeb, null);
expectValidate([
makeError(
line: 1,
column: 2,
length: 3,
analyzerError: "unspecified",
cfeError: "unspecified",
webError: "unspecified"),
], actualAnalyzer, null);
// Specified on one, unspecified on another, no error at all on the third.
var specifiedAnalyzer = [
makeError(
line: 1,
column: 1,
length: 3,
analyzerError: "ERROR.CODE1",
cfeError: "unspecified")
];
var specifiedCfe = [
makeError(
line: 1,
column: 1,
length: 3,
cfeError: "Actual 1.",
webError: "unspecified")
];
var specifiedWeb = [
makeError(
line: 1,
column: 1,
length: 3,
analyzerError: "unspecified",
webError: "Web 1.")
];
expectValidate(specifiedAnalyzer, actualCfe, null);
expectValidate(specifiedCfe, actualWeb, null);
expectValidate(specifiedWeb, actualAnalyzer, null);
expectValidate(specifiedAnalyzer, actualAnalyzer, """
Unexpected static error at line 1, column 2, length 3:
- Had analyzer error 'ERROR.CODE2'.
Unexpected static error at line 1, column 3, length 3:
- Had analyzer error 'ERROR.CODE3'.""");
expectValidate(specifiedCfe, actualCfe, """
Unexpected static error at line 1, column 2, length 3:
- Had CFE error 'Actual 2.'.
Unexpected static error at line 1, column 3, length 3:
- Had CFE error 'Actual 3.'.""");
expectValidate(specifiedWeb, actualWeb, """
Unexpected static error at line 1, column 2, length 3:
- Had web error 'Web 2.'.
Unexpected static error at line 1, column 3, length 3:
- Had web error 'Web 3.'.""");
}
void expectNoDifferences(StaticError expectedError, StaticError actualError) {
var actualLines = expectedError.describeDifferences(actualError);
if (actualLines != null) {
Expect.fail("Expected no differences, but got:\n${actualLines.join('\n')}");
}
}
void expectDifferences(StaticError expectedError, StaticError actualError,
String expectedDifferences) {
var expectedLines = expectedDifferences
.split("\n")
.map((line) => line.trim())
.where((line) => line.isNotEmpty)
.toList();
var actualLines = expectedError.describeDifferences(actualError);
if (actualLines == null) {
Expect.fail("Got no differences, but expected:\n$expectedDifferences");
}
Expect.listEquals(expectedLines, actualLines);
}
void expectSimplify(List<StaticError> input, List<StaticError> expected) {
var actual = StaticError.simplify(input);
Expect.listEquals(expected.map((error) => error.toString()).toList(),
actual.map((error) => error.toString()).toList());
], [
makeError(line: 1, column: 1, length: 3, webError: "Web 1."),
makeError(line: 1, column: 2, length: 3, webError: "Web 2."),
makeError(line: 1, column: 3, length: 3, webError: "Web 3."),
], null);
}
void expectValidate(List<StaticError> expected, List<StaticError> actual,

View file

@ -299,6 +299,7 @@ int i = "s";
/\/ ^^^
/\/ [analyzer] CompileTimeErrorCode.WRONG_TYPE
/\/ [cfe] Error: Can't assign a string to an int.
/\/ [cfe] Another CFE error.
/\/ [web] Web-specific error.
num j = "str";
@ -311,16 +312,25 @@ num j = "str";
line: 1,
column: 9,
length: 3,
analyzerError: "CompileTimeErrorCode.WRONG_TYPE",
cfeError: "Error: Can't assign a string to an int.",
webError: "Web-specific error."),
analyzerError: "CompileTimeErrorCode.WRONG_TYPE"),
makeError(
line: 7,
line: 1,
column: 9,
length: 3,
cfeError: "Error: Can't assign a string to an int."),
makeError(line: 1, column: 9, length: 3, cfeError: "Another CFE error."),
makeError(line: 1, column: 9, length: 3, webError: "Web-specific error."),
makeError(
line: 8,
column: 9,
length: 5,
analyzerError: "CompileTimeErrorCode.ALSO_WRONG_TYPE",
cfeError: "Error: Can't assign a string to a num.",
webError: "Another web error.")
analyzerError: "CompileTimeErrorCode.ALSO_WRONG_TYPE"),
makeError(
line: 8,
column: 9,
length: 5,
cfeError: "Error: Can't assign a string to a num."),
makeError(line: 8, column: 9, length: 5, webError: "Another web error.")
]);
// Explicit error location.
@ -341,15 +351,15 @@ num j = "str";
line: 123,
column: 45,
length: 678,
analyzerError: "CompileTimeErrorCode.FIRST",
cfeError: "First error."),
analyzerError: "CompileTimeErrorCode.FIRST"),
makeError(line: 123, column: 45, length: 678, cfeError: "First error."),
makeError(
line: 23,
column: 5,
length: 78,
analyzerError: "CompileTimeErrorCode.SECOND",
cfeError: "Second error.",
webError: "Second web error."),
analyzerError: "CompileTimeErrorCode.SECOND"),
makeError(line: 23, column: 5, length: 78, cfeError: "Second error."),
makeError(line: 23, column: 5, length: 78, webError: "Second web error."),
makeError(line: 9, column: 8, length: 7, cfeError: "Third."),
makeError(line: 10, column: 9, cfeError: "No length.")
]);
@ -372,8 +382,16 @@ int i = "s";
line: 1,
column: 9,
length: 3,
analyzerError: "CompileTimeErrorCode.WRONG_TYPE",
cfeError: "First line.\nSecond line.\nThird line.",
analyzerError: "CompileTimeErrorCode.WRONG_TYPE"),
makeError(
line: 1,
column: 9,
length: 3,
cfeError: "First line.\nSecond line.\nThird line."),
makeError(
line: 1,
column: 9,
length: 3,
webError: "Web first line.\nWeb second line.\nWeb third line.")
]);
@ -418,25 +436,13 @@ int n = "s";
/\/ ^^^
// [web] unspecified
""", [
makeError(
line: 1,
column: 8,
length: 3,
analyzerError: "unspecified",
cfeError: "unspecified",
webError: "unspecified"),
makeError(
line: 6,
column: 8,
length: 3,
analyzerError: "unspecified",
cfeError: "Message."),
makeError(
line: 10,
column: 8,
length: 3,
analyzerError: "Error.CODE",
cfeError: "unspecified"),
makeError(line: 1, column: 8, length: 3, analyzerError: "unspecified"),
makeError(line: 1, column: 8, length: 3, cfeError: "unspecified"),
makeError(line: 1, column: 8, length: 3, webError: "unspecified"),
makeError(line: 6, column: 8, length: 3, analyzerError: "unspecified"),
makeError(line: 6, column: 8, length: 3, cfeError: "Message."),
makeError(line: 10, column: 8, length: 3, analyzerError: "Error.CODE"),
makeError(line: 10, column: 8, length: 3, cfeError: "unspecified"),
makeError(line: 14, column: 8, length: 3, analyzerError: "unspecified"),
makeError(line: 17, column: 8, length: 3, cfeError: "unspecified"),
makeError(line: 20, column: 8, length: 3, webError: "unspecified"),
@ -453,11 +459,9 @@ int i = "s";
/\/ [cfe] Message.
""", [
makeError(
line: 1,
column: 9,
length: 3,
analyzerError: "ErrorCode.BAD_THING",
cfeError: "Message.\nMore message."),
line: 1, column: 9, length: 3, analyzerError: "ErrorCode.BAD_THING"),
makeError(
line: 1, column: 9, length: 3, cfeError: "Message.\nMore message."),
makeError(line: 12, column: 34, length: 56, cfeError: "Message."),
]);
@ -468,12 +472,9 @@ int i = "s";
/\/ [cfe] Error message.
/\/ [analyzer] ErrorCode.BAD_THING
""", [
makeError(line: 1, column: 9, length: 3, cfeError: "Error message."),
makeError(
line: 1,
column: 9,
length: 3,
analyzerError: "ErrorCode.BAD_THING",
cfeError: "Error message."),
line: 1, column: 9, length: 3, analyzerError: "ErrorCode.BAD_THING"),
]);
expectParseErrorExpectations("""
int i = "s";
@ -481,12 +482,9 @@ int i = "s";
/\/ [web] Web message.
/\/ [analyzer] ErrorCode.BAD_THING
""", [
makeError(line: 1, column: 9, length: 3, webError: "Web message."),
makeError(
line: 1,
column: 9,
length: 3,
analyzerError: "ErrorCode.BAD_THING",
webError: "Web message."),
line: 1, column: 9, length: 3, analyzerError: "ErrorCode.BAD_THING"),
]);
expectParseErrorExpectations("""
int i = "s";
@ -494,12 +492,8 @@ int i = "s";
/\/ [web] Web message.
/\/ [cfe] Error message.
""", [
makeError(
line: 1,
column: 9,
length: 3,
cfeError: "Error message.",
webError: "Web message."),
makeError(line: 1, column: 9, length: 3, webError: "Web message."),
makeError(line: 1, column: 9, length: 3, cfeError: "Error message."),
]);
// Must have at least one error message.
@ -538,7 +532,7 @@ int i = "s";
/\/ [analyzer] Not error code.
""");
// A CFE-only error with length one is treated as having no length.
// A CFE error with length one is treated as having no length.
expectParseErrorExpectations("""
int i = "s";
/\/ ^
@ -555,40 +549,11 @@ int j = "s";
/\/ [web] Web message.
""", [
makeError(line: 1, column: 9, length: null, cfeError: "Message."),
makeError(
line: 5,
column: 9,
length: 1,
analyzerError: "Error.BAD",
cfeError: "Message."),
makeError(
line: 10,
column: 9,
length: 1,
cfeError: "Message.",
webError: "Web message.",
),
makeError(line: 5, column: 9, length: 1, analyzerError: "Error.BAD"),
makeError(line: 5, column: 9, length: null, cfeError: "Message."),
makeError(line: 10, column: 9, length: null, cfeError: "Message."),
makeError(line: 10, column: 9, length: 1, webError: "Web message."),
]);
// Cannot have the same front end more than once.
expectFormatError("""
int i = "s";
/\/ ^^^
/\/ [analyzer] ErrorCode.BAD_THING
/\/ [analyzer] ErrorCode.ANOTHER_THING
""");
expectFormatError("""
int i = "s";
/\/ ^^^
/\/ [cfe] Message 1.
/\/ [cfe] Message 2.
""");
expectFormatError("""
int i = "s";
/\/ ^^^
/\/ [web] Web 1.
/\/ [web] Web 2.
""");
}
void testIsRuntimeTest() {

View file

@ -53,31 +53,15 @@ int third = "boo";
int last = "oops";
""", errors: [
makeError(
line: 1,
column: 9,
length: 5,
analyzerError: "some.error",
cfeError: "Bad."),
makeError(
line: 3,
column: 15,
length: 7,
cfeError: "Another bad.",
webError: "Web.\nError."),
makeError(
line: 5,
column: 13,
length: 5,
analyzerError: "third.error",
webError: "Web error."),
makeError(
line: 7,
column: 12,
length: 6,
analyzerError: "last.error",
cfeError: "Final.\nError.",
webError: "Web error."),
makeError(line: 1, column: 9, length: 5, analyzerError: "some.error"),
makeError(line: 1, column: 9, length: 5, cfeError: "Bad."),
makeError(line: 3, column: 15, length: 7, cfeError: "Another bad."),
makeError(line: 3, column: 15, length: 7, webError: "Web.\nError."),
makeError(line: 5, column: 13, length: 5, analyzerError: "third.error"),
makeError(line: 5, column: 13, length: 5, webError: "Web error."),
makeError(line: 7, column: 12, length: 6, analyzerError: "last.error"),
makeError(line: 7, column: 12, length: 6, cfeError: "Final.\nError."),
makeError(line: 7, column: 12, length: 6, webError: "Web error."),
], expected: """
int i = "bad";
/\/ ^^^^^
@ -215,12 +199,9 @@ int i = "bad";
/\/ ^^
/\/ [analyzer] previous.error
""", errors: [
makeError(line: 1, column: 9, length: 5, analyzerError: "updated.error"),
makeError(
line: 1,
column: 9,
length: 5,
analyzerError: "updated.error",
cfeError: "Long.\nError.\nMessage."),
line: 1, column: 9, length: 5, cfeError: "Long.\nError.\nMessage."),
], expected: """
int i = "bad";
/\/ ^^^^^
@ -387,6 +368,28 @@ someBadCode();
/\/ [cfe] Wrong 2.
/\/ ^^^^^
/\/ [cfe] Wrong 1.
""");
// Shared locations between errors with and without length.
expectUpdate("""
someBadCode(arg);
moreBadCode(arg);
""", errors: [
makeError(line: 1, column: 13, length: 3, analyzerError: "Error.CODE"),
makeError(line: 1, column: 13, cfeError: "Wrong 1."),
makeError(line: 3, column: 13, cfeError: "Wrong 2."),
makeError(line: 3, column: 13, length: 3, webError: "Web error."),
], expected: """
someBadCode(arg);
/\/ ^^^
/\/ [analyzer] Error.CODE
/\/ [cfe] Wrong 1.
moreBadCode(arg);
/\/ ^^^
/\/ [web] Web error.
/\/ [cfe] Wrong 2.
""");
// Doesn't crash with RangeError.

View file

@ -26,6 +26,9 @@ StandardTestSuite makeTestSuite(TestConfiguration configuration,
List<TestFile> testFiles, String suite) =>
_MockTestSuite(configuration, testFiles, suite);
/// Creates a [StaticError].
///
/// Only one of [analyzerError], [cfeError], or [webError] may be passed.
StaticError makeError(
{int line = 1,
int column = 2,
@ -33,12 +36,19 @@ StaticError makeError(
String analyzerError,
String cfeError,
String webError}) {
var errors = {
if (analyzerError != null) ErrorSource.analyzer: analyzerError,
if (cfeError != null) ErrorSource.cfe: cfeError,
if (webError != null) ErrorSource.web: webError,
};
return StaticError(errors, line: line, column: column, length: length);
if (analyzerError != null) {
assert(cfeError == null && webError == null);
return StaticError(ErrorSource.analyzer, analyzerError,
line: line, column: column, length: length);
} else if (cfeError != null) {
assert(webError == null);
return StaticError(ErrorSource.cfe, cfeError,
line: line, column: column, length: length);
} else {
assert(webError != null);
return StaticError(ErrorSource.web, webError,
line: line, column: column, length: length);
}
}
class _MockTestSuite extends StandardTestSuite {

View file

@ -203,8 +203,6 @@ Future<void> _processFile(File file,
}
}
errors = StaticError.simplify(errors);
var result = updateErrorExpectations(source, errors, remove: remove);
stdout.writeln("\r${file.path} (Updated with ${errors.length} errors)");
@ -291,8 +289,7 @@ Future<List<StaticError>> runDart2js(
return dart2jsError.line == cfeError.line &&
dart2jsError.column == cfeError.column &&
dart2jsError.length == cfeError.length &&
dart2jsError.errorFor(ErrorSource.web) ==
cfeError.errorFor(ErrorSource.cfe);
dart2jsError.message == cfeError.message;
});
});