// Copyright (c) 2021, 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 'dart:io'; import 'dart:isolate'; import 'package:_macros/src/api.dart'; import 'package:_macros/src/bootstrap.dart'; import 'package:_macros/src/executor.dart'; import 'package:_macros/src/executor/isolated_executor.dart' as isolated_executor; import 'package:_macros/src/executor/process_executor.dart' as process_executor show start; import 'package:_macros/src/executor/process_executor.dart' hide start; import 'package:_macros/src/executor/serialization.dart'; import 'package:test/test.dart'; import '../util.dart'; void main() { late MacroExecutor executor; late File kernelOutputFile; final danglingTaskMacroName = 'DanglingTaskMacro'; final danglingTimerMacroName = 'DanglingTimerMacro'; final danglingPeriodicTimerMacroName = 'DanglingPeriodicTimerMacro'; final diagnosticMacroName = 'DiagnosticMacro'; final simpleMacroName = 'SimpleMacro'; late MacroInstanceIdentifier danglingTaskMacroId; late MacroInstanceIdentifier danglingTimerMacroId; late MacroInstanceIdentifier danglingPeriodicTimerMacroId; late MacroInstanceIdentifier diagnosticMacroInstanceId; late MacroInstanceIdentifier simpleMacroInstanceId; late Uri macroUri; late File simpleMacroFile; late Directory tmpDir; for (var executorKind in [ 'Isolated', 'ProcessSocket', 'ProcessStdio', ]) { group('$executorKind executor', () { for (var mode in [SerializationMode.byteData, SerializationMode.json]) { group('$mode', () { setUpAll(() async { simpleMacroFile = File(Platform.script.resolve('simple_macro.dart').toFilePath()); tmpDir = Directory.systemTemp.createTempSync('executor_test'); macroUri = simpleMacroFile.absolute.uri; var bootstrapContent = bootstrapMacroIsolate({ macroUri.toString(): { simpleMacroName: ['', 'named'], danglingTaskMacroName: [''], danglingTimerMacroName: [''], danglingPeriodicTimerMacroName: [''], diagnosticMacroName: [''], } }, mode); var bootstrapFile = File(tmpDir.uri.resolve('main.dart').toFilePath()) ..writeAsStringSync(bootstrapContent); kernelOutputFile = File(tmpDir.uri.resolve('main.dart.dill').toFilePath()); var packageConfigPath = (await Isolate.packageConfig)!.toFilePath(); var buildSnapshotResult = await Process.run(Platform.resolvedExecutable, [ if (executorKind == 'Isolated') ...[ '--snapshot=${kernelOutputFile.uri.toFilePath()}', '--snapshot-kind=kernel', ] else ...[ 'compile', 'exe', '-o', kernelOutputFile.uri.toFilePath(), ], '--packages=$packageConfigPath', bootstrapFile.uri.toFilePath(), ]); expect(buildSnapshotResult.exitCode, 0, reason: 'stdout: ${buildSnapshotResult.stdout}\n' 'stderr: ${buildSnapshotResult.stderr}'); executor = executorKind == 'Isolated' ? await isolated_executor.start(mode, kernelOutputFile.uri) : executorKind == 'ProcessSocket' ? await process_executor.start(mode, CommunicationChannel.socket, kernelOutputFile.path) : await process_executor.start(mode, CommunicationChannel.stdio, kernelOutputFile.path); simpleMacroInstanceId = await executor.instantiateMacro( macroUri, simpleMacroName, '', Arguments([], {})); expect(simpleMacroInstanceId, isNotNull, reason: 'Can create an instance with no arguments.'); executor.disposeMacro(simpleMacroInstanceId); simpleMacroInstanceId = await executor.instantiateMacro( macroUri, simpleMacroName, '', Arguments([IntArgument(1)], {})); expect(simpleMacroInstanceId, isNotNull, reason: 'Can create an instance with positional arguments.'); executor.disposeMacro(simpleMacroInstanceId); simpleMacroInstanceId = await executor.instantiateMacro( macroUri, simpleMacroName, 'named', Arguments([], { 'myBool': BoolArgument(true), 'myDouble': DoubleArgument(1.0), 'myInt': IntArgument(1), 'myList': ListArgument([ IntArgument(1), IntArgument(2), IntArgument(3), ], [ ArgumentKind.nullable, ArgumentKind.int ]), 'mySet': SetArgument([ BoolArgument(true), NullArgument(), MapArgument({StringArgument('a'): DoubleArgument(1.0)}, [ArgumentKind.string, ArgumentKind.double]), ], [ ArgumentKind.nullable, ArgumentKind.object, ]), 'myMap': MapArgument({ StringArgument('x'): IntArgument(1), }, [ ArgumentKind.string, ArgumentKind.int ]), 'myString': StringArgument('a'), })); expect(simpleMacroInstanceId, isNotNull, reason: 'Can create an instance with named arguments.'); danglingTaskMacroId = await executor.instantiateMacro( macroUri, danglingTaskMacroName, '', Arguments([], {})); expect(danglingTaskMacroId, isNotNull); danglingTimerMacroId = await executor.instantiateMacro( macroUri, danglingTimerMacroName, '', Arguments([], {})); expect(danglingTimerMacroId, isNotNull); danglingPeriodicTimerMacroId = await executor.instantiateMacro( macroUri, danglingPeriodicTimerMacroName, '', Arguments([], {})); expect(danglingPeriodicTimerMacroId, isNotNull); diagnosticMacroInstanceId = await executor.instantiateMacro( macroUri, diagnosticMacroName, '', Arguments([], {})); expect(diagnosticMacroInstanceId, isNotNull); }); tearDownAll(() async { executor.disposeMacro(diagnosticMacroInstanceId); executor.disposeMacro(danglingTaskMacroId); executor.disposeMacro(danglingTimerMacroId); executor.disposeMacro(danglingPeriodicTimerMacroId); executor.disposeMacro(simpleMacroInstanceId); await expectLater( () => executor.executeTypesPhase(simpleMacroInstanceId, Fixtures.myFunction, TestTypePhaseIntrospector()), throwsA(isA().having((e) => e.message, 'message', contains('Unrecognized macro instance'))), reason: 'Should be able to dispose macro instances', skip: 'https://github.com/dart-lang/sdk/issues/55992'); if (tmpDir.existsSync()) { try { // Fails flakily on windows if a process still has the file open tmpDir.deleteSync(recursive: true); } catch (_) {} } await executor.close(); }); group('run macros', () { group('in the types phase', () { test('on functions', () async { var result = await executor.executeTypesPhase( simpleMacroInstanceId, Fixtures.myFunction, TestTypePhaseIntrospector()); expect(result.enumValueAugmentations, isEmpty); expect(result.interfaceAugmentations, isEmpty); expect(result.mixinAugmentations, isEmpty); expect(result.typeAugmentations, isEmpty); expect( result.libraryAugmentations.single.debugString().toString(), equalsIgnoringWhitespace('class GeneratedByMyFunction {}')); }); test('on methods', () async { var result = await executor.executeTypesPhase( simpleMacroInstanceId, Fixtures.myMethod, TestTypePhaseIntrospector()); expect(result.enumValueAugmentations, isEmpty); expect(result.interfaceAugmentations, isEmpty); expect(result.mixinAugmentations, isEmpty); expect(result.typeAugmentations, isEmpty); expect( result.libraryAugmentations.single.debugString().toString(), equalsIgnoringWhitespace('class GeneratedByMyMethod {}')); }); test('on getters', () async { var result = await executor.executeTypesPhase( simpleMacroInstanceId, Fixtures.myVariableGetter, TestTypePhaseIntrospector()); expect(result.enumValueAugmentations, isEmpty); expect(result.interfaceAugmentations, isEmpty); expect(result.mixinAugmentations, isEmpty); expect(result.typeAugmentations, isEmpty); expect( result.libraryAugmentations.single.debugString().toString(), equalsIgnoringWhitespace( 'class GeneratedByMyVariableGetter {}')); }); test('on setters', () async { var result = await executor.executeTypesPhase( simpleMacroInstanceId, Fixtures.myVariableSetter, TestTypePhaseIntrospector()); expect(result.enumValueAugmentations, isEmpty); expect(result.interfaceAugmentations, isEmpty); expect(result.mixinAugmentations, isEmpty); expect(result.typeAugmentations, isEmpty); expect( result.libraryAugmentations.single.debugString().toString(), equalsIgnoringWhitespace( 'class GeneratedByMyVariableSetter {}')); }); test('on variables', () async { var result = await executor.executeTypesPhase( simpleMacroInstanceId, Fixtures.myVariable, TestTypePhaseIntrospector()); expect(result.enumValueAugmentations, isEmpty); expect(result.interfaceAugmentations, isEmpty); expect(result.mixinAugmentations, isEmpty); expect(result.typeAugmentations, isEmpty); expect( result.libraryAugmentations.single.debugString().toString(), equalsIgnoringWhitespace( 'class GeneratedBy_myVariable {}')); }); test('on constructors', () async { var result = await executor.executeTypesPhase( simpleMacroInstanceId, Fixtures.myConstructor, TestTypePhaseIntrospector()); expect(result.enumValueAugmentations, isEmpty); expect(result.interfaceAugmentations, isEmpty); expect(result.mixinAugmentations, isEmpty); expect(result.typeAugmentations, isEmpty); expect( result.libraryAugmentations.single.debugString().toString(), equalsIgnoringWhitespace( 'class GeneratedByMyConstructor {}')); }); test('on fields', () async { var result = await executor.executeTypesPhase( simpleMacroInstanceId, Fixtures.myField, TestTypePhaseIntrospector()); expect(result.enumValueAugmentations, isEmpty); expect(result.interfaceAugmentations, isEmpty); expect(result.mixinAugmentations, isEmpty); expect(result.typeAugmentations, isEmpty); expect( result.libraryAugmentations.single.debugString().toString(), equalsIgnoringWhitespace('class GeneratedByMyField {}')); }); test('on classes', () async { var result = await executor.executeTypesPhase( simpleMacroInstanceId, Fixtures.myClass, TestTypePhaseIntrospector()); expect(result.enumValueAugmentations, isEmpty); expect( result .extendsTypeAugmentations[Fixtures.myClass.identifier]! .debugString() .toString(), equals(Fixtures.mySuperclass.identifier.name)); expect( result.interfaceAugmentations.mapValuesToDebugCodeString(), equals({ Fixtures.myClass.identifier: ['HasX'], })); expect( result.mixinAugmentations.mapValuesToDebugCodeString(), equals({ Fixtures.myClass.identifier: ['GetX'], })); expect(result.typeAugmentations, isEmpty); expect( result.libraryAugmentations.mapToDebugCodeString(), unorderedEquals([ equalsIgnoringWhitespace( 'class MyClassBuilder implements Builder ' '{}'), equalsIgnoringWhitespace('''mixin GetX implements HasX { int get x => 1; }'''), equalsIgnoringWhitespace( '''abstract interface class HasX { int get x; }'''), ])); }); test('on enums', () async { var result = await executor.executeTypesPhase( simpleMacroInstanceId, Fixtures.myEnum, TestTypePhaseIntrospector()); expect(result.enumValueAugmentations, isEmpty); expect(result.interfaceAugmentations, isEmpty); expect(result.mixinAugmentations, isEmpty); expect(result.typeAugmentations, isEmpty); expect( result.libraryAugmentations.single.debugString().toString(), equalsIgnoringWhitespace('class GeneratedByMyEnum {}')); }); test('on enum values', () async { var result = await executor.executeTypesPhase( simpleMacroInstanceId, Fixtures.myEnumValues.first, TestTypePhaseIntrospector()); expect(result.enumValueAugmentations, isEmpty); expect(result.interfaceAugmentations, isEmpty); expect(result.mixinAugmentations, isEmpty); expect(result.typeAugmentations, isEmpty); expect( result.libraryAugmentations.single.debugString().toString(), equalsIgnoringWhitespace('class GeneratedByMyEnum_A {}')); }); test('on extensions', () async { var result = await executor.executeTypesPhase( simpleMacroInstanceId, Fixtures.myExtension, TestTypePhaseIntrospector()); expect(result.enumValueAugmentations, isEmpty); expect(result.typeAugmentations, isEmpty); expect( result.libraryAugmentations.single.debugString().toString(), equalsIgnoringWhitespace('class MyExtensionOnMyClass {}')); }); test('on extension types', () async { var result = await executor.executeTypesPhase( simpleMacroInstanceId, Fixtures.myExtensionType, TestTypePhaseIntrospector()); expect(result.enumValueAugmentations, isEmpty); expect(result.typeAugmentations, isEmpty); expect( result.libraryAugmentations.single.debugString().toString(), equalsIgnoringWhitespace( 'class MyExtensionTypeOnMyClass {}')); }); test('on type aliases', () async { var result = await executor.executeTypesPhase( simpleMacroInstanceId, Fixtures.myTypeAlias, TestTypePhaseIntrospector(), ); expect(result.enumValueAugmentations, isEmpty); expect(result.typeAugmentations, isEmpty); expect( result.libraryAugmentations.single.debugString().toString(), equalsIgnoringWhitespace( 'class MyTypeAliasAliasedTypeMyClass {}', ), ); }); test('on mixins', () async { var result = await executor.executeTypesPhase( simpleMacroInstanceId, Fixtures.myMixin, TestTypePhaseIntrospector()); expect(result.enumValueAugmentations, isEmpty); expect(result.interfaceAugmentations, isEmpty); expect(result.mixinAugmentations, isEmpty); expect(result.typeAugmentations, isEmpty); expect( result.libraryAugmentations.single.debugString().toString(), equalsIgnoringWhitespace( 'class GeneratedByMyMixinOnMyClass {}')); }); test('on libraries', () async { var result = await executor.executeTypesPhase( simpleMacroInstanceId, Fixtures.library, TestTypePhaseIntrospector(), ); expect(result.enumValueAugmentations, isEmpty); expect(result.interfaceAugmentations, isEmpty); expect(result.mixinAugmentations, isEmpty); expect(result.typeAugmentations, isEmpty); expect( result.libraryAugmentations.single.debugString().toString(), equalsIgnoringWhitespace(''' class LibraryInfo { final Uri uri; final String languageVersion; final List definedTypes; const LibraryInfo(this.uri, this.languageVersion, this.definedTypes); } '''), ); }); test('Cannot leave dangling tasks', () async { expect( () => executor.executeTypesPhase(danglingTaskMacroId, Fixtures.library, TestTypePhaseIntrospector()), throwsA( isA().having((e) => e.message, 'message', contains('1 async tasks still pending')), )); }); test('Cannot leave dangling timers', () async { expect( () => executor.executeTypesPhase(danglingTimerMacroId, Fixtures.library, TestTypePhaseIntrospector()), throwsA( isA().having((e) => e.message, 'message', contains('1 active timers')), )); expect( () => executor.executeTypesPhase( danglingPeriodicTimerMacroId, Fixtures.library, TestTypePhaseIntrospector()), throwsA( isA().having((e) => e.message, 'message', contains('1 active timers')), )); }); }); group('in the declaration phase', () { test('on functions', () async { var result = await executor.executeDeclarationsPhase( simpleMacroInstanceId, Fixtures.myFunction, Fixtures.testDeclarationPhaseIntrospector); expect(result.enumValueAugmentations, isEmpty); expect(result.interfaceAugmentations, isEmpty); expect(result.mixinAugmentations, isEmpty); expect(result.typeAugmentations, isEmpty); expect( result.libraryAugmentations.single.debugString().toString(), equalsIgnoringWhitespace( 'String delegateMyFunction() => myFunction();')); }); test('on methods', () async { var result = await executor.executeDeclarationsPhase( simpleMacroInstanceId, Fixtures.myMethod, Fixtures.testDeclarationPhaseIntrospector); expect(result.enumValueAugmentations, isEmpty); expect(result.interfaceAugmentations, isEmpty); expect(result.mixinAugmentations, isEmpty); expect(result.typeAugmentations, isEmpty); expect( result.libraryAugmentations.single.debugString().toString(), equalsIgnoringWhitespace( '(String, bool? hello, {String world}) ' 'delegateMemberMyMethod() => myMethod();')); }); test('on constructors', () async { var result = await executor.executeDeclarationsPhase( simpleMacroInstanceId, Fixtures.myConstructor, Fixtures.testDeclarationPhaseIntrospector); expect(result.enumValueAugmentations, isEmpty); expect(result.interfaceAugmentations, isEmpty); expect(result.mixinAugmentations, isEmpty); expect(result.typeAugmentations, hasLength(1)); expect( result .typeAugmentations[Fixtures.myConstructor.definingType]! .single .debugString() .toString(), equalsIgnoringWhitespace(''' factory MyClass.myConstructorDelegate() => MyClass.myConstructor(); ''')); expect(result.libraryAugmentations, isEmpty); }); test('on getters', () async { var result = await executor.executeDeclarationsPhase( simpleMacroInstanceId, Fixtures.myVariableGetter, Fixtures.testDeclarationPhaseIntrospector); expect(result.enumValueAugmentations, isEmpty); expect(result.interfaceAugmentations, isEmpty); expect(result.mixinAugmentations, isEmpty); expect(result.typeAugmentations, isEmpty); expect( result.libraryAugmentations.single.debugString().toString(), equalsIgnoringWhitespace(''' String get delegateMyVariable => myVariable;''')); }); test('on setters', () async { var result = await executor.executeDeclarationsPhase( simpleMacroInstanceId, Fixtures.myVariableSetter, Fixtures.testDeclarationPhaseIntrospector); expect(result.enumValueAugmentations, isEmpty); expect(result.interfaceAugmentations, isEmpty); expect(result.mixinAugmentations, isEmpty); expect(result.typeAugmentations, isEmpty); expect( result.libraryAugmentations.single.debugString().toString(), equalsIgnoringWhitespace(''' void set delegateMyVariable(String value) => myVariable = value;''')); }); test('on variables', () async { var result = await executor.executeDeclarationsPhase( simpleMacroInstanceId, Fixtures.myVariable, Fixtures.testDeclarationPhaseIntrospector); expect(result.enumValueAugmentations, isEmpty); expect(result.interfaceAugmentations, isEmpty); expect(result.mixinAugmentations, isEmpty); expect(result.typeAugmentations, isEmpty); expect( result.libraryAugmentations.single.debugString().toString(), equalsIgnoringWhitespace(''' /*inferred*/String get delegate_myVariable => _myVariable;''')); }); test('on fields', () async { var result = await executor.executeDeclarationsPhase( simpleMacroInstanceId, Fixtures.myField, Fixtures.testDeclarationPhaseIntrospector); expect(result.enumValueAugmentations, isEmpty); expect(result.interfaceAugmentations, isEmpty); expect(result.mixinAugmentations, isEmpty); expect(result.typeAugmentations, hasLength(1)); expect( result.typeAugmentations[Fixtures.myField.definingType]! .single .debugString() .toString(), equalsIgnoringWhitespace(''' String get delegateMyField => myField; ''')); expect(result.libraryAugmentations, isEmpty); }); test('on classes', () async { var result = await executor.executeDeclarationsPhase( simpleMacroInstanceId, Fixtures.myClass, Fixtures.testDeclarationPhaseIntrospector); expect(result.enumValueAugmentations, isEmpty); expect(result.interfaceAugmentations, isEmpty); expect(result.mixinAugmentations, isEmpty); expect(result.typeAugmentations, hasLength(1)); expect( result .typeAugmentations[Fixtures.myClass.identifier]!.single .debugString() .toString(), equalsIgnoringWhitespace(''' static const List fieldNames = ['myField',]; ''')); }); test('on enums', () async { var result = await executor.executeDeclarationsPhase( simpleMacroInstanceId, Fixtures.myEnum, Fixtures.testDeclarationPhaseIntrospector); expect(result.enumValueAugmentations, isEmpty); expect(result.interfaceAugmentations, isEmpty); expect(result.mixinAugmentations, isEmpty); expect(result.typeAugmentations, hasLength(1)); expect( result.typeAugmentations[Fixtures.myEnum.identifier]!.single .debugString() .toString(), equalsIgnoringWhitespace(''' static const List valuesByName = {'a': a}; ''')); expect(result.libraryAugmentations, isEmpty); }); test('on enum values', () async { var result = await executor.executeDeclarationsPhase( simpleMacroInstanceId, Fixtures.myEnumValues.first, Fixtures.testDeclarationPhaseIntrospector); expect(result.enumValueAugmentations, isEmpty); expect(result.interfaceAugmentations, isEmpty); expect(result.mixinAugmentations, isEmpty); expect(result.typeAugmentations, hasLength(1)); expect( result.typeAugmentations[Fixtures.myEnum.identifier]!.single .debugString() .toString(), equalsIgnoringWhitespace(''' MyEnum aToString() => a.toString(); ''')); expect(result.libraryAugmentations, isEmpty); }); test('on extensions', () async { var result = await executor.executeDeclarationsPhase( simpleMacroInstanceId, Fixtures.myExtension, Fixtures.testDeclarationPhaseIntrospector); expect(result.enumValueAugmentations, isEmpty); expect(result.typeAugmentations, hasLength(1)); expect( result.typeAugmentations[Fixtures.myExtension.identifier]! .single .debugString() .toString(), equalsIgnoringWhitespace(''' List get onTypeFieldNames; ''')); expect(result.libraryAugmentations, isEmpty); }); test('on extension types', () async { var result = await executor.executeDeclarationsPhase( simpleMacroInstanceId, Fixtures.myExtensionType, Fixtures.testDeclarationPhaseIntrospector); expect(result.enumValueAugmentations, isEmpty); expect(result.typeAugmentations, hasLength(1)); expect( result .typeAugmentations[Fixtures.myExtensionType.identifier]! .single .debugString() .toString(), equalsIgnoringWhitespace(''' List get onTypeFieldNames; ''')); expect(result.libraryAugmentations, isEmpty); }); test('on type aliases', () async { var result = await executor.executeDeclarationsPhase( simpleMacroInstanceId, Fixtures.myTypeAlias, Fixtures.testDeclarationPhaseIntrospector, ); expect(result.enumValueAugmentations, isEmpty); expect(result.libraryAugmentations, hasLength(1)); expect( result.libraryAugmentations.single.debugString().toString(), equalsIgnoringWhitespace( 'List get aliasedTypeFieldNames;', ), ); expect(result.typeAugmentations, isEmpty); }); test('on mixins', () async { var result = await executor.executeDeclarationsPhase( simpleMacroInstanceId, Fixtures.myMixin, Fixtures.testDeclarationPhaseIntrospector); expect(result.enumValueAugmentations, isEmpty); expect(result.interfaceAugmentations, isEmpty); expect(result.mixinAugmentations, isEmpty); expect(result.typeAugmentations, hasLength(1)); expect( result .typeAugmentations[Fixtures.myMixin.identifier]!.single .debugString() .toString(), equalsIgnoringWhitespace(''' static const List methodNames = ['myMixinMethod',]; ''')); expect(result.libraryAugmentations, isEmpty); }); test('on libraries', () async { var result = await executor.executeDeclarationsPhase( simpleMacroInstanceId, Fixtures.library, Fixtures.testDeclarationPhaseIntrospector); expect(result.enumValueAugmentations, isEmpty); expect(result.interfaceAugmentations, isEmpty); expect(result.mixinAugmentations, isEmpty); expect(result.typeAugmentations, isEmpty); expect( result.libraryAugmentations.single.debugString().toString(), equalsIgnoringWhitespace('final LibraryInfo library;'), ); }); test('Cannot leave dangling tasks', () async { expect( () => executor.executeDeclarationsPhase( danglingTaskMacroId, Fixtures.library, Fixtures.testDeclarationPhaseIntrospector), throwsA( isA().having((e) => e.message, 'message', contains('1 async tasks still pending')), )); }); test('Cannot leave dangling timers', () async { expect( () => executor.executeDeclarationsPhase( danglingTimerMacroId, Fixtures.library, Fixtures.testDeclarationPhaseIntrospector), throwsA( isA().having((e) => e.message, 'message', contains('1 active timers')), )); expect( () => executor.executeDeclarationsPhase( danglingPeriodicTimerMacroId, Fixtures.library, Fixtures.testDeclarationPhaseIntrospector), throwsA( isA().having((e) => e.message, 'message', contains('1 active timers')), )); }); }); group('in the definition phase', () { test('on functions', () async { var result = await executor.executeDefinitionsPhase( simpleMacroInstanceId, Fixtures.myFunction, Fixtures.testDefinitionPhaseIntrospector); expect(result.enumValueAugmentations, isEmpty); expect(result.interfaceAugmentations, isEmpty); expect(result.mixinAugmentations, isEmpty); expect(result.typeAugmentations, isEmpty); expect( result.libraryAugmentations.single.debugString().toString(), equalsIgnoringWhitespace(''' // A comment! augment String myFunction() { print('isExternal: false'); print('isGetter: false'); print('isSetter: false'); print('returnType: String'); return augmented(); }''')); }); test('on methods', () async { var definitionResult = await executor.executeDefinitionsPhase( simpleMacroInstanceId, Fixtures.myMethod, Fixtures.testDefinitionPhaseIntrospector); expect(definitionResult.enumValueAugmentations, isEmpty); expect(definitionResult.interfaceAugmentations, isEmpty); expect(definitionResult.mixinAugmentations, isEmpty); expect(definitionResult.typeAugmentations, hasLength(1)); var augmentationStrings = definitionResult .typeAugmentations[Fixtures.myMethod.definingType]! .mapToDebugCodeString(); expect(augmentationStrings, unorderedEquals(methodDefinitionMatchers)); expect(definitionResult.libraryAugmentations, isEmpty); }); test('on regular constructors', () async { var definitionResult = await executor.executeDefinitionsPhase( simpleMacroInstanceId, Fixtures.myConstructor, Fixtures.testDefinitionPhaseIntrospector); expect(definitionResult.enumValueAugmentations, isEmpty); expect(definitionResult.interfaceAugmentations, isEmpty); expect(definitionResult.mixinAugmentations, isEmpty); expect(definitionResult.typeAugmentations, hasLength(1)); expect( definitionResult .typeAugmentations[Fixtures.myConstructor.definingType]! .first .debugString() .toString(), constructorDefinitionMatcher); expect(definitionResult.libraryAugmentations, isEmpty); }); test('on factory constructors', () async { var definitionResult = await executor.executeDefinitionsPhase( simpleMacroInstanceId, Fixtures.myFactoryConstructor, Fixtures.testDefinitionPhaseIntrospector); expect(definitionResult.enumValueAugmentations, isEmpty); expect(definitionResult.interfaceAugmentations, isEmpty); expect(definitionResult.mixinAugmentations, isEmpty); expect(definitionResult.typeAugmentations, hasLength(1)); expect( definitionResult .typeAugmentations[ Fixtures.myFactoryConstructor.definingType]! .first .debugString() .toString(), factoryConstructorDefinitionMatcher); expect(definitionResult.libraryAugmentations, isEmpty); }); test('on const constructors', () async { var definitionResult = await executor.executeDefinitionsPhase( simpleMacroInstanceId, Fixtures.myConstConstructor, Fixtures.testDefinitionPhaseIntrospector); expect(definitionResult.enumValueAugmentations, isEmpty); expect(definitionResult.interfaceAugmentations, isEmpty); expect(definitionResult.mixinAugmentations, isEmpty); expect(definitionResult.typeAugmentations, hasLength(1)); expect( definitionResult .typeAugmentations[ Fixtures.myConstConstructor.definingType]! .first .debugString() .toString(), constConstructorDefinitionMatcher); expect(definitionResult.libraryAugmentations, isEmpty); }); test('on getters', () async { var result = await executor.executeDefinitionsPhase( simpleMacroInstanceId, Fixtures.myVariableGetter, Fixtures.testDefinitionPhaseIntrospector); expect(result.enumValueAugmentations, isEmpty); expect(result.interfaceAugmentations, isEmpty); expect(result.mixinAugmentations, isEmpty); expect(result.typeAugmentations, isEmpty); expect( result.libraryAugmentations.single.debugString().toString(), equalsIgnoringWhitespace(''' // A comment! augment String get myVariable { print('isExternal: false'); print('isGetter: true'); print('isSetter: false'); print('returnType: String'); return augmented; }''')); }); test('on setters', () async { var result = await executor.executeDefinitionsPhase( simpleMacroInstanceId, Fixtures.myVariableSetter, Fixtures.testDefinitionPhaseIntrospector); expect(result.enumValueAugmentations, isEmpty); expect(result.interfaceAugmentations, isEmpty); expect(result.mixinAugmentations, isEmpty); expect(result.typeAugmentations, isEmpty); expect( result.libraryAugmentations.single.debugString().toString(), equalsIgnoringWhitespace(''' // A comment! augment void set myVariable(String value, ) { print('isExternal: false'); print('isGetter: false'); print('isSetter: true'); print('returnType: void'); print('positionalParam: String value'); return augmented = value; }''')); }); test('on variables', () async { var result = await executor.executeDefinitionsPhase( simpleMacroInstanceId, Fixtures.myVariable, Fixtures.testDefinitionPhaseIntrospector); expect(result.enumValueAugmentations, isEmpty); expect(result.interfaceAugmentations, isEmpty); expect(result.mixinAugmentations, isEmpty); expect(result.typeAugmentations, isEmpty); expect( result.libraryAugmentations.mapToDebugCodeString(), unorderedEquals([ equalsIgnoringWhitespace(''' augment /*inferred*/String get _myVariable { print('parentClass: '); print('isExternal: false'); print('isFinal: true'); print('isLate: false'); return augmented; }'''), equalsIgnoringWhitespace(''' augment set _myVariable(/*inferred*/String value) { augmented = value; }'''), equalsIgnoringWhitespace(''' augment final /*inferred*/String _myVariable = 'new initial value' + augmented; '''), ])); }); test('on fields', () async { var definitionResult = await executor.executeDefinitionsPhase( simpleMacroInstanceId, Fixtures.myField, Fixtures.testDefinitionPhaseIntrospector); expect(definitionResult.enumValueAugmentations, isEmpty); expect(definitionResult.interfaceAugmentations, isEmpty); expect(definitionResult.mixinAugmentations, isEmpty); expect(definitionResult.typeAugmentations, hasLength(1)); expect( definitionResult .typeAugmentations[Fixtures.myField.definingType]! .mapToDebugCodeString(), unorderedEquals(fieldDefinitionMatchers)); expect(definitionResult.libraryAugmentations, isEmpty); }); test('on classes', () async { var definitionResult = await executor.executeDefinitionsPhase( simpleMacroInstanceId, Fixtures.myClass, Fixtures.testDefinitionPhaseIntrospector); expect(definitionResult.enumValueAugmentations, isEmpty); expect(definitionResult.interfaceAugmentations, isEmpty); expect(definitionResult.mixinAugmentations, isEmpty); expect(definitionResult.typeAugmentations, hasLength(1)); var augmentationStrings = definitionResult .typeAugmentations[Fixtures.myClass.identifier]! .mapToDebugCodeString(); expect( augmentationStrings, unorderedEquals([ ...methodDefinitionMatchers, constructorDefinitionMatcher, constConstructorDefinitionMatcher, factoryConstructorDefinitionMatcher, ...fieldDefinitionMatchers, ])); }); test('on enums', () async { var definitionResult = await executor.executeDefinitionsPhase( simpleMacroInstanceId, Fixtures.myEnum, Fixtures.testDefinitionPhaseIntrospector); expect(definitionResult.enumValueAugmentations, hasLength(1)); var entryAugmentationStrings = definitionResult .enumValueAugmentations[Fixtures.myEnum.identifier]! .mapToDebugCodeString() .toList(); expect(entryAugmentationStrings, unorderedEquals(["a('myField', ),"])); expect(definitionResult.interfaceAugmentations, isEmpty); expect(definitionResult.mixinAugmentations, isEmpty); expect(definitionResult.typeAugmentations, hasLength(1)); var typeAugmentationStrings = definitionResult .typeAugmentations[Fixtures.myEnum.identifier]! .mapToDebugCodeString(); expect( typeAugmentationStrings, unorderedEquals([ equalsIgnoringWhitespace(''' augment MyEnum.myEnumConstructor(String this.myField, ) { print('definingClass: MyEnum'); print('isConst: false'); print('isFactory: false'); print('isExternal: false'); print('isGetter: false'); print('isSetter: false'); print('returnType: MyEnum'); print('positionalParam: String myField'); return augmented(); }''') ])); }); test('on enum values', () async { var definitionResult = await executor.executeDefinitionsPhase( simpleMacroInstanceId, Fixtures.myEnumValues.first, Fixtures.testDefinitionPhaseIntrospector); expect(definitionResult.enumValueAugmentations, hasLength(1)); expect(definitionResult.interfaceAugmentations, isEmpty); expect(definitionResult.mixinAugmentations, isEmpty); var augmentationStrings = definitionResult .enumValueAugmentations[Fixtures.myEnum.identifier]! .mapToDebugCodeString(); expect( augmentationStrings, unorderedEquals(["a('myField', ),"])); expect(definitionResult.typeAugmentations, isEmpty); }); test('on extensions', () async { var definitionResult = await executor.executeDefinitionsPhase( simpleMacroInstanceId, Fixtures.myExtension, Fixtures.testDefinitionPhaseIntrospector); expect(definitionResult.enumValueAugmentations, isEmpty); expect(definitionResult.typeAugmentations, hasLength(1)); expect( definitionResult .typeAugmentations[Fixtures.myExtension.identifier]! .single .debugString() .toString(), equalsIgnoringWhitespace( "augment List get onTypeFieldNames => " "['myField',];")); }); test('on extension types', () async { var definitionResult = await executor.executeDefinitionsPhase( simpleMacroInstanceId, Fixtures.myExtensionType, Fixtures.testDefinitionPhaseIntrospector); expect(definitionResult.enumValueAugmentations, isEmpty); expect(definitionResult.typeAugmentations, hasLength(1)); expect( definitionResult .typeAugmentations[Fixtures.myExtensionType.identifier]! .single .debugString() .toString(), equalsIgnoringWhitespace( "augment List get onTypeFieldNames => " "['myField',];")); }); test('on mixins', () async { var definitionResult = await executor.executeDefinitionsPhase( simpleMacroInstanceId, Fixtures.myMixin, Fixtures.testDefinitionPhaseIntrospector); expect(definitionResult.enumValueAugmentations, isEmpty); expect(definitionResult.interfaceAugmentations, isEmpty); expect(definitionResult.mixinAugmentations, isEmpty); expect(definitionResult.typeAugmentations, hasLength(1)); var augmentationStrings = definitionResult .typeAugmentations[Fixtures.myMixin.identifier]! .mapToDebugCodeString(); expect( augmentationStrings, unorderedEquals( mixinMethodDefinitionMatchers, )); }); test('on libraries', () async { var result = await executor.executeDefinitionsPhase( simpleMacroInstanceId, Fixtures.library, Fixtures.testDefinitionPhaseIntrospector); expect(result.enumValueAugmentations, isEmpty); expect(result.interfaceAugmentations, isEmpty); expect(result.mixinAugmentations, isEmpty); expect(result.typeAugmentations, isEmpty); expect( result.libraryAugmentations.single.debugString().toString(), equalsIgnoringWhitespace(''' augment final LibraryInfo library = LibraryInfo(Uri.parse('package:foo/bar.dart'), '3.0', [MyClass, MyEnum, MyMixin, ]); ''')); }); test('Cannot leave dangling tasks', () async { expect( () => executor.executeDefinitionsPhase( danglingTaskMacroId, Fixtures.library, Fixtures.testDefinitionPhaseIntrospector), throwsA( isA().having((e) => e.message, 'message', contains('1 async tasks still pending')), )); }); test('Cannot leave dangling timers', () async { expect( () => executor.executeDefinitionsPhase( danglingTimerMacroId, Fixtures.library, Fixtures.testDefinitionPhaseIntrospector), throwsA( isA().having((e) => e.message, 'message', contains('1 active timers')), )); expect( () => executor.executeDefinitionsPhase( danglingPeriodicTimerMacroId, Fixtures.library, Fixtures.testDefinitionPhaseIntrospector), throwsA( isA().having((e) => e.message, 'message', contains('1 active timers')), )); }); }); test('and report diagnostics', () async { final result = await executor.executeTypesPhase( diagnosticMacroInstanceId, Fixtures.myClass, TestTypePhaseIntrospector()); expect(result.diagnostics, [ predicate((d) => d.severity == Severity.info && d.message.message == 'superclass' && (d.message.target as TypeAnnotationDiagnosticTarget) .typeAnnotation == Fixtures.mySuperclassType && d.contextMessages.single.message == 'interface' && (d.contextMessages.single.target as TypeAnnotationDiagnosticTarget) .typeAnnotation == Fixtures.myInterfaceType && d.correctionMessage == 'correct me!'), predicate((d) => d.severity == Severity.error && d.message.message.contains('bug in the macro') && // Quick test that some stack trace also appears d.contextMessages.first.message .contains('simple_macro.dart')), ]); }); }); }); } }); } } final constructorDefinitionMatcher = equalsIgnoringWhitespace(''' augment MyClass.myConstructor(/*inferred*/String super.myField, ) { print('definingClass: MyClass'); print('isConst: false'); print('isFactory: false'); print('isExternal: false'); print('isGetter: false'); print('isSetter: false'); print('returnType: MyClass'); print('positionalParam: String (inferred) myField'); return augmented(); }'''); final constConstructorDefinitionMatcher = equalsIgnoringWhitespace(''' augment const MyClass.myConstConstructor(/*inferred*/String this.myField, ) { print('definingClass: MyClass'); print('isConst: true'); print('isFactory: false'); print('isExternal: false'); print('isGetter: false'); print('isSetter: false'); print('returnType: MyClass'); print('positionalParam: String (inferred) myField'); return augmented(); }'''); final factoryConstructorDefinitionMatcher = equalsIgnoringWhitespace(''' augment factory MyClass.myFactoryConstructor(/*inferred*/String myField, ) { print('definingClass: MyClass'); print('isConst: false'); print('isFactory: true'); print('isExternal: false'); print('isGetter: false'); print('isSetter: false'); print('returnType: MyClass'); print('positionalParam: String (inferred) myField'); return augmented(); }'''); final fieldDefinitionMatchers = [ equalsIgnoringWhitespace(''' augment String get myField { print('parentClass: MyClass'); print('isAbstract: false'); print('isExternal: false'); print('isFinal: false'); print('isLate: false'); return augmented; }'''), equalsIgnoringWhitespace(''' augment set myField(String value) { augmented = value; }'''), equalsIgnoringWhitespace(''' augment String myField = 'new initial value' + augmented;'''), ]; final methodDefinitionMatchers = [ equalsIgnoringWhitespace(''' // A comment! augment (String, bool? hello, {String world}) myMethod() { print('definingClass: MyClass'); print('isExternal: false'); print('isGetter: false'); print('isSetter: false'); print('returnType: (String, bool? hello, {String world})'); return augmented(); }'''), equalsIgnoringWhitespace(''' augment (String, bool? hello, {String world}) myMethod() { print('myBool: true'); print('myDouble: 1.0'); print('myInt: 1'); print('myList: [1, 2, 3]'); print('mySet: {true, null, {a: 1.0}}'); print('myMap: {x: 1}'); print('myString: a'); print('parentClass: MyClass'); print('superClass: MySuperclass'); print('interface: MyInterface'); print('mixin: MyMixin'); print('field: myField'); print('method: myMethod'); print('constructor: myConstructor'); print('constructor: myConstConstructor'); print('constructor: myFactoryConstructor'); return augmented(); }'''), ]; final mixinMethodDefinitionMatchers = [ equalsIgnoringWhitespace(''' // A comment! augment (String, bool? hello, {String world}) myMixinMethod() { print('definingClass: MyMixin'); print('isExternal: false'); print('isGetter: false'); print('isSetter: false'); print('returnType: (String, bool? hello, {String world})'); return augmented(); }'''), equalsIgnoringWhitespace(''' augment (String, bool? hello, {String world}) myMixinMethod() { print('myBool: true'); print('myDouble: 1.0'); print('myInt: 1'); print('myList: [1, 2, 3]'); print('mySet: {true, null, {a: 1.0}}'); print('myMap: {x: 1}'); print('myString: a'); print('parentClass: MyMixin'); print('superClass: null'); print('method: myMixinMethod'); return augmented(); }'''), ];