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

Commit 98bd197

Browse files
authored
Add a first JSON macro based on package:json/json.dart. (#68)
* Add a first JSON macro based on package:json/json.dart. * Address review comments. * Dartfmt.
1 parent 8903e77 commit 98bd197

File tree

12 files changed

+474
-89
lines changed

12 files changed

+474
-89
lines changed

goldens/foo/lib/foo.analyzer.json

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,16 @@
1111
"isField": true,
1212
"isMethod": false,
1313
"isStatic": false
14+
},
15+
"returnType": {
16+
"type": "NamedTypeDesc",
17+
"value": {
18+
"name": {
19+
"uri": "dart:core",
20+
"name": "int"
21+
},
22+
"instantiation": []
23+
}
1424
}
1525
}
1626
}
@@ -24,6 +34,16 @@
2434
"isField": true,
2535
"isMethod": false,
2636
"isStatic": false
37+
},
38+
"returnType": {
39+
"type": "NamedTypeDesc",
40+
"value": {
41+
"name": {
42+
"uri": "dart:core",
43+
"name": "int"
44+
},
45+
"instantiation": []
46+
}
2747
}
2848
}
2949
}
@@ -33,25 +53,6 @@
3353
},
3454
"types": {
3555
"named": {
36-
"package:foo/foo.dart#Foo": {
37-
"typeParameters": [],
38-
"self": {
39-
"name": {
40-
"uri": "package:foo/foo.dart",
41-
"name": "Foo"
42-
},
43-
"instantiation": []
44-
},
45-
"supertypes": [
46-
{
47-
"name": {
48-
"uri": "dart:core",
49-
"name": "Object"
50-
},
51-
"instantiation": []
52-
}
53-
]
54-
},
5556
"dart:core#Object": {
5657
"typeParameters": [],
5758
"self": {
@@ -112,6 +113,25 @@
112113
}
113114
]
114115
},
116+
"package:foo/foo.dart#Foo": {
117+
"typeParameters": [],
118+
"self": {
119+
"name": {
120+
"uri": "package:foo/foo.dart",
121+
"name": "Foo"
122+
},
123+
"instantiation": []
124+
},
125+
"supertypes": [
126+
{
127+
"name": {
128+
"uri": "dart:core",
129+
"name": "Object"
130+
},
131+
"instantiation": []
132+
}
133+
]
134+
},
115135
"package:foo/foo.dart#Bar": {
116136
"typeParameters": [],
117137
"self": {
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
part of 'package:foo/json_codable.dart';
2+
3+
import 'package:foo/json_codable.dart' as prefix0;
4+
import 'dart:core' as prefix1;
5+
6+
augment class A {
7+
external prefix0.A.fromJson(prefix1.Map<prefix1.String, prefix1.Object?> json);
8+
external prefix1.Map<prefix1.String, prefix1.Object?> toJson();
9+
10+
augment prefix0.A.fromJson(prefix1.Map<prefix1.String, prefix1.Object?> json) :
11+
boolField = json[r'boolField'] as prefix1.bool,
12+
nullableBoolField = json[r'nullableBoolField'] as prefix1.bool?,
13+
stringField = json[r'stringField'] as prefix1.String,
14+
nullableStringField = json[r'nullableStringField'] as prefix1.String?,
15+
intField = json[r'intField'] as prefix1.int,
16+
nullableIntField = json[r'nullableIntField'] as prefix1.int?,
17+
doubleField = json[r'doubleField'] as prefix1.double,
18+
nullableDoubleField = json[r'nullableDoubleField'] as prefix1.double?,
19+
numField = json[r'numField'] as prefix1.num,
20+
nullableNumField = json[r'nullableNumField'] as prefix1.num?,
21+
listOfSerializableField = json[r'listOfSerializableField'] == null ? null : [for (final item in json[r'listOfSerializableField'] as prefix1.List<prefix1.Object?>) item == null ? null : prefix0.C.fromJson(item as prefix1.Map<prefix1.String, prefix1.Object?>],
22+
nullableListOfSerializableField = [for (final item in json[r'nullableListOfSerializableField'] as prefix1.List<prefix1.Object?>) item == null ? null : prefix0.C.fromJson(item as prefix1.Map<prefix1.String, prefix1.Object?>],
23+
setOfSerializableField = json[r'setOfSerializableField'] == null ? null : {for (final item in json[r'setOfSerializableField'] as prefix1.Set<prefix1.Object?>) item == null ? null : prefix0.C.fromJson(item as prefix1.Map<prefix1.String, prefix1.Object?>},
24+
nullableSetOfSerializableField = {for (final item in json[r'nullableSetOfSerializableField'] as prefix1.Set<prefix1.Object?>) item == null ? null : prefix0.C.fromJson(item as prefix1.Map<prefix1.String, prefix1.Object?>},
25+
mapOfSerializableField = json[r'mapOfSerializableField'] == null ? null : {for (final (:key, :value) in json[r'mapOfSerializableField'] as prefix1.Map<prefix1.String, prefix1.Object?>) key: value == null ? null : prefix0.C.fromJson(value as prefix1.Map<prefix1.String, prefix1.Object?>},
26+
nullableMapOfSerializableField = {for (final (:key, :value) in json[r'nullableMapOfSerializableField'] as prefix1.Map<prefix1.String, prefix1.Object?>) key: value == null ? null : prefix0.C.fromJson(value as prefix1.Map<prefix1.String, prefix1.Object?>};
27+
28+
prefix1.Map<prefix1.String, prefix1.Object?> toJson() {
29+
final json = prefix1.Map<prefix1.String, prefix1.Object?>{};
30+
json[r'boolField'] = boolField;
31+
json[r'nullableBoolField'] = nullableBoolField;
32+
json[r'stringField'] = stringField;
33+
json[r'nullableStringField'] = nullableStringField;
34+
json[r'intField'] = intField;
35+
json[r'nullableIntField'] = nullableIntField;
36+
json[r'doubleField'] = doubleField;
37+
json[r'nullableDoubleField'] = nullableDoubleField;
38+
json[r'numField'] = numField;
39+
json[r'nullableNumField'] = nullableNumField;
40+
json[r'listOfSerializableField'] = listOfSerializableField == null ? null : [for (final item in listOfSerializableField) item == null ? null : item.toJson()];
41+
json[r'nullableListOfSerializableField'] = [for (final item in nullableListOfSerializableField) item == null ? null : item.toJson()];
42+
json[r'setOfSerializableField'] = setOfSerializableField == null ? null : [for (final item in setOfSerializableField) item == null ? null : item.toJson()];
43+
json[r'nullableSetOfSerializableField'] = [for (final item in nullableSetOfSerializableField) item == null ? null : item.toJson()];
44+
json[r'mapOfSerializableField'] = mapOfSerializableField == null ? null : {for (final (:key, :value) in mapOfSerializableField.entries) key: value == null ? null : value.toJson()};
45+
json[r'nullableMapOfSerializableField'] = {for (final (:key, :value) in nullableMapOfSerializableField.entries) key: value == null ? null : value.toJson()};
46+
47+
return json;
48+
};
49+
50+
}
51+
augment class C {
52+
external prefix0.C.fromJson(prefix1.Map<prefix1.String, prefix1.Object?> json);
53+
external prefix1.Map<prefix1.String, prefix1.Object?> toJson();
54+
55+
augment prefix0.C.fromJson(prefix1.Map<prefix1.String, prefix1.Object?> json) :
56+
boolField = json[r'boolField'] as prefix1.bool;
57+
58+
prefix1.Map<prefix1.String, prefix1.Object?> toJson() {
59+
final json = prefix1.Map<prefix1.String, prefix1.Object?>{};
60+
json[r'boolField'] = boolField;
61+
62+
return json;
63+
};
64+
65+
}

goldens/foo/lib/json_codable.dart

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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/json_codable.dart';
6+
7+
@JsonCodable()
8+
class A {
9+
final bool boolField;
10+
11+
final bool? nullableBoolField;
12+
13+
final String stringField;
14+
15+
final String? nullableStringField;
16+
17+
final int intField;
18+
19+
final int? nullableIntField;
20+
21+
final double doubleField;
22+
23+
final double? nullableDoubleField;
24+
25+
final num numField;
26+
27+
final num? nullableNumField;
28+
29+
final List<C> listOfSerializableField;
30+
31+
final List<C>? nullableListOfSerializableField;
32+
33+
final Set<C> setOfSerializableField;
34+
35+
final Set<C>? nullableSetOfSerializableField;
36+
37+
final Map<String, C> mapOfSerializableField;
38+
39+
final Map<String, C>? nullableMapOfSerializableField;
40+
}
41+
42+
@JsonCodable()
43+
class C {
44+
final bool boolField;
45+
}

pkgs/_analyzer_macros/lib/macro_implementation.dart

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ class AnalyzerRunningMacro implements injected.RunningMacro {
8787
DeclarationPhaseIntrospector declarationsPhaseIntrospector) async {
8888
// TODO(davidmorgan): this is a hack to access analyzer internals; remove.
8989
introspector = declarationsPhaseIntrospector;
90-
return AnalyzerMacroExecutionResult(
90+
return await AnalyzerMacroExecutionResult.expandTemplates(
9191
target,
9292
await _impl._host.augment(
9393
name, AugmentRequest(phase: 2, target: target.qualifiedName)));
@@ -99,7 +99,7 @@ class AnalyzerRunningMacro implements injected.RunningMacro {
9999
DefinitionPhaseIntrospector definitionPhaseIntrospector) async {
100100
// TODO(davidmorgan): this is a hack to access analyzer internals; remove.
101101
introspector = definitionPhaseIntrospector;
102-
return AnalyzerMacroExecutionResult(
102+
return await AnalyzerMacroExecutionResult.expandTemplates(
103103
target,
104104
await _impl._host.augment(
105105
name, AugmentRequest(phase: 3, target: target.qualifiedName)));
@@ -110,7 +110,7 @@ class AnalyzerRunningMacro implements injected.RunningMacro {
110110
MacroTarget target, TypePhaseIntrospector typePhaseIntrospector) async {
111111
// TODO(davidmorgan): this is a hack to access analyzer internals; remove.
112112
introspector = typePhaseIntrospector;
113-
return AnalyzerMacroExecutionResult(
113+
return await AnalyzerMacroExecutionResult.expandTemplates(
114114
target,
115115
await _impl._host.augment(
116116
name, AugmentRequest(phase: 1, target: target.qualifiedName)));
@@ -123,9 +123,25 @@ class AnalyzerRunningMacro implements injected.RunningMacro {
123123
/// functionality of `MacroExecutionResult`.
124124
class AnalyzerMacroExecutionResult implements injected.MacroExecutionResult {
125125
final MacroTarget target;
126-
final AugmentResponse augmentResponse;
127-
128-
AnalyzerMacroExecutionResult(this.target, this.augmentResponse);
126+
@override
127+
final Map<Identifier, Iterable<DeclarationCode>> typeAugmentations;
128+
129+
AnalyzerMacroExecutionResult(
130+
this.target, Iterable<DeclarationCode> declarations)
131+
// TODO(davidmorgan): this assumes augmentations are for the macro
132+
// application target. Instead, it should be explicit in
133+
// `AugmentResponse`.
134+
: typeAugmentations = {(target as Declaration).identifier: declarations};
135+
136+
static Future<AnalyzerMacroExecutionResult> expandTemplates(
137+
MacroTarget target, AugmentResponse augmentResponse) async {
138+
final declarations = <DeclarationCode>[];
139+
for (final augmentation in augmentResponse.augmentations) {
140+
declarations.add(
141+
DeclarationCode.fromParts(await _expandTemplates(augmentation.code)));
142+
}
143+
return AnalyzerMacroExecutionResult(target, declarations);
144+
}
129145

130146
@override
131147
List<Diagnostic> get diagnostics => [];
@@ -154,16 +170,6 @@ class AnalyzerMacroExecutionResult implements injected.MacroExecutionResult {
154170

155171
@override
156172
void serialize(Object serializer) => throw UnimplementedError();
157-
158-
@override
159-
Map<Identifier, Iterable<DeclarationCode>> get typeAugmentations => {
160-
// TODO(davidmorgan): this assumes augmentations are for the macro
161-
// application target. Instead, it should be explicit in
162-
// `AugmentResponse`.
163-
(target as Declaration).identifier: augmentResponse.augmentations
164-
.map((a) => DeclarationCode.fromParts([a.code]))
165-
.toList(),
166-
};
167173
}
168174

169175
extension MacroTargetExtension on MacroTarget {
@@ -175,3 +181,37 @@ extension MacroTargetExtension on MacroTarget {
175181
name: element.displayName);
176182
}
177183
}
184+
185+
/// Converts [code] to a mix of `Identifier` and `String`.
186+
///
187+
/// Looks up references of the form `{{uri#name}}` using `resolveIdentifier`.
188+
///
189+
/// TODO(davidmorgan): move to the client side.
190+
Future<List<Object>> _expandTemplates(String code) async {
191+
final result = <Object>[];
192+
var index = 0;
193+
while (index < code.length) {
194+
final start = code.indexOf('{{', index);
195+
if (start == -1) {
196+
result.add(code.substring(index));
197+
break;
198+
}
199+
result.add(code.substring(index, start));
200+
final end = code.indexOf('}}', start);
201+
if (end == -1) {
202+
throw ArgumentError('Unmatched opening brace: $code');
203+
}
204+
final name = code.substring(start + 2, end);
205+
final parts = name.split('#');
206+
if (parts.length != 2) {
207+
throw ArgumentError('Expected "uri#name" in: $name');
208+
}
209+
final uri = Uri.parse(parts[0]);
210+
final identifier = await (introspector as TypePhaseIntrospector)
211+
// ignore: deprecated_member_use
212+
.resolveIdentifier(uri, parts[1]);
213+
result.add(identifier);
214+
index = end + 2;
215+
}
216+
return result;
217+
}

0 commit comments

Comments
 (0)