Parts. Support for nested parts in LibraryFileKind.files/fileKinds

Change-Id: I69495418e660077942ead08e26fa746222f308c4
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/376462
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Phil Quitslund <pquitslund@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analyzer/lib/src/dart/analysis/driver.dart b/pkg/analyzer/lib/src/dart/analysis/driver.dart
index 2518295..f1d5775 100644
--- a/pkg/analyzer/lib/src/dart/analysis/driver.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart
@@ -1648,10 +1648,8 @@
     var dartCoreUri = uriCache.parse('dart:core');
     var dartCoreResolution = _fsState.getFileForUri(dartCoreUri);
     if (dartCoreResolution is UriResolutionFile) {
-      var kind = dartCoreResolution.file.kind;
-      if (kind is LibraryFileKind) {
-        kind.discoverReferencedFiles();
-      }
+      var dartCoreFile = dartCoreResolution.file;
+      dartCoreFile.kind.discoverReferencedFiles();
     }
   }
 
diff --git a/pkg/analyzer/lib/src/dart/analysis/file_state.dart b/pkg/analyzer/lib/src/dart/analysis/file_state.dart
index 709835b..6bbe3cc 100644
--- a/pkg/analyzer/lib/src/dart/analysis/file_state.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/file_state.dart
@@ -188,11 +188,6 @@
   }
 
   @override
-  void invalidateLibraryCycle() {
-    augmented?.invalidateLibraryCycle();
-  }
-
-  @override
   bool isAugmentationOf(FileKind container) {
     return uriFile == container.file;
   }
@@ -543,6 +538,11 @@
           import.importedLibrary?.collectTransitive(files);
         }
       }
+      for (var part in parts) {
+        if (part is PartWithFile) {
+          part.includedPart?.collectTransitive(files);
+        }
+      }
     }
   }
 
@@ -551,15 +551,12 @@
   /// textual dumps we want to check that we reference only objects that
   /// are available. So, we need to discover all referenced files before
   /// we register available objects.
-  @visibleForTesting
   void discoverReferencedFiles() {
+    augmentationImports;
     libraryExports;
     libraryImports;
-    for (var import in augmentationImports) {
-      if (import is AugmentationImportWithFile) {
-        import.importedAugmentation?.discoverReferencedFiles();
-      }
-    }
+    parts;
+    docImports;
   }
 
   @mustCallSuper
@@ -567,6 +564,7 @@
     _augmentationImports?.disposeAll();
     _libraryExports?.disposeAll();
     _libraryImports?.disposeAll();
+    _parts?.disposeAll();
     _docImports?.disposeAll();
   }
 
@@ -603,7 +601,11 @@
   }
 
   /// Invalidates the containing [LibraryFileKind] cycle.
-  void invalidateLibraryCycle() {}
+  void invalidateLibraryCycle() {
+    for (var reference in file.referencingFiles) {
+      reference.kind.invalidateLibraryCycle();
+    }
+  }
 
   /// Creates a [LibraryImportState] with the given unlinked [directive].
   LibraryImportState _buildLibraryImportState(
@@ -1636,6 +1638,20 @@
     }
   }
 
+  /// When printing the state for testing, we want to see all files.
+  @visibleForTesting
+  void discoverReferencedFiles() {
+    while (true) {
+      var fileCount = _pathToFile.length;
+      for (var file in _pathToFile.values.toList()) {
+        file.kind.discoverReferencedFiles();
+      }
+      if (_pathToFile.length == fileCount) {
+        break;
+      }
+    }
+  }
+
   /// Notifies this object that it is about to be discarded.
   ///
   /// Returns the keys of the artifacts that are no longer used.
@@ -1778,23 +1794,6 @@
     return flag;
   }
 
-  /// When printing the state for testing, we want to see all files.
-  @visibleForTesting
-  void pullReferencedFiles() {
-    while (true) {
-      var fileCount = _pathToFile.length;
-      for (var file in _pathToFile.values.toList()) {
-        var kind = file.kind;
-        kind.libraryImports;
-        kind.libraryExports;
-        kind.parts;
-      }
-      if (_pathToFile.length == fileCount) {
-        break;
-      }
-    }
-  }
-
   /// Remove the file with the given [path].
   void removeFile(String path) {
     _clearFiles();
@@ -2163,18 +2162,32 @@
     return result;
   }
 
