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

Commit f335ef9

Browse files
committed
Add a first JSON macro based on package:json/json.dart.
1 parent 5c53968 commit f335ef9

File tree

10 files changed

+443
-80
lines changed

10 files changed

+443
-80
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: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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+
json[r'boolField'] = boolField;
30+
json[r'nullableBoolField'] = nullableBoolField;
31+
json[r'stringField'] = stringField;
32+
json[r'nullableStringField'] = nullableStringField;
33+
json[r'intField'] = intField;
34+
json[r'nullableIntField'] = nullableIntField;
35+
json[r'doubleField'] = doubleField;
36+
json[r'nullableDoubleField'] = nullableDoubleField;
37+
json[r'numField'] = numField;
38+
json[r'nullableNumField'] = nullableNumField;
39+
json[r'listOfSerializableField'] = listOfSerializableField == null ? null : {for (final item in listOfSerializableField) item == null ? null : item.toJson()};
40+
json[r'nullableListOfSerializableField'] = {for (final item in nullableListOfSerializableField) item == null ? null : item.toJson()};
41+
json[r'setOfSerializableField'] = setOfSerializableField == null ? null : {for (final item in setOfSerializableField) item == null ? null : item.toJson()};
42+
json[r'nullableSetOfSerializableField'] = {for (final item in nullableSetOfSerializableField) item == null ? null : item.toJson()};
43+
json[r'mapOfSerializableField'] = mapOfSerializableField == null ? null : {for (final (:key, :value) in mapOfSerializableField.entries) key: value == null ? null : value.toJson()};
44+
json[r'nullableMapOfSerializableField'] = {for (final (:key, :value) in nullableMapOfSerializableField.entries) key: value == null ? null : value.toJson()}
45+
};
46+
47+
}
48+
augment class C {
49+
external prefix0.C.fromJson(prefix1.Map<prefix1.String, prefix1.Object?> json);
50+
external prefix1.Map<prefix1.String, prefix1.Object?> toJson();
51+
52+
augment prefix0.C.fromJson(prefix1.Map<prefix1.String, prefix1.Object?> json) :
53+
boolField = json[r'boolField'] as prefix1.bool;
54+
55+
prefix1.Map<prefix1.String, prefix1.Object?> toJson() {
56+
json[r'boolField'] = boolField
57+
};
58+
59+
}

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: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ class AnalyzerRunningMacro implements injected.RunningMacro {
8888
DeclarationPhaseIntrospector declarationsPhaseIntrospector) async {
8989
// TODO(davidmorgan): this is a hack to access analyzer internals; remove.
9090
introspector = declarationsPhaseIntrospector;
91-
return AnalyzerMacroExecutionResult(
91+
return await AnalyzerMacroExecutionResult.expandTemplates(
9292
target,
9393
await _impl._host.augment(
9494
name, AugmentRequest(phase: 2, target: target.qualifiedName)));
@@ -100,7 +100,7 @@ class AnalyzerRunningMacro implements injected.RunningMacro {
100100
DefinitionPhaseIntrospector definitionPhaseIntrospector) async {
101101
// TODO(davidmorgan): this is a hack to access analyzer internals; remove.
102102
introspector = definitionPhaseIntrospector;
103-
return AnalyzerMacroExecutionResult(
103+
return await AnalyzerMacroExecutionResult.expandTemplates(
104104
target,
105105
await _impl._host.augment(
106106
name, AugmentRequest(phase: 3, target: target.qualifiedName)));
@@ -111,7 +111,7 @@ class AnalyzerRunningMacro implements injected.RunningMacro {
111111
MacroTarget target, TypePhaseIntrospector typePhaseIntrospector) async {
112112
// TODO(davidmorgan): this is a hack to access analyzer internals; remove.
113113
introspector = typePhaseIntrospector;
114-
return AnalyzerMacroExecutionResult(
114+
return await AnalyzerMacroExecutionResult.expandTemplates(
115115
target,
116116
await _impl._host.augment(
117117
name, AugmentRequest(phase: 1, target: target.qualifiedName)));
@@ -124,9 +124,27 @@ class AnalyzerRunningMacro implements injected.RunningMacro {
124124
/// functionality of `MacroExecutionResult`.
125125
class AnalyzerMacroExecutionResult implements injected.MacroExecutionResult {
126126
final MacroTarget target;
127-
final AugmentResponse augmentResponse;
127+
@override
128+
final Map<Identifier, Iterable<DeclarationCode>> typeAugmentations;
129+
130+
AnalyzerMacroExecutionResult(
131+
this.target, Iterable<DeclarationCode> declarations)
132+
// TODO(davidmorgan): this assumes augmentations are for the macro
133+
// application target. Instead, it should be explicit in
134+
// `AugmentResponse`.
135+
: typeAugmentations = {(target as Declaration).identifier: declarations};
136+
137+
static Future<AnalyzerMacroExecutionResult> expandTemplates(
138+
MacroTarget target, AugmentResponse augmentResponse) async {
139+
final declarations = <DeclarationCode>[];
140+
for (final augmentation in augmentResponse.augmentations) {
141+
declarations.add(
142+
DeclarationCode.fromParts(await _expandTemplates(augmentation.code)));
143+
}
144+
return AnalyzerMacroExecutionResult(target, declarations);
145+
}
128146

129-
AnalyzerMacroExecutionResult(this.target, this.augmentResponse);
147+
Future<void> resolveTypes() async {}
130148

131149
@override
132150
List<Diagnostic> get diagnostics => [];
@@ -155,16 +173,6 @@ class AnalyzerMacroExecutionResult implements injected.MacroExecutionResult {
155173

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

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

0 commit comments

Comments
 (0)