Index: pkg/analyzer_cli/lib/src/error_formatter.dart
|
diff --git a/pkg/analyzer_cli/lib/src/error_formatter.dart b/pkg/analyzer_cli/lib/src/error_formatter.dart
|
index bac7f7ce80ad1137acd8a1517ac13209d41eeb88..086edfc52198b2e9f5af0d98d673b0745b35fcbf 100644
|
--- a/pkg/analyzer_cli/lib/src/error_formatter.dart
|
+++ b/pkg/analyzer_cli/lib/src/error_formatter.dart
|
@@ -12,47 +12,36 @@ import 'package:analyzer_cli/src/options.dart';
|
import 'package:path/path.dart' as path;
|
|
/// Returns the given error's severity.
|
-ProcessedSeverity _identity(AnalysisError error) =>
|
+ProcessedSeverity _severityIdentity(AnalysisError error) =>
|
new ProcessedSeverity(error.errorCode.errorSeverity);
|
|
String _pluralize(String word, int count) => count == 1 ? word : word + "s";
|
|
/// Returns desired severity for the given [error] (or `null` if it's to be
|
/// suppressed).
|
-typedef ProcessedSeverity _SeverityProcessor(AnalysisError error);
|
+typedef ProcessedSeverity SeverityProcessor(AnalysisError error);
|
|
/// Analysis statistics counter.
|
class AnalysisStats {
|
/// The total number of diagnostics sent to [formatErrors].
|
- int unfilteredCount;
|
+ int unfilteredCount = 0;
|
|
- int errorCount;
|
- int hintCount;
|
- int lintCount;
|
- int warnCount;
|
+ int errorCount = 0;
|
+ int hintCount = 0;
|
+ int lintCount = 0;
|
+ int warnCount = 0;
|
|
- AnalysisStats() {
|
- init();
|
- }
|
+ AnalysisStats();
|
|
/// The total number of diagnostics reported to the user.
|
int get filteredCount => errorCount + warnCount + hintCount + lintCount;
|
|
- /// (Re)set initial values.
|
- void init() {
|
- unfilteredCount = 0;
|
- errorCount = 0;
|
- hintCount = 0;
|
- lintCount = 0;
|
- warnCount = 0;
|
- }
|
-
|
/// Print statistics to [out].
|
void print(StringSink out) {
|
- var hasErrors = errorCount != 0;
|
- var hasWarns = warnCount != 0;
|
- var hasHints = hintCount != 0;
|
- var hasLints = lintCount != 0;
|
+ bool hasErrors = errorCount != 0;
|
+ bool hasWarns = warnCount != 0;
|
+ bool hasHints = hintCount != 0;
|
+ bool hasLints = lintCount != 0;
|
bool hasContent = false;
|
if (hasErrors) {
|
out.write(errorCount);
|
@@ -73,26 +62,22 @@ class AnalysisStats {
|
out.write(_pluralize("warning", warnCount));
|
hasContent = true;
|
}
|
- if (hasHints) {
|
+ if (hasLints) {
|
if (hasContent) {
|
- if (!hasLints) {
|
- out.write(' and ');
|
- } else {
|
- out.write(", ");
|
- }
|
+ out.write(hasHints ? ', ' : ' and ');
|
}
|
- out.write(hintCount);
|
+ out.write(lintCount);
|
out.write(' ');
|
- out.write(_pluralize("hint", hintCount));
|
+ out.write(_pluralize("lint", lintCount));
|
hasContent = true;
|
}
|
- if (hasLints) {
|
+ if (hasHints) {
|
if (hasContent) {
|
out.write(" and ");
|
}
|
- out.write(lintCount);
|
+ out.write(hintCount);
|
out.write(' ');
|
- out.write(_pluralize("lint", lintCount));
|
+ out.write(_pluralize("hint", hintCount));
|
hasContent = true;
|
}
|
if (hasContent) {
|
@@ -107,166 +92,110 @@ class AnalysisStats {
|
///
|
/// The two format options are a user consumable format and a machine consumable
|
/// format.
|
-class ErrorFormatter {
|
- static final int _pipeCodeUnit = '|'.codeUnitAt(0);
|
- static final int _slashCodeUnit = '\\'.codeUnitAt(0);
|
- static final int _newline = '\n'.codeUnitAt(0);
|
- static final int _return = '\r'.codeUnitAt(0);
|
-
|
+abstract class ErrorFormatter {
|
final StringSink out;
|
final CommandLineOptions options;
|
final AnalysisStats stats;
|
-
|
- final _SeverityProcessor processSeverity;
|
-
|
- AnsiLogger ansi;
|
+ SeverityProcessor _severityProcessor;
|
|
ErrorFormatter(this.out, this.options, this.stats,
|
- [this.processSeverity = _identity]) {
|
- ansi = new AnsiLogger(this.options.color);
|
+ {SeverityProcessor severityProcessor}) {
|
+ _severityProcessor =
|
+ severityProcessor == null ? _severityIdentity : severityProcessor;
|
}
|
|
/// Compute the severity for this [error] or `null` if this error should be
|
/// filtered.
|
- ErrorSeverity computeSeverity(AnalysisError error) =>
|
- processSeverity(error)?.severity;
|
-
|
- void formatError(
|
- Map<AnalysisError, LineInfo> errorToLine, AnalysisError error) {
|
- Source source = error.source;
|
- LineInfo_Location location = errorToLine[error].getLocation(error.offset);
|
- int length = error.length;
|
-
|
- ProcessedSeverity processedSeverity = processSeverity(error);
|
- ErrorSeverity severity = processedSeverity.severity;
|
-
|
- if (options.machineFormat) {
|
- if (!processedSeverity.overridden) {
|
- if (severity == ErrorSeverity.WARNING && options.warningsAreFatal) {
|
- severity = ErrorSeverity.ERROR;
|
- }
|
- }
|
- out.write(severity);
|
- out.write('|');
|
- out.write(error.errorCode.type);
|
- out.write('|');
|
- out.write(error.errorCode.name);
|
- out.write('|');
|
- out.write(escapeForMachineMode(source.fullName));
|
- out.write('|');
|
- out.write(location.lineNumber);
|
- out.write('|');
|
- out.write(location.columnNumber);
|
- out.write('|');
|
- out.write(length);
|
- out.write('|');
|
- out.write(escapeForMachineMode(error.message));
|
- out.writeln();
|
- } else {
|
- // Get display name.
|
- String errorType = severity.displayName;
|
-
|
- // Translate INFOs into LINTS and HINTS.
|
- if (severity == ErrorSeverity.INFO) {
|
- if (error.errorCode.type == ErrorType.HINT ||
|
- error.errorCode.type == ErrorType.LINT) {
|
- errorType = error.errorCode.type.displayName;
|
- }
|
- }
|
-
|
- final int errLength = ErrorSeverity.WARNING.displayName.length;
|
- final int indent = errLength + 5;
|
-
|
- // warning • 'foo' is not a bar at lib/foo.dart:1:2 • foo_warning
|
- String message = error.message;
|
- // Remove any terminating '.' from the end of the message.
|
- if (message.endsWith('.')) {
|
- message = message.substring(0, message.length - 1);
|
- }
|
- String issueColor =
|
- (severity == ErrorSeverity.ERROR || severity == ErrorSeverity.WARNING)
|
- ? ansi.red
|
- : '';
|
- out.write(' $issueColor${errorType.padLeft(errLength)}${ansi.none} '
|
- '${ansi.bullet} ${ansi.bold}$message${ansi.none} ');
|
- String sourceName;
|
- if (source.uriKind == UriKind.DART_URI) {
|
- sourceName = source.uri.toString();
|
- } else if (source.uriKind == UriKind.PACKAGE_URI) {
|
- sourceName = _relative(source.fullName);
|
- if (sourceName == source.fullName) {
|
- // If we weren't able to shorten the path name, use the package: version.
|
- sourceName = source.uri.toString();
|
- }
|
- } else {
|
- sourceName = _relative(source.fullName);
|
- }
|
- out.write('at $sourceName');
|
- out.write(':${location.lineNumber}:${location.columnNumber} ');
|
- out.write('${ansi.bullet} ${error.errorCode.name.toLowerCase()}');
|
- out.writeln();
|
-
|
- // If verbose, also print any associated correction.
|
- if (options.verbose && error.correction != null) {
|
- out.writeln('${' '.padLeft(indent)}${error.correction}');
|
- }
|
- }
|
- }
|
+ ErrorSeverity _computeSeverity(AnalysisError error) =>
|
+ _severityProcessor(error)?.severity;
|
|
void formatErrors(List<AnalysisErrorInfo> errorInfos) {
|
stats.unfilteredCount += errorInfos.length;
|
|
- var errors = new List<AnalysisError>();
|
- var errorToLine = new Map<AnalysisError, LineInfo>();
|
+ List<AnalysisError> errors = new List<AnalysisError>();
|
+ Map<AnalysisError, LineInfo> errorToLine =
|
+ new Map<AnalysisError, LineInfo>();
|
for (AnalysisErrorInfo errorInfo in errorInfos) {
|
for (AnalysisError error in errorInfo.errors) {
|
- if (computeSeverity(error) != null) {
|
+ if (_computeSeverity(error) != null) {
|
errors.add(error);
|
errorToLine[error] = errorInfo.lineInfo;
|
}
|
}
|
}
|
- // Sort errors.
|
- errors.sort((AnalysisError error1, AnalysisError error2) {
|
- // Severity.
|
- ErrorSeverity severity1 = computeSeverity(error1);
|
- ErrorSeverity severity2 = computeSeverity(error2);
|
- int compare = severity2.compareTo(severity1);
|
- if (compare != 0) {
|
- return compare;
|
- }
|
- // Path.
|
- compare = Comparable.compare(error1.source.fullName.toLowerCase(),
|
- error2.source.fullName.toLowerCase());
|
- if (compare != 0) {
|
- return compare;
|
- }
|
- // Offset.
|
- return error1.offset - error2.offset;
|
- });
|
- // Format errors.
|
+
|
for (AnalysisError error in errors) {
|
- ProcessedSeverity processedSeverity = processSeverity(error);
|
- ErrorSeverity severity = processedSeverity.severity;
|
- if (severity == ErrorSeverity.ERROR) {
|
+ formatError(errorToLine, error);
|
+ }
|
+ }
|
+
|
+ void formatError(
|
+ Map<AnalysisError, LineInfo> errorToLine, AnalysisError error);
|
+
|
+ /// Call to write any batched up errors from [formatErrors].
|
+ void flush();
|
+}
|
+
|
+class MachineErrorFormatter extends ErrorFormatter {
|
+ static final int _pipeCodeUnit = '|'.codeUnitAt(0);
|
+ static final int _slashCodeUnit = '\\'.codeUnitAt(0);
|
+ static final int _newline = '\n'.codeUnitAt(0);
|
+ static final int _return = '\r'.codeUnitAt(0);
|
+
|
+ MachineErrorFormatter(
|
+ StringSink out, CommandLineOptions options, AnalysisStats stats,
|
+ {SeverityProcessor severityProcessor})
|
+ : super(out, options, stats, severityProcessor: severityProcessor);
|
+
|
+ void formatError(
|
+ Map<AnalysisError, LineInfo> errorToLine, AnalysisError error) {
|
+ Source source = error.source;
|
+ LineInfo_Location location = errorToLine[error].getLocation(error.offset);
|
+ int length = error.length;
|
+
|
+ ProcessedSeverity processedSeverity = _severityProcessor(error);
|
+ ErrorSeverity severity = processedSeverity.severity;
|
+
|
+ if (!processedSeverity.overridden) {
|
+ if (severity == ErrorSeverity.WARNING && options.warningsAreFatal) {
|
+ severity = ErrorSeverity.ERROR;
|
+ }
|
+ }
|
+
|
+ if (severity == ErrorSeverity.ERROR) {
|
+ stats.errorCount++;
|
+ } else if (severity == ErrorSeverity.WARNING) {
|
+ // Only treat a warning as an error if it's not been set by a processor.
|
+ if (!processedSeverity.overridden && options.warningsAreFatal) {
|
stats.errorCount++;
|
- } else if (severity == ErrorSeverity.WARNING) {
|
- // Only treat a warning as an error if it's not been set by a processor.
|
- if (!processedSeverity.overridden && options.warningsAreFatal) {
|
- stats.errorCount++;
|
- } else {
|
- stats.warnCount++;
|
- }
|
- } else if (error.errorCode.type == ErrorType.HINT) {
|
- stats.hintCount++;
|
- } else if (error.errorCode.type == ErrorType.LINT) {
|
- stats.lintCount++;
|
+ } else {
|
+ stats.warnCount++;
|
}
|
- formatError(errorToLine, error);
|
+ } else if (error.errorCode.type == ErrorType.HINT) {
|
+ stats.hintCount++;
|
+ } else if (error.errorCode.type == ErrorType.LINT) {
|
+ stats.lintCount++;
|
}
|
+
|
+ out.write(severity);
|
+ out.write('|');
|
+ out.write(error.errorCode.type);
|
+ out.write('|');
|
+ out.write(error.errorCode.name);
|
+ out.write('|');
|
+ out.write(_escapeForMachineMode(source.fullName));
|
+ out.write('|');
|
+ out.write(location.lineNumber);
|
+ out.write('|');
|
+ out.write(location.columnNumber);
|
+ out.write('|');
|
+ out.write(length);
|
+ out.write('|');
|
+ out.write(_escapeForMachineMode(error.message));
|
+ out.writeln();
|
}
|
|
- static String escapeForMachineMode(String input) {
|
+ static String _escapeForMachineMode(String input) {
|
StringBuffer result = new StringBuffer();
|
for (int c in input.codeUnits) {
|
if (c == _newline) {
|
@@ -282,12 +211,181 @@ class ErrorFormatter {
|
}
|
return result.toString();
|
}
|
+
|
+ void flush() {}
|
+}
|
+
|
+class HumanErrorFormatter extends ErrorFormatter {
|
+ AnsiLogger ansi;
|
+
|
+ // This is a Set in order to de-dup CLI errors.
|
+ Set<CLIError> batchedErrors = new Set();
|
+
|
+ HumanErrorFormatter(
|
+ StringSink out, CommandLineOptions options, AnalysisStats stats,
|
+ {SeverityProcessor severityProcessor})
|
+ : super(out, options, stats, severityProcessor: severityProcessor) {
|
+ ansi = new AnsiLogger(this.options.color);
|
+ }
|
+
|
+ void formatError(
|
+ Map<AnalysisError, LineInfo> errorToLine, AnalysisError error) {
|
+ Source source = error.source;
|
+ LineInfo_Location location = errorToLine[error].getLocation(error.offset);
|
+
|
+ ProcessedSeverity processedSeverity = _severityProcessor(error);
|
+ ErrorSeverity severity = processedSeverity.severity;
|
+
|
+ // Get display name; translate INFOs into LINTS and HINTS.
|
+ String errorType = severity.displayName;
|
+ if (severity == ErrorSeverity.INFO) {
|
+ if (error.errorCode.type == ErrorType.HINT ||
|
+ error.errorCode.type == ErrorType.LINT) {
|
+ errorType = error.errorCode.type.displayName;
|
+ }
|
+ }
|
+
|
+ // warning • 'foo' is not a bar at lib/foo.dart:1:2 • foo_warning
|
+ String message = error.message;
|
+ // Remove any terminating '.' from the end of the message.
|
+ if (message.endsWith('.')) {
|
+ message = message.substring(0, message.length - 1);
|
+ }
|
+ String sourcePath;
|
+ if (source.uriKind == UriKind.DART_URI) {
|
+ sourcePath = source.uri.toString();
|
+ } else if (source.uriKind == UriKind.PACKAGE_URI) {
|
+ sourcePath = _relative(source.fullName);
|
+ if (sourcePath == source.fullName) {
|
+ // If we weren't able to shorten the path name, use the package: version.
|
+ sourcePath = source.uri.toString();
|
+ }
|
+ } else {
|
+ sourcePath = _relative(source.fullName);
|
+ }
|
+
|
+ batchedErrors.add(new CLIError(
|
+ severity: errorType,
|
+ sourcePath: sourcePath,
|
+ offset: error.offset,
|
+ line: location.lineNumber,
|
+ column: location.columnNumber,
|
+ message: message,
|
+ errorCode: error.errorCode.name.toLowerCase(),
|
+ correction: error.correction,
|
+ ));
|
+ }
|
+
|
+ void flush() {
|
+ // sort
|
+ List<CLIError> sortedErrors = batchedErrors.toList()..sort();
|
+
|
+ // print
|
+ for (CLIError error in sortedErrors) {
|
+ if (error.isError) {
|
+ stats.errorCount++;
|
+ } else if (error.isWarning) {
|
+ stats.warnCount++;
|
+ } else if (error.isLint) {
|
+ stats.lintCount++;
|
+ } else if (error.isHint) {
|
+ stats.hintCount++;
|
+ }
|
+
|
+ // warning • 'foo' is not a bar at lib/foo.dart:1:2 • foo_warning
|
+ String issueColor = (error.isError == ErrorSeverity.ERROR ||
|
+ error.isWarning == ErrorSeverity.WARNING)
|
+ ? ansi.red
|
+ : '';
|
+ out.write(' $issueColor${error.severity}${ansi.none} '
|
+ '${ansi.bullet} ${ansi.bold}${error.message}${ansi.none} ');
|
+ out.write('at ${error.sourcePath}');
|
+ out.write(':${error.line}:${error.column} ');
|
+ out.write('${ansi.bullet} ${error.errorCode}');
|
+ out.writeln();
|
+
|
+ // If verbose, also print any associated correction.
|
+ if (options.verbose && error.correction != null) {
|
+ out.writeln(
|
+ '${' '.padLeft(error.severity.length + 2)}${error.correction}');
|
+ }
|
+ }
|
+
|
+ // clear out batched errors
|
+ batchedErrors.clear();
|
+ }
|
+}
|
+
|
+final Map<String, int> _severityCompare = {
|
+ 'error': 5,
|
+ 'warning': 4,
|
+ 'info': 3,
|
+ 'lint': 2,
|
+ 'hint': 1,
|
+};
|
+
|
+/// An [AnalysisError] with line and column information.
|
+class CLIError implements Comparable<CLIError> {
|
+ final String severity;
|
+ final String sourcePath;
|
+ final int offset;
|
+ final int line;
|
+ final int column;
|
+ final String message;
|
+ final String errorCode;
|
+ final String correction;
|
+
|
+ CLIError({
|
+ this.severity,
|
+ this.sourcePath,
|
+ this.offset,
|
+ this.line,
|
+ this.column,
|
+ this.message,
|
+ this.errorCode,
|
+ this.correction,
|
+ });
|
+
|
+ bool get isError => severity == 'error';
|
+ bool get isWarning => severity == 'warning';
|
+ bool get isLint => severity == 'lint';
|
+ bool get isHint => severity == 'hint';
|
+
|
+ @override
|
+ int get hashCode =>
|
+ severity.hashCode ^ sourcePath.hashCode ^ errorCode.hashCode ^ offset;
|
+
|
+ @override
|
+ bool operator ==(other) {
|
+ if (other is! CLIError) return false;
|
+
|
+ return severity == other.severity &&
|
+ sourcePath == other.sourcePath &&
|
+ errorCode == other.errorCode &&
|
+ offset == other.offset;
|
+ }
|
+
|
+ @override
|
+ int compareTo(CLIError other) {
|
+ // severity
|
+ int compare =
|
+ _severityCompare[other.severity] - _severityCompare[this.severity];
|
+ if (compare != 0) return compare;
|
+
|
+ // path
|
+ compare = Comparable.compare(
|
+ this.sourcePath.toLowerCase(), other.sourcePath.toLowerCase());
|
+ if (compare != 0) return compare;
|
+
|
+ // offset
|
+ return this.offset - other.offset;
|
+ }
|
}
|
|
/// A severity with awareness of whether it was overridden by a processor.
|
class ProcessedSeverity {
|
- ErrorSeverity severity;
|
- bool overridden;
|
+ final ErrorSeverity severity;
|
+ final bool overridden;
|
ProcessedSeverity(this.severity, [this.overridden = false]);
|
}
|
|
|