Add LibraryOrAugmentationFileKind.exports/imports

These will replace eventually FileState.exported/importedFiles,
because only libraries or augmentation can have them. Also, we will
build Export/ImportElement(s) from them, when google3 switches to
using AnalysisDriver.buildPackageBundle(), so we are free to make
changes to the way linker works.

Change-Id: I66674dc7720aa704cfc36d16fc78e17dd05a8b93
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/246681
Commit-Queue: Konstantin Shcheglov <scheglov@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 17a79a5..3aa510f 100644
--- a/pkg/analyzer/lib/src/dart/analysis/driver.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart
@@ -86,7 +86,7 @@
 /// TODO(scheglov) Clean up the list of implicitly analyzed files.
 class AnalysisDriver implements AnalysisDriverGeneric {
   /// The version of data format, should be incremented on every format change.
-  static const int DATA_VERSION = 221;
+  static const int DATA_VERSION = 222;
 
   /// The number of exception contexts allowed to write. Once this field is
   /// zero, we stop writing any new exception contexts in this process.
diff --git a/pkg/analyzer/lib/src/dart/analysis/file_state.dart b/pkg/analyzer/lib/src/dart/analysis/file_state.dart
index a6763f8..66b5ac0 100644
--- a/pkg/analyzer/lib/src/dart/analysis/file_state.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/file_state.dart
@@ -85,6 +85,75 @@
   });
 }
 
+/// Information about a single `import` directive.
+class ExportDirectiveState {
+  final UnlinkedNamespaceDirective directive;
+
+  ExportDirectiveState({
+    required this.directive,
+  });
+
+  /// If [exportedSource] corresponds to a library, returns it.
+  Source? get exportedLibrarySource => null;
+
+  /// Returns a [Source] that is referenced by this directive. If the are
+  /// configurations, selects the one which satisfies the conditions.
+  ///
+  /// Returns `null` if the selected URI is not valid, or cannot be resolved
+  /// into a [Source].
+  Source? get exportedSource => null;
+}
+
+/// [ExportDirectiveState] that has a valid URI that references a file.
+class ExportDirectiveWithFile extends ExportDirectiveState {
+  final FileState exportedFile;
+
+  ExportDirectiveWithFile({
+    required super.directive,
+    required this.exportedFile,
+  });
+
+  /// Returns [exportedFile] if it is a library.
+  LibraryFileStateKind? get exportedLibrary {
+    final kind = exportedFile.kind;
+    if (kind is LibraryFileStateKind) {
+      return kind;
+    }
+    return null;
+  }
+
+  @override
+  Source? get exportedLibrarySource {
+    if (exportedFile.kind is LibraryFileStateKind) {
+      return exportedSource;
+    }
+    return null;
+  }
+
+  @override
+  Source get exportedSource => exportedFile.source;
+}
+
+/// [ExportDirectiveState] with a URI that resolves to [InSummarySource].
+class ExportDirectiveWithInSummarySource extends ExportDirectiveState {
+  @override
+  final InSummarySource exportedSource;
+
+  ExportDirectiveWithInSummarySource({
+    required super.directive,
+    required this.exportedSource,
+  });
+
+  @override
+  Source? get exportedLibrarySource {
+    if (exportedSource.kind == InSummarySourceKind.library) {
+      return exportedSource;
+    } else {
+      return null;
+    }
+  }
+}
+
 /// A library from [SummaryDataStore].
 class ExternalLibrary {
   final InSummarySource source;
@@ -587,6 +656,7 @@
     return unit;
   }
 
