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';