blob: 4d2e1928b4dc5a363c80eea2150b1e294c8ec7bf [file] [log] [blame]
// 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 'dart:typed_data';
import 'package:analyzer/dart/analysis/declared_variables.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/exception/exception.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/source/source.dart';
import 'package:analyzer/src/context/packages.dart';
import 'package:analyzer/src/dart/analysis/analysis_options_map.dart';
import 'package:analyzer/src/dart/analysis/byte_store.dart';
import 'package:analyzer/src/dart/analysis/driver_based_analysis_context.dart';
import 'package:analyzer/src/dart/analysis/driver_event.dart' as events;
import 'package:analyzer/src/dart/analysis/feature_set_provider.dart';
import 'package:analyzer/src/dart/analysis/file_content_cache.dart';
import 'package:analyzer/src/dart/analysis/file_state.dart';
import 'package:analyzer/src/dart/analysis/file_tracker.dart';
import 'package:analyzer/src/dart/analysis/index.dart';
import 'package:analyzer/src/dart/analysis/info_declaration_store.dart';
import 'package:analyzer/src/dart/analysis/library_analyzer.dart';
import 'package:analyzer/src/dart/analysis/library_context.dart';
import 'package:analyzer/src/dart/analysis/performance_logger.dart';
import 'package:analyzer/src/dart/analysis/results.dart';
import 'package:analyzer/src/dart/analysis/search.dart';
import 'package:analyzer/src/dart/analysis/session.dart';
import 'package:analyzer/src/dart/analysis/status.dart';
import 'package:analyzer/src/dart/analysis/testing_data.dart';
import 'package:analyzer/src/dart/analysis/unlinked_unit_store.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/resolver/flow_analysis_visitor.dart';
import 'package:analyzer/src/diagnostic/diagnostic.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/exception/exception.dart';
import 'package:analyzer/src/generated/engine.dart'
show AnalysisContext, AnalysisEngine, AnalysisOptions, AnalysisOptionsImpl;
import 'package:analyzer/src/generated/source.dart' show SourceFactory;
import 'package:analyzer/src/lint/registry.dart' as linter;
import 'package:analyzer/src/summary/api_signature.dart';
import 'package:analyzer/src/summary/format.dart';
import 'package:analyzer/src/summary/idl.dart';
import 'package:analyzer/src/summary/package_bundle_reader.dart';
import 'package:analyzer/src/summary2/ast_binary_flags.dart';
import 'package:analyzer/src/summary2/bundle_writer.dart';
import 'package:analyzer/src/summary2/macro.dart';
import 'package:analyzer/src/summary2/package_bundle_format.dart';
import 'package:analyzer/src/util/file_paths.dart' as file_paths;
import 'package:analyzer/src/util/performance/operation_performance.dart';
import 'package:analyzer/src/utilities/extensions/async.dart';
import 'package:analyzer/src/utilities/extensions/collection.dart';
import 'package:analyzer/src/utilities/extensions/string.dart';
import 'package:analyzer/src/utilities/uri_cache.dart';
import 'package:analyzer/src/workspace/pub.dart';
import 'package:meta/meta.dart';
/// This class computes analysis results for Dart files.
///
/// Let the set of "explicitly analyzed files" denote the set of paths that have
/// been passed to [addFile] but not subsequently passed to [removeFile]. Let
/// the "current analysis results" denote the map from the set of explicitly
/// analyzed files to the most recent [AnalysisResult] delivered to `events`
/// for each file. Let the "current file state" represent a map from file path
/// to the file contents most recently read from that file, or fetched from the
/// content cache (considering all possible file paths, regardless of
/// whether they're in the set of explicitly analyzed files). Let the
/// "analysis state" be either "working" or "idle".
///
/// (These are theoretical constructs; they may not necessarily reflect data
/// structures maintained explicitly by the driver).
///
/// Then we make the following guarantees:
///
/// - Whenever the analysis state is idle, the current analysis results are
/// consistent with the current file state.
///
/// - A call to [addFile] or [changeFile] causes the analysis state to
/// transition to "working", and schedules the contents of the given
/// files to be read into the current file state prior to the next time
/// the analysis state transitions back to "idle".
///
/// - If at any time the client stops making calls to [addFile], [changeFile],
/// and [removeFile], the analysis state will eventually transition back to
/// "idle" after a finite amount of processing.
///
/// As a result of these guarantees, a client may ensure that the analysis
/// results are "eventually consistent" with the file system by simply calling
/// [changeFile] any time the contents of a file on the file system have changed.
///
// TODO(scheglov): Clean up the list of implicitly analyzed files.
class AnalysisDriver {
/// The version of data format, should be incremented on every format change.
static const int DATA_VERSION = 361;
/// The number of exception contexts allowed to write. Once this field is
/// zero, we stop writing any new exception contexts in this process.
static int allowedNumberOfContextsToWrite = 10;
/// The scheduler that schedules analysis work in this, and possibly other
/// analysis drivers.
final AnalysisDriverScheduler _scheduler;
/// The logger to write performed operations and performance to.
final PerformanceLog _logger;
/// The resource provider for working with files.
final ResourceProvider _resourceProvider;
/// The byte storage to get and put serialized data.
///
/// It can be shared with other [AnalysisDriver]s.
final ByteStore _byteStore;
/// The cache of deserialized data read from SummaryDataReader.
///
/// It can be shared with other [AnalysisDriver]s.
final InfoDeclarationStore _infoDeclarationStore;
/// The optional store with externally provided unlinked and corresponding
/// linked summaries. These summaries are always added to the store for any
/// file analysis.
final SummaryDataStore? _externalSummaries;
/// This [ContentCache] is consulted for a file content before reading
/// the content from the file.
final FileContentCache _fileContentCache;
/// The already loaded unlinked units, consulted before deserializing
/// from file again.
final UnlinkedUnitStore _unlinkedUnitStore;
late final StoredFileContentStrategy _fileContentStrategy;
/// The [Packages] object with packages and their language versions.
final Packages _packages;
/// The [SourceFactory] is used to resolve URIs to paths and restore URIs
/// from file paths.
final SourceFactory _sourceFactory;
/// The support for executing macros.
final MacroSupport? macroSupport;
/// The container, shared with other drivers within the same collection,
/// into which all drivers record files ownership.
final OwnedFiles? ownedFiles;
/// The declared environment variables.
final DeclaredVariables declaredVariables;
/// The analysis context that created this driver / session.
DriverBasedAnalysisContext? analysisContext;
/// The salt to mix into all hashes used as keys for unlinked data.
Uint32List _saltForUnlinked = Uint32List(0);
/// The salt to mix into all hashes used as keys for elements.
Uint32List _saltForElements = Uint32List(0);
/// The salt to mix into all hashes used as keys for linked data.
Uint32List _saltForResolution = Uint32List(0);
/// The set of priority files, that should be analyzed sooner.
final _priorityFiles = <String>{};
/// The file changes that should be applied before processing requests.
final List<_FileChange> _pendingFileChanges = [];
/// When [_applyFileChangesSynchronously] is `true`, affected files are
/// accumulated here.
Set<String> _accumulatedAffected = {};
/// The completers to complete after [_pendingFileChanges] are applied.
final _pendingFileChangesCompleters = <Completer<List<String>>>[];
/// The mapping from the files for which analysis was requested using
/// [getResolvedUnit] to the [Completer]s to report the result.
final _requestedFiles = <String, List<Completer<SomeResolvedUnitResult>>>{};
/// The mapping from the files for which analysis was requested using
/// [getResolvedLibrary] to the [Completer]s to report the result.
final _requestedLibraries =
<String, List<Completer<SomeResolvedLibraryResult>>>{};
/// The queue of requests for completion.
final List<_ResolveForCompletionRequest> _resolveForCompletionRequests = [];
/// Set to `true` after first [discoverAvailableFiles].
bool _hasAvailableFilesDiscovered = false;
/// The requests to compute files defining a class member with the name.
final _definingClassMemberNameRequests =
<_GetFilesDefiningClassMemberNameRequest>[];
/// The requests to compute files referencing a name.
final _referencingNameRequests = <_GetFilesReferencingNameRequest>[];
/// The mapping from the files for which errors were requested using
/// [getErrors] to the [Completer]s to report the result.
final _errorsRequestedFiles = <String, List<Completer<SomeErrorsResult>>>{};
/// The mapping from the files for which the index was requested using
/// [getIndex] to the [Completer]s to report the result.
final _indexRequestedFiles =
<String, List<Completer<AnalysisDriverUnitIndex?>>>{};
/// The mapping from the files for which the unit element was requested using
/// [getUnitElement] to the [Completer]s to report the result.
final _unitElementRequestedFiles =
<String, List<Completer<SomeUnitElementResult>>>{};
/// The list of dispose requests, added in [dispose2], almost always empty.
/// We expect that at most one is added, at the very end of the life cycle.
final List<Completer<void>> _disposeRequests = [];
/// Resolution signatures of the most recently produced results for files.
final Map<String, String> _lastProducedSignatures = {};
/// Cached results for [_priorityFiles].
final Map<String, ResolvedUnitResult> _priorityResults = {};
/// Cached results of [getResolvedLibrary].
final Map<String, ResolvedLibraryResultImpl> _resolvedLibraryCache = {};
/// The controller for the [exceptions] stream.
final StreamController<ExceptionResult> _exceptionController =
StreamController<ExceptionResult>();
/// The instance of the [Search] helper.
late final Search _search;
final AnalysisDriverTestView? testView;
late FileSystemState _fsState;
/// The [FileTracker] used by this driver.
late FileTracker _fileTracker;
/// Whether resolved units should be indexed.
final bool enableIndex;
/// The context in which libraries should be analyzed.
LibraryContext? _libraryContext;
/// Whether `dart:core` has been transitively discovered.
bool _hasDartCoreDiscovered = false;
/// This flag is reset to `false` when a new file is added, because it
/// might be a library, so that some files that were disconnected parts
/// could be analyzed now.
bool _hasLibrariesDiscovered = false;
/// If testing data is being retained, a pointer to the object that is
/// retaining the testing data. Otherwise `null`.
final TestingData? testingData;
bool _disposed = false;
/// A map that associates files to corresponding analysis options.
late final AnalysisOptionsMap analysisOptionsMap;
/// Create a new instance of [AnalysisDriver].
///
/// The given [SourceFactory] is cloned to ensure that it does not contain a
/// reference to an [AnalysisContext] in which it could have been used.
AnalysisDriver({
required AnalysisDriverScheduler scheduler,
required PerformanceLog logger,
required ResourceProvider resourceProvider,
required ByteStore byteStore,
required SourceFactory sourceFactory,
required Packages packages,
this.macroSupport,
this.ownedFiles,
this.analysisContext,
@Deprecated("Use 'analysisOptionsMap' instead")
AnalysisOptionsImpl? analysisOptions,
AnalysisOptionsMap? analysisOptionsMap,
FileContentCache? fileContentCache,
UnlinkedUnitStore? unlinkedUnitStore,
InfoDeclarationStore? infoDeclarationStore,
this.enableIndex = false,
SummaryDataStore? externalSummaries,
DeclaredVariables? declaredVariables,
bool retainDataForTesting = false,
this.testView,
}) : _scheduler = scheduler,
_resourceProvider = resourceProvider,
_byteStore = byteStore,
_fileContentCache =
fileContentCache ?? FileContentCache.ephemeral(resourceProvider),
_unlinkedUnitStore = unlinkedUnitStore ?? UnlinkedUnitStoreImpl(),
_infoDeclarationStore =
infoDeclarationStore ?? NoOpInfoDeclarationStore(),
_logger = logger,
_packages = packages,
_sourceFactory = sourceFactory,
_externalSummaries = externalSummaries,
declaredVariables = declaredVariables ?? DeclaredVariables(),
testingData = retainDataForTesting ? TestingData() : null {
analysisContext?.driver = this;
testView?.driver = this;
// Setup the options map.
// This extra work is temporary and will get simplified when the deprecated support for
// passing in a single analysisOptions is removed.
if (analysisOptionsMap != null && analysisOptions != null) {
throw AssertionError(
'An analysisOptionsMap or analysisOptions can be specified, but not both');
}
if (analysisOptions != null) {
this.analysisOptionsMap =
AnalysisOptionsMap.forSharedOptions(analysisOptions);
} else {
// This '!' is temporary. The analysisOptionsMap is effectively required but can't be until Google3 is updated.
this.analysisOptionsMap = analysisOptionsMap!;
}
_fileContentStrategy = StoredFileContentStrategy(_fileContentCache);
_createFileTracker();
_scheduler.add(this);
_search = Search(this);
}
/// Return the set of files explicitly added to analysis using [addFile].
Set<String> get addedFiles => _fileTracker.addedFiles;
/// See [addedFiles].
Set<File> get addedFiles2 {
return addedFiles.map(resourceProvider.getFile).toSet();
}
/// Return the analysis options used to control analysis.
@Deprecated("Use 'getAnalysisOptionsForFile(file)' instead")
AnalysisOptions get analysisOptions => analysisOptionsMap.firstOrDefault;
/// Return the current analysis session.
AnalysisSessionImpl get currentSession {
return libraryContext.elementFactory.analysisSession;
}
/// Return a list of the names of all the plugins enabled in analysis options
/// in this driver.
List<String> get enabledPluginNames => analysisOptionsMap.entries
.map((e) => e.options.enabledPluginNames)
.flattenedToList2;
/// Return the stream that produces [ExceptionResult]s.
Stream<ExceptionResult> get exceptions => _exceptionController.stream;
/// The current file system state.
FileSystemState get fsState => _fsState;
bool get hasPendingFileChanges => _pendingFileChanges.isNotEmpty;
/// Return the set of files that are known at this moment. This set does not
/// always include all added files or all implicitly used file. If a file has
/// not been processed yet, it might be missing.
Set<FileState> get knownFiles => _fsState.knownFiles;
/// Return the context in which libraries should be analyzed.
LibraryContext get libraryContext {
return _libraryContext ??= LibraryContext(
testData: testView?.libraryContext,
analysisSession: AnalysisSessionImpl(this),
logger: _logger,
byteStore: _byteStore,
infoDeclarationStore: _infoDeclarationStore,
analysisOptionsMap: analysisOptionsMap,
declaredVariables: declaredVariables,
sourceFactory: _sourceFactory,
macroSupport: macroSupport,
externalSummaries: _externalSummaries,
fileSystemState: _fsState,
);
}
/// Return the path of the folder at the root of the context.
String get name => analysisContext?.contextRoot.root.path ?? '';
/// Return the number of files scheduled for analysis.
int get numberOfFilesToAnalyze => _fileTracker.numberOfPendingFiles;
/// Return the list of files that the driver should try to analyze sooner.
List<String> get priorityFiles => _priorityFiles.toList(growable: false);
/// Set the list of files that the driver should try to analyze sooner.
///
/// Every path in the list must be absolute and normalized.
///
/// The driver will produce the results through the `events` stream. The
/// exact order in which results are produced is not defined, neither
/// between priority files, nor between priority and non-priority files.
set priorityFiles(List<String> priorityPaths) {
_priorityResults.keys
.toSet()
.difference(priorityPaths.toSet())
.forEach(_priorityResults.remove);
_priorityFiles.clear();
_priorityFiles.addAll(priorityPaths);
_scheduler.notify();
}
/// See [priorityFiles].
set priorityFiles2(List<File> files) {
priorityFiles = files.map((e) => e.path).toList();
}
/// Return the [ResourceProvider] that is used to access the file system.
ResourceProvider get resourceProvider => _resourceProvider;
AnalysisDriverScheduler get scheduler => _scheduler;
/// Return the search support for the driver.
Search get search => _search;
/// Return the source factory used to resolve URIs to paths and restore URIs
/// from file paths.
SourceFactory get sourceFactory => _sourceFactory;
/// Return the priority of work that the driver needs to perform.
AnalysisDriverPriority get workPriority {
if (_disposeRequests.isNotEmpty) {
return AnalysisDriverPriority.interactive;
}
if (_resolveForCompletionRequests.isNotEmpty) {
return AnalysisDriverPriority.completion;
}
if (_requestedFiles.isNotEmpty) {
return AnalysisDriverPriority.interactive;
}
if (_requestedLibraries.isNotEmpty) {
return AnalysisDriverPriority.interactive;
}
if (_definingClassMemberNameRequests.isNotEmpty) {
return AnalysisDriverPriority.interactive;
}
if (_referencingNameRequests.isNotEmpty) {
return AnalysisDriverPriority.interactive;
}
if (_errorsRequestedFiles.isNotEmpty) {
return AnalysisDriverPriority.interactive;
}
if (_indexRequestedFiles.isNotEmpty) {
return AnalysisDriverPriority.interactive;
}
if (_unitElementRequestedFiles.isNotEmpty) {
return AnalysisDriverPriority.interactive;
}
if (_priorityFiles.isNotEmpty) {
for (String path in _priorityFiles) {
if (_fileTracker.isFilePending(path)) {
return AnalysisDriverPriority.priority;
}
}
}
if (_pendingFileChanges.isNotEmpty) {
return AnalysisDriverPriority.general;
}
if (_fileTracker.hasChangedFiles) {
return AnalysisDriverPriority.changedFiles;
}
if (_fileTracker.hasPendingChangedFiles) {
return AnalysisDriverPriority.generalChanged;
}
if (_fileTracker.hasPendingImportFiles) {
return AnalysisDriverPriority.generalImportChanged;
}
if (_fileTracker.hasPendingErrorFiles) {
return AnalysisDriverPriority.generalWithErrors;
}
if (_fileTracker.hasPendingFiles) {
return AnalysisDriverPriority.general;
}
if (_pendingFileChangesCompleters.isNotEmpty) {
return AnalysisDriverPriority.general;
}
return AnalysisDriverPriority.nothing;
}
/// Whether the driver has a file to analyze.
bool get _hasFilesToAnalyze {
return hasPendingFileChanges ||
_fileTracker.hasPendingFiles ||
_fileTracker.hasChangedFiles ||
_requestedLibraries.isNotEmpty ||
_requestedFiles.isNotEmpty ||
_errorsRequestedFiles.isNotEmpty ||
_definingClassMemberNameRequests.isNotEmpty ||
_referencingNameRequests.isNotEmpty ||
_indexRequestedFiles.isNotEmpty ||
_unitElementRequestedFiles.isNotEmpty ||
_disposeRequests.isNotEmpty;
}
/// Add the file with the given [path] to the set of files that are explicitly
/// being analyzed.
///
/// The [path] must be absolute and normalized.
///
/// The results of analysis are eventually produced by the `events` stream.
void addFile(String path) {
_throwIfNotAbsolutePath(path);
if (!_fsState.hasUri(path)) {
return;
}
if (file_paths.isDart(resourceProvider.pathContext, path)) {
_priorityResults.clear();
_resolvedLibraryCache.clear();
_pendingFileChanges.add(
_FileChange(path, _FileChangeKind.add),
);
_scheduler.notify();
}
}
/// See [addFile].
void addFile2(File file) {
addFile(file.path);
}
/// Return a [Future] that completes after pending file changes are applied,
/// so that [currentSession] can be used to compute results.
///
/// The value is the set of all files that are potentially affected by
/// the pending changes. This set can be both wider than the set of analyzed
/// files (because it may include files imported from other packages, and
/// which are on the import path from a changed file to an analyze file),
/// and narrower than the set of analyzed files (because only files that
/// were previously accessed are considered to be known and affected).
Future<List<String>> applyPendingFileChanges() {
if (_pendingFileChanges.isNotEmpty) {
if (_disposed) throw DisposedAnalysisContextResult();
var completer = Completer<List<String>>();
_pendingFileChangesCompleters.add(completer);
return completer.future;
} else {
var accumulatedAffected = _accumulatedAffected.toList();
_accumulatedAffected = {};
return Future.value(accumulatedAffected);
}
}
/// Builds elements for library files from [uriList], and packs them into
/// a bundle suitable for [PackageBundleReader].
///
/// Disconnected non-library files are ignored.
Future<Uint8List> buildPackageBundle({
required List<Uri> uriList,
PackageBundleSdk? packageBundleSdk,
}) async {
var elementFactory = libraryContext.elementFactory;
var bundleWriter = BundleWriter(
elementFactory.dynamicRef,
);
var packageBundleBuilder = PackageBundleBuilder();
for (var uri in uriList) {
var uriStr = uri.toString();
var libraryResult = await getLibraryByUri(uriStr);
if (libraryResult is LibraryElementResult) {
var libraryElement = libraryResult.element as LibraryElementImpl;
bundleWriter.writeLibraryElement(libraryElement);
packageBundleBuilder.addLibrary(
uriStr,
libraryElement.units.map((e) {
return e.source.uri.toString();
}).toList(),
);
}
}
var writeWriterResult = bundleWriter.finish();
return packageBundleBuilder.finish(
resolutionBytes: writeWriterResult.resolutionBytes,
sdk: packageBundleSdk,
);
}
/// The file with the given [path] might have changed - updated, added or
/// removed. Or not, we don't know. Or it might have, but then changed back.
///
/// The [path] must be absolute and normalized.
///
/// The [path] can be any file - explicitly or implicitly analyzed, or neither.
///
/// Causes the analysis state to transition to "working" (if it is not in
/// that state already). Schedules the file contents for [path] to be read
/// into the current file state prior to the next time the analysis state
/// transitions to "idle".
///
/// Invocation of this method will not prevent a [Future] returned from
/// [getResolvedUnit] from completing with a result, but the result is not
/// guaranteed to be consistent with the new current file state after this
/// [changeFile] invocation.
void changeFile(String path) {
_throwIfNotAbsolutePath(path);
if (!_fsState.hasUri(path)) {
return;
}
if (file_paths.isDart(resourceProvider.pathContext, path)) {
_priorityResults.clear();
_resolvedLibraryCache.clear();
_pendingFileChanges.add(
_FileChange(path, _FileChangeKind.change),
);
_scheduler.notify();
}
}
/// See [changeFile].
void changeFile2(File file) {
changeFile(file.path);
}
/// Clear the library context and any related data structures. Mostly we do
/// this to reduce memory consumption. The library context holds to every
/// library that was resynthesized, but after some initial analysis we might
/// not get again to many of these libraries. So, we should clear the context
/// periodically.
void clearLibraryContext() {
_libraryContext?.dispose();
_libraryContext = null;
}
/// Return a [Future] that completes when discovery of all files that are
/// potentially available is done, so that they are included in [knownFiles].
Future<void> discoverAvailableFiles() async {
if (_hasAvailableFilesDiscovered) {
return;
}
_hasAvailableFilesDiscovered = true;
// Discover added files.
for (var path in addedFiles) {
_fsState.getFileForPath(path);
}
// Discover SDK libraries.
if (_sourceFactory.dartSdk case var dartSdk?) {
for (var sdkLibrary in dartSdk.sdkLibraries) {
var source = dartSdk.mapDartUri(sdkLibrary.shortName);
var path = source!.fullName;
_fsState.getFileForPath(path);
}
}
void discoverRecursively(Folder folder) {
try {
var pathContext = resourceProvider.pathContext;
for (var child in folder.getChildren()) {
if (child is File) {
var path = child.path;
if (file_paths.isDart(pathContext, path)) {
_fsState.getFileForPath(path);
}
} else if (child is Folder) {
discoverRecursively(child);
}
}
} catch (_) {}
}
// Discover files in package/lib folders.
if (_sourceFactory.packageMap case var packageMap?) {
var folders = packageMap.values.flattenedToList2;
for (var folder in folders) {
discoverRecursively(folder);
}
}
}
/// Notify the driver that the client is going to stop using it.
Future<void> dispose2() async {
var completer = Completer<void>();
_disposed = true;
_disposeRequests.add(completer);
// Complete all waiting completers.
for (var completerList in _requestedLibraries.values) {
for (var completer in completerList) {
completer.complete(DisposedAnalysisContextResult());
}
}
_requestedLibraries.clear();
for (var completerList in _requestedFiles.values) {
for (var completer in completerList) {
completer.complete(DisposedAnalysisContextResult());
}
}
_requestedFiles.clear();
for (var completerList in _unitElementRequestedFiles.values) {
for (var completer in completerList) {
completer.complete(DisposedAnalysisContextResult());
}
}
_unitElementRequestedFiles.clear();
for (var completerList in _errorsRequestedFiles.values) {
for (var completer in completerList) {
completer.complete(DisposedAnalysisContextResult());
}
}
_errorsRequestedFiles.clear();
for (var completer in _pendingFileChangesCompleters) {
completer.completeError(DisposedAnalysisContextResult());
}
_pendingFileChangesCompleters.clear();
_scheduler.notify();
return completer.future;
}
/// NOTE: this API is experimental and subject to change in a future
/// release (see https://github.com/dart-lang/sdk/issues/53876 for context).
@experimental
AnalysisOptionsImpl getAnalysisOptionsForFile(File file) =>
analysisOptionsMap.getOptions(file);
/// Return the cached [ResolvedUnitResult] for the Dart file with the given
/// [path]. If there is no cached result, return `null`. Usually only results
/// of priority files are cached.
///
/// The [path] must be absolute and normalized.
///
/// The [path] can be any file - explicitly or implicitly analyzed, or neither.
ResolvedUnitResult? getCachedResolvedUnit(String path) {
_throwIfNotAbsolutePath(path);
return _priorityResults[path];
}
/// See [getCachedResolvedUnit].
ResolvedUnitResult? getCachedResolvedUnit2(File file) {
return _priorityResults[file.path];
}
/// Return a [Future] that completes with the [ErrorsResult] for the Dart
/// file with the given [path].
///
/// The [path] must be absolute and normalized.
///
/// This method does not use analysis priorities, and must not be used in
/// interactive analysis, such as Analysis Server or its plugins.
Future<SomeErrorsResult> getErrors(String path) async {
if (!_isAbsolutePath(path)) {
return Future.value(
InvalidPathResult(),
);
}
if (!_fsState.hasUri(path)) {
return Future.value(
NotPathOfUriResult(),
);
}
if (_disposed) {
return Future.value(
DisposedAnalysisContextResult(),
);
}
// If a macro generated file, request its library instead.
var file = resourceProvider.getFile(path);
if (file.libraryForMacro case var library?) {
_errorsRequestedFiles.addKey(library.path);
}
// Schedule analysis.
var completer = Completer<SomeErrorsResult>();
_errorsRequestedFiles.add(path, completer);
_scheduler.notify();
return completer.future;
}
/// Completes with files that define a class member with the [name].
Future<List<FileState>> getFilesDefiningClassMemberName(String name) async {
await discoverAvailableFiles();
var request = _GetFilesDefiningClassMemberNameRequest(name);
_definingClassMemberNameRequests.add(request);
_scheduler.notify();
return request.completer.future;
}
/// Completes with files that reference the given external [name].
Future<List<FileState>> getFilesReferencingName(String name) async {
await discoverAvailableFiles();
var request = _GetFilesReferencingNameRequest(name);
_referencingNameRequests.add(request);
_scheduler.notify();
return request.completer.future;
}
/// Return the [FileResult] for the Dart file with the given [path].
///
/// The [path] must be absolute and normalized.
SomeFileResult getFileSync(String path) {
if (!_isAbsolutePath(path)) {
return InvalidPathResult();
}
FileState file = _fsState.getFileForPath(path);
return FileResultImpl(
session: currentSession,
fileState: file,
);
}
/// See [getFileSync].
SomeFileResult getFileSync2(File file) {
return getFileSync(file.path);
}
/// Return a [Future] that completes with the [AnalysisDriverUnitIndex] for
/// the file with the given [path], or with `null` if the file cannot be
/// analyzed.
Future<AnalysisDriverUnitIndex?> getIndex(String path) {
_throwIfNotAbsolutePath(path);
if (!enableIndex) {
throw ArgumentError('Indexing is not enabled.');
}
if (!_fsState.hasUri(path)) {
return Future.value();
}
// If a macro generated file, request its library instead.
var file = resourceProvider.getFile(path);
if (file.libraryForMacro case var library?) {
_indexRequestedFiles.addKey(library.path);
}
// Schedule analysis.
var completer = Completer<AnalysisDriverUnitIndex?>();
_indexRequestedFiles.add(path, completer);
_scheduler.notify();
return completer.future;
}
/// See [getIndex].
Future<AnalysisDriverUnitIndex?> getIndex2(File file) {
return getIndex(file.path);
}
/// Return a [Future] that completes with [LibraryElementResult] for the given
/// [uri], which is either resynthesized from the provided external summary
/// store, or built for a file to which the given [uri] is resolved.
Future<SomeLibraryElementResult> getLibraryByUri(String uri) async {
var uriObj = uriCache.parse(uri);
// Check if the element is already computed.
if (_pendingFileChanges.isEmpty) {
var rootReference = libraryContext.elementFactory.rootReference;
var reference = rootReference.getChild('$uriObj');
var element = reference.element;
if (element is LibraryElementImpl) {
return LibraryElementResultImpl(element);
}
}
var fileOr = _fsState.getFileForUri(uriObj);
switch (fileOr) {
case null:
return CannotResolveUriResult();
case UriResolutionFile(:var file):
var kind = file.kind;
if (kind is LibraryFileKind) {
} else if (kind is AugmentationFileKind) {
return NotLibraryButAugmentationResult();
} else if (kind is PartFileKind) {
return NotLibraryButPartResult();
} else {
throw UnimplementedError('(${kind.runtimeType}) $kind');
}
var unitResult = await getUnitElement(file.path);
if (unitResult is UnitElementResult) {
return LibraryElementResultImpl(unitResult.element.library);
}
// Some invalid results are invalid results for this request.
// Note that up-down promotion does not work.
if (unitResult is InvalidResult &&
unitResult is SomeLibraryElementResult) {
return unitResult as SomeLibraryElementResult;
}
// Should not happen.
return UnspecifiedInvalidResult();
case UriResolutionExternalLibrary(:var source):
var uri = source.uri;
// TODO(scheglov): Check if the source is not for library.
var element = libraryContext.getLibraryElement(uri);
return LibraryElementResultImpl(element);
}
}
/// Return a [ParsedLibraryResult] for the library with the given [path].
///
/// The [path] must be absolute and normalized.
SomeParsedLibraryResult getParsedLibrary(String path) {
if (!_isAbsolutePath(path)) {
return InvalidPathResult();
}
if (!_fsState.hasUri(path)) {
return NotPathOfUriResult();
}
var file = _fsState.getFileForPath(path);
var kind = file.kind;
if (kind is LibraryFileKind) {
} else if (kind is AugmentationFileKind) {
return NotLibraryButAugmentationResult();
} else if (kind is PartFileKind) {
return NotLibraryButPartResult();
} else {
throw UnimplementedError('(${kind.runtimeType}) $kind');
}
var units = <ParsedUnitResult>[];
for (var unitFile in kind.files) {
var unitPath = unitFile.path;
var unitResult = parseFileSync(unitPath);
if (unitResult is! ParsedUnitResult) {
return UnspecifiedInvalidResult();
}
units.add(unitResult);
}
return ParsedLibraryResultImpl(
session: currentSession,
units: units,
);
}
/// See [getParsedLibrary].
SomeParsedLibraryResult getParsedLibrary2(File file) {
return getParsedLibrary(file.path);
}
/// Return a [ParsedLibraryResult] for the library with the given [uri].
SomeParsedLibraryResult getParsedLibraryByUri(Uri uri) {
var fileOr = _fsState.getFileForUri(uri);
switch (fileOr) {
case null:
return CannotResolveUriResult();
case UriResolutionFile(:var file):
return getParsedLibrary(file.path);
case UriResolutionExternalLibrary():
return UriOfExternalLibraryResult();
}
}
/// Return a [Future] that completes with a [ResolvedLibraryResult] for the
/// Dart library file with the given [path]. If the file cannot be analyzed,
/// the [Future] completes with an [InvalidResult].
///
/// The [path] must be absolute and normalized.
///
/// The [path] can be any file - explicitly or implicitly analyzed, or neither.
///
/// Invocation of this method causes the analysis state to transition to
/// "working" (if it is not in that state already), the driver will produce
/// the resolution result for it, which is consistent with the current file
/// state (including new states of the files previously reported using
/// [changeFile]), prior to the next time the analysis state transitions
/// to "idle".
Future<SomeResolvedLibraryResult> getResolvedLibrary(String path) async {
if (!_isAbsolutePath(path)) {
return InvalidPathResult();
}
if (!_fsState.hasUri(path)) {
return NotPathOfUriResult();
}
if (_disposed) {
return Future.value(
DisposedAnalysisContextResult(),
);
}
if (_resolvedLibraryCache[path] case var cached?) {
return cached;
}
var completer = Completer<SomeResolvedLibraryResult>();
_requestedLibraries.add(path, completer);
_scheduler.notify();
return completer.future;
}
/// Return a [Future] that completes with a [ResolvedLibraryResult] for the
/// Dart library file with the given [uri]. If the file cannot be analyzed,
/// the [Future] completes with an [InvalidResult].
///
/// Invocation of this method causes the analysis state to transition to
/// "working" (if it is not in that state already), the driver will produce
/// the resolution result for it, which is consistent with the current file
/// state (including new states of the files previously reported using
/// [changeFile]), prior to the next time the analysis state transitions
/// to "idle".
Future<SomeResolvedLibraryResult> getResolvedLibraryByUri(Uri uri) async {
var fileOr = _fsState.getFileForUri(uri);
switch (fileOr) {
case null:
return CannotResolveUriResult();
case UriResolutionFile(:var file):
return getResolvedLibrary(file.path);
case UriResolutionExternalLibrary():
return UriOfExternalLibraryResult();
}
}
/// Return a [Future] that completes with a [SomeResolvedUnitResult] for the
/// Dart file with the given [path]. If the file cannot be analyzed,
/// the [Future] completes with an [InvalidResult].
///
/// The [path] must be absolute and normalized.
///
/// The [path] can be any file - explicitly or implicitly analyzed, or neither.
///
/// If the driver has the cached analysis result for the file, it is returned.
/// If [sendCachedToStream] is `true`, then the result is also reported into
/// the `events` stream, just as if it were freshly computed.
///
/// Otherwise causes the analysis state to transition to "working" (if it is
/// not in that state already), the driver will produce the analysis result for
/// it, which is consistent with the current file state (including new states
/// of the files previously reported using [changeFile]), prior to the next
/// time the analysis state transitions to "idle".
Future<SomeResolvedUnitResult> getResolvedUnit(String path,
{bool sendCachedToStream = false}) {
if (!_isAbsolutePath(path)) {
return Future.value(
InvalidPathResult(),
);
}
if (!_fsState.hasUri(path)) {
return Future.value(
NotPathOfUriResult(),
);
}
// Return the cached result.
{
ResolvedUnitResult? result = getCachedResolvedUnit(path);
if (result != null) {
if (sendCachedToStream) {
_scheduler.eventsController.add(result);
}
return Future.value(result);
}
}
if (_disposed) {
return Future.value(
DisposedAnalysisContextResult(),
);
}
// If a macro generated file, request its library instead.
var file = resourceProvider.getFile(path);
if (file.libraryForMacro case var library?) {
_requestedFiles.addKey(library.path);
}
// Schedule analysis.
var completer = Completer<SomeResolvedUnitResult>();
_requestedFiles.add(path, completer);
_scheduler.notify();
return completer.future;
}
/// See [getResolvedUnit].
Future<SomeResolvedUnitResult> getResolvedUnit2(File file,
{bool sendCachedToStream = false}) {
return getResolvedUnit(
file.path,
sendCachedToStream: sendCachedToStream,
);
}
/// Return a [Future] that completes with the [SomeUnitElementResult]
/// for the file with the given [path].
Future<SomeUnitElementResult> getUnitElement(String path) {
if (!_isAbsolutePath(path)) {
return Future.value(
InvalidPathResult(),
);
}
if (!_fsState.hasUri(path)) {
return Future.value(
NotPathOfUriResult(),
);
}
if (_disposed) {
return Future.value(
DisposedAnalysisContextResult(),
);
}
// If a macro generated file, request its library.
// Once the library is ready, we can return the requested result.
var file = resourceProvider.getFile(path);
if (file.libraryForMacro case var library?) {
_unitElementRequestedFiles.addKey(library.path);
}
// Schedule analysis.
var completer = Completer<SomeUnitElementResult>();
_unitElementRequestedFiles.add(path, completer);
_scheduler.notify();
return completer.future;
}
/// See [getUnitElement].
Future<SomeUnitElementResult> getUnitElement2(File file) {
return getUnitElement(file.path);
}
/// Return a [ParsedUnitResult] for the file with the given [path].
///
/// The [path] must be absolute and normalized.
///
/// The [path] can be any file - explicitly or implicitly analyzed, or neither.
///
/// The parsing is performed in the method itself, and the result is not
/// produced through the `events` stream (just because it is not a fully
/// resolved unit).
SomeParsedUnitResult parseFileSync(String path) {
if (!_isAbsolutePath(path)) {
return InvalidPathResult();
}
FileState file = _fsState.getFileForPath(path);
RecordingErrorListener listener = RecordingErrorListener();
CompilationUnit unit = file.parse(listener);
return ParsedUnitResultImpl(
session: currentSession,
fileState: file,
unit: unit,
errors: listener.errors,
);
}
/// See [parseFileSync].
SomeParsedUnitResult parseFileSync2(File file) {
return parseFileSync(file.path);
}
/// Perform a single chunk of work and produce `events`.
Future<void> performWork() async {
_discoverDartCore();
_discoverLibraries();
if (_resolveForCompletionRequests.removeLastOrNull() case var request?) {
try {
var result = await _resolveForCompletion(request);
request.completer.complete(result);
} catch (exception, stackTrace) {
_reportException(request.path, exception, stackTrace, null);
request.completer.completeError(exception, stackTrace);
_clearLibraryContextAfterException();
}
return;
}
// Analyze a requested file.
if (_requestedFiles.firstKey case var path?) {
await _analyzeFile(path);
return;
}
// Analyze a requested library.
if (_requestedLibraries.firstKey case var path?) {
await _getResolvedLibrary(path);
return;
}
// Process an error request.
if (_errorsRequestedFiles.firstKey case var path?) {
await _getErrors(path);
return;
}
// Process an index request.
if (_indexRequestedFiles.firstKey case var path?) {
await _getIndex(path);
return;
}
// Process a unit element request.
if (_unitElementRequestedFiles.firstKey case var path?) {
await _getUnitElement(path);
return;
}
// Compute files defining a class member.
if (_definingClassMemberNameRequests.removeLastOrNull() case var request?) {
await _getFilesDefiningClassMemberName(request);
return;
}
// Compute files referencing a name.
if (_referencingNameRequests.removeLastOrNull() case var request?) {
await _getFilesReferencingName(request);
return;
}
// Analyze a priority file.
for (var path in _priorityFiles) {
if (_fileTracker.isFilePending(path)) {
await _analyzeFile(path);
return;
}
}
// Analyze a general file.
if (_fileTracker.anyPendingFile case var path?) {
await _produceErrors(path);
return;
}
}
/// Remove the file with the given [path] from the list of files to analyze.
///
/// The [path] must be absolute and normalized.
///
/// The results of analysis of the file might still be produced by the
/// `events` stream. The driver will try to stop producing these results,
/// but does not guarantee this.
void removeFile(String path) {
_throwIfNotAbsolutePath(path);
if (!_fsState.hasUri(path)) {
return;
}
if (file_paths.isDart(resourceProvider.pathContext, path)) {
_lastProducedSignatures.remove(path);
_priorityResults.clear();
_resolvedLibraryCache.clear();
_pendingFileChanges.add(
_FileChange(path, _FileChangeKind.remove),
);
_scheduler.notify();
}
}
/// See [removeFile].
void removeFile2(File file) {
removeFile(file.path);
}
Future<ResolvedForCompletionResultImpl?> resolveForCompletion({
required String path,
required int offset,
required OperationPerformanceImpl performance,
}) async {
var request = _ResolveForCompletionRequest(
path: path,
offset: offset,
performance: performance,
);
_resolveForCompletionRequests.add(request);
_scheduler.notify();
return request.completer.future;
}
void _addDeclaredVariablesToSignature(ApiSignature buffer) {
var variableNames = declaredVariables.variableNames;
buffer.addInt(variableNames.length);
for (var name in variableNames) {
var value = declaredVariables.get(name);
buffer.addString(name);
buffer.addString(value!);
}
}
Future<void> _analyzeFile(String path) async {
await scheduler.accumulatedPerformance.runAsync(
'analyzeFile',
(performance) async {
await _analyzeFileImpl(
path: path,
performance: performance,
);
},
);
}
Future<void> _analyzeFileImpl({
required String path,
required OperationPerformanceImpl performance,
}) async {
// We will produce the result for this file, at least.
// And for any other files of the same library.
_fileTracker.fileWasAnalyzed(path);
var file = _fsState.getFileForPath(path);
// Prepare the library - the file itself, or the known library.
var kind = file.kind;
var library = kind.library ?? kind.asLibrary;
// We need the fully resolved unit, or the result is not cached.
return _logger.runAsync('Compute analysis result for $path', () async {
_logger.writeln('Work in $name');
try {
testView?.numOfAnalyzedLibraries++;
_scheduler.eventsController.add(
events.AnalyzeFile(
file: file,
library: library,
),
);
if (!_hasLibraryByUri('dart:core')) {
_errorsRequestedFiles.completeAll(
path,
_newMissingDartLibraryResult(file, 'dart:core'),
);
return;
}
if (!_hasLibraryByUri('dart:async')) {
_errorsRequestedFiles.completeAll(
path,
_newMissingDartLibraryResult(file, 'dart:async'),
);
return;
}
await performance.runAsync(
'libraryContext',
(performance) async {
await libraryContext.load(
targetLibrary: library,
performance: performance,
);
},
);
for (var import in library.docImports) {
if (import is LibraryImportWithFile) {
if (import.importedLibrary case var libraryFileKind?) {
await libraryContext.load(
targetLibrary: libraryFileKind,
performance: OperationPerformanceImpl('<root>'),
);
}
}
}
var analysisOptions = file.analysisOptions;
var libraryElement =
libraryContext.elementFactory.libraryOfUri2(library.file.uri);
var typeSystemOperations = TypeSystemOperations(
libraryElement.typeSystem,
strictCasts: analysisOptions.strictCasts);
var results = LibraryAnalyzer(
analysisOptions,
declaredVariables,
libraryElement,
libraryContext.elementFactory.analysisSession.inheritanceManager,
library,
testingData: testingData,
typeSystemOperations: typeSystemOperations,
).analyze();
var isLibraryWithPriorityFile = _isLibraryWithPriorityFile(library);
var resolvedUnits = <ResolvedUnitResultImpl>[];
for (var unitResult in results) {
var unitFile = unitResult.file;
var index = enableIndex
? indexUnit(unitResult.unit)
: AnalysisDriverUnitIndexBuilder();
var resolvedUnit = _createResolvedUnitImpl(
file: unitFile,
unitResult: unitResult,
);
resolvedUnits.add(resolvedUnit);
// getResolvedUnit()
_requestedFiles.completeAll(unitFile.path, resolvedUnit);
// getErrors()
_errorsRequestedFiles.completeAll(
unitFile.path,
_createErrorsResultImpl(
file: unitFile,
errors: unitResult.errors,
),
);
// getIndex()
_indexRequestedFiles.completeAll(unitFile.path, index);
var unitSignature = _getResolvedUnitSignature(library, unitFile);
{
var unitKey = _getResolvedUnitKey(unitSignature);
var unitBytes = AnalysisDriverResolvedUnitBuilder(
errors: unitResult.errors.map((error) {
return ErrorEncoding.encode(error);
}).toList(),
index: index,
).toBuffer();
_byteStore.putGet(unitKey, unitBytes);
}
_fileTracker.fileWasAnalyzed(unitFile.path);
_lastProducedSignatures[path] = unitSignature;
_scheduler.eventsController.add(resolvedUnit);
if (isLibraryWithPriorityFile) {
_priorityResults[unitFile.path] = resolvedUnit;
}
_updateHasErrorOrWarningFlag(unitFile, resolvedUnit.errors);
}
var libraryResult = ResolvedLibraryResultImpl(
session: currentSession,
element: resolvedUnits.first.libraryElement,
units: resolvedUnits,
);
if (isLibraryWithPriorityFile) {
_resolvedLibraryCache[library.file.path] = libraryResult;
}
// getResolvedLibrary()
_requestedLibraries.completeAll(library.file.path, libraryResult);
// Return the result, full or partial.
_logger.writeln('Computed new analysis result.');
// return result;
} catch (exception, stackTrace) {
var contextKey =
_storeExceptionContext(path, library, exception, stackTrace);
_reportException(path, exception, stackTrace, contextKey);
// Complete all related requests with an error.
void completeWithError<T>(List<Completer<T>>? completers) {
if (completers != null) {
for (var completer in completers) {
completer.completeError(exception, stackTrace);
}
}
}
// TODO(scheglov): write tests
for (var file in library.files) {
// getResolvedUnit()
completeWithError(
_requestedFiles.remove(file.path),
);
// getErrors()
completeWithError(
_errorsRequestedFiles.remove(file.path),
);
}
// getResolvedLibrary()
completeWithError(
_requestedLibraries.remove(library.file.path),
);
_clearLibraryContextAfterException();
}
});
}
void _applyPendingFileChanges() {
var accumulatedAffected = <String>{};
for (var fileChange in _pendingFileChanges) {
var path = fileChange.path;
_removePotentiallyAffectedLibraries(accumulatedAffected, path);
switch (fileChange.kind) {
case _FileChangeKind.add:
_fileTracker.addFile(path);
case _FileChangeKind.change:
_fileTracker.changeFile(path);
case _FileChangeKind.remove:
_fileTracker.removeFile(path);
// TODO(scheglov): We have to do this because we discard files.
// But this is not right, we need to handle removing better.
clearLibraryContext();
}
}
_pendingFileChanges.clear();
// Read files, so that synchronous methods also see new content.
while (_fileTracker.verifyChangedFilesIfNeeded()) {}
if (_pendingFileChangesCompleters.isNotEmpty) {
var completers = _pendingFileChangesCompleters.toList();
_pendingFileChangesCompleters.clear();
for (var completer in completers) {
completer.complete(
accumulatedAffected.toList(),
);
}
}
}
/// There was an exception during a file analysis, we don't know why.
/// But it might have been caused by an inconsistency of files state, and
/// the library context state. Reset the library context, and hope that
/// we will solve the inconsistency while loading / building summaries.
void _clearLibraryContextAfterException() {
clearLibraryContext();
_priorityResults.clear();
_resolvedLibraryCache.clear();
}
ErrorsResultImpl _createErrorsResultFromBytes(
FileState file,
LibraryFileKind library,
Uint8List bytes,
) {
_scheduler.eventsController.add(
events.GetErrorsFromBytes(
file: file,
library: library,
),
);
var unit = AnalysisDriverResolvedUnit.fromBuffer(bytes);
var errors = _getErrorsFromSerialized(file, unit.errors);
_updateHasErrorOrWarningFlag(file, errors);
var result = _createErrorsResultImpl(
file: file,
errors: errors,
);
return result;
}
ErrorsResultImpl _createErrorsResultImpl({
required FileState file,
required List<AnalysisError> errors,
}) {
return ErrorsResultImpl(
session: currentSession,
file: file.resource,
content: file.content,
lineInfo: file.lineInfo,
uri: file.uri,
isAugmentation: file.kind is AugmentationFileKind,
isLibrary: file.kind is LibraryFileKind,
isMacroAugmentation: file.isMacroAugmentation,
isPart: file.kind is PartFileKind,
errors: errors,
analysisOptions: file.analysisOptions,
);
}
/// Creates new [FileSystemState] and [FileTracker] objects.
///
/// This is used both on initial construction and whenever the configuration
/// changes.
void _createFileTracker() {
_fillSalt();
var featureSetProvider = FeatureSetProvider.build(
sourceFactory: sourceFactory,
resourceProvider: _resourceProvider,
packages: _packages,
);
_fsState = FileSystemState(
_logger,
_byteStore,
_resourceProvider,
name,
sourceFactory,
analysisContext?.contextRoot.workspace,
declaredVariables,
_saltForUnlinked,
_saltForElements,
featureSetProvider,
analysisOptionsMap,
fileContentStrategy: _fileContentStrategy,
unlinkedUnitStore: _unlinkedUnitStore,
prefetchFiles: null,
isGenerated: (_) => false,
onNewFile: _onNewFile,
testData: testView?.fileSystem,
);
_fileTracker = FileTracker(_logger, _fsState, _fileContentStrategy);
}
ResolvedUnitResultImpl _createResolvedUnitImpl({
required FileState file,
required UnitAnalysisResult unitResult,
}) {
return ResolvedUnitResultImpl(
session: currentSession,
fileState: file,
unit: unitResult.unit,
errors: unitResult.errors,
);
}
/// When we look at a part that has a `part of name;` directive, we
/// usually don't know the library (in contrast to `part of uri;`).
/// So, we have no choice than to resolve this part as its own library.
///
/// But parts of `dart:xyz` libraries are special. The reason is that
/// `dart:core` is always implicitly imported. So, when we start building
/// the library cycle of such "part as a library", we discover `dart:core`,
/// and see that it contains our part. So, we don't add it as a library on
/// its own. But have already committed that it is a library. This causes
/// an exception in `LinkedElementFactory`.
///
/// The current workaround for this is to discover `dart:core` before any
/// analysis.
void _discoverDartCore() {
if (_hasDartCoreDiscovered) {
return;
}
_hasDartCoreDiscovered = true;
var dartCoreUri = uriCache.parse('dart:core');
var dartCoreResolution = _fsState.getFileForUri(dartCoreUri);
if (dartCoreResolution is UriResolutionFile) {
var kind = dartCoreResolution.file.kind;
if (kind is LibraryFileKind) {
kind.discoverReferencedFiles();
}
}
}
void _discoverLibraries() {
if (_hasLibrariesDiscovered) {
return;
}
_hasLibrariesDiscovered = true;
for (var path in _fileTracker.addedFiles) {
_fsState.getFileForPath(path);
}
}
Future<void> _ensureMacroGeneratedFiles() async {
for (var file in knownFiles.toList()) {
if (file.kind case LibraryFileKind libraryKind) {
var libraryCycle = libraryKind.libraryCycle;
if (libraryCycle.importsMacroClass) {
if (!libraryCycle.hasMacroFilesCreated) {
libraryCycle.hasMacroFilesCreated = true;
// We create macro-generated FileState(s) when load bundles.
await libraryContext.load(
targetLibrary: libraryKind,
performance: OperationPerformanceImpl('<root>'),
);
}
}
}
}
}
void _fillSalt() {
_fillSaltForUnlinked();
_fillSaltForElements();
_fillSaltForResolution();
}
void _fillSaltForElements() {
var buffer = ApiSignature();
buffer.addInt(DATA_VERSION);
_addDeclaredVariablesToSignature(buffer);
_saltForElements = buffer.toUint32List();
}
void _fillSaltForResolution() {
var buffer = ApiSignature();
buffer.addInt(DATA_VERSION);
buffer.addBool(enableIndex);
buffer.addBool(enableDebugResolutionMarkers);
_addDeclaredVariablesToSignature(buffer);
var workspace = analysisContext?.contextRoot.workspace;
workspace?.contributeToResolutionSalt(buffer);
_saltForResolution = buffer.toUint32List();
}
void _fillSaltForUnlinked() {
var buffer = ApiSignature();
buffer.addInt(DATA_VERSION);
buffer.addBool(enableIndex);
_saltForUnlinked = buffer.toUint32List();
}
Future<void> _getErrors(String path) async {
var file = _fsState.getFileForPath(path);
// Prepare the library - the file itself, or the known library.
var kind = file.kind;
var library = kind.library ?? kind.asLibrary;
// Prepare the signature and key.
var signature = _getResolvedUnitSignature(library, file);
var key = _getResolvedUnitKey(signature);
var bytes = _byteStore.get(key);
if (bytes != null) {
var result = _createErrorsResultFromBytes(file, library, bytes);
_errorsRequestedFiles.completeAll(path, result);
return;
}
await _analyzeFile(path);
}
/// Return [AnalysisError]s for the given [serialized] errors.
List<AnalysisError> _getErrorsFromSerialized(
FileState file, List<AnalysisDriverUnitError> serialized) {
List<AnalysisError> errors = <AnalysisError>[];
for (AnalysisDriverUnitError error in serialized) {
var analysisError = ErrorEncoding.decode(file.source, error);
if (analysisError != null) {
errors.add(analysisError);
}
}
return errors;
}
Future<void> _getFilesDefiningClassMemberName(
_GetFilesDefiningClassMemberNameRequest request,
) async {
await _ensureMacroGeneratedFiles();
var result = <FileState>[];
for (var file in knownFiles) {
if (file.definedClassMemberNames.contains(request.name)) {
result.add(file);
}
}
request.completer.complete(result);
}
Future<void> _getFilesReferencingName(
_GetFilesReferencingNameRequest request,
) async {
await _ensureMacroGeneratedFiles();
var result = <FileState>[];
for (var file in knownFiles) {
if (file.referencedNames.contains(request.name)) {
result.add(file);
}
}
request.completer.complete(result);
}
Future<void> _getIndex(String path) async {
var file = _fsState.getFileForPath(path);
// Prepare the library - the file itself, or the known library.
var kind = file.kind;
var library = kind.library ?? kind.asLibrary;
// Prepare the signature and key.
var signature = _getResolvedUnitSignature(library, file);
var key = _getResolvedUnitKey(signature);
var bytes = _byteStore.get(key);
if (bytes != null) {
var unit = AnalysisDriverResolvedUnit.fromBuffer(bytes);
_indexRequestedFiles.completeAll(path, unit.index!);
return;
}
await _analyzeFile(path);
}
/// Completes the [getResolvedLibrary] request.
Future<void> _getResolvedLibrary(String path) async {
var file = _fsState.getFileForPath(path);
var kind = file.kind;
switch (kind) {
case LibraryFileKind():
break;
case AugmentationFileKind():
_requestedLibraries.completeAll(
path,
NotLibraryButAugmentationResult(),
);
return;
case PartFileKind():
_requestedLibraries.completeAll(
path,
NotLibraryButPartResult(),
);
return;
default:
throw UnimplementedError('(${kind.runtimeType}) $kind');
}
if (_resolvedLibraryCache[path] case var cached?) {
_requestedLibraries.completeAll(path, cached);
return;
}
await _analyzeFile(path);
}
/// Return the key to store fully resolved results for the [signature].
String _getResolvedUnitKey(String signature) {
return '$signature.resolved';
}
/// Return the signature that identifies fully resolved results for the [file]
/// in the [library], e.g. element model, errors, index, etc.
String _getResolvedUnitSignature(LibraryFileKind library, FileState file) {
ApiSignature signature = ApiSignature();
signature.addUint32List(_saltForResolution);
if (file.workspacePackage is PubPackage) {
signature.addString(
(file.workspacePackage as PubPackage).pubspecContent ?? '');
}
signature.addString(library.file.uriStr);
signature.addString(library.libraryCycle.apiSignature);
signature.addUint32List(library.file.analysisOptions.signature);
signature.addString(file.uriStr);
signature.addString(file.contentHash);
return signature.toHex();
}
Future<void> _getUnitElement(String path) async {
FileState file = _fsState.getFileForPath(path);
// Prepare the library - the file itself, or the known library.
var kind = file.kind;
var library = kind.library ?? kind.asLibrary;
await libraryContext.load(
targetLibrary: library,
performance: OperationPerformanceImpl('<root>'),
);
var element = libraryContext.computeUnitElement(library, file);
var result = UnitElementResultImpl(
session: currentSession,
fileState: file,
element: element,
);
_unitElementRequestedFiles.completeAll(path, result);
}
bool _hasLibraryByUri(String uriStr) {
var uri = uriCache.parse(uriStr);
var fileOr = _fsState.getFileForUri(uri);
return switch (fileOr) {
null => false,
UriResolutionFile(:var file) => file.exists,
UriResolutionExternalLibrary() => true,
};
}
bool _isAbsolutePath(String path) {
return _resourceProvider.pathContext.isAbsolute(path);
}
bool _isLibraryWithPriorityFile(LibraryFileKind library) {
for (var file in library.files) {
if (_priorityFiles.contains(file.path)) {
return true;
}
}
return false;
}
Future<void> _maybeDispose() async {
if (_disposeRequests.isNotEmpty) {
_scheduler.remove(this);
clearLibraryContext();
for (var completer in _disposeRequests.toList()) {
completer.complete();
}
}
}
/// We detected that one of the required `dart` libraries is missing.
/// Return the empty analysis result with the error.
ErrorsResultImpl _newMissingDartLibraryResult(
FileState file, String missingUri) {
// TODO(scheglov): Find a better way to report this.
return ErrorsResultImpl(
session: currentSession,
file: file.resource,
content: file.content,
lineInfo: file.lineInfo,
uri: file.uri,
isAugmentation: file.kind is AugmentationFileKind,
isLibrary: file.kind is LibraryFileKind,
isMacroAugmentation: file.isMacroAugmentation,
isPart: file.kind is PartFileKind,
errors: [
AnalysisError.tmp(
source: file.source,
offset: 0,
length: 0,
errorCode: CompileTimeErrorCode.MISSING_DART_LIBRARY,
arguments: [missingUri],
),
],
analysisOptions: file.analysisOptions,
);
}
void _onNewFile(FileState file) {
final ownedFiles = this.ownedFiles;
if (ownedFiles != null) {
if (addedFiles.contains(file.path)) {
ownedFiles.addAdded(file.uri, this);
} else {
ownedFiles.addKnown(file.uri, this);
}
}
}
Future<void> _produceErrors(String path) async {
var file = _fsState.getFileForPath(path);
// Prepare the library - the file itself, or the known library.
var kind = file.kind;
var library = kind.library ?? kind.asLibrary;
// Errors are based on elements, so load them.
await libraryContext.load(
targetLibrary: library,
performance: OperationPerformanceImpl('<root>'),
);
// Check if we have cached errors for all library files.
List<(FileState, String, Uint8List)>? forAllFiles = [];
for (var file in library.files) {
// If the file is priority, we need the resolved unit.
// So, the cached errors is not enough.
if (priorityFiles.contains(file.path)) {
forAllFiles = null;
break;
}
var signature = _getResolvedUnitSignature(library, file);
var key = _getResolvedUnitKey(signature);
var bytes = _byteStore.get(key);
if (bytes == null) {
forAllFiles = null;
break;
}
// Will not be `null` here.
forAllFiles?.add((file, signature, bytes));
}
// If we have results for all library files, produce them.
if (forAllFiles != null) {
for (var (file, signature, bytes) in forAllFiles) {
// We have the result for this file.
_fileTracker.fileWasAnalyzed(file.path);
// Don't produce the result if the signature is the same.
if (_lastProducedSignatures[file.path] == signature) {
continue;
}
// Produce the result from bytes.
var result = _createErrorsResultFromBytes(file, library, bytes);
_lastProducedSignatures[file.path] = signature;
_errorsRequestedFiles.completeAll(file.path, result);
_scheduler.eventsController.add(result);
}
// We produced all results for the library.
return;
}
// Analyze, will produce results into the stream.
await _analyzeFile(path);
}
void _removePotentiallyAffectedLibraries(
Set<String> accumulatedAffected,
String path,
) {
var affected = <FileState>{};
_fsState.collectAffected(path, affected);
var removedKeys = <String>{};
_libraryContext?.remove(affected, removedKeys);
// TODO(scheglov): Eventually list of `LibraryOrAugmentationFileKind`.
for (var file in affected) {
var kind = file.kind;
if (kind is LibraryFileKind) {
kind.invalidateLibraryCycle();
}
accumulatedAffected.add(file.path);
}
_libraryContext?.elementFactory.replaceAnalysisSession(
AnalysisSessionImpl(this),
);
}
void _reportException(String path, Object exception, StackTrace stackTrace,
String? contextKey) {
CaughtException caught = CaughtException(exception, stackTrace);
var fileContentMap = <String, String>{};
try {
var file = _fsState.getFileForPath(path);
var fileKind = file.kind;
var libraryKind = fileKind.library;
if (libraryKind != null) {
for (var file in libraryKind.files) {
fileContentMap[file.path] = file.content;
}
} else {
var file = fileKind.file;
fileContentMap[file.path] = file.content;
}
} catch (_) {
// We might get an exception while parsing to access parts.
// Ignore, continue with the exception that we are reporting now.
}
if (exception is CaughtExceptionWithFiles) {
for (var nested in exception.fileContentMap.entries) {
fileContentMap['nested-${nested.key}'] = nested.value;
}
}
_exceptionController.add(
ExceptionResult(
filePath: path,
fileContentMap: fileContentMap,
exception: caught,
contextKey: contextKey,
),
);
}
Future<ResolvedForCompletionResultImpl?> _resolveForCompletion(
_ResolveForCompletionRequest request,
) async {
return request.performance.runAsync('body', (performance) async {
var path = request.path;
if (!_isAbsolutePath(path)) {
return null;
}
if (!_fsState.hasUri(path)) {
return null;
}
var file = _fsState.getFileForPath(path);
// Prepare the library - the file itself, or the known library.
var kind = file.kind;
var library = kind.library ?? kind.asLibrary;
await performance.runAsync(
'libraryContext',
(performance) async {
await libraryContext.load(
targetLibrary: library,
performance: performance,
);
},
);
var unitElement = libraryContext.computeUnitElement(library, file);
var analysisOptions = libraryContext.analysisContext
.getAnalysisOptionsForFile(file.resource);
var libraryElement =
libraryContext.elementFactory.libraryOfUri2(library.file.uri);
var typeSystemOperations = TypeSystemOperations(libraryElement.typeSystem,
strictCasts: analysisOptions.strictCasts);
var analysisResult = LibraryAnalyzer(
analysisOptions,
declaredVariables,
libraryElement,
libraryContext.elementFactory.analysisSession.inheritanceManager,
library,
testingData: testingData,
typeSystemOperations: typeSystemOperations,
).analyzeForCompletion(
file: file,
offset: request.offset,
unitElement: unitElement,
performance: performance,
);
return ResolvedForCompletionResultImpl(
analysisSession: currentSession,
fileState: file,
path: path,
uri: file.uri,
exists: file.exists,
content: file.content,
lineInfo: file.lineInfo,
parsedUnit: analysisResult.parsedUnit,
unitElement: unitElement,
resolvedNodes: analysisResult.resolvedNodes,
);
});
}
String? _storeExceptionContext(String path, LibraryFileKind library,
Object exception, StackTrace stackTrace) {
if (allowedNumberOfContextsToWrite <= 0) {
return null;
} else {
allowedNumberOfContextsToWrite--;
}
try {
var contextFiles = library.files
.map((file) => AnalysisDriverExceptionFileBuilder(
path: file.path, content: file.content))
.toList();
contextFiles.sort((a, b) => a.path.compareTo(b.path));
AnalysisDriverExceptionContextBuilder contextBuilder =
AnalysisDriverExceptionContextBuilder(
path: path,
exception: exception.toString(),
stackTrace: stackTrace.toString(),
files: contextFiles);
var bytes = contextBuilder.toBuffer();
String twoDigits(int n) {
if (n >= 10) return '$n';
return '0$n';
}
String threeDigits(int n) {
if (n >= 100) return '$n';
if (n >= 10) return '0$n';
return '00$n';
}
DateTime time = DateTime.now();
String m = twoDigits(time.month);
String d = twoDigits(time.day);
String h = twoDigits(time.hour);
String min = twoDigits(time.minute);
String sec = twoDigits(time.second);
String ms = threeDigits(time.millisecond);
String key = 'exception_${time.year}$m${d}_$h$min${sec}_$ms';
_byteStore.putGet(key, bytes);
return key;
} catch (_) {
return null;
}
}
/// The driver supports only absolute paths, this method is used to validate
/// any input paths to prevent errors later.
void _throwIfNotAbsolutePath(String path) {
if (!_isAbsolutePath(path)) {
throw ArgumentError('Only absolute paths are supported: $path');
}
}
/// Given the list of [errors] for the [file], update the [file]'s
/// [FileState.hasErrorOrWarning] flag.
void _updateHasErrorOrWarningFlag(
FileState file, List<AnalysisError> errors) {
for (AnalysisError error in errors) {
ErrorSeverity severity = error.errorCode.errorSeverity;
if (severity == ErrorSeverity.ERROR) {
file.hasErrorOrWarning = true;
return;
}
}
file.hasErrorOrWarning = false;
}
}
/// Priorities of [AnalysisDriver] work. The farther a priority to the beginning
/// of the list, the earlier the corresponding [AnalysisDriver] should be asked
/// to perform work.
enum AnalysisDriverPriority {
nothing,
general,
generalWithErrors,
generalImportChanged,
generalChanged,
changedFiles,
priority,
interactive,
completion
}
/// Instances of this class schedule work in multiple [AnalysisDriver]s so that
/// work with the highest priority is performed first.
class AnalysisDriverScheduler {
/// Time interval in milliseconds before pumping the event queue.
///
/// Relinquishing execution flow and running the event loop after every task
/// has too much overhead. Instead we use a fixed length of time, so we can
/// spend less time overall and still respond quickly enough.
static const int _MS_BEFORE_PUMPING_EVENT_QUEUE = 2;
/// Event queue pumping is required to allow IO and other asynchronous data
/// processing while analysis is active. For example Analysis Server needs to
/// be able to process `updateContent` or `setPriorityFiles` requests while
/// background analysis is in progress.
///
/// The number of pumpings is arbitrary, might be changed if we see that
/// analysis or other data processing tasks are starving. Ideally we would
/// need to run all asynchronous operations using a single global scheduler.
static const int _NUMBER_OF_EVENT_QUEUE_PUMPINGS = 128;
final PerformanceLog _logger;
/// The object used to watch as analysis drivers are created and deleted.
final DriverWatcher? driverWatcher;
/// The controller for [events] stream.
final StreamController<Object> eventsController = StreamController<Object>();
/// The cached instance of [events] stream.
late final Stream<Object> _events = eventsController.stream;
final List<AnalysisDriver> _drivers = [];
final Monitor _hasWork = Monitor();
late final StatusSupport _statusSupport = StatusSupport(
eventsController: eventsController,
);
bool _started = false;
/// The optional worker that is invoked when its work priority is higher
/// than work priorities in drivers.
///
/// Don't use outside of Analyzer and Analysis Server.
SchedulerWorker? outOfBandWorker;
/// The operations performance accumulated so far.
///
/// It is expected that the consumer of this performance operation will
/// do analysis operations, take the instance to print and otherwise
/// process, and reset this field with a new instance.
OperationPerformanceImpl accumulatedPerformance =
OperationPerformanceImpl('<scheduler>');
AnalysisDriverScheduler(this._logger, {this.driverWatcher});
/// The [Stream] that produces analysis results for all drivers, and status
/// events.
///
/// Note that the stream supports only one single subscriber.
///
/// Analysis starts when the [AnalysisDriverScheduler] is started and the
/// driver is added to it. The analysis state transitions to "working" and
/// an analysis result is produced for every added file prior to the next time
/// the analysis state transitions to "idle".
///
/// [AnalysisStatusWorking] is produced every time when the current status
/// is [AnalysisStatusIdle], and there is any work to do in any
/// [AnalysisDriver]. This includes analysis of files passed to
/// [AnalysisDriver.addFile], any asynchronous `getXyz()` requests, and
/// [AnalysisDriver.changeFile].
///
/// [AnalysisStatusIdle] is produced every time when there is no more work
/// to do after [AnalysisStatusWorking].
///
/// [ErrorsResult]s are produced for files passed to [AnalysisDriver.addFile]
/// which are not in [AnalysisDriver.priorityFiles]. We can avoid analyzing
/// a file, if there is already result for it in the [ByteStore].
///
/// [ResolvedUnitResult]s are produced for every analyzed file. Currently
/// to analyze a file of a library, the whole library is analyzed, all its
/// files - the defining unit, augmentations, and parts.
///
/// A file requires analysis if:
/// 1. It was requested by [AnalysisDriver.getResolvedUnit] or
/// [AnalysisDriver.getResolvedLibrary], and not cached.
/// 2. It was [AnalysisDriver.addFile], and either there is no result for it
/// in the [ByteStore], or it is in [AnalysisDriver.priorityFiles].
Stream<Object> get events => _events;
bool get isStarted => _started;
/// Returns `true` if we are currently working on requests.
bool get isWorking {
return _statusSupport.currentStatus.isWorking;
}
/// Return `true` if there is a driver with a file to analyze.
bool get _hasFilesToAnalyze {
for (var driver in _drivers) {
if (driver._hasFilesToAnalyze) {
return true;
}
}
return false;
}
/// Add the given [driver] and schedule it to perform its work.
void add(AnalysisDriver driver) {
_drivers.add(driver);
_hasWork.notify();
if (driver.analysisContext != null) {
driverWatcher?.addedDriver(driver);
}
}
/// Notifies the scheduler that there might be work to do.
void notify() {
_hasWork.notify();
_statusSupport.transitionToWorking();
}
/// Remove the given [driver] from the scheduler, so that it will not be
/// asked to perform any new work.
void remove(AnalysisDriver driver) {
driverWatcher?.removedDriver(driver);
_drivers.remove(driver);
_hasWork.notify();
}
/// Start the scheduler, so that any [AnalysisDriver] created before or
/// after will be asked to perform work.
void start() {
if (_started) {
throw StateError('The scheduler has already been started.');
}
_started = true;
_run();
}
/// Return a future that will be completed the next time the status is idle.
///
/// If the status is currently idle, the returned future will be signaled
/// immediately.
Future<void> waitForIdle() => _statusSupport.waitForIdle();
/// Run infinitely analysis cycle, selecting the drivers with the highest
/// priority first.
Future<void> _run() async {
// Give other microtasks the time to run before doing the analysis cycle.
await null;
Stopwatch timer = Stopwatch()..start();
PerformanceLogSection? analysisSection;
while (true) {
// Pump the event queue.
if (timer.elapsedMilliseconds > _MS_BEFORE_PUMPING_EVENT_QUEUE) {
await _pumpEventQueue(_NUMBER_OF_EVENT_QUEUE_PUMPINGS);
timer.reset();
}
await _hasWork.signal;
for (var driver in _drivers.toList()) {
await driver._maybeDispose();
}
for (var driver in _drivers) {
driver._applyPendingFileChanges();
}
// Transition to working if there are files to analyze.
if (_hasFilesToAnalyze) {
_statusSupport.transitionToWorking();
analysisSection ??= _logger.enter('Working');
}
// Find the driver with the highest priority.
late AnalysisDriver bestDriver;
AnalysisDriverPriority bestPriority = AnalysisDriverPriority.nothing;
for (var driver in _drivers) {
AnalysisDriverPriority priority = driver.workPriority;
if (priority.index > bestPriority.index) {
bestDriver = driver;
bestPriority = priority;
}
}
if (outOfBandWorker != null) {
var workerPriority = outOfBandWorker!.workPriority;
if (workerPriority != AnalysisDriverPriority.nothing) {
if (workerPriority.index > bestPriority.index) {
await outOfBandWorker!.performWork();
_hasWork.notify();
continue;
}
}
}
// Transition to idle if no files to analyze.
if (!_hasFilesToAnalyze) {
_statusSupport.transitionToIdle();
analysisSection?.exit();
analysisSection = null;
}
// Continue to sleep if no work to do.
if (bestPriority == AnalysisDriverPriority.nothing) {
continue;
}
// Ask the driver to perform a chunk of work.
await bestDriver.performWork();
// Schedule one more cycle.
_hasWork.notify();
}
}
/// Returns a [Future] that completes after performing [times] pumpings of
/// the event queue.
static Future _pumpEventQueue(int times) {
if (times == 0) {
return Future.value();
}
return Future.delayed(Duration.zero, () => _pumpEventQueue(times - 1));
}
}
class AnalysisDriverTestView {
final fileSystem = FileSystemTestData();
late final libraryContext = LibraryContextTestData(
fileSystemTestData: fileSystem,
);
late final AnalysisDriver driver;
int numOfAnalyzedLibraries = 0;
FileTracker get fileTracker => driver._fileTracker;
Set<String> get loadedLibraryUriSet {
var elementFactory = driver.libraryContext.elementFactory;
var libraryReferences = elementFactory.rootReference.children;
return libraryReferences.map((e) => e.name).toSet();
}
Map<String, ResolvedUnitResult> get priorityResults {
return driver._priorityResults;
}
}
/// An object that watches for the creation and removal of analysis drivers.
///
/// Clients may not extend, implement or mix-in this class.
abstract class DriverWatcher {
/// The context manager has just added the given analysis [driver]. This method
/// must be called before the driver has been allowed to perform any analysis.
void addedDriver(AnalysisDriver driver);
/// The context manager has just removed the given analysis [driver].
void removedDriver(AnalysisDriver driver);
}
class ErrorEncoding {
static AnalysisError? decode(
Source source,
AnalysisDriverUnitError error,
) {
String errorName = error.uniqueName;
ErrorCode? errorCode =
errorCodeByUniqueName(errorName) ?? _lintCodeByUniqueName(errorName);
if (errorCode == null) {
// This could fail because the error code is no longer defined, or, in
// the case of a lint rule, if the lint rule has been disabled since the
// errors were written.
AnalysisEngine.instance.instrumentationService
.logError('No error code for "$error" in "$source"');
return null;
}
var contextMessages = <DiagnosticMessageImpl>[];
for (var message in error.contextMessages) {
var url = message.url;
contextMessages.add(
DiagnosticMessageImpl(
filePath: message.filePath,
length: message.length,
message: message.message,
offset: message.offset,
url: url.isEmpty ? null : url,
),
);
}
return AnalysisError.forValues(
source: source,
offset: error.offset,
length: error.length,
errorCode: errorCode,
message: error.message,
correctionMessage: error.correction.isEmpty ? null : error.correction,
contextMessages: contextMessages,
);
}
static AnalysisDriverUnitErrorBuilder encode(AnalysisError error) {
var contextMessages = <DiagnosticMessageBuilder>[];
for (var message in error.contextMessages) {
contextMessages.add(
DiagnosticMessageBuilder(
filePath: message.filePath,
length: message.length,
message: message.messageText(includeUrl: false),
offset: message.offset,
url: message.url,
),
);
}
return AnalysisDriverUnitErrorBuilder(
offset: error.offset,
length: error.length,
uniqueName: error.errorCode.uniqueName,
message: error.message,
correction: error.correction ?? '',
contextMessages: contextMessages,
);
}
/// Return the lint code with the given [errorName], or `null` if there is no
/// lint registered with that name.
static ErrorCode? _lintCodeByUniqueName(String errorName) {
return linter.Registry.ruleRegistry.codeForUniqueName(errorName);
}
}
/// Exception that happened during analysis.
class ExceptionResult {
/// The path of the library being analyzed when the [exception] happened.
///
/// Absolute and normalized.
final String filePath;
/// The content of the library and its parts.
final Map<String, String> fileContentMap;
/// The exception during analysis of the file with the [filePath].
final CaughtException exception;
/// If the exception happened during a file analysis, and the context in which
/// the exception happened was stored, this field is the key of the context
/// in the byte store. May be `null` if the context is unknown, the maximum
/// number of context to store was reached, etc.
final String? contextKey;
ExceptionResult({
required this.filePath,
required this.fileContentMap,
required this.exception,
required this.contextKey,
});
}
/// Container that keeps track of file owners.
class OwnedFiles {
/// Key: the absolute file URI.
/// Value: the driver to which the file is added.
final Map<Uri, AnalysisDriver> addedFiles = {};
/// Key: the absolute file URI.
/// Value: a driver in which this file is available via dependencies.
/// This map does not contain any files that are in [addedFiles].
final Map<Uri, AnalysisDriver> knownFiles = {};
void addAdded(Uri uri, AnalysisDriver analysisDriver) {
addedFiles[uri] ??= analysisDriver;
knownFiles.remove(uri);
}
void addKnown(Uri uri, AnalysisDriver analysisDriver) {
if (!addedFiles.containsKey(uri)) {
knownFiles[uri] = analysisDriver;
}
}
}
/// Worker in [AnalysisDriverScheduler].
abstract class SchedulerWorker {
/// Return the priority of work that this worker needs to perform.
AnalysisDriverPriority get workPriority;
/// Perform a single chunk of work.
Future<void> performWork();
}
class _FileChange {
final String path;
final _FileChangeKind kind;
_FileChange(this.path, this.kind);
@override
String toString() {
return '[path: $path][kind: $kind]';
}
}
enum _FileChangeKind { add, change, remove }
class _GetFilesDefiningClassMemberNameRequest {
final String name;
final completer = Completer<List<FileState>>();
_GetFilesDefiningClassMemberNameRequest(this.name);
}
class _GetFilesReferencingNameRequest {
final String name;
final completer = Completer<List<FileState>>();
_GetFilesReferencingNameRequest(this.name);
}
class _ResolveForCompletionRequest {
final String path;
final int offset;
final OperationPerformanceImpl performance;
final Completer<ResolvedForCompletionResultImpl?> completer = Completer();
_ResolveForCompletionRequest({
required this.path,
required this.offset,
required this.performance,
});
}
extension<K, V> on Map<K, List<Completer<V>>> {
void completeAll(K key, V value) {
remove(key)?.completeAll(value);
}
}
extension FileExtension on File {
File? get libraryForMacro {
if (path.removeSuffix('.macro.dart') case var noExtPath?) {
var libraryPath = '$noExtPath.dart';
return provider.getFile(libraryPath);
}
return null;
}
File? get macroForLibrary {
if (path.removeSuffix('.dart') case var noExtPath?) {
var libraryPath = '$noExtPath.macro.dart';
return provider.getFile(libraryPath);
}
return null;
}
}