blob: 66b5ac019e51865974f3ff71bd19878e1cd80fc1 [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:typed_data';
import 'package:_fe_analyzer_shared/src/scanner/token_impl.dart'
show StringTokenImpl;
import 'package:analyzer/dart/analysis/declared_variables.dart';
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/dart/analysis/byte_store.dart';
import 'package:analyzer/src/dart/analysis/defined_names.dart';
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/library_graph.dart';
import 'package:analyzer/src/dart/analysis/performance_logger.dart';
import 'package:analyzer/src/dart/analysis/referenced_names.dart';
import 'package:analyzer/src/dart/analysis/unlinked_api_signature.dart';
import 'package:analyzer/src/dart/analysis/unlinked_data.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/scanner/reader.dart';
import 'package:analyzer/src/dart/scanner/scanner.dart';
import 'package:analyzer/src/exception/exception.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/parser.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/generated/utilities_dart.dart';
import 'package:analyzer/src/source/source_resource.dart';
import 'package:analyzer/src/summary/api_signature.dart';
import 'package:analyzer/src/summary/package_bundle_reader.dart';
import 'package:analyzer/src/summary2/informative_data.dart';
import 'package:analyzer/src/util/either.dart';
import 'package:analyzer/src/util/uri.dart';
import 'package:analyzer/src/workspace/workspace.dart';
import 'package:collection/collection.dart';
import 'package:convert/convert.dart';
import 'package:meta/meta.dart';
import 'package:path/path.dart' as package_path;
import 'package:pub_semver/pub_semver.dart';
/// The file has a `library augmentation` directive.
abstract class AugmentationFileStateKind extends LibraryOrAugmentationFileKind {
final UnlinkedLibraryAugmentationDirective directive;
AugmentationFileStateKind({
required super.file,
required this.directive,
});
}
/// The URI of the [directive] can be resolved.
class AugmentationKnownFileStateKind extends AugmentationFileStateKind {
/// The file that is referenced by the [directive].
final FileState uriFile;
AugmentationKnownFileStateKind({
required super.file,
required super.directive,
required this.uriFile,
});
/// If the [uriFile] has `import augment` of this file, returns [uriFile].
/// Otherwise, this file is not a valid augmentation, returns `null`.
FileState? get augmented {
final uriKind = uriFile.kind;
if (uriKind is LibraryOrAugmentationFileKind) {
if (uriKind.hasAugmentation(this)) {
return uriFile;
}
}
return null;
}
}
/// The URI of the [directive] can not be resolved.
class AugmentationUnknownFileStateKind extends AugmentationFileStateKind {
AugmentationUnknownFileStateKind({
required super.file,
required super.directive,
});
}
/// Information about a single `import` directive.
class ExportDirectiveState {
final UnlinkedNamespaceDirective directive;
ExportDirectiveState({
required this.directive,
});
/// If [exportedSource] corresponds to a library, returns it.
Source? get exportedLibrarySource => null;
/// Returns a [Source] that is referenced by this directive. If the are
/// configurations, selects the one which satisfies the conditions.
///
/// Returns `null` if the selected URI is not valid, or cannot be resolved
/// into a [Source].
Source? get exportedSource => null;
}
/// [ExportDirectiveState] that has a valid URI that references a file.
class ExportDirectiveWithFile extends ExportDirectiveState {
final FileState exportedFile;
ExportDirectiveWithFile({
required super.directive,
required this.exportedFile,
});
/// Returns [exportedFile] if it is a library.
LibraryFileStateKind? get exportedLibrary {
final kind = exportedFile.kind;
if (kind is LibraryFileStateKind) {
return kind;
}
return null;
}
@override
Source? get exportedLibrarySource {
if (exportedFile.kind is LibraryFileStateKind) {
return exportedSource;
}
return null;
}
@override
Source get exportedSource => exportedFile.source;
}
/// [ExportDirectiveState] with a URI that resolves to [InSummarySource].
class ExportDirectiveWithInSummarySource extends ExportDirectiveState {
@override
final InSummarySource exportedSource;
ExportDirectiveWithInSummarySource({
required super.directive,
required this.exportedSource,
});
@override
Source? get exportedLibrarySource {
if (exportedSource.kind == InSummarySourceKind.library) {
return exportedSource;
} else {
return null;
}
}
}
/// A library from [SummaryDataStore].
class ExternalLibrary {
final InSummarySource source;
ExternalLibrary._(this.source);
}
/// [FileContentOverlay] is used to temporary override content of files.
class FileContentOverlay {
final _map = <String, String>{};
/// Return the paths currently being overridden.
Iterable<String> get paths => _map.keys;
/// Return the content of the file with the given [path], or `null` the
/// overlay does not override the content of the file.
///
/// The [path] must be absolute and normalized.
String? operator [](String path) => _map[path];
/// Return the new [content] of the file with the given [path].
///
/// The [path] must be absolute and normalized.
void operator []=(String path, String? content) {
if (content == null) {
_map.remove(path);
} else {
_map[path] = content;
}
}
}
/// Information about a file being analyzed, explicitly or implicitly.
///
/// It provides a consistent view on its properties.
///
/// The properties are not guaranteed to represent the most recent state
/// of the file system. To update the file to the most recent state, [refresh]
/// should be called.
class FileState {
final FileSystemState _fsState;
/// The absolute path of the file.
final String path;
/// The absolute URI of the file.
final Uri uri;
/// Properties of the [uri].
final FileUriProperties uriProperties;
/// The [Source] of the file with the [uri].
final Source source;
/// The [WorkspacePackage] that contains this file.
///
/// It might be `null` if the file is outside of the workspace.
final WorkspacePackage? workspacePackage;
/// The [FeatureSet] for all files in the analysis context.
///
/// Usually it is the feature set of the latest language version, plus
/// possibly additional enabled experiments (from the analysis options file,
/// or from SDK allowed experiments).
///
/// This feature set is then restricted, with the [packageLanguageVersion],
/// or with a `@dart` language override token in the file header.
final FeatureSet _contextFeatureSet;
/// The language version for the package that contains this file.
final Version packageLanguageVersion;
bool? _exists;
String? _content;
String? _contentHash;
LineInfo? _lineInfo;
Uint8List? _unlinkedSignature;
String? _unlinkedKey;
AnalysisDriverUnlinkedUnit? _driverUnlinkedUnit;
Uint8List? _apiSignature;
UnlinkedUnit? _unlinked2;
FileStateKind? _kind;
/// Files that reference this file.
final List<FileState> referencingFiles = [];
List<FileState?> _augmentationFiles = [];
List<FileState?>? _importedFiles;
List<FileState?>? _exportedFiles;
List<FileState?> _partedFiles = [];
List<FileState> _libraryFiles = [];
Set<FileState>? _directReferencedFiles;
Set<FileState>? _directReferencedLibraries;
LibraryCycle? _libraryCycle;
/// The flag that shows whether the file has an error or warning that
/// might be fixed by a change to another file.
bool hasErrorOrWarning = false;
/// Set to `true` if this file contains code that might be executed by
/// a macro - declares a macro class itself, or is directly or indirectly
/// imported into a library that declares one.
bool mightBeExecutedByMacroClass = false;
FileState._(
this._fsState,
this.path,
this.uri,
this.source,
this.workspacePackage,
this._contextFeatureSet,
this.packageLanguageVersion,
) : uriProperties = FileUriProperties(uri);
/// The unlinked API signature of the file.
Uint8List get apiSignature => _apiSignature!;
/// The list of imported augmentations.
List<FileState?> get augmentationFiles {
return _augmentationFiles;
}
/// The content of the file.
String get content => _content!;
/// The MD5 hash of the [content].
String get contentHash => _contentHash!;
/// The class member names defined by the file.
Set<String> get definedClassMemberNames {
return _driverUnlinkedUnit!.definedClassMemberNames;
}
/// The top-level names defined by the file.
Set<String> get definedTopLevelNames {
return _driverUnlinkedUnit!.definedTopLevelNames;
}
/// Return the set of all directly referenced files - imported, exported or
/// parted.
Set<FileState> get directReferencedFiles {
return _directReferencedFiles ??= <FileState>{
...importedFiles.whereNotNull(),
...exportedFiles.whereNotNull(),
...partedFiles.whereNotNull(),
};
}
/// Return the set of all directly referenced libraries - imported or
/// exported.
Set<FileState> get directReferencedLibraries {
return _directReferencedLibraries ??= <FileState>{
...importedFiles.whereNotNull(),
...exportedFiles.whereNotNull(),
};
}
/// Return `true` if the file exists.
bool get exists => _exists!;
/// The list of files this file exports.
List<FileState?> get exportedFiles {
return _exportedFiles ??= _unlinked2!.exports.map((directive) {
var uri = _selectRelativeUri(directive);
return _fileForRelativeUri(uri).map(
(file) {
file?.referencingFiles.add(this);
return file;
},
(_) => null,
);
}).toList();
}
@override
int get hashCode => uri.hashCode;
/// The list of files this file imports.
List<FileState?> get importedFiles {
return _importedFiles ??= _unlinked2!.imports.map((directive) {
var uri = _selectRelativeUri(directive);
return _fileForRelativeUri(uri).map(
(file) {
file?.referencingFiles.add(this);
return file;
},
(_) => null,
);
}).toList();
}
LibraryCycle? get internal_libraryCycle => _libraryCycle;
/// Return `true` if the file does not have a `library` directive, and has a
/// `part of` directive, so is probably a part.
bool get isPart {
if (_unlinked2!.libraryDirective != null) {
return false;
}
return _unlinked2!.partOfNameDirective != null ||
_unlinked2!.partOfUriDirective != null;
}
FileStateKind get kind => _kind!;
/// If the file [isPart], return a currently know library the file is a part
/// of. Return `null` if a library is not known, for example because we have
/// not processed a library file yet.
FileState? get library {
final kind = _kind;
if (kind is PartFileStateKind) {
return kind.library;
} else {
return null;
}
}
/// Return the [LibraryCycle] this file belongs to, even if it consists of
/// just this file. If the library cycle is not known yet, compute it.
LibraryCycle get libraryCycle {
if (_libraryCycle == null) {
computeLibraryCycle(_fsState._saltForElements, this);
}
return _libraryCycle!;
}
/// The list of files files that this library consists of, i.e. this library
/// file itself and its [partedFiles].
List<FileState> get libraryFiles {
return _libraryFiles;
}
/// Return information about line in the file.
LineInfo get lineInfo => _lineInfo!;
/// The list of files this library file references as parts.
List<FileState?> get partedFiles {
return _partedFiles;
}
/// The external names referenced by the file.
Set<String> get referencedNames {
return _driverUnlinkedUnit!.referencedNames;
}
@visibleForTesting
FileStateTestView get test => FileStateTestView(this);
/// Return the set of transitive files - the file itself and all of the
/// directly or indirectly referenced files.
Set<FileState> get transitiveFiles {
var transitiveFiles = <FileState>{};
void appendReferenced(FileState file) {
if (transitiveFiles.add(file)) {
file.directReferencedFiles.forEach(appendReferenced);
}
}
appendReferenced(this);
return transitiveFiles;
}
/// Return the signature of the file, based on API signatures of the
/// transitive closure of imported / exported files.
/// TODO(scheglov) Remove it.
String get transitiveSignature {
var librarySignatureBuilder = ApiSignature()
..addString(uriStr)
..addString(libraryCycle.apiSignature);
return librarySignatureBuilder.toHex();
}
/// The [UnlinkedUnit] of the file.
UnlinkedUnit get unlinked2 => _unlinked2!;
/// The MD5 signature based on the content, feature sets, language version.
Uint8List get unlinkedSignature => _unlinkedSignature!;
/// Return the [uri] string.
String get uriStr => uri.toString();
@override
bool operator ==(Object other) {
return other is FileState && other.uri == uri;
}
void internal_setLibraryCycle(LibraryCycle? cycle) {
_libraryCycle = cycle;
}
void invalidateLibraryCycle() {
_libraryCycle?.invalidate();
_libraryCycle = null;
}
/// Return a new parsed unresolved [CompilationUnit].
CompilationUnitImpl parse([AnalysisErrorListener? errorListener]) {
errorListener ??= AnalysisErrorListener.NULL_LISTENER;
try {
return _parse(errorListener);
} catch (exception, stackTrace) {
throw CaughtExceptionWithFiles(
exception,
stackTrace,
{path: content},
);
}
}
/// Read the file content and ensure that all of the file properties are
/// consistent with the read content, including API signature.
///
/// Return how the file changed since the last refresh.
FileStateRefreshResult refresh() {
_invalidateCurrentUnresolvedData();
final rawFileState = _fsState._fileContentCache.get(path);
final contentChanged = _contentHash != rawFileState.contentHash;
_content = rawFileState.content;
_exists = rawFileState.exists;
_contentHash = rawFileState.contentHash;
// Prepare the unlinked bundle key.
{
var signature = ApiSignature();
signature.addUint32List(_fsState._saltForUnlinked);
signature.addFeatureSet(_contextFeatureSet);
signature.addLanguageVersion(packageLanguageVersion);
signature.addString(_contentHash!);
signature.addBool(_exists!);
_unlinkedSignature = signature.toByteList();
var signatureHex = hex.encode(_unlinkedSignature!);
// TODO(scheglov) Use the path as the key, and store the signature.
_unlinkedKey = '$signatureHex.unlinked2';
}
// Prepare the unlinked unit.
_driverUnlinkedUnit = _getUnlinkedUnit();
_unlinked2 = _driverUnlinkedUnit!.unit;
_lineInfo = LineInfo(_unlinked2!.lineStarts);
// Prepare API signature.
var newApiSignature = _unlinked2!.apiSignature;
bool apiSignatureChanged = _apiSignature != null &&
!_equalByteLists(_apiSignature, newApiSignature);
_apiSignature = newApiSignature;
// The API signature changed.
// Flush affected library cycles.
// Flush exported top-level declarations of all files.
if (apiSignatureChanged) {
_libraryCycle?.invalidate();
_invalidatesLibrariesOfThisPart();
}
// It is possible that this file does not reference these files.
_stopReferencingByThisFile();
// Read imports/exports on demand.
_importedFiles = null;
_exportedFiles = null;
_directReferencedFiles = null;
_directReferencedLibraries = null;
// Read parts eagerly to link parts to libraries.
_updateKind();
_updatePartedFiles();
_updateAugmentationFiles();
// Update mapping from subtyped names to files.
for (var name in _driverUnlinkedUnit!.subtypedNames) {
var files = _fsState._subtypedNameToFiles[name];
if (files == null) {
files = <FileState>{};
_fsState._subtypedNameToFiles[name] = files;
}
files.add(this);
}
// Return how the file changed.
if (apiSignatureChanged) {
return FileStateRefreshResult.apiChanged;
} else if (contentChanged) {
return FileStateRefreshResult.contentChanged;
} else {
return FileStateRefreshResult.nothing;
}
}
@override
String toString() {
return '$uri = $path';
}
/// Return the [FileState] for the given [relativeUri], or `null` if the
/// URI cannot be parsed, cannot correspond any file, etc.
Either2<FileState?, ExternalLibrary> _fileForRelativeUri(
String relativeUri,
) {
if (relativeUri.isEmpty) {
return Either2.t1(null);
}
Uri absoluteUri;
try {
absoluteUri = resolveRelativeUri(uri, Uri.parse(relativeUri));
} on FormatException {
return Either2.t1(null);
}
return _fsState.getFileForUri(absoluteUri);
}
/// Return the unlinked unit, from bytes or new.
AnalysisDriverUnlinkedUnit _getUnlinkedUnit() {
var bytes = _fsState._byteStore.get(_unlinkedKey!);
if (bytes != null && bytes.isNotEmpty) {
return AnalysisDriverUnlinkedUnit.fromBytes(bytes);
}
var unit = parse();
return _fsState._logger.run('Create unlinked for $path', () {
var unlinkedUnit = serializeAstUnlinked2(unit);
var definedNames = computeDefinedNames(unit);
var referencedNames = computeReferencedNames(unit);
var subtypedNames = computeSubtypedNames(unit);
var driverUnlinkedUnit = AnalysisDriverUnlinkedUnit(
definedTopLevelNames: definedNames.topLevelNames,
definedClassMemberNames: definedNames.classMemberNames,
referencedNames: referencedNames,
subtypedNames: subtypedNames,
unit: unlinkedUnit,
);
var bytes = driverUnlinkedUnit.toBytes();
_fsState._byteStore.put(_unlinkedKey!, bytes);
return driverUnlinkedUnit;
});
}
/// Invalidate any data that depends on the current unlinked data of the file,
/// because [refresh] is going to recompute the unlinked data.
void _invalidateCurrentUnresolvedData() {
if (_driverUnlinkedUnit != null) {
for (var name in _driverUnlinkedUnit!.subtypedNames) {
var files = _fsState._subtypedNameToFiles[name];
files?.remove(this);
}
}
}
/// If this is a part, invalidate the libraries that use it.
void _invalidatesLibrariesOfThisPart() {
if (_kind is PartFileStateKind) {
for (final library in _fsState._pathToFile.values) {
if (library._kind is LibraryFileStateKind) {
if (library.partedFiles.contains(this)) {
library._libraryCycle?.invalidate();
}
}
}
}
}
CompilationUnitImpl _parse(AnalysisErrorListener errorListener) {
CharSequenceReader reader = CharSequenceReader(content);
Scanner scanner = Scanner(source, reader, errorListener)
..configureFeatures(
featureSetForOverriding: _contextFeatureSet,
featureSet: _contextFeatureSet.restrictToVersion(
packageLanguageVersion,
),
);
Token token = scanner.tokenize(reportScannerErrors: false);
LineInfo lineInfo = LineInfo(scanner.lineStarts);
Parser parser = Parser(
source,
errorListener,
featureSet: scanner.featureSet,
lineInfo: lineInfo,
);
parser.enableOptionalNewAndConst = true;
var unit = parser.parseCompilationUnit(token);
unit.languageVersion = LibraryLanguageVersion(
package: packageLanguageVersion,
override: scanner.overrideVersion,
);
// StringToken uses a static instance of StringCanonicalizer, so we need
// to clear it explicitly once we are done using it for this file.
StringTokenImpl.canonicalizer.clear();
return unit;
}
/// TODO(scheglov) move to _fsState?
String _selectRelativeUri(UnlinkedNamespaceDirective directive) {
for (var configuration in directive.configurations) {
var name = configuration.name;
var value = configuration.value;
if (value.isEmpty) {
value = 'true';
}
if (_fsState._declaredVariables.get(name) == value) {
return configuration.uri;
}
}
return directive.uri;
}
void _stopReferencingByThisFile() {
void removeForOne(List<FileState?>? referencedFiles) {
if (referencedFiles != null) {
for (var referenced in referencedFiles) {
referenced?.referencingFiles.remove(this);
}
}
}
removeForOne(_augmentationFiles);
removeForOne(_exportedFiles);
removeForOne(_importedFiles);
removeForOne(_partedFiles);
}
void _updateAugmentationFiles() {
_augmentationFiles = unlinked2.augmentations.map((directive) {
return _fileForRelativeUri(directive.uri).map(
(augmentation) {
augmentation?.referencingFiles.add(this);
return augmentation;
},
(_) => null,
);
}).toList();
}
void _updateKind() {
_fsState._libraryNameToFiles.remove(_kind);
final libraryAugmentationDirective = unlinked2.libraryAugmentationDirective;
final libraryDirective = unlinked2.libraryDirective;
final partOfNameDirective = unlinked2.partOfNameDirective;
final partOfUriDirective = unlinked2.partOfUriDirective;
if (libraryAugmentationDirective != null) {
final uri = libraryAugmentationDirective.uri;
final uriFile = _fileForRelativeUri(uri).map(
(file) => file,
(_) => null,
);
if (uriFile != null) {
_kind = AugmentationKnownFileStateKind(
file: this,
directive: libraryAugmentationDirective,
uriFile: uriFile,
);
} else {
_kind = AugmentationUnknownFileStateKind(
file: this,
directive: libraryAugmentationDirective,
);
}
} else if (libraryDirective != null) {
_kind = LibraryFileStateKind(
file: this,
name: libraryDirective.name,
);
} else if (partOfNameDirective != null) {
_kind = PartOfNameFileStateKind(
file: this,
directive: partOfNameDirective,
);
} else if (partOfUriDirective != null) {
final uri = partOfUriDirective.uri;
final uriFile = _fileForRelativeUri(uri).map(
(file) => file,
(_) => null,
);
if (uriFile != null) {
_kind = PartOfUriKnownFileStateKind(
file: this,
directive: partOfUriDirective,
uriFile: uriFile,
);
} else {
_kind = PartOfUriUnknownFileStateKind(
file: this,
directive: partOfUriDirective,
);
}
} else {
_kind = LibraryFileStateKind(
file: this,
name: null,
);
}
_fsState._libraryNameToFiles.add(_kind);
_invalidatesLibrariesOfThisPart();
}
void _updatePartedFiles() {
_partedFiles = unlinked2.parts.map((uri) {
return _fileForRelativeUri(uri).map(
(part) {
part?.referencingFiles.add(this);
return part;
},
(_) => null,
);
}).toList();
_libraryFiles = [
this,
...partedFiles.whereNotNull(),
];
}
static UnlinkedUnit serializeAstUnlinked2(CompilationUnit unit) {
UnlinkedLibraryDirective? libraryDirective;
UnlinkedLibraryAugmentationDirective? libraryAugmentationDirective;
UnlinkedPartOfNameDirective? partOfNameDirective;
UnlinkedPartOfUriDirective? partOfUriDirective;
var augmentations = <UnlinkedImportAugmentationDirective>[];
var exports = <UnlinkedNamespaceDirective>[];
var imports = <UnlinkedNamespaceDirective>[];
var parts = <String>[];
var macroClasses = <MacroClass>[];
var hasDartCoreImport = false;
for (var directive in unit.directives) {
if (directive is ExportDirective) {
var builder = _serializeNamespaceDirective(directive);
exports.add(builder);
} else if (directive is ImportDirectiveImpl) {
if (directive.augmentKeyword != null) {
augmentations.add(
UnlinkedImportAugmentationDirective(
uri: directive.uri.stringValue ?? '',
),
);
} else {
var builder = _serializeNamespaceDirective(directive);
imports.add(builder);
if (builder.uri == 'dart:core') {
hasDartCoreImport = true;
}
}
} else if (directive is LibraryAugmentationDirective) {
final uri = directive.uri;
final uriStr = uri.stringValue;
if (uriStr != null) {
libraryAugmentationDirective = UnlinkedLibraryAugmentationDirective(
uri: uriStr,
uriRange: UnlinkedSourceRange(
offset: uri.offset,
length: uri.length,
),
);
}
} else if (directive is LibraryDirective) {
libraryDirective = UnlinkedLibraryDirective(
name: directive.name.name,
);
} else if (directive is PartDirective) {
var uriStr = directive.uri.stringValue;
parts.add(uriStr ?? '');
} else if (directive is PartOfDirective) {
final libraryName = directive.libraryName;
final uri = directive.uri;
if (libraryName != null) {
partOfNameDirective = UnlinkedPartOfNameDirective(
name: libraryName.name,
nameRange: UnlinkedSourceRange(
offset: libraryName.offset,
length: libraryName.length,
),
);
} else if (uri != null) {
final uriStr = uri.stringValue;
if (uriStr != null) {
partOfUriDirective = UnlinkedPartOfUriDirective(
uri: uriStr,
uriRange: UnlinkedSourceRange(
offset: uri.offset,
length: uri.length,
),
);
}
}
}
}
for (var declaration in unit.declarations) {
if (declaration is ClassDeclarationImpl) {
if (declaration.macroKeyword != null) {
var constructors = declaration.members
.whereType<ConstructorDeclaration>()
.map((e) => e.name?.name ?? '')
.where((e) => !e.startsWith('_'))
.toList();
if (constructors.isNotEmpty) {
macroClasses.add(
MacroClass(
name: declaration.name.name,
constructors: constructors,
),
);
}
}
}
}
if (!hasDartCoreImport) {
imports.add(
UnlinkedNamespaceDirective(
configurations: [],
isSyntheticDartCoreImport: true,
uri: 'dart:core',
),
);
}
return UnlinkedUnit(
apiSignature: Uint8List.fromList(computeUnlinkedApiSignature(unit)),
augmentations: augmentations,
exports: exports,
imports: imports,
informativeBytes: writeUnitInformative(unit),
libraryAugmentationDirective: libraryAugmentationDirective,
libraryDirective: libraryDirective,
lineStarts: Uint32List.fromList(unit.lineInfo.lineStarts),
macroClasses: macroClasses,
parts: parts,
partOfNameDirective: partOfNameDirective,
partOfUriDirective: partOfUriDirective,
);
}
/// Return `true` if the given byte lists are equal.
static bool _equalByteLists(List<int>? a, List<int>? b) {
if (a == null) {
return b == null;
} else if (b == null) {
return false;
}
if (a.length != b.length) {
return false;
}
for (int i = 0; i < a.length; i++) {
if (a[i] != b[i]) {
return false;
}
}
return true;
}
static UnlinkedNamespaceDirective _serializeNamespaceDirective(
NamespaceDirective directive) {
return UnlinkedNamespaceDirective(
configurations: directive.configurations.map((configuration) {
var name = configuration.name.components.join('.');
var value = configuration.value?.stringValue ?? '';
return UnlinkedNamespaceDirectiveConfiguration(
name: name,
value: value,
uri: configuration.uri.stringValue ?? '',
);
}).toList(),
uri: directive.uri.stringValue ?? '',
);
}
}
abstract class FileStateKind {
final FileState file;
FileStateKind({
required this.file,
});
}
enum FileStateRefreshResult {
/// No changes to the content, so no changes at all.
nothing,
/// The content changed, but the API of the file is the same.
contentChanged,
/// The content changed, and the API of the file is different.
apiChanged,
}
@visibleForTesting
class FileStateTestView {
final FileState file;
FileStateTestView(this.file);
String get unlinkedKey => file._unlinkedKey!;
}
/// Information about known file system state.
class FileSystemState {
final PerformanceLog _logger;
final ResourceProvider _resourceProvider;
final String contextName;
final ByteStore _byteStore;
final SourceFactory _sourceFactory;
final Workspace? _workspace;
final DeclaredVariables _declaredVariables;
final Uint32List _saltForUnlinked;
final Uint32List _saltForElements;
final FeatureSetProvider featureSetProvider;
/// Mapping from a URI to the corresponding [FileState].
final Map<Uri, FileState> _uriToFile = {};
/// All known file paths.
final Set<String> knownFilePaths = <String>{};
/// All known files.
final List<FileState> knownFiles = [];
/// Mapping from a path to the flag whether there is a URI for the path.
final Map<String, bool> _hasUriForPath = {};
/// Mapping from a path to the corresponding [FileState].
final Map<String, FileState> _pathToFile = {};
/// Mapping from a library name to the [LibraryFileStateKind] that have it.
final _LibraryNameToFiles _libraryNameToFiles = _LibraryNameToFiles();
/// The map of subtyped names to files where these names are subtyped.
final Map<String, Set<FileState>> _subtypedNameToFiles = {};
/// The value of this field is incremented when the set of files is updated.
int fileStamp = 0;
/// The cache of content of files, possibly shared with other file system
/// states.
final FileContentCache _fileContentCache;
late final FileSystemStateTestView _testView;
FileSystemState(
this._logger,
this._byteStore,
this._resourceProvider,
this.contextName,
this._sourceFactory,
this._workspace,
@Deprecated('No longer used; will be removed')
AnalysisOptions analysisOptions,
this._declaredVariables,
this._saltForUnlinked,
this._saltForElements,
this.featureSetProvider, {
required FileContentCache fileContentCache,
}) : _fileContentCache = fileContentCache {
_testView = FileSystemStateTestView(this);
}
package_path.Context get pathContext => _resourceProvider.pathContext;
@visibleForTesting
FileSystemStateTestView get test => _testView;
/// Collected files that transitively reference a file with the [path].
/// These files are potentially affected by the change.
void collectAffected(String path, Set<FileState> affected) {
// TODO(scheglov) This should not be necessary.
// We use affected files to remove library elements, and we can only get
// these library elements when we link or load them, using library cycles.
// And we get library cycles by asking `directReferencedFiles`.
for (var file in knownFiles.toList()) {
file.directReferencedFiles;
}
collectAffected(FileState file) {
if (affected.add(file)) {
for (var other in file.referencingFiles) {
collectAffected(other);
}
}
}
var file = _pathToFile[path];
if (file != null) {
collectAffected(file);
}
}
FeatureSet contextFeatureSet(
String path,
Uri uri,
WorkspacePackage? workspacePackage,
) {
var workspacePackageExperiments = workspacePackage?.enabledExperiments;
if (workspacePackageExperiments != null) {
return featureSetProvider.featureSetForExperiments(
workspacePackageExperiments,
);
}
return featureSetProvider.getFeatureSet(path, uri);
}
Version contextLanguageVersion(
String path,
Uri uri,
WorkspacePackage? workspacePackage,
) {
var workspaceLanguageVersion = workspacePackage?.languageVersion;
if (workspaceLanguageVersion != null) {
return workspaceLanguageVersion;
}
return featureSetProvider.getLanguageVersion(path, uri);
}
/// Return the [FileState] for the given absolute [path]. The returned file
/// has the last known state since if was last refreshed.
FileState getFileForPath(String path) {
var file = _pathToFile[path];
if (file == null) {
File resource = _resourceProvider.getFile(path);
Uri uri = _sourceFactory.pathToUri(path)!;
file = _newFile(resource, path, uri);
}
return file;
}
/// The given [uri] must be absolute.
///
/// If [uri] corresponds to a library from the summary store, return a
/// [ExternalLibrary].
///
/// Otherwise the [uri] is resolved to a file, and the corresponding
/// [FileState] is returned. Might be `null` if the [uri] cannot be resolved
/// to a file, for example because it is invalid (e.g. a `package:` URI
/// without a package name), or we don't know this package. The returned
/// file has the last known state since if was last refreshed.
Either2<FileState?, ExternalLibrary> getFileForUri(Uri uri) {
final uriSource = _sourceFactory.forUri2(uri);
// If the external store has this URI, create a stub file for it.
// We are given all required unlinked and linked summaries for it.
if (uriSource is InSummarySource) {
return Either2.t2(ExternalLibrary._(uriSource));
}
FileState? file = _uriToFile[uri];
if (file == null) {
// If the URI cannot be resolved, for example because the factory
// does not understand the scheme, return the unresolved file instance.
if (uriSource == null) {
return Either2.t1(null);
}
String path = uriSource.fullName;
// Check if already resolved to this path via different URI.
// That different URI must be the canonical one.
file = _pathToFile[path];
if (file != null) {
return Either2.t1(file);
}
File resource = _resourceProvider.getFile(path);
var rewrittenUri = rewriteToCanonicalUri(_sourceFactory, uri);
if (rewrittenUri == null) {
return Either2.t1(null);
}
file = _newFile(resource, path, rewrittenUri);
}
return Either2.t1(file);
}
/// Return files where the given [name] is subtyped, i.e. used in `extends`,
/// `with` or `implements` clauses.
Set<FileState>? getFilesSubtypingName(String name) {
return _subtypedNameToFiles[name];
}
/// Return `true` if there is a URI that can be resolved to the [path].
///
/// When a file exists, but for the URI that corresponds to the file is
/// resolved to another file, e.g. a generated one in Bazel, Gn, etc, we
/// cannot analyze the original file.
bool hasUri(String path) {
bool? flag = _hasUriForPath[path];
if (flag == null) {
Uri uri = _sourceFactory.pathToUri(path)!;
Source? uriSource = _sourceFactory.forUri2(uri);
flag = uriSource?.fullName == path;
_hasUriForPath[path] = flag;
}
return flag;
}
/// The file with the given [path] might have changed, so ensure that it is
/// read the next time it is refreshed.
void markFileForReading(String path) {
_fileContentCache.invalidate(path);
}
/// Remove the file with the given [path].
void removeFile(String path) {
markFileForReading(path);
_clearFiles();
}
/// Clear all [FileState] data - all maps from path or URI, etc.
void _clearFiles() {
_uriToFile.clear();
knownFilePaths.clear();
knownFiles.clear();
_hasUriForPath.clear();
_pathToFile.clear();
_subtypedNameToFiles.clear();
_libraryNameToFiles.clear();
}
FileState _newFile(File resource, String path, Uri uri) {
FileSource uriSource = FileSource(resource, uri);
WorkspacePackage? workspacePackage = _workspace?.findPackageFor(path);
FeatureSet featureSet = contextFeatureSet(path, uri, workspacePackage);
Version packageLanguageVersion =
contextLanguageVersion(path, uri, workspacePackage);
var file = FileState._(this, path, uri, uriSource, workspacePackage,
featureSet, packageLanguageVersion);
_pathToFile[path] = file;
_uriToFile[uri] = file;
knownFilePaths.add(path);
knownFiles.add(file);
fileStamp++;
file.refresh();
return file;
}
}
@visibleForTesting
class FileSystemStateTestView {
final FileSystemState state;
FileSystemStateTestView(this.state);
Set<FileState> get filesWithoutLibraryCycle {
return state._uriToFile.values
.where((f) => f._libraryCycle == null)
.toSet();
}
}
/// Precomputed properties of a file URI, used because [Uri] is relatively
/// expensive to work with, if we do this thousand times.
class FileUriProperties {
static const int _isDart = 1 << 0;
static const int _isDartInternal = 1 << 1;
static const int _isSrc = 1 << 2;
final int _flags;
final String? packageName;
factory FileUriProperties(Uri uri) {
if (uri.isScheme('dart')) {
var dartName = uri.pathSegments.firstOrNull;
return FileUriProperties._dart(
isInternal: dartName != null && dartName.startsWith('_'),
);
} else if (uri.isScheme('package')) {
var segments = uri.pathSegments;
if (segments.length >= 2) {
return FileUriProperties._package(
packageName: segments[0],
isSrc: segments[1] == 'src',
);
}
}
return const FileUriProperties._unknown();
}
const FileUriProperties._dart({
required bool isInternal,
}) : _flags = _isDart | (isInternal ? _isDartInternal : 0),
packageName = null;
FileUriProperties._package({
required this.packageName,
required bool isSrc,
}) : _flags = isSrc ? _isSrc : 0;
const FileUriProperties._unknown()
: _flags = 0,
packageName = null;
bool get isDart => (_flags & _isDart) != 0;
bool get isDartInternal => (_flags & _isDartInternal) != 0;
bool get isSrc => (_flags & _isSrc) != 0;
}
/// Information about a single `import` directive.
class ImportDirectiveState {
final UnlinkedNamespaceDirective directive;
ImportDirectiveState({
required this.directive,
});
/// If [importedSource] corresponds to a library, returns it.
Source? get importedLibrarySource => null;
/// Returns a [Source] that is referenced by this directive. If the are
/// configurations, selects the one which satisfies the conditions.
///
/// Returns `null` if the selected URI is not valid, or cannot be resolved
/// into a [Source].
Source? get importedSource => null;
bool get isSyntheticDartCoreImport => directive.isSyntheticDartCoreImport;
}
/// [ImportDirectiveState] that has a valid URI that references a file.
class ImportDirectiveWithFile extends ImportDirectiveState {
final FileState importedFile;
ImportDirectiveWithFile({
required super.directive,
required this.importedFile,
});
/// Returns [importedFile] if it is a library.
LibraryFileStateKind? get importedLibrary {
final kind = importedFile.kind;
if (kind is LibraryFileStateKind) {
return kind;
}
return null;
}
@override
Source? get importedLibrarySource {
if (importedFile.kind is LibraryFileStateKind) {
return importedSource;
}
return null;
}
@override
Source get importedSource => importedFile.source;
}
/// [ImportDirectiveState] with a URI that resolves to [InSummarySource].
class ImportDirectiveWithInSummarySource extends ImportDirectiveState {
@override
final InSummarySource importedSource;
ImportDirectiveWithInSummarySource({
required super.directive,
required this.importedSource,
});
@override
Source? get importedLibrarySource {
if (importedSource.kind == InSummarySourceKind.library) {
return importedSource;
} else {
return null;
}
}
}
class LibraryFileStateKind extends LibraryOrAugmentationFileKind {
/// The name of the library from the `library` directive.
/// Or `null` if no `library` directive.
final String? name;
LibraryFileStateKind({
required super.file,
required this.name,
});
bool hasPart(PartFileStateKind part) {
return file.partedFiles.contains(part.file);
}
}
abstract class LibraryOrAugmentationFileKind extends FileStateKind {
List<ExportDirectiveState>? _exports;
List<ImportDirectiveState>? _imports;
LibraryOrAugmentationFileKind({
required super.file,
});
List<ExportDirectiveState> get exports {
return _exports ??= file.unlinked2.exports.map((directive) {
final uriStr = file._selectRelativeUri(directive);
return file._fileForRelativeUri(uriStr).map(
(refFile) {
if (refFile != null) {
refFile.referencingFiles.add(file);
return ExportDirectiveWithFile(
directive: directive,
exportedFile: refFile,
);
} else {
return ExportDirectiveState(
directive: directive,
);
}
},
(externalLibrary) {
return ExportDirectiveWithInSummarySource(
directive: directive,
exportedSource: externalLibrary.source,
);
},
);
}).toList();
}
List<ImportDirectiveState> get imports {
return _imports ??= file.unlinked2.imports.map((directive) {
final uriStr = file._selectRelativeUri(directive);
return file._fileForRelativeUri(uriStr).map(
(refFile) {
if (refFile != null) {
refFile.referencingFiles.add(file);
return ImportDirectiveWithFile(
directive: directive,
importedFile: refFile,
);
} else {
return ImportDirectiveState(
directive: directive,
);
}
},
(externalLibrary) {
return ImportDirectiveWithInSummarySource(
directive: directive,
importedSource: externalLibrary.source,
);
},
);
}).toList();
}
bool hasAugmentation(AugmentationFileStateKind augmentation) {
return file.augmentationFiles.contains(augmentation.file);
}
}
/// The file has `part of` directive.
abstract class PartFileStateKind extends FileStateKind {
PartFileStateKind({
required super.file,
});
/// When [library] returns `null`, this getter is used to look at this
/// file itself as a library.
LibraryFileStateKind get asLibrary {
return LibraryFileStateKind(
file: file,
name: null,
);
}
/// Returns the library in which this part should be analyzed.
FileState? get library;
}
/// The file has `part of name` directive.
class PartOfNameFileStateKind extends PartFileStateKind {
final UnlinkedPartOfNameDirective directive;
PartOfNameFileStateKind({
required super.file,
required this.directive,
});
/// Libraries with the same name as in [directive].
List<LibraryFileStateKind> get libraries {
final files = file._fsState._libraryNameToFiles;
return files[directive.name] ?? [];
}
/// If there are libraries that include this file as a part, return the
/// first one as if sorted by path.
@override
FileState? get library {
LibraryFileStateKind? result;
for (final library in libraries) {
if (library.hasPart(this)) {
if (result == null) {
result = library;
} else if (library.file.path.compareTo(result.file.path) < 0) {
result = library;
}
}
}
return result?.file;
}
}
/// The file has `part of URI` directive.
abstract class PartOfUriFileStateKind extends PartFileStateKind {
final UnlinkedPartOfUriDirective directive;
PartOfUriFileStateKind({
required super.file,
required this.directive,
});
}
/// The file has `part of URI` directive, and the URI can be resolved.
class PartOfUriKnownFileStateKind extends PartOfUriFileStateKind {
final FileState uriFile;
PartOfUriKnownFileStateKind({
required super.file,
required super.directive,
required this.uriFile,
});
@override
FileState? get library {
final uriKind = uriFile.kind;
if (uriKind is LibraryFileStateKind) {
if (uriKind.hasPart(this)) {
return uriFile;
}
}
return null;
}
}
/// The file has `part of URI` directive, and the URI cannot be resolved.
class PartOfUriUnknownFileStateKind extends PartOfUriFileStateKind {
PartOfUriUnknownFileStateKind({
required super.file,
required super.directive,
});
@override
FileState? get library => null;
}
class _LibraryNameToFiles {
final Map<String, List<LibraryFileStateKind>> _map = {};
List<LibraryFileStateKind>? operator [](String name) {
return _map[name];
}
/// If [kind] is a named library, register it.
void add(FileStateKind? kind) {
if (kind is LibraryFileStateKind) {
final name = kind.name;
if (name != null) {
final libraries = _map[name] ??= [];
libraries.add(kind);
}
}
}
void clear() {
_map.clear();
}
/// If [kind] is a named library, unregister it.
void remove(FileStateKind? kind) {
if (kind is LibraryFileStateKind) {
final name = kind.name;
if (name != null) {
final libraries = _map[name];
if (libraries != null) {
libraries.remove(kind);
if (libraries.isEmpty) {
_map.remove(name);
}
}
}
}
}
}