| // 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:_fe_analyzer_shared/src/macros/executor/multi_executor.dart' |
| as macro; |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/src/dart/element/element.dart'; |
| import 'package:analyzer/src/test_utilities/package_config_file_builder.dart'; |
| import 'package:test/test.dart'; |
| import 'package:test_reflective_loader/test_reflective_loader.dart'; |
| |
| import 'element_text.dart'; |
| import 'elements_base.dart'; |
| import 'repository_macro_kernel_builder.dart'; |
| |
| main() { |
| try { |
| MacrosEnvironment.instance; |
| } catch (_) { |
| print('Cannot initialize environment. Skip macros tests.'); |
| test('fake', () {}); |
| return; |
| } |
| |
| defineReflectiveSuite(() { |
| defineReflectiveTests(MacroElementsKeepLinkingTest); |
| defineReflectiveTests(MacroElementsFromBytesTest); |
| }); |
| } |
| |
| @reflectiveTest |
| class MacroElementsFromBytesTest extends MacroElementsTest { |
| @override |
| bool get keepLinkingLibraries => false; |
| } |
| |
| @reflectiveTest |
| class MacroElementsKeepLinkingTest extends MacroElementsTest { |
| @override |
| bool get keepLinkingLibraries => true; |
| } |
| |
| class MacroElementsTest extends ElementsBaseTest { |
| @override |
| bool get keepLinkingLibraries => false; |
| |
| /// The path for external packages. |
| String get packagesRootPath => '/packages'; |
| |
| /// Return the code for `DeclarationTextMacro`. |
| String get _declarationTextCode { |
| var code = MacrosEnvironment.instance.packageAnalyzerFolder |
| .getChildAssumingFile('test/src/summary/macro/declaration_text.dart') |
| .readAsStringSync(); |
| return code.replaceAll('/*macro*/', 'macro'); |
| } |
| |
| Future<void> setUp() async { |
| writeTestPackageConfig( |
| PackageConfigFileBuilder(), |
| macrosEnvironment: MacrosEnvironment.instance, |
| ); |
| |
| macroKernelBuilder = DartRepositoryMacroKernelBuilder( |
| MacrosEnvironment.instance.platformDillBytes, |
| ); |
| |
| macroExecutor = macro.MultiMacroExecutor(); |
| } |
| |
| Future<void> tearDown() async { |
| await macroExecutor?.close(); |
| } |
| |
| test_build_types() async { |
| newFile2('$testPackageLibPath/a.dart', r''' |
| import 'dart:async'; |
| import 'package:_fe_analyzer_shared/src/macros/api.dart'; |
| |
| macro class MyMacro implements ClassTypesMacro { |
| FutureOr<void> buildTypesForClass(clazz, builder) { |
| builder.declareType( |
| 'MyClass', |
| DeclarationCode.fromString('class MyClass {}'), |
| ); |
| } |
| } |
| '''); |
| |
| var library = await buildLibrary(r''' |
| import 'a.dart'; |
| |
| @MyMacro() |
| class A {} |
| ''', preBuildSequence: [ |
| {'package:test/a.dart'} |
| ]); |
| |
| checkElementText( |
| library, |
| r''' |
| library |
| imports |
| package:test/a.dart |
| definingUnit |
| classes |
| class A @35 |
| metadata |
| Annotation |
| atSign: @ @18 |
| name: SimpleIdentifier |
| token: MyMacro @19 |
| staticElement: package:test/a.dart::@class::MyMacro |
| staticType: null |
| arguments: ArgumentList |
| leftParenthesis: ( @26 |
| rightParenthesis: ) @27 |
| element: package:test/a.dart::@class::MyMacro::@constructor::• |
| constructors |
| synthetic @-1 |
| parts |
| package:test/_macro_types.dart |
| classes |
| class MyClass @6 |
| constructors |
| synthetic @-1 |
| exportScope |
| A: package:test/test.dart;A |
| MyClass: package:test/test.dart;package:test/_macro_types.dart;MyClass |
| ''', |
| withExportScope: true); |
| } |
| |
| test_introspect_types_ClassDeclaration_interfaces() async { |
| await _assertTypesPhaseIntrospectionText(r''' |
| class A implements B, C<int, String> {} |
| ''', r''' |
| class A |
| interfaces |
| B |
| C<int, String> |
| '''); |
| } |
| |
| test_introspect_types_ClassDeclaration_isAbstract() async { |
| await _assertTypesPhaseIntrospectionText(r''' |
| abstract class A {} |
| ''', r''' |
| abstract class A |
| '''); |
| } |
| |
| test_introspect_types_ClassDeclaration_mixins() async { |
| await _assertTypesPhaseIntrospectionText(r''' |
| class A with B, C<int, String> {} |
| ''', r''' |
| class A |
| mixins |
| B |
| C<int, String> |
| '''); |
| } |
| |
| test_introspect_types_ClassDeclaration_superclass() async { |
| await _assertTypesPhaseIntrospectionText(r''' |
| class A extends B {} |
| ''', r''' |
| class A |
| superclass: B |
| '''); |
| } |
| |
| test_introspect_types_ClassDeclaration_superclass_nullable() async { |
| await _assertTypesPhaseIntrospectionText(r''' |
| class A extends B<int?> {} |
| ''', r''' |
| class A |
| superclass: B<int?> |
| '''); |
| } |
| |
| test_introspect_types_ClassDeclaration_superclass_typeArguments() async { |
| await _assertTypesPhaseIntrospectionText(r''' |
| class A extends B<String, List<int>> {} |
| ''', r''' |
| class A |
| superclass: B<String, List<int>> |
| '''); |
| } |
| |
| test_introspect_types_ClassDeclaration_typeParameters() async { |
| await _assertTypesPhaseIntrospectionText(r''' |
| class A<T, U extends List<T>> {} |
| ''', r''' |
| class A |
| typeParameters |
| T |
| U |
| bound: List<T> |
| '''); |
| } |
| |
| test_macroFlag_class() async { |
| var library = await buildLibrary(r''' |
| macro class A {} |
| '''); |
| checkElementText(library, r''' |
| library |
| definingUnit |
| classes |
| macro class A @12 |
| constructors |
| synthetic @-1 |
| '''); |
| } |
| |
| test_macroFlag_classAlias() async { |
| var library = await buildLibrary(r''' |
| mixin M {} |
| macro class A = Object with M; |
| '''); |
| checkElementText(library, r''' |
| library |
| definingUnit |
| classes |
| macro class alias A @23 |
| supertype: Object |
| mixins |
| M |
| constructors |
| synthetic const @-1 |
| constantInitializers |
| SuperConstructorInvocation |
| superKeyword: super @0 |
| argumentList: ArgumentList |
| leftParenthesis: ( @0 |
| rightParenthesis: ) @0 |
| staticElement: dart:core::@class::Object::@constructor::• |
| mixins |
| mixin M @6 |
| superclassConstraints |
| Object |
| '''); |
| } |
| |
| void writeTestPackageConfig( |
| PackageConfigFileBuilder config, { |
| MacrosEnvironment? macrosEnvironment, |
| }) { |
| config = config.copy(); |
| |
| config.add( |
| name: 'test', |
| rootPath: testPackageRootPath, |
| ); |
| |
| if (macrosEnvironment != null) { |
| var packagesRootFolder = getFolder(packagesRootPath); |
| macrosEnvironment.packageSharedFolder.copyTo(packagesRootFolder); |
| config.add( |
| name: '_fe_analyzer_shared', |
| rootPath: getFolder('$packagesRootPath/_fe_analyzer_shared').path, |
| ); |
| } |
| |
| newPackageConfigJsonFile( |
| testPackageRootPath, |
| config.toContent( |
| toUriStr: toUriStr, |
| ), |
| ); |
| } |
| |
| /// Assert that the textual dump of the introspection information for |
| /// the first declaration in [declarationCode] is the same as [expected]. |
| Future<void> _assertTypesPhaseIntrospectionText( |
| String declarationCode, String expected) async { |
| var actual = await _getDeclarationText(declarationCode); |
| if (actual != expected) { |
| print(actual); |
| } |
| expect(actual, expected); |
| } |
| |
| /// The [declarationCode] is expected to start with a declaration. It may |
| /// include other declaration below, for example to reference them in |
| /// the first declaration. |
| /// |
| /// Use `DeclarationTextMacro` to generate a library that produces exactly |
| /// one part, with exactly one top-level constant `x`, with a string |
| /// literal initializer. We expect that the value of this literal is |
| /// the textual dump of the introspection information for the first |
| /// declaration. |
| Future<String> _getDeclarationText(String declarationCode) async { |
| newFile2( |
| '$testPackageLibPath/declaration_text.dart', |
| _declarationTextCode, |
| ); |
| |
| var library = await buildLibrary(''' |
| import 'declaration_text.dart'; |
| |
| @DeclarationTextMacro() |
| $declarationCode |
| ''', preBuildSequence: [ |
| {'package:test/declaration_text.dart'} |
| ]); |
| |
| var x = library.parts.single.topLevelVariables.single; |
| expect(x.name, 'x'); |
| x as ConstTopLevelVariableElementImpl; |
| var x_literal = x.constantInitializer as SimpleStringLiteral; |
| return x_literal.value; |
| } |
| } |