22// for details. All rights reserved. Use of this source code is governed by a
33// BSD-style license that can be found in the LICENSE file.
44
5- import 'dart:mirrors' ;
5+ import 'dart:mirrors' hide SourceLocation ;
66
77import 'package:analyzer/dart/constant/value.dart' ;
88import 'package:analyzer/dart/element/element.dart' ;
99import 'package:analyzer/dart/element/type.dart' ;
10+ // TODO(https://github.com/dart-lang/sdk/issues/32454):
11+ // ignore: implementation_imports
12+ import 'package:analyzer/src/dart/element/element.dart' ;
13+ import 'package:source_span/source_span.dart' ;
1014
1115import 'utils.dart' ;
1216
@@ -74,7 +78,8 @@ abstract class TypeChecker {
7478
7579 /// Returns the first constant annotating [element] that is exactly this type.
7680 ///
77- /// Throws on unresolved annotations unless [throwOnUnresolved] is `false` .
81+ /// Throws [UnresolvedAnnotationException] on unresolved annotations unless
82+ /// [throwOnUnresolved] is explicitly set to `false` (default is `true` ).
7883 DartObject firstAnnotationOfExact (Element element, {bool throwOnUnresolved}) {
7984 if (element.metadata.isEmpty) {
8085 return null ;
@@ -86,42 +91,70 @@ abstract class TypeChecker {
8691
8792 /// Returns if a constant annotating [element] is exactly this type.
8893 ///
89- /// Throws on unresolved annotations unless [throwOnUnresolved] is `false` .
94+ /// Throws [UnresolvedAnnotationException] on unresolved annotations unless
95+ /// [throwOnUnresolved] is explicitly set to `false` (default is `true` ).
9096 bool hasAnnotationOfExact (Element element, {bool throwOnUnresolved}) =>
9197 firstAnnotationOfExact (element, throwOnUnresolved: throwOnUnresolved) !=
9298 null ;
9399
94- DartObject _computeConstantValue (ElementAnnotation annotation,
95- {bool throwOnUnresolved}) {
100+ DartObject _computeConstantValue (
101+ Element element,
102+ int annotationIndex, {
103+ bool throwOnUnresolved,
104+ }) {
96105 throwOnUnresolved ?? = true ;
106+ final annotation = element.metadata[annotationIndex];
97107 final result = annotation.computeConstantValue ();
98108 if (result == null && throwOnUnresolved) {
99- throw new StateError (
100- 'Could not resolve $annotation . An import or dependency may be '
101- 'missing or invalid.' );
109+ throw new UnresolvedAnnotationException ._from (element, annotationIndex);
102110 }
103111 return result;
104112 }
105113
106114 /// Returns annotating constants on [element] assignable to this type.
107115 ///
108- /// Throws on unresolved annotations unless [throwOnUnresolved] is `false` .
109- Iterable <DartObject > annotationsOf (Element element,
110- {bool throwOnUnresolved}) =>
111- element.metadata
112- .map ((annotation) => _computeConstantValue (annotation,
113- throwOnUnresolved: throwOnUnresolved))
114- .where ((a) => a? .type != null && isAssignableFromType (a.type));
116+ /// Throws [UnresolvedAnnotationException] on unresolved annotations unless
117+ /// [throwOnUnresolved] is explicitly set to `false` (default is `true` ).
118+ Iterable <DartObject > annotationsOf (
119+ Element element, {
120+ bool throwOnUnresolved,
121+ }) =>
122+ _annotationsWhere (
123+ element,
124+ isAssignableFromType,
125+ throwOnUnresolved: throwOnUnresolved,
126+ );
127+
128+ Iterable <DartObject > _annotationsWhere (
129+ Element element,
130+ bool Function (DartType ) predicate, {
131+ bool throwOnUnresolved,
132+ }) sync * {
133+ for (var i = 0 ; i < element.metadata.length; i++ ) {
134+ final value = _computeConstantValue (
135+ element,
136+ i,
137+ throwOnUnresolved: throwOnUnresolved,
138+ );
139+ if (value? .type != null && predicate (value.type)) {
140+ yield value;
141+ }
142+ }
143+ }
115144
116145 /// Returns annotating constants on [element] of exactly this type.
117146 ///
118- /// Throws on unresolved annotations unless [throwOnUnresolved] is `false` .
119- Iterable <DartObject > annotationsOfExact (Element element,
120- {bool throwOnUnresolved}) =>
121- element.metadata
122- .map ((annotation) => _computeConstantValue (annotation,
123- throwOnUnresolved: throwOnUnresolved))
124- .where ((a) => a? .type != null && isExactlyType (a.type));
147+ /// Throws [UnresolvedAnnotationException] on unresolved annotations unless
148+ /// [throwOnUnresolved] is explicitly set to `false` (default is `true` ).
149+ Iterable <DartObject > annotationsOfExact (
150+ Element element, {
151+ bool throwOnUnresolved,
152+ }) =>
153+ _annotationsWhere (
154+ element,
155+ isExactlyType,
156+ throwOnUnresolved: throwOnUnresolved,
157+ );
125158
126159 /// Returns `true` if the type of [element] can be assigned to this type.
127160 bool isAssignableFrom (Element element) =>
@@ -242,3 +275,52 @@ class _AnyChecker extends TypeChecker {
242275 @override
243276 bool isExactly (Element element) => _checkers.any ((c) => c.isExactly (element));
244277}
278+
279+ /// Exception thrown when [TypeChecker] fails to resolve a metadata annotation.
280+ ///
281+ /// Methods such as [TypeChecker.firstAnnotationOf] may throw this exception
282+ /// when one or more annotations are not resolvable. This is usually a sign that
283+ /// something was misspelled, an import is missing, or a dependency was not
284+ /// defined (for build systems such as Bazel).
285+ class UnresolvedAnnotationException implements Exception {
286+ /// Element that was annotated with something we could not resolve.
287+ final Element annotatedElement;
288+
289+ /// Source span of the annotation that was not resolved.
290+ final SourceSpan annotationSource;
291+
292+ // TODO: Remove internal API once ElementAnnotation has source information.
293+ // https://github.com/dart-lang/sdk/issues/32454
294+ static SourceSpan _getSourceSpanFrom (ElementAnnotation annotation) {
295+ final internals = annotation as ElementAnnotationImpl ;
296+ final astNode = internals.annotationAst;
297+ final contents = annotation.source.contents.data;
298+ final start = astNode.offset;
299+ final end = start + astNode.length;
300+ return new SourceSpan (
301+ new SourceLocation (start, sourceUrl: annotation.source.uri),
302+ new SourceLocation (end, sourceUrl: annotation.source.uri),
303+ contents.substring (start, end),
304+ );
305+ }
306+
307+ /// Creates an exception from an annotation ([annotationIndex] ) that was not
308+ /// resolvable while traversing [Element.metadata] on [annotatedElement] .
309+ factory UnresolvedAnnotationException ._from (
310+ Element annotatedElement,
311+ int annotationIndex,
312+ ) {
313+ final annotation = annotatedElement.metadata[annotationIndex];
314+ final sourceSpan = _getSourceSpanFrom (annotation);
315+ return new UnresolvedAnnotationException ._(annotatedElement, sourceSpan);
316+ }
317+
318+ const UnresolvedAnnotationException ._(
319+ this .annotatedElement,
320+ this .annotationSource,
321+ );
322+
323+ @override
324+ String toString () => annotationSource
325+ .message ('Could not resolve annotation for $annotatedElement ' );
326+ }
0 commit comments