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 3 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 typedef-aliases in type arguments were not correctly
dickermoshe marked this conversation as resolved.
Show resolved Hide resolved
resolved.
* Fix a bug where record types were not correctly resolved.

## 5.4.4

Expand Down
32 changes: 20 additions & 12 deletions lib/src/builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -167,25 +167,32 @@ $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);
librariesWithTypes.addAll([
if (type.element?.library != null) type.element!.library!,
dickermoshe marked this conversation as resolved.
Show resolved Hide resolved
if (type.alias?.element.library != null) type.alias!.element.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 +321,7 @@ class _TypeVisitor extends RecursiveElementVisitor<void> {

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

test('when its a type parameter of function which returns another type',
dickermoshe marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what you mean here by "a type parameter of function which returns another type." In this test case, the typedef appears to only be used as a type argument in a class definition class Foo extends BaseFoo<CreateBar>.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about?
typedef mocks are generated properly when it\'s a function which returns any type

In this instance, Bar was being ignored becuase the return type of CreateBar want being resolved

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That SG, thanks!

() 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 its a duplicate type parameter', () async {
dickermoshe marked this conversation as resolved.
Show resolved Hide resolved
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> {
dickermoshe marked this conversation as resolved.
Show resolved Hide resolved
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'));
});
});

test('generation throws when the aliased type is nullable', () {
Expand Down Expand Up @@ -3620,6 +3678,54 @@ void main() {
contains('returnValue: _i3.Future<(int, {_i2.Bar bar})>.value('),
contains('bar: _FakeBar_0('))));
});
test('are supported as typedefs', () async {
dickermoshe marked this conversation as resolved.
Show resolved Hide resolved
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 typedefs', () async {
dickermoshe marked this conversation as resolved.
Show resolved Hide resolved
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