From 33b6483ad1d1a9a4bc44ad90311eeb868a0e55e3 Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Wed, 25 May 2022 16:54:38 +0000 Subject: [PATCH] Support for AugmentationFileStateKind. Change-Id: Ib32b7b56739d89118c9a497087dc5066bab177dd Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/245680 Reviewed-by: Brian Wilkerson Commit-Queue: Konstantin Shcheglov --- .../notification_highlights2_test.dart | 11 +- .../analysis/notification_outline_test.dart | 2 +- .../lib/src/dart/analysis/driver.dart | 6 +- .../lib/src/dart/analysis/file_state.dart | 391 ++++-- .../lib/src/dart/analysis/unlinked_data.dart | 30 + .../lib/src/dart/micro/library_graph.dart | 20 +- .../test/generated/invalid_code_test.dart | 6 +- .../test/src/dart/analysis/driver_test.dart | 47 +- .../src/dart/analysis/file_state_test.dart | 1119 +++++++++++++++-- 9 files changed, 1384 insertions(+), 248 deletions(-) diff --git a/pkg/analysis_server/test/analysis/notification_highlights2_test.dart b/pkg/analysis_server/test/analysis/notification_highlights2_test.dart index f8d77d07778..a1aa51706b4 100644 --- a/pkg/analysis_server/test/analysis/notification_highlights2_test.dart +++ b/pkg/analysis_server/test/analysis/notification_highlights2_test.dart @@ -339,7 +339,7 @@ void f() { Future test_BUILT_IN_partOf() async { addTestFile(''' -part of lib; +part of my.lib.name; void f() { var part = 1; var of = 2; @@ -613,11 +613,14 @@ export 'dart:math' Future test_DIRECTIVE_partOf() async { addTestFile(''' -part of lib; +part of my.lib.name; '''); _addLibraryForTestPart(); await prepareHighlights(); - assertHasStringRegion(HighlightRegionType.DIRECTIVE, 'part of lib;'); + assertHasStringRegion( + HighlightRegionType.DIRECTIVE, + 'part of my.lib.name;', + ); } Future test_DYNAMIC_LOCAL_VARIABLE() async { @@ -1695,7 +1698,7 @@ class HighlightsTestSupport extends PubPackageAnalysisServerTest { void _addLibraryForTestPart() { newFile('$testPackageLibPath/my_lib.dart', ''' -library lib; +library my.lib.name; part 'test.dart'; '''); } diff --git a/pkg/analysis_server/test/analysis/notification_outline_test.dart b/pkg/analysis_server/test/analysis/notification_outline_test.dart index 4347bad5dcb..8bce6e665fa 100644 --- a/pkg/analysis_server/test/analysis/notification_outline_test.dart +++ b/pkg/analysis_server/test/analysis/notification_outline_test.dart @@ -97,7 +97,7 @@ library my.lib; Future test_libraryName_hasPartOfDirective() async { newFile('$testPackageLibPath/a.dart', r''' -library lib; +library my.lib; part 'test.dart'; '''); diff --git a/pkg/analyzer/lib/src/dart/analysis/driver.dart b/pkg/analyzer/lib/src/dart/analysis/driver.dart index 1160d53865b..f3ca930c5ce 100644 --- a/pkg/analyzer/lib/src/dart/analysis/driver.dart +++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart @@ -84,7 +84,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 = 220; + static const int DATA_VERSION = 221; /// The number of exception contexts allowed to write. Once this field is /// zero, we stop writing any new exception contexts in this process. @@ -1325,6 +1325,10 @@ class AnalysisDriver implements AnalysisDriverGeneric { } } + if (file.kind is PartFileStateKind && !library.partedFiles.contains(file)) { + return null; + } + // Prepare the signature and key. String signature = _getResolvedUnitSignature(library, file); String key = _getResolvedUnitKey(signature); diff --git a/pkg/analyzer/lib/src/dart/analysis/file_state.dart b/pkg/analyzer/lib/src/dart/analysis/file_state.dart index 2e35c8529c3..42d05a8337b 100644 --- a/pkg/analyzer/lib/src/dart/analysis/file_state.dart +++ b/pkg/analyzer/lib/src/dart/analysis/file_state.dart @@ -43,6 +43,48 @@ 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, + }); +} + /// A library from [SummaryDataStore]. class ExternalLibrary { final Uri uri; @@ -131,6 +173,7 @@ class FileState { /// Files that reference this file. final List referencingFiles = []; + List _augmentationFiles = []; List? _importedFiles; List? _exportedFiles; List _partedFiles = []; @@ -163,6 +206,11 @@ class FileState { /// The unlinked API signature of the file. Uint8List get apiSignature => _apiSignature!; + /// The list of imported augmentations. + List get augmentationFiles { + return _augmentationFiles; + } + /// The content of the file. String get content => _content!; @@ -251,7 +299,7 @@ class FileState { /// not processed a library file yet. FileState? get library { final kind = _kind; - if (kind is PartKnownFileStateKind) { + if (kind is PartFileStateKind) { return kind.library; } else { return null; @@ -395,14 +443,7 @@ class FileState { // Flush exported top-level declarations of all files. if (apiSignatureChanged) { _libraryCycle?.invalidate(); - - // If this is a part, invalidate the libraries. - final kind = _kind; - if (kind is PartKnownFileStateKind) { - for (final library in kind._libraries) { - library._libraryCycle?.invalidate(); - } - } + _invalidatesLibrariesOfThisPart(); } // It is possible that this file does not reference these files. @@ -417,6 +458,7 @@ class FileState { // Read parts eagerly to link parts to libraries. _updateKind(); _updatePartedFiles(); + _updateAugmentationFiles(); // Update mapping from subtyped names to files. for (var name in _driverUnlinkedUnit!.subtypedNames) { @@ -499,6 +541,19 @@ class FileState { } } + /// 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) @@ -555,107 +610,93 @@ class FileState { } } - removeForOne(_importedFiles); + 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() { - /// This file is a part (was not part, or no kind at all). - /// Now we might be able to update its kind to a known part. - void updateLibrariesWithThisPart() { - for (final maybeLibrary in _fsState._pathToFile.values) { - if (maybeLibrary.kind is LibraryFileStateKind) { - if (maybeLibrary.partedFiles.contains(this)) { - maybeLibrary._updatePartedFiles(); - maybeLibrary._libraryCycle?.invalidate(); - } - } - } - } + _fsState._libraryNameToFiles.remove(_kind); final libraryAugmentationDirective = unlinked2.libraryAugmentationDirective; + final libraryDirective = unlinked2.libraryDirective; final partOfNameDirective = unlinked2.partOfNameDirective; final partOfUriDirective = unlinked2.partOfUriDirective; if (libraryAugmentationDirective != null) { - // TODO(scheglov) This code does not have enough tests. - _kind = LibraryAugmentationUnknownFileStateKind( - file: this, - directive: libraryAugmentationDirective, + final uri = libraryAugmentationDirective.uri; + final uriFile = _fileForRelativeUri(uri).map( + (file) => file, + (_) => null, ); - _fileForRelativeUri(libraryAugmentationDirective.uri); - } else if (unlinked2.libraryDirective != 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) { - if (_kind is! PartKnownFileStateKind) { - _kind = PartUnknownNameFileStateKind( - file: this, - directive: partOfNameDirective, - ); - updateLibrariesWithThisPart(); - } + _kind = PartOfNameFileStateKind( + file: this, + directive: partOfNameDirective, + ); } else if (partOfUriDirective != null) { - if (_kind is! PartKnownFileStateKind) { - _kind = PartUnknownUriFileStateKind( + 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, ); - _fileForRelativeUri(partOfUriDirective.uri); - updateLibrariesWithThisPart(); } } else { _kind = LibraryFileStateKind( file: this, + name: null, ); } + + _fsState._libraryNameToFiles.add(_kind); + _invalidatesLibrariesOfThisPart(); } void _updatePartedFiles() { - // Disconnect all parts of this library. - for (final part in _partedFiles) { - if (part != null) { - final partKind = part.kind; - if (partKind is PartKnownFileStateKind) { - partKind._libraries.remove(this); - // If no libraries, switch to unknown. - if (partKind._libraries.isEmpty) { - final partOfNameDirective = part.unlinked2.partOfNameDirective; - final partOfUriDirective = part.unlinked2.partOfUriDirective; - if (partOfNameDirective != null) { - part._kind = PartUnknownNameFileStateKind( - file: part, - directive: partOfNameDirective, - ); - } else if (partOfUriDirective != null) { - part._kind = PartUnknownUriFileStateKind( - file: part, - directive: partOfUriDirective, - ); - } - } - } - } - } - _partedFiles = unlinked2.parts.map((uri) { return _fileForRelativeUri(uri).map( (part) { - if (part != null) { - part.referencingFiles.add(this); - // Either add this as a library, or switch from unknown. - final kind = part._kind; - if (kind is PartKnownFileStateKind) { - kind._libraries.add(this); - } else if (kind is PartUnknownNameFileStateKind || - kind is PartUnknownUriFileStateKind) { - part._kind = PartKnownFileStateKind( - file: part, - library: this, - ); - } - } + part?.referencingFiles.add(this); return part; }, (_) => null, @@ -673,6 +714,7 @@ class FileState { UnlinkedLibraryAugmentationDirective? libraryAugmentationDirective; UnlinkedPartOfNameDirective? partOfNameDirective; UnlinkedPartOfUriDirective? partOfUriDirective; + var augmentations = []; var exports = []; var imports = []; var parts = []; @@ -682,11 +724,19 @@ class FileState { 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 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; @@ -761,6 +811,7 @@ class FileState { } return UnlinkedUnit( apiSignature: Uint8List.fromList(computeUnlinkedApiSignature(unit)), + augmentations: augmentations, exports: exports, imports: imports, informativeBytes: writeUnitInformative(unit), @@ -875,6 +926,9 @@ class FileSystemState { /// Mapping from a path to the corresponding [FileState]. final Map _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> _subtypedNameToFiles = {}; @@ -1070,9 +1124,11 @@ class FileSystemState { _hasUriForPath.clear(); _pathToFile.clear(); _subtypedNameToFiles.clear(); + _libraryNameToFiles.clear(); } FileState _newFile(File resource, String path, Uri uri) { + // print('[_newFile][uri: $uri][path: $path]'); FileSource uriSource = FileSource(resource, uri); WorkspacePackage? workspacePackage = _workspace?.findPackageFor(path); FeatureSet featureSet = contextFeatureSet(path, uri, workspacePackage); @@ -1152,63 +1208,152 @@ class FileUriProperties { bool get isSrc => (_flags & _isSrc) != 0; } -class LibraryAugmentationKnownFileStateKind extends FileStateKind { - final FileState augmented; +class LibraryFileStateKind extends LibraryOrAugmentationFileKind { + /// The name of the library from the `library` directive. + /// Or `null` if no `library` directive. + final String? name; - LibraryAugmentationKnownFileStateKind({ - required super.file, - required this.augmented, - }); -} - -/// A library augmentation when the augmented target is unknown, e.g. -/// the provided URI cannot be resolved. -class LibraryAugmentationUnknownFileStateKind extends FileStateKind { - final UnlinkedLibraryAugmentationDirective directive; - - LibraryAugmentationUnknownFileStateKind({ - required super.file, - required this.directive, - }); -} - -class LibraryFileStateKind extends FileStateKind { LibraryFileStateKind({ required super.file, + required this.name, }); -} -class PartKnownFileStateKind extends FileStateKind { - final List _libraries = []; - - PartKnownFileStateKind({ - required super.file, - required FileState library, - }) { - _libraries.add(library); + bool hasPart(PartFileStateKind part) { + return file.partedFiles.contains(part.file); } - - FileState get library => _libraries.first; } -/// The file is a part, but its library is unknown. -/// We don't know the library with this name. -class PartUnknownNameFileStateKind extends FileStateKind { +abstract class LibraryOrAugmentationFileKind extends FileStateKind { + LibraryOrAugmentationFileKind({ + required super.file, + }); + + 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, + }); + + /// 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; - PartUnknownNameFileStateKind({ + PartOfNameFileStateKind({ required super.file, required this.directive, }); + + /// Libraries with the same name as in [directive]. + List 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 is a part, but its library is unknown. -/// When we don't understand the URI. -class PartUnknownUriFileStateKind extends FileStateKind { +/// The file has `part of URI` directive. +abstract class PartOfUriFileStateKind extends PartFileStateKind { final UnlinkedPartOfUriDirective directive; - PartUnknownUriFileStateKind({ + 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> _map = {}; + + List? 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); + } + } + } + } + } +} diff --git a/pkg/analyzer/lib/src/dart/analysis/unlinked_data.dart b/pkg/analyzer/lib/src/dart/analysis/unlinked_data.dart index e4b47734519..605461c70b8 100644 --- a/pkg/analyzer/lib/src/dart/analysis/unlinked_data.dart +++ b/pkg/analyzer/lib/src/dart/analysis/unlinked_data.dart @@ -90,6 +90,26 @@ class MacroClass { } } +class UnlinkedImportAugmentationDirective { + final String uri; + + UnlinkedImportAugmentationDirective({ + required this.uri, + }); + + factory UnlinkedImportAugmentationDirective.read( + SummaryDataReader reader, + ) { + return UnlinkedImportAugmentationDirective( + uri: reader.readStringUtf8(), + ); + } + + void write(BufferedSink sink) { + sink.writeStringUtf8(uri); + } +} + class UnlinkedLibraryAugmentationDirective { final String uri; final UnlinkedSourceRange uriRange; @@ -285,6 +305,9 @@ class UnlinkedUnit { /// TODO(scheglov) Do we need it? final Uint8List apiSignature; + /// `import augmentation` directives. + final List augmentations; + /// URIs of `export` directives. final List exports; @@ -317,6 +340,7 @@ class UnlinkedUnit { UnlinkedUnit({ required this.apiSignature, + required this.augmentations, required this.exports, required this.imports, required this.informativeBytes, @@ -332,6 +356,9 @@ class UnlinkedUnit { factory UnlinkedUnit.read(SummaryDataReader reader) { return UnlinkedUnit( apiSignature: reader.readUint8List(), + augmentations: reader.readTypedList( + () => UnlinkedImportAugmentationDirective.read(reader), + ), exports: reader.readTypedList( () => UnlinkedNamespaceDirective.read(reader), ), @@ -361,6 +388,9 @@ class UnlinkedUnit { void write(BufferedSink sink) { sink.writeUint8List(apiSignature); + sink.writeList(augmentations, (x) { + x.write(sink); + }); sink.writeList(exports, (x) { x.write(sink); }); diff --git a/pkg/analyzer/lib/src/dart/micro/library_graph.dart b/pkg/analyzer/lib/src/dart/micro/library_graph.dart index 0f722d7ba8f..f560b033a2a 100644 --- a/pkg/analyzer/lib/src/dart/micro/library_graph.dart +++ b/pkg/analyzer/lib/src/dart/micro/library_graph.dart @@ -904,6 +904,7 @@ class _FileStateUnlinked { UnlinkedLibraryAugmentationDirective? libraryAugmentationDirective; UnlinkedPartOfNameDirective? partOfNameDirective; UnlinkedPartOfUriDirective? partOfUriDirective; + var augmentations = []; var exports = []; var imports = []; var parts = []; @@ -912,11 +913,19 @@ class _FileStateUnlinked { 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 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; @@ -995,6 +1004,7 @@ class _FileStateUnlinked { var unlinkedUnit = UnlinkedUnit( apiSignature: computeUnlinkedApiSignature(unit), + augmentations: augmentations, exports: exports, imports: imports, informativeBytes: writeUnitInformative(unit), diff --git a/pkg/analyzer/test/generated/invalid_code_test.dart b/pkg/analyzer/test/generated/invalid_code_test.dart index 1e701bbf0c2..da2607ea9a0 100644 --- a/pkg/analyzer/test/generated/invalid_code_test.dart +++ b/pkg/analyzer/test/generated/invalid_code_test.dart @@ -2,6 +2,7 @@ // 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 'package:analyzer/dart/analysis/results.dart'; import 'package:analyzer/dart/element/nullability_suffix.dart'; import 'package:test_reflective_loader/test_reflective_loader.dart'; @@ -396,10 +397,13 @@ C c; } test_invalidPart_withPart() async { - await _assertCanBeAnalyzed(''' + newFile(testFilePath, ''' part of a; part 'test.dart'; '''); + final analysisSession = contextFor(testFile.path).currentSession; + final result = await analysisSession.getResolvedUnit(testFile.path); + result as PartWithoutLibraryResult; } test_issue_48688() async { diff --git a/pkg/analyzer/test/src/dart/analysis/driver_test.dart b/pkg/analyzer/test/src/dart/analysis/driver_test.dart index 5ceb724b14f..57476751b1e 100644 --- a/pkg/analyzer/test/src/dart/analysis/driver_test.dart +++ b/pkg/analyzer/test/src/dart/analysis/driver_test.dart @@ -2548,7 +2548,7 @@ class A {} expect(driver.knownFiles, contains(p)); } - test_part_getErrors_afterLibrary() async { + test_partOfName_getErrors_afterLibrary() async { var a = convertPath('/test/lib/a.dart'); var b = convertPath('/test/lib/b.dart'); var c = convertPath('/test/lib/c.dart'); @@ -2584,7 +2584,7 @@ var b = new B(); } } - test_part_getErrors_beforeLibrary() async { + test_partOfName_getErrors_beforeLibrary() async { var a = convertPath('/test/lib/a.dart'); var b = convertPath('/test/lib/b.dart'); var c = convertPath('/test/lib/c.dart'); @@ -2614,7 +2614,7 @@ var b = new B(); } } - test_part_getResult_afterLibrary() async { + test_partOfName_getResult_afterLibrary() async { var a = convertPath('/test/lib/a.dart'); var b = convertPath('/test/lib/b.dart'); var c = convertPath('/test/lib/c.dart'); @@ -2653,7 +2653,7 @@ var b = new B(); } } - test_part_getResult_beforeLibrary() async { + test_partOfName_getResult_beforeLibrary() async { var a = convertPath('/test/lib/a.dart'); var b = convertPath('/test/lib/b.dart'); var c = convertPath('/test/lib/c.dart'); @@ -2684,7 +2684,7 @@ var b = new B(); _assertTopLevelVarType(result.unit, 'b', 'B'); } - test_part_getResult_changePart_invalidatesLibraryCycle() async { + test_partOfName_getResult_changePart_invalidatesLibraryCycle() async { var a = convertPath('/test/lib/a.dart'); var b = convertPath('/test/lib/b.dart'); newFile(a, r''' @@ -2710,7 +2710,26 @@ Future f; expect(result.errors, isEmpty); } - test_part_getResult_noLibrary() async { + test_partOfName_getResult_hasLibrary_noPart() async { + final a = newFile('/test/lib/a.dart', r''' +library my.lib; +'''); + + final c = newFile('/test/lib/c.dart', r''' +part of my.lib; +class C {} +'''); + + // Discover the library. + driver.getFileSync(a.path); + + // There is no library which c.dart is a part of, so invalid result. + final result = await driver.getResult(c.path); + result as PartWithoutLibraryResult; + expect(result.path, c.path); + } + + test_partOfName_getResult_noLibrary() async { final c = newFile('/test/lib/c.dart', r''' part of a; class C {} @@ -2722,7 +2741,7 @@ class C {} expect(result.path, c.path); } - test_part_getUnitElement_afterLibrary() async { + test_partOfName_getUnitElement_afterLibrary() async { var a = convertPath('/test/lib/a.dart'); var b = convertPath('/test/lib/b.dart'); var c = convertPath('/test/lib/c.dart'); @@ -2762,7 +2781,7 @@ var b = new B(); } } - test_part_getUnitElement_beforeLibrary() async { + test_partOfName_getUnitElement_beforeLibrary() async { var a = convertPath('/test/lib/a.dart'); var b = convertPath('/test/lib/b.dart'); var c = convertPath('/test/lib/c.dart'); @@ -2799,7 +2818,7 @@ var b = new B(); } } - test_part_getUnitElement_noLibrary() async { + test_partOfName_getUnitElement_noLibrary() async { final c = newFile('/test/lib/c.dart', r''' part of a; '''); @@ -2810,7 +2829,7 @@ part of a; expect(result.path, c.path); } - test_part_results_afterLibrary() async { + test_partOfName_results_afterLibrary() async { var a = convertPath('/test/lib/a.dart'); var b = convertPath('/test/lib/b.dart'); var c = convertPath('/test/lib/c.dart'); @@ -2859,7 +2878,7 @@ var b = new B(); } } - test_part_results_beforeLibrary() async { + test_partOfName_results_beforeLibrary() async { var a = convertPath('/test/lib/a.dart'); var b = convertPath('/test/lib/b.dart'); var c = convertPath('/test/lib/c.dart'); @@ -2894,7 +2913,7 @@ var b = new B(); expect(result.errors, isEmpty); } - test_part_results_noLibrary() async { + test_partOfName_results_noLibrary() async { var c = convertPath('/test/lib/c.dart'); newFile(c, r''' part of a; @@ -2913,7 +2932,7 @@ var b = new B(); .lastWhere((result) => result.path == c); } - test_part_results_noLibrary_priority() async { + test_partOfName_results_noLibrary_priority() async { var c = newFile('/test/lib/c.dart', r''' part of a; class C {} @@ -2932,7 +2951,7 @@ var b = new B(); .lastWhere((result) => result.path == c.path); } - test_part_results_priority_beforeLibrary() async { + test_partOfName_results_priority_beforeLibrary() async { var a = convertPath('/test/lib/a.dart'); var b = convertPath('/test/lib/b.dart'); var c = convertPath('/test/lib/c.dart'); diff --git a/pkg/analyzer/test/src/dart/analysis/file_state_test.dart b/pkg/analyzer/test/src/dart/analysis/file_state_test.dart index 0070cbe3bb0..c0b0ac1e838 100644 --- a/pkg/analyzer/test/src/dart/analysis/file_state_test.dart +++ b/pkg/analyzer/test/src/dart/analysis/file_state_test.dart @@ -129,6 +129,416 @@ class FileSystemState_PubPackageTest extends PubPackageResolutionTest { return driverFor(file.path).fsState; } + test_newFile_augmentation_augmentationExists_hasImport() async { + final a = newFile('$testPackageLibPath/a.dart', r''' +import augment 'b.dart'; +'''); + + final b = newFile('$testPackageLibPath/b.dart', r''' +library augment 'a.dart'; +import augment 'c.dart'; +'''); + + final c = newFile('$testPackageLibPath/c.dart', r''' +library augment 'b.dart'; +'''); + + final cState = fileStateFor(c); + // We have not asked for `b.dart` yet, but it was found using URI. + cState.assertKind((kind) { + kind as AugmentationKnownFileStateKind; + expect(kind.uriFile.path, b.path); + expect(kind.augmented?.path, b.path); + }); + + final bState = fileStateFor(b); + _assertAugmentationFiles(bState, [c]); + // We have not asked for `a.dart` yet, but it was found using URI. + bState.assertKind((kind) { + kind as AugmentationKnownFileStateKind; + expect(kind.uriFile.path, a.path); + expect(kind.augmented?.path, a.path); + }); + // Check `c.dart` again, now using the `b.dart` state. + cState.assertKind((kind) { + kind as AugmentationKnownFileStateKind; + expect(kind.uriFile, same(bState)); + expect(kind.augmented, same(bState)); + }); + + final aState = fileStateFor(a); + _assertAugmentationFiles(aState, [b]); + // Check `b.dart` again, now using the `a.dart` state. + bState.assertKind((kind) { + kind as AugmentationKnownFileStateKind; + expect(kind.uriFile, same(aState)); + expect(kind.augmented, same(aState)); + }); + } + + test_newFile_augmentation_augmentationExists_hasImport_disconnected() async { + final a = getFile('$testPackageLibPath/a.dart'); + + final b = newFile('$testPackageLibPath/b.dart', r''' +library augment 'a.dart'; +import augment 'c.dart'; +'''); + + final c = newFile('$testPackageLibPath/c.dart', r''' +library augment 'b.dart'; +'''); + + final cState = fileStateFor(c); + // We have not asked for `b.dart` yet, but it was found using URI. + cState.assertKind((kind) { + kind as AugmentationKnownFileStateKind; + expect(kind.uriFile.path, b.path); + expect(kind.augmented?.path, b.path); + }); + + final bState = fileStateFor(b); + _assertAugmentationFiles(bState, [c]); + // We have not asked for `a.dart` yet, but it was found using URI. + bState.assertKind((kind) { + kind as AugmentationKnownFileStateKind; + expect(kind.uriFile.path, a.path); + // The file `a.dart` does not exist, so no import, so `null`. + expect(kind.augmented, isNull); + }); + // Check `c.dart` again, now using the `b.dart` state. + cState.assertKind((kind) { + kind as AugmentationKnownFileStateKind; + expect(kind.uriFile, same(bState)); + expect(kind.augmented, same(bState)); + }); + + // The file `a.dart` does not exist. + final aState = fileStateFor(a); + expect(aState.exists, isFalse); + _assertAugmentationFiles(aState, []); + // Check `b.dart` again, now using the `a.dart` state. + bState.assertKind((kind) { + kind as AugmentationKnownFileStateKind; + // The URI can be resolved, it points at `a.dart` file. + expect(kind.uriFile, same(aState)); + // The file `a.dart` does not exist, so no import, so `null`. + expect(kind.augmented, isNull); + }); + } + + test_newFile_augmentation_augmentationExists_noImport() async { + final a = newFile('$testPackageLibPath/a.dart', r''' +import augment 'b.dart'; +'''); + + final b = newFile('$testPackageLibPath/b.dart', r''' +library augment 'a.dart'; +'''); + + final c = newFile('$testPackageLibPath/c.dart', r''' +library augment 'b.dart'; +'''); + + // We found `b.dart` from the augmentation file `c.dart`. + final cState = fileStateFor(c); + cState.assertKind((kind) { + kind as AugmentationKnownFileStateKind; + expect(kind.uriFile.path, b.path); + // `b.dart` does not import `c.dart` as an augmentation. + expect(kind.augmented, isNull); + }); + + // Reading `a.dart` does not change anything. + final aState = fileStateFor(a); + _assertAugmentationFiles(aState, [b]); + aState.assertKind((kind) { + kind as LibraryFileStateKind; + expect(kind.name, isNull); + }); + + // `b.dart` does not import `c.dart` as an augmentation. + final bState = fileStateFor(b); + _assertAugmentationFiles(bState, []); + bState.assertKind((kind) { + kind as AugmentationKnownFileStateKind; + expect(kind.uriFile, same(aState)); + expect(kind.augmented, same(aState)); + }); + + // Check `c.dart` again, now using the `b.dart` state. + cState.assertKind((kind) { + kind as AugmentationKnownFileStateKind; + expect(kind.uriFile, same(bState)); + expect(kind.augmented, isNull); + }); + } + + test_newFile_augmentation_cycle1_augmentSelf() async { + final a = newFile('$testPackageLibPath/a.dart', r''' +import augment 'b.dart'; +'''); + + final b = newFile('$testPackageLibPath/b.dart', r''' +library augment 'b.dart'; +import augment 'b.dart'; +'''); + + final aState = fileStateFor(a); + _assertAugmentationFiles(aState, [b]); + + // We can construct a cycle using augmentations. + final bState = fileStateFor(b); + _assertAugmentationFiles(bState, [b]); + bState.assertKind((bKind) { + bKind as AugmentationKnownFileStateKind; + expect(bKind.uriFile, same(bState)); + expect(bKind.augmented, same(bState)); + }); + + // The cycle does not prevent building of the library cycle. + aState.assertKind((kind) { + kind as LibraryFileStateKind; + expect(kind.name, isNull); + // TODO(scheglov) ask for the cycle signature + }); + } + + test_newFile_augmentation_cycle2() async { + final a = newFile('$testPackageLibPath/a.dart', r''' +import augment 'b.dart'; +'''); + + final b = newFile('$testPackageLibPath/b.dart', r''' +library augment 'a.dart'; +import augment 'c.dart'; +'''); + + final c = newFile('$testPackageLibPath/c.dart', r''' +library augment 'b.dart'; +import augment 'b.dart'; +'''); + + final aState = fileStateFor(a); + _assertAugmentationFiles(aState, [b]); + + final bState = fileStateFor(b); + _assertAugmentationFiles(bState, [c]); + + final cState = fileStateFor(c); + _assertAugmentationFiles(cState, [b]); + + // We can construct a cycle using augmentations. + bState.assertKind((kind) { + kind as AugmentationKnownFileStateKind; + expect(kind.uriFile, same(aState)); + expect(kind.augmented, same(aState)); + }); + cState.assertKind((kind) { + kind as AugmentationKnownFileStateKind; + expect(kind.uriFile, same(bState)); + expect(kind.augmented, same(bState)); + }); + + // The cycle does not prevent building of the library cycle. + aState.assertKind((kind) { + kind as LibraryFileStateKind; + expect(kind.name, isNull); + // TODO(scheglov) ask for the cycle signature + }); + } + + test_newFile_augmentation_invalid() async { + final a = newFile('$testPackageLibPath/a.dart', r''' +library augment 'da:'; +'''); + + // The URI is invalid, so there is no way to discover the target. + final aState = fileStateFor(a); + aState.assertKind((kind) { + kind as AugmentationUnknownFileStateKind; + expect(kind.directive.uri, 'da:'); + }); + } + + test_newFile_augmentation_libraryExists_hasImport() async { + final a = newFile('$testPackageLibPath/a.dart', r''' +import augment 'b.dart'; +'''); + + final b = newFile('$testPackageLibPath/b.dart', r''' +library augment 'a.dart'; +'''); + + final bState = fileStateFor(b); + // We have not asked for `a.dart` yet, but it was found using URI. + bState.assertKind((kind) { + kind as AugmentationKnownFileStateKind; + expect(kind.uriFile.path, a.path); + expect(kind.augmented?.path, a.path); + }); + + final aState = fileStateFor(a); + _assertAugmentationFiles(aState, [b]); + // Check `b.dart` again, now using the `a.dart` state. + bState.assertKind((kind) { + kind as AugmentationKnownFileStateKind; + expect(kind.uriFile, same(aState)); + expect(kind.augmented, same(aState)); + }); + } + + test_newFile_augmentation_libraryExists_noImport() async { + final a = newFile('$testPackageLibPath/a.dart', ''); + + final b = newFile('$testPackageLibPath/b.dart', r''' +library augment 'a.dart'; +'''); + + final aState = fileStateFor(a); + _assertAugmentationFiles(aState, []); + + final bState = fileStateFor(b); + // We can find `a.dart` using the URI. + bState.assertKind((kind) { + kind as AugmentationKnownFileStateKind; + expect(kind.uriFile, same(aState)); + // But `a.dart` does not import `b.dart`. + expect(kind.augmented, isNull); + }); + + // Refreshing `a.dart` does not change anything. + aState.refresh(); + _assertAugmentationFiles(aState, []); + bState.assertKind((kind) { + kind as AugmentationKnownFileStateKind; + expect(kind.uriFile, same(aState)); + expect(kind.augmented, isNull); + }); + } + + test_newFile_augmentation_targetNotExists() async { + final a = getFile('$testPackageLibPath/a.dart'); + + final b = newFile('$testPackageLibPath/b.dart', r''' +library augment 'a.dart'; +'''); + + final bState = fileStateFor(b); + // We can find `a.dart` from `b.dart` using the URI. + bState.assertKind((kind) { + kind as AugmentationKnownFileStateKind; + expect(kind.uriFile.path, a.path); + // The file `a.dart` does not exist, so no import. + expect(kind.augmented, isNull); + }); + + // We can get `a.dart`, but it does not exist. + final aState = fileStateFor(a); + expect(aState.exists, isFalse); + _assertAugmentationFiles(aState, []); + bState.assertKind((kind) { + kind as AugmentationKnownFileStateKind; + expect(kind.uriFile, same(aState)); + // The file `a.dart` does not exist, so no import. + expect(kind.augmented, isNull); + }); + } + + test_newFile_augmentation_twoLibraries() async { + final a = newFile('$testPackageLibPath/a.dart', r''' +import augment 'c.dart'; +'''); + + final b = newFile('$testPackageLibPath/b.dart', r''' +import augment 'c.dart'; +'''); + + final c = newFile('$testPackageLibPath/c.dart', r''' +library augment 'a.dart'; +'''); + + final aState = fileStateFor(a); + _assertAugmentationFiles(aState, [c]); + + // We use the URI from `library augment` to find the augmentation target. + final cState = fileStateFor(c); + cState.assertKind((kind) { + kind as AugmentationKnownFileStateKind; + expect(kind.uriFile, same(aState)); + expect(kind.augmented, same(aState)); + }); + + // Reading `b.dart` does not update the augmentation. + final bState = fileStateFor(b); + _assertAugmentationFiles(bState, [c]); + cState.assertKind((kind) { + kind as AugmentationKnownFileStateKind; + expect(kind.uriFile, same(aState)); + expect(kind.augmented, same(aState)); + }); + + // Refreshing `a.dart` does not update the augmentation. + aState.refresh(); + cState.assertKind((kind) { + kind as AugmentationKnownFileStateKind; + expect(kind.uriFile, same(aState)); + expect(kind.augmented, same(aState)); + }); + + // Refreshing `b.dart` does not update the augmentation. + bState.refresh(); + cState.assertKind((kind) { + kind as AugmentationKnownFileStateKind; + expect(kind.uriFile, same(aState)); + expect(kind.augmented, same(aState)); + }); + + // Exclude from `a.dart`, the URI still points at `a.dart`. + // But `c.dart` is not a valid augmentation anymore. + newFile(a.path, ''); + aState.refresh(); + _assertAugmentationFiles(aState, []); + cState.assertKind((kind) { + kind as AugmentationKnownFileStateKind; + expect(kind.uriFile, same(aState)); + expect(kind.augmented, isNull); + }); + + // Exclude from `b.dart`, still point at `a.dart`, still not valid. + newFile(b.path, ''); + bState.refresh(); + _assertAugmentationFiles(bState, []); + cState.assertKind((kind) { + kind as AugmentationKnownFileStateKind; + expect(kind.uriFile, same(aState)); + expect(kind.augmented, isNull); + }); + + // Include into `b.dart`, still point at `a.dart`, still not valid. + newFile(b.path, r''' +import augment 'c.dart'; +'''); + bState.refresh(); + _assertAugmentationFiles(bState, [c]); + cState.assertKind((kind) { + kind as AugmentationKnownFileStateKind; + expect(kind.uriFile, same(aState)); + expect(kind.augmented, isNull); + }); + + // Include into `a.dart`, restore to `a.dart` as the target. + newFile(a.path, r''' +import augment 'c.dart'; +'''); + aState.refresh(); + _assertAugmentationFiles(aState, [c]); + cState.assertKind((kind) { + kind as AugmentationKnownFileStateKind; + expect(kind.uriFile, same(aState)); + expect(kind.augmented, same(aState)); + }); + } + test_newFile_library_includePart_withoutPartOf() async { final a = newFile('$testPackageLibPath/a.dart', r''' part 'b.dart'; @@ -141,6 +551,7 @@ part 'b.dart'; final aState = fileStateFor(a); aState.assertKind((kind) { kind as LibraryFileStateKind; + expect(kind.name, isNull); }); // Library `a.dart` includes `b.dart` as a part. @@ -150,6 +561,7 @@ part 'b.dart'; final bState = fileStateFor(b); bState.assertKind((kind) { kind as LibraryFileStateKind; + expect(kind.name, isNull); }); // Refreshing the library does not change this. @@ -157,36 +569,7 @@ part 'b.dart'; _assertPartedFiles(aState, [b]); bState.assertKind((kind) { kind as LibraryFileStateKind; - }); - } - - test_newFile_libraryAugmentation_invalid() async { - final a = newFile('$testPackageLibPath/a.dart', r''' -library augment 'da:'; -'''); - - final aState = fileStateFor(a); - aState.assertKind((kind) { - kind as LibraryAugmentationUnknownFileStateKind; - }); - } - - /// TODO(scheglov) implement - @FailingTest(reason: 'No import augment directive') - test_newFile_libraryAugmentation_valid() async { - final a = newFile('$testPackageLibPath/a.dart', r''' -import augment 'b.dart'; -'''); - - final b = newFile('$testPackageLibPath/a.dart', r''' -library augment 'a.dart'; -'''); - - final bState = fileStateFor(b); - bState.assertKind((kind) { - kind as LibraryAugmentationKnownFileStateKind; - final aState = fileStateFor(a); - expect(kind.augmented, same(aState)); + expect(kind.name, isNull); }); } @@ -198,6 +581,7 @@ library my; final aState = fileStateFor(a); aState.assertKind((kind) { kind as LibraryFileStateKind; + expect(kind.name, 'my'); }); } @@ -207,6 +591,7 @@ library my; final aState = fileStateFor(a); aState.assertKind((kind) { kind as LibraryFileStateKind; + expect(kind.name, isNull); }); } @@ -225,20 +610,24 @@ part of my.lib; // We don't know the library initially. // Even though the library file exists, we have not seen it yet. bState.assertKind((kind) { - kind as PartUnknownNameFileStateKind; + kind as PartOfNameFileStateKind; expect(kind.directive.name, 'my.lib'); + expect(kind.libraries, isEmpty); + expect(kind.library, isNull); }); // Read the library file. final aState = fileStateFor(a); aState.assertKind((kind) { kind as LibraryFileStateKind; + expect(kind.name, 'my.lib'); }); _assertPartedFiles(aState, [b]); // Now the part knows its library. bState.assertKind((kind) { - kind as PartKnownFileStateKind; + kind as PartOfNameFileStateKind; + kind.assertLibraries([aState]); expect(kind.library, same(aState)); }); } @@ -256,117 +645,184 @@ part of other.lib; final bState = fileStateFor(b); // We don't know the library initially. - // Even though the library file exists, we have not seen it yet. bState.assertKind((kind) { - kind as PartUnknownNameFileStateKind; + kind as PartOfNameFileStateKind; expect(kind.directive.name, 'other.lib'); + kind.assertLibraries([]); + expect(kind.library, isNull); }); // Read the library file. final aState = fileStateFor(a); aState.assertKind((kind) { kind as LibraryFileStateKind; + expect(kind.name, 'my.lib'); }); _assertPartedFiles(aState, [b]); - // Now the part knows its library. - // The name of the library in the part file does not matter. - // What counts is that the library includes it. + // We still don't know the library, because the part wants `other.lib`, + // but `a.dart` that includes `b.dart` has the name `my.lib`. bState.assertKind((kind) { - kind as PartKnownFileStateKind; - expect(kind.library, same(aState)); + kind as PartOfNameFileStateKind; + kind.assertLibraries([]); + expect(kind.library, isNull); }); } test_newFile_partOfName_twoLibraries() async { final a = newFile('$testPackageLibPath/a.dart', r''' +library my.lib; part 'c.dart'; '''); final b = newFile('$testPackageLibPath/b.dart', r''' +library my.lib; part 'c.dart'; '''); final c = newFile('$testPackageLibPath/c.dart', r''' -part of 'doesNotMatter.dart'; +part of my.lib; '''); final aState = fileStateFor(a); + aState.assertKind((kind) { + kind as LibraryFileStateKind; + expect(kind.name, 'my.lib'); + }); _assertPartedFiles(aState, [c]); // We set the library while reading `a.dart` file. final cState = fileStateFor(c); cState.assertKind((kind) { - kind as PartKnownFileStateKind; + kind as PartOfNameFileStateKind; + kind.assertLibraries([aState]); expect(kind.library, aState); }); // Reading `b.dart` does not update the part. final bState = fileStateFor(b); + bState.assertKind((kind) { + kind as LibraryFileStateKind; + expect(kind.name, 'my.lib'); + }); _assertPartedFiles(bState, [c]); cState.assertKind((kind) { - kind as PartKnownFileStateKind; + kind as PartOfNameFileStateKind; + kind.assertLibraries([aState, bState]); expect(kind.library, aState); }); // Refreshing `b.dart` does not update the part. bState.refresh(); cState.assertKind((kind) { - kind as PartKnownFileStateKind; + kind as PartOfNameFileStateKind; + kind.assertLibraries([aState, bState]); expect(kind.library, aState); }); - // Exclude the part from `a.dart` - switches to `b.dart` as its library. + // Refreshing `a.dart` does not update the part. + aState.refresh(); + cState.assertKind((kind) { + kind as PartOfNameFileStateKind; + kind.assertLibraries([aState, bState]); + expect(kind.library, aState); + }); + + // Exclude the part from `a.dart`, switch to `b.dart` instead. newFile(a.path, ''); aState.refresh(); cState.assertKind((kind) { - kind as PartKnownFileStateKind; + kind as PartOfNameFileStateKind; + kind.assertLibraries([bState]); expect(kind.library, bState); }); - // Exclude the part from `b.dart` as well - switches to unknown. + // Exclude the part from `b.dart`, no library. newFile(b.path, ''); bState.refresh(); cState.assertKind((kind) { - kind as PartUnknownUriFileStateKind; + kind as PartOfNameFileStateKind; + kind.assertLibraries([]); + expect(kind.library, isNull); + }); + + // Include into `b.dart`, use it as the library. + newFile(b.path, r''' +library my.lib; +part 'c.dart'; +'''); + bState.refresh(); + cState.assertKind((kind) { + kind as PartOfNameFileStateKind; + kind.assertLibraries([bState]); + expect(kind.library, bState); + }); + + // Include into `a.dart`, switch to `a.dart`. + newFile(a.path, r''' +library my.lib; +part 'c.dart'; +'''); + aState.refresh(); + cState.assertKind((kind) { + kind as PartOfNameFileStateKind; + kind.assertLibraries([aState, bState]); + expect(kind.library, aState); }); } test_newFile_partOfUri_doesNotExist() async { + final a = getFile('$testPackageLibPath/a.dart'); + final b = newFile('$testPackageLibPath/b.dart', r''' part of 'a.dart'; '''); final bState = fileStateFor(b); - // If the library does not part the file, it does not matter what the - // part file says - the part file will not be analyzed during the library - // analysis. + // The URI in `part of URI` tells us which library to use. + // However it does not exist, so it does not include the file, so the + // part file will not be analyzed during the library analysis. bState.assertKind((kind) { - kind as PartUnknownUriFileStateKind; + kind as PartOfUriKnownFileStateKind; + expect(kind.uriFile.path, a.path); + expect(kind.library, isNull); }); - // Create the library that includes the part file. - final a = newFile('$testPackageLibPath/a.dart', r''' + final aState = fileStateFor(a); + expect(aState.exists, isFalse); + _assertPartedFiles(aState, []); + aState.assertKind((kind) { + kind as LibraryFileStateKind; + }); + bState.assertKind((kind) { + kind as PartOfUriKnownFileStateKind; + expect(kind.uriFile, same(aState)); + expect(kind.library, isNull); + }); + + // Create `a.dart` that includes the part file. + newFile(a.path, r''' part 'b.dart'; '''); // The library file has already been read because of `part of uri`. // So, we explicitly refresh it. - final aState = fileStateFor(a); aState.refresh(); _assertPartedFiles(aState, [b]); // Now the part file knows its library. bState.assertKind((kind) { - kind as PartKnownFileStateKind; + kind as PartOfUriKnownFileStateKind; + expect(kind.uriFile, same(aState)); expect(kind.library, same(aState)); }); // Refreshing the part file does not break the kind. bState.refresh(); bState.assertKind((kind) { - kind as PartKnownFileStateKind; + kind as PartOfUriKnownFileStateKind; + expect(kind.uriFile, same(aState)); expect(kind.library, same(aState)); }); } @@ -381,38 +837,51 @@ part of 'a.dart'; '''); final bState = fileStateFor(b); + // We have not read the library file explicitly yet. + // But it was read because of the `part of` directive. bState.assertKind((kind) { - kind as PartKnownFileStateKind; - // We have not read the library file explicitly yet. - // But it was read because of the `part of` directive. - final aState = fileStateFor(a); - _assertPartedFiles(aState, [b]); + kind as PartOfUriKnownFileStateKind; + expect(kind.uriFile.path, a.path); + expect(kind.library?.path, a.path); + }); + + final aState = fileStateFor(a); + aState.assertKind((kind) { + kind as LibraryFileStateKind; + }); + _assertPartedFiles(aState, [b]); + bState.assertKind((kind) { + kind as PartOfUriKnownFileStateKind; + expect(kind.uriFile, same(aState)); expect(kind.library, same(aState)); }); // Refreshing the part file does not break the kind. bState.refresh(); bState.assertKind((kind) { - kind as PartKnownFileStateKind; - final aState = fileStateFor(a); + kind as PartOfUriKnownFileStateKind; + expect(kind.uriFile, same(aState)); expect(kind.library, same(aState)); }); } test_newFile_partOfUri_exists_noPart() async { - newFile('$testPackageLibPath/a.dart', ''); + final a = newFile('$testPackageLibPath/a.dart', ''); final b = newFile('$testPackageLibPath/b.dart', r''' part of 'a.dart'; '''); + final aState = fileStateFor(a); final bState = fileStateFor(b); - // If the library does not part the file, it does not matter what the - // part file says - the part file will not be analyzed during the library - // analysis. + // The URI in `part of URI` tells us which library to use. + // However `a.dart` does not include `b.dart` as a part, so `b.dart` will + // not be analyzed during the library analysis. bState.assertKind((kind) { - kind as PartUnknownUriFileStateKind; + kind as PartOfUriKnownFileStateKind; + expect(kind.uriFile, same(aState)); + expect(kind.library, isNull); }); } @@ -425,19 +894,265 @@ part of 'da:'; // The URI is invalid, so there is no way to discover the library. bState.assertKind((kind) { - kind as PartUnknownUriFileStateKind; + kind as PartOfUriUnknownFileStateKind; expect(kind.directive.uri, 'da:'); }); - // But reading a library that includes this part will update it. + // Reading a library that includes this part does not change the fact + // that the URI in the `part of URI` in `b.dart` cannot be resolved. final a = newFile('$testPackageLibPath/a.dart', r''' part 'b.dart'; '''); final aState = fileStateFor(a); + aState.assertKind((kind) { + kind as LibraryFileStateKind; + }); _assertPartedFiles(aState, [b]); bState.assertKind((kind) { - kind as PartKnownFileStateKind; + kind as PartOfUriUnknownFileStateKind; + expect(kind.directive.uri, 'da:'); + }); + } + + test_newFile_partOfUri_twoLibraries() async { + final a = newFile('$testPackageLibPath/a.dart', r''' +part 'c.dart'; +'''); + + final b = newFile('$testPackageLibPath/b.dart', r''' +part 'c.dart'; +'''); + + final c = newFile('$testPackageLibPath/c.dart', r''' +part of 'a.dart'; +'''); + + final aState = fileStateFor(a); + _assertPartedFiles(aState, [c]); + + // We set the library while reading `a.dart` file. + final cState = fileStateFor(c); + cState.assertKind((kind) { + kind as PartOfUriKnownFileStateKind; + expect(kind.library, aState); + }); + + // Reading `b.dart` does not update the part. + final bState = fileStateFor(b); + _assertPartedFiles(bState, [c]); + cState.assertKind((kind) { + kind as PartOfUriKnownFileStateKind; + expect(kind.library, aState); + }); + + // Refreshing `b.dart` does not update the part. + bState.refresh(); + cState.assertKind((kind) { + kind as PartOfUriKnownFileStateKind; + expect(kind.library, aState); + }); + + // Refreshing `a.dart` does not update the part. + aState.refresh(); + cState.assertKind((kind) { + kind as PartOfUriKnownFileStateKind; + expect(kind.library, aState); + }); + + // Exclude the part from `a.dart`, but the URI in `part of` still resolves + // to `a.dart`, so no changes. + newFile(a.path, ''); + aState.refresh(); + cState.assertKind((kind) { + kind as PartOfUriKnownFileStateKind; + expect(kind.uriFile, same(aState)); + expect(kind.library, isNull); + }); + + // Exclude the part from `b.dart`, no changes. + newFile(b.path, ''); + bState.refresh(); + cState.assertKind((kind) { + kind as PartOfUriKnownFileStateKind; + expect(kind.uriFile, same(aState)); + expect(kind.library, isNull); + }); + + // Include into `b.dart`, no changes. + newFile(b.path, r''' +part 'c.dart'; +'''); + bState.refresh(); + cState.assertKind((kind) { + kind as PartOfUriKnownFileStateKind; + expect(kind.uriFile, same(aState)); + expect(kind.library, isNull); + }); + + // Include into `a.dart`, no changes. + newFile(a.path, r''' +part 'c.dart'; +'''); + aState.refresh(); + cState.assertKind((kind) { + kind as PartOfUriKnownFileStateKind; + expect(kind.uriFile, same(aState)); + expect(kind.library, aState); + }); + } + + test_refresh_augmentation_to_library() async { + final a = newFile('$testPackageLibPath/a.dart', r''' +import augment 'b.dart'; +'''); + + final b = newFile('$testPackageLibPath/b.dart', r''' +library augment 'a.dart'; +'''); + + final aState = fileStateFor(a); + _assertAugmentationFiles(aState, [b]); + + final bState = fileStateFor(b); + bState.assertKind((kind) { + kind as AugmentationKnownFileStateKind; + expect(kind.uriFile, same(aState)); + expect(kind.augmented, same(aState)); + }); + + // Make it a library. + newFile(b.path, ''); + + // Not an augmentation anymore, but a library. + bState.refresh(); + bState.assertKind((kind) { + kind as LibraryFileStateKind; + expect(kind.name, isNull); + }); + + // But `a.dart` still uses `b.dart` as an augmentation. + _assertAugmentationFiles(aState, [b]); + + // ...even if we attempt to refresh. + aState.refresh(); + _assertAugmentationFiles(aState, [b]); + } + + test_refresh_augmentation_to_partOfName() async { + final a = newFile('$testPackageLibPath/a.dart', r''' +library my.lib; +import augment 'b.dart'; +'''); + + final b = newFile('$testPackageLibPath/b.dart', r''' +library augment 'a.dart'; +'''); + + final aState = fileStateFor(a); + _assertAugmentationFiles(aState, [b]); + + final bState = fileStateFor(b); + bState.assertKind((kind) { + kind as AugmentationKnownFileStateKind; + expect(kind.uriFile, same(aState)); + expect(kind.augmented, same(aState)); + }); + + // Make it a part. + newFile(b.path, r''' +part of my.lib; +'''); + + // Not an augmentation anymore, but a part. + // This part can find the referenced library by name `my.lib`. + // But the library does not include this part, so no library. + bState.refresh(); + bState.assertKind((kind) { + kind as PartOfNameFileStateKind; + kind.assertLibraries([aState]); + expect(kind.library, isNull); + }); + + // But `a.dart` still uses `b.dart` as an augmentation. + _assertAugmentationFiles(aState, [b]); + _assertPartedFiles(aState, []); + + // ...even if we attempt to refresh. + aState.refresh(); + _assertAugmentationFiles(aState, [b]); + _assertPartedFiles(aState, []); + + // Now include `b.dart` into `a.dart` as a part. + newFile(a.path, r''' +library my.lib; +part 'b.dart'; +'''); + aState.refresh(); + + // ...not an augmentation, but a known part. + _assertAugmentationFiles(aState, []); + _assertPartedFiles(aState, [b]); + bState.assertKind((kind) { + kind as PartOfNameFileStateKind; + kind.assertLibraries([aState]); + expect(kind.library, same(aState)); + }); + } + + test_refresh_augmentation_to_partOfUri() async { + final a = newFile('$testPackageLibPath/a.dart', r''' +import augment 'b.dart'; +'''); + + final b = newFile('$testPackageLibPath/b.dart', r''' +library augment 'a.dart'; +'''); + + final aState = fileStateFor(a); + _assertAugmentationFiles(aState, [b]); + + final bState = fileStateFor(b); + bState.assertKind((kind) { + kind as AugmentationKnownFileStateKind; + expect(kind.uriFile, same(aState)); + expect(kind.augmented, same(aState)); + }); + + // Make it a part. + newFile(b.path, r''' +part of 'a.dart'; +'''); + + // Not an augmentation anymore, but a part. + bState.refresh(); + bState.assertKind((kind) { + kind as PartOfUriKnownFileStateKind; + expect(kind.uriFile, same(aState)); + expect(kind.library, isNull); + }); + + // But `a.dart` still uses `b.dart` as an augmentation. + _assertAugmentationFiles(aState, [b]); + _assertPartedFiles(aState, []); + + // ...even if we attempt to refresh. + aState.refresh(); + _assertAugmentationFiles(aState, [b]); + _assertPartedFiles(aState, []); + + // Now include `b.dart` into `a.dart` as a part. + newFile(a.path, r''' +part 'b.dart'; +'''); + aState.refresh(); + + // ...not an augmentation, but a known part. + _assertAugmentationFiles(aState, []); + _assertPartedFiles(aState, [b]); + bState.assertKind((kind) { + kind as PartOfUriKnownFileStateKind; + expect(kind.uriFile, same(aState)); expect(kind.library, same(aState)); }); } @@ -460,6 +1175,7 @@ part 'b.dart'; final cState = fileStateFor(c); cState.assertKind((kind) { kind as LibraryFileStateKind; + expect(kind.name, 'my'); }); _assertPartedFiles(cState, [a, b]); @@ -468,12 +1184,14 @@ part 'b.dart'; // Both part files know the library. aState.assertKind((kind) { - kind as PartKnownFileStateKind; - expect(kind.library, cState); + kind as PartOfNameFileStateKind; + kind.assertLibraries([cState]); + expect(kind.library, same(cState)); }); bState.assertKind((kind) { - kind as PartKnownFileStateKind; - expect(kind.library, cState); + kind as PartOfNameFileStateKind; + kind.assertLibraries([cState]); + expect(kind.library, same(cState)); }); newFile(c.path, r''' @@ -485,24 +1203,27 @@ part 'b.dart'; cState.refresh(); _assertPartedFiles(cState, [b]); - // So, the `a.dart` does not know its library anymore. - // But `b.dart` still knows it. + // The library does not include `a.dart` as a part anymore. + // The part `b.dart` is still connected. aState.assertKind((kind) { - kind as PartUnknownNameFileStateKind; + kind as PartOfNameFileStateKind; + kind.assertLibraries([cState]); + expect(kind.library, isNull); }); bState.assertKind((kind) { - kind as PartKnownFileStateKind; - expect(kind.library, cState); + kind as PartOfNameFileStateKind; + kind.assertLibraries([cState]); + expect(kind.library, same(cState)); }); } test_refresh_library_removePart_partOfUri() async { final a = newFile('$testPackageLibPath/a.dart', r''' -part of 'test.dart'; +part of 'c.dart'; '''); final b = newFile('$testPackageLibPath/b.dart', r''' -part of 'test.dart'; +part of 'c.dart'; '''); final c = newFile('$testPackageLibPath/c.dart', r''' @@ -514,6 +1235,7 @@ part 'b.dart'; final cState = fileStateFor(c); cState.assertKind((kind) { kind as LibraryFileStateKind; + expect(kind.name, 'my'); }); _assertPartedFiles(cState, [a, b]); @@ -522,11 +1244,13 @@ part 'b.dart'; // Both part files know the library. aState.assertKind((kind) { - kind as PartKnownFileStateKind; + kind as PartOfUriKnownFileStateKind; + expect(kind.uriFile, cState); expect(kind.library, cState); }); bState.assertKind((kind) { - kind as PartKnownFileStateKind; + kind as PartOfUriKnownFileStateKind; + expect(kind.uriFile, cState); expect(kind.library, cState); }); @@ -539,19 +1263,63 @@ part 'b.dart'; cState.refresh(); _assertPartedFiles(cState, [b]); - // So, the `a.dart` does not know its library anymore. - // But `b.dart` still knows it. + // But the URIs in the `part of URI` are still the same. + // So, both parts are still linked to the library. aState.assertKind((kind) { - kind as PartUnknownUriFileStateKind; + kind as PartOfUriKnownFileStateKind; + expect(kind.uriFile, same(cState)); + expect(kind.library, isNull); }); bState.assertKind((kind) { - kind as PartKnownFileStateKind; - expect(kind.library, cState); + kind as PartOfUriKnownFileStateKind; + expect(kind.uriFile, cState); + expect(kind.library, same(cState)); }); } + test_refresh_library_to_augmentation() async { + final a = newFile('$testPackageLibPath/a.dart', r''' +import augment 'b.dart'; +'''); + + final b = newFile('$testPackageLibPath/b.dart', r''' +library b; +'''); + + final aState = fileStateFor(a); + _assertAugmentationFiles(aState, [b]); + + // TODO(scheglov) Restore. + // final aCycle_1 = aState.libraryCycle; + + final bState = fileStateFor(b); + bState.assertKind((kind) { + kind as LibraryFileStateKind; + expect(kind.name, 'b'); + }); + + newFile(b.path, r''' +library augment 'a.dart'; +'''); + + // We will discover the target by URI. + bState.refresh(); + bState.assertKind((kind) { + kind as AugmentationKnownFileStateKind; + expect(kind.uriFile, aState); + expect(kind.augmented, same(aState)); + }); + + // The file `b.dart` was something else, but now it is a known augmentation. + // This affects libraries that include it. + // TODO(scheglov) Restore. + // final aCycle_2 = aState.libraryCycle; + // expect(aCycle_2.apiSignature, isNot(aCycle_1.apiSignature)); + } + test_refresh_library_to_partOfName() async { final a = newFile('$testPackageLibPath/a.dart', r''' +library my.lib; part 'b.dart'; '''); @@ -568,6 +1336,7 @@ part 'b.dart'; final bState = fileStateFor(b); bState.assertKind((kind) { kind as LibraryFileStateKind; + expect(kind.name, isNull); }); // Make it a part. @@ -575,11 +1344,12 @@ part 'b.dart'; part of my.lib; '''); - // We will discover the library, by asking each of them. + // We will discover the library by name. bState.refresh(); bState.assertKind((kind) { - kind as PartKnownFileStateKind; - expect(kind.library, same(aState)); + kind as PartOfNameFileStateKind; + kind.assertLibraries([aState]); + expect(kind.library, aState); }); // The file `b.dart` was something else, but now it is a known part. @@ -596,6 +1366,7 @@ library my; final aState = fileStateFor(a); aState.assertKind((kind) { kind as LibraryFileStateKind; + expect(kind.name, 'my'); }); newFile(a.path, r''' @@ -606,8 +1377,10 @@ part of my; // No library that includes it, so it stays unknown. aState.assertKind((kind) { - kind as PartUnknownNameFileStateKind; + kind as PartOfNameFileStateKind; expect(kind.directive.name, 'my'); + kind.assertLibraries([]); + expect(kind.library, isNull); }); } @@ -628,16 +1401,18 @@ library b; final bState = fileStateFor(b); bState.assertKind((kind) { kind as LibraryFileStateKind; + expect(kind.name, 'b'); }); newFile(b.path, r''' part of 'a.dart'; '''); - // We will discover the library, by asking each of them. + // We will discover the library using the URI. bState.refresh(); bState.assertKind((kind) { - kind as PartKnownFileStateKind; + kind as PartOfUriKnownFileStateKind; + expect(kind.uriFile, same(aState)); expect(kind.library, same(aState)); }); @@ -647,6 +1422,71 @@ part of 'a.dart'; expect(aCycle_2.apiSignature, isNot(aCycle_1.apiSignature)); } + test_refresh_partOfName_twoLibraries() async { + final a = newFile('$testPackageLibPath/a.dart', r''' +part of my.lib; +class A1 {} +'''); + + final b = newFile('$testPackageLibPath/b.dart', r''' +library my.lib; +part 'a.dart'; +'''); + + final c = newFile('$testPackageLibPath/c.dart', r''' +library my.lib; +part 'a.dart'; +'''); + + final bState = fileStateFor(b); + bState.assertKind((kind) { + kind as LibraryFileStateKind; + expect(kind.name, 'my.lib'); + }); + _assertPartedFiles(bState, [a]); + + final cState = fileStateFor(c); + cState.assertKind((kind) { + kind as LibraryFileStateKind; + expect(kind.name, 'my.lib'); + }); + _assertPartedFiles(cState, [a]); + + final aState = fileStateFor(a); + aState.assertKind((kind) { + kind as PartOfNameFileStateKind; + expect(kind.directive.name, 'my.lib'); + kind.assertLibraries([bState, cState]); + expect(kind.library, same(bState)); + }); + + final bCycle_1 = bState.libraryCycle; + final cCycle_1 = cState.libraryCycle; + + // Update `a.dart` part. + newFile(a.path, r''' +part of my.lib; +class A2 {} +'''); + aState.refresh(); + // `a.dart` is still a part. + aState.assertKind((kind) { + kind as PartOfNameFileStateKind; + expect(kind.directive.name, 'my.lib'); + kind.assertLibraries([bState, cState]); + expect(kind.library, same(bState)); + }); + + // ...but the unlinked signature of `a.dart` is different. + // We invalidate `b.dart` it references `a.dart`. + // We invalidate `c.dart` it references `a.dart`. + // Even though `a.dart` is not a valid part of `c.dart`. + final bCycle_2 = bState.libraryCycle; + final cCycle_2 = cState.libraryCycle; + expect(bCycle_2.apiSignature, isNot(bCycle_1.apiSignature)); + expect(cCycle_2.apiSignature, isNot(cCycle_1.apiSignature)); + } + test_refresh_partOfUri_to_library() async { final a = newFile('$testPackageLibPath/a.dart', r''' part 'b.dart'; @@ -659,6 +1499,7 @@ part of 'a.dart'; final aState = fileStateFor(a); aState.assertKind((kind) { kind as LibraryFileStateKind; + expect(kind.name, isNull); }); _assertPartedFiles(aState, [b]); @@ -667,8 +1508,9 @@ part of 'a.dart'; // There is `part of` in `b.dart`, so it is a part. final bState = fileStateFor(b); bState.assertKind((kind) { - kind as PartKnownFileStateKind; - expect(kind.library, aState); + kind as PartOfUriKnownFileStateKind; + expect(kind.uriFile, same(aState)); + expect(kind.library, same(aState)); }); // There are no directives in `b.dart`, so it is a library. @@ -678,6 +1520,7 @@ part of 'a.dart'; bState.refresh(); bState.assertKind((kind) { kind as LibraryFileStateKind; + expect(kind.name, isNull); }); // Library `a.dart` still considers `b.dart` its part. @@ -688,6 +1531,76 @@ part of 'a.dart'; expect(aCycle_2.apiSignature, isNot(aCycle_1.apiSignature)); } + test_refresh_partOfUri_twoLibraries() async { + final a = newFile('$testPackageLibPath/a.dart', r''' +part of 'b.dart'; +class A1 {} +'''); + + final b = newFile('$testPackageLibPath/b.dart', r''' +part 'a.dart'; +'''); + + final c = newFile('$testPackageLibPath/c.dart', r''' +part 'a.dart'; +'''); + + final bState = fileStateFor(b); + bState.assertKind((kind) { + kind as LibraryFileStateKind; + expect(kind.name, isNull); + }); + _assertPartedFiles(bState, [a]); + + final cState = fileStateFor(c); + cState.assertKind((kind) { + kind as LibraryFileStateKind; + expect(kind.name, isNull); + }); + _assertPartedFiles(cState, [a]); + + final aState = fileStateFor(a); + aState.assertKind((kind) { + kind as PartOfUriKnownFileStateKind; + expect(kind.uriFile, same(bState)); + expect(kind.library, same(bState)); + }); + + final bCycle_1 = bState.libraryCycle; + final cCycle_1 = cState.libraryCycle; + + // Update `a.dart` part. + newFile(a.path, r''' +part of 'b.dart'; +class A2 {} +'''); + aState.refresh(); + // `a.dart` is still a part. + aState.assertKind((kind) { + kind as PartOfUriKnownFileStateKind; + expect(kind.uriFile, same(bState)); + expect(kind.library, same(bState)); + }); + + // ...but the unlinked signature of `a.dart` is different. + // We invalidate `b.dart` it references `a.dart`. + // We invalidate `c.dart` it references `a.dart`. + // Even though `a.dart` is not a valid part of `c.dart`. + final bCycle_2 = bState.libraryCycle; + final cCycle_2 = cState.libraryCycle; + expect(bCycle_2.apiSignature, isNot(bCycle_1.apiSignature)); + expect(cCycle_2.apiSignature, isNot(cCycle_1.apiSignature)); + } + + void _assertAugmentationFiles(FileState fileState, List expected) { + final actualFiles = fileState.augmentationFiles.map((part) { + if (part != null) { + return getFile(part.path); + } + }).toList(); + expect(actualFiles, expected); + } + void _assertPartedFiles(FileState fileState, List expected) { final actualFiles = fileState.partedFiles.map((part) { if (part != null) { @@ -1043,8 +1956,7 @@ class A2 {} expect(file_a2.library, same(file_a1)); // Now update the library, and refresh its file. - // The 'a2.dart' is not referenced anymore. - // So the part file does not have the library anymore. + // The library does not include this part, so no library. newFile(a1, r''' library a1; part 'not-a2.dart'; @@ -1545,6 +2457,15 @@ extension on FileState { } } +extension on PartOfNameFileStateKind { + void assertLibraries(Iterable expectedFiles) { + final expectedKinds = expectedFiles.map((e) { + return e.kind as LibraryFileStateKind; + }).toList(); + expect(libraries, unorderedEquals(expectedKinds)); + } +} + extension _Either2Extension on Either2 { T1 get t1 { late T1 result;