blob: 391f16f4894220a468a03343180b5addb7e9b7e9 [file] [log] [blame] [edit]
// 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(null);
}
}
}
/// 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;
}
}
}