• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dart-lang / linter / 6444183028

01 Sep 2023 04:26PM UTC coverage: 96.562% (+0.01%) from 96.551%
6444183028

push

github

web-flow
update labels (#4740)

9127 of 9452 relevant lines covered (96.56%)

1.48 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.12
/lib/src/formatter.dart
1
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
2
// for details. All rights reserved. Use of this source code is governed by a
3
// BSD-style license that can be found in the LICENSE file.
4

5
import 'dart:io';
6
import 'dart:math';
7

8
import 'package:analyzer/error/error.dart';
9

10
import 'analyzer.dart';
11
import 'util/charcodes.dart' show $backslash, $pipe;
12
import 'util/score_utils.dart';
13

14
// Number of times to perform linting to get stable benchmarks.
15
const benchmarkRuns = 10;
16

17
String getLineContents(int? lineNumber, AnalysisError error) {
1✔
18
  var path = error.source.fullName;
2✔
19
  var file = File(path);
1✔
20
  String failureDetails;
21
  if (!file.existsSync()) {
1✔
22
    failureDetails = 'file at $path does not exist';
×
23
  } else {
24
    var lines = file.readAsLinesSync();
1✔
25
    var lineIndex = lineNumber! - 1;
1✔
26
    if (lines.length > lineIndex) {
2✔
27
      return lines[lineIndex];
1✔
28
    }
29
    failureDetails =
30
        'line index ($lineIndex), outside of file line range (${lines.length})';
×
31
  }
32
  throw StateError('Unable to get contents for line: $failureDetails');
×
33
}
34

35
String pluralize(String word, int? count) =>
1✔
36
    "$count ${count == 1 ? word : '${word}s'}";
3✔
37
String shorten(String? fileRoot, String fullName) {
1✔
38
  if (fileRoot == null || !fullName.startsWith(fileRoot)) {
1✔
39
    return fullName;
40
  }
41
  return fullName.substring(fileRoot.length);
2✔
42
}
43

44
Future writeBenchmarks(
×
45
    IOSink out, List<File> filesToLint, LinterOptions lintOptions) async {
46
  var timings = <String, int>{};
×
47
  for (var i = 0; i < benchmarkRuns; ++i) {
×
48
    await lintFiles(DartLinter(lintOptions), filesToLint);
×
49
    lintRegistry.timers.forEach((n, t) {
×
50
      var timing = t.elapsedMilliseconds;
×
51
      var previous = timings[n];
×
52
      if (previous == null) {
53
        timings[n] = timing;
×
54
      } else {
55
        timings[n] = min(previous, timing);
×
56
      }
57
    });
58
  }
59

60
  var coreRuleset = await coreRules;
×
61
  var recommendedRuleset = await recommendedRules;
×
62
  var flutterRuleset = await flutterRules;
×
63

64
  var stats = timings.keys.map((t) {
×
65
    var sets = <String>[];
×
66
    if (coreRuleset.contains(t)) {
×
67
      sets.add('core');
×
68
    }
69
    if (recommendedRuleset.contains(t)) {
×
70
      sets.add('recommended');
×
71
    }
72
    if (flutterRuleset.contains(t)) {
×
73
      sets.add('flutter');
×
74
    }
75

76
    var details = sets.isEmpty ? '' : " [${sets.join(', ')}]";
×
77
    return _Stat('$t$details', timings[t] ?? 0);
×
78
  }).toList();
×
79
  _writeTimings(out, stats, 0);
×
80
}
81

82
String _escapePipe(String input) {
1✔
83
  var result = StringBuffer();
1✔
84
  for (var c in input.codeUnits) {
2✔
85
    if (c == $backslash || c == $pipe) {
2✔
86
      result.write('\\');
×
87
    }
88
    result.writeCharCode(c);
1✔
89
  }
90
  return result.toString();
1✔
91
}
92

93
void _writeTimings(IOSink out, List<_Stat> timings, int summaryLength) {
1✔
94
  var names = timings.map((s) => s.name).toList();
2✔
95

96
  var longestName =
97
      names.fold<int>(0, (prev, element) => max(prev, element.length));
1✔
98
  var longestTime = 8;
99
  var tableWidth = max(summaryLength, longestName + longestTime);
1✔
100
  var pad = tableWidth - longestName;
1✔
101
  var line = ''.padLeft(tableWidth, '-');
1✔
102

103
  out
104
    ..writeln()
1✔
105
    ..writeln(line)
1✔
106
    ..writeln('${'Timings'.padRight(longestName)}${'ms'.padLeft(pad)}')
4✔
107
    ..writeln(line);
1✔
108
  var totalTime = 0;
109

110
  timings.sort();
1✔
111
  for (var stat in timings) {
1✔
112
    totalTime += stat.elapsed;
×
113
    out.writeln(
×
114
        '${stat.name.padRight(longestName)}${stat.elapsed.toString().padLeft(pad)}');
×
115
  }
116
  out
117
    ..writeln(line)
1✔
118
    ..writeln(
1✔
119
        '${'Total'.padRight(longestName)}${totalTime.toString().padLeft(pad)}')
4✔
120
    ..writeln(line);
1✔
121
}
122

123
class DetailedReporter extends SimpleFormatter {
124
  DetailedReporter(super.errors, super.filter, super.out,
1✔
125
      {super.fileCount,
126
      super.elapsedMs,
127
      super.fileRoot,
128
      super.showStatistics,
129
      super.machineOutput,
130
      super.quiet});
131

132
  @override
1✔
133
  void writeLint(AnalysisError error, {int? offset, int? line, int? column}) {
134
    super.writeLint(error, offset: offset, column: column, line: line);
1✔
135

136
    if (!machineOutput) {
1✔
137
      var contents = getLineContents(line, error);
1✔
138
      out.writeln(contents);
2✔
139

140
      var spaces = column! - 1;
1✔
141
      var arrows = max(1, min(error.length, contents.length - spaces));
5✔
142

143
      var result = '${" " * spaces}${"^" * arrows}';
3✔
144
      out.writeln(result);
2✔
145
    }
146
  }
147
}
148

149
abstract class ReportFormatter {
150
  factory ReportFormatter(
1✔
151
          Iterable<AnalysisErrorInfo> errors, LintFilter? filter, IOSink out,
152
          {int? fileCount,
153
          int? elapsedMs,
154
          String? fileRoot,
155
          bool showStatistics = false,
156
          bool machineOutput = false,
157
          bool quiet = false}) =>
158
      DetailedReporter(errors, filter, out,
1✔
159
          fileCount: fileCount,
160
          fileRoot: fileRoot,
161
          elapsedMs: elapsedMs,
162
          showStatistics: showStatistics,
163
          machineOutput: machineOutput,
164
          quiet: quiet);
165

166
  void write();
167
}
168

169
/// Simple formatter suitable for subclassing.
170
class SimpleFormatter implements ReportFormatter {
171
  final IOSink out;
172
  final Iterable<AnalysisErrorInfo> errors;
173
  final LintFilter? filter;
174

175
  int errorCount = 0;
176
  int filteredLintCount = 0;
177

178
  final int? fileCount;
179
  final int? elapsedMs;
180
  final String? fileRoot;
181
  final bool showStatistics;
182
  final bool machineOutput;
183
  final bool quiet;
184

185
  /// Cached for the purposes of statistics report formatting.
186
  int _summaryLength = 0;
187

188
  Map<String, int> stats = <String, int>{};
189

190
  SimpleFormatter(this.errors, this.filter, this.out,
1✔
191
      {this.fileCount,
192
      this.fileRoot,
193
      this.elapsedMs,
194
      this.showStatistics = false,
195
      this.quiet = false,
196
      this.machineOutput = false});
197

198
  /// Override to influence error sorting
199
  int compare(AnalysisError error1, AnalysisError error2) {
1✔
200
    // Severity
201
    var compare = error2.errorCode.errorSeverity
2✔
202
        .compareTo(error1.errorCode.errorSeverity);
3✔
203
    if (compare != 0) {
1✔
204
      return compare;
205
    }
206
    // Path
207
    compare = Comparable.compare(error1.source.fullName.toLowerCase(),
4✔
208
        error2.source.fullName.toLowerCase());
3✔
209
    if (compare != 0) {
1✔
210
      return compare;
211
    }
212
    // Offset
213
    return error1.offset - error2.offset;
3✔
214
  }
215

216
  @override
1✔
217
  void write() {
218
    writeLints();
1✔
219
    writeSummary();
1✔
220
    if (showStatistics) {
1✔
221
      out.writeln();
2✔
222
      writeStatistics();
1✔
223
    }
224
    out.writeln();
2✔
225
  }
226

227
  void writeCounts() {
1✔
228
    var codes = stats.keys.toList()..sort();
4✔
229
    var largestCountGuess = 8;
230
    var longest =
231
        codes.fold(0, (int prev, element) => max(prev, element.length));
3✔
232
    var tableWidth = max(_summaryLength, longest + largestCountGuess);
2✔
233
    var pad = tableWidth - longest;
1✔
234
    var line = ''.padLeft(tableWidth, '-');
1✔
235
    out
1✔
236
      ..writeln(line)
1✔
237
      ..writeln('Counts')
1✔
238
      ..writeln(line);
1✔
239
    for (var code in codes) {
2✔
240
      out
1✔
241
        ..write(code.padRight(longest))
2✔
242
        ..writeln(stats[code].toString().padLeft(pad));
5✔
243
    }
244
    out.writeln(line);
2✔
245
  }
246

247
  void writeLint(AnalysisError error, {int? offset, int? line, int? column}) {
1✔
248
    if (machineOutput) {
1✔
249
      //INFO|LINT|constant_identifier_names|test/engine_test.dart|91|22|3|Prefer using lowerCamelCase for constant names.
250
      out
1✔
251
        ..write(error.errorCode.errorSeverity)
3✔
252
        ..write('|')
1✔
253
        ..write(error.errorCode.type)
3✔
254
        ..write('|')
1✔
255
        ..write(error.errorCode.name)
3✔
256
        ..write('|')
1✔
257
        ..write(_escapePipe(error.source.fullName))
4✔
258
        ..write('|')
1✔
259
        ..write(line)
1✔
260
        ..write('|')
1✔
261
        ..write(column)
1✔
262
        ..write('|')
1✔
263
        ..write(error.length)
2✔
264
        ..write('|')
1✔
265
        ..writeln(_escapePipe(error.message));
3✔
266
    } else {
267
      // test/engine_test.dart 452:9 [lint] DO name types using UpperCamelCase.
268
      out
1✔
269
        ..write('${shorten(fileRoot, error.source.fullName)} ')
6✔
270
        ..write('$line:$column ')
2✔
271
        ..writeln('[${error.errorCode.type.displayName}] ${error.message}');
6✔
272
    }
273
  }
274

275
  void writeLints() {
1✔
276
    var filter = this.filter;
1✔
277
    for (var info in errors) {
2✔
278
      for (var e in (info.errors.toList()..sort(compare))) {
5✔
279
        if (filter != null && filter.filter(e)) {
1✔
280
          filteredLintCount++;
2✔
281
        } else {
282
          ++errorCount;
2✔
283
          if (!quiet) {
1✔
284
            _writeLint(e, info.lineInfo);
2✔
285
          }
286
          _recordStats(e);
1✔
287
        }
288
      }
289
    }
290
    if (!quiet) {
1✔
291
      out.writeln();
2✔
292
    }
293
  }
294

295
  void writeStatistics() {
1✔
296
    writeCounts();
1✔
297
    writeTimings();
1✔
298
  }
299

300
  void writeSummary() {
1✔
301
    var summary = '${pluralize("file", fileCount)} analyzed, '
3✔
302
        '${pluralize("issue", errorCount)} found'
2✔
303
        "${filteredLintCount == 0 ? '' : ' ($filteredLintCount filtered)'}, in $elapsedMs ms.";
5✔
304
    out.writeln(summary);
2✔
305
    // Cache for output table sizing
306
    _summaryLength = summary.length;
2✔
307
  }
308

309
  void writeTimings() {
1✔
310
    var timers = lintRegistry.timers;
2✔
311
    var timings = timers.keys
1✔
312
        .map((t) => _Stat(t, timers[t]?.elapsedMilliseconds ?? 0))
1✔
313
        .toList();
1✔
314
    _writeTimings(out, timings, _summaryLength);
3✔
315
  }
316

317
  void _recordStats(AnalysisError error) {
1✔
318
    var codeName = error.errorCode.name;
2✔
319
    stats.putIfAbsent(codeName, () => 0);
3✔
320
    stats[codeName] = stats[codeName]! + 1;
5✔
321
  }
322

323
  void _writeLint(AnalysisError error, LineInfo lineInfo) {
1✔
324
    var offset = error.offset;
1✔
325
    var location = lineInfo.getLocation(offset);
1✔
326
    var line = location.lineNumber;
1✔
327
    var column = location.columnNumber;
1✔
328

329
    writeLint(error, offset: offset, column: column, line: line);
1✔
330
  }
331
}
332

333
class _Stat implements Comparable<_Stat> {
334
  final String name;
335
  final int elapsed;
336

337
  _Stat(this.name, this.elapsed);
×
338

339
  @override
×
340
  int compareTo(_Stat other) => other.elapsed - elapsed;
×
341
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc