Add LibraryOrAugmentationFileKind.augmentations

Change-Id: Id339b36d37ae904693c27173cd606856b0114eec
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/250182
Reviewed-by: Samuel Rawlins <srawlins@google.com>
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 e7c3225..1164841 100644
--- a/pkg/analyzer/lib/src/dart/analysis/file_state.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/file_state.dart
@@ -60,6 +60,9 @@
     // TODO(scheglov): implement asLibrary
     throw UnimplementedError();
   }
+
+  /// Returns `true` if the `library augment` directive confirms [container].
+  bool isAugmentationOf(LibraryOrAugmentationFileKind container);
 }
 
 /// The URI of the [directive] can be resolved.
@@ -100,6 +103,11 @@
     }
     return null;
   }
+
+  @override
+  bool isAugmentationOf(LibraryOrAugmentationFileKind container) {
+    return uriFile == container.file;
+  }
 }
 
 /// The URI of the [directive] can not be resolved.
@@ -111,6 +119,9 @@
 
   @override
   LibraryFileStateKind? get library => null;
+
+  @override
+  bool isAugmentationOf(LibraryOrAugmentationFileKind container) => false;
 }
 
 /// Information about a single `import` directive.
@@ -278,7 +289,6 @@
   /// Files that reference this file.
   final Set<FileState> referencingFiles = {};
 
-  List<FileState?> _augmentationFiles = [];
   List<FileState?>? _importedFiles;
   List<FileState?>? _exportedFiles;
 
@@ -306,11 +316,6 @@
   /// The unlinked API signature of the file.
   Uint8List get apiSignature => _apiSignature!;
 
-  /// The list of imported augmentations.
-  List<FileState?> get augmentationFiles {
-    return _augmentationFiles;
-  }
-
   /// The content of the file.
   String get content => _fileContent!.content;
 
@@ -568,7 +573,6 @@
 
     // Read parts eagerly to link parts to libraries.
     _updateKind();
-    _updateAugmentationFiles();
 
     // Update mapping from subtyped names to files.
     for (var name in _driverUnlinkedUnit!.subtypedNames) {
@@ -743,23 +747,10 @@
       }
     }
 
-    removeForOne(_augmentationFiles);
     removeForOne(_exportedFiles);
     removeForOne(_importedFiles);
   }
 
-  void _updateAugmentationFiles() {
-    _augmentationFiles = unlinked2.augmentations.map((directive) {
-      return _fileForRelativeUri(directive.uri).map(
-        (augmentation) {
-          augmentation?.referencingFiles.add(this);
-          return augmentation;
-        },
-        (_) => null,
-      );
-    }).toList();
-  }
-
   void _updateKind() {
     _kind?.dispose();
 
@@ -1026,6 +1017,7 @@
   /// Returns the library in which this file should be analyzed.
   LibraryFileStateKind? get library;
 
+  @mustCallSuper
   void dispose() {}
 }
 
@@ -1502,6 +1494,47 @@
   bool get isSrc => (_flags & _isSrc) != 0;
 }
 
+/// Information about a single `import augment` directive.
+class ImportAugmentationDirectiveState {
+  final LibraryOrAugmentationFileKind container;
+  final UnlinkedImportAugmentationDirective directive;
+
+  ImportAugmentationDirectiveState({
+    required this.container,
+    required this.directive,
+  });
+
+  /// Returns a [Source] that is referenced by this directive.
+  ///
+  /// Returns `null` if the URI cannot be resolved into a [Source].
+  Source? get importedSource => null;
+}
+
+/// [PartDirectiveState] that has a valid URI that references a file.
+class ImportAugmentationDirectiveWithFile
+    extends ImportAugmentationDirectiveState {
+  final FileState importedFile;
+
+  ImportAugmentationDirectiveWithFile({
+    required super.container,
+    required super.directive,
+    required this.importedFile,
+  });
+
+  /// If [importedFile] is a [AugmentationFileStateKind], and it confirms that
+  /// it is an augmentation of the [container], returns the [importedFile].
+  AugmentationFileStateKind? get importedAugmentation {
+    final kind = importedFile.kind;
+    if (kind is AugmentationFileStateKind && kind.isAugmentationOf(container)) {
+      return kind;
+    }
+    return null;
+  }
+
+  @override
+  Source? get importedSource => importedFile.source;
+}
+
 /// Information about a single `import` directive.
 class ImportDirectiveState {
   final UnlinkedNamespaceDirective directive;
@@ -1673,23 +1706,7 @@
       }
     }
 
