Skip to content

Commit

Permalink
Add a first JSON macro based on package:json/json.dart. (#68)
Browse files Browse the repository at this point in the history
* Add a first JSON macro based on package:json/json.dart.

* Address review comments.

* Dartfmt.
  • Loading branch information
davidmorgan authored Sep 27, 2024
1 parent 8903e77 commit 98bd197
Show file tree
Hide file tree
Showing 12 changed files with 474 additions and 89 deletions.
58 changes: 39 additions & 19 deletions goldens/foo/lib/foo.analyzer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@
"isField": true,
"isMethod": false,
"isStatic": false
},
"returnType": {
"type": "NamedTypeDesc",
"value": {
"name": {
"uri": "dart:core",
"name": "int"
},
"instantiation": []
}
}
}
}
Expand All @@ -24,6 +34,16 @@
"isField": true,
"isMethod": false,
"isStatic": false
},
"returnType": {
"type": "NamedTypeDesc",
"value": {
"name": {
"uri": "dart:core",
"name": "int"
},
"instantiation": []
}
}
}
}
Expand All @@ -33,25 +53,6 @@
},
"types": {
"named": {
"package:foo/foo.dart#Foo": {
"typeParameters": [],
"self": {
"name": {
"uri": "package:foo/foo.dart",
"name": "Foo"
},
"instantiation": []
},
"supertypes": [
{
"name": {
"uri": "dart:core",
"name": "Object"
},
"instantiation": []
}
]
},
"dart:core#Object": {
"typeParameters": [],
"self": {
Expand Down Expand Up @@ -112,6 +113,25 @@
}
]
},
"package:foo/foo.dart#Foo": {
"typeParameters": [],
"self": {
"name": {
"uri": "package:foo/foo.dart",
"name": "Foo"
},
"instantiation": []
},
"supertypes": [
{
"name": {
"uri": "dart:core",
"name": "Object"
},
"instantiation": []
}
]
},
"package:foo/foo.dart#Bar": {
"typeParameters": [],
"self": {
Expand Down
65 changes: 65 additions & 0 deletions goldens/foo/lib/json_codable.analyzer.augmentations
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
part of 'package:foo/json_codable.dart';

import 'package:foo/json_codable.dart' as prefix0;
import 'dart:core' as prefix1;

augment class A {
external prefix0.A.fromJson(prefix1.Map<prefix1.String, prefix1.Object?> json);
external prefix1.Map<prefix1.String, prefix1.Object?> toJson();

augment prefix0.A.fromJson(prefix1.Map<prefix1.String, prefix1.Object?> json) :
boolField = json[r'boolField'] as prefix1.bool,
nullableBoolField = json[r'nullableBoolField'] as prefix1.bool?,
stringField = json[r'stringField'] as prefix1.String,
nullableStringField = json[r'nullableStringField'] as prefix1.String?,
intField = json[r'intField'] as prefix1.int,
nullableIntField = json[r'nullableIntField'] as prefix1.int?,
doubleField = json[r'doubleField'] as prefix1.double,
nullableDoubleField = json[r'nullableDoubleField'] as prefix1.double?,
numField = json[r'numField'] as prefix1.num,
nullableNumField = json[r'nullableNumField'] as prefix1.num?,
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?>],
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?>],
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?>},
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?>},
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?>},
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?>};

prefix1.Map<prefix1.String, prefix1.Object?> toJson() {
final json = prefix1.Map<prefix1.String, prefix1.Object?>{};
json[r'boolField'] = boolField;
json[r'nullableBoolField'] = nullableBoolField;
json[r'stringField'] = stringField;
json[r'nullableStringField'] = nullableStringField;
json[r'intField'] = intField;
json[r'nullableIntField'] = nullableIntField;
json[r'doubleField'] = doubleField;
json[r'nullableDoubleField'] = nullableDoubleField;
json[r'numField'] = numField;
json[r'nullableNumField'] = nullableNumField;
json[r'listOfSerializableField'] = listOfSerializableField == null ? null : [for (final item in listOfSerializableField) item == null ? null : item.toJson()];
json[r'nullableListOfSerializableField'] = [for (final item in nullableListOfSerializableField) item == null ? null : item.toJson()];
json[r'setOfSerializableField'] = setOfSerializableField == null ? null : [for (final item in setOfSerializableField) item == null ? null : item.toJson()];
json[r'nullableSetOfSerializableField'] = [for (final item in nullableSetOfSerializableField) item == null ? null : item.toJson()];
json[r'mapOfSerializableField'] = mapOfSerializableField == null ? null : {for (final (:key, :value) in mapOfSerializableField.entries) key: value == null ? null : value.toJson()};
json[r'nullableMapOfSerializableField'] = {for (final (:key, :value) in nullableMapOfSerializableField.entries) key: value == null ? null : value.toJson()};

return json;
};

}
augment class C {
external prefix0.C.fromJson(prefix1.Map<prefix1.String, prefix1.Object?> json);
external prefix1.Map<prefix1.String, prefix1.Object?> toJson();

augment prefix0.C.fromJson(prefix1.Map<prefix1.String, prefix1.Object?> json) :
boolField = json[r'boolField'] as prefix1.bool;

prefix1.Map<prefix1.String, prefix1.Object?> toJson() {
final json = prefix1.Map<prefix1.String, prefix1.Object?>{};
json[r'boolField'] = boolField;

return json;
};

}
45 changes: 45 additions & 0 deletions goldens/foo/lib/json_codable.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:_test_macros/json_codable.dart';