-  /// The list of files that this library consists of, i.e. this library file
-  /// itself, its [parts], and augmentations.
+  /// The list of files that this library consists of:
+  /// - the library file itself;
+  /// - the part files, in the depth-first pre-order order.
+  List<FileKind> get fileKinds {
+    var result = <FileKind>[];
+
+    void visitParts(FileKind kind) {
+      result.add(kind);
+      for (var part in kind.parts) {
+        if (part is PartWithFile) {
+          var includedPart = part.includedPart;
+          if (includedPart != null) {
+            visitParts(includedPart);
+          }
+        }
+      }
+    }
+
+    visitParts(this);
+    result.addAll(augmentations);
+    return result;
+  }
+
+  /// The files extracted from [fileKinds].
   List<FileState> get files {
-    return [
-      file,
-      ...parts
-          .whereType<PartWithFile>()
-          .map((partState) => partState.includedPart)
-          .nonNulls
-          .map((partKind) => partKind.file),
-      ...augmentations.map((e) => e.file),
-    ];
+    return fileKinds.map((kind) => kind.file).toList();
   }
 
   LibraryCycle? get internal_libraryCycle => _libraryCycle;
@@ -2274,26 +2287,9 @@
   }
 
   @override
-  void collectTransitive(Set<FileState> files) {
-    super.collectTransitive(files);
-    for (var part in parts) {
-      if (part is PartWithFile) {
-        files.add(part.includedFile);
-      }
-    }
-  }
-
-  @override
-  void discoverReferencedFiles() {
-    super.discoverReferencedFiles();
-    parts;
-  }
-
-  @override
   void dispose() {
     invalidateLibraryCycle();
     file._fsState._libraryNameToFiles.remove(this);
-    _parts?.disposeAll();
     super.dispose();
   }
 
@@ -2526,9 +2522,7 @@
   /// This method is invoked when the part file is updated.
   /// The file either becomes a part, or might stop being a part.
   void _invalidateLibraries() {
-    for (var reference in file.referencingFiles) {
-      reference.kind.invalidateLibraryCycle();
-    }
+    invalidateLibraryCycle();
   }
 }
 
diff --git a/pkg/analyzer/lib/src/dart/analysis/library_graph.dart b/pkg/analyzer/lib/src/dart/analysis/library_graph.dart
index c30bc0a..3beea0c 100644
--- a/pkg/analyzer/lib/src/dart/analysis/library_graph.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/library_graph.dart
@@ -186,21 +186,28 @@
 
   @override
   List<_LibraryNode> computeDependencies() {
-    var referencedLibraries = {kind, ...kind.augmentations}
-        .map((container) => [
-              ...container.libraryImports
-                  .whereType<LibraryImportWithFile>()
-                  .map((import) => import.importedLibrary),
-              ...container.libraryExports
-                  .whereType<LibraryExportWithFile>()
-                  .map((export) => export.exportedLibrary),
-            ])
+    var referencedLibraries = kind.fileKinds
+        .map((fileKind) {
+          return [
+            ...fileKind.libraryImports
+                .whereType<LibraryImportWithFile>()
+                .map((import) => import.importedLibrary),
+            ...fileKind.libraryExports
+                .whereType<LibraryExportWithFile>()
+                .map((export) => export.exportedLibrary),
+          ];
+        })
         .flattenedToList
         .nonNulls
         .toSet();
 
     return referencedLibraries.map(walker.getNode).toList();
   }
+
+  @override
+  String toString() {
+    return 'LibraryNode($kind)';
+  }
 }
 
 /// Helper that organizes dependencies of a library into topologically
diff --git a/pkg/analyzer/test/src/dart/analysis/analyzer_state_printer.dart b/pkg/analyzer/test/src/dart/analysis/analyzer_state_printer.dart
index c44494a..5632d52 100644
--- a/pkg/analyzer/test/src/dart/analysis/analyzer_state_printer.dart
+++ b/pkg/analyzer/test/src/dart/analysis/analyzer_state_printer.dart
@@ -68,6 +68,8 @@
         } else if (cycle.libraries
             .any((e) => e.file.uriStr == 'dart:collection')) {
           return 'dart:collection';
+        } else if (cycle.libraries.any((e) => e.file.uriStr == 'dart:io')) {
+          return 'dart:io';
         } else {
           throw UnimplementedError('$cycle');
         }
