Prefer to import libraries which export types that need referencing.

If the generated code needs to represent a type, `T`, and that type is declared
in library `L`, then we now prefer to import a library which _exports_ `L` (if
one exists), over importing `L` directly.

To find such a library, we look at the type, `U` which references `T`. Perhaps
`U` is a class to mocked, and `U` has a method with a return type `T`, or `U` is
a supertype of a class to be mocked, which has a method with a parameter type
`T`, etc. We examine all of the import libraries, `IL`, of the library in which
`U` is declared, and all of the libraries which are exported by the libraries
`IL`.

If the type `T` is declared in a library which is exported as a conditional
export, this strategy avoids complications with the conditional export.
Additionally, as a heuristic, it generally leads to public libraries which
export private implementation, avoiding importing the private implementation
directly.

Fixes https://github.com/dart-lang/mockito/issues/443

PiperOrigin-RevId: 385425794
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5adf0bb..6538c15 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,10 @@
 * Use an empty list with a correct type argument for a fallback value for a
   method which returns Iterable.
   [#445](https://github.com/dart-lang/mockito/issues/445)
+* When selecting the library that should be imported in order to reference a
+  type, prefer a library which exports the library in which the type is
+  declared. This avoids some confusion with conditional exports.
+  [#443](https://github.com/dart-lang/mockito/issues/443)
 * Support analyzer 2.0.0
 
 ## 5.0.11
diff --git a/lib/src/builder.dart b/lib/src/builder.dart
index 4a4ed2a..397534f 100644
--- a/lib/src/builder.dart
+++ b/lib/src/builder.dart
@@ -149,19 +149,15 @@
 
     for (final element in elements) {
       final elementLibrary = element.library!;
-      if (elementLibrary.isInSdk) {
-        // ignore:unnecessary_non_null_assertion
-        if (elementLibrary.name!.startsWith('dart._')) {
-          typeUris[element] = _findPublicExportOf(
-              Queue.of(librariesWithTypes), elementLibrary)!;
-        } else {
-          typeUris[element] = elementLibrary.source.uri.toString();
-        }
+      if (elementLibrary.isInSdk && !elementLibrary.name.startsWith('dart._')) {
+        // For public SDK libraries, just use the source URI.
+        typeUris[element] = elementLibrary.source.uri.toString();
         continue;
       }
+      final exportingLibrary = _findExportOf(librariesWithTypes, element);
 
       try {
-        final typeAssetId = await resolver.assetIdForElement(elementLibrary);
+        final typeAssetId = await resolver.assetIdForElement(exportingLibrary);
 
         if (typeAssetId.path.startsWith('lib/')) {
           typeUris[element] = typeAssetId.uri.toString();
@@ -171,32 +167,35 @@
         }
       } on UnresolvableAssetException {
         // Asset may be in a summary.
-        typeUris[element] = elementLibrary.source.uri.toString();
-        continue;
+        typeUris[element] = exportingLibrary.source.uri.toString();
       }
     }
 
     return typeUris;
   }
 
-  /// Returns the String import path of the correct public library which
-  /// exports [privateLibrary], selecting from the imports of [inputLibraries].
-  static String? _findPublicExportOf(
-      Queue<LibraryElement> inputLibraries, LibraryElement privateLibrary) {
+  /// Returns a library which exports [element], selecting from the imports of
+  /// [inputLibraries] (and all exported libraries).
+  ///
+  /// If [element] is not exported by any libraries in this set, then
+  /// [element]'s declaring library is returned.
+  static LibraryElement _findExportOf(
+      Iterable<LibraryElement> inputLibraries, Element element) {
+    final elementName = element.name;
+    if (elementName == null) {
+      return element.library!;
+    }
+
     final libraries = Queue.of([
       for (final library in inputLibraries) ...library.importedLibraries,
     ]);
 
-    while (libraries.isNotEmpty) {
-      final library = libraries.removeFirst();
-      if (library.exportedLibraries.contains(privateLibrary)) {
-        return library.source.uri.toString();
+    for (final library in libraries) {
+      if (library.exportNamespace.get(elementName) == element) {
+        return library;
       }
-      // A library may provide [privateLibrary] by exporting a library which
-      // provides it (directly or via further exporting).
-      libraries.addAll(library.exportedLibraries);
     }
-    return null;
+    return element.library!;
   }
 
   @override
diff --git a/test/builder/auto_mocks_test.dart b/test/builder/auto_mocks_test.dart
index 7550357..1735c90 100644
--- a/test/builder/auto_mocks_test.dart
+++ b/test/builder/auto_mocks_test.dart
@@ -120,8 +120,7 @@
 
   /// Test [MockBuilder] on a single source file, in a package which has opted
   /// into null safety, and with the non-nullable experiment enabled.
-  Future<void> expectSingleNonNullableOutput(
-      String sourceAssetText,
+  Future<void> expectSingleNonNullableOutput(String sourceAssetText,
       /*String|Matcher<List<int>>*/ Object output) async {
     await testWithNonNullable({
       ...metaAssets,
@@ -1140,6 +1139,32 @@
     expect(mocksContent, contains('_i2.HttpStatus f() =>'));
   });
 
+  test('imports libraries which export external class types', () async {
+    await testWithNonNullable({
+      ...annotationsAsset,
+      ...simpleTestAsset,
+      'foo|lib/foo.dart': '''
+        import 'types.dart';
+        abstract class Foo {
+          void m(Bar a);
+        }
+        ''',
+      'foo|lib/types.dart': '''
+        export 'base.dart' if (dart.library.html) 'html.dart';
+        ''',
+      'foo|lib/base.dart': '''
+        class Bar {}
+        ''',
+      'foo|lib/html.dart': '''
+        class Bar {}
+        ''',
+    });
+    final mocksAsset = AssetId('foo', 'test/foo_test.mocks.dart');
+    final mocksContent = utf8.decode(writer.assets[mocksAsset]!);
+    expect(mocksContent, contains("import 'package:foo/types.dart' as _i3;"));
+    expect(mocksContent, contains('m(_i3.Bar? a)'));
+  });
+
   test('prefixes parameter type on generic function-typed parameter', () async {
     await expectSingleNonNullableOutput(
       dedent(r'''