blob: 32b7598e3675d3c85005203c9a51413194706dc2 [file] [log] [blame]
// Copyright (c) 2019, 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:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/test_utilities/find_element.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../../summary/resolved_ast_printer.dart';
import 'context_collection_resolution.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(MetadataResolutionTest);
defineReflectiveTests(MetadataResolutionWithNullSafetyTest);
});
}
@reflectiveTest
class MetadataResolutionTest extends PubPackageResolutionTest
with WithoutNullSafetyMixin {
test_genericClass_instanceGetter() async {
await resolveTestCode(r'''
class A<T> {
T get foo {}
}
@A.foo
void f() {}
''');
_assertResolvedNodeText(findNode.annotation('@A'), r'''
Annotation
element: self::@class::A::@getter::foo
name: PrefixedIdentifier
identifier: SimpleIdentifier
staticElement: self::@class::A::@getter::foo
staticType: null
token: foo
period: .
prefix: SimpleIdentifier
staticElement: self::@class::A
staticType: null
token: A
staticElement: self::@class::A::@getter::foo
staticType: null
''');
}
test_genericClass_namedConstructor() async {
await assertNoErrorsInCode(r'''
class A<T> {
const A.named();
}
@A.named()
void f() {}
''');
_assertResolvedNodeText(findNode.annotation('@A'), r'''
Annotation
arguments: ArgumentList
element: ConstructorMember
base: self::@class::A::@constructor::named
substitution: {T: dynamic}
name: PrefixedIdentifier
identifier: SimpleIdentifier
staticElement: ConstructorMember
base: self::@class::A::@constructor::named
substitution: {T: dynamic}
staticType: null
token: named
period: .
prefix: SimpleIdentifier
staticElement: self::@class::A
staticType: null
token: A
staticElement: ConstructorMember
base: self::@class::A::@constructor::named
substitution: {T: dynamic}
staticType: null
''');
}
test_genericClass_staticGetter() async {
await resolveTestCode(r'''
class A<T> {
static T get foo {}
}
@A.foo
void f() {}
''');
_assertResolvedNodeText(findNode.annotation('@A'), r'''
Annotation
element: self::@class::A::@getter::foo
name: PrefixedIdentifier
identifier: SimpleIdentifier
staticElement: self::@class::A::@getter::foo
staticType: null
token: foo
period: .
prefix: SimpleIdentifier
staticElement: self::@class::A
staticType: null
token: A
staticElement: self::@class::A::@getter::foo
staticType: null
''');
}
test_genericClass_unnamedConstructor() async {
await assertNoErrorsInCode(r'''
class A<T> {
const A();
}
@A()
void f() {}
''');
_assertResolvedNodeText(findNode.annotation('@A'), r'''
Annotation
arguments: ArgumentList
element: ConstructorMember
base: self::@class::A::@constructor::•
substitution: {T: dynamic}
name: SimpleIdentifier
staticElement: self::@class::A
staticType: null
token: A
''');
}
test_onFieldFormal() async {
await assertNoErrorsInCode(r'''
class A {
const A(_);
}
class B {
final int f;
B({@A( A(0) ) this.f});
}
''');
_assertResolvedNodeText(findNode.annotation('@A'), r'''
Annotation
arguments: ArgumentList
arguments
InstanceCreationExpression
argumentList: ArgumentList
arguments
IntegerLiteral
literal: 0
staticType: int
constructorName: ConstructorName
staticElement: self::@class::A::@constructor::•
type: TypeName
name: SimpleIdentifier
staticElement: self::@class::A
staticType: null
token: A
type: A
staticType: A
element: self::@class::A::@constructor::•
name: SimpleIdentifier
staticElement: self::@class::A
staticType: null
token: A
''');
}
test_otherLibrary_constructor_named() async {
newFile('$testPackageLibPath/a.dart', content: r'''
class A {
final int f;
const A.named(this.f);
}
''');
newFile('$testPackageLibPath/b.dart', content: r'''
import 'a.dart';
@A.named(42)
class B {}
''');
await assertNoErrorsInCode(r'''
import 'b.dart';
B b;
''');
var classB = findNode.typeName('B b;').name.staticElement!;
var annotation = classB.metadata.single;
var value = annotation.computeConstantValue()!;
assertType(value.type, 'A');
expect(value.getField('f')!.toIntValue(), 42);
}
test_otherLibrary_constructor_unnamed() async {
newFile('$testPackageLibPath/a.dart', content: r'''
class A {
final int f;
const A(this.f);
}
''');
newFile('$testPackageLibPath/b.dart', content: r'''
import 'a.dart';
@A(42)
class B {}
''');
await assertNoErrorsInCode(r'''
import 'b.dart';
B b;
''');
var classB = findNode.typeName('B b;').name.staticElement!;
var annotation = classB.metadata.single;
var value = annotation.computeConstantValue()!;
assertType(value.type, 'A');
expect(value.getField('f')!.toIntValue(), 42);
}
test_otherLibrary_implicitConst() async {
newFile('$testPackageLibPath/a.dart', content: r'''
class A {
final int f;
const A(this.f);
}
class B {
final A a;
const B(this.a);
}
@B( A(42) )
class C {}
''');
await assertNoErrorsInCode(r'''
import 'a.dart';
C c;
''');
var classC = findNode.typeName('C c;').name.staticElement!;
var annotation = classC.metadata.single;
var value = annotation.computeConstantValue()!;
assertType(value.type, 'B');
expect(value.getField('a')!.getField('f')!.toIntValue(), 42);
}
@FailingTest(reason: 'Reverted because of dartbug.com/38565')
test_sameLibrary_genericClass_constructor_unnamed() async {
await assertNoErrorsInCode(r'''
class A<T> {
final T f;
const A(this.f);
}
@A(42)
class B {}
''');
var annotation = findElement.class_('B').metadata.single;
var value = annotation.computeConstantValue()!;
assertType(value.type, 'A<int>');
expect(value.getField('f')!.toIntValue(), 42);
}
void _assertResolvedNodeText(AstNode node, String expected) {
var actual = _resolvedNodeText(node);
expect(actual, expected);
}
String _resolvedNodeText(AstNode node) {
var buffer = StringBuffer();
node.accept(
ResolvedAstPrinter(
selfUriStr: result.uri.toString(),
sink: buffer,
indent: '',
),
);
return buffer.toString();
}
}
@reflectiveTest
class MetadataResolutionWithNullSafetyTest extends PubPackageResolutionTest
with WithNullSafetyMixin {
ImportFindElement get import_a {
return findElement.importFind('package:test/a.dart');
}
test_optIn_fromOptOut_class() async {
newFile('$testPackageLibPath/a.dart', content: r'''
class A {
const A(int a);
}
''');
await assertNoErrorsInCode(r'''
// @dart = 2.7
import 'a.dart';
@A(0)
void f() {}
''');
assertElement2(
findNode.simple('A('),
declaration: import_a.class_('A'),
);
assertElement2(
findNode.annotation('@A'),
declaration: import_a.unnamedConstructor('A'),
isLegacy: true,
);
}
test_optIn_fromOptOut_class_constructor() async {
newFile('$testPackageLibPath/a.dart', content: r'''
class A {
final int a;
const A.named(this.a);
}
''');
await assertNoErrorsInCode(r'''
// @dart = 2.7
import 'a.dart';
@A.named(42)
void f() {}
''');
assertElement2(
findNode.simple('A.named('),
declaration: import_a.class_('A'),
);
assertElement2(
findNode.annotation('@A'),
declaration: import_a.constructor('named', of: 'A'),
isLegacy: true,
);
_assertConstantValue(
findElement.function('f').metadata[0].computeConstantValue()!,
type: 'A*',
fieldMap: {'a': 42},
);
}
test_optIn_fromOptOut_class_constructor_withDefault() async {
newFile('$testPackageLibPath/a.dart', content: r'''
class A {
final int a;
const A.named({this.a = 42});
}
''');
await assertNoErrorsInCode(r'''
// @dart = 2.7
import 'a.dart';
@A.named()
void f() {}
''');
assertElement2(
findNode.simple('A.named('),
declaration: import_a.class_('A'),
);
assertElement2(
findNode.annotation('@A'),
declaration: import_a.constructor('named', of: 'A'),
isLegacy: true,
);
_assertConstantValue(
findElement.function('f').metadata[0].computeConstantValue()!,
type: 'A*',
fieldMap: {'a': 42},
);
}
test_optIn_fromOptOut_class_getter() async {
newFile('$testPackageLibPath/a.dart', content: r'''
class A {
static const foo = 42;
}
''');
await assertNoErrorsInCode(r'''
// @dart = 2.7
import 'a.dart';
@A.foo
void f() {}
''');
assertElement2(
findNode.simple('A.foo'),
declaration: import_a.class_('A'),
);
assertElement2(
findNode.annotation('@A.foo'),
declaration: import_a.getter('foo'),
isLegacy: true,
);
_assertIntValue(findElement.function('f').metadata[0], 42);
}
test_optIn_fromOptOut_getter() async {
newFile('$testPackageLibPath/a.dart', content: r'''
const foo = 42;
''');
await assertNoErrorsInCode(r'''
// @dart = 2.7
import 'a.dart';
@foo
void f() {}
''');
assertElement2(
findNode.annotation('@foo'),
declaration: import_a.topGet('foo'),
isLegacy: true,
);
_assertIntValue(findElement.function('f').metadata[0], 42);
}
test_optIn_fromOptOut_prefix_class() async {
newFile('$testPackageLibPath/a.dart', content: r'''
class A {
const A(int a);
}
''');
await assertNoErrorsInCode(r'''
// @dart = 2.7
import 'a.dart' as a;
@a.A(0)
void f() {}
''');
assertElement2(
findNode.simple('A('),
declaration: import_a.class_('A'),
);
assertElement2(
findNode.annotation('@a.A'),
declaration: import_a.unnamedConstructor('A'),
isLegacy: true,
);
}
test_optIn_fromOptOut_prefix_class_constructor() async {
newFile('$testPackageLibPath/a.dart', content: r'''
class A {
const A.named(int a);
}
''');
await assertNoErrorsInCode(r'''
// @dart = 2.7
import 'a.dart' as a;
@a.A.named(0)
void f() {}
''');
assertElement2(
findNode.simple('A.named('),
declaration: import_a.class_('A'),
);
assertElement2(
findNode.annotation('@a.A'),
declaration: import_a.constructor('named', of: 'A'),
isLegacy: true,
);
}
test_optIn_fromOptOut_prefix_class_getter() async {
newFile('$testPackageLibPath/a.dart', content: r'''
class A {
static const foo = 0;
}
''');
await assertNoErrorsInCode(r'''
// @dart = 2.7
import 'a.dart' as a;
@a.A.foo
void f() {}
''');
assertElement2(
findNode.simple('A.foo'),
declaration: import_a.class_('A'),
);
assertElement2(
findNode.annotation('@a.A'),
declaration: import_a.getter('foo'),
isLegacy: true,
);
}
test_optIn_fromOptOut_prefix_getter() async {
newFile('$testPackageLibPath/a.dart', content: r'''
const foo = 0;
''');
await assertNoErrorsInCode(r'''
// @dart = 2.7
import 'a.dart' as a;
@a.foo
void f() {}
''');
assertElement2(
findNode.annotation('@a.foo'),
declaration: import_a.topGet('foo'),
isLegacy: true,
);
}
void _assertConstantValue(
DartObject object, {
required String type,
Map<String, Object>? fieldMap,
int? intValue,
}) {
assertType(object.type, type);
if (fieldMap != null) {
for (var entry in fieldMap.entries) {
var actual = object.getField(entry.key);
var expected = entry.value;
if (expected is int) {
expect(actual!.toIntValue(), expected);
} else {
fail('Unsupported expected type: ${expected.runtimeType} $expected');
}
}
} else if (intValue != null) {
expect(object.toIntValue(), intValue);
} else {
fail('No expectations.');
}
}
void _assertIntValue(ElementAnnotation annotation, int intValue) {
_assertConstantValue(
annotation.computeConstantValue()!,
type: 'int',
intValue: intValue,
);
}
}