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