Add ElementAnnotation.isValidAtElement()
Bug: https://github.com/dart-lang/sdk/issues/63503 Change-Id: Iadc096c244607edcb773d70338f1b5b769c25128 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/509560 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Reviewed-by: Paul Berry <paulberry@google.com>
This commit is contained in:
@@ -10,7 +10,7 @@ import 'package:analyzer/dart/ast/token.dart';
|
||||
import 'package:analyzer/diagnostic/diagnostic.dart';
|
||||
import 'package:analyzer/source/line_info.dart';
|
||||
import 'package:analyzer/src/dart/ast/ast.dart';
|
||||
import 'package:analyzer/src/dart/element/extensions.dart';
|
||||
import 'package:analyzer/src/dart/element/annotation_target.dart';
|
||||
import 'package:analyzer/src/diagnostic/diagnostic.dart' as diag;
|
||||
import 'package:analyzer/src/ignore_comments/ignore_info.dart';
|
||||
import 'package:analyzer_plugin/protocol/protocol_common.dart'
|
||||
@@ -358,7 +358,7 @@ class ImportOrganizer {
|
||||
IgnoreInfo.isIgnoreComment(token.lexeme);
|
||||
|
||||
static bool _isLibraryTargetAnnotation(Annotation annotation) =>
|
||||
annotation.elementAnnotation?.targetKinds.contains(TargetKind.library) ??
|
||||
annotation.elementAnnotation?.targetKinds?.contains(TargetKind.library) ??
|
||||
false;
|
||||
}
|
||||
|
||||
|
||||
@@ -3501,6 +3501,7 @@ package:analyzer/dart/element/element.dart:
|
||||
isWidgetFactory (getter: bool)
|
||||
libraryFragment (getter: LibraryFragment)
|
||||
computeConstantValue (method: DartObject? Function())
|
||||
isValidAtElement (method: bool? Function(Element))
|
||||
toSource (method: String Function())
|
||||
ElementDirective (class extends Object, abstract):
|
||||
new (constructor: ElementDirective Function())
|
||||
|
||||
@@ -766,6 +766,16 @@ abstract class ElementAnnotation {
|
||||
/// if the value of this annotation could not be computed because of errors.
|
||||
DartObject? computeConstantValue();
|
||||
|
||||
/// Whether this annotation is a valid annotation for the given [element].
|
||||
///
|
||||
/// Returns `true` if the annotation has known target kinds, and the
|
||||
/// [element] matches one of them. Returns `false` if the annotation has known
|
||||
/// target kinds, and the [element] does not match any of them.
|
||||
///
|
||||
/// Returns `null` if there is no known set of target kinds for this
|
||||
/// annotation.
|
||||
bool? isValidAtElement(Element element);
|
||||
|
||||
/// Returns a textual description of this annotation in a form approximating
|
||||
/// valid source.
|
||||
///
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
// Copyright (c) 2026, 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/element/element.dart';
|
||||
import 'package:analyzer/dart/element/type.dart';
|
||||
import 'package:meta/meta_meta.dart';
|
||||
|
||||
const Set<TargetKind> _overrideTargetKinds = {
|
||||
TargetKind.field,
|
||||
TargetKind.getter,
|
||||
TargetKind.method,
|
||||
TargetKind.setter,
|
||||
};
|
||||
|
||||
final Map<String, TargetKind> _targetKindsByName = {
|
||||
for (var kind in TargetKind.values) kind.name: kind,
|
||||
};
|
||||
|
||||
bool? isAnnotationValidAtElement(
|
||||
ElementAnnotation annotation,
|
||||
Element element,
|
||||
) {
|
||||
var kinds = annotation.targetKinds;
|
||||
if (kinds == null) {
|
||||
return null;
|
||||
}
|
||||
return isValidAnnotationTargetElement(element, kinds);
|
||||
}
|
||||
|
||||
bool isValidAnnotationTargetElement(Element element, Set<TargetKind> kinds) {
|
||||
if (kinds.contains(TargetKind.overridableMember) &&
|
||||
_isOverridableMember(element)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return switch (element) {
|
||||
ClassElement() =>
|
||||
kinds.contains(TargetKind.classType) || kinds.contains(TargetKind.type),
|
||||
ConstructorElement() => kinds.contains(TargetKind.constructor),
|
||||
EnumElement() =>
|
||||
kinds.contains(TargetKind.enumType) || kinds.contains(TargetKind.type),
|
||||
ExtensionElement() => kinds.contains(TargetKind.extension),
|
||||
ExtensionTypeElement() => kinds.contains(TargetKind.extensionType),
|
||||
FieldElement(:var isEnumConstant) =>
|
||||
isEnumConstant
|
||||
? kinds.contains(TargetKind.enumValue)
|
||||
: kinds.contains(TargetKind.field),
|
||||
FormalParameterElement(:var isOptional) =>
|
||||
kinds.contains(TargetKind.parameter) ||
|
||||
isOptional && kinds.contains(TargetKind.optionalParameter),
|
||||
GetterElement() => kinds.contains(TargetKind.getter),
|
||||
LibraryElement() => kinds.contains(TargetKind.library),
|
||||
LocalFunctionElement() ||
|
||||
TopLevelFunctionElement() => kinds.contains(TargetKind.function),
|
||||
MethodElement() => kinds.contains(TargetKind.method),
|
||||
MixinElement() =>
|
||||
kinds.contains(TargetKind.mixinType) || kinds.contains(TargetKind.type),
|
||||
SetterElement() => kinds.contains(TargetKind.setter),
|
||||
TopLevelVariableElement() => kinds.contains(TargetKind.topLevelVariable),
|
||||
TypeAliasElement() =>
|
||||
kinds.contains(TargetKind.typedefType) || kinds.contains(TargetKind.type),
|
||||
TypeParameterElement() => kinds.contains(TargetKind.typeParameter),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
bool _isOverridableMember(Element element) {
|
||||
if (element case FieldElement(:var isStatic, :var enclosingElement)) {
|
||||
return !isStatic && _isOverridableMemberContainer(enclosingElement);
|
||||
}
|
||||
|
||||
if (element case GetterElement(:var isStatic, :var enclosingElement)) {
|
||||
return !isStatic && _isOverridableMemberContainer(enclosingElement);
|
||||
}
|
||||
|
||||
if (element case MethodElement(:var isStatic, :var enclosingElement)) {
|
||||
return !isStatic && _isOverridableMemberContainer(enclosingElement);
|
||||
}
|
||||
|
||||
if (element case SetterElement(:var isStatic, :var enclosingElement)) {
|
||||
return !isStatic && _isOverridableMemberContainer(enclosingElement);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _isOverridableMemberContainer(Element? element) {
|
||||
return element is ClassElement ||
|
||||
element is ExtensionTypeElement ||
|
||||
element is MixinElement;
|
||||
}
|
||||
|
||||
extension ElementAnnotationTargetKinds on ElementAnnotation {
|
||||
/// Return the known target kinds for this annotation.
|
||||
///
|
||||
/// Returns `null` if there is no known set of target kinds.
|
||||
Set<TargetKind>? get targetKinds {
|
||||
if (isOverride) {
|
||||
return _overrideTargetKinds;
|
||||
}
|
||||
|
||||
var element = this.element;
|
||||
InterfaceElement? interfaceElement;
|
||||
if (element is GetterElement) {
|
||||
var type = element.returnType;
|
||||
if (type is InterfaceType) {
|
||||
interfaceElement = type.element;
|
||||
}
|
||||
} else if (element is ConstructorElement) {
|
||||
interfaceElement = element.enclosingElement;
|
||||
}
|
||||
|
||||
if (interfaceElement == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (var annotation in interfaceElement.metadata.annotations) {
|
||||
if (annotation.isTarget) {
|
||||
var value = annotation.computeConstantValue();
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var annotationKinds = value.getField('kinds')?.toSetValue();
|
||||
if (annotationKinds == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return annotationKinds
|
||||
.map((e) {
|
||||
// Support class-based and enum-based target kind implementations.
|
||||
var field = e.getField('name') ?? e.getField('_name');
|
||||
return field?.toStringValue();
|
||||
})
|
||||
.map((name) => _targetKindsByName[name])
|
||||
.nonNulls
|
||||
.toSet();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ import 'package:analyzer/src/dart/ast/token.dart';
|
||||
import 'package:analyzer/src/dart/constant/compute.dart';
|
||||
import 'package:analyzer/src/dart/constant/evaluation.dart';
|
||||
import 'package:analyzer/src/dart/constant/value.dart';
|
||||
import 'package:analyzer/src/dart/element/annotation_target.dart';
|
||||
import 'package:analyzer/src/dart/element/class_hierarchy.dart';
|
||||
import 'package:analyzer/src/dart/element/display_string_builder.dart';
|
||||
import 'package:analyzer/src/dart/element/field_name_non_promotability_info.dart';
|
||||
@@ -1954,6 +1955,11 @@ class ElementAnnotationImpl
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
bool? isValidAtElement(Element element) {
|
||||
return isAnnotationValidAtElement(this, element);
|
||||
}
|
||||
|
||||
@override
|
||||
String toSource() => annotationAst.toSource();
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import 'package:analyzer/src/dart/element/type.dart';
|
||||
import 'package:analyzer/src/diagnostic/diagnostic.dart'
|
||||
show DiagnosticMessageImpl;
|
||||
import 'package:analyzer/src/generated/utilities_dart.dart';
|
||||
import 'package:meta/meta_meta.dart';
|
||||
|
||||
extension DartTypeExtension on DartType {
|
||||
bool get isExtensionType {
|
||||
@@ -115,54 +114,6 @@ extension Element2Extension on Element {
|
||||
}
|
||||
}
|
||||
|
||||
extension ElementAnnotationExtension on ElementAnnotation {
|
||||
static final Map<String, TargetKind> _targetKindsByName = {
|
||||
for (var kind in TargetKind.values) kind.name: kind,
|
||||
};
|
||||
|
||||
/// Return the target kinds defined for this [ElementAnnotation].
|
||||
Set<TargetKind> get targetKinds {
|
||||
var element = this.element;
|
||||
InterfaceElement? interfaceElement;
|
||||
|
||||
if (element is GetterElement) {
|
||||
var type = element.returnType;
|
||||
if (type is InterfaceType) {
|
||||
interfaceElement = type.element;
|
||||
}
|
||||
} else if (element is ConstructorElement) {
|
||||
interfaceElement = element.enclosingElement;
|
||||
}
|
||||
if (interfaceElement == null) {
|
||||
return const <TargetKind>{};
|
||||
}
|
||||
for (var annotation in interfaceElement.metadata.annotations) {
|
||||
if (annotation.isTarget) {
|
||||
var value = annotation.computeConstantValue();
|
||||
if (value == null) {
|
||||
return const <TargetKind>{};
|
||||
}
|
||||
|
||||
var annotationKinds = value.getField('kinds')?.toSetValue();
|
||||
if (annotationKinds == null) {
|
||||
return const <TargetKind>{};
|
||||
}
|
||||
|
||||
return annotationKinds
|
||||
.map((e) {
|
||||
// Support class-based and enum-based target kind implementations.
|
||||
var field = e.getField('name') ?? e.getField('_name');
|
||||
return field?.toStringValue();
|
||||
})
|
||||
.map((name) => _targetKindsByName[name])
|
||||
.nonNulls
|
||||
.toSet();
|
||||
}
|
||||
}
|
||||
return const <TargetKind>{};
|
||||
}
|
||||
}
|
||||
|
||||
extension ExecutableElement2Extension on ExecutableElement {
|
||||
/// Whether the enclosing element is the class `Object`.
|
||||
bool get isObjectMember {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
import 'package:analyzer/dart/element/type.dart';
|
||||
import 'package:analyzer/src/dart/ast/ast.dart';
|
||||
import 'package:analyzer/src/dart/element/annotation_target.dart';
|
||||
import 'package:analyzer/src/dart/element/extensions.dart';
|
||||
import 'package:analyzer/src/diagnostic/diagnostic.dart' as diag;
|
||||
import 'package:analyzer/src/error/listener.dart';
|
||||
@@ -373,18 +374,8 @@ class AnnotationVerifier {
|
||||
}
|
||||
|
||||
void _checkKinds(Annotation node, AstNode parent, ElementAnnotation element) {
|
||||
// As `@override` is declared in the Dart SDK, `TargetKind` is unavailable
|
||||
// to it.
|
||||
var kinds = element.isOverride
|
||||
? {
|
||||
TargetKind.field,
|
||||
TargetKind.getter,
|
||||
TargetKind.method,
|
||||
TargetKind.setter,
|
||||
}
|
||||
: element.targetKinds;
|
||||
|
||||
if (kinds.isNotEmpty) {
|
||||
var kinds = element.targetKinds;
|
||||
if (kinds != null && kinds.isNotEmpty) {
|
||||
if (!_isValidTarget(parent, kinds)) {
|
||||
var invokedElement = element.element!;
|
||||
var name = invokedElement.name;
|
||||
@@ -720,36 +711,6 @@ class AnnotationVerifier {
|
||||
/// when the annotation is marked as being valid for the given [kinds] of
|
||||
/// targets.
|
||||
bool _isValidTarget(AstNode target, Set<TargetKind> kinds) {
|
||||
// `TargetKind.overridableMember` is complex, so we handle it separately.
|
||||
if (kinds.contains(TargetKind.overridableMember)) {
|
||||
if ((target is FieldDeclaration && !target.isStatic) ||
|
||||
target is MethodDeclaration && !target.isStatic) {
|
||||
var parent = target.parent;
|
||||
var parent2 = parent?.parent;
|
||||
if (parent is BlockClassBody &&
|
||||
(parent2 is ClassDeclaration ||
|
||||
parent2 is ExtensionTypeDeclaration ||
|
||||
parent2 is MixinDeclaration)) {
|
||||
// Members of `EnumDeclaration`s and `ExtensionDeclaration`s are not
|
||||
// overridable.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (target is FormalParameter) {
|
||||
var element = target.declaredFragment?.element;
|
||||
if (element is FieldFormalParameterElement && element.isDeclaring) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (target is FormalParameter) {
|
||||
var element = target.declaredFragment?.element;
|
||||
if (element is FieldFormalParameterElement && element.isDeclaring) {
|
||||
if (kinds.contains(TargetKind.field)) return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the case of the deprecated `TargetKind.directive` before handling
|
||||
// the Directive subclasses below.
|
||||
// ignore: deprecated_member_use
|
||||
@@ -767,43 +728,52 @@ class AnnotationVerifier {
|
||||
}
|
||||
}
|
||||
|
||||
for (var element in _targetElements(target)) {
|
||||
if (isValidAnnotationTargetElement(element, kinds)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return switch (target) {
|
||||
ClassDeclaration() =>
|
||||
kinds.contains(TargetKind.classType) || kinds.contains(TargetKind.type),
|
||||
ClassTypeAlias() =>
|
||||
kinds.contains(TargetKind.classType) || kinds.contains(TargetKind.type),
|
||||
ConstructorDeclaration() => kinds.contains(TargetKind.constructor),
|
||||
EnumConstantDeclaration() => kinds.contains(TargetKind.enumValue),
|
||||
EnumDeclaration() =>
|
||||
kinds.contains(TargetKind.enumType) || kinds.contains(TargetKind.type),
|
||||
ExportDirective() => kinds.contains(TargetKind.exportDirective),
|
||||
ExtensionTypeDeclaration() => kinds.contains(TargetKind.extensionType),
|
||||
ExtensionDeclaration() => kinds.contains(TargetKind.extension),
|
||||
FieldDeclaration() => kinds.contains(TargetKind.field),
|
||||
FunctionDeclaration(isGetter: true) => kinds.contains(TargetKind.getter),
|
||||
FunctionDeclaration(isSetter: true) => kinds.contains(TargetKind.setter),
|
||||
FunctionDeclaration() => kinds.contains(TargetKind.function),
|
||||
ImportDirective() => kinds.contains(TargetKind.importDirective),
|
||||
MethodDeclaration(isGetter: true) => kinds.contains(TargetKind.getter),
|
||||
MethodDeclaration(isSetter: true) => kinds.contains(TargetKind.setter),
|
||||
MethodDeclaration() => kinds.contains(TargetKind.method),
|
||||
MixinDeclaration() =>
|
||||
kinds.contains(TargetKind.mixinType) || kinds.contains(TargetKind.type),
|
||||
PartOfDirective() => kinds.contains(TargetKind.partOfDirective),
|
||||
PrimaryConstructorBody() => kinds.contains(TargetKind.constructor),
|
||||
FormalParameter() =>
|
||||
kinds.contains(TargetKind.parameter) ||
|
||||
(target.isOptional && kinds.contains(TargetKind.optionalParameter)),
|
||||
FunctionTypeAlias() || GenericTypeAlias() =>
|
||||
kinds.contains(TargetKind.typedefType) ||
|
||||
kinds.contains(TargetKind.type),
|
||||
TopLevelVariableDeclaration() => kinds.contains(
|
||||
TargetKind.topLevelVariable,
|
||||
),
|
||||
TypeParameter() => kinds.contains(TargetKind.typeParameter),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// Maps the syntax where an annotation is written to the element targets it
|
||||
/// can be understood to annotate.
|
||||
///
|
||||
/// Usually this is a single declared element, but some syntax, such as a
|
||||
/// field declaration with multiple variables or a declaring formal parameter,
|
||||
/// can correspond to multiple element targets.
|
||||
List<Element> _targetElements(AstNode target) {
|
||||
switch (target) {
|
||||
case FieldDeclaration(:var fields):
|
||||
return [
|
||||
for (var variable in fields.variables)
|
||||
?variable.declaredFragment?.element,
|
||||
];
|
||||
case FormalParameter():
|
||||
var element = target.declaredFragment?.element;
|
||||
if (element is FieldFormalParameterElement && element.isDeclaring) {
|
||||
return [element, ?element.field];
|
||||
}
|
||||
return [?element];
|
||||
case PrimaryConstructorBody(:var declaration):
|
||||
return [?declaration?.declaredFragment?.element];
|
||||
case TopLevelVariableDeclaration(:var variables):
|
||||
return [
|
||||
for (var variable in variables.variables)
|
||||
?variable.declaredFragment?.element,
|
||||
];
|
||||
case Declaration(:var declaredFragment):
|
||||
return [?declaredFragment?.element];
|
||||
default:
|
||||
return const [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension on ClassElement {
|
||||
|
||||
@@ -27,6 +27,12 @@ main() {
|
||||
|
||||
@reflectiveTest
|
||||
class ElementAnnotationImplTest extends PubPackageResolutionTest {
|
||||
@override
|
||||
void setUp() {
|
||||
super.setUp();
|
||||
writeTestPackageConfigWithMeta();
|
||||
}
|
||||
|
||||
test_computeConstantValue() async {
|
||||
newFile('$testPackageLibPath/a.dart', r'''
|
||||
class A {
|
||||
@@ -49,6 +55,88 @@ main() {
|
||||
DartObject value = annotation.computeConstantValue()!;
|
||||
expect(value.getField('f')!.toStringValue(), 'x');
|
||||
}
|
||||
|
||||
test_isValidAtElement_declaringFormalParameter() async {
|
||||
var result = await resolveTestCodeWithDiagnostics(r'''
|
||||
import 'package:meta/meta_meta.dart';
|
||||
|
||||
@Target({TargetKind.field})
|
||||
class FieldOnly {
|
||||
const FieldOnly();
|
||||
}
|
||||
|
||||
@Target({TargetKind.parameter})
|
||||
class ParameterOnly {
|
||||
const ParameterOnly();
|
||||
}
|
||||
|
||||
class A {
|
||||
@FieldOnly()
|
||||
final int explicit = 0;
|
||||
}
|
||||
|
||||
class B(@FieldOnly() @ParameterOnly() final int f);
|
||||
''');
|
||||
|
||||
var explicitField = result.findElement.field('explicit', of: 'A');
|
||||
var declaringField = result.findElement.field('f', of: 'B');
|
||||
var declaringParameter = result.findElement
|
||||
.class_('B')
|
||||
.constructors
|
||||
.single
|
||||
.formalParameters
|
||||
.single;
|
||||
|
||||
var explicitFieldOnly = explicitField.metadata.annotations.single;
|
||||
expect(explicitFieldOnly.isValidAtElement(explicitField), isTrue);
|
||||
expect(explicitFieldOnly.isValidAtElement(declaringParameter), isFalse);
|
||||
|
||||
var declaringFieldOnly = declaringParameter.metadata.annotations[0];
|
||||
var declaringParameterOnly = declaringParameter.metadata.annotations[1];
|
||||
expect(declaringFieldOnly.isValidAtElement(declaringParameter), isFalse);
|
||||
expect(declaringFieldOnly.isValidAtElement(declaringField), isTrue);
|
||||
expect(declaringParameterOnly.isValidAtElement(declaringParameter), isTrue);
|
||||
expect(declaringParameterOnly.isValidAtElement(declaringField), isFalse);
|
||||
}
|
||||
|
||||
test_isValidAtElement_noTarget() async {
|
||||
var result = await resolveTestCodeWithDiagnostics(r'''
|
||||
class A {
|
||||
const A();
|
||||
}
|
||||
|
||||
@A()
|
||||
class B {}
|
||||
''');
|
||||
var class_ = result.findElement.class_('B');
|
||||
var annotation = class_.metadata.annotations.single;
|
||||
|
||||
expect(annotation.isValidAtElement(class_), isNull);
|
||||
}
|
||||
|
||||
test_isValidAtElement_override() async {
|
||||
var result = await resolveTestCodeWithDiagnostics(r'''
|
||||
class A {
|
||||
void m() {}
|
||||
}
|
||||
|
||||
class B extends A {
|
||||
@override
|
||||
void m() {}
|
||||
|
||||
void n(int p) {}
|
||||
}
|
||||
''');
|
||||
var method = result.findElement.method('m', of: 'B');
|
||||
var parameter = result.findElement
|
||||
.method('n', of: 'B')
|
||||
.formalParameters
|
||||
.single;
|
||||
var annotation = method.metadata.annotations.single;
|
||||
|
||||
expect(annotation.isValidAtElement(method), isTrue);
|
||||
expect(annotation.isValidAtElement(parameter), isFalse);
|
||||
}
|
||||
}
|
||||
|
||||
@reflectiveTest
|
||||
|
||||
@@ -10,7 +10,7 @@ import 'package:analyzer/dart/ast/visitor.dart';
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
import 'package:analyzer/error/error.dart';
|
||||
// ignore: implementation_imports
|
||||
import 'package:analyzer/src/dart/element/extensions.dart';
|
||||
import 'package:analyzer/src/dart/element/annotation_target.dart';
|
||||
import 'package:meta/meta_meta.dart';
|
||||
|
||||
import '../analyzer.dart';
|
||||
@@ -64,8 +64,10 @@ class _Visitor extends SimpleAstVisitor<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
if (elementAnnotation.targetKinds.length == 1 &&
|
||||
elementAnnotation.targetKinds.contains(TargetKind.library) &&
|
||||
var targetKinds = elementAnnotation.targetKinds;
|
||||
if (targetKinds != null &&
|
||||
targetKinds.length == 1 &&
|
||||
targetKinds.contains(TargetKind.library) &&
|
||||
firstDirective == node) {
|
||||
rule.reportAtNode(annotation);
|
||||
} else if (elementAnnotation.isPragmaLateTrust) {
|
||||
|
||||
Reference in New Issue
Block a user