-    final imports = _imports;
-    if (imports != null) {
-      for (final import in imports) {
-        if (import is ImportDirectiveWithFile) {
-          import.importedFile.referencingFiles.remove(file);
-        }
-      }
-    }
-
-    final exports = _exports;
-    if (exports != null) {
-      for (final export in exports) {
-        if (export is ExportDirectiveWithFile) {
-          export.exportedFile.referencingFiles.remove(file);
-        }
-      }
-    }
+    super.dispose();
   }
 
   bool hasPart(PartFileStateKind partKind) {
@@ -1714,6 +1731,7 @@
 }
 
 abstract class LibraryOrAugmentationFileKind extends FileStateKind {
+  List<ImportAugmentationDirectiveState>? _augmentations;
   List<ExportDirectiveState>? _exports;
   List<ImportDirectiveState>? _imports;
 
@@ -1721,6 +1739,34 @@
     required super.file,
   });
 
+  List<ImportAugmentationDirectiveState> get augmentations {
+    return _augmentations ??= file.unlinked2.augmentations.map((directive) {
+      return file._fileForRelativeUri(directive.uri).map(
+        (refFile) {
+          if (refFile != null) {
+            refFile.referencingFiles.add(file);
+            return ImportAugmentationDirectiveWithFile(
+              container: this,
+              directive: directive,
+              importedFile: refFile,
+            );
+          } else {
+            return ImportAugmentationDirectiveState(
+              container: this,
+              directive: directive,
+            );
+          }
+        },
+        (externalLibrary) {
+          return ImportAugmentationDirectiveState(
+            container: this,
+            directive: directive,
+          );
+        },
+      );
+    }).toList();
+  }
+
   List<ExportDirectiveState> get exports {
     return _exports ??= file.unlinked2.exports.map((directive) {
       final uriStr = file._selectRelativeUri(directive);
@@ -1782,12 +1828,56 @@
   /// we register available objects.
   @visibleForTesting
   void discoverReferencedFiles() {
-    imports;
     exports;
+    imports;
+    for (final import in augmentations) {
+      if (import is ImportAugmentationDirectiveWithFile) {
+        import.importedAugmentation?.discoverReferencedFiles();
+      }
+    }
+  }
+
+  @override
+  void dispose() {
+    final augmentations = _augmentations;
+    if (augmentations != null) {
+      for (final import in augmentations) {
+        if (import is ImportAugmentationDirectiveWithFile) {
+          import.importedFile.referencingFiles.remove(file);
+        }
+      }
+    }
+
+    final exports = _exports;
+    if (exports != null) {
+      for (final export in exports) {
+        if (export is ExportDirectiveWithFile) {
+          export.exportedFile.referencingFiles.remove(file);
+        }
+      }
+    }
+
+    final imports = _imports;
+    if (imports != null) {
+      for (final import in imports) {
+        if (import is ImportDirectiveWithFile) {
+          import.importedFile.referencingFiles.remove(file);
+        }
+      }
+    }
+
+    super.dispose();
   }
 
   bool hasAugmentation(AugmentationFileStateKind augmentation) {
-    return file.augmentationFiles.contains(augmentation.file);
+    for (final import in augmentations) {
+      if (import is ImportAugmentationDirectiveWithFile) {
+        if (import.importedFile == augmentation.file) {
+          return true;
+        }
+      }
+    }
+    return false;
   }
 }
 
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 b11d66d..242801d 100644
--- a/pkg/analyzer/test/src/dart/analysis/analyzer_state_printer.dart
+++ b/pkg/analyzer/test/src/dart/analysis/analyzer_state_printer.dart
@@ -92,13 +92,31 @@
     _indent = indent;
   }
 
-  /// TODO(scheglov) Support unresolved URIs, not augmentations, etc.
-  void _writeAugmentations(LibraryOrAugmentationFileKind kind) {
-    final files = kind.file.augmentationFiles.whereNotNull();
-    if (files.isNotEmpty) {
-      final keys = files.map(idProvider.fileState).join(' ');
-      _writelnWithIndent('augmentations: $keys');
-    }
+  void _writeAugmentations(LibraryOrAugmentationFileKind container) {
+    _writeElements<ImportAugmentationDirectiveState>(
+      'augmentations',
+      container.augmentations,
+      (augmentation) {
+        expect(augmentation.container, same(container));
+        if (augmentation is ImportAugmentationDirectiveWithFile) {
+          final file = augmentation.importedFile;
+          sink.write(_indent);
+
+          final importedAugmentation = augmentation.importedAugmentation;
+          if (importedAugmentation != null) {
+            expect(importedAugmentation.file, file);
+            sink.write(idProvider.fileStateKind(importedAugmentation));
+          } else {
+            sink.write('notAugmentation ${idProvider.fileState(file)}');
+          }
+          sink.writeln();
+        } else {
+          sink.write(_indent);
+          sink.write('uri: ${_stringOfUriStr(augmentation.directive.uri)}');
+          sink.writeln();
+        }
+      },
+    );
   }
 
   void _writeByteStore() {
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 b121f74..5ac5829 100644
--- a/pkg/analyzer/test/src/dart/analysis/file_state_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/file_state_test.dart
@@ -686,7 +686,8 @@
       kind: library_0
         imports
           library_3 dart:core synthetic
-        augmentations: file_1
+        augmentations
+          augmentation_1
         cycle_0
           dependencies: dart:core
           libraries: library_0
@@ -701,7 +702,8 @@
         library: library_0
         imports
           library_3 dart:core synthetic
-        augmentations: file_2
+        augmentations
+          augmentation_2
       referencingFiles: file_0
       unlinkedKey: k01
   /home/test/lib/c.dart
@@ -756,7 +758,8 @@
         uriFile: file_0
         imports
           library_3 dart:core synthetic
-        augmentations: file_2
+        augmentations
+          augmentation_2
       unlinkedKey: k01
   /home/test/lib/c.dart
     uri: package:test/c.dart
@@ -799,7 +802,8 @@
       kind: library_0
         imports
           library_3 dart:core synthetic
-        augmentations: file_1
+        augmentations
+          augmentation_1
         cycle_0
           dependencies: dart:core
           libraries: library_0
@@ -853,7 +857,8 @@
       kind: library_0
         imports
           library_2 dart:core synthetic
-        augmentations: file_1
+        augmentations
+          notAugmentation file_1
         cycle_0
           dependencies: dart:core
           libraries: library_0
@@ -867,7 +872,8 @@
         augmented: augmentation_1
         imports
           library_2 dart:core synthetic
-        augmentations: file_1
+        augmentations
+          augmentation_1
       referencingFiles: file_0 file_1
       unlinkedKey: k01
 libraryCycles
@@ -901,7 +907,8 @@
       kind: library_0
         imports
           library_3 dart:core synthetic
-        augmentations: file_1
+        augmentations
+          augmentation_1
         cycle_0
           dependencies: dart:core
           libraries: library_0
@@ -916,7 +923,8 @@
         library: library_0
         imports
           library_3 dart:core synthetic
-        augmentations: file_2
+        augmentations
+          augmentation_2
       referencingFiles: file_0 file_2
       unlinkedKey: k01
   /home/test/lib/c.dart
@@ -928,7 +936,8 @@
         library: library_0
         imports
           library_3 dart:core synthetic
-        augmentations: file_1
+        augmentations
+          notAugmentation file_1
       referencingFiles: file_1
       unlinkedKey: k02
 libraryCycles
@@ -936,7 +945,7 @@
 ''');
   }
 
-  test_newFile_augmentation_invalid() async {
+  test_newFile_augmentation_invalidRelativeUri() async {
     final a = newFile('$testPackageLibPath/a.dart', r'''
 library augment 'da:';
 ''');
@@ -978,7 +987,8 @@
       kind: library_0
         imports
           library_2 dart:core synthetic
-        augmentations: file_1
+        augmentations
+          augmentation_1
         cycle_0
           dependencies: dart:core
           libraries: library_0
@@ -1130,7 +1140,8 @@
       kind: library_0
         imports
           library_2 dart:core synthetic
-        augmentations: file_1
+        augmentations
+          augmentation_1
         cycle_0
           dependencies: dart:core
           libraries: library_0
@@ -1162,7 +1173,8 @@
       kind: library_0
         imports
           library_2 dart:core synthetic
-        augmentations: file_1
+        augmentations
+          augmentation_1
         cycle_0
           dependencies: dart:core
           libraries: library_0
@@ -1175,7 +1187,8 @@
       kind: library_7
         imports
           library_2 dart:core synthetic
-        augmentations: file_1
+        augmentations
+          notAugmentation file_1
         cycle_2
           dependencies: dart:core
           libraries: library_7
@@ -1207,7 +1220,8 @@
       kind: library_0
         imports
           library_2 dart:core synthetic
-        augmentations: file_1
+        augmentations
+          augmentation_1
         cycle_0
           dependencies: dart:core
           libraries: library_0
@@ -1220,7 +1234,8 @@
       kind: library_8
         imports
           library_2 dart:core synthetic
-        augmentations: file_1
+        augmentations
+          notAugmentation file_1
         cycle_3
           dependencies: dart:core
           libraries: library_8
@@ -1266,7 +1281,8 @@
       kind: library_8
         imports
           library_2 dart:core synthetic
-        augmentations: file_1
+        augmentations
+          notAugmentation file_1
         cycle_3
           dependencies: dart:core
           libraries: library_8
@@ -1354,7 +1370,8 @@
       kind: library_11
         imports
           library_2 dart:core synthetic
-        augmentations: file_1
+        augmentations
+          notAugmentation file_1
         cycle_6
           dependencies: dart:core
           libraries: library_11
@@ -1388,7 +1405,8 @@
       kind: library_12
         imports
           library_2 dart:core synthetic
-        augmentations: file_1
+        augmentations
+          augmentation_1
         cycle_7
           dependencies: dart:core
           libraries: library_12
@@ -1401,7 +1419,8 @@
       kind: library_11
         imports
           library_2 dart:core synthetic
-        augmentations: file_1
+        augmentations
+          notAugmentation file_1
         cycle_6
           dependencies: dart:core
           libraries: library_11
@@ -1451,6 +1470,62 @@
 ''');
   }
 
+  test_newFile_library_augmentations_invalidRelativeUri() async {
+    final a = newFile('$testPackageLibPath/a.dart', r'''
+import augment 'da:';
+''');
+
+    fileStateFor(a);
+
+    assertDriverStateString(testFile, r'''
+files
+  /home/test/lib/a.dart
+    uri: package:test/a.dart
+    current
+      id: file_0
+      kind: library_0
+        imports
+          library_1 dart:core synthetic
+        augmentations
+          uri: da:
+        cycle_0
+          dependencies: dart:core
+          libraries: library_0
+          apiSignature_0
+      unlinkedKey: k00
+libraryCycles
+elementFactory
+''');
+  }
+
+  test_newFile_library_augmentations_invalidRelativeUri_empty() {
+    final a = newFile('$testPackageLibPath/a.dart', r'''
+import augment '';
+''');
+
+    fileStateFor(a);
+
+    assertDriverStateString(testFile, r'''
+files
+  /home/test/lib/a.dart
+    uri: package:test/a.dart
+    current
+      id: file_0
+      kind: library_0
+        imports
+          library_1 dart:core synthetic
+        augmentations
+          uri: ''
+        cycle_0
+          dependencies: dart:core
+          libraries: library_0
+          apiSignature_0
+      unlinkedKey: k00
+libraryCycles
+elementFactory
+''');
+  }
+
   test_newFile_library_dartCore() async {
     final core = fsStateFor(testFile).getFileForUri(
       Uri.parse('dart:core'),
@@ -3900,7 +3975,8 @@
       kind: library_0
         imports
           library_2 dart:core synthetic
-        augmentations: file_1
+        augmentations
+          augmentation_1
         cycle_0
           dependencies: dart:core
           libraries: library_0
@@ -3927,6 +4003,7 @@
 
     // Not an augmentation anymore, but a library.
     // But `a.dart` still uses `b.dart` as an augmentation.
+    // TODO(scheglov) Any `augmentation_to_X` should change the signature.
     assertDriverStateString(testFile, r'''
 files
   /home/test/lib/a.dart
@@ -3936,7 +4013,8 @@
       kind: library_0
         imports
           library_2 dart:core synthetic
-        augmentations: file_1
+        augmentations
+          notAugmentation file_1
         cycle_0
           dependencies: dart:core
           libraries: library_0
@@ -3970,7 +4048,8 @@
       kind: library_8
         imports
           library_2 dart:core synthetic
-        augmentations: file_1
+        augmentations
+          notAugmentation file_1
         cycle_3
           dependencies: dart:core
           libraries: library_8
@@ -4016,7 +4095,8 @@
         name: my.lib
         imports
           library_2 dart:core synthetic
-        augmentations: file_1
+        augmentations
+          augmentation_1
         cycle_0
           dependencies: dart:core
           libraries: library_0
@@ -4059,7 +4139,8 @@
         name: my.lib
         imports
           library_2 dart:core synthetic
-        augmentations: file_1
+        augmentations
+          notAugmentation file_1
         cycle_0
           dependencies: dart:core
           libraries: library_0
@@ -4090,7 +4171,8 @@
         name: my.lib
         imports
           library_2 dart:core synthetic
-        augmentations: file_1
+        augmentations
+          notAugmentation file_1
         cycle_2
           dependencies: dart:core
           libraries: library_8
@@ -4167,7 +4249,8 @@
       kind: library_0
         imports
           library_2 dart:core synthetic
-        augmentations: file_1
+        augmentations
+          augmentation_1
         cycle_0
           dependencies: dart:core
           libraries: library_0
@@ -4206,7 +4289,8 @@
       kind: library_0
         imports
           library_2 dart:core synthetic
-        augmentations: file_1
+        augmentations
+          notAugmentation file_1
         cycle_0
           dependencies: dart:core
           libraries: library_0
@@ -4235,7 +4319,8 @@
       kind: library_8
         imports
           library_2 dart:core synthetic
-        augmentations: file_1
+        augmentations
+          notAugmentation file_1
         cycle_2
           dependencies: dart:core
           libraries: library_8
@@ -4511,7 +4596,8 @@
       kind: library_0
         imports
           library_2 dart:core synthetic
-        augmentations: file_1
+        augmentations
+          notAugmentation file_1
         cycle_0
           dependencies: dart:core
           libraries: library_0
@@ -4551,7 +4637,8 @@
       kind: library_0
         imports
           library_2 dart:core synthetic
-        augmentations: file_1
+        augmentations
+          augmentation_7
         cycle_0
           dependencies: dart:core
           libraries: library_0