Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions source_gen/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
## 3.1.0-wip

- Prepare to stop using `dart:mirrors`: deprecate `TypeChecker.fromRuntime`.
It will be removed in version `4.0.0`. Add `TypeChecker.typeNamed` which is
the recommended replacement.
- Prepare to stop using `dart:mirrors`: add `inPackage` and `inSdk` parameters
to `GenerateForAnnotation`. These will start being used in version `4.0.0`
when it switches to `TypeChecker.typeNamed`.

## 3.0.0

- **Breaking Change**: use the new `element2` APIs in `analyzer`. Builders that
Expand Down
15 changes: 14 additions & 1 deletion source_gen/lib/src/generator_for_annotation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,25 @@ import 'type_checker.dart';
/// may be helpful to check which elements have a given annotation.
abstract class GeneratorForAnnotation<T> extends Generator {
final bool throwOnUnresolved;
final String? inPackage;
final bool? inSdk;

/// By default, this generator will throw if it encounters unresolved
/// annotations. You can override this by setting [throwOnUnresolved] to
/// `false`.
const GeneratorForAnnotation({this.throwOnUnresolved = true});
///
/// With `source_gen` 4.0.0 this class will stop using mirrors for matching
/// annotations and will fall back to comparing the name of `T`. Pass
/// [inPackage] and [inSdk] to tighten the check; see [TypeChecker#typeNamed].
/// To use a custom annotation check, override [typeChecker].
const GeneratorForAnnotation({
this.throwOnUnresolved = true,
this.inPackage,
this.inSdk,
});

// This will switch to `typeNamed` in 4.0.0.
// ignore: deprecated_member_use_from_same_package
TypeChecker get typeChecker => TypeChecker.fromRuntime(T);

@override
Expand Down
51 changes: 51 additions & 0 deletions source_gen/lib/src/type_checker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,27 @@ abstract class TypeChecker {
/// Create a new [TypeChecker] backed by a runtime [type].
///
/// This implementation uses `dart:mirrors` (runtime reflection).
@Deprecated('''
Will be removed in 4.0.0 to drop `dart:mirrors` dependency.

Recommended: replace `fromRuntime(Foo)` with
`typeNamed(Foo, inPackage: 'foo_package')`. This is a slighly weaker check than
`fromRuntime(Foo)` as it matches any annotation named `Foo` in
`package:foo_package`.

If you need an exact match, use `fromUrl`.''')
const factory TypeChecker.fromRuntime(Type type) = _MirrorTypeChecker;

/// Create a new [TypeChecker] for types matching the name of [type].
///
/// Optionally, also pass [inPackage] to restrict to a specific package by
/// name. Set [inSdk] if it's a `dart` package.
const factory TypeChecker.typeNamed(
Type type, {
String? inPackage,
bool? inSdk,
}) = _NameTypeChecker;

/// Create a new [TypeChecker] backed by a static [type].
const factory TypeChecker.fromStatic(DartType type) = _LibraryTypeChecker;

Expand Down Expand Up @@ -269,6 +288,38 @@ class _MirrorTypeChecker extends TypeChecker {
String toString() => _computed.toString();
}

// Checks a runtime type name and optional package against a static type.
class _NameTypeChecker extends TypeChecker {
final Type _type;

final String? _inPackage;
final bool _inSdk;

const _NameTypeChecker(this._type, {String? inPackage, bool? inSdk})
: _inPackage = inPackage,
_inSdk = inSdk ?? false,
super._();

String get _typeName {
final result = _type.toString();
return result.contains('<')
? result.substring(0, result.indexOf('<'))
: result;
}

@override
bool isExactly(Element2 element) {
final uri = element.library2!.uri;
return element.name3 == _typeName &&
(_inPackage == null ||
(((uri.scheme == 'dart') == _inSdk) &&
uri.pathSegments.first == _inPackage));
}

@override
String toString() => _inPackage == null ? '$_type' : '$_inPackage#$_type';
}

// Checks a runtime type against an Uri and Symbol.
class _UriTypeChecker extends TypeChecker {
final String _url;
Expand Down
2 changes: 1 addition & 1 deletion source_gen/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: source_gen
version: 3.0.0
version: 3.1.0-wip
description: >-
Source code generation builders and utilities for the Dart build system
repository: https://github.com/dart-lang/source_gen/tree/master/source_gen
Expand Down
6 changes: 5 additions & 1 deletion source_gen/test/constants_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,11 @@ void main() {

test('should compare using TypeChecker', () {
final $deprecated = constants[8];
const check = TypeChecker.fromRuntime(Deprecated);
const check = TypeChecker.typeNamed(
Deprecated,
inPackage: 'core',
inSdk: true,
);
expect($deprecated.instanceOf(check), isTrue, reason: '$deprecated');
});
});
Expand Down
4 changes: 3 additions & 1 deletion source_gen/test/external_only_type_checker_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@ void main() {
'TypeChecker.forRuntime',
() {
commonTests(
checkNonPublic: () => const TypeChecker.fromRuntime(NonPublic),
checkNonPublic:
() =>
const TypeChecker.typeNamed(NonPublic, inPackage: 'source_gen'),
);
},
onPlatform: const {
Expand Down
74 changes: 70 additions & 4 deletions source_gen/test/type_checker_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -335,15 +335,77 @@ void main() {

group('TypeChecker.forRuntime', () {
commonTests(
// ignore: deprecated_member_use_from_same_package
checkIterable: () => const TypeChecker.fromRuntime(Iterable),
// ignore: deprecated_member_use_from_same_package
checkEnum: () => const TypeChecker.fromRuntime(Enum),
// ignore: deprecated_member_use_from_same_package
checkEnumMixin: () => const TypeChecker.fromRuntime(MyEnumMixin),
// ignore: deprecated_member_use_from_same_package
checkMap: () => const TypeChecker.fromRuntime(Map),
// ignore: deprecated_member_use_from_same_package
checkMapMixin: () => const TypeChecker.fromRuntime(MyMapMixin),
// ignore: deprecated_member_use_from_same_package
checkHashMap: () => const TypeChecker.fromRuntime(HashMap),
// ignore: deprecated_member_use_from_same_package
checkGenerator: () => const TypeChecker.fromRuntime(Generator),

checkGeneratorForAnnotation:
// ignore: deprecated_member_use_from_same_packages
() => const TypeChecker.typeNamed(
GeneratorForAnnotation,
inPackage: 'source_gen',
),
);
});

group('TypeChecker.typeNamed without package', () {
commonTests(
checkIterable: () => const TypeChecker.typeNamed(Iterable),
checkEnum: () => const TypeChecker.typeNamed(Enum),
checkEnumMixin: () => const TypeChecker.typeNamed(MyEnumMixin),
checkMap: () => const TypeChecker.typeNamed(Map),
checkMapMixin: () => const TypeChecker.typeNamed(MyMapMixin),
checkHashMap: () => const TypeChecker.typeNamed(HashMap),
checkGenerator: () => const TypeChecker.typeNamed(Generator),
checkGeneratorForAnnotation:
() => const TypeChecker.fromRuntime(GeneratorForAnnotation),
() => const TypeChecker.typeNamed(GeneratorForAnnotation),
);
});

group('TypeChecker.typeNamed with package', () {
commonTests(
checkIterable:
() => const TypeChecker.typeNamed(
Iterable,
inPackage: 'core',
inSdk: true,
),
checkEnum:
() =>
const TypeChecker.typeNamed(Enum, inPackage: 'core', inSdk: true),
checkEnumMixin:
() =>
const TypeChecker.typeNamed(MyEnumMixin, inPackage: 'source_gen'),
checkMap:
() =>
const TypeChecker.typeNamed(Map, inPackage: 'core', inSdk: true),
checkMapMixin:
() =>
const TypeChecker.typeNamed(MyMapMixin, inPackage: 'source_gen'),
checkHashMap:
() => const TypeChecker.typeNamed(
HashMap,
inPackage: 'collection',
inSdk: true,
),
checkGenerator:
() => const TypeChecker.typeNamed(Generator, inPackage: 'source_gen'),
checkGeneratorForAnnotation:
() => const TypeChecker.typeNamed(
GeneratorForAnnotation,
inPackage: 'source_gen',
),
);
});

Expand Down Expand Up @@ -393,7 +455,11 @@ void main() {
class X {}
''', (resolver) async => (await resolver.findLibraryByName('_test'))!);
final classX = library.getClass2('X')!;
const $deprecated = TypeChecker.fromRuntime(Deprecated);
const $deprecated = TypeChecker.typeNamed(
Deprecated,
inPackage: 'core',
inSdk: true,
);

expect(
() => $deprecated.annotationsOf(classX),
Expand All @@ -412,8 +478,8 @@ void main() {

test('should check multiple checkers', () {
const listOrMap = TypeChecker.any([
TypeChecker.fromRuntime(List),
TypeChecker.fromRuntime(Map),
TypeChecker.typeNamed(List, inPackage: 'core', inSdk: true),
TypeChecker.typeNamed(Map, inPackage: 'core', inSdk: true),
]);
expect(listOrMap.isExactlyType(staticMap), isTrue);
});
Expand Down
Loading