From d5fc2732847e2f17d4a850c6af27b15e4a9378a1 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Tue, 4 Jul 2017 20:12:27 -0700 Subject: [PATCH 1/3] Add annotationsOfExact. --- CHANGELOG.md | 7 +++++++ lib/src/type_checker.dart | 26 +++++++++++++++++++------- pubspec.yaml | 2 +- test/type_checker_test.dart | 2 ++ 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 127e42f2..bd7803aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.6.0 + +* **Breaking change**: `TypeChecker#annotationsOf|firstAnnotationOf` now + returns annotations that are _assignable_ to the `TypeChecker`'s type. As a + result we've added `#annotationsOfExact|firstAnnotationOfExact` which has the + old behavior for precise checks. + ## 0.5.10+1 * Update minimum `analyzer` package to `0.29.10`. diff --git a/lib/src/type_checker.dart b/lib/src/type_checker.dart index 3c24864c..5e91470e 100644 --- a/lib/src/type_checker.dart +++ b/lib/src/type_checker.dart @@ -38,7 +38,7 @@ abstract class TypeChecker { /// package like in the `dart:` SDK. const factory TypeChecker.fromUrl(dynamic url) = _UriTypeChecker; - /// Returns the first constant annotating [element] that is this type. + /// Returns the first constant annotating [element] assignable to this type. /// /// Otherwise returns `null`. DartObject firstAnnotationOf(Element element) { @@ -49,18 +49,30 @@ abstract class TypeChecker { return results.isEmpty ? null : results.first; } - /// Returns every constant annotating [element] that is this type. + /// Returns the first constant annotating [element] that is exactly this type. + DartObject firstAnnotationOfExact(Element element) { + if (element.metadata.isEmpty) { + return null; + } + final results = annotationsOfExact(element); + return results.isEmpty ? null : results.first; + } + + /// Returns annotating constants on [element] assignable to this type. Iterable annotationsOf(Element element) => element.metadata + .map((a) => a.computeConstantValue()) + .where((a) => isAssignableFromType(a.type)); + + /// Returns annotating constants on [element] of exactly this type. + Iterable annotationsOfExact(Element element) => element.metadata .map((a) => a.computeConstantValue()) .where((a) => isExactlyType(a.type)); - /// Returns `true` if the type of [element] can be assigned to the type - /// represented by `this`. + /// Returns `true` if the type of [element] can be assigned to this type. bool isAssignableFrom(Element element) => isExactly(element) || _getAllSupertypes(element).any(isExactlyType); - /// Returns `true` if [staticType] can be assigned to the type represented - /// by `this`. + /// Returns `true` if [staticType] can be assigned to this type. bool isAssignableFromType(DartType staticType) => isAssignableFrom(staticType.element); @@ -97,7 +109,7 @@ abstract class TypeChecker { bool isSuperTypeOf(DartType staticType) => isSuperOf(staticType.element); } -//TODO(kevmoo) Remove when bug with `ClassElement.allSupertypes` is fixed +// TODO(kevmoo) Remove when bug with `ClassElement.allSupertypes` is fixed // https://github.com/dart-lang/sdk/issues/29767 Iterable _getAllSupertypes(Element element) sync* { if (element is ClassElement) { diff --git a/pubspec.yaml b/pubspec.yaml index 725a6e84..4e73c121 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: source_gen -version: 0.5.10+1 +version: 0.6.0-dev author: Dart Team description: Automated source code generation for Dart. homepage: https://github.com/dart-lang/source_gen diff --git a/test/type_checker_test.dart b/test/type_checker_test.dart index b13cd6c6..d5e6d8bc 100644 --- a/test/type_checker_test.dart +++ b/test/type_checker_test.dart @@ -2,6 +2,8 @@ // 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. +// Increase timeouts on this test which resolves source code and can be slow. +@Timeout.factor(2.0) import 'dart:collection'; import 'package:analyzer/dart/element/type.dart'; From 108c31af48253a800bd3b70176c277504742b08c Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Tue, 4 Jul 2017 20:25:37 -0700 Subject: [PATCH 2/3] Add type checks. --- lib/src/type_checker.dart | 18 ++++++++++++++---- test/type_checker_test.dart | 15 +++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/lib/src/type_checker.dart b/lib/src/type_checker.dart index 5e91470e..6c091b40 100644 --- a/lib/src/type_checker.dart +++ b/lib/src/type_checker.dart @@ -58,15 +58,25 @@ abstract class TypeChecker { return results.isEmpty ? null : results.first; } + DartObject _checkedConstantValue(ElementAnnotation annotation) { + final result = annotation.computeConstantValue(); + if (result == null) { + throw new StateError( + 'Could not resolve $annotation. An import or dependency may be ' + 'missing or invalid.'); + } + return result; + } + /// Returns annotating constants on [element] assignable to this type. Iterable annotationsOf(Element element) => element.metadata - .map((a) => a.computeConstantValue()) - .where((a) => isAssignableFromType(a.type)); + .map(_checkedConstantValue) + .where((a) => a?.type != null && isAssignableFromType(a.type)); /// Returns annotating constants on [element] of exactly this type. Iterable annotationsOfExact(Element element) => element.metadata - .map((a) => a.computeConstantValue()) - .where((a) => isExactlyType(a.type)); + .map(_checkedConstantValue) + .where((a) => a?.type != null && isExactlyType(a.type)); /// Returns `true` if the type of [element] can be assigned to this type. bool isAssignableFrom(Element element) => diff --git a/test/type_checker_test.dart b/test/type_checker_test.dart index d5e6d8bc..ae127b6c 100644 --- a/test/type_checker_test.dart +++ b/test/type_checker_test.dart @@ -179,4 +179,19 @@ void main() { checkGeneratorForAnnotation: () => const TypeChecker.fromUrl( 'package:source_gen/src/generator_for_annotation.dart#GeneratorForAnnotation')); }); + + test('should gracefully when something is not resolvable', () async { + final resolver = await resolveSource(r''' + library _test; + + @depracated // Intentionally mispelled. + class X {} + '''); + final lib = resolver.getLibraryByName('_test'); + final classX = lib.getType('X'); + final $deprecated = const TypeChecker.fromRuntime(Deprecated); + + expect(() => $deprecated.annotationsOf(classX), throwsStateError, + reason: 'deprecated was spelled wrong; no annotation can be resolved'); + }); } From 779215b3ed532e2686ee698b69d5aa0682ec6a36 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Tue, 4 Jul 2017 20:27:32 -0700 Subject: [PATCH 3/3] Update CHANGELOG. --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd7803aa..2a22470f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ result we've added `#annotationsOfExact|firstAnnotationOfExact` which has the old behavior for precise checks. +* `TypeChecker#annotations...`-methods now throw a `StateError` if one or more + annotations on an element are not resolvable. This is usually a sign of a + mispelling, missing import, or missing dependency. + ## 0.5.10+1 * Update minimum `analyzer` package to `0.29.10`.