Use FileState from AnalysisDriver in Cider.

Change-Id: I3f6dc6f80845d24e555328d25f8355eb11e51883
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/248709
Reviewed-by: Keerti Parthasarathy <keertip@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
Konstantin Shcheglov 2022-06-16 17:48:49 +00:00 committed by Commit Bot
parent a2f9ad4ba7
commit 31c49c4fd8
12 changed files with 555 additions and 149 deletions

View file

@ -12,6 +12,7 @@ import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/instrumentation/service.dart';
import 'package:analyzer/source/line_info.dart';
import 'package:analyzer/src/dart/analysis/file_state.dart';
import 'package:analyzer/src/dart/analysis/performance_logger.dart';
import 'package:analyzer/src/dart/micro/resolve_file.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_workspace.dart';
@ -88,7 +89,8 @@ class _CiderDartFixContextImpl extends DartFixContextImpl {
var result = <LibraryElement, Element>{};
var files = _fileResolver.getFilesWithTopLevelDeclarations(name);
for (var file in files) {
if (file.partOfLibrary == null) {
final kind = file.kind;
if (kind is LibraryFileStateKind) {
var libraryElement = await _fileResolver.getLibraryByUri2(
uriStr: file.uriStr,
);

View file

@ -86,7 +86,7 @@ import 'package:meta/meta.dart';
/// TODO(scheglov) Clean up the list of implicitly analyzed files.
class AnalysisDriver implements AnalysisDriverGeneric {
/// The version of data format, should be incremented on every format change.
static const int DATA_VERSION = 223;
static const int DATA_VERSION = 224;
/// The number of exception contexts allowed to write. Once this field is
/// zero, we stop writing any new exception contexts in this process.
@ -116,6 +116,8 @@ class AnalysisDriver implements AnalysisDriverGeneric {
/// the content from the file.
final FileContentCache _fileContentCache;
late final StoredFileContentStrategy _fileContentStrategy;
/// The analysis options to analyze with.
AnalysisOptionsImpl _analysisOptions;
@ -277,6 +279,9 @@ class AnalysisDriver implements AnalysisDriverGeneric {
analysisContext?.driver = this;
_onResults = _resultController.stream.asBroadcastStream();
_testView = AnalysisDriverTestView(this);
_fileContentStrategy = StoredFileContentStrategy(_fileContentCache);
_createFileTracker();
_scheduler.add(this);
_search = Search(this);
@ -1507,9 +1512,10 @@ class AnalysisDriver implements AnalysisDriverGeneric {
_saltForUnlinked,
_saltForElements,
featureSetProvider,
fileContentCache: _fileContentCache,
fileContentStrategy: _fileContentStrategy,
prefetchFiles: null,
);
_fileTracker = FileTracker(_logger, _fsState);
_fileTracker = FileTracker(_logger, _fsState, _fileContentStrategy);
}
/// If this has not been done yet, schedule discovery of all files that are

View file

@ -30,11 +30,14 @@ 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/ignore_comments/ignore_info.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/file_paths.dart' as file_paths;
import 'package:analyzer/src/util/performance/operation_performance.dart';
import 'package:analyzer/src/util/uri.dart';
import 'package:analyzer/src/workspace/workspace.dart';
import 'package:collection/collection.dart';
@ -186,6 +189,12 @@ class ExternalLibrary {
ExternalLibrary._(this.source);
}
abstract class FileContent {
String get content;
String get contentHash;
bool get exists;
}
/// [FileContentOverlay] is used to temporary override content of files.
class FileContentOverlay {
final _map = <String, String>{};
@ -211,6 +220,10 @@ class FileContentOverlay {
}
}
abstract class FileContentStrategy {
FileContent get(String path);
}
/// Information about a file being analyzed, explicitly or implicitly.
///
/// It provides a consistent view on its properties.
@ -251,9 +264,7 @@ class FileState {
/// The language version for the package that contains this file.
final Version packageLanguageVersion;
bool? _exists;
String? _content;
String? _contentHash;
FileContent? _fileContent;
LineInfo? _lineInfo;
Uint8List? _unlinkedSignature;
String? _unlinkedKey;
@ -306,10 +317,10 @@ class FileState {
}
/// The content of the file.
String get content => _content!;
String get content => _fileContent!.content;
/// The MD5 hash of the [content].
String get contentHash => _contentHash!;
String get contentHash => _fileContent!.contentHash;
/// The class member names defined by the file.
Set<String> get definedClassMemberNames {
@ -341,7 +352,7 @@ class FileState {
}
/// Return `true` if the file exists.
bool get exists => _exists!;
bool get exists => _fileContent!.exists;
/// The list of files this file exports.
List<FileState?> get exportedFiles {
@ -417,6 +428,10 @@ class FileState {
return _driverUnlinkedUnit!.referencedNames;
}
File get resource {
return _fsState._resourceProvider.getFile(path);
}
@visibleForTesting
FileStateTestView get test => FileStateTestView(this);
@ -448,6 +463,8 @@ class FileState {
/// The [UnlinkedUnit] of the file.
UnlinkedUnit get unlinked2 => _unlinked2!;
String get unlinkedKey => _unlinkedKey!;
/// The MD5 signature based on the content, feature sets, language version.
Uint8List get unlinkedSignature => _unlinkedSignature!;
@ -459,6 +476,25 @@ class FileState {
return other is FileState && other.uri == uri;
}
/// Collect all files that are transitively referenced by this file via
/// imports, exports, and parts.
void collectAllReferencedFiles(Set<String> referencedFiles) {
for (final file in directReferencedFiles) {
if (referencedFiles.add(file.path)) {
file.collectAllReferencedFiles(referencedFiles);
}
}
}
/// Return the content of the file, the empty string if cannot be read.
///
/// We read the file digest, end verify that it is the same as the digest
/// that was recorded during the file creation. If it is not, then the file
/// was changed, and we failed to call [FileSystemState.changeFile].
String getContent() {
return _fileContent!.content;
}
void internal_setLibraryCycle(LibraryCycle? cycle) {
_libraryCycle = cycle;
}
@ -482,6 +518,43 @@ class FileState {
}
}
/// TODO(scheglov) Remove it when [IgnoreInfo] is stored here.
CompilationUnitImpl parse2(
AnalysisErrorListener errorListener,
String content,
) {
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;
}
/// Read the file content and ensure that all of the file properties are
/// consistent with the read content, including API signature.
///
@ -489,11 +562,10 @@ class FileState {
FileStateRefreshResult refresh() {
_invalidateCurrentUnresolvedData();
final rawFileState = _fsState._fileContentCache.get(path);
final contentChanged = _contentHash != rawFileState.contentHash;
_content = rawFileState.content;
_exists = rawFileState.exists;
_contentHash = rawFileState.contentHash;
final rawFileState = _fsState.fileContentStrategy.get(path);
final contentChanged =
_fileContent?.contentHash != rawFileState.contentHash;
_fileContent = rawFileState;
// Prepare the unlinked bundle key.
{
@ -501,8 +573,8 @@ class FileState {
signature.addUint32List(_fsState._saltForUnlinked);
signature.addFeatureSet(_contextFeatureSet);
signature.addLanguageVersion(packageLanguageVersion);
signature.addString(_contentHash!);
signature.addBool(_exists!);
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.
@ -514,6 +586,8 @@ class FileState {
_unlinked2 = _driverUnlinkedUnit!.unit;
_lineInfo = LineInfo(_unlinked2!.lineStarts);
_prefetchDirectReferences();
// Prepare API signature.
var newApiSignature = _unlinked2!.apiSignature;
bool apiSignatureChanged = _apiSignature != null &&
@ -588,8 +662,11 @@ class FileState {
/// Return the unlinked unit, from bytes or new.
AnalysisDriverUnlinkedUnit _getUnlinkedUnit() {
final testData = _fsState.testData?.forFile(resource);
var bytes = _fsState._byteStore.get(_unlinkedKey!);
if (bytes != null && bytes.isNotEmpty) {
testData?.unlinkedKeyGet.add(unlinkedKey);
return AnalysisDriverUnlinkedUnit.fromBytes(bytes);
}
@ -608,6 +685,7 @@ class FileState {
);
var bytes = driverUnlinkedUnit.toBytes();
_fsState._byteStore.putGet(_unlinkedKey!, bytes);
testData?.unlinkedKeyPut.add(unlinkedKey);
return driverUnlinkedUnit;
});
}
@ -637,36 +715,43 @@ class FileState {
}
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);
return parse2(errorListener, content);
}
Parser parser = Parser(
source,
errorListener,
featureSet: scanner.featureSet,
lineInfo: lineInfo,
);
parser.enableOptionalNewAndConst = true;
/// TODO(scheglov) write tests
void _prefetchDirectReferences() {
final prefetchFiles = _fsState.prefetchFiles;
if (prefetchFiles == null) {
return;
}
var unit = parser.parseCompilationUnit(token);
unit.languageVersion = LibraryLanguageVersion(
package: packageLanguageVersion,
override: scanner.overrideVersion,
);
var paths = <String>{};
// 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();
void addRelativeUri(String relativeUriStr) {
final Uri absoluteUri;
try {
final relativeUri = Uri.parse(relativeUriStr);
absoluteUri = resolveRelativeUri(uri, relativeUri);
} on FormatException {
return;
}
final path = _fsState._sourceFactory.forUri2(absoluteUri)?.fullName;
if (path != null) {
paths.add(path);
}
}
return unit;
for (final directive in unlinked2.imports) {
addRelativeUri(directive.uri);
}
for (final directive in unlinked2.exports) {
addRelativeUri(directive.uri);
}
for (final uri in unlinked2.parts) {
addRelativeUri(uri);
}
prefetchFiles(paths.toList());
}
/// TODO(scheglov) move to _fsState?
@ -893,6 +978,29 @@ class FileState {
),
);
}
final topLevelDeclarations = <String>{};
for (final declaration in unit.declarations) {
if (declaration is ClassDeclaration) {
topLevelDeclarations.add(declaration.name.name);
} else if (declaration is EnumDeclaration) {
topLevelDeclarations.add(declaration.name.name);
} else if (declaration is ExtensionDeclaration) {
var name = declaration.name;
if (name != null) {
topLevelDeclarations.add(name.name);
}
} else if (declaration is FunctionDeclaration) {
topLevelDeclarations.add(declaration.name.name);
} else if (declaration is MixinDeclaration) {
topLevelDeclarations.add(declaration.name.name);
} else if (declaration is TopLevelVariableDeclaration) {
for (var variable in declaration.variables.variables) {
topLevelDeclarations.add(variable.name.name);
}
}
}
return UnlinkedUnit(
apiSignature: Uint8List.fromList(computeUnlinkedApiSignature(unit)),
augmentations: augmentations,
@ -906,6 +1014,7 @@ class FileState {
parts: parts,
partOfNameDirective: partOfNameDirective,
partOfUriDirective: partOfUriDirective,
topLevelDeclarations: topLevelDeclarations,
);
}
@ -1022,12 +1131,16 @@ class FileSystemState {
/// 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;
final FileContentStrategy fileContentStrategy;
/// A function that fetches the given list of files. This function can be used
/// to batch file reads in systems where file fetches are expensive.
final void Function(List<String> paths)? prefetchFiles;
late final FileSystemStateTestView _testView;
FileSystemTestData? testData;
FileSystemState(
this._logger,
this._byteStore,
@ -1041,8 +1154,9 @@ class FileSystemState {
this._saltForUnlinked,
this._saltForElements,
this.featureSetProvider, {
required FileContentCache fileContentCache,
}) : _fileContentCache = fileContentCache {
required this.fileContentStrategy,
required this.prefetchFiles,
}) {
_testView = FileSystemStateTestView(this);
}
@ -1051,6 +1165,29 @@ class FileSystemState {
@visibleForTesting
FileSystemStateTestView get test => _testView;
/// Update the state to reflect the fact that the file with the given [path]
/// was changed. Specifically this means that we evict this file and every
/// file that referenced it.
void changeFile(String path, List<FileState> removedFiles) {
var file = _pathToFile.remove(path);
if (file == null) {
return;
}
removedFiles.add(file);
_uriToFile.remove(file.uri);
// The removed file does not reference other file anymore.
for (var referencedFile in file.directReferencedFiles) {
referencedFile.referencingFiles.remove(file);
}
// Recursively remove files that reference the removed file.
for (var reference in file.referencingFiles.toList()) {
changeFile(reference.path, removedFiles);
}
}
/// 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) {
@ -1104,9 +1241,40 @@ class FileSystemState {
return featureSetProvider.getLanguageVersion(path, uri);
}
/// Notifies this object that it is about to be discarded.
///
/// Returns the keys of the artifacts that are no longer used.
Set<String> dispose() {
final result = <String>{};
for (final file in _pathToFile.values) {
result.add(file._unlinkedKey!);
}
_pathToFile.clear();
_uriToFile.clear();
return result;
}
@visibleForTesting
FileState? getExistingFileForResource(File file) {
return _pathToFile[file.path];
}
/// Return the [FileState] for the given absolute [path]. The returned file
/// has the last known state since if was last refreshed.
/// TODO(scheglov) Merge with [getFileForPath2].
FileState getFileForPath(String path) {
return getFileForPath2(
path: path,
performance: OperationPerformanceImpl('<root>'),
);
}
/// Return the [FileState] for the given absolute [path]. The returned file
/// has the last known state since if was last refreshed.
FileState getFileForPath2({
required String path,
required OperationPerformanceImpl performance,
}) {
var file = _pathToFile[path];
if (file == null) {
File resource = _resourceProvider.getFile(path);
@ -1164,12 +1332,40 @@ class FileSystemState {
return Either2.t1(file);
}
/// Returns a list of files whose contents contains the given string.
/// Generated files are not included in the search.
List<String> getFilesContaining(String value) {
var result = <String>[];
_pathToFile.forEach((path, file) {
// TODO(scheglov) Exclude generated files.
// var genFile = isGenerated == null ? false : isGenerated!(path);
// if (!genFile && file.getContent().contains(value)) {
// result.add(path);
// }
if (file.getContent().contains(value)) {
result.add(path);
}
});
return result;
}
/// 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 files that have a top-level declaration with the [name].
List<FileState> getFilesWithTopLevelDeclarations(String name) {
final result = <FileState>[];
for (final file in _pathToFile.values) {
if (file.unlinked2.topLevelDeclarations.contains(name)) {
result.add(file);
}
}
return result;
}
/// 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
@ -1186,18 +1382,34 @@ class FileSystemState {
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();
}
/// Computes the set of [FileState]'s used/not used to analyze the given
/// [files]. Removes the [FileState]'s of the files not used for analysis from
/// the cache. Returns the set of unused [FileState]'s.
List<FileState> removeUnusedFiles(List<String> files) {
var allReferenced = <String>{};
for (var path in files) {
allReferenced.add(path);
_pathToFile[path]?.collectAllReferencedFiles(allReferenced);
}
var unusedPaths = _pathToFile.keys.toSet();
unusedPaths.removeAll(allReferenced);
var removedFiles = <FileState>[];
for (var path in unusedPaths) {
var file = _pathToFile.remove(path)!;
_uriToFile.remove(file.uri);
removedFiles.add(file);
}
return removedFiles;
}
/// Clear all [FileState] data - all maps from path or URI, etc.
void _clearFiles() {
_uriToFile.clear();
@ -1240,6 +1452,26 @@ class FileSystemStateTestView {
}
}
class FileSystemTestData {
final Map<File, FileTestData> files = {};
FileTestData forFile(File file) {
return files[file] ??= FileTestData._(file);
}
}
class FileTestData {
final File file;
/// We add the key every time we get unlinked data from the byte store.
final List<String> unlinkedKeyGet = [];
/// We add the key every time we put unlinked data into the byte store.
final List<String> unlinkedKeyPut = [];
FileTestData._(this.file);
}
/// Precomputed properties of a file URI, used because [Uri] is relatively
/// expensive to work with, if we do this thousand times.
class FileUriProperties {
@ -1471,6 +1703,8 @@ class PartOfNameFileStateKind extends PartFileStateKind {
/// first one as if sorted by path.
@override
LibraryFileStateKind? get library {
_discoverLibraries();
LibraryFileStateKind? result;
for (final library in libraries) {
if (library.hasPart(this)) {
@ -1483,6 +1717,27 @@ class PartOfNameFileStateKind extends PartFileStateKind {
}
return result;
}
void _discoverLibraries() {
if (libraries.isEmpty) {
var resourceProvider = file._fsState._resourceProvider;
var pathContext = resourceProvider.pathContext;
var siblings = <Resource>[];
try {
siblings = file.resource.parent.getChildren();
} catch (_) {}
for (final sibling in siblings) {
if (file_paths.isDart(pathContext, sibling.path)) {
file._fsState.getFileForPath2(
path: sibling.path,
performance: OperationPerformanceImpl('<root>'),
);
}
}
}
}
}
/// The file has `part of URI` directive.
@ -1528,6 +1783,45 @@ class PartOfUriUnknownFileStateKind extends PartOfUriFileStateKind {
LibraryFileStateKind? get library => null;
}
class StoredFileContent implements FileContent {
@override
final String content;
@override
final String contentHash;
@override
final bool exists;
StoredFileContent({
required this.content,
required this.contentHash,
required this.exists,
});
}
class StoredFileContentStrategy implements FileContentStrategy {
final FileContentCache _fileContentCache;
StoredFileContentStrategy(this._fileContentCache);
@override
FileContent get(String path) {
final fileContent = _fileContentCache.get(path);
return StoredFileContent(
content: fileContent.content,
contentHash: fileContent.contentHash,
exists: fileContent.exists,
);
}
/// 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);
}
}
class _LibraryNameToFiles {
final Map<String, List<LibraryFileStateKind>> _map = {};

View file

@ -22,6 +22,8 @@ class FileTracker {
/// The current file system state.
final FileSystemState _fsState;
final StoredFileContentStrategy _fileContentStrategy;
/// The set of added files.
final addedFiles = <String>{};
@ -45,7 +47,7 @@ class FileTracker {
/// have any special relation with changed files.
var _pendingFiles = <String>{};
FileTracker(this._logger, this._fsState);
FileTracker(this._logger, this._fsState, this._fileContentStrategy);
/// Returns the path to exactly one that needs analysis. Throws a
/// [StateError] if no files need analysis.
@ -108,7 +110,7 @@ class FileTracker {
/// Adds the given [path] to the set of "changed files".
void changeFile(String path) {
_fsState.markFileForReading(path);
_fileContentStrategy.markFileForReading(path);
_changedFiles.add(path);
if (addedFiles.contains(path)) {
@ -137,6 +139,7 @@ class FileTracker {
/// Removes the given [path] from the set of "added files".
void removeFile(String path) {
_fileContentStrategy.markFileForReading(path);
addedFiles.remove(path);
_pendingChangedFiles.remove(path);
_pendingImportFiles.remove(path);

View file

@ -52,6 +52,10 @@ class LibraryCycle {
/// include [implSignature] of the macro defining library.
String implSignature;
/// The key of the resolution cache entry.
/// TODO(scheglov) clean up
String? resolutionKey;
late final bool hasMacroClass = () {
for (final library in libraries) {
for (final file in library.libraryFiles) {

View file

@ -343,6 +343,9 @@ class UnlinkedUnit {
/// The `part of 'uri';` directive.
final UnlinkedPartOfUriDirective? partOfUriDirective;
/// Top-level declarations of the unit.
final Set<String> topLevelDeclarations;
UnlinkedUnit({
required this.apiSignature,
required this.augmentations,
@ -356,6 +359,7 @@ class UnlinkedUnit {
required this.parts,
required this.partOfNameDirective,
required this.partOfUriDirective,
required this.topLevelDeclarations,
});
factory UnlinkedUnit.read(SummaryDataReader reader) {
@ -388,6 +392,7 @@ class UnlinkedUnit {
partOfUriDirective: reader.readOptionalObject(
UnlinkedPartOfUriDirective.read,
),
topLevelDeclarations: reader.readStringUtf8Set(),
);
}
@ -424,5 +429,6 @@ class UnlinkedUnit {
partOfUriDirective,
(x) => x.write(sink),
);
sink.writeStringUtf8Iterable(topLevelDeclarations);
}
}

View file

@ -9,6 +9,7 @@ import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/context/source.dart';
import 'package:analyzer/src/dart/analysis/file_state.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/utilities.dart';
import 'package:analyzer/src/dart/constant/compute.dart';
@ -19,7 +20,6 @@ import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
import 'package:analyzer/src/dart/element/type_provider.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
import 'package:analyzer/src/dart/micro/library_graph.dart';
import 'package:analyzer/src/dart/resolver/flow_analysis_visitor.dart';
import 'package:analyzer/src/dart/resolver/legacy_type_asserter.dart';
import 'package:analyzer/src/dart/resolver/resolution_visitor.dart';
@ -106,7 +106,8 @@ class LibraryAnalyzer {
// Parse all files.
performance.run('parse', (performance) {
for (FileState file in _library.files().ofLibrary) {
final libraryKind = _library.kind.asLibrary;
for (FileState file in libraryKind.file.libraryFiles) {
if (completionPath == null || file.path == completionPath) {
units[file] = _parse(
file: file,
@ -227,21 +228,24 @@ class LibraryAnalyzer {
});
}
final libraryKind = _library.kind.asLibrary;
final libraryFiles = libraryKind.file.libraryFiles.toList();
if (_analysisOptions.lint) {
performance.run('computeLints', (performance) {
var allUnits = _library.files().ofLibrary.map((file) {
var allUnits = libraryFiles.map((file) {
var content = getFileContent(file);
return LinterContextUnit(content, units[file]!);
}).toList();
for (int i = 0; i < allUnits.length; i++) {
_computeLints(_library.files().ofLibrary[i], allUnits[i], allUnits);
_computeLints(libraryFiles[i], allUnits[i], allUnits);
}
});
}
// This must happen after all other diagnostics have been computed but
// before the list of diagnostics has been filtered.
for (var file in _library.files().ofLibrary) {
for (var file in libraryFiles) {
IgnoreValidator(
_getErrorReporter(file),
_getErrorListener(file).errors,
@ -475,7 +479,8 @@ class LibraryAnalyzer {
}
bool _isExistingSource(Source source) {
for (var file in _library.files().directReferencedFiles) {
final libraryKind = _library.kind.asLibrary;
for (var file in libraryKind.file.directReferencedFiles) {
if (file.uri == source.uri) {
return file.exists;
}
@ -499,7 +504,7 @@ class LibraryAnalyzer {
performance.getDataInt('length').add(content.length);
AnalysisErrorListener errorListener = _getErrorListener(file);
var unit = file.parse(errorListener, content);
var unit = file.parse2(errorListener, content);
LineInfo lineInfo = unit.lineInfo;
_fileToLineInfo[file] = lineInfo;
@ -544,6 +549,8 @@ class LibraryAnalyzer {
return;
}
final libraryKind = _library.kind.asLibrary;
var definingCompilationUnit = units[_library]!;
definingCompilationUnit.element = _libraryElement.definingCompilationUnit;
@ -598,7 +605,7 @@ class LibraryAnalyzer {
} else if (directive is PartDirectiveImpl) {
StringLiteral partUri = directive.uri;
FileState partFile = _library.files().parted[partIndex];
FileState partFile = libraryKind.file.partedFiles[partIndex]!;
var partUnit = units[partFile]!;
CompilationUnitElement partElement = _libraryElement.parts[partIndex];
partUnit.element = partElement;

View file

@ -1045,6 +1045,7 @@ class _FileStateUnlinked {
parts: parts,
partOfNameDirective: partOfNameDirective,
partOfUriDirective: partOfUriDirective,
topLevelDeclarations: topLevelDeclarations,
);
return CiderUnlinkedUnit(

View file

@ -5,6 +5,7 @@
import 'dart:collection';
import 'dart:typed_data';
import 'package:analyzer/dart/analysis/declared_variables.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/error/error.dart';
@ -19,14 +20,14 @@ import 'package:analyzer/src/dart/analysis/context_root.dart';
import 'package:analyzer/src/dart/analysis/driver.dart' show ErrorEncoding;
import 'package:analyzer/src/dart/analysis/experiments.dart';
import 'package:analyzer/src/dart/analysis/feature_set_provider.dart';
import 'package:analyzer/src/dart/analysis/file_state.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/results.dart';
import 'package:analyzer/src/dart/analysis/search.dart';
import 'package:analyzer/src/dart/micro/analysis_context.dart';
import 'package:analyzer/src/dart/micro/library_analyzer.dart';
import 'package:analyzer/src/dart/micro/library_graph.dart';
import 'package:analyzer/src/dart/micro/utils.dart';
import 'package:analyzer/src/exception/exception.dart';
import 'package:analyzer/src/generated/engine.dart' show AnalysisOptionsImpl;
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/summary/api_signature.dart';
@ -43,6 +44,75 @@ import 'package:collection/collection.dart';
import 'package:meta/meta.dart';
import 'package:yaml/yaml.dart';
class CiderFileContent implements FileContent {
final CiderFileContentStrategy strategy;
final String path;
final String digestStr;
CiderFileContent({
required this.strategy,
required this.path,
required this.digestStr,
});
@override
String get content {
final contentWithDigest = _getContent();
if (contentWithDigest.digestStr != digestStr) {
throw StateError('File was changed, but not invalidated: $path');
}
return contentWithDigest.content;
}
@override
String get contentHash => digestStr;
@override
bool get exists => digestStr.isNotEmpty;
_ContentWithDigest _getContent() {
String content;
try {
final file = strategy.resourceProvider.getFile(path);
content = file.readAsStringSync();
} catch (_) {
content = '';
}
final digestStr = strategy.getFileDigest(path);
return _ContentWithDigest(
content: content,
digestStr: digestStr,
);
}
}
class CiderFileContentStrategy implements FileContentStrategy {
final ResourceProvider resourceProvider;
/// A function that returns the digest for a file as a String. The function
/// returns a non null value, returns an empty string if file does
/// not exist/has no contents.
final String Function(String path) getFileDigest;
CiderFileContentStrategy({
required this.resourceProvider,
required this.getFileDigest,
});
@override
CiderFileContent get(String path) {
final digestStr = getFileDigest(path);
return CiderFileContent(
strategy: this,
path: path,
digestStr: digestStr,
);
}
}
class CiderSearchInfo {
final CharacterLocation startPosition;
final int length;
@ -276,8 +346,8 @@ class FileResolver {
var file = fileContext.file;
final errorsSignatureBuilder = ApiSignature();
errorsSignatureBuilder.addBytes(file.libraryCycle.signature);
errorsSignatureBuilder.addBytes(file.digest);
errorsSignatureBuilder.addString(file.libraryCycle.apiSignature);
errorsSignatureBuilder.addString(file.contentHash);
final errorsKey = '${errorsSignatureBuilder.toHex()}.errors';
final List<AnalysisError> errors;
@ -330,7 +400,7 @@ class FileResolver {
});
var file = performance.run('fileForPath', (performance) {
return fsState!.getFileForPath(
return fsState!.getFileForPath2(
path: path,
performance: performance,
);
@ -368,7 +438,8 @@ class FileResolver {
);
var file = fileContext.file;
if (file.partOfLibrary != null) {
final kind = file.kind;
if (kind is! LibraryFileStateKind) {
throw ArgumentError('$uri is not a library.');
}
@ -388,12 +459,12 @@ class FileResolver {
}) {
_throwIfNotAbsoluteNormalizedPath(path);
var file = fsState!.getFileForPath(
var file = fsState!.getFileForPath2(
path: path,
performance: performance,
);
return file.libraryCycle.signatureStr;
return file.libraryCycle.apiSignature;
}
/// Ensure that libraries necessary for resolving [path] are linked.
@ -427,7 +498,7 @@ class FileResolver {
performance: performance,
);
var file = fileContext.file;
var libraryFile = file.partOfLibrary ?? file;
var libraryFile = file.kind.library!.file;
// Load the library, link if necessary.
await libraryContext!.load(
@ -486,15 +557,17 @@ class FileResolver {
);
var file = fileContext.file;
// If we have a `part of` directive, we want to analyze this library.
// But the library must include the file, so have its element.
var libraryFile = file;
var partOfLibrary = file.partOfLibrary;
if (partOfLibrary != null) {
if (partOfLibrary.files().ofLibrary.contains(file)) {
libraryFile = partOfLibrary;
}
}
// // If we have a `part of` directive, we want to analyze this library.
// // But the library must include the file, so have its element.
// var libraryFile = file;
// var partOfLibrary = file.partOfLibrary;
// if (partOfLibrary != null) {
// if (partOfLibrary.files().ofLibrary.contains(file)) {
// libraryFile = partOfLibrary;
// }
// }
final libraryKind = file.kind.library ?? file.kind.asLibrary;
final libraryFile = libraryKind.file;
var libraryResult = await resolveLibrary2(
completionLine: completionLine,
@ -533,15 +606,17 @@ class FileResolver {
);
var file = fileContext.file;
// If we have a `part of` directive, we want to analyze this library.
// But the library must include the file, so have its element.
var libraryFile = file;
var partOfLibrary = file.partOfLibrary;
if (partOfLibrary != null) {
if (partOfLibrary.files().ofLibrary.contains(file)) {
libraryFile = partOfLibrary;
}
}
// // If we have a `part of` directive, we want to analyze this library.
// // But the library must include the file, so have its element.
// var libraryFile = file;
// var partOfLibrary = file.partOfLibrary;
// if (partOfLibrary != null) {
// if (partOfLibrary.files().ofLibrary.contains(file)) {
// libraryFile = partOfLibrary;
// }
// }
final libraryKind = file.kind.library ?? file.kind.asLibrary;
final libraryFile = libraryKind.file;
int? completionOffset;
if (completionLine != null && completionColumn != null) {
@ -573,26 +648,13 @@ class FileResolver {
(file) => file.getContent(),
);
try {
results = performance!.run('analyze', (performance) {
return libraryAnalyzer.analyze(
completionPath: completionOffset != null ? completionPath : null,
completionOffset: completionOffset,
performance: performance,
);
});
} catch (exception, stackTrace) {
var fileContentMap = <String, String>{};
for (var file in libraryFile.files().ofLibrary) {
var path = file.path;
fileContentMap[path] = _getFileContent(path);
}
throw CaughtExceptionWithFiles(
exception,
stackTrace,
fileContentMap,
results = performance!.run('analyze', (performance) {
return libraryAnalyzer.analyze(
completionPath: completionOffset != null ? completionPath : null,
completionOffset: completionOffset,
performance: performance,
);
}
});
});
var resolvedUnits = results.values.map((fileResult) {
@ -655,15 +717,24 @@ class FileResolver {
);
fsState = FileSystemState(
resourceProvider,
logger,
byteStore,
resourceProvider,
'contextName',
sourceFactory,
workspace,
Uint32List(0), // linkedSalt
AnalysisOptionsImpl(), // TODO(scheglov) remove it
DeclaredVariables.fromMap({}),
Uint32List(0), // _saltForUnlinked
Uint32List(0), // _saltForElements
featureSetProvider,
getFileDigest,
prefetchFiles,
isGenerated,
fileContentStrategy: CiderFileContentStrategy(
resourceProvider: resourceProvider,
getFileDigest: getFileDigest,
),
prefetchFiles: prefetchFiles,
// TODO(scheglov) use these
// isGenerated,
)..testData = testView?.fileSystemTestData;
}
@ -779,15 +850,6 @@ class FileResolver {
return options;
}
/// Return the file content, the empty string if any exception.
String _getFileContent(String path) {
try {
return resourceProvider.getFile(path).readAsStringSync();
} catch (_) {
return '';
}
}
Future<List<CiderSearchMatch>> _searchReferences_Import(
ImportElement element) async {
var results = <CiderSearchMatch>[];
@ -897,14 +959,13 @@ class LibraryContext {
await loadBundle(directDependency);
}
var resolutionKey = '${cycle.signatureStr}.resolution';
var resolutionKey = '${cycle.apiSignature}.linked_bundle';
var resolutionBytes = byteStore.get(resolutionKey);
var unitsInformativeBytes = <Uri, Uint8List>{};
for (var library in cycle.libraries) {
for (var file in library.files().ofLibrary) {
var informativeBytes = file.unlinkedUnit.informativeBytes;
unitsInformativeBytes[file.uri] = informativeBytes;
for (var file in library.libraryFiles) {
unitsInformativeBytes[file.uri] = file.unlinked2.informativeBytes;
}
}
@ -918,21 +979,21 @@ class LibraryContext {
var inputUnits = <LinkInputUnit>[];
var partIndex = -1;
for (var file in libraryFile.files().ofLibrary) {
for (var file in libraryFile.libraryFiles) {
var isSynthetic = !file.exists;
var content = file.getContent();
performance.getDataInt('parseCount').increment();
performance.getDataInt('parseLength').add(content.length);
var unit = file.parse(
var unit = file.parse2(
AnalysisErrorListener.NULL_LISTENER,
content,
);
String? partUriStr;
if (partIndex >= 0) {
partUriStr = libraryFile.unlinkedUnit.parts[partIndex];
partUriStr = libraryFile.unlinked2.parts[partIndex];
}
partIndex++;
@ -1069,3 +1130,13 @@ class LibraryCycleTestData {
final List<String> getKeys = [];
final List<String> putKeys = [];
}
class _ContentWithDigest {
final String content;
final String digestStr;
_ContentWithDigest({
required this.content,
required this.digestStr,
});
}

View file

@ -1136,10 +1136,11 @@ library my;
});
}
/// TODO(scheglov) Test discovery of a sibling library
test_newFile_partOfName() async {
final a = newFile('$testPackageLibPath/a.dart', r'''
final a = newFile('$testPackageLibPath/nested/a.dart', r'''
library my.lib;
part 'b.dart';
part '../b.dart';
''');
final b = newFile('$testPackageLibPath/b.dart', r'''
@ -2239,7 +2240,10 @@ class FileSystemStateTest with ResourceProviderMixin {
Uint32List(0),
Uint32List(0),
featureSetProvider,
fileContentCache: FileContentCache.ephemeral(resourceProvider),
fileContentStrategy: StoredFileContentStrategy(
FileContentCache.ephemeral(resourceProvider),
),
prefetchFiles: null,
);
}

View file

@ -7,8 +7,8 @@ import 'dart:convert';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/dart/analysis/byte_store.dart';
import 'package:analyzer/src/dart/analysis/file_state.dart';
import 'package:analyzer/src/dart/analysis/performance_logger.dart';
import 'package:analyzer/src/dart/micro/library_graph.dart';
import 'package:analyzer/src/dart/micro/resolve_file.dart';
import 'package:analyzer/src/dart/sdk/sdk.dart';
import 'package:analyzer/src/test_utilities/find_element.dart';

View file

@ -5,6 +5,7 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/source/line_info.dart';
import 'package:analyzer/src/dart/analysis/file_state.dart';
import 'package:analyzer/src/dart/ast/utilities.dart';
import 'package:analyzer/src/dart/error/syntactic_errors.dart';
import 'package:analyzer/src/dart/micro/resolve_file.dart';
@ -362,9 +363,12 @@ class B {
}
''');
expect(() async {
try {
await resolveFile(a.path);
}, throwsStateError);
fail('Expected StateError');
} on StateError {
// OK
}
// Notify the resolver about b.dart, it is OK now.
fileResolver.changeFiles([b.path]);
@ -2465,8 +2469,7 @@ byteStore
}
test_resolve_libraryWithPart_noLibraryDiscovery() async {
var partPath = '/workspace/dart/test/lib/a.dart';
newFile(partPath, r'''
newFile('/workspace/dart/test/lib/a.dart', r'''
part of 'test.dart';
class A {}
@ -2480,11 +2483,12 @@ void f(A a) {}
// We started resolution from the library, and then followed to the part.
// So, the part knows its library, there is no need to discover it.
_assertDiscoveredLibraryForParts([]);
// TODO(scheglov) Use textual dump
// _assertDiscoveredLibraryForParts([]);
}
test_resolve_part_of_name() async {
newFile('/workspace/dart/test/lib/a.dart', r'''
final a = newFile('/workspace/dart/test/lib/a.dart', r'''
library my.lib;
part 'test.dart';
@ -2503,11 +2507,15 @@ void func() {
}
''');
_assertDiscoveredLibraryForParts([result.path]);
// TODO(scheglov) Use textual dump
final fsState = fileResolver.fsState!;
final testState = fsState.getExistingFileForResource(testFile)!;
final testKind = testState.kind as PartFileStateKind;
expect(testKind.library?.file, fsState.getExistingFileForResource(a));
}
test_resolve_part_of_uri() async {
newFile('/workspace/dart/test/lib/a.dart', r'''
final a = newFile('/workspace/dart/test/lib/a.dart', r'''
part 'test.dart';
class A {
@ -2524,7 +2532,11 @@ void func() {
}
''');
_assertDiscoveredLibraryForParts([result.path]);
// TODO(scheglov) Use textual dump
final fsState = fileResolver.fsState!;
final testState = fsState.getExistingFileForResource(testFile)!;
final testKind = testState.kind as PartFileStateKind;
expect(testKind.library?.file, fsState.getExistingFileForResource(a));
}
test_resolveFile_cache() async {
@ -2699,10 +2711,6 @@ import 'foo:bar';
]);
}
void _assertDiscoveredLibraryForParts(List<String> expected) {
expect(fileResolver.fsState!.testView.partsDiscoveredLibraries, expected);
}
void _assertResolvedFiles(
List<File> expected, {
bool andClear = true,