@JsonCodable()
class A {
final bool boolField;

final bool? nullableBoolField;

final String stringField;

final String? nullableStringField;

final int intField;

final int? nullableIntField;

final double doubleField;

final double? nullableDoubleField;

final num numField;

final num? nullableNumField;

final List<C> listOfSerializableField;

final List<C>? nullableListOfSerializableField;

final Set<C> setOfSerializableField;

final Set<C>? nullableSetOfSerializableField;

final Map<String, C> mapOfSerializableField;

final Map<String, C>? nullableMapOfSerializableField;
}

@JsonCodable()
class C {
final bool boolField;
}
72 changes: 56 additions & 16 deletions pkgs/_analyzer_macros/lib/macro_implementation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ class AnalyzerRunningMacro implements injected.RunningMacro {
DeclarationPhaseIntrospector declarationsPhaseIntrospector) async {
// TODO(davidmorgan): this is a hack to access analyzer internals; remove.
introspector = declarationsPhaseIntrospector;
return AnalyzerMacroExecutionResult(
return await AnalyzerMacroExecutionResult.expandTemplates(
target,
await _impl._host.augment(
name, AugmentRequest(phase: 2, target: target.qualifiedName)));
Expand All @@ -99,7 +99,7 @@ class AnalyzerRunningMacro implements injected.RunningMacro {
DefinitionPhaseIntrospector definitionPhaseIntrospector) async {
// TODO(davidmorgan): this is a hack to access analyzer internals; remove.
introspector = definitionPhaseIntrospector;
return AnalyzerMacroExecutionResult(
return await AnalyzerMacroExecutionResult.expandTemplates(
target,
await _impl._host.augment(
name, AugmentRequest(phase: 3, target: target.qualifiedName)));
Expand All @@ -110,7 +110,7 @@ class AnalyzerRunningMacro implements injected.RunningMacro {
MacroTarget target, TypePhaseIntrospector typePhaseIntrospector) async {
// TODO(davidmorgan): this is a hack to access analyzer internals; remove.
introspector = typePhaseIntrospector;
return AnalyzerMacroExecutionResult(
return await AnalyzerMacroExecutionResult.expandTemplates(
target,
await _impl._host.augment(
name, AugmentRequest(phase: 1, target: target.qualifiedName)));
Expand All @@ -123,9 +123,25 @@ class AnalyzerRunningMacro implements injected.RunningMacro {
/// functionality of `MacroExecutionResult`.
class AnalyzerMacroExecutionResult implements injected.MacroExecutionResult {
final MacroTarget target;
final AugmentResponse augmentResponse;

AnalyzerMacroExecutionResult(this.target, this.augmentResponse);
@override
final Map<Identifier, Iterable<DeclarationCode>> typeAugmentations;

AnalyzerMacroExecutionResult(
this.target, Iterable<DeclarationCode> declarations)
// TODO(davidmorgan): this assumes augmentations are for the macro
// application target. Instead, it should be explicit in
// `AugmentResponse`.
: typeAugmentations = {(target as Declaration).identifier: declarations};

static Future<AnalyzerMacroExecutionResult> expandTemplates(
MacroTarget target, AugmentResponse augmentResponse) async {
final declarations = <DeclarationCode>[];
for (final augmentation in augmentResponse.augmentations) {
declarations.add(
DeclarationCode.fromParts(await _expandTemplates(augmentation.code)));
}
return AnalyzerMacroExecutionResult(target, declarations);
}

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

@override
void serialize(Object serializer) => throw UnimplementedError();

@override
Map<Identifier, Iterable<DeclarationCode>> get typeAugmentations => {
// TODO(davidmorgan): this assumes augmentations are for the macro
// application target. Instead, it should be explicit in
// `AugmentResponse`.
(target as Declaration).identifier: augmentResponse.augmentations
.map((a) => DeclarationCode.fromParts([a.code]))
.toList(),
};
}

extension MacroTargetExtension on MacroTarget {
Expand All @@ -175,3 +181,37 @@ extension MacroTargetExtension on MacroTarget {
name: element.displayName);
}
}

/// Converts [code] to a mix of `Identifier` and `String`.
///
/// Looks up references of the form `{{uri#name}}` using `resolveIdentifier`.
///
/// TODO(davidmorgan): move to the client side.
Future<List<Object>> _expandTemplates(String code) async {
final result = <Object>[];
var index = 0;
while (index < code.length) {
final start = code.indexOf('{{', index);
if (start == -1) {
result.add(code.substring(index));
break;
}
result.add(code.substring(index, start));
final end = code.indexOf('}}', start);
if (end == -1) {
throw ArgumentError('Unmatched opening brace: $code');
}
final name = code.substring(start + 2, end);
final parts = name.split('#');
if (parts.length != 2) {
throw ArgumentError('Expected "uri#name" in: $name');
}
final uri = Uri.parse(parts[0]);
final identifier = await (introspector as TypePhaseIntrospector)
// ignore: deprecated_member_use
.resolveIdentifier(uri, parts[1]);
result.add(identifier);
index = end + 2;
}
return result;
}
Loading

0 comments on commit 98bd197

Please sign in to comment.