Add FileState.kind
Change-Id: Icc8718c3d7bc5ab0c28f741a557bbb54c28bb52a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/245225
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analyzer/lib/src/dart/analysis/file_state.dart b/pkg/analyzer/lib/src/dart/analysis/file_state.dart
index 1bd5c9c..2e35c85 100644
--- a/pkg/analyzer/lib/src/dart/analysis/file_state.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/file_state.dart
@@ -126,13 +126,15 @@
UnlinkedUnit? _unlinked2;
+ FileStateKind? _kind;
+
/// Files that reference this file.
final List<FileState> referencingFiles = [];
List<FileState?>? _importedFiles;
List<FileState?>? _exportedFiles;
- List<FileState?>? _partedFiles;
- List<FileState>? _libraryFiles;
+ List<FileState?> _partedFiles = [];
+ List<FileState> _libraryFiles = [];
Set<FileState>? _directReferencedFiles;
Set<FileState>? _directReferencedLibraries;
@@ -242,16 +244,17 @@
_unlinked2!.partOfUriDirective != null;
}
+ FileStateKind get kind => _kind!;
+
/// If the file [isPart], return a currently know library the file is a part
/// of. Return `null` if a library is not known, for example because we have
/// not processed a library file yet.
FileState? get library {
- _fsState.readPartsForLibraries();
- List<FileState>? libraries = _fsState._partToLibraries[this];
- if (libraries == null || libraries.isEmpty) {
- return null;
+ final kind = _kind;
+ if (kind is PartKnownFileStateKind) {
+ return kind.library;
} else {
- return libraries.first;
+ return null;
}
}
@@ -268,10 +271,7 @@
/// The list of files files that this library consists of, i.e. this library
/// file itself and its [partedFiles].
List<FileState> get libraryFiles {
- return _libraryFiles ??= [
- this,
- ...partedFiles.whereNotNull(),
- ];
+ return _libraryFiles;
}
/// Return information about line in the file.
@@ -279,20 +279,7 @@
/// The list of files this library file references as parts.
List<FileState?> get partedFiles {
- return _partedFiles ??= _unlinked2!.parts.map((uri) {
- return _fileForRelativeUri(uri).map(
- (file) {
- if (file != null) {
- file.referencingFiles.add(this);
- _fsState._partToLibraries
- .putIfAbsent(file, () => <FileState>[])
- .add(this);
- }
- return file;
- },
- (_) => null,
- );
- }).toList();
+ return _partedFiles;
}
/// The external names referenced by the file.
@@ -410,21 +397,14 @@
_libraryCycle?.invalidate();
// If this is a part, invalidate the libraries.
- var libraries = _fsState._partToLibraries[this];
- if (libraries != null) {
- for (var library in libraries) {
- library.libraryCycle.invalidate();
+ final kind = _kind;
+ if (kind is PartKnownFileStateKind) {
+ for (final library in kind._libraries) {
+ library._libraryCycle?.invalidate();
}
}
}
- // This file is potentially not a library for its previous parts anymore.
- if (_partedFiles != null) {
- for (var part in _partedFiles!) {
- _fsState._partToLibraries[part]?.remove(this);
- }
- }
-
// It is possible that this file does not reference these files.
_stopReferencingByThisFile();
@@ -434,10 +414,9 @@
_directReferencedFiles = null;
_directReferencedLibraries = null;
- // Read parts on demand.
- _fsState._librariesWithoutPartsRead.add(this);
- _partedFiles = null;
- _libraryFiles = null;
+ // Read parts eagerly to link parts to libraries.
+ _updateKind();
+ _updatePartedFiles();
// Update mapping from subtyped names to files.
for (var name in _driverUnlinkedUnit!.subtypedNames) {
@@ -581,6 +560,114 @@
removeForOne(_partedFiles);
}
+ 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();
+ }
+ }
+ }
+ }
+
+ final libraryAugmentationDirective = unlinked2.libraryAugmentationDirective;
+ 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,
+ );
+ _fileForRelativeUri(libraryAugmentationDirective.uri);
+ } else if (unlinked2.libraryDirective != null) {
+ _kind = LibraryFileStateKind(
+ file: this,
+ );
+ } else if (partOfNameDirective != null) {
+ if (_kind is! PartKnownFileStateKind) {
+ _kind = PartUnknownNameFileStateKind(
+ file: this,
+ directive: partOfNameDirective,
+ );
+ updateLibrariesWithThisPart();
+ }
+ } else if (partOfUriDirective != null) {
+ if (_kind is! PartKnownFileStateKind) {
+ _kind = PartUnknownUriFileStateKind(
+ file: this,
+ directive: partOfUriDirective,
+ );
+ _fileForRelativeUri(partOfUriDirective.uri);
+ updateLibrariesWithThisPart();
+ }
+ } else {
+ _kind = LibraryFileStateKind(
+ file: this,
+ );
+ }
+ }
+
+ 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,
+ );
+ }
+ }
+ return part;
+ },
+ (_) => null,
+ );
+ }).toList();
+
+ _libraryFiles = [
+ this,
+ ...partedFiles.whereNotNull(),
+ ];
+ }
+
static UnlinkedUnit serializeAstUnlinked2(CompilationUnit unit) {
UnlinkedLibraryDirective? libraryDirective;
UnlinkedLibraryAugmentationDirective? libraryAugmentationDirective;
@@ -722,6 +809,14 @@
}
}
+abstract class FileStateKind {
+ final FileState file;
+
+ FileStateKind({
+ required this.file,
+ });
+}
+
enum FileStateRefreshResult {
/// No changes to the content, so no changes at all.
nothing,
@@ -780,14 +875,6 @@
/// Mapping from a path to the corresponding [FileState].
final Map<String, FileState> _pathToFile = {};
- /// We don't read parts until requested, but if we need to know the
- /// library for a file, we need to read parts of every file to know
- /// which libraries reference this part.
- final List<FileState> _librariesWithoutPartsRead = [];
-
- /// Mapping from a part to the libraries it is a part of.
- final Map<FileState, List<FileState>> _partToLibraries = {};
-
/// The map of subtyped names to files where these names are subtyped.
final Map<String, Set<FileState>> _subtypedNameToFiles = {};
@@ -905,7 +992,7 @@
final externalSummaries = this.externalSummaries;
if (externalSummaries != null) {
String uriStr = uri.toString();
- if (externalSummaries.hasLinkedLibrary(uriStr)) {
+ if (externalSummaries.uriToSummaryPath.containsKey(uriStr)) {
return Either2.t2(ExternalLibrary._(uri));
}
}
@@ -969,19 +1056,6 @@
_fileContentCache.invalidate(path);
}
- void readPartsForLibraries() {
- // Make a copy, because reading new files will update it.
- var libraryToProcess = _librariesWithoutPartsRead.toList();
-
- // We will process these files, so clear it now.
- // It will be filled with new files during the loop below.
- _librariesWithoutPartsRead.clear();
-
- for (var library in libraryToProcess) {
- library.partedFiles;
- }
- }
-
/// Remove the file with the given [path].
void removeFile(String path) {
markFileForReading(path);
@@ -995,8 +1069,6 @@
knownFiles.clear();
_hasUriForPath.clear();
_pathToFile.clear();
- _librariesWithoutPartsRead.clear();
- _partToLibraries.clear();
_subtypedNameToFiles.clear();
}
@@ -1079,3 +1151,64 @@
bool get isSrc => (_flags & _isSrc) != 0;
}
+
+class LibraryAugmentationKnownFileStateKind extends FileStateKind {
+ final FileState augmented;
+
+ 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,
+ });
+}
+
+class PartKnownFileStateKind extends FileStateKind {
+ final List<FileState> _libraries = [];
+
+ PartKnownFileStateKind({
+ required super.file,
+ required FileState library,
+ }) {
+ _libraries.add(library);
+ }
+
+ 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 {
+ final UnlinkedPartOfNameDirective directive;
+
+ PartUnknownNameFileStateKind({
+ required super.file,
+ required this.directive,
+ });
+}
+
+/// The file is a part, but its library is unknown.
+/// When we don't understand the URI.
+class PartUnknownUriFileStateKind extends FileStateKind {
+ final UnlinkedPartOfUriDirective directive;
+
+ PartUnknownUriFileStateKind({
+ required super.file,
+ required this.directive,
+ });
+}
diff --git a/pkg/analyzer/test/src/dart/analysis/driver_test.dart b/pkg/analyzer/test/src/dart/analysis/driver_test.dart
index 5a963a6..5ceb724 100644
--- a/pkg/analyzer/test/src/dart/analysis/driver_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/driver_test.dart
@@ -2509,29 +2509,6 @@
expect(driver.fsState.knownFilePaths, unorderedEquals([b]));
}
- test_parseFileSync_doesNotReadPartedFiles() async {
- var a = convertPath('/test/lib/a.dart');
- var b = convertPath('/test/lib/b.dart');
-
- newFile(a, r'''
-part of my;
-''');
- newFile(b, r'''
-library my;
-part 'a.dart';
-''');
-
- expect(driver.fsState.knownFilePaths, isEmpty);
-
- // Don't read `a.dart` when parse.
- driver.parseFileSync(b);
- expect(driver.fsState.knownFilePaths, unorderedEquals([b]));
-
- // Still don't read `a.dart` when parse the second time.
- driver.parseFileSync(b);
- expect(driver.fsState.knownFilePaths, unorderedEquals([b]));
- }
-
test_parseFileSync_languageVersion() async {
var path = convertPath('/test/lib/test.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 2ec38e5..0070cbe 100644
--- a/pkg/analyzer/test/src/dart/analysis/file_state_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/file_state_test.dart
@@ -36,6 +36,7 @@
defineReflectiveSuite(() {
defineReflectiveTests(FileSystemStateTest);
defineReflectiveTests(FileSystemState_BazelWorkspaceTest);
+ defineReflectiveTests(FileSystemState_PubPackageTest);
});
}
@@ -119,6 +120,585 @@
}
@reflectiveTest
+class FileSystemState_PubPackageTest extends PubPackageResolutionTest {
+ FileState fileStateFor(File file) {
+ return fsStateFor(file).getFileForPath(file.path);
+ }
+
+ FileSystemState fsStateFor(File file) {
+ return driverFor(file.path).fsState;
+ }
+
+ test_newFile_library_includePart_withoutPartOf() async {
+ final a = newFile('$testPackageLibPath/a.dart', r'''
+part 'b.dart';
+''');
+
+ final b = newFile('$testPackageLibPath/b.dart', r'''
+// no part of
+''');
+
+ final aState = fileStateFor(a);
+ aState.assertKind((kind) {
+ kind as LibraryFileStateKind;
+ });
+
+ // Library `a.dart` includes `b.dart` as a part.
+ _assertPartedFiles(aState, [b]);
+
+ // But `b.dart` thinks that it is a library itself.
+ final bState = fileStateFor(b);
+ bState.assertKind((kind) {
+ kind as LibraryFileStateKind;
+ });
+
+ // Refreshing the library does not change this.
+ aState.refresh();
+ _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));
+ });
+ }
+
+ test_newFile_libraryDirective() async {
+ final a = newFile('$testPackageLibPath/a.dart', r'''
+library my;
+''');
+
+ final aState = fileStateFor(a);
+ aState.assertKind((kind) {
+ kind as LibraryFileStateKind;
+ });
+ }
+
+ test_newFile_noDirectives() async {
+ final a = newFile('$testPackageLibPath/a.dart', '');
+
+ final aState = fileStateFor(a);
+ aState.assertKind((kind) {
+ kind as LibraryFileStateKind;
+ });
+ }
+
+ test_newFile_partOfName() async {
+ final a = newFile('$testPackageLibPath/a.dart', r'''
+library my.lib;
+part 'b.dart';
+''');
+
+ final b = newFile('$testPackageLibPath/b.dart', r'''
+part of my.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;
+ expect(kind.directive.name, 'my.lib');
+ });
+
+ // Read the library file.
+ final aState = fileStateFor(a);
+ aState.assertKind((kind) {
+ kind as LibraryFileStateKind;
+ });
+ _assertPartedFiles(aState, [b]);
+
+ // Now the part knows its library.
+ bState.assertKind((kind) {
+ kind as PartKnownFileStateKind;
+ expect(kind.library, same(aState));
+ });
+ }
+
+ test_newFile_partOfName_differentName() async {
+ final a = newFile('$testPackageLibPath/a.dart', r'''
+library my.lib;
+part 'b.dart';
+''');
+
+ final b = newFile('$testPackageLibPath/b.dart', r'''
+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;
+ expect(kind.directive.name, 'other.lib');
+ });
+
+ // Read the library file.
+ final aState = fileStateFor(a);
+ aState.assertKind((kind) {
+ kind as LibraryFileStateKind;
+ });
+ _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.
+ bState.assertKind((kind) {
+ kind as PartKnownFileStateKind;
+ expect(kind.library, same(aState));
+ });
+ }
+
+ test_newFile_partOfName_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 'doesNotMatter.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 PartKnownFileStateKind;
+ expect(kind.library, aState);
+ });
+
+ // Reading `b.dart` does not update the part.
+ final bState = fileStateFor(b);
+ _assertPartedFiles(bState, [c]);
+ cState.assertKind((kind) {
+ kind as PartKnownFileStateKind;
+ expect(kind.library, aState);
+ });
+
+ // Refreshing `b.dart` does not update the part.
+ bState.refresh();
+ cState.assertKind((kind) {
+ kind as PartKnownFileStateKind;
+ expect(kind.library, aState);
+ });
+
+ // Exclude the part from `a.dart` - switches to `b.dart` as its library.
+ newFile(a.path, '');
+ aState.refresh();
+ cState.assertKind((kind) {
+ kind as PartKnownFileStateKind;
+ expect(kind.library, bState);
+ });
+
+ // Exclude the part from `b.dart` as well - switches to unknown.
+ newFile(b.path, '');
+ bState.refresh();
+ cState.assertKind((kind) {
+ kind as PartUnknownUriFileStateKind;
+ });
+ }
+
+ test_newFile_partOfUri_doesNotExist() async {
+ 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.
+ bState.assertKind((kind) {
+ kind as PartUnknownUriFileStateKind;
+ });
+
+ // Create the library that includes the part file.
+ final a = newFile('$testPackageLibPath/a.dart', 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;
+ expect(kind.library, same(aState));
+ });
+
+ // Refreshing the part file does not break the kind.
+ bState.refresh();
+ bState.assertKind((kind) {
+ kind as PartKnownFileStateKind;
+ expect(kind.library, same(aState));
+ });
+ }
+
+ test_newFile_partOfUri_exists_hasPart() async {
+ final a = newFile('$testPackageLibPath/a.dart', r'''
+part 'b.dart';
+''');
+
+ final b = newFile('$testPackageLibPath/b.dart', r'''
+part of 'a.dart';
+''');
+
+ final bState = fileStateFor(b);
+ 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]);
+ 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);
+ expect(kind.library, same(aState));
+ });
+ }
+
+ test_newFile_partOfUri_exists_noPart() async {
+ newFile('$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.
+ bState.assertKind((kind) {
+ kind as PartUnknownUriFileStateKind;
+ });
+ }
+
+ test_newFile_partOfUri_invalid() async {
+ final b = newFile('$testPackageLibPath/b.dart', r'''
+part of 'da:';
+''');
+
+ final bState = fileStateFor(b);
+
+ // The URI is invalid, so there is no way to discover the library.
+ bState.assertKind((kind) {
+ kind as PartUnknownUriFileStateKind;
+ expect(kind.directive.uri, 'da:');
+ });
+
+ // But reading a library that includes this part will update it.
+ final a = newFile('$testPackageLibPath/a.dart', r'''
+part 'b.dart';
+''');
+ final aState = fileStateFor(a);
+ _assertPartedFiles(aState, [b]);
+
+ bState.assertKind((kind) {
+ kind as PartKnownFileStateKind;
+ expect(kind.library, same(aState));
+ });
+ }
+
+ test_refresh_library_removePart_partOfName() async {
+ final a = newFile('$testPackageLibPath/a.dart', r'''
+part of my;
+''');
+
+ final b = newFile('$testPackageLibPath/b.dart', r'''
+part of my;
+''');
+
+ final c = newFile('$testPackageLibPath/c.dart', r'''
+library my;
+part 'a.dart';
+part 'b.dart';
+''');
+
+ final cState = fileStateFor(c);
+ cState.assertKind((kind) {
+ kind as LibraryFileStateKind;
+ });
+ _assertPartedFiles(cState, [a, b]);
+
+ final aState = fileStateFor(a);
+ final bState = fileStateFor(b);
+
+ // Both part files know the library.
+ aState.assertKind((kind) {
+ kind as PartKnownFileStateKind;
+ expect(kind.library, cState);
+ });
+ bState.assertKind((kind) {
+ kind as PartKnownFileStateKind;
+ expect(kind.library, cState);
+ });
+
+ newFile(c.path, r'''
+library my;
+part 'b.dart';
+''');
+
+ // Stop referencing `a.dart` part file.
+ cState.refresh();
+ _assertPartedFiles(cState, [b]);
+
+ // So, the `a.dart` does not know its library anymore.
+ // But `b.dart` still knows it.
+ aState.assertKind((kind) {
+ kind as PartUnknownNameFileStateKind;
+ });
+ bState.assertKind((kind) {
+ kind as PartKnownFileStateKind;
+ expect(kind.library, cState);
+ });
+ }
+
+ test_refresh_library_removePart_partOfUri() async {
+ final a = newFile('$testPackageLibPath/a.dart', r'''
+part of 'test.dart';
+''');
+
+ final b = newFile('$testPackageLibPath/b.dart', r'''
+part of 'test.dart';
+''');
+
+ final c = newFile('$testPackageLibPath/c.dart', r'''
+library my;
+part 'a.dart';
+part 'b.dart';
+''');
+
+ final cState = fileStateFor(c);
+ cState.assertKind((kind) {
+ kind as LibraryFileStateKind;
+ });
+ _assertPartedFiles(cState, [a, b]);
+
+ final aState = fileStateFor(a);
+ final bState = fileStateFor(b);
+
+ // Both part files know the library.
+ aState.assertKind((kind) {
+ kind as PartKnownFileStateKind;
+ expect(kind.library, cState);
+ });
+ bState.assertKind((kind) {
+ kind as PartKnownFileStateKind;
+ expect(kind.library, cState);
+ });
+
+ newFile(c.path, r'''
+library my;
+part 'b.dart';
+''');
+
+ // Stop referencing `a.dart` part file.
+ cState.refresh();
+ _assertPartedFiles(cState, [b]);
+
+ // So, the `a.dart` does not know its library anymore.
+ // But `b.dart` still knows it.
+ aState.assertKind((kind) {
+ kind as PartUnknownUriFileStateKind;
+ });
+ bState.assertKind((kind) {
+ kind as PartKnownFileStateKind;
+ expect(kind.library, cState);
+ });
+ }
+
+ test_refresh_library_to_partOfName() async {
+ final a = newFile('$testPackageLibPath/a.dart', r'''
+part 'b.dart';
+''');
+
+ // No `part of`, so it is a library.
+ final b = newFile('$testPackageLibPath/b.dart', '');
+
+ final aState = fileStateFor(a);
+ _assertPartedFiles(aState, [b]);
+
+ final aCycle_1 = aState.libraryCycle;
+
+ // No `part of`, so it is a library.
+ // It does not matter, that `a.dart` tried to use it as part.
+ final bState = fileStateFor(b);
+ bState.assertKind((kind) {
+ kind as LibraryFileStateKind;
+ });
+
+ // Make it a part.
+ newFile(b.path, r'''
+part of my.lib;
+''');
+
+ // We will discover the library, by asking each of them.
+ bState.refresh();
+ bState.assertKind((kind) {
+ kind as PartKnownFileStateKind;
+ expect(kind.library, same(aState));
+ });
+
+ // The file `b.dart` was something else, but now it is a known part.
+ // This affects libraries that include it.
+ final aCycle_2 = aState.libraryCycle;
+ expect(aCycle_2.apiSignature, isNot(aCycle_1.apiSignature));
+ }
+
+ test_refresh_library_to_partOfName_noLibrary() async {
+ final a = newFile('$testPackageLibPath/a.dart', r'''
+library my;
+''');
+
+ final aState = fileStateFor(a);
+ aState.assertKind((kind) {
+ kind as LibraryFileStateKind;
+ });
+
+ newFile(a.path, r'''
+part of my;
+''');
+
+ aState.refresh();
+
+ // No library that includes it, so it stays unknown.
+ aState.assertKind((kind) {
+ kind as PartUnknownNameFileStateKind;
+ expect(kind.directive.name, 'my');
+ });
+ }
+
+ test_refresh_library_to_partOfUri() async {
+ final a = newFile('$testPackageLibPath/a.dart', r'''
+part 'b.dart';
+''');
+
+ final b = newFile('$testPackageLibPath/b.dart', r'''
+library b;
+''');
+
+ final aState = fileStateFor(a);
+ _assertPartedFiles(aState, [b]);
+
+ final aCycle_1 = aState.libraryCycle;
+
+ final bState = fileStateFor(b);
+ bState.assertKind((kind) {
+ kind as LibraryFileStateKind;
+ });
+
+ newFile(b.path, r'''
+part of 'a.dart';
+''');
+
+ // We will discover the library, by asking each of them.
+ bState.refresh();
+ bState.assertKind((kind) {
+ kind as PartKnownFileStateKind;
+ expect(kind.library, same(aState));
+ });
+
+ // The file `b.dart` was something else, but now it is a known part.
+ // This affects libraries that include it.
+ final aCycle_2 = aState.libraryCycle;
+ expect(aCycle_2.apiSignature, isNot(aCycle_1.apiSignature));
+ }
+
+ test_refresh_partOfUri_to_library() async {
+ final a = newFile('$testPackageLibPath/a.dart', r'''
+part 'b.dart';
+''');
+
+ final b = newFile('$testPackageLibPath/b.dart', r'''
+part of 'a.dart';
+''');
+
+ final aState = fileStateFor(a);
+ aState.assertKind((kind) {
+ kind as LibraryFileStateKind;
+ });
+ _assertPartedFiles(aState, [b]);
+
+ final aCycle_1 = aState.libraryCycle;
+
+ // 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);
+ });
+
+ // There are no directives in `b.dart`, so it is a library.
+ newFile(b.path, r'''
+// no part of
+''');
+ bState.refresh();
+ bState.assertKind((kind) {
+ kind as LibraryFileStateKind;
+ });
+
+ // Library `a.dart` still considers `b.dart` its part.
+ _assertPartedFiles(aState, [b]);
+
+ // The library cycle for `a.dart` is different now.
+ final aCycle_2 = aState.libraryCycle;
+ expect(aCycle_2.apiSignature, isNot(aCycle_1.apiSignature));
+ }
+
+ void _assertPartedFiles(FileState fileState, List<File> expected) {
+ final actualFiles = fileState.partedFiles.map((part) {
+ if (part != null) {
+ return getFile(part.path);
+ }
+ }).toList();
+ expect(actualFiles, expected);
+ }
+}
+
+@reflectiveTest
class FileSystemStateTest with ResourceProviderMixin {
final ByteStore byteStore = MemoryByteStore();
final FileContentOverlay contentOverlay = FileContentOverlay();
@@ -958,6 +1538,13 @@
}
}
+extension on FileState {
+ void assertKind(void Function(FileStateKind kind) f) {
+ expect(kind.file, same(this));
+ f(kind);
+ }
+}
+
extension _Either2Extension<T1, T2> on Either2<T1, T2> {
T1 get t1 {
late T1 result;
diff --git a/pkg/analyzer/test/src/dart/resolution/context_collection_resolution.dart b/pkg/analyzer/test/src/dart/resolution/context_collection_resolution.dart
index 40822da..f638f17 100644
--- a/pkg/analyzer/test/src/dart/resolution/context_collection_resolution.dart
+++ b/pkg/analyzer/test/src/dart/resolution/context_collection_resolution.dart
@@ -282,6 +282,8 @@
/// The path that is not in [workspaceRootPath], contains external packages.
String get packagesRootPath => '/packages';
+ File get testFile => getFile(testFilePath);
+
@override
String get testFilePath => '$testPackageLibPath/test.dart';