@@ -319,7 +321,7 @@
   }
 
   void _writeFiles(FileSystemTestData testData) {
-    fileSystemState.pullReferencedFiles();
+    fileSystemState.discoverReferencedFiles();
 
     if (configuration.discardPartialMacroAugmentationFiles) {
       var pattern = RegExp(r'^.*\.macro\d+\.dart$');
@@ -342,17 +344,8 @@
       }
     }
 
-    // Discover referenced files.
     // This is required for consistency checking.
-    for (var fileData in testData.files.values.toList()) {
-      var current = fileSystemState.getExisting(fileData.file);
-      if (current != null) {
-        var kind = current.kind;
-        if (kind is LibraryOrAugmentationFileKind) {
-          kind.discoverReferencedFiles();
-        }
-      }
-    }
+    fileSystemState.discoverReferencedFiles();
 
     // Sort, mostly by path.
     // But sort SDK libraries to the end, with `dart:core` first.
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 57c4378..9cad7ff 100644
--- a/pkg/analyzer/test/src/dart/analysis/file_state_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/file_state_test.dart
@@ -4226,7 +4226,7 @@
 
     newFile('$testPackageLibPath/c.dart', r'''
 part of 'b.dart';
-import 'dart:async';
+import 'dart:io';
 ''');
 
     fileStateFor(a);
@@ -4242,9 +4242,9 @@
           library_3 dart:core synthetic
         parts
           partOfUriKnown_1
-        files: file_0 file_1
+        files: file_0 file_1 file_2
         cycle_0
-          dependencies: dart:core
+          dependencies: dart:core dart:io
           libraries: library_0
           apiSignature_0
       unlinkedKey: k00
@@ -4267,7 +4267,7 @@
         uriFile: file_1
         library: library_0
         libraryImports
-          library_5 dart:async
+          library_8 dart:io
       referencingFiles: file_1
       unlinkedKey: k02
 libraryCycles
@@ -5287,6 +5287,125 @@
 ''');
   }
 
+  test_refresh_library_importedBy_part() {
+    var a = newFile('$testPackageLibPath/a.dart', r'''
+part 'b.dart';
+''');
+
+    newFile('$testPackageLibPath/b.dart', r'''
+part of 'a.dart';
+import 'c.dart';
+''');
+
+    var c = newFile('$testPackageLibPath/c.dart', r'''
+class C {}
+''');
+
+    fileStateFor(a);
+
+    // `c.dart` is imported by `b.dart`, so it is a dependency of `c.dart`.
+    assertDriverStateString(testFile, r'''
+files
+  /home/test/lib/a.dart
+    uri: package:test/a.dart
+    current
+      id: file_0
+      kind: library_0
+        libraryImports
+          library_3 dart:core synthetic
+        parts
+          partOfUriKnown_1
+        files: file_0 file_1
+        cycle_0
+          dependencies: cycle_1 dart:core
+          libraries: library_0
+          apiSignature_0
+      unlinkedKey: k00
+  /home/test/lib/b.dart
+    uri: package:test/b.dart
+    current
+      id: file_1
+      kind: partOfUriKnown_1
+        uriFile: file_0
+        library: library_0
+        libraryImports
+          library_2
+      referencingFiles: file_0
+      unlinkedKey: k01
+  /home/test/lib/c.dart
+    uri: package:test/c.dart
+    current
+      id: file_2
+      kind: library_2
+        libraryImports
+          library_3 dart:core synthetic
+        files: file_2
+        cycle_1
+          dependencies: dart:core
+          libraries: library_2
+          apiSignature_1
+          users: cycle_0
+      referencingFiles: file_1
+      unlinkedKey: k02
+libraryCycles
+elementFactory
+''');
+
+    newFile(c.path, r'''
+class C2 {}
+''');
+    fileStateFor(c).refresh();
+
+    // Updated `c.dart` invalidates the library cycle for `a.dart`, both
+    // have now different signatures.
+    assertDriverStateString(testFile, r'''
+files
+  /home/test/lib/a.dart
+    uri: package:test/a.dart
+    current
+      id: file_0
+      kind: library_0
+        libraryImports
+          library_3 dart:core synthetic
+        parts
+          partOfUriKnown_1
+        files: file_0 file_1
+        cycle_3
+          dependencies: cycle_4 dart:core
+          libraries: library_0
+          apiSignature_2
+      unlinkedKey: k00
+  /home/test/lib/b.dart
+    uri: package:test/b.dart
+    current
+      id: file_1
+      kind: partOfUriKnown_1
+        uriFile: file_0
+        library: library_0
+        libraryImports
+          library_8
+      referencingFiles: file_0
+      unlinkedKey: k01
+  /home/test/lib/c.dart
+    uri: package:test/c.dart
+    current
+      id: file_2
+      kind: library_8
+        libraryImports
+          library_3 dart:core synthetic
+        files: file_2
+        cycle_4
+          dependencies: dart:core
+          libraries: library_8
+          apiSignature_3
+          users: cycle_3
+      referencingFiles: file_1
+      unlinkedKey: k03
+libraryCycles
+elementFactory
+''');
+  }
+
   test_refresh_library_removePart_partOfName() async {
     newFile('$testPackageLibPath/a.dart', r'''
 part of my;
