Skip to content

Commit c3ea180

Browse files
authored
Add findType, and fix a bug in TypeChecker. (#165)
1 parent eb8423c commit c3ea180

File tree

6 files changed

+137
-5
lines changed

6 files changed

+137
-5
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,16 @@
3434

3535
In Dart SDK `>=1.25.0` this can be relaxed as `part of` can refer to a path.
3636

37+
* Added `findType`, an utility method for `LibraryElement#getType` that also
38+
traverses `export` directives for publicly exported types. For example, to
39+
find `Generator` from `package:source_gen/source_gen.dart`:
40+
41+
```dart
42+
void example(LibraryElement pkgSourceGen) {
43+
findType(pkgSourceGen, 'Generator');
44+
}
45+
```
46+
3747
## 0.5.8
3848

3949
* Add `formatOutput` optional parameter to the `GeneratorBuilder` constructor.

lib/source_gen.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
library source_gen;
66

77
export 'src/builder.dart';
8+
export 'src/find_type.dart' show findType;
89
export 'src/generator.dart';
910
export 'src/generator_for_annotation.dart';
1011
export 'src/type_checker.dart' show TypeChecker;

lib/src/find_type.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:analyzer/dart/element/element.dart';
6+
import 'package:analyzer/src/dart/resolver/scope.dart';
7+
8+
final _namespaceCache = new Expando<Namespace>();
9+
10+
/// Returns a top-level [ClassElement] publicly visible in [library] by [name].
11+
///
12+
/// Unlike [LibraryElement.getType], this also correctly traverses identifiers
13+
/// that are accessible via one or more `export` directives.
14+
ClassElement findType(LibraryElement library, String name) =>
15+
library.getType(name) ??
16+
(_namespaceCache[library] ??=
17+
new NamespaceBuilder().createExportNamespaceForLibrary(library))
18+
.get(name) as ClassElement;

lib/src/type_checker.dart

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ abstract class TypeChecker {
2828
/// 'dart:collection#LinkedHashMap',
2929
/// );
3030
/// ```
31+
///
32+
/// **NOTE**: This is considered a more _brittle_ way of determining the type
33+
/// because it relies on knowing the _absolute_ path (i.e. after resolved
34+
/// `export` directives). You should ideally only use `fromUrl` when you know
35+
/// the full path (likely you own/control the package) or it is in a stable
36+
/// package like in the `dart:` SDK.
3137
const factory TypeChecker.fromUrl(dynamic url) = _UriTypeChecker;
3238

3339
/// Returns the first constant annotating [element] that is this type.
@@ -170,7 +176,7 @@ class _UriTypeChecker extends TypeChecker {
170176
int get hashCode => _url.hashCode;
171177

172178
/// Url as a [Uri] object, lazily constructed.
173-
Uri get uri => _cache[this] ??= Uri.parse(_url);
179+
Uri get uri => _cache[this] ??= _normalizeUrl(Uri.parse(_url));
174180

175181
/// Returns whether this type represents the same as [url].
176182
bool hasSameUrl(dynamic url) =>

test/find_type_test.dart

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:analyzer/dart/element/element.dart';
6+
import 'package:build_test/build_test.dart';
7+
import 'package:source_gen/source_gen.dart';
8+
import 'package:test/test.dart';
9+
10+
void main() {
11+
LibraryElement library;
12+
13+
setUpAll(() async {
14+
final resolver = await resolveSource(r'''
15+
library test_lib;
16+
17+
export 'dart:collection' show LinkedHashMap;
18+
export 'package:source_gen/source_gen.dart' show Generator;
19+
import 'dart:async' show Stream;
20+
21+
class Example {}
22+
''');
23+
library = resolver.getLibraryByName('test_lib');
24+
});
25+
26+
final isClassElement = const isInstanceOf<ClassElement>();
27+
28+
test('should return a type not exported', () {
29+
expect(findType(library, 'Example'), isClassElement);
30+
});
31+
32+
test('should return a type exported from dart:', () {
33+
expect(findType(library, 'LinkedHashMap'), isClassElement);
34+
});
35+
36+
test('should return a type exported from package:', () {
37+
expect(findType(library, 'Generator'), isClassElement);
38+
});
39+
40+
test('should not return a type imported', () {
41+
expect(findType(library, 'Stream'), isNull);
42+
});
43+
}

test/type_checker_test.dart

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,16 @@ void main() {
1717
TypeChecker staticMapChecker;
1818
TypeChecker staticHashMapChecker;
1919

20+
// Resolved top-level types from package:source_gen.
21+
DartType staticGenerator;
22+
DartType staticGeneratorForAnnotation;
23+
TypeChecker staticGeneratorChecker;
24+
TypeChecker staticGeneratorForAnnotationChecker;
25+
2026
setUpAll(() async {
21-
final resolver = await resolveSource('');
27+
final resolver = await resolveSource(r'''
28+
export 'package:source_gen/source_gen.dart';
29+
''');
2230

2331
final core = resolver.getLibraryByName('dart.core');
2432
staticMap = core.getType('Map').type;
@@ -27,12 +35,22 @@ void main() {
2735
final collection = resolver.getLibraryByName('dart.collection');
2836
staticHashMap = collection.getType('HashMap').type;
2937
staticHashMapChecker = new TypeChecker.fromStatic(staticHashMap);
38+
39+
final sourceGen = resolver.getLibraryByName('source_gen');
40+
staticGenerator = findType(sourceGen, 'Generator').type;
41+
staticGeneratorChecker = new TypeChecker.fromStatic(staticGenerator);
42+
staticGeneratorForAnnotation =
43+
findType(sourceGen, 'GeneratorForAnnotation').type;
44+
staticGeneratorForAnnotationChecker =
45+
new TypeChecker.fromStatic(staticGeneratorForAnnotation);
3046
});
3147

3248
// Run a common set of type comparison checks with various implementations.
3349
void commonTests({
3450
@required TypeChecker checkMap(),
3551
@required TypeChecker checkHashMap(),
52+
@required TypeChecker checkGenerator(),
53+
@required TypeChecker checkGeneratorForAnnotation(),
3654
}) {
3755
group('(Map)', () {
3856
test('should equal dart:core#Map', () {
@@ -72,24 +90,60 @@ void main() {
7290
expect(checkHashMap().isAssignableFromType(staticMap), isFalse);
7391
});
7492
});
93+
94+
group('(Generator)', () {
95+
test('should equal Generator', () {
96+
expect(checkGenerator().isExactlyType(staticGenerator), isTrue,
97+
reason: '${checkGenerator()} != $staticGenerator');
98+
});
99+
100+
test('should not be a super type of Generator', () {
101+
expect(checkGenerator().isSuperTypeOf(staticGenerator), isFalse,
102+
reason: '${checkGenerator()} is super of $staticGenerator');
103+
});
104+
105+
test('should be a super type of GeneratorForAnnotation', () {
106+
expect(checkGenerator().isSuperTypeOf(staticGeneratorForAnnotation),
107+
isTrue,
108+
reason:
109+
'${checkGenerator()} is not super of $staticGeneratorForAnnotation');
110+
});
111+
112+
test('should be assignable from GeneratorForAnnotation', () {
113+
expect(
114+
checkGenerator().isAssignableFromType(staticGeneratorForAnnotation),
115+
isTrue,
116+
reason:
117+
'${checkGenerator()} is not assignable from $staticGeneratorForAnnotation');
118+
});
119+
});
75120
}
76121

77122
group('TypeChecker.forRuntime', () {
78123
commonTests(
79124
checkMap: () => const TypeChecker.fromRuntime(Map),
80-
checkHashMap: () => const TypeChecker.fromRuntime(HashMap));
125+
checkHashMap: () => const TypeChecker.fromRuntime(HashMap),
126+
checkGenerator: () => const TypeChecker.fromRuntime(Generator),
127+
checkGeneratorForAnnotation: () =>
128+
const TypeChecker.fromRuntime(GeneratorForAnnotation));
81129
});
82130

83131
group('TypeChecker.forStatic', () {
84132
commonTests(
85133
checkMap: () => staticMapChecker,
86-
checkHashMap: () => staticHashMapChecker);
134+
checkHashMap: () => staticHashMapChecker,
135+
checkGenerator: () => staticGeneratorChecker,
136+
checkGeneratorForAnnotation: () => staticGeneratorForAnnotationChecker);
87137
});
88138

89139
group('TypeChecker.fromUrl', () {
90140
commonTests(
91141
checkMap: () => const TypeChecker.fromUrl('dart:core#Map'),
92142
checkHashMap: () =>
93-
const TypeChecker.fromUrl('dart:collection#HashMap'));
143+
const TypeChecker.fromUrl('dart:collection#HashMap'),
144+
checkGenerator: () => const TypeChecker.fromUrl(
145+
'package:source_gen/src/generator.dart#Generator'),
146+
checkGeneratorForAnnotation: () => const TypeChecker.fromUrl(
147+
'package:source_gen/src/generator_for_annotation.dart#GeneratorForAnnotation'));
94148
});
95149
}

0 commit comments

Comments
 (0)