blob: 2a428916e3cb8877346540724ce88d170ff51189 [file] [log] [blame]
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:test/fake.dart';
import 'package:test/test.dart';
import 'package:_macros/src/api.dart';
import 'package:_macros/src/executor.dart';
import 'package:_macros/src/executor/augmentation_library.dart';
import 'package:_macros/src/executor/introspection_impls.dart';
import 'package:_macros/src/executor/remote_instance.dart';
import 'package:_macros/src/executor/response_impls.dart';
import '../util.dart';
void main() {
group('AugmentationLibraryBuilder', () {
final intIdentifier = TestIdentifier(
id: RemoteInstance.uniqueId,
name: 'int',
kind: IdentifierKind.topLevelMember,
staticScope: null,
uri: Uri.parse('dart:core'));
final objectIdentifier = TestIdentifier(
id: RemoteInstance.uniqueId,
name: 'Object',
kind: IdentifierKind.topLevelMember,
staticScope: null,
uri: Uri.parse('dart:core'));
final superclassIdentifier = TestIdentifier(
id: RemoteInstance.uniqueId,
name: 'SomeSuperclass',
kind: IdentifierKind.topLevelMember,
uri: null,
staticScope: null);
final interfaceIdentifiers = [
for (var i = 0; i < 2; i++)
TestIdentifier(
id: RemoteInstance.uniqueId,
name: 'I$i',
kind: IdentifierKind.topLevelMember,
uri: null,
staticScope: null),
];
final mixinIdentifiers = [
for (var i = 0; i < 2; i++)
TestIdentifier(
id: RemoteInstance.uniqueId,
name: 'M$i',
kind: IdentifierKind.topLevelMember,
uri: null,
staticScope: null),
];
test('can combine multiple execution results', () {
final classes = <IdentifierImpl, ClassDeclaration>{};
for (var i = 0; i < 2; i++) {
for (var j = 0; j < 3; j++) {
final identifier =
IdentifierImpl(id: RemoteInstance.uniqueId, name: 'Foo$i$j');
classes[identifier] = ClassDeclarationImpl(
id: RemoteInstance.uniqueId,
identifier: identifier,
library: Fixtures.library,
metadata: [],
typeParameters: [],
interfaces: [],
hasAbstract: false,
hasBase: false,
hasExternal: false,
hasFinal: false,
hasInterface: false,
hasMixin: false,
hasSealed: false,
mixins: [],
superclass: null);
}
}
var results = [
for (var i = 0; i < 2; i++)
MacroExecutionResultImpl(
diagnostics: [],
enumValueAugmentations: {},
extendsTypeAugmentations: {
for (var j in [0, 2])
classes.keys.firstWhere(
(identifier) => identifier.name == 'Foo$i$j'):
NamedTypeAnnotationCode(name: superclassIdentifier),
},
interfaceAugmentations: {
for (var j = 0; j < 3; j++)
classes.keys
.firstWhere((identifier) => identifier.name == 'Foo$i$j'): [
for (var k = 0; k < j; k++)
NamedTypeAnnotationCode(name: interfaceIdentifiers[k]),
]
},
libraryAugmentations: [
for (var j = 0; j < 3; j++)
DeclarationCode.fromParts(
[intIdentifier, ' get i${i}j$j => ${i + j};\n']),
],
mixinAugmentations: {
for (var j = 0; j < 3; j++)
classes.keys
.firstWhere((identifier) => identifier.name == 'Foo$i$j'): [
for (var k = 0; k < i; k++)
NamedTypeAnnotationCode(name: mixinIdentifiers[k]),
]
},
newTypeNames: [
'Foo${i}0',
'Foo${i}1',
'Foo${i}2',
],
typeAugmentations: {
for (var j = 0; j < 3; j++)
classes.keys
.firstWhere((identifier) => identifier.name == 'Foo$i$j'): [
DeclarationCode.fromParts([intIdentifier, ' get i => $i;\n']),
DeclarationCode.fromParts([intIdentifier, ' get j => $j;\n']),
]
},
),
];
var library = _TestExecutor().buildAugmentationLibrary(
Fixtures.library.uri,
results,
(Identifier i) => classes[i]!,
(Identifier i) => (i as TestIdentifier).resolved,
(OmittedTypeAnnotation i) =>
(i as TestOmittedTypeAnnotation).inferredType);
expect(library, equalsIgnoringWhitespace('''
augment library 'package:foo/bar.dart';
import 'dart:core' as prefix0;
prefix0.int get i0j0 => 0;
prefix0.int get i0j1 => 1;
prefix0.int get i0j2 => 2;
prefix0.int get i1j0 => 1;
prefix0.int get i1j1 => 2;
prefix0.int get i1j2 => 3;
augment class Foo00 extends SomeSuperclass {
prefix0.int get i => 0;
prefix0.int get j => 0;
}
augment class Foo02 extends SomeSuperclass implements I0, I1 {
prefix0.int get i => 0;
prefix0.int get j => 2;
}
augment class Foo10 extends SomeSuperclass with M0 {
prefix0.int get i => 1;
prefix0.int get j => 0;
}
augment class Foo12 extends SomeSuperclass with M0 implements I0, I1 {
prefix0.int get i => 1;
prefix0.int get j => 2;
}
augment class Foo01 implements I0 {
prefix0.int get i => 0;
prefix0.int get j => 1;
}
augment class Foo11 with M0 implements I0 {
prefix0.int get i => 1;
prefix0.int get j => 1;
}
'''));
});
test('can add imports for identifiers', () {
var fooIdentifier = TestIdentifier(
id: RemoteInstance.uniqueId,
name: 'Foo',
kind: IdentifierKind.topLevelMember,
staticScope: null,
uri: Uri.parse('package:foo/foo.dart'));
var barIdentifier = TestIdentifier(
id: RemoteInstance.uniqueId,
name: 'Bar',
kind: IdentifierKind.topLevelMember,
staticScope: null,
uri: Uri.parse('package:bar/bar.dart'));
var builderIdentifier = TestIdentifier(
id: RemoteInstance.uniqueId,
name: 'Builder',
kind: IdentifierKind.topLevelMember,
staticScope: null,
uri: Uri.parse('package:builder/builder.dart'));
var barInstanceMember = TestIdentifier(
id: RemoteInstance.uniqueId,
name: 'baz',
kind: IdentifierKind.instanceMember,
staticScope: null,
uri: Uri.parse('package:bar/bar.dart'));
var barStaticMember = TestIdentifier(
id: RemoteInstance.uniqueId,
name: 'zap',
kind: IdentifierKind.staticInstanceMember,
staticScope: 'Bar',
uri: Uri.parse('package:bar/bar.dart'));
var results = [
MacroExecutionResultImpl(
diagnostics: [],
enumValueAugmentations: {},
extendsTypeAugmentations: {},
interfaceAugmentations: {},
mixinAugmentations: {},
typeAugmentations: {},
libraryAugmentations: [
DeclarationCode.fromParts([
'class FooBuilder<T extends ',
fooIdentifier,
'> implements ',
builderIdentifier,
'<',
barIdentifier,
'<T>> {\n',
'late ',
intIdentifier,
' ${barInstanceMember.name};\n',
barIdentifier,
'<T> build() => new ',
barIdentifier,
'()..',
barInstanceMember,
' = ',
barStaticMember,
';',
'\n}',
]),
],
newTypeNames: [
'FooBuilder',
],
)
];
var library = _TestExecutor().buildAugmentationLibrary(
Fixtures.library.uri,
results,
(_) => throw UnimplementedError(),
(Identifier i) => (i as TestIdentifier).resolved,
(OmittedTypeAnnotation i) =>
(i as TestOmittedTypeAnnotation).inferredType);
expect(library, equalsIgnoringWhitespace('''
augment library 'package:foo/bar.dart';
import 'package:foo/foo.dart' as prefix0;
import 'package:builder/builder.dart' as prefix1;
import 'package:bar/bar.dart' as prefix2;
import 'dart:core' as prefix3;
class FooBuilder<T extends prefix0.Foo> implements prefix1.Builder<prefix2.Bar<T>> {
late prefix3.int baz;
prefix2.Bar<T> build() => new prefix2.Bar()..baz = prefix2.Bar.zap;
}
'''));
});
test('can handle omitted type annotations', () {
var results = [
MacroExecutionResultImpl(
diagnostics: [],
enumValueAugmentations: {},
extendsTypeAugmentations: {},
interfaceAugmentations: {},
mixinAugmentations: {},
typeAugmentations: {},
libraryAugmentations: [
DeclarationCode.fromParts([
OmittedTypeAnnotationCode(
TestOmittedTypeAnnotation(NamedTypeAnnotationImpl(
id: RemoteInstance.uniqueId,
identifier: intIdentifier,
isNullable: false,
typeArguments: [],
))),
' x = 1;',
]),
],
newTypeNames: []),
];
var library = _TestExecutor().buildAugmentationLibrary(
Fixtures.library.uri,
results,
(_) => throw UnimplementedError(),
(Identifier i) => (i as TestIdentifier).resolved,
(OmittedTypeAnnotation i) =>
(i as TestOmittedTypeAnnotation).inferredType);
expect(library, equalsIgnoringWhitespace('''
augment library 'package:foo/bar.dart';
import 'dart:core' as prefix0;
prefix0.int x = 1;
'''));
});
test('can handle name conflicts', () {
var omittedType0 = TestOmittedTypeAnnotation();
var omittedType1 = TestOmittedTypeAnnotation();
var omittedTypeIdentifier = TestIdentifier(
id: RemoteInstance.uniqueId,
name: 'OmittedType',
kind: IdentifierKind.topLevelMember,
staticScope: null,
uri: Uri.parse('package:foo/foo.dart'));
var omittedTypeIdentifier0 = TestIdentifier(
id: RemoteInstance.uniqueId,
name: 'OmittedType0',
kind: IdentifierKind.topLevelMember,
staticScope: null,
uri: Uri.parse('package:bar/bar.dart'));
var prefixInstanceMember = TestIdentifier(
id: RemoteInstance.uniqueId,
name: 'prefix',
kind: IdentifierKind.instanceMember,
staticScope: null,
uri: Uri.parse('package:bar/bar.dart'));
var prefix0InstanceMember = TestIdentifier(
id: RemoteInstance.uniqueId,
name: 'prefix0',
kind: IdentifierKind.instanceMember,
staticScope: null,
uri: Uri.parse('package:bar/bar.dart'));
var prefix1StaticMember = TestIdentifier(
id: RemoteInstance.uniqueId,
name: 'prefix1',
kind: IdentifierKind.staticInstanceMember,
staticScope: 'OmittedType1',
uri: Uri.parse('package:bar/bar.dart'));
var results = [
MacroExecutionResultImpl(
diagnostics: [],
enumValueAugmentations: {},
extendsTypeAugmentations: {},
interfaceAugmentations: {},
mixinAugmentations: {},
typeAugmentations: {},
libraryAugmentations: [
DeclarationCode.fromParts([
'class OmittedType {\n ',
omittedType0.code,
' method(',
omittedType1.code,
' o) {\n ',
intIdentifier,
' ${prefixInstanceMember.name} = 0;\n ',
omittedTypeIdentifier,
' ${prefix0InstanceMember.name} = ',
'new ',
omittedTypeIdentifier,
'();\n ',
'new ',
omittedTypeIdentifier0,
'()..',
prefixInstanceMember,
' = ',
prefix1StaticMember,
';',
'\n }',
'\n}',
]),
],
newTypeNames: [
'OmittedType',
],
)
];
var omittedTypes = <OmittedTypeAnnotation, String>{};
var library = _TestExecutor().buildAugmentationLibrary(
Fixtures.library.uri,
results,
(_) => throw UnimplementedError(),
(Identifier i) => (i as TestIdentifier).resolved,
(OmittedTypeAnnotation i) =>
(i as TestOmittedTypeAnnotation).inferredType,
omittedTypes: omittedTypes);
expect(library, equalsIgnoringWhitespace('''
augment library 'package:foo/bar.dart';
import 'dart:core' as prefix2_0;
import 'package:foo/foo.dart' as prefix2_1;
import 'package:bar/bar.dart' as prefix2_2;
class OmittedType {
OmittedType2_0 method(OmittedType2_1 o) {
prefix2_0.int prefix = 0;
prefix2_1.OmittedType prefix0 = new prefix2_1.OmittedType();
new prefix2_2.OmittedType0()..prefix = prefix2_2.OmittedType1.prefix1;
}
}
'''));
expect(omittedTypes[omittedType0], 'OmittedType2_0');
expect(omittedTypes[omittedType1], 'OmittedType2_1');
});
test('can augment enums and enum values', () async {
final myEnum = EnumDeclarationImpl(
id: RemoteInstance.uniqueId,
identifier: TestIdentifier(
id: RemoteInstance.uniqueId,
name: 'MyEnum',
kind: IdentifierKind.topLevelMember,
uri: Uri.parse('a.dart'),
staticScope: null),
library: Fixtures.library,
metadata: [],
typeParameters: [],
interfaces: [],
mixins: [],
);
final myField = FieldDeclarationImpl(
id: RemoteInstance.uniqueId,
identifier: TestIdentifier(
id: RemoteInstance.uniqueId,
name: 'value',
kind: IdentifierKind.instanceMember,
uri: Uri.parse('a.dart'),
staticScope: null),
library: Fixtures.library,
metadata: [],
definingType: myEnum.identifier,
hasAbstract: false,
hasConst: false,
hasExternal: false,
hasFinal: true,
hasInitializer: false,
hasLate: false,
hasStatic: false,
type: NamedTypeAnnotationImpl(
id: RemoteInstance.uniqueId,
isNullable: false,
identifier: intIdentifier,
typeArguments: []));
var results = [
MacroExecutionResultImpl(diagnostics: [], enumValueAugmentations: {
myEnum.identifier: [
DeclarationCode.fromParts(['a(1),\n']),
],
}, extendsTypeAugmentations: {}, typeAugmentations: {
myEnum.identifier: [
DeclarationCode.fromParts(['MyEnum(', myField.identifier, ');\n']),
DeclarationCode.fromParts(
['final ', intIdentifier, ' ', myField.identifier.name, ';\n']),
],
}, interfaceAugmentations: {
myEnum.identifier: [
NamedTypeAnnotationCode(name: interfaceIdentifiers.first),
],
}, mixinAugmentations: {
myEnum.identifier: [
NamedTypeAnnotationCode(name: mixinIdentifiers.first),
],
}, newTypeNames: [], libraryAugmentations: []),
];
var library = _TestExecutor().buildAugmentationLibrary(
Fixtures.library.uri,
results,
(Identifier i) =>
i == myEnum.identifier ? myEnum : throw UnimplementedError(),
(Identifier i) => (i as TestIdentifier).resolved,
(OmittedTypeAnnotation i) =>
(i as TestOmittedTypeAnnotation).inferredType);
expect(library, equalsIgnoringWhitespace('''
augment library 'package:foo/bar.dart';
import 'a.dart' as prefix0;
import 'dart:core' as prefix1;
augment enum MyEnum with M0 implements I0 {
a(1),
;
MyEnum(this.value);
final prefix1.int value;
}
'''));
});
test('can augment extensions', () async {
final myExtension = ExtensionDeclarationImpl(
id: RemoteInstance.uniqueId,
identifier: TestIdentifier(
id: RemoteInstance.uniqueId,
name: 'MyExtension',
kind: IdentifierKind.topLevelMember,
uri: Uri.parse('a.dart'),
staticScope: null),
library: Fixtures.library,
metadata: [],
typeParameters: [],
onType: Fixtures.myClassType,
);
final myGetter = MethodDeclarationImpl(
id: RemoteInstance.uniqueId,
identifier: TestIdentifier(
id: RemoteInstance.uniqueId,
name: 'x',
kind: IdentifierKind.instanceMember,
uri: Uri.parse('a.dart'),
staticScope: null),
library: Fixtures.library,
metadata: [],
definingType: myExtension.identifier,
hasExternal: false,
hasStatic: false,
returnType: NamedTypeAnnotationImpl(
id: RemoteInstance.uniqueId,
isNullable: false,
identifier: intIdentifier,
typeArguments: []),
hasBody: true,
isGetter: true,
isOperator: false,
isSetter: false,
namedParameters: [],
positionalParameters: [],
typeParameters: []);
var results = [
MacroExecutionResultImpl(
diagnostics: [],
enumValueAugmentations: {},
extendsTypeAugmentations: {},
typeAugmentations: {
myExtension.identifier: [
DeclarationCode.fromParts([
intIdentifier,
' get ',
myGetter.identifier.name,
' => 1;\n'
]),
],
},
interfaceAugmentations: {},
mixinAugmentations: {},
newTypeNames: [],
libraryAugmentations: []),
];
var library = _TestExecutor().buildAugmentationLibrary(
Fixtures.library.uri,
results,
(Identifier i) => i == myExtension.identifier
? myExtension
: throw UnimplementedError(),
(Identifier i) => (i as TestIdentifier).resolved,
(OmittedTypeAnnotation i) =>
(i as TestOmittedTypeAnnotation).inferredType);
expect(library, equalsIgnoringWhitespace('''
augment library 'package:foo/bar.dart';
import 'dart:core' as prefix0;
augment extension MyExtension {
prefix0.int get x => 1;
}
'''));
});
test('copies keywords for classes', () async {
for (final hasKeywords in [true, false]) {
final clazz = ClassDeclarationImpl(
id: RemoteInstance.uniqueId,
identifier:
IdentifierImpl(id: RemoteInstance.uniqueId, name: 'MyClass'),
library: Fixtures.library,
metadata: [],
typeParameters: [],
interfaces: [],
hasAbstract: hasKeywords,
hasBase: hasKeywords,
hasExternal: hasKeywords,
hasFinal: hasKeywords,
hasInterface: hasKeywords,
hasMixin: hasKeywords,
hasSealed: hasKeywords,
mixins: [],
superclass: null);
var results = [
MacroExecutionResultImpl(
diagnostics: [],
enumValueAugmentations: {},
extendsTypeAugmentations: {},
typeAugmentations: {
clazz.identifier: [
DeclarationCode.fromParts(['']),
]
},
interfaceAugmentations: {},
mixinAugmentations: {},
newTypeNames: [],
libraryAugmentations: []),
];
var library = _TestExecutor().buildAugmentationLibrary(
Fixtures.library.uri,
results,
(Identifier i) =>
i == clazz.identifier ? clazz : throw UnimplementedError(),
(Identifier i) => (i as TestIdentifier).resolved,
(OmittedTypeAnnotation i) =>
(i as TestOmittedTypeAnnotation).inferredType);
final expectedKeywords = [
if (hasKeywords) ...[
'abstract',
'base',
'external',
'final',
'interface',
'mixin',
'sealed'
]
];
// Add extra space after, if we have keywords
if (expectedKeywords.isNotEmpty) expectedKeywords.add('');
expect(library, equalsIgnoringWhitespace('''
augment library 'package:foo/bar.dart';
augment ${expectedKeywords.join(' ')}class MyClass {
}
'''));
}
});
test('copies generic types and bounds', () async {
final clazz = ClassDeclarationImpl(
id: RemoteInstance.uniqueId,
identifier:
IdentifierImpl(id: RemoteInstance.uniqueId, name: 'MyClass'),
library: Fixtures.library,
metadata: [],
typeParameters: [
TypeParameterDeclarationImpl(
id: RemoteInstance.uniqueId,
identifier:
IdentifierImpl(id: RemoteInstance.uniqueId, name: 'T'),
library: Fixtures.library,
metadata: [],
bound: NamedTypeAnnotationImpl(
id: RemoteInstance.uniqueId,
isNullable: false,
identifier: objectIdentifier,
typeArguments: [])),
TypeParameterDeclarationImpl(
id: RemoteInstance.uniqueId,
identifier:
IdentifierImpl(id: RemoteInstance.uniqueId, name: 'S'),
library: Fixtures.library,
metadata: [],
bound: null),
],
interfaces: [],
hasAbstract: false,
hasBase: false,
hasExternal: false,
hasFinal: false,
hasInterface: false,
hasMixin: false,
hasSealed: false,
mixins: [],
superclass: null);
var results = [
MacroExecutionResultImpl(
diagnostics: [],
enumValueAugmentations: {},
extendsTypeAugmentations: {},
typeAugmentations: {
clazz.identifier: [
DeclarationCode.fromParts(['']),
]
},
interfaceAugmentations: {},
mixinAugmentations: {},
newTypeNames: [],
libraryAugmentations: []),
];
var library = _TestExecutor().buildAugmentationLibrary(
Fixtures.library.uri,
results,
(Identifier i) =>
i == clazz.identifier ? clazz : throw UnimplementedError(),
(Identifier i) => (i as TestIdentifier).resolved,
(OmittedTypeAnnotation i) =>
(i as TestOmittedTypeAnnotation).inferredType);
expect(library, equalsIgnoringWhitespace('''
augment library 'package:foo/bar.dart';
import 'dart:core' as prefix0;
augment class MyClass<T extends prefix0.Object, S> {
}
'''));
});
});
}
class _TestExecutor extends MacroExecutor
with AugmentationLibraryBuilder, Fake {}