From e8e95bcd5221a142c8777b8ebf2c3f7409be37c1 Mon Sep 17 00:00:00 2001 From: Moshe Dicker Date: Tue, 29 Oct 2024 11:55:18 -0400 Subject: [PATCH 1/5] parse records and typedef in type arguments --- CHANGELOG.md | 3 ++ lib/src/builder.dart | 27 ++++++----- test/builder/auto_mocks_test.dart | 76 +++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51c8ada3..1f384b0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 + resolved. +* Fix a bug where record types were not correctly resolved. ## 5.4.4 diff --git a/lib/src/builder.dart b/lib/src/builder.dart index 309e5b0a..3c8d909d 100644 --- a/lib/src/builder.dart +++ b/lib/src/builder.dart @@ -167,25 +167,28 @@ $rawOutput // `Future`, which is needed when overriding some methods which return // `FutureOr`. final typeVisitor = _TypeVisitor(entryLib.typeProvider.futureDynamicType); - final seenTypes = {}; + final seenTypes = {}; final librariesWithTypes = {}; - void addTypesFrom(analyzer.InterfaceType type) { + void addTypesFrom(analyzer.DartType type) { // Prevent infinite recursion. if (seenTypes.contains(type)) { 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`, add the `Bar`. - type.typeArguments - .whereType() - .forEach(addTypesFrom); - // For a type like `Foo extends Bar`, add the `Baz`. - for (final supertype in type.allSupertypes) { - addTypesFrom(supertype); + librariesWithTypes.addAll([ + if (type.element?.library != null) type.element!.library!, + 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); } } diff --git a/test/builder/auto_mocks_test.dart b/test/builder/auto_mocks_test.dart index 83631677..c273cef6 100644 --- a/test/builder/auto_mocks_test.dart +++ b/test/builder/auto_mocks_test.dart @@ -3553,6 +3553,34 @@ void main() { expect(mocksContent, contains('class MockBaz extends _i1.Mock')); expect(mocksContent, contains('implements _i2.Baz')); }); + + test('when a type parameter is a typedef a function', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + typedef CreateInt = int Function(); + + class BaseFoo { + BaseFoo(this.t); + final T t; + } + + class Foo extends BaseFoo { + 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('generation throws when the aliased type is nullable', () { @@ -3620,6 +3648,54 @@ void main() { contains('returnValue: _i3.Future<(int, {_i2.Bar bar})>.value('), contains('bar: _FakeBar_0(')))); }); + test('are supported as typedefs', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Bar {} + class BaseFoo { + 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 { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Bar {} + class BaseFoo { + 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', () { From 14706599dd84c0e1e43e6656f28603d397552481 Mon Sep 17 00:00:00 2001 From: Moshe Dicker Date: Tue, 29 Oct 2024 13:05:39 -0400 Subject: [PATCH 2/5] parse the contents of a typedef --- lib/src/builder.dart | 1 + test/builder/auto_mocks_test.dart | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/src/builder.dart b/lib/src/builder.dart index 3c8d909d..b8346672 100644 --- a/lib/src/builder.dart +++ b/lib/src/builder.dart @@ -317,6 +317,7 @@ class _TypeVisitor extends RecursiveElementVisitor { @override void visitTypeAliasElement(TypeAliasElement element) { + _addType(element.aliasedType); _elements.add(element); super.visitTypeAliasElement(element); } diff --git a/test/builder/auto_mocks_test.dart b/test/builder/auto_mocks_test.dart index c273cef6..74d99c50 100644 --- a/test/builder/auto_mocks_test.dart +++ b/test/builder/auto_mocks_test.dart @@ -3554,18 +3554,21 @@ void main() { expect(mocksContent, contains('implements _i2.Baz')); }); - test('when a type parameter is a typedef a function', () async { + test( + 'when a type parameter is a typedef a function which returns another type', + () async { final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' - typedef CreateInt = int Function(); + class Bar {} + typedef CreateBar = Bar Function(); class BaseFoo { BaseFoo(this.t); final T t; } - class Foo extends BaseFoo { + class Foo extends BaseFoo { Foo() : super(() => 1); } '''), From 673028819390f6d98eb258f53be2e9b5a6287e7e Mon Sep 17 00:00:00 2001 From: Moshe Dicker Date: Tue, 29 Oct 2024 13:58:31 -0400 Subject: [PATCH 3/5] allow duplicate typedefs --- lib/src/builder.dart | 4 ++++ test/builder/auto_mocks_test.dart | 31 +++++++++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/lib/src/builder.dart b/lib/src/builder.dart index b8346672..3383a728 100644 --- a/lib/src/builder.dart +++ b/lib/src/builder.dart @@ -173,6 +173,10 @@ $rawOutput 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); diff --git a/test/builder/auto_mocks_test.dart b/test/builder/auto_mocks_test.dart index 74d99c50..017393fc 100644 --- a/test/builder/auto_mocks_test.dart +++ b/test/builder/auto_mocks_test.dart @@ -3554,8 +3554,7 @@ void main() { expect(mocksContent, contains('implements _i2.Baz')); }); - test( - 'when a type parameter is a typedef a function which returns another type', + test('when its a type parameter of function which returns another type', () async { final mocksContent = await buildWithNonNullable({ ...annotationsAsset, @@ -3581,6 +3580,34 @@ void main() { ''' }); + expect(mocksContent, contains('class MockFoo extends _i1.Mock')); + expect(mocksContent, contains('implements _i2.Foo')); + }); + test('when its a duplicate type parameter', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Bar {} + typedef BarDef = int Function(); + typedef BarDef2 = int Function(); + class BaseFoo { + BaseFoo(this.t1, this.t2); + final T t1; + final P t2; + } + class Foo extends BaseFoo { + 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')); expect(mocksContent, contains('implements _i2.Foo')); }); From f083cc5063d75609d095649e5a193a0a496eec7b Mon Sep 17 00:00:00 2001 From: Moshe Dicker Date: Wed, 6 Nov 2024 14:33:21 -0500 Subject: [PATCH 4/5] fix nits --- CHANGELOG.md | 2 +- lib/src/builder.dart | 12 ++++++++---- test/builder/auto_mocks_test.dart | 12 ++++++------ 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f384b0a..da6ae6fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ * 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 +* Fix a bug where type aliases in type arguments were not correctly resolved. * Fix a bug where record types were not correctly resolved. diff --git a/lib/src/builder.dart b/lib/src/builder.dart index 3383a728..5eb0386d 100644 --- a/lib/src/builder.dart +++ b/lib/src/builder.dart @@ -180,10 +180,14 @@ $rawOutput return; } seenTypes.add(type); - librariesWithTypes.addAll([ - if (type.element?.library != null) type.element!.library!, - if (type.alias?.element.library != null) type.alias!.element.library, - ]); + + 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) { diff --git a/test/builder/auto_mocks_test.dart b/test/builder/auto_mocks_test.dart index 017393fc..2332e24e 100644 --- a/test/builder/auto_mocks_test.dart +++ b/test/builder/auto_mocks_test.dart @@ -3554,8 +3554,7 @@ void main() { expect(mocksContent, contains('implements _i2.Baz')); }); - test('when its a type parameter of function which returns another type', - () async { + test('when it\'s a function which returns any type', () async { final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' @@ -3583,14 +3582,15 @@ void main() { expect(mocksContent, contains('class MockFoo extends _i1.Mock')); expect(mocksContent, contains('implements _i2.Foo')); }); - test('when its a duplicate type parameter', () async { + 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 { + class BaseFoo { BaseFoo(this.t1, this.t2); final T t1; final P t2; @@ -3678,7 +3678,7 @@ void main() { contains('returnValue: _i3.Future<(int, {_i2.Bar bar})>.value('), contains('bar: _FakeBar_0(')))); }); - test('are supported as typedefs', () async { + test('are supported as type arguments', () async { final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' @@ -3702,7 +3702,7 @@ void main() { expect(mocksContent, contains('class MockFoo extends _i1.Mock')); expect(mocksContent, contains('implements _i2.Foo')); }); - test('are supported as nested typedefs', () async { + test('are supported as nested type arguments', () async { final mocksContent = await buildWithNonNullable({ ...annotationsAsset, 'foo|lib/foo.dart': dedent(r''' From 8b2f24899ec7169db1931546f92316ee8c2970ac Mon Sep 17 00:00:00 2001 From: Moshe Dicker Date: Wed, 6 Nov 2024 14:44:16 -0500 Subject: [PATCH 5/5] fix identical type alias test --- test/builder/auto_mocks_test.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/builder/auto_mocks_test.dart b/test/builder/auto_mocks_test.dart index 2332e24e..de3d5f46 100644 --- a/test/builder/auto_mocks_test.dart +++ b/test/builder/auto_mocks_test.dart @@ -3610,6 +3610,8 @@ void main() { expect(mocksContent, contains('class MockFoo extends _i1.Mock')); expect(mocksContent, contains('implements _i2.Foo')); + expect(mocksContent, contains('_i2.BarDef get t1')); + expect(mocksContent, contains('_i2.BarDef2 get t2')); }); });