blob: 42e0a5935bfb9cc53f42d7d2664d4ead0d89fef0 [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:_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/summary2/kernel_compilation_service.dart';
import 'package:analyzer/src/summary2/macro.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 'macros_environment.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 = FrontEndServerMacroKernelBuilder();
macroExecutor = macro.MultiMacroExecutor();
}
Future<void> tearDown() async {
await macroExecutor?.close();
KernelCompilationService.disposeDelayed(
const Duration(milliseconds: 100),
);
}
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;
}
}