+  /// TODO(scheglov) move to _fsState?
   String _selectRelativeUri(UnlinkedNamespaceDirective directive) {
     for (var configuration in directive.configurations) {
       var name = configuration.name;
@@ -805,6 +875,7 @@
       imports.add(
         UnlinkedNamespaceDirective(
           configurations: [],
+          isSyntheticDartCoreImport: true,
           uri: 'dart:core',
         ),
       );
@@ -1193,6 +1264,77 @@
   bool get isSrc => (_flags & _isSrc) != 0;
 }
 
+/// Information about a single `import` directive.
+class ImportDirectiveState {
+  final UnlinkedNamespaceDirective directive;
+
+  ImportDirectiveState({
+    required this.directive,
+  });
+
+  /// If [importedSource] corresponds to a library, returns it.
+  Source? get importedLibrarySource => null;
+
+  /// Returns a [Source] that is referenced by this directive. If the are
+  /// configurations, selects the one which satisfies the conditions.
+  ///
+  /// Returns `null` if the selected URI is not valid, or cannot be resolved
+  /// into a [Source].
+  Source? get importedSource => null;
+
+  bool get isSyntheticDartCoreImport => directive.isSyntheticDartCoreImport;
+}
+
+/// [ImportDirectiveState] that has a valid URI that references a file.
+class ImportDirectiveWithFile extends ImportDirectiveState {
+  final FileState importedFile;
+
+  ImportDirectiveWithFile({
+    required super.directive,
+    required this.importedFile,
+  });
+
+  /// Returns [importedFile] if it is a library.
+  LibraryFileStateKind? get importedLibrary {
+    final kind = importedFile.kind;
+    if (kind is LibraryFileStateKind) {
+      return kind;
+    }
+    return null;
+  }
+
+  @override
+  Source? get importedLibrarySource {
+    if (importedFile.kind is LibraryFileStateKind) {
+      return importedSource;
+    }
+    return null;
+  }
+
+  @override
+  Source get importedSource => importedFile.source;
+}
+
+/// [ImportDirectiveState] with a URI that resolves to [InSummarySource].
+class ImportDirectiveWithInSummarySource extends ImportDirectiveState {
+  @override
+  final InSummarySource importedSource;
+
+  ImportDirectiveWithInSummarySource({
+    required super.directive,
+    required this.importedSource,
+  });
+
+  @override
+  Source? get importedLibrarySource {
+    if (importedSource.kind == InSummarySourceKind.library) {
+      return importedSource;
+    } else {
+      return null;
+    }
+  }
+}
+
 class LibraryFileStateKind extends LibraryOrAugmentationFileKind {
   /// The name of the library from the `library` directive.
   /// Or `null` if no `library` directive.
@@ -1209,10 +1351,67 @@
 }
 
 abstract class LibraryOrAugmentationFileKind extends FileStateKind {
+  List<ExportDirectiveState>? _exports;
+  List<ImportDirectiveState>? _imports;
+
   LibraryOrAugmentationFileKind({
     required super.file,
   });
 
+  List<ExportDirectiveState> get exports {
+    return _exports ??= file.unlinked2.exports.map((directive) {
+      final uriStr = file._selectRelativeUri(directive);
+      return file._fileForRelativeUri(uriStr).map(
+        (refFile) {
+          if (refFile != null) {
+            refFile.referencingFiles.add(file);
+            return ExportDirectiveWithFile(
+              directive: directive,
+              exportedFile: refFile,
+            );
+          } else {
+            return ExportDirectiveState(
+              directive: directive,
+            );
+          }
+        },
+        (externalLibrary) {
+          return ExportDirectiveWithInSummarySource(
+            directive: directive,
+            exportedSource: externalLibrary.source,
+          );
+        },
+      );
+    }).toList();
+  }
+
+  List<ImportDirectiveState> get imports {
+    return _imports ??= file.unlinked2.imports.map((directive) {
+      final uriStr = file._selectRelativeUri(directive);
+      return file._fileForRelativeUri(uriStr).map(
+        (refFile) {
+          if (refFile != null) {
+            refFile.referencingFiles.add(file);
+            return ImportDirectiveWithFile(
+              directive: directive,
+              importedFile: refFile,
+            );
+          } else {
+            return ImportDirectiveState(
+              directive: directive,
+            );
+          }
+        },
+        (externalLibrary) {
+          return ImportDirectiveWithInSummarySource(
+            directive: directive,
+            importedSource: externalLibrary.source,
+          );
+        },
+      );
+    }).toList();
+  }
+
   bool hasAugmentation(AugmentationFileStateKind augmentation) {
     return file.augmentationFiles.contains(augmentation.file);
   }
diff --git a/pkg/analyzer/lib/src/dart/analysis/unlinked_data.dart b/pkg/analyzer/lib/src/dart/analysis/unlinked_data.dart
index 605461c..d9a7714 100644
--- a/pkg/analyzer/lib/src/dart/analysis/unlinked_data.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/unlinked_data.dart
@@ -159,12 +159,15 @@
   /// The configurations that control which library will actually be used.
   final List<UnlinkedNamespaceDirectiveConfiguration> configurations;
 
+  final bool isSyntheticDartCoreImport;
+
   /// The URI referenced by this directive, nad used by default when none
   /// of the [configurations] matches.
   final String uri;
 
   UnlinkedNamespaceDirective({
     required this.configurations,
+    this.isSyntheticDartCoreImport = false,
     required this.uri,
   });
 
@@ -174,6 +177,7 @@
         () => UnlinkedNamespaceDirectiveConfiguration.read(reader),
       ),
       uri: reader.readStringUtf8(),
+      isSyntheticDartCoreImport: reader.readBool(),
     );
   }
 
@@ -185,6 +189,7 @@
       },
     );
     sink.writeStringUtf8(uri);
+    sink.writeBool(isSyntheticDartCoreImport);
   }
 }
 
diff --git a/pkg/analyzer/lib/src/dart/micro/library_graph.dart b/pkg/analyzer/lib/src/dart/micro/library_graph.dart
index f560b03..4a51ff1 100644
--- a/pkg/analyzer/lib/src/dart/micro/library_graph.dart
+++ b/pkg/analyzer/lib/src/dart/micro/library_graph.dart
@@ -975,6 +975,7 @@
       imports.add(
         UnlinkedNamespaceDirective(
           configurations: [],
+          isSyntheticDartCoreImport: true,
           uri: 'dart:core',
         ),
       );
diff --git a/pkg/analyzer/lib/src/summary/package_bundle_reader.dart b/pkg/analyzer/lib/src/summary/package_bundle_reader.dart
index 34780d5..8648d55 100644
--- a/pkg/analyzer/lib/src/summary/package_bundle_reader.dart
+++ b/pkg/analyzer/lib/src/summary/package_bundle_reader.dart
@@ -65,7 +65,13 @@
   /// The summary file where this source was defined.
   final String summaryPath;
 
-  InSummarySource(super.uri, this.summaryPath);
+  final InSummarySourceKind kind;
+
+  InSummarySource({
+    required Uri uri,
+    required this.summaryPath,
+    required this.kind,
+  }) : super(uri);
 
   @override
   TimestampedData<String> get contents => TimestampedData<String>(0, '');
@@ -77,6 +83,8 @@
   String toString() => uri.toString();
 }
 
