blob: 13709f409d14c25a7573264cea056fbea6a60a02 [file] [log] [blame]
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:io';
import 'dart:math';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/source/line_info.dart';
// ignore: implementation_imports
import 'package:analyzer/src/lint/analysis_rule_timers.dart';
import 'analysis_error_info.dart';
String getLineContents(int lineNumber, AnalysisError error) {
var path = error.source.fullName;
var file = File(path);
String failureDetails;
if (!file.existsSync()) {
failureDetails = 'file at $path does not exist';
} else {
var lines = file.readAsLinesSync();
var lineIndex = lineNumber - 1;
if (lines.length > lineIndex) {
return lines[lineIndex];
}
failureDetails =
'line index ($lineIndex), outside of file line range (${lines.length})';
}
throw StateError('Unable to get contents for line: $failureDetails');
}
String pluralize(String word, int count) =>
"$count ${count == 1 ? word : '${word}s'}";
class ReportFormatter {
final StringSink out;
final Iterable<AnalysisErrorInfo> errors;
int errorCount = 0;
final int? elapsedMs;
final bool showStatistics;
/// Cached for the purposes of statistics report formatting.
int _summaryLength = 0;
Map<String, int> stats = <String, int>{};
ReportFormatter(
this.errors,
this.out, {
this.elapsedMs,
this.showStatistics = false,
});
/// Override to influence error sorting.
int compare(AnalysisError error1, AnalysisError error2) {
// Severity.
var compare = error2.errorCode.errorSeverity
.compareTo(error1.errorCode.errorSeverity);
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;
}
void write() {
writeLints();
writeSummary();
if (showStatistics) {
out.writeln();
writeStatistics();
}
out.writeln();
}
void writeCounts() {
var codes = stats.keys.toList()..sort();
var largestCountGuess = 8;
var longest =
codes.fold(0, (int prev, element) => max(prev, element.length));
var tableWidth = max(_summaryLength, longest + largestCountGuess);
var pad = tableWidth - longest;
var line = ''.padLeft(tableWidth, '-');
out
..writeln(line)
..writeln('Counts')
..writeln(line);
for (var code in codes) {
out
..write(code.padRight(longest))
..writeln(stats[code].toString().padLeft(pad));
}
out.writeln(line);
}
void writeLint(
AnalysisError error, {
required int offset,
required int line,
required int column,
}) {
// test/engine_test.dart 452:9 [lint] DO name types using UpperCamelCase.
out
..write('${error.source.fullName} ')
..write('$line:$column ')
..writeln('[${error.errorCode.type.displayName}] ${error.message}');
var contents = getLineContents(line, error);
out.writeln(contents);
var spaces = column - 1;
var arrows = max(1, min(error.length, contents.length - spaces));
var result = '${" " * spaces}${"^" * arrows}';
out.writeln(result);
}
void writeLints() {
for (var info in errors) {
for (var e in (info.errors.toList()..sort(compare))) {
++errorCount;
_writeLint(e, info.lineInfo);
_recordStats(e);
}
}
out.writeln();
}
void writeStatistics() {
writeCounts();
writeTimings();
}
void writeSummary() {
var summary = 'files analyzed, '
'${pluralize("issue", errorCount)} found, in $elapsedMs ms.';
out.writeln(summary);
// Cache for output table sizing
_summaryLength = summary.length;
}
void writeTimings() {
var timers = analysisRuleTimers.timers;
var timings = timers.keys
.map((t) => Stat(t, timers[t]?.elapsedMilliseconds ?? 0))
.toList();
out.writeTimings(timings, _summaryLength);
}
void _recordStats(AnalysisError error) {
var codeName = error.errorCode.name;
stats.putIfAbsent(codeName, () => 0);
stats[codeName] = stats[codeName]! + 1;
}
void _writeLint(AnalysisError error, LineInfo lineInfo) {
var offset = error.offset;
var location = lineInfo.getLocation(offset);
var line = location.lineNumber;
var column = location.columnNumber;
writeLint(error, offset: offset, column: column, line: line);
}
}
class Stat implements Comparable<Stat> {
final String name;
final int elapsed;
Stat(this.name, this.elapsed);
@override
int compareTo(Stat other) => other.elapsed - elapsed;
}
extension StringSinkExtension on StringSink {
void writeTimings(List<Stat> timings, int summaryLength) {
var names = timings.map((s) => s.name).toList();
var longestName =
names.fold<int>(0, (prev, element) => max(prev, element.length));
var longestTime = 8;
var tableWidth = max(summaryLength, longestName + longestTime);
var pad = tableWidth - longestName;
var line = ''.padLeft(tableWidth, '-');
writeln();
writeln(line);
writeln('${'Timings'.padRight(longestName)}${'ms'.padLeft(pad)}');
writeln(line);
var totalTime = 0;
timings.sort();
for (var stat in timings) {
totalTime += stat.elapsed;
writeln(
'${stat.name.padRight(longestName)}${stat.elapsed.toString().padLeft(pad)}');
}
writeln(line);
writeln(
'${'Total'.padRight(longestName)}${totalTime.toString().padLeft(pad)}');
writeln(line);
}
}