blob: 8965b4526799579719eb32212d1332e52d7d3bca [file] [log] [blame]
// Copyright (c) 2020, 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:async';
import 'dart:io';
import 'package:path/path.dart' as path;
import '../core.dart';
import '../sdk.dart';
import '../utils.dart';
import 'analyze_impl.dart';
class AnalyzeCommand extends DartdevCommand<int> {
AnalyzeCommand({bool verbose = false})
: super('analyze', "Analyze the project's Dart code.") {
argParser
..addFlag('fatal-infos',
help: 'Treat info level issues as fatal.',
defaultsTo: false,
negatable: false)
..addFlag('fatal-warnings',
help: 'Treat warning level issues as fatal.',
defaultsTo: true,
negatable: true);
}
@override
String get invocation => '${super.invocation} [<directory>]';
@override
FutureOr<int> run() async {
if (argResults.rest.length > 1) {
usageException("Only one directory is expected.");
}
// find directory from argResults.rest
var dir = argResults.rest.isEmpty
? Directory.current
: Directory(argResults.rest.single);
if (!dir.existsSync()) {
usageException("Directory doesn't exist: ${dir.path}");
}
final Completer<void> analysisCompleter = Completer<void>();
final List<AnalysisError> errors = <AnalysisError>[];
var progress = log.progress('Analyzing ${path.basename(dir.path)}');
final AnalysisServer server = AnalysisServer(
Directory(sdk.sdkPath),
[dir],
);
StreamSubscription<bool> subscription;
subscription = server.onAnalyzing.listen((bool isAnalyzing) {
if (!isAnalyzing) {
analysisCompleter.complete();
subscription.cancel();
}
});
server.onErrors.listen((FileAnalysisErrors fileErrors) {
// Record the issues found (but filter out to do comments).
errors.addAll(fileErrors.errors
.where((AnalysisError error) => error.type != 'TODO'));
});
await server.start();
// Completing the future in the callback can't fail.
//ignore: unawaited_futures
server.onExit.then<void>((int exitCode) {
if (!analysisCompleter.isCompleted) {
analysisCompleter.completeError('analysis server exited: $exitCode');
}
});
await analysisCompleter.future;
progress.finish(showTiming: true);
errors.sort();
if (errors.isNotEmpty) {
final bullet = log.ansi.bullet;
log.stdout('');
bool hasErrors = false;
bool hasWarnings = false;
bool hasInfos = false;
for (final AnalysisError error in errors) {
// error • Message ... at path.dart:line:col • (code)
var filePath = path.relative(error.file, from: dir.path);
var severity = error.severity.toLowerCase().padLeft(7);
if (error.isError) {
severity = log.ansi.error(severity);
}
log.stdout(
'$severity $bullet '
'${log.ansi.emphasized(error.messageSentenceFragment)} '
'at $filePath:${error.startLine}:${error.startColumn} $bullet '
'(${error.code})',
);
hasErrors |= error.isError;
hasWarnings |= error.isWarning;
hasInfos |= error.isInfo;
}
log.stdout('');
final errorCount = errors.length;
log.stdout('$errorCount ${pluralize('issue', errorCount)} found.');
// Return an error code in the range [0-3] dependent on the severity of
// the issue(s) found.
if (hasErrors) {
return 3;
}
bool fatalWarnings = argResults['fatal-warnings'];
bool fatalInfos = argResults['fatal-infos'];
if (fatalWarnings && hasWarnings) {
return 2;
} else if (fatalInfos && hasInfos) {
return 1;
} else {
return 0;
}
} else {
log.stdout('No issues found!');
return 0;
}
}
}