22// for details. All rights reserved. Use of this source code is governed by a
33// BSD-style license that can be found in the LICENSE file.
44
5+ import 'dart:async' ;
6+ import 'dart:convert' ;
57import 'dart:io' ;
6- import 'dart:math' ;
8+ import 'dart:math' as math ;
79
810import 'package:analyzer/error/error.dart' ;
911import 'package:analyzer/src/lint/config.dart' ;
@@ -14,57 +16,53 @@ import 'package:glob/glob.dart';
1416import 'package:linter/src/analyzer.dart' ;
1517import 'package:linter/src/extensions.dart' ;
1618import 'package:linter/src/rules.dart' ;
17- import 'package:linter/src/test_utilities/analyzer_utils.dart' ;
1819import 'package:linter/src/test_utilities/formatter.dart' ;
1920import 'package:linter/src/test_utilities/test_linter.dart' ;
2021
2122import 'lint_sets.dart' ;
2223
23- /// Starts linting from the command-line .
24+ /// Benchmarks lint rules .
2425Future <void > main (List <String > args) async {
2526 await runLinter (args);
2627}
2728
2829const unableToProcessExitCode = 64 ;
2930
30- String ? getRoot (List <String > paths) =>
31- paths.length == 1 && Directory (paths.first).existsSync ()
32- ? paths.first
33- : null ;
31+ Future <Iterable <AnalysisErrorInfo >> lintFiles (
32+ TestLinter linter, List <File > filesToLint) async {
33+ // Setup an error watcher to track whether an error was logged to stderr so
34+ // we can set the exit code accordingly.
35+ var errorWatcher = _ErrorWatchingSink (errorSink);
36+ errorSink = errorWatcher;
37+ var errors = await linter.lintFiles (filesToLint);
38+ if (errorWatcher.encounteredError) {
39+ exitCode = loggedAnalyzerErrorExitCode;
40+ } else if (errors.isNotEmpty) {
41+ exitCode = _maxSeverity (errors, linter.options.filter);
42+ }
43+
44+ return errors;
45+ }
3446
3547void printUsage (ArgParser parser, IOSink out, [String ? error]) {
36- var message = 'Lints Dart source files and pubspecs .' ;
48+ var message = 'Benchmark lint rules .' ;
3749 if (error != null ) {
3850 message = error;
3951 }
4052
4153 out.writeln ('''$message
42- Usage: linter <file>
54+ Usage: benchmark.dart <file>
4355${parser .usage }
44-
45- For more information, see https://github.com/dart-lang/linter
4656''' );
4757}
4858
49- // TODO(pq): consider using `dart analyze` where possible
50- // see: https://github.com/dart-lang/linter/pull/2537
5159Future <void > runLinter (List <String > args) async {
52- // Force the rule registry to be populated.
5360 registerLintRules ();
5461
5562 var parser = ArgParser ();
5663 parser
5764 ..addFlag ('help' ,
5865 abbr: 'h' , negatable: false , help: 'Show usage information.' )
59- ..addFlag ('stats' ,
60- abbr: 's' , negatable: false , help: 'Show lint statistics.' )
61- ..addFlag ('benchmark' , negatable: false , help: 'Show lint benchmarks.' )
62- ..addFlag ('visit-transitive-closure' ,
63- help: 'Visit the transitive closure of imported/exported libraries.' )
64- ..addFlag ('quiet' , abbr: 'q' , help: "Don't show individual lint errors." )
65- ..addFlag ('machine' ,
66- help: 'Print results in a format suitable for parsing.' ,
67- negatable: false )
6866 ..addOption ('config' , abbr: 'c' , help: 'Use configuration from this file.' )
6967 ..addOption ('dart-sdk' , help: 'Custom path to a Dart SDK.' )
7068 ..addMultiOption ('rules' ,
@@ -85,7 +83,8 @@ Future<void> runLinter(List<String> args) async {
8583 return ;
8684 }
8785
88- if (options.rest.isEmpty) {
86+ var paths = options.rest;
87+ if (paths.isEmpty) {
8988 printUsage (parser, errorSink,
9089 'Please provide at least one file or directory to lint.' );
9190 exitCode = unableToProcessExitCode;
@@ -122,52 +121,16 @@ Future<void> runLinter(List<String> args) async {
122121 linterOptions.dartSdkPath = customSdk;
123122 }
124123
125- var stats = options['stats' ] as bool ;
126- var benchmark = options['benchmark' ] as bool ;
127- if (stats || benchmark) {
128- linterOptions.enableTiming = true ;
129- }
124+ linterOptions.enableTiming = true ;
130125
131126 var filesToLint = [
132- for (var path in options.rest )
127+ for (var path in paths )
133128 ...collectFiles (path)
134129 .map ((file) => file.path.toAbsoluteNormalizedPath ())
135130 .map (File .new ),
136131 ];
137132
138- if (benchmark) {
139- await writeBenchmarks (outSink, filesToLint, linterOptions);
140- return ;
141- }
142-
143- var linter = TestLinter (linterOptions);
144-
145- try {
146- var timer = Stopwatch ()..start ();
147- var errors = await lintFiles (linter, filesToLint);
148- timer.stop ();
149-
150- var commonRoot = getRoot (options.rest);
151- var machine = options['machine' ] ?? false ;
152- var quiet = options['quiet' ] ?? false ;
153- ReportFormatter (
154- errors,
155- linterOptions.filter,
156- outSink,
157- elapsedMs: timer.elapsedMilliseconds,
158- fileCount: linter.numSourcesAnalyzed,
159- fileRoot: commonRoot,
160- showStatistics: stats,
161- machineOutput: machine as bool ,
162- quiet: quiet as bool ,
163- ).write ();
164- // ignore: avoid_catches_without_on_clauses
165- } catch (err, stack) {
166- errorSink.writeln ('''An error occurred while linting
167- Please report it at: github.com/dart-lang/linter/issues
168- $err
169- $stack ''' );
170- }
133+ await writeBenchmarks (outSink, filesToLint, linterOptions);
171134}
172135
173136Future <void > writeBenchmarks (
@@ -178,11 +141,7 @@ Future<void> writeBenchmarks(
178141 lintRuleTimers.timers.forEach ((n, t) {
179142 var timing = t.elapsedMilliseconds;
180143 var previous = timings[n];
181- if (previous == null ) {
182- timings[n] = timing;
183- } else {
184- timings[n] = min (previous, timing);
185- }
144+ timings[n] = previous == null ? timing : math.min (previous, timing);
186145 });
187146 }
188147
@@ -208,6 +167,73 @@ Future<void> writeBenchmarks(
208167 out.writeTimings (stats, 0 );
209168}
210169
170+ Iterable <AnalysisError > _filtered (
171+ Iterable <AnalysisError > errors, LintFilter ? filter) =>
172+ (filter == null )
173+ ? errors
174+ : errors.where ((AnalysisError e) => ! filter.filter (e));
175+
176+ int _maxSeverity (List <AnalysisErrorInfo > infos, LintFilter ? filter) {
177+ var allErrors = _filtered (infos.expand ((i) => i.errors), filter);
178+ return allErrors.fold (
179+ 0 , (value, e) => math.max (value, e.errorCode.errorSeverity.ordinal));
180+ }
181+
182+ class _ErrorWatchingSink implements IOSink {
183+ bool encounteredError = false ;
184+
185+ final IOSink delegate;
186+
187+ _ErrorWatchingSink (this .delegate);
188+
189+ @override
190+ Future <void > get done => delegate.done;
191+
192+ @override
193+ Encoding get encoding => delegate.encoding;
194+
195+ @override
196+ set encoding (Encoding encoding) => delegate.encoding = encoding;
197+
198+ @override
199+ void add (List <int > data) => delegate.add (data);
200+
201+ @override
202+ void addError (Object error, [StackTrace ? stackTrace]) {
203+ encounteredError = true ;
204+ delegate.addError (error, stackTrace);
205+ }
206+
207+ @override
208+ Future <void > addStream (Stream <List <int >> stream) =>
209+ delegate.addStream (stream);
210+
211+ @override
212+ Future <void > close () => delegate.close ();
213+
214+ @override
215+ Future <void > flush () => delegate.flush ();
216+
217+ @override
218+ void write (Object ? obj) => delegate.write (obj);
219+
220+ @override
221+ void writeAll (Iterable <Object ?> objects, [String separator = '' ]) =>
222+ delegate.writeAll (objects, separator);
223+
224+ @override
225+ void writeCharCode (int charCode) => delegate.writeCharCode (charCode);
226+
227+ @override
228+ void writeln ([Object ? obj = '' ]) {
229+ // 'Exception while using a Visitor to visit ...' (
230+ if (obj.toString ().startsWith ('Exception' )) {
231+ encounteredError = true ;
232+ }
233+ delegate.writeln (obj);
234+ }
235+ }
236+
211237class _FileGlobFilter extends LintFilter {
212238 final List <Glob > includes;
213239 final List <Glob > excludes;
0 commit comments