Skip to content

Commit c6f1dfb

Browse files
committed
Elements. Support for generateForAnnotatedDirective() and related.
1 parent 0aa4656 commit c6f1dfb

File tree

6 files changed

+176
-28
lines changed

6 files changed

+176
-28
lines changed

source_gen/lib/source_gen.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ export 'src/constants/revive.dart' show Revivable;
99
export 'src/generator.dart'
1010
show Generator, InvalidGenerationSource, InvalidGenerationSourceError;
1111
export 'src/generator_for_annotation.dart' show GeneratorForAnnotation;
12-
export 'src/library.dart' show AnnotatedElement, LibraryReader;
12+
export 'src/library.dart'
13+
show AnnotatedDirective, AnnotatedElement, LibraryReader;
1314
export 'src/span_for_element.dart' show spanForElement, spanForElement2;
1415
export 'src/type_checker.dart' show TypeChecker, UnresolvedAnnotationException;
1516
export 'src/utils.dart' show typeNameOf;

source_gen/lib/src/generator_for_annotation.dart

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,21 @@ abstract class GeneratorForAnnotation<T> extends Generator {
5555
FutureOr<String> generate(LibraryReader library, BuildStep buildStep) async {
5656
final values = <String>{};
5757

58+
for (var annotatedDirective in library.libraryDirectivesAnnotatedWith(
59+
typeChecker,
60+
throwOnUnresolved: throwOnUnresolved,
61+
)) {
62+
final generatedValue = generateForAnnotatedDirective(
63+
annotatedDirective.directive,
64+
annotatedDirective.annotation,
65+
buildStep,
66+
);
67+
await for (var value in normalizeGeneratorOutput(generatedValue)) {
68+
assert(value.length == value.trim().length);
69+
values.add(value);
70+
}
71+
}
72+
5873
for (var annotatedElement in library.annotatedWith(
5974
typeChecker,
6075
throwOnUnresolved: throwOnUnresolved,
@@ -123,4 +138,27 @@ abstract class GeneratorForAnnotation<T> extends Generator {
123138
ConstantReader annotation,
124139
BuildStep buildStep,
125140
) {}
141+
142+
/// Implement to return source code to generate for [directive].
143+
///
144+
/// This method is invoked based on finding directives annotated with an
145+
/// instance of [T]. The [annotation] is provided as a [ConstantReader].
146+
///
147+
/// Supported return values include a single [String] or multiple [String]
148+
/// instances within an [Iterable] or [Stream]. It is also valid to return a
149+
/// [Future] of [String], [Iterable], or [Stream]. When multiple values are
150+
/// returned through an iterable or stream they will be deduplicated.
151+
/// Typically each value will be an independent unit of code and the
152+
/// deduplication prevents re-defining the same member multiple times. For
153+
/// example if multiple annotated elements may need a specific utility method
154+
/// available it can be output for each one, and the single deduplicated
155+
/// definition can be shared.
156+
///
157+
/// Implementations should return `null` when no content is generated. Empty
158+
/// or whitespace-only [String] instances are also ignored.
159+
dynamic generateForAnnotatedDirective(
160+
ElementDirective directive,
161+
ConstantReader annotation,
162+
BuildStep buildStep,
163+
) {}
126164
}

source_gen/lib/src/library.dart

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,16 @@ import 'constants/reader.dart';
1313
import 'type_checker.dart';
1414
import 'utils.dart';
1515

16+
/// Result of finding an [annotation] on [directive] through [LibraryReader].
17+
class AnnotatedDirective {
18+
final ConstantReader annotation;
19+
final ElementDirective directive;
20+
21+
const AnnotatedDirective(this.annotation, this.directive);
22+
23+
Metadata? get metadata2 => directive.metadata2;
24+
}
25+
1626
/// Result of finding an [annotation] on [element] through [LibraryReader].
1727
class AnnotatedElement {
1828
final ConstantReader annotation;
@@ -87,6 +97,30 @@ class LibraryReader {
8797
}
8898
}
8999

100+
/// All of the directives in this library annotated with [checker].
101+
Iterable<AnnotatedDirective> libraryDirectivesAnnotatedWith(
102+
TypeChecker checker, {
103+
bool throwOnUnresolved = true,
104+
}) sync* {
105+
final firstFragment = element2.firstFragment;
106+
final directives = [
107+
...firstFragment.libraryImports2,
108+
...firstFragment.libraryExports2,
109+
...firstFragment.partIncludes,
110+
];
111+
112+
for (final directive in directives) {
113+
final annotation = checker.firstAnnotationOf2(
114+
directive,
115+
throwOnUnresolved: throwOnUnresolved,
116+
);
117+
118+
if (annotation != null) {
119+
yield AnnotatedDirective(ConstantReader(annotation), directive);
120+
}
121+
}
122+
}
123+
90124
/// All of the declarations in this library annotated with exactly [checker].
91125
Iterable<AnnotatedElement> annotatedWithExact(
92126
TypeChecker checker, {

source_gen/lib/src/type_checker.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ abstract class TypeChecker {
8484
///
8585
/// Throws on unresolved annotations unless [throwOnUnresolved] is `false`.
8686
DartObject? firstAnnotationOf2(
87-
Element2 element, {
87+
Object element, {
8888
bool throwOnUnresolved = true,
8989
}) {
9090
if (element case final Annotatable annotatable) {
@@ -188,13 +188,13 @@ abstract class TypeChecker {
188188
}
189189

190190
DartObject? _computeConstantValue2(
191-
Element2 element,
191+
Object element,
192192
ElementAnnotation annotation,
193193
int annotationIndex, {
194194
bool throwOnUnresolved = true,
195195
}) {
196196
final result = annotation.computeConstantValue();
197-
if (result == null && throwOnUnresolved) {
197+
if (result == null && throwOnUnresolved && element is Element2) {
198198
throw UnresolvedAnnotationException._from(element, annotationIndex);
199199
}
200200
return result;
@@ -219,7 +219,7 @@ abstract class TypeChecker {
219219
/// Throws [UnresolvedAnnotationException] on unresolved annotations unless
220220
/// [throwOnUnresolved] is explicitly set to `false` (default is `true`).
221221
Iterable<DartObject> annotationsOf2(
222-
Element2 element, {
222+
Object element, {
223223
bool throwOnUnresolved = true,
224224
}) =>
225225
_annotationsWhere2(
@@ -246,7 +246,7 @@ abstract class TypeChecker {
246246
}
247247

248248
Iterable<DartObject> _annotationsWhere2(
249-
Element2 element,
249+
Object element,
250250
bool Function(DartType) predicate, {
251251
bool throwOnUnresolved = true,
252252
}) sync* {

source_gen/pubspec.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ dev_dependencies:
2929
test: ^1.16.0
3030

3131
dependency_overrides:
32-
analyzer: ^7.1.0
32+
analyzer:
33+
path: /Users/scheglov/Source/Dart/sdk.git/sdk/pkg/analyzer
3334
build:
3435
git:
3536
url: https://github.com/dart-lang/build.git

source_gen/test/generator_for_annotation_test.dart

Lines changed: 95 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ library;
99
import 'package:analyzer/dart/analysis/utilities.dart';
1010
import 'package:analyzer/dart/ast/ast.dart';
1111
import 'package:analyzer/dart/element/element.dart';
12+
import 'package:analyzer/dart/element/element2.dart';
1213
import 'package:build/build.dart';
1314
import 'package:build_test/build_test.dart';
1415
import 'package:source_gen/source_gen.dart';
@@ -25,19 +26,24 @@ void main() {
2526
'list with null, empty, and whitespace items': [null, '', '\n \t'],
2627
}.entries) {
2728
test(entry.key, () async {
28-
final generator =
29-
_StubGenerator<Deprecated>('Value', (_) => entry.value);
29+
final generator = _StubGenerator<Deprecated>(
30+
'Value',
31+
elementBehavior: (_) => entry.value,
32+
);
3033
final builder = LibraryBuilder(generator);
3134
await testBuilder(builder, _inputMap, outputs: {});
3235
});
3336
}
3437
});
3538

3639
test('Supports and dedupes multiple return values', () async {
37-
final generator = _StubGenerator<Deprecated>('Repeating', (element) sync* {
38-
yield '// There are deprecated values in this library!';
39-
yield '// ${element.name}';
40-
});
40+
final generator = _StubGenerator<Deprecated>(
41+
'Repeating',
42+
elementBehavior: (element) sync* {
43+
yield '// There are deprecated values in this library!';
44+
yield '// ${element.name}';
45+
},
46+
);
4147
final builder = LibraryBuilder(generator);
4248
await testBuilder(
4349
builder,
@@ -65,13 +71,19 @@ $dartFormatWidth
6571

6672
group('handles errors correctly', () {
6773
for (var entry in {
68-
'sync errors': _StubGenerator<Deprecated>('Failing', (_) {
69-
throw StateError('not supported!');
70-
}),
71-
'from iterable': _StubGenerator<Deprecated>('FailingIterable', (_) sync* {
72-
yield '// There are deprecated values in this library!';
73-
throw StateError('not supported!');
74-
}),
74+
'sync errors': _StubGenerator<Deprecated>(
75+
'Failing',
76+
elementBehavior: (_) {
77+
throw StateError('not supported!');
78+
},
79+
),
80+
'from iterable': _StubGenerator<Deprecated>(
81+
'FailingIterable',
82+
elementBehavior: (_) sync* {
83+
yield '// There are deprecated values in this library!';
84+
throw StateError('not supported!');
85+
},
86+
),
7587
}.entries) {
7688
test(entry.key, () async {
7789
final builder = LibraryBuilder(entry.value);
@@ -92,8 +104,12 @@ $dartFormatWidth
92104

93105
test('Does not resolve the library if there are no top level annotations',
94106
() async {
95-
final builder =
96-
LibraryBuilder(_StubGenerator<Deprecated>('Deprecated', (_) => null));
107+
final builder = LibraryBuilder(
108+
_StubGenerator<Deprecated>(
109+
'Deprecated',
110+
elementBehavior: (_) => null,
111+
),
112+
);
97113
final input = AssetId('a', 'lib/a.dart');
98114
final assets = {input: 'main() {}'};
99115

@@ -116,7 +132,7 @@ $dartFormatWidth
116132
final builder = LibraryBuilder(
117133
_StubGenerator<Deprecated>(
118134
'Deprecated',
119-
(element) => '// ${element.displayName}',
135+
elementBehavior: (element) => '// ${element.displayName}',
120136
),
121137
);
122138
await testBuilder(
@@ -142,12 +158,54 @@ $dartFormatWidth
142158
);
143159
});
144160

161+
test('applies to annotated directives', () async {
162+
final builder = LibraryBuilder(
163+
_StubGenerator<Deprecated>(
164+
'Deprecated',
165+
directiveBehavior: (element) => '// ${element.runtimeType}',
166+
elementBehavior: (element) => '// ${element.runtimeType}',
167+
),
168+
);
169+
await testBuilder(
170+
builder,
171+
{
172+
'a|lib/imported.dart': '',
173+
'a|lib/part.dart': 'part of \'file.dart\';',
174+
'a|lib/file.dart': '''
175+
library;
176+
@deprecated
177+
import 'imported.dart';
178+
@deprecated
179+
export 'imported.dart';
180+
@deprecated
181+
part 'part.dart';
182+
''',
183+
},
184+
outputs: {
185+
'a|lib/file.g.dart': '''
186+
$dartFormatWidth
187+
// GENERATED CODE - DO NOT MODIFY BY HAND
188+
189+
// **************************************************************************
190+
// Generator: Deprecated
191+
// **************************************************************************
192+
193+
// LibraryImportElementImpl
194+
195+
// LibraryExportElementImpl
196+
197+
// PartElementImpl
198+
''',
199+
},
200+
);
201+
});
202+
145203
group('Unresolved annotations', () {
146204
test('cause an error by default', () async {
147205
final builder = LibraryBuilder(
148206
_StubGenerator<Deprecated>(
149207
'Deprecated',
150-
(element) => '// ${element.displayName}',
208+
elementBehavior: (element) => '// ${element.displayName}',
151209
),
152210
);
153211
expect(
@@ -169,7 +227,7 @@ $dartFormatWidth
169227
final builder = LibraryBuilder(
170228
_StubGenerator<Deprecated>(
171229
'Deprecated',
172-
(element) => '// ${element.displayName}',
230+
elementBehavior: (element) => '// ${element.displayName}',
173231
throwOnUnresolved: false,
174232
),
175233
);
@@ -192,20 +250,36 @@ $dartFormatWidth
192250

193251
class _StubGenerator<T> extends GeneratorForAnnotation<T> {
194252
final String _name;
195-
final Object? Function(Element) _behavior;
253+
final Object? Function(ElementDirective) directiveBehavior;
254+
final Object? Function(Element) elementBehavior;
255+
256+
const _StubGenerator(
257+
this._name, {
258+
this.directiveBehavior = _returnNull,
259+
required this.elementBehavior,
260+
super.throwOnUnresolved,
261+
});
196262

197-
const _StubGenerator(this._name, this._behavior, {super.throwOnUnresolved});
263+
@override
264+
Object? generateForAnnotatedDirective(
265+
ElementDirective directive,
266+
ConstantReader annotation,
267+
BuildStep buildStep,
268+
) =>
269+
directiveBehavior(directive);
198270

199271
@override
200272
Object? generateForAnnotatedElement(
201273
Element element,
202274
ConstantReader annotation,
203275
BuildStep buildStep,
204276
) =>
205-
_behavior(element);
277+
elementBehavior(element);
206278

207279
@override
208280
String toString() => _name;
281+
282+
static Null _returnNull(Object _) => null;
209283
}
210284

211285
const _inputMap = {

0 commit comments

Comments
 (0)