+enum InSummarySourceKind { library, part }
+
 /// The [UriResolver] that knows about sources that are served from their
 /// summaries.
 class InSummaryUriResolver extends UriResolver {
@@ -92,7 +100,13 @@
     String uriString = uri.toString();
     String? summaryPath = _dataStore.uriToSummaryPath[uriString];
     if (summaryPath != null) {
-      return InSummarySource(uri, summaryPath);
+      final isLibrary = _dataStore._libraryUris.contains(uriString);
+      return InSummarySource(
+        uri: uri,
+        summaryPath: summaryPath,
+        kind:
+            isLibrary ? InSummarySourceKind.library : InSummarySourceKind.part,
+      );
     }
     return null;
   }
diff --git a/pkg/analyzer/lib/src/workspace/simple.dart b/pkg/analyzer/lib/src/workspace/simple.dart
index dc9b694..6430e61 100644
--- a/pkg/analyzer/lib/src/workspace/simple.dart
+++ b/pkg/analyzer/lib/src/workspace/simple.dart
@@ -43,11 +43,10 @@
     DartSdk? sdk,
     SummaryDataStore? summaryData,
   ) {
-    if (summaryData != null) {
-      throw UnsupportedError(
-          'Summary files are not supported in a Pub workspace.');
-    }
     List<UriResolver> resolvers = <UriResolver>[];
+    if (summaryData != null) {
+      resolvers.add(InSummaryUriResolver(summaryData));
+    }
     if (sdk != null) {
       resolvers.add(DartUriResolver(sdk));
     }
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 c0b0ac1..8dd3040 100644
--- a/pkg/analyzer/test/src/dart/analysis/file_state_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/file_state_test.dart
@@ -7,6 +7,7 @@
 
 import 'package:analyzer/dart/analysis/declared_variables.dart';
 import 'package:analyzer/dart/analysis/features.dart';
+import 'package:analyzer/dart/sdk/build_sdk_summary.dart';
 import 'package:analyzer/file_system/file_system.dart';
 import 'package:analyzer/src/context/packages.dart';
 import 'package:analyzer/src/dart/analysis/byte_store.dart';
@@ -21,12 +22,15 @@
     show AnalysisOptions, AnalysisOptionsImpl;
 import 'package:analyzer/src/generated/source.dart';
 import 'package:analyzer/src/source/package_map_resolver.dart';
+import 'package:analyzer/src/summary/package_bundle_reader.dart';
 import 'package:analyzer/src/test_utilities/mock_sdk.dart';
 import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
 import 'package:analyzer/src/util/either.dart';
 import 'package:analyzer/src/workspace/basic.dart';
+import 'package:analyzer_utilities/check/check.dart';
 import 'package:convert/convert.dart';
 import 'package:crypto/crypto.dart';
+import 'package:meta/meta.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
@@ -121,10 +125,31 @@
 
 @reflectiveTest
 class FileSystemState_PubPackageTest extends PubPackageResolutionTest {
+  FileState get _dartAsyncState {
+    return fileStateForUriStr('dart:async');
+  }
+
+  FileState get _dartCoreState {
+    return fileStateForUriStr('dart:core');
+  }
+
+  FileState get _dartMathState {
+    return fileStateForUriStr('dart:math');
+  }
+
   FileState fileStateFor(File file) {
     return fsStateFor(file).getFileForPath(file.path);
   }
 
+  FileState fileStateForUri(Uri uri) {
+    return fsStateFor(testFile).getFileForUri(uri).t1!;
+  }
+
+  FileState fileStateForUriStr(String uriStr) {
+    final uri = Uri.parse(uriStr);
+    return fileStateForUri(uri);
+  }
+
   FileSystemState fsStateFor(File file) {
     return driverFor(file.path).fsState;
   }
@@ -539,6 +564,487 @@
     });
   }
 
