| // 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 '../base/common.dart'; |
| import '../base/file_system.dart'; |
| import '../base/logger.dart'; |
| import '../dart/analysis.dart'; |
| import 'analyze_base.dart'; |
| |
| class AnalyzeOnce extends AnalyzeBase { |
| AnalyzeOnce( |
| super.argResults, |
| List<String> repoRoots, |
| List<Directory> repoPackages, { |
| required super.fileSystem, |
| required super.logger, |
| required super.platform, |
| required super.processManager, |
| required super.terminal, |
| required super.artifacts, |
| this.workingDirectory, |
| }) : super( |
| repoRoots: repoRoots, |
| repoPackages: repoPackages, |
| ); |
| |
| /// The working directory for testing analysis using dartanalyzer. |
| final Directory? workingDirectory; |
| |
| @override |
| Future<void> analyze() async { |
| final String currentDirectory = |
| (workingDirectory ?? fileSystem.currentDirectory).path; |
| final Set<String> items = findDirectories(argResults, fileSystem); |
| |
| if (isFlutterRepo) { |
| // check for conflicting dependencies |
| final PackageDependencyTracker dependencies = PackageDependencyTracker(); |
| dependencies.checkForConflictingDependencies(repoPackages, dependencies); |
| items.addAll(repoRoots); |
| if (argResults.wasParsed('current-package') && (argResults['current-package'] as bool)) { |
| items.add(currentDirectory); |
| } |
| } else { |
| if ((argResults['current-package'] as bool) && items.isEmpty) { |
| items.add(currentDirectory); |
| } |
| } |
| |
| if (items.isEmpty) { |
| throwToolExit('Nothing to analyze.', exitCode: 0); |
| } |
| |
| final Completer<void> analysisCompleter = Completer<void>(); |
| final List<AnalysisError> errors = <AnalysisError>[]; |
| |
| final AnalysisServer server = AnalysisServer( |
| sdkPath, |
| items.toList(), |
| fileSystem: fileSystem, |
| platform: platform, |
| logger: logger, |
| processManager: processManager, |
| terminal: terminal, |
| protocolTrafficLog: protocolTrafficLog, |
| ); |
| |
| Stopwatch? timer; |
| Status? progress; |
| try { |
| StreamSubscription<bool>? subscription; |
| |
| void handleAnalysisStatus(bool isAnalyzing) { |
| if (!isAnalyzing) { |
| analysisCompleter.complete(); |
| subscription?.cancel(); |
| subscription = null; |
| } |
| } |
| |
| subscription = server.onAnalyzing.listen((bool isAnalyzing) => handleAnalysisStatus(isAnalyzing)); |
| |
| void handleAnalysisErrors(FileAnalysisErrors fileErrors) { |
| fileErrors.errors.removeWhere((AnalysisError error) => error.type == 'TODO'); |
| |
| errors.addAll(fileErrors.errors); |
| } |
| |
| server.onErrors.listen(handleAnalysisErrors); |
| |
| await server.start(); |
| // Completing the future in the callback can't fail. |
| unawaited(server.onExit.then<void>((int? exitCode) { |
| if (!analysisCompleter.isCompleted) { |
| analysisCompleter.completeError( |
| // Include the last 20 lines of server output in exception message |
| Exception( |
| 'analysis server exited with code $exitCode and output:\n${server.getLogs(20)}', |
| ), |
| ); |
| } |
| })); |
| |
| // collect results |
| timer = Stopwatch()..start(); |
| final String message = items.length > 1 |
| ? '${items.length} ${items.length == 1 ? 'item' : 'items'}' |
| : fileSystem.path.basename(items.first); |
| progress = argResults['preamble'] == true |
| ? logger.startProgress( |
| 'Analyzing $message...', |
| ) |
| : null; |
| |
| await analysisCompleter.future; |
| } finally { |
| await server.dispose(); |
| progress?.cancel(); |
| timer?.stop(); |
| } |
| |
| // emit benchmarks |
| if (isBenchmarking) { |
| writeBenchmark(timer, errors.length); |
| } |
| |
| // --write |
| dumpErrors(errors.map<String>((AnalysisError error) => error.toLegacyString())); |
| |
| // report errors |
| if (errors.isNotEmpty && (argResults['preamble'] as bool)) { |
| logger.printStatus(''); |
| } |
| errors.sort(); |
| for (final AnalysisError error in errors) { |
| logger.printStatus(error.toString(), hangingIndent: 7); |
| } |
| |
| final int errorCount = errors.length; |
| final String seconds = (timer.elapsedMilliseconds / 1000.0).toStringAsFixed(1); |
| final String errorsMessage = AnalyzeBase.generateErrorsMessage( |
| issueCount: errorCount, |
| seconds: seconds, |
| ); |
| |
| if (errorCount > 0) { |
| logger.printStatus(''); |
| throwToolExit(errorsMessage, exitCode: _isFatal(errors) ? 1 : 0); |
| } |
| |
| if (argResults['congratulate'] as bool) { |
| logger.printStatus(errorsMessage); |
| } |
| |
| if (server.didServerErrorOccur) { |
| throwToolExit('Server error(s) occurred. (ran in ${seconds}s)'); |
| } |
| } |
| |
| bool _isFatal(List<AnalysisError> errors) { |
| for (final AnalysisError error in errors) { |
| final AnalysisSeverity severityLevel = error.writtenError.severityLevel; |
| if (severityLevel == AnalysisSeverity.error) { |
| return true; |
| } |
| if (severityLevel == AnalysisSeverity.warning && |
| (argResults['fatal-warnings'] as bool || argResults['fatal-infos'] as bool)) { |
| return true; |
| } |
| if (severityLevel == AnalysisSeverity.info && argResults['fatal-infos'] as bool) { |
| return true; |
| } |
| } |
| return false; |
| } |
| } |