Skip to content
This repository was archived by the owner on Feb 4, 2025. It is now read-only.

Commit e9b480a

Browse files
authored
Add built_value test macro. (#144)
* Add built_value test macro. * Address review comments.
1 parent 7fa7e9d commit e9b480a

File tree

8 files changed

+209
-29
lines changed

8 files changed

+209
-29
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
part of 'package:foo/built_value/built_value_test.dart';
2+
3+
import 'package:_test_macros/built_value.dart' as prefix0;
4+
import 'package:foo/built_value/built_value_test.dart' as prefix1;
5+
6+
@prefix0.BuiltValueBuilder()
7+
class EmptyBuilder {}
8+
9+
augment class Empty {
10+
Empty([void Function(prefix1.EmptyBuilder)? updates]) {}
11+
Empty._() {}
12+
13+
prefix1.EmptyBuilder toBuilder() => prefix1.EmptyBuilder()..replace(this);
14+
prefix1.Empty rebuild(void Function(prefix1.EmptyBuilder) updates) =>
15+
(toBuilder()..update(updates)).build();
16+
17+
bool operator==(Object other) => other is prefix1.Empty;
18+
19+
}
20+
augment class EmptyBuilder {
21+
void replace(prefix1.Empty other) {}
22+
void update(void Function(prefix1.EmptyBuilder) updates) => updates(this);
23+
prefix1.Empty build() => prefix1.Empty._();
24+
25+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright (c) 2024, 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:_test_macros/built_value.dart';
6+
import 'package:test/test.dart';
7+
8+
void main() {
9+
group('Empty class', () {
10+
test('instantiation, builder, rebuild, comparison', () {
11+
final empty = Empty();
12+
final empty2 = empty.rebuild((b) {});
13+
expect(empty2, empty);
14+
15+
// analyzer: The function 'EmptyBuilder' isn't defined.
16+
// final emptyBuilder = EmptyBuilder();
17+
// final empty3 = emptyBuilder.build();
18+
// expect(empty3, empty);
19+
});
20+
});
21+
}
22+
23+
@BuiltValue()
24+
class Empty {}

pkgs/_analyzer_macros/lib/macro_implementation.dart

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,10 @@ class AnalyzerMacroPackageConfigs implements injected.MacroPackageConfigs {
5454
AnalyzerMacroPackageConfigs(this._impl);
5555

5656
@override
57-
bool isMacro(Uri uri, String name) =>
58-
_impl._host.isMacro(QualifiedName(uri: '$uri', name: name));
57+
bool isMacro(Uri uri, String name) {
58+
name = _removePrefix(name);
59+
return _impl._host.isMacro(QualifiedName(uri: '$uri', name: name));
60+
}
5961
}
6062

6163
class AnalyzerMacroRunner implements injected.MacroRunner {
@@ -64,13 +66,24 @@ class AnalyzerMacroRunner implements injected.MacroRunner {
6466
AnalyzerMacroRunner(this._impl);
6567

6668
@override
67-
injected.RunningMacro run(Uri uri, String name) => AnalyzerRunningMacro.run(
68-
_impl,
69-
QualifiedName(uri: '$uri', name: name),
70-
_impl._host.lookupMacroImplementation(
69+
injected.RunningMacro run(Uri uri, String name) {
70+
name = _removePrefix(name);
71+
return AnalyzerRunningMacro.run(
72+
_impl,
7173
QualifiedName(uri: '$uri', name: name),
72-
)!,
73-
);
74+
_impl._host.lookupMacroImplementation(
75+
QualifiedName(uri: '$uri', name: name),
76+
)!,
77+
);
78+
}
79+
}
80+
81+
// The analyzer currently sends import prefixes, for example
82+
// `prefix0.BuiltValueBuilder` when it should send just `BuiltValueBuilder`.
83+
// TODO(davidmorgan): fix in the analyzer instead.
84+
String _removePrefix(String name) {
85+
final index = name.indexOf('.');
86+
return index == -1 ? name : name.substring(index + 1);
7487
}
7588

7689
class AnalyzerRunningMacro implements injected.RunningMacro {
@@ -190,17 +203,23 @@ class AnalyzerMacroExecutionResult
190203
@override
191204
final Map<macros_api_v1.Identifier, Iterable<macros_api_v1.DeclarationCode>>
192205
typeAugmentations;
206+
@override
207+
final Iterable<macros_api_v1.DeclarationCode> libraryAugmentations;
208+
@override
209+
final List<String> newTypeNames;
193210

194211
AnalyzerMacroExecutionResult(
195-
this.target,
196-
Iterable<macros_api_v1.DeclarationCode> declarations,
197-
)
212+
this.target, {
213+
required Iterable<macros_api_v1.DeclarationCode> declarations,
214+
required this.libraryAugmentations,
215+
required this.newTypeNames,
216+
})
198217
// TODO(davidmorgan): this assumes augmentations are for the macro
199218
// application target. Instead, it should be explicit in
200219
// `AugmentResponse`.
201220
: typeAugmentations = {
202-
(target as macros_api_v1.Declaration).identifier: declarations,
203-
};
221+
(target as macros_api_v1.Declaration).identifier: declarations,
222+
};
204223

205224
static Future<AnalyzerMacroExecutionResult> dartModelToInjected(
206225
macros_api_v1.MacroTarget target,
@@ -228,9 +247,10 @@ class AnalyzerMacroExecutionResult
228247
}
229248
}
230249

250+
final libraryDeclarations = <macros_api_v1.DeclarationCode>[];
231251
for (final augmentation
232252
in augmentResponse.libraryAugmentations ?? const <Augmentation>[]) {
233-
declarations.add(
253+
libraryDeclarations.add(
234254
macros_api_v1.DeclarationCode.fromParts(
235255
await _resolveNames(augmentation.code),
236256
),
@@ -246,7 +266,12 @@ class AnalyzerMacroExecutionResult
246266
throw UnimplementedError('Type augmentations are not implemented');
247267
}
248268

249-
return AnalyzerMacroExecutionResult(target, declarations);
269+
return AnalyzerMacroExecutionResult(
270+
target,
271+
declarations: declarations,
272+
libraryAugmentations: libraryDeclarations,
273+
newTypeNames: augmentResponse.newTypeNames ?? [],
274+
);
250275
}
251276

252277
@override
@@ -267,16 +292,10 @@ class AnalyzerMacroExecutionResult
267292
Map<macros_api_v1.Identifier, Iterable<macros_api_v1.TypeAnnotationCode>>
268293
get interfaceAugmentations => {};
269294

270-
@override
271-
Iterable<macros_api_v1.DeclarationCode> get libraryAugmentations => {};
272-
273295
@override
274296
Map<macros_api_v1.Identifier, Iterable<macros_api_v1.TypeAnnotationCode>>
275297
get mixinAugmentations => {};
276298

277-
@override
278-
Iterable<String> get newTypeNames => [];
279-
280299
@override
281300
void serialize(Object serializer) => throw UnimplementedError();
282301
}

pkgs/_macro_tool/lib/macro_tool.dart

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,22 +30,22 @@ class MacroTool {
3030

3131
/// Runs macros.
3232
///
33-
/// Throws `StateError` if there are any errors.
33+
/// Writes macro augmentations next to each source file with the extension
34+
/// `.macro_tool_output`.
3435
///
35-
/// Otherwise, writes macro augmentations next to each source file with the
36-
/// extension `.macro_tool_output`.
36+
/// Then throws `StateError` if there were any errors.
3737
Future<void> apply() async {
3838
_applyResult = await macroRunner.run();
3939

40-
if (_applyResult!.allErrors.isNotEmpty) {
41-
throw StateError('Errors: ${_applyResult!.allErrors}');
42-
}
43-
4440
for (final result in _applyResult!.fileResults) {
4541
if (result.output != null) {
4642
result.sourceFile.writeOutput(macroRunner, result.output!);
4743
}
4844
}
45+
46+
if (_applyResult!.allErrors.isNotEmpty) {
47+
throw StateError('Errors: ${_applyResult!.allErrors}');
48+
}
4949
}
5050

5151
/// Patches source so the analyzer can analyze it without running macros.
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Copyright (c) 2024, 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 'dart:async';
6+
7+
import 'package:dart_model/dart_model.dart';
8+
import 'package:macro/macro.dart';
9+
import 'package:macro_service/macro_service.dart';
10+
11+
import 'templating.dart';
12+
13+
/// A macro equivalent to `package:built_value` value types.
14+
class BuiltValue {
15+
const BuiltValue();
16+
}
17+
18+
/// A macro equivalent to `package:built_value` builders.
19+
class BuiltValueBuilder {
20+
const BuiltValueBuilder();
21+
}
22+
23+
class BuiltValueImplementation
24+
implements ClassTypesMacro, ClassDeclarationsMacro {
25+
@override
26+
MacroDescription get description => MacroDescription(
27+
annotation: QualifiedName(
28+
uri: 'package:_test_macros/built_value.dart',
29+
name: 'BuiltValue',
30+
),
31+
runsInPhases: [1, 2],
32+
);
33+
34+
@override
35+
Future<void> buildTypesForClass(ClassTypesBuilder<Interface> builder) async {
36+
final valueName = builder.model.qualifiedNameOf(builder.target.node)!;
37+
final builderSimpleName = '${valueName.name}Builder';
38+
builder.declareType(
39+
builderSimpleName,
40+
augmentation('''
41+
@{{package:_test_macros/built_value.dart#BuiltValueBuilder}}()
42+
class $builderSimpleName {}
43+
'''),
44+
);
45+
}
46+
47+
@override
48+
Future<void> buildDeclarationsForClass(
49+
ClassDeclarationsBuilder builder,
50+
) async {
51+
final valueName = builder.model.qualifiedNameOf(builder.target.node)!;
52+
final builderName = QualifiedName(
53+
uri: valueName.uri,
54+
name: '${valueName.name}Builder',
55+
);
56+
// TODO(davidmorgan): query for fields, set and compare fields.
57+
builder.declareInType(
58+
augmentation('''
59+
${valueName.name}([void Function(${builderName.code})? updates]) {}
60+
${valueName.name}._() {}
61+
62+
${builderName.code} toBuilder() => ${builderName.code}()..replace(this);
63+
${valueName.code} rebuild(void Function(${builderName.code}) updates) =>
64+
(toBuilder()..update(updates)).build();
65+
66+
bool operator==(Object other) => other is ${valueName.code};
67+
'''),
68+
);
69+
}
70+
}
71+
72+
class BuiltValueBuilderImplementation implements ClassDeclarationsMacro {
73+
@override
74+
MacroDescription get description => MacroDescription(
75+
annotation: QualifiedName(
76+
uri: 'package:_test_macros/built_value.dart',
77+
name: 'BuiltValueBuilder',
78+
),
79+
runsInPhases: [2],
80+
);
81+
82+
@override
83+
Future<void> buildDeclarationsForClass(
84+
ClassDeclarationsBuilder builder,
85+
) async {
86+
final builderName = builder.model.qualifiedNameOf(builder.target.node)!;
87+
var valueShortName = builderName.name;
88+
if (valueShortName.endsWith('Builder')) {
89+
valueShortName = valueShortName.substring(
90+
0,
91+
valueShortName.length - 'Builder'.length,
92+
);
93+
} else {
94+
throw StateError('Builder class should have name ending "Builder".');
95+
}
96+
final valueName = QualifiedName(uri: builderName.uri, name: valueShortName);
97+
98+
// TODO(davidmorgan): query for fields, actually build fields.
99+
builder.declareInType(
100+
augmentation('''
101+
void replace(${valueName.code} other) {}
102+
void update(void Function(${builderName.code}) updates) => updates(this);
103+
${valueName.code} build() => ${valueName.code}._();
104+
'''),
105+
);
106+
}
107+
}

pkgs/_test_macros/lib/templating.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ extension TemplatingExtension on QualifiedName {
99
String get code => '{{$uri#$name}}';
1010
}
1111

12+
Augmentation augmentation(String template) =>
13+
Augmentation(code: expandTemplate(template));
14+
1215
/// Converts [template] to a mix of `Identifier` and `String`.
1316
///
1417
/// References of the form `{{uri#name}}` become [QualifiedName] wrapped in

pkgs/_test_macros/pubspec.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ dev_dependencies:
1818
test: ^1.25.0
1919

2020
# TODO(language/3728): use the real feature when there is one.
21+
# macro lib/built_value.dart#BuiltValue package:_test_macros/built_value.dart#BuiltValueImplementation
22+
# macro lib/built_value.dart#BuiltValueBuilder package:_test_macros/built_value.dart#BuiltValueBuilderImplementation
2123
# macro lib/declare_x_macro.dart#DeclareX package:_test_macros/declare_x_macro.dart#DeclareXImplementation
2224
# macro lib/json_codable.dart#JsonCodable package:_test_macros/json_codable.dart#JsonCodableImplementation
2325
# macro lib/query_class.dart#QueryClass package:_test_macros/query_class.dart#QueryClassImplementation

tool/run_e2e_tests.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ cd $(dirname ${BASH_SOURCE[0]})
77

88
echo "Testing goldens/foo/*_test.dart..."
99

10-
for test_file in $(ls ../goldens/foo/lib/ | egrep '_test.dart$'); do
10+
for test_file in $(cd ../goldens/foo/lib/; find . -name \*_test.dart); do
1111
echo "Testing $test_file..."
1212
if dart run _macro_tool \
1313
--workspace=../goldens/foo \

0 commit comments

Comments
 (0)