blob: c6271d5dcb1936d9d5c2efd14b332b84c732e617 [file] [log] [blame]
// Copyright 2014 The Flutter Authors. 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 'package:args/args.dart';
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/logger.dart';
import '../base/utils.dart';
import '../cache.dart';
import '../dart/analysis.dart';
import '../dart/sdk.dart' as sdk;
import '../globals.dart' as globals;
import 'analyze_base.dart';
class AnalyzeContinuously extends AnalyzeBase {
AnalyzeContinuously(ArgResults argResults, this.repoRoots, this.repoPackages) : super(argResults);
final List<String> repoRoots;
final List<Directory> repoPackages;
String analysisTarget;
bool firstAnalysis = true;
Set<String> analyzedPaths = <String>{};
Map<String, List<AnalysisError>> analysisErrors = <String, List<AnalysisError>>{};
Stopwatch analysisTimer;
int lastErrorCount = 0;
Status analysisStatus;
@override
Future<void> analyze() async {
List<String> directories;
if (argResults['flutter-repo'] as bool) {
final PackageDependencyTracker dependencies = PackageDependencyTracker();
dependencies.checkForConflictingDependencies(repoPackages, dependencies);
directories = repoRoots;
analysisTarget = 'Flutter repository';
globals.printTrace('Analyzing Flutter repository:');
for (final String projectPath in repoRoots) {
globals.printTrace(' ${globals.fs.path.relative(projectPath)}');
}
} else {
directories = <String>[globals.fs.currentDirectory.path];
analysisTarget = globals.fs.currentDirectory.path;
}
final String sdkPath = argResults['dart-sdk'] as String ?? sdk.dartSdkPath;
final AnalysisServer server = AnalysisServer(sdkPath, directories);
server.onAnalyzing.listen((bool isAnalyzing) => _handleAnalysisStatus(server, isAnalyzing));
server.onErrors.listen(_handleAnalysisErrors);
Cache.releaseLockEarly();
await server.start();
final int exitCode = await server.onExit;
final String message = 'Analysis server exited with code $exitCode.';
if (exitCode != 0) {
throwToolExit(message, exitCode: exitCode);
}
globals.printStatus(message);
if (server.didServerErrorOccur) {
throwToolExit('Server error(s) occurred.');
}
}
void _handleAnalysisStatus(AnalysisServer server, bool isAnalyzing) {
if (isAnalyzing) {
analysisStatus?.cancel();
if (!firstAnalysis) {
globals.printStatus('\n');
}
analysisStatus = globals.logger.startProgress('Analyzing $analysisTarget...', timeout: timeoutConfiguration.slowOperation);
analyzedPaths.clear();
analysisTimer = Stopwatch()..start();
} else {
analysisStatus?.stop();
analysisStatus = null;
analysisTimer.stop();
globals.logger.printStatus(globals.terminal.clearScreen(), newline: false);
// Remove errors for deleted files, sort, and print errors.
final List<AnalysisError> errors = <AnalysisError>[];
for (final String path in analysisErrors.keys.toList()) {
if (globals.fs.isFileSync(path)) {
errors.addAll(analysisErrors[path]);
} else {
analysisErrors.remove(path);
}
}
int issueCount = errors.length;
// count missing dartdocs
final int undocumentedMembers = errors.where((AnalysisError error) {
return error.code == 'public_member_api_docs';
}).length;
if (!(argResults['dartdocs'] as bool)) {
errors.removeWhere((AnalysisError error) => error.code == 'public_member_api_docs');
issueCount -= undocumentedMembers;
}
errors.sort();
for (final AnalysisError error in errors) {
globals.printStatus(error.toString());
if (error.code != null) {
globals.printTrace('error code: ${error.code}');
}
}
dumpErrors(errors.map<String>((AnalysisError error) => error.toLegacyString()));
// Print an analysis summary.
String errorsMessage;
final int issueDiff = issueCount - lastErrorCount;
lastErrorCount = issueCount;
if (firstAnalysis) {
errorsMessage = '$issueCount ${pluralize('issue', issueCount)} found';
} else if (issueDiff > 0) {
errorsMessage = '$issueCount ${pluralize('issue', issueCount)} found ($issueDiff new)';
} else if (issueDiff < 0) {
errorsMessage = '$issueCount ${pluralize('issue', issueCount)} found (${-issueDiff} fixed)';
} else if (issueCount != 0) {
errorsMessage = '$issueCount ${pluralize('issue', issueCount)} found';
} else {
errorsMessage = 'no issues found';
}
String dartdocMessage;
if (undocumentedMembers == 1) {
dartdocMessage = 'one public member lacks documentation';
} else {
dartdocMessage = '$undocumentedMembers public members lack documentation';
}
final String files = '${analyzedPaths.length} ${pluralize('file', analyzedPaths.length)}';
final String seconds = (analysisTimer.elapsedMilliseconds / 1000.0).toStringAsFixed(2);
if (undocumentedMembers > 0) {
globals.printStatus('$errorsMessage • $dartdocMessage • analyzed $files in $seconds seconds');
} else {
globals.printStatus('$errorsMessage • analyzed $files in $seconds seconds');
}
if (firstAnalysis && isBenchmarking) {
writeBenchmark(analysisTimer, issueCount, undocumentedMembers);
server.dispose().whenComplete(() { exit(issueCount > 0 ? 1 : 0); });
}
firstAnalysis = false;
}
}
void _handleAnalysisErrors(FileAnalysisErrors fileErrors) {
fileErrors.errors.removeWhere((AnalysisError error) => error.type == 'TODO');
analyzedPaths.add(fileErrors.file);
analysisErrors[fileErrors.file] = fileErrors.errors;
}
}