+  test_newFile_library_exports_dart() async {
+    final a = newFile('$testPackageLibPath/a.dart', r'''
+export 'dart:async';
+export 'dart:math';
+''');
+
+    final aState = fileStateFor(a);
+    aState.assertKind((kind) {
+      kind as LibraryFileStateKind;
+      check(kind.exports).matches([
+        (export) => export.isLibrary(_dartAsyncState),
+        (export) => export.isLibrary(_dartMathState),
+      ]);
+    });
+  }
+
+  test_newFile_library_exports_inSummary_library() async {
+    // Prepare a bundle where `package:foo/foo.dart` is a library.
+    final librarySummaryFiles = <File>[];
+    {
+      final fooRoot = getFolder('$workspaceRootPath/foo');
+
+      newFile('${fooRoot.path}/lib/foo.dart', 'class F {}');
+
+      final fooPackageConfigFile = getFile(
+        '${fooRoot.path}/.dart_tool/package_config.json',
+      );
+
+      writePackageConfig(
+        fooPackageConfigFile.path,
+        PackageConfigFileBuilder()..add(name: 'foo', rootPath: fooRoot.path),
+      );
+
+      final analysisDriver = driverFor(fooRoot.path);
+      final bundleBytes = await analysisDriver.buildPackageBundle(
+        uriList: [
+          Uri.parse('package:foo/foo.dart'),
+        ],
+      );
+
+      final bundleFile =
+          resourceProvider.getFile('/home/summaries/packages.sum');
+      bundleFile.writeAsBytesSync(bundleBytes);
+
+      librarySummaryFiles.add(bundleFile);
+
+      // Delete, so it is not available as a file.
+      // We don't have a package config for it anyway, but just to be sure.
+      fooRoot.delete();
+    }
+
+    // Prepare for recreating the collection, with summaries.
+    sdkSummaryFile = await _writeSdkSummary();
+    this.librarySummaryFiles = librarySummaryFiles;
+
+    disposeAnalysisContextCollection();
+
+    final a = newFile('$testPackageLibPath/a.dart', r'''
+export 'dart:async';
+export 'package:foo/foo.dart';
+export 'b.dart';
+''');
+
+    final b = getFile('$testPackageLibPath/b.dart');
+
+    final aState = fileStateFor(a);
+    final bState = fileStateFor(b);
+
+    aState.assertKind((kind) {
+      kind as LibraryFileStateKind;
+      check(kind.exports).matches([
+        (import) {
+          final expected = Uri.parse('dart:async');
+          import.withInSummaryLibrary(expected);
+        },
+        (import) {
+          final expected = Uri.parse('package:foo/foo.dart');
+          import.withInSummaryLibrary(expected);
+        },
+        (import) => import.isLibrary(bState),
+      ]);
+    });
+  }
+
+  test_newFile_library_exports_inSummary_part() async {
+    // Prepare a bundle where `package:foo/foo2.dart` is a part.
+    final librarySummaryFiles = <File>[];
+    {
+      final fooRoot = getFolder('$workspaceRootPath/foo');
+
+      newFile('${fooRoot.path}/lib/foo.dart', r'''
+part 'foo2.dart';
+''');
+
+      newFile('${fooRoot.path}/lib/foo2.dart', r'''
+part of 'foo.dart';
+''');
+
+      final fooPackageConfigFile = getFile(
+        '${fooRoot.path}/.dart_tool/package_config.json',
+      );
+
+      writePackageConfig(
+        fooPackageConfigFile.path,
+        PackageConfigFileBuilder()..add(name: 'foo', rootPath: fooRoot.path),
+      );
+
+      final analysisDriver = driverFor(fooRoot.path);
+      final bundleBytes = await analysisDriver.buildPackageBundle(
+        uriList: [
+          Uri.parse('package:foo/foo.dart'),
+        ],
+      );
+
+      final bundleFile =
+          resourceProvider.getFile('/home/summaries/packages.sum');
+      bundleFile.writeAsBytesSync(bundleBytes);
+
+      librarySummaryFiles.add(bundleFile);
+
+      // Delete, so it is not available as a file.
+      // We don't have a package config for it anyway, but just to be sure.
+      fooRoot.delete();
+    }
+
+    // Prepare for recreating the collection, with summaries.
+    sdkSummaryFile = await _writeSdkSummary();
+    this.librarySummaryFiles = librarySummaryFiles;
+
+    disposeAnalysisContextCollection();
+
+    final a = newFile('$testPackageLibPath/a.dart', r'''
+export 'package:foo/foo2.dart';
+export 'b.dart';
+''');
+
+    final b = getFile('$testPackageLibPath/b.dart');
+
+    final aState = fileStateFor(a);
+    final bState = fileStateFor(b);
+
+    aState.assertKind((kind) {
+      kind as LibraryFileStateKind;
+      check(kind.exports).matches([
+        (export) {
+          final expected = Uri.parse('package:foo/foo2.dart');
+          export.withInSummaryNotLibrary(expected);
+        },
+        (export) => export.isLibrary(bState),
+      ]);
+    });
+  }
+
+  test_newFile_library_exports_invalidRelativeUri() async {
+    final a = newFile('$testPackageLibPath/a.dart', r'''
+export '::net';
+''');
+
+    final aState = fileStateFor(a);
+    aState.assertKind((kind) {
+      kind as LibraryFileStateKind;
+      check(kind.exports).matches([
+        (export) => export.isNotFile(),
+      ]);
+    });
+  }
+
+  test_newFile_library_exports_package() async {
+    final a = newFile('$testPackageLibPath/a.dart', '');
+    final b = newFile('$testPackageLibPath/b.dart', '');
+
+    final c = newFile('$testPackageLibPath/c.dart', r'''
+export 'a.dart';
+export 'package:test/b.dart';
+''');
+
+    final aState = fileStateFor(a);
+    final bState = fileStateFor(b);
+    final cState = fileStateFor(c);
+
+    cState.assertKind((kind) {
+      kind as LibraryFileStateKind;
+      check(kind.exports).matches([
+        (export) => export.isLibrary(aState),
+        (export) => export.isLibrary(bState),
+      ]);
+    });
+  }
+
+  test_newFile_library_exports_part() async {
+    final a = newFile('$testPackageLibPath/a.dart', r'''
+part of my.lib;
+''');
+
+    final b = newFile('$testPackageLibPath/b.dart', r'''
+export 'a.dart';
+''');
+
+    final aState = fileStateFor(a);
+    final bState = fileStateFor(b);
+
+    bState.assertKind((kind) {
+      kind as LibraryFileStateKind;
+      check(kind.exports).matches([
+        (export) => export.isFile(aState),
+      ]);
+    });
+  }
+
+  test_newFile_library_imports_library_augmentation() async {
+    final b = newFile('$testPackageLibPath/b.dart', r'''
+library augment 'a.dart';
+''');
+
+    final c = newFile('$testPackageLibPath/c.dart', r'''
+import 'b.dart';
+''');
+
+    final bState = fileStateFor(b);
+    final cState = fileStateFor(c);
+
+    cState.assertKind((kind) {
+      kind as LibraryFileStateKind;
+      check(kind.imports).matches([
+        (import) => import.isFile(bState),
+        (import) => import
+          ..isLibrary(_dartCoreState)
+          ..isSyntheticDartCoreImport.isTrue,
+      ]);
+    });
+  }
+
+  test_newFile_library_imports_library_dart() async {
+    final a = newFile('$testPackageLibPath/a.dart', r'''
+import 'dart:async';
+import 'dart:math';
+''');
+
+    final aState = fileStateFor(a);
+    aState.assertKind((kind) {
+      kind as LibraryFileStateKind;
+      check(kind.imports).matches([
+        (import) => import.isLibrary(_dartAsyncState),
+        (import) => import.isLibrary(_dartMathState),
+        (import) => import
+          ..isLibrary(_dartCoreState)
+          ..isSyntheticDartCoreImport.isTrue,
+      ]);
+    });
+  }
+
+  test_newFile_library_imports_library_dart_explicitDartCore() async {
+    final a = newFile('$testPackageLibPath/a.dart', r'''
+import 'dart:core';
+import 'dart:math';
+''');
+
+    final aState = fileStateFor(a);
+    aState.assertKind((kind) {
+      kind as LibraryFileStateKind;
+      check(kind.imports).matches([
+        (import) => import
+          ..isLibrary(_dartCoreState)
+          ..isSyntheticDartCoreImport.isFalse,
+        (import) => import.isLibrary(_dartMathState),
+      ]);
+    });
+  }
+
+  test_newFile_library_imports_library_inSummary_library() async {
+    // Prepare a bundle where `package:foo/foo.dart` is a library.
+    final librarySummaryFiles = <File>[];
+    {
+      final fooRoot = getFolder('$workspaceRootPath/foo');
+
+      newFile('${fooRoot.path}/lib/foo.dart', 'class F {}');
+
+      final fooPackageConfigFile = getFile(
+        '${fooRoot.path}/.dart_tool/package_config.json',
+      );
+
+      writePackageConfig(
+        fooPackageConfigFile.path,
+        PackageConfigFileBuilder()..add(name: 'foo', rootPath: fooRoot.path),
+      );
+
+      final analysisDriver = driverFor(fooRoot.path);
+      final bundleBytes = await analysisDriver.buildPackageBundle(
+        uriList: [
+          Uri.parse('package:foo/foo.dart'),
+        ],
+      );
+
+      final bundleFile =
+          resourceProvider.getFile('/home/summaries/packages.sum');
+      bundleFile.writeAsBytesSync(bundleBytes);
+
+      librarySummaryFiles.add(bundleFile);
+
+      // Delete, so it is not available as a file.
+      // We don't have a package config for it anyway, but just to be sure.
+      fooRoot.delete();
+    }
+
+    // Prepare for recreating the collection, with summaries.
+    sdkSummaryFile = await _writeSdkSummary();
+    this.librarySummaryFiles = librarySummaryFiles;
+
+    disposeAnalysisContextCollection();
+
+    final a = newFile('$testPackageLibPath/a.dart', r'''
+import 'dart:async';
+import 'package:foo/foo.dart';
+import 'b.dart';
+''');
+
+    final b = getFile('$testPackageLibPath/b.dart');
+
+    final aState = fileStateFor(a);
+    final bState = fileStateFor(b);
+
+    aState.assertKind((kind) {
+      kind as LibraryFileStateKind;
+      check(kind.imports).matches([
+        (import) {
+          final expected = Uri.parse('dart:async');
+          import.withInSummaryLibrary(expected);
+        },
+        (import) {
+          final expected = Uri.parse('package:foo/foo.dart');
+          import.withInSummaryLibrary(expected);
+        },
+        (import) => import.isLibrary(bState),
+        (import) {
+          final expected = Uri.parse('dart:core');
+          import
+            ..withInSummaryLibrary(expected)
+            ..isSyntheticDartCoreImport.isTrue;
+        },
+      ]);
+    });
+  }
+
+  test_newFile_library_imports_library_inSummary_part() async {
+    // Prepare a bundle where `package:foo/foo2.dart` is a part.
+    final librarySummaryFiles = <File>[];
+    {
+      final fooRoot = getFolder('$workspaceRootPath/foo');
+
+      newFile('${fooRoot.path}/lib/foo.dart', r'''
+part 'foo2.dart';
+''');
+
+      newFile('${fooRoot.path}/lib/foo2.dart', r'''
+part of 'foo.dart';
+''');
+
+      final fooPackageConfigFile = getFile(
+        '${fooRoot.path}/.dart_tool/package_config.json',
+      );
+
+      writePackageConfig(
+        fooPackageConfigFile.path,
+        PackageConfigFileBuilder()..add(name: 'foo', rootPath: fooRoot.path),
+      );
+
+      final analysisDriver = driverFor(fooRoot.path);
+      final bundleBytes = await analysisDriver.buildPackageBundle(
+        uriList: [
+          Uri.parse('package:foo/foo.dart'),
+        ],
+      );
+
+      final bundleFile =
+          resourceProvider.getFile('/home/summaries/packages.sum');
+      bundleFile.writeAsBytesSync(bundleBytes);
+
+      librarySummaryFiles.add(bundleFile);
+
+      // Delete, so it is not available as a file.
+      // We don't have a package config for it anyway, but just to be sure.
+      fooRoot.delete();
+    }
+
+    // Prepare for recreating the collection, with summaries.
+    sdkSummaryFile = await _writeSdkSummary();
+    this.librarySummaryFiles = librarySummaryFiles;
+
+    disposeAnalysisContextCollection();
+
+    final a = newFile('$testPackageLibPath/a.dart', r'''
+import 'package:foo/foo2.dart';
+import 'b.dart';
+''');
+
+    final b = getFile('$testPackageLibPath/b.dart');
+
+    final aState = fileStateFor(a);
+    final bState = fileStateFor(b);
+
+    aState.assertKind((kind) {
+      kind as LibraryFileStateKind;
+      check(kind.imports).matches([
+        (export) {
+          final expected = Uri.parse('package:foo/foo2.dart');
+          export.withInSummaryNotLibrary(expected);
+        },
+        (export) => export.isLibrary(bState),
+        (export) {
+          final expected = Uri.parse('dart:core');
+          export
+            ..withInSummaryLibrary(expected)
+            ..isSyntheticDartCoreImport.isTrue;
+        },
+      ]);
+    });
+  }
+
+  test_newFile_library_imports_library_invalidRelativeUri() async {
+    final a = newFile('$testPackageLibPath/a.dart', r'''
+import '::net';
+''');
+
+    final aState = fileStateFor(a);
+    aState.assertKind((kind) {
+      kind as LibraryFileStateKind;
+      check(kind.imports).matches([
+        (import) => import.isNotFile(),
+        (import) => import.isLibrary(_dartCoreState),
+      ]);
+    });
+  }
+
+  test_newFile_library_imports_library_package() async {
+    final a = newFile('$testPackageLibPath/a.dart', '');
+    final b = newFile('$testPackageLibPath/b.dart', '');
+
+    final c = newFile('$testPackageLibPath/c.dart', r'''
+import 'a.dart';
+import 'package:test/b.dart';
+''');
+
+    final aState = fileStateFor(a);
+    final bState = fileStateFor(b);
+    final cState = fileStateFor(c);
+
+    cState.assertKind((kind) {
+      kind as LibraryFileStateKind;
+      check(kind.imports).matches([
+        (import) => import.isLibrary(aState),
+        (import) => import.isLibrary(bState),
+        (import) => import
+          ..isLibrary(_dartCoreState)
+          ..isSyntheticDartCoreImport.isTrue,
+      ]);
+    });
+  }
+
+  test_newFile_library_imports_library_part() async {
+    final a = newFile('$testPackageLibPath/a.dart', r'''
+part of my.lib;
+''');
+
+    final b = newFile('$testPackageLibPath/b.dart', r'''
+import 'a.dart';
+''');
+
+    final aState = fileStateFor(a);
+    final bState = fileStateFor(b);
+
+    bState.assertKind((kind) {
+      kind as LibraryFileStateKind;
+      check(kind.imports).matches([
+        (import) => import.isFile(aState),
+        (import) => import
+          ..isLibrary(_dartCoreState)
+          ..isSyntheticDartCoreImport.isTrue,
+      ]);
+    });
+  }
+
   test_newFile_library_includePart_withoutPartOf() async {
     final a = newFile('$testPackageLibPath/a.dart', r'''
 part 'b.dart';
@@ -1609,6 +2115,16 @@
     }).toList();
     expect(actualFiles, expected);
   }
+
+  Future<File> _writeSdkSummary() async {
+    final file = resourceProvider.getFile('/home/summaries/sdk.sum');
+    final bytes = await buildSdkSummary2(
+      resourceProvider: resourceProvider,
+      sdkPath: sdkRoot.path,
+    );
+    file.writeAsBytesSync(bytes);
+    return file;
+  }
 }
 
 @reflectiveTest
@@ -2466,6 +2982,188 @@
   }
 }
 
+extension on CheckTarget<ImportDirectiveState> {
+  @useResult
+  CheckTarget<Source?> get importedLibrarySource {
+    return nest(
+      value.importedLibrarySource,
+      (selected) => 'importedLibrarySource ${valueStr(selected)}',
+    );
+  }
+
+  @useResult
+  CheckTarget<bool> get isSyntheticDartCoreImport {
+    return nest(
+      value.isSyntheticDartCoreImport,
+      (selected) => 'isSyntheticDartCoreImport ${valueStr(selected)}',
+    );
+  }
+
+  /// Is [ImportDirectiveWithFile], but not a library.
+  void isFile(FileState expected) {
+    this.isA<ImportDirectiveWithFile>()
+      ..importedFile.isIdenticalTo(expected)
+      ..importedSource.uri.isEqualTo(expected.uri)
+      ..importedLibrary.isNull
+      ..importedLibrarySource.isNull;
+  }
+
+  /// Is [ImportDirectiveWithFile], and is a library.
+  void isLibrary(FileState expected) {
+    final expectedKind = expected.kind as LibraryFileStateKind;
+    this.isA<ImportDirectiveWithFile>()
+      ..importedFile.isIdenticalTo(expected)
+      ..importedSource.uri.isEqualTo(expected.uri)
+      ..importedLibrary.isNotNull.isIdenticalTo(expectedKind)
+      ..importedLibrarySource.isNotNull.uri.isEqualTo(expected.uri);
+  }
+
+  /// Exactly [ImportDirectiveState], even the file is not known.
+  void isNotFile() {
+    hasExactType<ImportDirectiveState>();
+  }
+
+  void withInSummaryLibrary(Uri expected) {
+    this.isA<ImportDirectiveWithInSummarySource>()
+      ..importedSource.uri.isEqualTo(expected)
+      ..importedLibrarySource.isNotNull.uri.isEqualTo(expected);
+  }
+
+  void withInSummaryNotLibrary(Uri expected) {
+    this.isA<ImportDirectiveWithInSummarySource>()
+      ..importedSource.uri.isEqualTo(expected)
+      ..importedLibrarySource.isNull;
+  }
+}
+
+extension on CheckTarget<ExportDirectiveState> {
+  @useResult
+  CheckTarget<Source?> get exportedLibrarySource {
+    return nest(
+      value.exportedLibrarySource,
+      (selected) => 'exportedLibrarySource ${valueStr(selected)}',
+    );
+  }
+
+  /// Is [ExportDirectiveWithFile], but not a library.
+  void isFile(FileState expected) {
+    this.isA<ExportDirectiveWithFile>()
+      ..exportedFile.isIdenticalTo(expected)
+      ..exportedSource.uri.isEqualTo(expected.uri)
+      ..exportedLibrary.isNull
+      ..exportedLibrarySource.isNull;
+  }
+
+  /// Is [ExportDirectiveWithFile], and is a library.
+  void isLibrary(FileState expected) {
+    final expectedKind = expected.kind as LibraryFileStateKind;
+    this.isA<ExportDirectiveWithFile>()
+      ..exportedFile.isIdenticalTo(expected)
+      ..exportedSource.uri.isEqualTo(expected.uri)
+      ..exportedLibrary.isIdenticalTo(expectedKind)
+      ..exportedLibrarySource.isNotNull.uri.isEqualTo(expected.uri);
+  }
+
+  /// Exactly [ExportDirectiveState], even the file is not known.
+  void isNotFile() {
+    hasExactType<ExportDirectiveState>();
+  }
+
+  void withInSummaryLibrary(Uri expected) {
+    this.isA<ExportDirectiveWithInSummarySource>()
+      ..exportedSource.uri.isEqualTo(expected)
+      ..exportedLibrarySource.isNotNull.uri.isEqualTo(expected);
+  }
+
+  void withInSummaryNotLibrary(Uri expected) {
+    this.isA<ExportDirectiveWithInSummarySource>()
+      ..exportedSource.uri.isEqualTo(expected)
+      ..exportedLibrarySource.isNull;
+  }
+}
+
+extension on CheckTarget<ImportDirectiveWithFile> {
+  @useResult
+  CheckTarget<FileState> get importedFile {
+    return nest(
+      value.importedFile,
+      (selected) => 'importedFile ${valueStr(selected)}',
+    );
+  }
+
+  @useResult
+  CheckTarget<LibraryFileStateKind?> get importedLibrary {
+    return nest(
+      value.importedLibrary,
+      (selected) => 'importedLibrary ${valueStr(selected)}',
+    );
+  }
+
+  @useResult
+  CheckTarget<Source> get importedSource {
+    return nest(
+      value.importedSource,
+      (selected) => 'importedSource ${valueStr(selected)}',
+    );
+  }
+}
+
+extension on CheckTarget<ExportDirectiveWithFile> {
+  @useResult
+  CheckTarget<FileState> get exportedFile {
+    return nest(
+      value.exportedFile,
+      (selected) => 'exportedFile ${valueStr(selected)}',
+    );
+  }
+
+  @useResult
+  CheckTarget<LibraryFileStateKind?> get exportedLibrary {
+    return nest(
+      value.exportedLibrary,
+      (selected) => 'exportedLibrary ${valueStr(selected)}',
+    );
+  }
+
+  @useResult
+  CheckTarget<Source> get exportedSource {
+    return nest(
+      value.exportedSource,
+      (selected) => 'exportedSource ${valueStr(selected)}',
+    );
+  }
+}
+
+extension on CheckTarget<ImportDirectiveWithInSummarySource> {
+  @useResult
+  CheckTarget<InSummarySource> get importedSource {
+    return nest(
+      value.importedSource,
+      (selected) => 'importedSource ${valueStr(selected)}',
+    );
+  }
+}
+
+extension on CheckTarget<ExportDirectiveWithInSummarySource> {
+  @useResult
+  CheckTarget<InSummarySource> get exportedSource {
+    return nest(
+      value.exportedSource,
+      (selected) => 'exportedSource ${valueStr(selected)}',
+    );
+  }
+}
+
+extension on CheckTarget<Source> {
+  @useResult
+  CheckTarget<Uri> get uri {
+    return nest(
+      value.uri,
+      (selected) => 'uri ${valueStr(selected)}',
+    );
+  }
+}
+
 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 8773df4..c2aa845 100644
--- a/pkg/analyzer/test/src/dart/resolution/context_collection_resolution.dart
+++ b/pkg/analyzer/test/src/dart/resolution/context_collection_resolution.dart
@@ -129,6 +129,12 @@
   /// to this path, instead of the given path.
   String? pathForContextSelection;
 
+  /// Optional Dart SDK summary file, to be used instead of [sdkRoot].
+  File? sdkSummaryFile;
+
+  /// Optional summaries to provide for the collection.
+  List<File>? librarySummaryFiles;
+
   List<MockSdkLibrary> get additionalMockSdkLibraries => [];
 
   List<String> get collectionIncludedPaths;
@@ -259,6 +265,8 @@
       resourceProvider: resourceProvider,
       retainDataForTesting: retainDataForTesting,
       sdkPath: sdkRoot.path,
+      sdkSummaryPath: sdkSummaryFile?.path,
+      librarySummaryPaths: librarySummaryFiles?.map((e) => e.path).toList(),
       updateAnalysisOptions: updateAnalysisOptions,
     );
 
diff --git a/pkg/analyzer/test/src/summary/in_summary_source_test.dart b/pkg/analyzer/test/src/summary/in_summary_source_test.dart
deleted file mode 100644
index 0587667..0000000
--- a/pkg/analyzer/test/src/summary/in_summary_source_test.dart
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import 'package:analyzer/src/generated/source.dart';
-import 'package:analyzer/src/summary/package_bundle_reader.dart';
-import 'package:test/test.dart';
-import 'package:test_reflective_loader/test_reflective_loader.dart';
-
-main() {
-  defineReflectiveSuite(() {
-    defineReflectiveTests(InSummarySourceTest);
-  });
-}
-
-@reflectiveTest
-class InSummarySourceTest {
-  test_InSummarySource() {
-    var sourceFactory = SourceFactory([
-      InSummaryUriResolver(
-        MockSummaryDataStore.fake({
-          'package:foo/foo.dart': 'foo.sum',
-          'package:foo/src/foo_impl.dart': 'foo.sum',
-          'package:bar/baz.dart': 'bar.sum',
-        }),
-      )
-    ]);
-
-    var source =
-        sourceFactory.forUri('package:foo/foo.dart') as InSummarySource;
-    expect(source, isNotNull);
-    expect(source.summaryPath, 'foo.sum');
-
-    source = sourceFactory.forUri('package:foo/src/foo_impl.dart')
-        as InSummarySource;
-    expect(source, isNotNull);
-    expect(source.summaryPath, 'foo.sum');
-
-    source = sourceFactory.forUri('package:bar/baz.dart') as InSummarySource;
-    expect(source, isNotNull);
-    expect(source.summaryPath, 'bar.sum');
-  }
-}
-
-class MockSummaryDataStore implements SummaryDataStore {
-  @override
-  final Map<String, String> uriToSummaryPath;
-
-  MockSummaryDataStore(this.uriToSummaryPath);
-
-  factory MockSummaryDataStore.fake(Map<String, String> uriToSummary) {
-    return MockSummaryDataStore(uriToSummary);
-  }
-
-  @override
-  noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
-}
diff --git a/pkg/analyzer/test/src/summary/test_all.dart b/pkg/analyzer/test/src/summary/test_all.dart
index 41786a1..9c87739 100644
--- a/pkg/analyzer/test/src/summary/test_all.dart
+++ b/pkg/analyzer/test/src/summary/test_all.dart
@@ -7,7 +7,6 @@
 import 'api_signature_test.dart' as api_signature;
 import 'elements_test.dart' as elements;
 import 'flat_buffers_test.dart' as flat_buffers;
-import 'in_summary_source_test.dart' as in_summary_source;
 import 'macro_test.dart' as macro;
 import 'top_level_inference_test.dart' as top_level_inference;
 
@@ -16,7 +15,6 @@
     api_signature.main();
     elements.main();
     flat_buffers.main();
-    in_summary_source.main();
     macro.main();
     top_level_inference.main();
   }, name: 'summary');
diff --git a/pkg/analyzer/test/src/workspace/bazel_test.dart b/pkg/analyzer/test/src/workspace/bazel_test.dart
index a79b85e..784c9b8 100644
--- a/pkg/analyzer/test/src/workspace/bazel_test.dart
+++ b/pkg/analyzer/test/src/workspace/bazel_test.dart
@@ -901,7 +901,11 @@
 
   Source _inSummarySource(String uriStr) {
     var uri = Uri.parse(uriStr);
-    return InSummarySource(uri, '');
+    return InSummarySource(
+      uri: uri,
+      summaryPath: '',
+      kind: InSummarySourceKind.library,
+    );
   }
 
   void _setUpPackage() {
diff --git a/pkg/analyzer_utilities/lib/check/equality.dart b/pkg/analyzer_utilities/lib/check/equality.dart
index 2d265b8..9701bb3 100644
--- a/pkg/analyzer_utilities/lib/check/equality.dart
+++ b/pkg/analyzer_utilities/lib/check/equality.dart
@@ -11,6 +11,12 @@
     }
   }
 
+  void isIdenticalTo(Object? other) {
+    if (!identical(value, other)) {
+      fail('is not identical to $other');
+    }
+  }
+
   void isNotEqualTo(Object? other) {
     if (value == other) {
       fail('is equal to $other');
diff --git a/pkg/analyzer_utilities/lib/check/type.dart b/pkg/analyzer_utilities/lib/check/type.dart
index ea28b9e..c261e2a 100644
--- a/pkg/analyzer_utilities/lib/check/type.dart
+++ b/pkg/analyzer_utilities/lib/check/type.dart
@@ -5,6 +5,15 @@
 import 'package:analyzer_utilities/check/check.dart';
 
 extension IsExtension<T> on CheckTarget<T> {
+  CheckTarget<U> hasExactType<U extends T>() {
+    final value = this.value;
+    if (value.runtimeType == U) {
+      return nest(value as U, (_) => 'is of type $U');
+    } else {
+      fail('is not of type $U');
+    }
+  }
+
   CheckTarget<U> isA<U extends T>() {
     final value = this.value;
     if (value is U) {
diff --git a/pkg/analyzer_utilities/test/check/check_test.dart b/pkg/analyzer_utilities/test/check/check_test.dart
index 9e5ee83..0f9bda1 100644
--- a/pkg/analyzer_utilities/test/check/check_test.dart
+++ b/pkg/analyzer_utilities/test/check/check_test.dart
@@ -29,6 +29,14 @@
         _fails(() => check(false).isTrue);
       });
     });
+    group('equality', () {
+      test('isIdenticalTo', () {
+        final a = Object();
+        final b = Object();
+        check(a).isIdenticalTo(a);
+        _fails(() => check(a).isIdenticalTo(b));
+      });
+    });
     group('int', () {
       test('isEqualTo', () {
         check(0).isEqualTo(0);
@@ -332,6 +340,10 @@
       });
     });
     group('type', () {
+      test('hasExactType', () {
+        check(42).hasExactType<int>();
+        _fails(() => check(42 as dynamic).hasExactType<num>());
+      });
       test('isA', () {
         check(0).isA<int>();
         _fails(() => check('abc' as dynamic).isA<int>());