| // Copyright (c) 2016, 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 'package:analyzer/src/fine/requirement_failure.dart'; | |
| /// The status of analysis. | |
| sealed class AnalysisStatus { | |
| const AnalysisStatus._(); | |
| bool get isIdle => !isWorking; | |
| bool get isWorking; | |
| } | |
| final class AnalysisStatusIdle extends AnalysisStatus { | |
| /// Statistics accumulated during the finished period of working. | |
| final AnalysisStatusWorkingStatistics? workingStatistics; | |
| AnalysisStatusIdle._({required this.workingStatistics}) : super._(); | |
| @override | |
| bool get isWorking => false; | |
| @override | |
| String toString() => 'idle'; | |
| } | |
| final class AnalysisStatusWorking extends AnalysisStatus { | |
| /// Will complete when we switch to [AnalysisStatusIdle]. | |
| final Completer<void> _idleCompleter = Completer<void>(); | |
| /// Accumulator for statistics during this working period. | |
| final AnalysisStatusWorkingStatistics workingStatistics; | |
| AnalysisStatusWorking._({required this.workingStatistics}) : super._(); | |
| @override | |
| bool get isWorking => true; | |
| @override | |
| String toString() => 'working'; | |
| } | |
| /// Accumulator for statistics during a single period of working. | |
| class AnalysisStatusWorkingStatistics { | |
| final bool withFineDependencies; | |
| /// Files changed before the described working period. | |
| /// Usually probably just one, as the user types in the editor. | |
| final Set<String> changedFiles; | |
| /// Files removed before the described working period. | |
| /// Usually hopefully zero, as this is expensive operation. | |
| final Set<String> removedFiles; | |
| /// The semi-accurate statistics about files. | |
| final FileCountsStatistics fileCounts; | |
| /// The timer for producing errors (background file analysis). | |
| final Stopwatch produceErrorsTimer = Stopwatch(); | |
| /// The timer for loading elements while producing errors. | |
| /// | |
| /// While doing this, we read transitive files, so it might be interesting | |
| /// to separate CPU intensive resolution and diagnostics from loading | |
| /// elements, that can have large IO portion (and also sometimes significant | |
| /// CPU, but much less than resolution). | |
| final Stopwatch produceErrorsElementsTimer = Stopwatch(); | |
| /// The count of files that we initially decided to produce errors. | |
| int produceErrorsPotentialFileCount = 0; | |
| /// The sum of line counts for [produceErrorsActualFileCount]. | |
| int produceErrorsPotentialFileLineCount = 0; | |
| /// The count of files that were actually analyzed, as opposite to | |
| /// producing errors from the cache, or not producing because no changes. | |
| int produceErrorsActualFileCount = 0; | |
| /// The sum of line counts for [produceErrorsActualFileCount]. | |
| int produceErrorsActualFileLineCount = 0; | |
| /// The counts of requirement failures for library diagnostics bundles. | |
| final Map<RequirementFailureKindId, int> | |
| libraryDiagnosticsBundleRequirementsFailures = {}; | |
| AnalysisStatusWorkingStatistics({ | |
| required this.withFineDependencies, | |
| required this.changedFiles, | |
| required this.removedFiles, | |
| required this.fileCounts, | |
| }); | |
| } | |
| /// The statistics about all files in the workspace. | |
| class FileCountsStatistics { | |
| final Stopwatch age = Stopwatch()..start(); | |
| /// The number of immediate files that were analyzed. | |
| int immediateFileCount = 0; | |
| /// The number of lines in the immediate files. | |
| int immediateFileLineCount = 0; | |
| /// The number of transitive files. If a single file is referenced from | |
| /// multiple analysis drivers, it will be counted multiple times. | |
| int transitiveFileCount = 0; | |
| /// The number of lines in the same files that are included in the | |
| /// [transitiveFileCount]. | |
| int transitiveFileLineCount = 0; | |
| } | |
| /// File updates since last transition to working status. | |
| class FileUpdatesStatistics { | |
| final Set<String> changedFiles = {}; | |
| final Set<String> removedFiles = {}; | |
| } | |
| /// [Monitor] can be used to wait for a signal. | |
| /// | |
| /// Signals are not queued, the client will receive exactly one signal | |
| /// regardless of the number of [notify] invocations. The [signal] is reset | |
| /// after completion and will not complete until [notify] is called next time. | |
| class Monitor { | |
| Completer<void> _completer = Completer<void>(); | |
| /// Return a [Future] that completes when [notify] is called at least once. | |
| Future<void> get signal async { | |
| await _completer.future; | |
| _completer = Completer<void>(); | |
| } | |
| /// Complete the [signal] future if it is not completed yet. It is safe to | |
| /// call this method multiple times, but the [signal] will complete only once. | |
| void notify() { | |
| if (!_completer.isCompleted) { | |
| _completer.complete(); | |
| } | |
| } | |
| } | |
| /// Helper for managing transitioning [AnalysisStatus]. | |
| class StatusSupport { | |
| final StreamController<Object> _eventsController; | |
| /// The last status sent to [_eventsController]. | |
| AnalysisStatus _currentStatus = AnalysisStatusIdle._(workingStatistics: null); | |
| StatusSupport({required StreamController<Object> eventsController}) | |
| : _eventsController = eventsController; | |
| /// The last status sent to [_eventsController]. | |
| AnalysisStatus get currentStatus => _currentStatus; | |
| /// If the current status is not [AnalysisStatusIdle] yet, set the | |
| /// current status to it, and send it to the stream. | |
| void transitionToIdle() { | |
| if (_currentStatus case AnalysisStatusWorking status) { | |
| var newStatus = AnalysisStatusIdle._( | |
| workingStatistics: status.workingStatistics, | |
| ); | |
| _currentStatus = newStatus; | |
| _eventsController.add(newStatus); | |
| status._idleCompleter.complete(); | |
| } | |
| } | |
| /// If the current status is not [AnalysisStatusWorking] yet, set the | |
| /// current status to it, and send it to the stream. | |
| void transitionToWorking({ | |
| required AnalysisStatusWorkingStatistics Function() buildStatistics, | |
| }) { | |
| if (_currentStatus is AnalysisStatusIdle) { | |
| var newStatus = AnalysisStatusWorking._( | |
| workingStatistics: buildStatistics(), | |
| ); | |
| _currentStatus = newStatus; | |
| _eventsController.add(newStatus); | |
| } | |
| } | |
| /// If the current status is [AnalysisStatusIdle], returns the future that | |
| /// will complete immediately. | |
| /// | |
| /// If the current status is [AnalysisStatusWorking], returns the future | |
| /// that will complete when the status changes to [AnalysisStatusIdle]. | |
| Future<void> waitForIdle() { | |
| switch (_currentStatus) { | |
| case AnalysisStatusIdle(): | |
| return Future<void>.value(); | |
| case AnalysisStatusWorking status: | |
| return status._idleCompleter.future; | |
| } | |
| } | |
| } |