@@ -5957,6 +6076,111 @@
 ''');
   }
 
+  test_refresh_partOfUri_nestedPart() async {
+    var a = newFile('$testPackageLibPath/a.dart', r'''
+part 'b.dart';
+''');
+
+    newFile('$testPackageLibPath/b.dart', r'''
+part of 'a.dart';
+part 'c.dart';
+''');
+
+    var c = newFile('$testPackageLibPath/c.dart', r'''
+part of 'b.dart';
+class C {}
+''');
+
+    fileStateFor(a);
+    assertDriverStateString(testFile, r'''
+files
+  /home/test/lib/a.dart
+    uri: package:test/a.dart
+    current
+      id: file_0
+      kind: library_0
+        libraryImports
+          library_3 dart:core synthetic
+        parts
+          partOfUriKnown_1
+        files: file_0 file_1 file_2
+        cycle_0
+          dependencies: dart:core
+          libraries: library_0
+          apiSignature_0
+      unlinkedKey: k00
+  /home/test/lib/b.dart
+    uri: package:test/b.dart
+    current
+      id: file_1
+      kind: partOfUriKnown_1
+        uriFile: file_0
+        library: library_0
+        parts
+          partOfUriKnown_2
+      referencingFiles: file_0
+      unlinkedKey: k01
+  /home/test/lib/c.dart
+    uri: package:test/c.dart
+    current
+      id: file_2
+      kind: partOfUriKnown_2
+        uriFile: file_1
+        library: library_0
+      referencingFiles: file_1
+      unlinkedKey: k02
+libraryCycles
+elementFactory
+''');
+
+    modifyFile2(c, r'''
+part of 'b.dart';
+class C2 {}
+''');
+    fileStateFor(c).refresh();
+
+    assertDriverStateString(testFile, r'''
+files
+  /home/test/lib/a.dart
+    uri: package:test/a.dart
+    current
+      id: file_0
+      kind: library_0
+        libraryImports
+          library_3 dart:core synthetic
+        parts
+          partOfUriKnown_1
+        files: file_0 file_1 file_2
+        cycle_2
+          dependencies: dart:core
+          libraries: library_0
+          apiSignature_1
+      unlinkedKey: k00
+  /home/test/lib/b.dart
+    uri: package:test/b.dart
+    current
+      id: file_1
+      kind: partOfUriKnown_1
+        uriFile: file_0
+        library: library_0
+        parts
+          partOfUriKnown_8
+      referencingFiles: file_0
+      unlinkedKey: k01
+  /home/test/lib/c.dart
+    uri: package:test/c.dart
+    current
+      id: file_2
+      kind: partOfUriKnown_8
+        uriFile: file_1
+        library: library_0
+      referencingFiles: file_1
+      unlinkedKey: k03
+libraryCycles
+elementFactory
+''');
+  }
+
   test_refresh_partOfUri_to_library() async {
     var a = newFile('$testPackageLibPath/a.dart', r'''
 part 'b.dart';