diff --git a/CHANGELOG.md b/CHANGELOG.md index 69427a34..2299714c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,16 @@ In Dart SDK `>=1.25.0` this can be relaxed as `part of` can refer to a path. +* Added `findType`, an utility method for `LibraryElement#getType` that also + traverses `export` directives for publicly exported types. For example, to + find `Generator` from `package:source_gen/source_gen.dart`: + + ```dart + void example(LibraryElement pkgSourceGen) { + findType(pkgSourceGen, 'Generator'); + } + ``` + ## 0.5.8 * Add `formatOutput` optional parameter to the `GeneratorBuilder` constructor. diff --git a/lib/source_gen.dart b/lib/source_gen.dart index be4d7c5b..966bef4a 100644 --- a/lib/source_gen.dart +++ b/lib/source_gen.dart @@ -5,6 +5,7 @@ library source_gen; export 'src/builder.dart'; +export 'src/find_type.dart' show findType; export 'src/generator.dart'; export 'src/generator_for_annotation.dart'; export 'src/type_checker.dart' show TypeChecker; diff --git a/lib/src/find_type.dart b/lib/src/find_type.dart new file mode 100644 index 00000000..341a495b --- /dev/null +++ b/lib/src/find_type.dart @@ -0,0 +1,18 @@ +// Copyright (c) 2017, 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/src/dart/resolver/scope.dart'; + +final _namespaceCache = new Expando(); + +/// Returns a top-level [ClassElement] publicly visible in [library] by [name]. +/// +/// Unlike [LibraryElement.getType], this also correctly traverses identifiers +/// that are accessible via one or more `export` directives. +ClassElement findType(LibraryElement library, String name) => + library.getType(name) ?? + (_namespaceCache[library] ??= + new NamespaceBuilder().createExportNamespaceForLibrary(library)) + .get(name) as ClassElement; diff --git a/lib/src/type_checker.dart b/lib/src/type_checker.dart index 3af42b16..ac0556df 100644 --- a/lib/src/type_checker.dart +++ b/lib/src/type_checker.dart @@ -28,6 +28,12 @@ abstract class TypeChecker { /// 'dart:collection#LinkedHashMap', /// ); /// ``` + /// + /// **NOTE**: This is considered a more _brittle_ way of determining the type + /// because it relies on knowing the _absolute_ path (i.e. after resolved + /// `export` directives). You should ideally only use `fromUrl` when you know + /// the full path (likely you own/control the package) or it is in a stable + /// package like in the `dart:` SDK. const factory TypeChecker.fromUrl(dynamic url) = _UriTypeChecker; /// Returns the first constant annotating [element] that is this type. @@ -170,7 +176,7 @@ class _UriTypeChecker extends TypeChecker { int get hashCode => _url.hashCode; /// Url as a [Uri] object, lazily constructed. - Uri get uri => _cache[this] ??= Uri.parse(_url); + Uri get uri => _cache[this] ??= _normalizeUrl(Uri.parse(_url)); /// Returns whether this type represents the same as [url]. bool hasSameUrl(dynamic url) => diff --git a/test/find_type_test.dart b/test/find_type_test.dart new file mode 100644 index 00000000..4f216b45 --- /dev/null +++ b/test/find_type_test.dart @@ -0,0 +1,43 @@ +// Copyright (c) 2017, 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:build_test/build_test.dart'; +import 'package:source_gen/source_gen.dart'; +import 'package:test/test.dart'; + +void main() { + LibraryElement library; + + setUpAll(() async { + final resolver = await resolveSource(r''' + library test_lib; + + export 'dart:collection' show LinkedHashMap; + export 'package:source_gen/source_gen.dart' show Generator; + import 'dart:async' show Stream; + + class Example {} + '''); + library = resolver.getLibraryByName('test_lib'); + }); + + final isClassElement = const isInstanceOf(); + + test('should return a type not exported', () { + expect(findType(library, 'Example'), isClassElement); + }); + + test('should return a type exported from dart:', () { + expect(findType(library, 'LinkedHashMap'), isClassElement); + }); + + test('should return a type exported from package:', () { + expect(findType(library, 'Generator'), isClassElement); + }); + + test('should not return a type imported', () { + expect(findType(library, 'Stream'), isNull); + }); +} diff --git a/test/type_checker_test.dart b/test/type_checker_test.dart index 5988ec4d..ea84384b 100644 --- a/test/type_checker_test.dart +++ b/test/type_checker_test.dart @@ -17,8 +17,16 @@ void main() { TypeChecker staticMapChecker; TypeChecker staticHashMapChecker; + // Resolved top-level types from package:source_gen. + DartType staticGenerator; + DartType staticGeneratorForAnnotation; + TypeChecker staticGeneratorChecker; + TypeChecker staticGeneratorForAnnotationChecker; + setUpAll(() async { - final resolver = await resolveSource(''); + final resolver = await resolveSource(r''' + export 'package:source_gen/source_gen.dart'; + '''); final core = resolver.getLibraryByName('dart.core'); staticMap = core.getType('Map').type; @@ -27,12 +35,22 @@ void main() { final collection = resolver.getLibraryByName('dart.collection'); staticHashMap = collection.getType('HashMap').type; staticHashMapChecker = new TypeChecker.fromStatic(staticHashMap); + + final sourceGen = resolver.getLibraryByName('source_gen'); + staticGenerator = findType(sourceGen, 'Generator').type; + staticGeneratorChecker = new TypeChecker.fromStatic(staticGenerator); + staticGeneratorForAnnotation = + findType(sourceGen, 'GeneratorForAnnotation').type; + staticGeneratorForAnnotationChecker = + new TypeChecker.fromStatic(staticGeneratorForAnnotation); }); // Run a common set of type comparison checks with various implementations. void commonTests({ @required TypeChecker checkMap(), @required TypeChecker checkHashMap(), + @required TypeChecker checkGenerator(), + @required TypeChecker checkGeneratorForAnnotation(), }) { group('(Map)', () { test('should equal dart:core#Map', () { @@ -72,24 +90,60 @@ void main() { expect(checkHashMap().isAssignableFromType(staticMap), isFalse); }); }); + + group('(Generator)', () { + test('should equal Generator', () { + expect(checkGenerator().isExactlyType(staticGenerator), isTrue, + reason: '${checkGenerator()} != $staticGenerator'); + }); + + test('should not be a super type of Generator', () { + expect(checkGenerator().isSuperTypeOf(staticGenerator), isFalse, + reason: '${checkGenerator()} is super of $staticGenerator'); + }); + + test('should be a super type of GeneratorForAnnotation', () { + expect(checkGenerator().isSuperTypeOf(staticGeneratorForAnnotation), + isTrue, + reason: + '${checkGenerator()} is not super of $staticGeneratorForAnnotation'); + }); + + test('should be assignable from GeneratorForAnnotation', () { + expect( + checkGenerator().isAssignableFromType(staticGeneratorForAnnotation), + isTrue, + reason: + '${checkGenerator()} is not assignable from $staticGeneratorForAnnotation'); + }); + }); } group('TypeChecker.forRuntime', () { commonTests( checkMap: () => const TypeChecker.fromRuntime(Map), - checkHashMap: () => const TypeChecker.fromRuntime(HashMap)); + checkHashMap: () => const TypeChecker.fromRuntime(HashMap), + checkGenerator: () => const TypeChecker.fromRuntime(Generator), + checkGeneratorForAnnotation: () => + const TypeChecker.fromRuntime(GeneratorForAnnotation)); }); group('TypeChecker.forStatic', () { commonTests( checkMap: () => staticMapChecker, - checkHashMap: () => staticHashMapChecker); + checkHashMap: () => staticHashMapChecker, + checkGenerator: () => staticGeneratorChecker, + checkGeneratorForAnnotation: () => staticGeneratorForAnnotationChecker); }); group('TypeChecker.fromUrl', () { commonTests( checkMap: () => const TypeChecker.fromUrl('dart:core#Map'), checkHashMap: () => - const TypeChecker.fromUrl('dart:collection#HashMap')); + const TypeChecker.fromUrl('dart:collection#HashMap'), + checkGenerator: () => const TypeChecker.fromUrl( + 'package:source_gen/src/generator.dart#Generator'), + checkGeneratorForAnnotation: () => const TypeChecker.fromUrl( + 'package:source_gen/src/generator_for_annotation.dart#GeneratorForAnnotation')); }); }