From 1aa6fa47766bc2fa2a10544bc88a5756678ba765 Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Mon, 25 Oct 2021 17:47:59 +0000 Subject: [PATCH] Refactor Cider FileState. The general direction is to make pieces of data that are produced together to be stored together, and so be available together. _FileStateLocation is created before FileState, so it is separated. Any FileState has some unlinked state, so we pass _FileStateUnlinked into the constructor. In Cider we don't refresh files, we discard them and recreate. So, '_unlinked' is a final field. Practically FileState works as a wrapper around _FileStateUnlinked. It could probably have been inlined into FileState. My excuse for not doing this is that in DAS, at least at the moment, we keep FileState instances and refresh them with replacing their unlinked states. So, they should be separate objects. Change-Id: I9439a4021e6efa9e4375eceb5eecf534eabda168 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/217860 Reviewed-by: Brian Wilkerson Reviewed-by: Keerti Parthasarathy Commit-Queue: Konstantin Shcheglov --- .../lib/src/dart/micro/library_analyzer.dart | 12 +- .../lib/src/dart/micro/library_graph.dart | 1011 +++++++++-------- .../lib/src/dart/micro/resolve_file.dart | 20 +- .../test/src/dart/micro/file_resolution.dart | 11 +- 4 files changed, 590 insertions(+), 464 deletions(-) diff --git a/pkg/analyzer/lib/src/dart/micro/library_analyzer.dart b/pkg/analyzer/lib/src/dart/micro/library_analyzer.dart index 4db5fcff946..be5b2ba5ae1 100644 --- a/pkg/analyzer/lib/src/dart/micro/library_analyzer.dart +++ b/pkg/analyzer/lib/src/dart/micro/library_analyzer.dart @@ -110,7 +110,7 @@ class LibraryAnalyzer { // Parse all files. performance.run('parse', (performance) { - for (FileState file in _library.libraryFiles) { + for (FileState file in _library.files().ofLibrary) { if (completionPath == null || file.path == completionPath) { units[file] = _parse( file: file, @@ -231,19 +231,19 @@ class LibraryAnalyzer { if (_analysisOptions.lint) { performance.run('computeLints', (performance) { - var allUnits = _library.libraryFiles.map((file) { + var allUnits = _library.files().ofLibrary.map((file) { var content = getFileContent(file); return LinterContextUnit(content, units[file]!); }).toList(); for (int i = 0; i < allUnits.length; i++) { - _computeLints(_library.libraryFiles[i], allUnits[i], allUnits); + _computeLints(_library.files().ofLibrary[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.libraryFiles) { + for (var file in _library.files().ofLibrary) { IgnoreValidator( _getErrorReporter(file), _getErrorListener(file).errors, @@ -472,7 +472,7 @@ class LibraryAnalyzer { } bool _isExistingSource(Source source) { - for (var file in _library.directReferencedFiles) { + for (var file in _library.files().directReferencedFiles) { if (file.uri == source.uri) { return file.exists; } @@ -595,7 +595,7 @@ class LibraryAnalyzer { } else if (directive is PartDirectiveImpl) { StringLiteral partUri = directive.uri; - FileState partFile = _library.partedFiles[partIndex]; + FileState partFile = _library.files().parted[partIndex]; var partUnit = units[partFile]!; CompilationUnitElement partElement = _libraryElement.parts[partIndex]; partUnit.element = partElement; diff --git a/pkg/analyzer/lib/src/dart/micro/library_graph.dart b/pkg/analyzer/lib/src/dart/micro/library_graph.dart index 2626139024b..a0379af02b1 100644 --- a/pkg/analyzer/lib/src/dart/micro/library_graph.dart +++ b/pkg/analyzer/lib/src/dart/micro/library_graph.dart @@ -112,69 +112,22 @@ class CiderUnlinkedUnit { } class FileState { - final FileSystemState _fsState; - - /// The path of the file. - final String path; - - /// The URI of the file. - final Uri uri; - - /// 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; + final _FileStateUnlinked _unlinked; /// Files that reference this file. final List referencingFiles = []; - final List importedFiles = []; - final List exportedFiles = []; - final List partedFiles = []; - final Set directReferencedFiles = {}; - final Set directReferencedLibraries = {}; - final List libraryFiles = []; - FileState? partOfLibrary; + _FileStateFiles? _files; - late Uint8List _digest; - late bool _exists; - late CiderUnlinkedUnit unlinked; LibraryCycle? _libraryCycle; - /// id of the cache entry with unlinked data. - late int unlinkedId; + FileState._(this._unlinked); - FileState._( - this._fsState, - this.path, - this.uri, - this.source, - this.workspacePackage, - this._contextFeatureSet, - this._packageLanguageVersion, - ); + Uint8List get apiSignature => unlinkedUnit.apiSignature; - Uint8List get apiSignature => unlinked.unit.apiSignature; + Uint8List get digest => _unlinked.digest; - Uint8List get digest => _digest; - - bool get exists => _exists; + bool get exists => _unlinked.exists; /// 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. @@ -185,7 +138,11 @@ class FileState { return _libraryCycle!; } - LineInfo get lineInfo => LineInfo(unlinked.unit.lineStarts); + LineInfo get lineInfo => LineInfo(unlinkedUnit.lineStarts); + + FileState? get partOfLibrary => _unlinked.partOfLibrary; + + String get path => _location.path; /// The resolved signature of the file, that depends on the [libraryCycle] /// signature, and the content of the file. @@ -194,47 +151,62 @@ class FileState { signatureBuilder.addString(path); signatureBuilder.addBytes(libraryCycle.signature); - var content = getContentWithSameDigest(); + var content = getContent(); signatureBuilder.addString(content); return signatureBuilder.toHex(); } + Source get source => _location.source; + + int get unlinkedId => _unlinked.unlinkedId; + + UnlinkedUnit get unlinkedUnit => _unlinked.unlinked.unit; + + Uri get uri => _location.uri; + /// Return the [uri] string. String get uriStr => uri.toString(); + WorkspacePackage? get workspacePackage => _location.workspacePackage; + + FileSystemState get _fsState => _location._fsState; + + _FileStateLocation get _location => _unlinked.location; + /// Collect all files that are transitively referenced by this file via /// imports, exports, and parts. void collectAllReferencedFiles(Set referencedFiles) { - for (var file in {...importedFiles, ...exportedFiles, ...partedFiles}) { + for (var file in files().directReferencedFiles) { if (referencedFiles.add(file.path)) { file.collectAllReferencedFiles(referencedFiles); } } } - /// Return the content of the file, the empty string if cannot be read. - String getContent() { - try { - var resource = _fsState._resourceProvider.getFile(path); - return resource.readAsStringSync(); - } catch (_) { - return ''; - } + _FileStateFiles files({ + OperationPerformanceImpl? performance, + }) { + return _files ??= _FileStateFiles( + owner: this, + performance: performance ?? OperationPerformanceImpl(''), + ); } /// Return the content of the file, the empty string if cannot be read. /// - /// Additionally, we read the file digest, end verify that it is the same - /// as the [_digest] that we recorded in [refresh]. If it is not, then the - /// file was changed, and we failed to call [FileSystemState.changeFile] - String getContentWithSameDigest() { - var digest = utf8.encode(_fsState.getFileDigest(path)); - if (!const ListEquality().equals(digest, _digest)) { + /// 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() { + var contentWithDigest = _location._getContent(); + + var digest = contentWithDigest.digest; + if (!const ListEquality().equals(digest, _unlinked.digest)) { throw StateError('File was changed, but not invalidated: $path'); } - return getContent(); + return contentWithDigest.content; } void internal_setLibraryCycle(LibraryCycle cycle, String signature) { @@ -243,366 +215,13 @@ class FileState { CompilationUnitImpl parse( 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); - - // Pass the feature set from the scanner to the parser - // because the scanner may have detected a language version comment - // and downgraded the feature set it holds. - Parser parser = Parser( - source, - errorListener, - featureSet: scanner.featureSet, - ); - parser.enableOptionalNewAndConst = true; - var unit = parser.parseCompilationUnit(token); - unit.lineInfo = lineInfo; - - // StringToken uses a static instance of StringCanonicalizer, so we need - // to clear it explicitly once we are done using it for this file. - StringToken.canonicalizer.clear(); - - // TODO(scheglov) Use actual versions. - unit.languageVersion = LibraryLanguageVersion( - package: ExperimentStatus.currentVersion, - override: null, - ); - - return unit; - } - - void refresh({ - FileState? containingLibrary, - required OperationPerformanceImpl performance, - }) { - _fsState.testView.refreshedFiles.add(path); - performance.getDataInt('count').increment(); - - performance.run('digest', (_) { - _digest = utf8.encode(_fsState.getFileDigest(path)) as Uint8List; - _exists = _digest.isNotEmpty; - }); - - String unlinkedKey = '$path.unlinked'; - - // Prepare bytes of the unlinked bundle - existing or new. - // TODO(migration): should not be nullable - Uint8List? unlinkedBytes; - { - var unlinkedData = _fsState._byteStore.get(unlinkedKey, _digest); - unlinkedBytes = unlinkedData?.bytes; - - if (unlinkedBytes == null || unlinkedBytes.isEmpty) { - var content = performance.run('content', (_) { - return getContent(); - }); - - var unit = performance.run('parse', (performance) { - performance.getDataInt('count').increment(); - performance.getDataInt('length').add(content.length); - return parse(AnalysisErrorListener.NULL_LISTENER, content); - }); - - performance.run('unlinked', (performance) { - var unlinkedBuilder = serializeAstCiderUnlinked(unit); - unlinkedBytes = unlinkedBuilder.toBytes(); - performance.getDataInt('length').add(unlinkedBytes!.length); - unlinkedData = - _fsState._byteStore.putGet(unlinkedKey, _digest, unlinkedBytes!); - unlinkedBytes = unlinkedData!.bytes; - }); - - unlinked = CiderUnlinkedUnit.fromBytes(unlinkedBytes!); - - // TODO(scheglov) We decode above only because we call it here. - performance.run('prefetch', (_) { - _prefetchDirectReferences(unlinked.unit); - }); - } - unlinkedId = unlinkedData!.id; - } - - // Read the unlinked bundle. - unlinked = CiderUnlinkedUnit.fromBytes(unlinkedBytes!); - - // Build the graph. - for (var directive in unlinked.unit.imports) { - var file = _fileForRelativeUri( - relativeUri: directive.uri, - performance: performance, - ); - if (file != null) { - importedFiles.add(file); - } - } - for (var directive in unlinked.unit.exports) { - var file = _fileForRelativeUri( - relativeUri: directive.uri, - performance: performance, - ); - if (file != null) { - exportedFiles.add(file); - } - } - for (var uri in unlinked.unit.parts) { - var file = _fileForRelativeUri( - containingLibrary: this, - relativeUri: uri, - performance: performance, - ); - if (file != null) { - partedFiles.add(file); - } - } - if (unlinked.unit.hasPartOfDirective) { - if (containingLibrary == null) { - _fsState.testView.partsDiscoveredLibraries.add(path); - var libraryName = unlinked.unit.partOfName; - var libraryUri = unlinked.unit.partOfUri; - partOfLibrary = null; - if (libraryName != null) { - _findPartOfNameLibrary(performance: performance); - } else if (libraryUri != null) { - partOfLibrary = _fileForRelativeUri( - relativeUri: libraryUri, - performance: performance, - ); - } - } else { - partOfLibrary = containingLibrary; - } - if (partOfLibrary != null) { - directReferencedFiles.add(partOfLibrary!); - } - } - libraryFiles.add(this); - libraryFiles.addAll(partedFiles); - - // Compute referenced files. - directReferencedFiles - ..addAll(importedFiles) - ..addAll(exportedFiles) - ..addAll(partedFiles); - directReferencedLibraries - ..addAll(importedFiles) - ..addAll(exportedFiles); + return _FileStateUnlinked.parse(errorListener, _location, content); } @override String toString() { return path; } - - FileState? _fileForRelativeUri({ - FileState? containingLibrary, - required String relativeUri, - required OperationPerformanceImpl performance, - }) { - if (relativeUri.isEmpty) { - return null; - } - - Uri absoluteUri; - try { - absoluteUri = resolveRelativeUri(uri, Uri.parse(relativeUri)); - } on FormatException { - return null; - } - - var file = _fsState.getFileForUri( - containingLibrary: containingLibrary, - uri: absoluteUri, - performance: performance, - ); - if (file == null) { - return null; - } - - file.referencingFiles.add(this); - return file; - } - - /// This file has a `part of some.library;` directive. Because it does not - /// specify the URI of the library, we don't know the library for sure. - /// But usually the library is one of the sibling files. - void _findPartOfNameLibrary({ - required OperationPerformanceImpl performance, - }) { - var resourceProvider = _fsState._resourceProvider; - var pathContext = resourceProvider.pathContext; - - var children = []; - try { - var parent = resourceProvider.getFile(path).parent2; - children = parent.getChildren(); - } catch (_) {} - - for (var siblingFile in children) { - if (file_paths.isDart(pathContext, siblingFile.path)) { - var childState = _fsState.getFileForPath( - path: siblingFile.path, - performance: performance, - ); - if (childState.partedFiles.contains(this)) { - partOfLibrary = childState; - break; - } - } - } - } - - void _prefetchDirectReferences(UnlinkedUnit unlinkedUnit2) { - if (_fsState.prefetchFiles == null) { - return; - } - - var paths = {}; - - void findPathForUri(String relativeUri) { - if (relativeUri.isEmpty) { - return; - } - Uri absoluteUri; - try { - absoluteUri = resolveRelativeUri(uri, Uri.parse(relativeUri)); - } on FormatException { - return; - } - var p = _fsState.getPathForUri(absoluteUri); - if (p != null) { - paths.add(p); - } - } - - for (var directive in unlinked.unit.imports) { - findPathForUri(directive.uri); - } - for (var directive in unlinked.unit.exports) { - findPathForUri(directive.uri); - } - for (var uri in unlinked.unit.parts) { - findPathForUri(uri); - } - _fsState.prefetchFiles!(paths.toList()); - } - - static CiderUnlinkedUnit serializeAstCiderUnlinked(CompilationUnit unit) { - var exports = []; - var imports = []; - var parts = []; - var hasDartCoreImport = false; - var hasLibraryDirective = false; - var hasPartOfDirective = false; - String? partOfName; - String? partOfUriStr; - for (var directive in unit.directives) { - if (directive is ExportDirective) { - var builder = _serializeNamespaceDirective(directive); - exports.add(builder); - } else if (directive is ImportDirective) { - var builder = _serializeNamespaceDirective(directive); - imports.add(builder); - if (builder.uri == 'dart:core') { - hasDartCoreImport = true; - } - } else if (directive is LibraryDirective) { - hasLibraryDirective = true; - } else if (directive is PartDirective) { - var uriStr = directive.uri.stringValue; - parts.add(uriStr ?? ''); - } else if (directive is PartOfDirective) { - hasPartOfDirective = true; - var libraryName = directive.libraryName; - var uriStr = directive.uri?.stringValue; - if (libraryName != null) { - partOfName = libraryName.components.map((e) => e.name).join('.'); - } else if (uriStr != null) { - partOfUriStr = uriStr; - } - } - } - if (!hasDartCoreImport) { - imports.add( - UnlinkedNamespaceDirective( - configurations: [], - uri: 'dart:core', - ), - ); - } - - var declaredExtensions = []; - var declaredFunctions = []; - var declaredTypes = []; - var declaredVariables = []; - for (var declaration in unit.declarations) { - if (declaration is ClassDeclaration) { - declaredTypes.add(declaration.name.name); - } else if (declaration is EnumDeclaration) { - declaredTypes.add(declaration.name.name); - } else if (declaration is ExtensionDeclaration) { - var name = declaration.name; - if (name != null) { - declaredExtensions.add(name.name); - } - } else if (declaration is FunctionDeclaration) { - declaredFunctions.add(declaration.name.name); - } else if (declaration is MixinDeclaration) { - declaredTypes.add(declaration.name.name); - } else if (declaration is TopLevelVariableDeclaration) { - for (var variable in declaration.variables.variables) { - declaredVariables.add(variable.name.name); - } - } - } - - var unlinkedUnit = UnlinkedUnit( - apiSignature: computeUnlinkedApiSignature(unit), - exports: exports, - hasLibraryDirective: hasLibraryDirective, - hasPartOfDirective: hasPartOfDirective, - imports: imports, - informativeBytes: writeUnitInformative(unit), - lineStarts: Uint32List.fromList(unit.lineInfo!.lineStarts), - partOfName: partOfName, - partOfUri: partOfUriStr, - parts: parts, - ); - - return CiderUnlinkedUnit( - unit: unlinkedUnit, - topLevelDeclarations: CiderUnitTopLevelDeclarations( - extensionNames: declaredExtensions, - functionNames: declaredFunctions, - typeNames: declaredTypes, - variableNames: declaredVariables, - ), - ); - } - - 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 ?? '', - ); - } } class FileSystemState { @@ -659,7 +278,7 @@ class FileSystemState { _uriToFile.remove(file.uri); // The removed file does not reference other file anymore. - for (var referencedFile in file.directReferencedFiles) { + for (var referencedFile in file.files().directReferencedFiles) { referencedFile.referencingFiles.remove(file); } @@ -674,7 +293,7 @@ class FileSystemState { Set collectSharedDataIdentifiers() { var result = {}; for (var file in _pathToFile.values) { - result.add(file.unlinkedId); + result.add(file._unlinked.unlinkedId); } return result; } @@ -730,17 +349,21 @@ class FileSystemState { var featureSet = contextFeatureSet(path, uri, workspacePackage); var packageLanguageVersion = contextLanguageVersion(path, uri, workspacePackage); - file = FileState._(this, path, uri, source, workspacePackage, featureSet, - packageLanguageVersion); + var location = _FileStateLocation._(this, path, uri, source, + workspacePackage, featureSet, packageLanguageVersion); + file = FileState._( + _FileStateUnlinked( + location: location, + partOfLibrary: null, + performance: performance, + ), + ); _pathToFile[path] = file; _uriToFile[uri] = file; - performance.run('refresh', (performance) { - file!.refresh( - performance: performance, - ); - }); + // Recurse with recording performance. + file.files(performance: performance); } return file; } @@ -763,15 +386,21 @@ class FileSystemState { var packageLanguageVersion = contextLanguageVersion(path, uri, workspacePackage); - file = FileState._(this, path, uri, source, workspacePackage, featureSet, - packageLanguageVersion); + var location = _FileStateLocation._(this, path, uri, source, + workspacePackage, featureSet, packageLanguageVersion); + file = FileState._( + _FileStateUnlinked( + location: location, + partOfLibrary: containingLibrary, + performance: performance, + ), + ); + _pathToFile[path] = file; _uriToFile[uri] = file; - file.refresh( - containingLibrary: containingLibrary, - performance: performance, - ); + // Recurse with recording performance. + file.files(performance: performance); } return file; } @@ -807,7 +436,7 @@ class FileSystemState { } } - var topLevelDeclarations = file.unlinked.topLevelDeclarations; + var topLevelDeclarations = file._unlinked.unlinked.topLevelDeclarations; addDeclaration( topLevelDeclarations.extensionNames, FileTopLevelDeclarationKind.extension, @@ -954,6 +583,16 @@ class LibraryCycle { } } +class _ContentWithDigest { + final String content; + final Uint8List digest; + + _ContentWithDigest({ + required this.content, + required this.digest, + }); +} + class _FakeSource implements Source { @override final String fullName; @@ -967,6 +606,486 @@ class _FakeSource implements Source { dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); } +class _FileStateFiles { + final List imported = []; + final List exported = []; + final List parted = []; + final List ofLibrary = []; + + _FileStateFiles({ + required FileState owner, + required OperationPerformanceImpl performance, + }) { + var unlinked = owner._unlinked; + var location = unlinked.location; + var unlinkedUnit = unlinked.unlinked.unit; + + // Build the graph. + for (var directive in unlinkedUnit.imports) { + var file = location._fileForRelativeUri( + relativeUri: directive.uri, + performance: performance, + ); + if (file != null) { + file.referencingFiles.add(owner); + imported.add(file); + } + } + for (var directive in unlinkedUnit.exports) { + var file = location._fileForRelativeUri( + relativeUri: directive.uri, + performance: performance, + ); + if (file != null) { + exported.add(file); + file.referencingFiles.add(owner); + } + } + for (var uri in unlinkedUnit.parts) { + var file = location._fileForRelativeUri( + containingLibrary: owner, + relativeUri: uri, + performance: performance, + ); + if (file != null) { + parted.add(file); + file.referencingFiles.add(owner); + } + } + + ofLibrary.add(owner); + ofLibrary.addAll(parted); + } + + /// Return all directly referenced files - imported, exported or parted. + Set get directReferencedFiles { + return {...imported, ...exported, ...parted}; + } + + /// Return all directly referenced libraries - imported or exported. + Set get directReferencedLibraries { + return {...imported, ...exported}; + } +} + +class _FileStateLocation { + final FileSystemState _fsState; + + /// The path of the file. + final String path; + + /// The URI of the file. + final Uri uri; + + /// 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; + + _FileStateLocation._( + this._fsState, + this.path, + this.uri, + this.source, + this.workspacePackage, + this._contextFeatureSet, + this._packageLanguageVersion, + ); + + File get resource { + return _fsState._resourceProvider.getFile(path); + } + + FileState? _fileForRelativeUri({ + FileState? containingLibrary, + required String relativeUri, + required OperationPerformanceImpl performance, + }) { + if (relativeUri.isEmpty) { + return null; + } + + Uri absoluteUri; + try { + absoluteUri = resolveRelativeUri(uri, Uri.parse(relativeUri)); + } on FormatException { + return null; + } + + return _fsState.getFileForUri( + containingLibrary: containingLibrary, + uri: absoluteUri, + performance: performance, + ); + } + + /// This file has a `part of some.library;` directive. Because it does not + /// specify the URI of the library, we don't know the library for sure. + /// But usually the library is one of the sibling files. + FileState? _findPartOfNameLibrary({ + required OperationPerformanceImpl performance, + }) { + var resourceProvider = _fsState._resourceProvider; + var pathContext = resourceProvider.pathContext; + + var siblings = []; + try { + siblings = resource.parent2.getChildren(); + } catch (_) {} + + for (var sibling in siblings) { + if (file_paths.isDart(pathContext, sibling.path)) { + var siblingState = _fsState.getFileForPath( + path: sibling.path, + performance: performance, + ); + if (siblingState.files().parted.any((part) => part.path == path)) { + return siblingState; + } + } + } + } + + _ContentWithDigest _getContent() { + String content; + try { + content = resource.readAsStringSync(); + } catch (_) { + content = ''; + } + + var digestStr = _fsState.getFileDigest(path); + var digest = utf8.encode(digestStr) as Uint8List; + + return _ContentWithDigest(content: content, digest: digest); + } +} + +class _FileStateUnlinked { + final _FileStateLocation location; + FileState? _partOfLibrary; + + final Uint8List digest; + final bool exists; + final CiderUnlinkedUnit unlinked; + + /// id of the cache entry with unlinked data. + final int unlinkedId; + + factory _FileStateUnlinked({ + required _FileStateLocation location, + required FileState? partOfLibrary, + required OperationPerformanceImpl performance, + }) { + location._fsState.testView.refreshedFiles.add(location.path); + + int unlinkedId; + CiderUnlinkedUnit unlinked; + + var digest = performance.run('digest', (performance) { + performance.getDataInt('count').increment(); + var digestStr = location._fsState.getFileDigest(location.path); + return utf8.encode(digestStr) as Uint8List; + }); + + var exists = digest.isNotEmpty; + + var unlinkedKey = '${location.path}.unlinked'; + var isUnlinkedFromCache = true; + + // Prepare bytes of the unlinked bundle - existing or new. + // TODO(migration): should not be nullable + Uint8List? unlinkedBytes; + { + var unlinkedData = location._fsState._byteStore.get(unlinkedKey, digest); + unlinkedBytes = unlinkedData?.bytes; + + if (unlinkedBytes == null || unlinkedBytes.isEmpty) { + isUnlinkedFromCache = false; + + var contentWithDigest = performance.run('content', (_) { + return location._getContent(); + }); + digest = contentWithDigest.digest; + var content = contentWithDigest.content; + + var unit = performance.run('parse', (performance) { + performance.getDataInt('count').increment(); + performance.getDataInt('length').add(content.length); + return parse(AnalysisErrorListener.NULL_LISTENER, location, content); + }); + + performance.run('unlinked', (performance) { + var unlinkedUnit = serializeAstCiderUnlinked(unit); + unlinkedBytes = unlinkedUnit.toBytes(); + performance.getDataInt('length').add(unlinkedBytes!.length); + unlinkedData = location._fsState._byteStore + .putGet(unlinkedKey, digest, unlinkedBytes!); + unlinkedBytes = unlinkedData!.bytes; + }); + + unlinked = CiderUnlinkedUnit.fromBytes(unlinkedBytes!); + } + unlinkedId = unlinkedData!.id; + } + + // Read the unlinked bundle. + unlinked = CiderUnlinkedUnit.fromBytes(unlinkedBytes!); + + var result = _FileStateUnlinked._( + location: location, + partOfLibrary: partOfLibrary, + digest: digest, + exists: exists, + unlinked: unlinked, + unlinkedId: unlinkedId, + ); + if (isUnlinkedFromCache) { + performance.run('prefetch', (_) { + result._prefetchDirectReferences(); + }); + } + return result; + } + + _FileStateUnlinked._({ + required this.location, + required FileState? partOfLibrary, + required this.digest, + required this.exists, + required this.unlinked, + required this.unlinkedId, + }) : _partOfLibrary = partOfLibrary; + + FileState? get partOfLibrary { + var partOfLibrary = _partOfLibrary; + if (partOfLibrary != null) { + return partOfLibrary; + } + + var performance = OperationPerformanceImpl(''); + + var libraryName = unlinked.unit.partOfName; + if (libraryName != null) { + location._fsState.testView.partsDiscoveredLibraries.add(location.path); + return _partOfLibrary = location._findPartOfNameLibrary( + performance: performance, + ); + } + + var libraryUri = unlinked.unit.partOfUri; + if (libraryUri != null) { + location._fsState.testView.partsDiscoveredLibraries.add(location.path); + return _partOfLibrary = location._fileForRelativeUri( + relativeUri: libraryUri, + performance: performance, + ); + } + } + + void _prefetchDirectReferences() { + if (location._fsState.prefetchFiles == null) { + return; + } + + var paths = {}; + + /// TODO(scheglov) This is duplicate. + void findPathForUri(String relativeUri) { + if (relativeUri.isEmpty) { + return; + } + Uri absoluteUri; + try { + absoluteUri = resolveRelativeUri(location.uri, Uri.parse(relativeUri)); + } on FormatException { + return; + } + var p = location._fsState.getPathForUri(absoluteUri); + if (p != null) { + paths.add(p); + } + } + + var unlinkedUnit = unlinked.unit; + for (var directive in unlinkedUnit.imports) { + findPathForUri(directive.uri); + } + for (var directive in unlinkedUnit.exports) { + findPathForUri(directive.uri); + } + for (var uri in unlinkedUnit.parts) { + findPathForUri(uri); + } + + location._fsState.prefetchFiles!(paths.toList()); + } + + static CompilationUnitImpl parse(AnalysisErrorListener errorListener, + _FileStateLocation location, String content) { + CharSequenceReader reader = CharSequenceReader(content); + Scanner scanner = Scanner(location.source, reader, errorListener) + ..configureFeatures( + featureSetForOverriding: location._contextFeatureSet, + featureSet: location._contextFeatureSet.restrictToVersion( + location._packageLanguageVersion, + ), + ); + Token token = scanner.tokenize(reportScannerErrors: false); + LineInfo lineInfo = LineInfo(scanner.lineStarts); + + // Pass the feature set from the scanner to the parser + // because the scanner may have detected a language version comment + // and downgraded the feature set it holds. + Parser parser = Parser( + location.source, + errorListener, + featureSet: scanner.featureSet, + ); + parser.enableOptionalNewAndConst = true; + var unit = parser.parseCompilationUnit(token); + unit.lineInfo = lineInfo; + + // StringToken uses a static instance of StringCanonicalizer, so we need + // to clear it explicitly once we are done using it for this file. + StringToken.canonicalizer.clear(); + + // TODO(scheglov) Use actual versions. + unit.languageVersion = LibraryLanguageVersion( + package: ExperimentStatus.currentVersion, + override: null, + ); + + return unit; + } + + static CiderUnlinkedUnit serializeAstCiderUnlinked(CompilationUnit unit) { + var exports = []; + var imports = []; + var parts = []; + var hasDartCoreImport = false; + var hasLibraryDirective = false; + var hasPartOfDirective = false; + String? partOfName; + String? partOfUriStr; + for (var directive in unit.directives) { + if (directive is ExportDirective) { + var builder = _serializeNamespaceDirective(directive); + exports.add(builder); + } else if (directive is ImportDirective) { + var builder = _serializeNamespaceDirective(directive); + imports.add(builder); + if (builder.uri == 'dart:core') { + hasDartCoreImport = true; + } + } else if (directive is LibraryDirective) { + hasLibraryDirective = true; + } else if (directive is PartDirective) { + var uriStr = directive.uri.stringValue; + parts.add(uriStr ?? ''); + } else if (directive is PartOfDirective) { + hasPartOfDirective = true; + var libraryName = directive.libraryName; + var uriStr = directive.uri?.stringValue; + if (libraryName != null) { + partOfName = libraryName.components.map((e) => e.name).join('.'); + } else if (uriStr != null) { + partOfUriStr = uriStr; + } + } + } + if (!hasDartCoreImport) { + imports.add( + UnlinkedNamespaceDirective( + configurations: [], + uri: 'dart:core', + ), + ); + } + + var declaredExtensions = []; + var declaredFunctions = []; + var declaredTypes = []; + var declaredVariables = []; + for (var declaration in unit.declarations) { + if (declaration is ClassDeclaration) { + declaredTypes.add(declaration.name.name); + } else if (declaration is EnumDeclaration) { + declaredTypes.add(declaration.name.name); + } else if (declaration is ExtensionDeclaration) { + var name = declaration.name; + if (name != null) { + declaredExtensions.add(name.name); + } + } else if (declaration is FunctionDeclaration) { + declaredFunctions.add(declaration.name.name); + } else if (declaration is MixinDeclaration) { + declaredTypes.add(declaration.name.name); + } else if (declaration is TopLevelVariableDeclaration) { + for (var variable in declaration.variables.variables) { + declaredVariables.add(variable.name.name); + } + } + } + + var unlinkedUnit = UnlinkedUnit( + apiSignature: computeUnlinkedApiSignature(unit), + exports: exports, + hasLibraryDirective: hasLibraryDirective, + hasPartOfDirective: hasPartOfDirective, + imports: imports, + informativeBytes: writeUnitInformative(unit), + lineStarts: Uint32List.fromList(unit.lineInfo!.lineStarts), + partOfName: partOfName, + partOfUri: partOfUriStr, + parts: parts, + ); + + return CiderUnlinkedUnit( + unit: unlinkedUnit, + topLevelDeclarations: CiderUnitTopLevelDeclarations( + extensionNames: declaredExtensions, + functionNames: declaredFunctions, + typeNames: declaredTypes, + variableNames: declaredVariables, + ), + ); + } + + 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 ?? '', + ); + } +} + /// Node in [_LibraryWalker]. class _LibraryNode extends graph.Node<_LibraryNode> { final _LibraryWalker walker; @@ -979,7 +1098,7 @@ class _LibraryNode extends graph.Node<_LibraryNode> { @override List<_LibraryNode> computeDependencies() { - return file.directReferencedLibraries.map(walker.getNode).toList(); + return file.files().directReferencedLibraries.map(walker.getNode).toList(); } } @@ -1013,8 +1132,8 @@ class _LibraryWalker extends graph.DependencyWalker<_LibraryNode> { // Append direct referenced cycles. for (var node in scc) { var file = node.file; - _appendDirectlyReferenced(cycle, signature, file.importedFiles); - _appendDirectlyReferenced(cycle, signature, file.exportedFiles); + _appendDirectlyReferenced(cycle, signature, file.files().imported); + _appendDirectlyReferenced(cycle, signature, file.files().exported); } // Fill the cycle with libraries. @@ -1023,8 +1142,8 @@ class _LibraryWalker extends graph.DependencyWalker<_LibraryNode> { signature.addString(node.file.uriStr); - signature.addInt(node.file.libraryFiles.length); - for (var file in node.file.libraryFiles) { + signature.addInt(node.file.files().ofLibrary.length); + for (var file in node.file.files().ofLibrary) { signature.addBool(file.exists); signature.addBytes(file.apiSignature); } diff --git a/pkg/analyzer/lib/src/dart/micro/resolve_file.dart b/pkg/analyzer/lib/src/dart/micro/resolve_file.dart index 7ff94e7c2c6..89c1fe00f14 100644 --- a/pkg/analyzer/lib/src/dart/micro/resolve_file.dart +++ b/pkg/analyzer/lib/src/dart/micro/resolve_file.dart @@ -430,7 +430,7 @@ class FileResolver { var libraryFile = file; var partOfLibrary = file.partOfLibrary; if (partOfLibrary != null) { - if (partOfLibrary.libraryFiles.contains(file)) { + if (partOfLibrary.files().ofLibrary.contains(file)) { libraryFile = partOfLibrary; } } @@ -477,7 +477,7 @@ class FileResolver { var libraryFile = file; var partOfLibrary = file.partOfLibrary; if (partOfLibrary != null) { - if (partOfLibrary.libraryFiles.contains(file)) { + if (partOfLibrary.files().ofLibrary.contains(file)) { libraryFile = partOfLibrary; } } @@ -509,7 +509,7 @@ class FileResolver { libraryContext!.elementFactory, contextObjects!.inheritanceManager, libraryFile, - (file) => file.getContentWithSameDigest(), + (file) => file.getContent(), ); try { @@ -522,7 +522,7 @@ class FileResolver { }); } catch (exception, stackTrace) { var fileContentMap = {}; - for (var file in libraryFile.libraryFiles) { + for (var file in libraryFile.files().ofLibrary) { var path = file.path; fileContentMap[path] = _getFileContent(path); } @@ -543,7 +543,7 @@ class FileResolver { file.exists, file.getContent(), file.lineInfo, - file.unlinked.unit.hasPartOfDirective, + file.unlinkedUnit.hasPartOfDirective, fileResult.unit, fileResult.errors, ); @@ -822,8 +822,8 @@ class _LibraryContext { var unitsInformativeBytes = {}; for (var library in cycle.libraries) { - for (var file in library.libraryFiles) { - var informativeBytes = file.unlinked.unit.informativeBytes; + for (var file in library.files().ofLibrary) { + var informativeBytes = file.unlinkedUnit.informativeBytes; unitsInformativeBytes[file.uri] = informativeBytes; } } @@ -838,10 +838,10 @@ class _LibraryContext { var inputUnits = []; var partIndex = -1; - for (var file in libraryFile.libraryFiles) { + for (var file in libraryFile.files().ofLibrary) { var isSynthetic = !file.exists; - var content = file.getContentWithSameDigest(); + var content = file.getContent(); performance.getDataInt('parseCount').increment(); performance.getDataInt('parseLength').add(content.length); @@ -852,7 +852,7 @@ class _LibraryContext { String? partUriStr; if (partIndex >= 0) { - partUriStr = libraryFile.unlinked.unit.parts[partIndex]; + partUriStr = libraryFile.unlinkedUnit.parts[partIndex]; } partIndex++; diff --git a/pkg/analyzer/test/src/dart/micro/file_resolution.dart b/pkg/analyzer/test/src/dart/micro/file_resolution.dart index 583646540c0..52969ecd42f 100644 --- a/pkg/analyzer/test/src/dart/micro/file_resolution.dart +++ b/pkg/analyzer/test/src/dart/micro/file_resolution.dart @@ -14,6 +14,7 @@ import 'package:analyzer/src/test_utilities/find_element.dart'; import 'package:analyzer/src/test_utilities/find_node.dart'; import 'package:analyzer/src/test_utilities/mock_sdk.dart'; import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart'; +import 'package:analyzer/src/util/performance/operation_performance.dart'; import 'package:analyzer/src/workspace/bazel.dart'; import 'package:crypto/crypto.dart'; import 'package:linter/src/rules.dart'; @@ -71,8 +72,14 @@ class FileResolutionTest with ResourceProviderMixin, ResolutionTest { } @override - Future resolveFile(String path) async { - result = fileResolver.resolve(path: path); + Future resolveFile( + String path, { + OperationPerformanceImpl? performance, + }) async { + result = fileResolver.resolve( + path: path, + performance: performance, + ); return result; }