// Copyright 2015 The Chromium 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/terminal.dart'; import '../base/utils.dart'; import '../cache.dart'; import '../dart/analysis.dart'; import '../dart/sdk.dart' as sdk; import '../globals.dart'; 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']) { final PackageDependencyTracker dependencies = PackageDependencyTracker(); dependencies.checkForConflictingDependencies(repoPackages, dependencies); directories = repoRoots; analysisTarget = 'Flutter repository'; printTrace('Analyzing Flutter repository:'); for (String projectPath in repoRoots) { printTrace(' ${fs.path.relative(projectPath)}'); } } else { directories = <String>[fs.currentDirectory.path]; analysisTarget = fs.currentDirectory.path; } final String sdkPath = argResults['dart-sdk'] ?? 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); printStatus(message); if (server.didServerErrorOccur) throwToolExit('Server error(s) occurred.'); } void _handleAnalysisStatus(AnalysisServer server, bool isAnalyzing) { if (isAnalyzing) { analysisStatus?.cancel(); if (!firstAnalysis) printStatus('\n'); analysisStatus = logger.startProgress('Analyzing $analysisTarget...', timeout: timeoutConfiguration.slowOperation); analyzedPaths.clear(); analysisTimer = Stopwatch()..start(); } else { analysisStatus?.stop(); analysisStatus = null; analysisTimer.stop(); logger.printStatus(terminal.clearScreen(), newline: false); // Remove errors for deleted files, sort, and print errors. final List<AnalysisError> errors = <AnalysisError>[]; for (String path in analysisErrors.keys.toList()) { if (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']) { errors.removeWhere((AnalysisError error) => error.code == 'public_member_api_docs'); issueCount -= undocumentedMembers; } errors.sort(); for (AnalysisError error in errors) { printStatus(error.toString()); if (error.code != null) 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) { printStatus('$errorsMessage • $dartdocMessage • analyzed $files in $seconds seconds'); } else { 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; } }