Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve for typedefs/aliases and records in typeArguments & resolve duplicate typedefs #776

Merged
merged 6 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
* Require dart_style >= 2.3.7, so that the current Dart language version can be
passed to `DartFormatter`.
* Add topics to `pubspec.yaml`.
* Fix a bug where type aliases in type arguments were not correctly
resolved.
* Fix a bug where record types were not correctly resolved.

## 5.4.4

Expand Down
36 changes: 24 additions & 12 deletions lib/src/builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -167,25 +167,36 @@ $rawOutput
// `Future`, which is needed when overriding some methods which return
// `FutureOr`.
final typeVisitor = _TypeVisitor(entryLib.typeProvider.futureDynamicType);
final seenTypes = <analyzer.InterfaceType>{};
final seenTypes = <analyzer.DartType>{};
final librariesWithTypes = <LibraryElement>{};

void addTypesFrom(analyzer.InterfaceType type) {
void addTypesFrom(analyzer.DartType type) {
// Prevent infinite recursion.
if (seenTypes.contains(type)) {
if (type.alias != null) {
// To check for duplicate typdefs that have different names
type.alias!.element.accept(typeVisitor);
}
return;
}
seenTypes.add(type);
librariesWithTypes.add(type.element.library);
type.element.accept(typeVisitor);
if (type.alias != null) type.alias!.element.accept(typeVisitor);
// For a type like `Foo<Bar>`, add the `Bar`.
type.typeArguments
.whereType<analyzer.InterfaceType>()
.forEach(addTypesFrom);
// For a type like `Foo extends Bar<Baz>`, add the `Baz`.
for (final supertype in type.allSupertypes) {
addTypesFrom(supertype);

if (type.element?.library case var library?) {
librariesWithTypes.add(library);
}
if (type.alias?.element.library case var library?) {
librariesWithTypes.add(library);
}

type.element?.accept(typeVisitor);
type.alias?.element.accept(typeVisitor);
switch (type) {
case analyzer.InterfaceType interface:
interface.typeArguments.forEach(addTypesFrom);
interface.allSupertypes.forEach(addTypesFrom);
case analyzer.RecordType record:
record.positionalTypes.forEach(addTypesFrom);
record.namedTypes.map((e) => e.type).forEach(addTypesFrom);
}
}

Expand Down Expand Up @@ -314,6 +325,7 @@ class _TypeVisitor extends RecursiveElementVisitor<void> {

@override
void visitTypeAliasElement(TypeAliasElement element) {
_addType(element.aliasedType);
_elements.add(element);
super.visitTypeAliasElement(element);
}
Expand Down
108 changes: 108 additions & 0 deletions test/builder/auto_mocks_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3582,6 +3582,66 @@ void main() {
expect(mocksContent, contains('class MockBaz extends _i1.Mock'));
expect(mocksContent, contains('implements _i2.Baz'));
});

test('when it\'s a function which returns any type', () async {
final mocksContent = await buildWithNonNullable({
...annotationsAsset,
'foo|lib/foo.dart': dedent(r'''
class Bar {}
typedef CreateBar = Bar Function();

class BaseFoo<T> {
BaseFoo(this.t);
final T t;
}

class Foo extends BaseFoo<CreateBar> {
Foo() : super(() => 1);
}
'''),
'foo|test/foo_test.dart': '''
import 'package:foo/foo.dart';
import 'package:mockito/annotations.dart';

@GenerateMocks([Foo])
void main() {}
'''
});

expect(mocksContent, contains('class MockFoo extends _i1.Mock'));
expect(mocksContent, contains('implements _i2.Foo'));
});
test('when the underlying type is identical to another type alias',
() async {
final mocksContent = await buildWithNonNullable({
...annotationsAsset,
'foo|lib/foo.dart': dedent(r'''
class Bar {}
typedef BarDef = int Function();
typedef BarDef2 = int Function();
class BaseFoo<T, P> {
BaseFoo(this.t1, this.t2);
final T t1;
final P t2;
}
class Foo extends BaseFoo<BarDef, BarDef2> {
Foo() : super(() => 1, () => 2);
}
'''),
'foo|test/foo_test.dart': '''
import 'package:foo/foo.dart';
import 'package:mockito/annotations.dart';

@GenerateMocks([Foo])
void main() {}
'''
});

expect(mocksContent, contains('class MockFoo extends _i1.Mock'));
dickermoshe marked this conversation as resolved.
Show resolved Hide resolved
expect(mocksContent, contains('implements _i2.Foo'));
expect(mocksContent, contains('_i2.BarDef get t1'));
expect(mocksContent, contains('_i2.BarDef2 get t2'));
});
});

test('generation throws when the aliased type is nullable', () {
Expand Down Expand Up @@ -3649,6 +3709,54 @@ void main() {
contains('returnValue: _i3.Future<(int, {_i2.Bar bar})>.value('),
contains('bar: _FakeBar_0('))));
});
test('are supported as type arguments', () async {
final mocksContent = await buildWithNonNullable({
...annotationsAsset,
'foo|lib/foo.dart': dedent(r'''
class Bar {}
class BaseFoo<T> {
BaseFoo(this.t);
final T t;
}
class Foo extends BaseFoo<(Bar, Bar)> {
Foo() : super((Bar(), Bar()));
}
'''),
'foo|test/foo_test.dart': '''
import 'package:foo/foo.dart';
import 'package:mockito/annotations.dart';
@GenerateMocks([Foo])
void main() {}
'''
});

expect(mocksContent, contains('class MockFoo extends _i1.Mock'));
expect(mocksContent, contains('implements _i2.Foo'));
});
test('are supported as nested type arguments', () async {
final mocksContent = await buildWithNonNullable({
...annotationsAsset,
'foo|lib/foo.dart': dedent(r'''
class Bar {}
class BaseFoo<T> {
BaseFoo(this.t);
final T t;
}
class Foo extends BaseFoo<(int, (Bar, Bar))> {
Foo() : super(((1, (Bar(), Bar()))));
}
'''),
'foo|test/foo_test.dart': '''
import 'package:foo/foo.dart';
import 'package:mockito/annotations.dart';
@GenerateMocks([Foo])
void main() {}
'''
});

expect(mocksContent, contains('class MockFoo extends _i1.Mock'));
expect(mocksContent, contains('implements _i2.Foo'));
});
});

group('Extension types', () {
Expand Down
Loading