support custom JSON keys for fields
Change-Id: I72c2a4f09b012c4100fe9286967d182831507160
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/350740
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Auto-Submit: Jake Macdonald <jakemac@google.com>
Reviewed-by: Morgan :) <davidmorgan@google.com>
Commit-Queue: Jake Macdonald <jakemac@google.com>
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/api/diagnostic.dart b/pkg/_fe_analyzer_shared/lib/src/macros/api/diagnostic.dart
index ec4d191..f520062 100644
--- a/pkg/_fe_analyzer_shared/lib/src/macros/api/diagnostic.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/macros/api/diagnostic.dart
@@ -77,6 +77,18 @@
new TypeAnnotationDiagnosticTarget(this);
}
+/// A [DiagnosticMessage] target which is a [MetadataAnnotation].
+final class MetadataAnnotationDiagnosticTarget extends DiagnosticTarget {
+ final MetadataAnnotation metadataAnnotation;
+
+ MetadataAnnotationDiagnosticTarget(this.metadataAnnotation);
+}
+
+extension MetadataAnnotationAsTarget on MetadataAnnotation {
+ MetadataAnnotationDiagnosticTarget get asDiagnosticTarget =>
+ new MetadataAnnotationDiagnosticTarget(this);
+}
+
/// The severities supported for [Diagnostic]s.
enum Severity {
/// Informational message only, for example a style guideline is not being
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor/serialization_extensions.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor/serialization_extensions.dart
index be984c8..ebb7b95 100644
--- a/pkg/_fe_analyzer_shared/lib/src/macros/executor/serialization_extensions.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/macros/executor/serialization_extensions.dart
@@ -654,6 +654,9 @@
(target.declaration as DeclarationImpl).serialize(serializer);
case TypeAnnotationDiagnosticTarget target:
(target.typeAnnotation as TypeAnnotationImpl).serialize(serializer);
+ case MetadataAnnotationDiagnosticTarget target:
+ (target.metadataAnnotation as MetadataAnnotationImpl)
+ .serialize(serializer);
}
}
}
diff --git a/pkg/analyzer/lib/src/generated/error_verifier.dart b/pkg/analyzer/lib/src/generated/error_verifier.dart
index 31eedcf..8de08c4 100644
--- a/pkg/analyzer/lib/src/generated/error_verifier.dart
+++ b/pkg/analyzer/lib/src/generated/error_verifier.dart
@@ -6091,6 +6091,9 @@
case TypeAnnotationMacroDiagnosticTarget():
// TODO(scheglov): Handle this case.
throw UnimplementedError();
+ case ElementAnnotationMacroDiagnosticTarget():
+ // TODO(scheglov): Handle this case.
+ throw UnimplementedError();
}
}
@@ -6184,6 +6187,9 @@
diagnostic.contextMessages.map(convertMessage).toList(),
);
}
+ case ElementAnnotationMacroDiagnosticTarget():
+ // TODO(scheglov): Handle this case.
+ throw UnimplementedError();
}
}
}
diff --git a/pkg/analyzer/lib/src/summary2/bundle_writer.dart b/pkg/analyzer/lib/src/summary2/bundle_writer.dart
index 32a8fe3..22f2d63 100644
--- a/pkg/analyzer/lib/src/summary2/bundle_writer.dart
+++ b/pkg/analyzer/lib/src/summary2/bundle_writer.dart
@@ -1021,6 +1021,9 @@
case TypeAnnotationMacroDiagnosticTarget():
writeEnum(MacroDiagnosticTargetKind.type);
writeTypeAnnotationLocation(target.location);
+ case ElementAnnotationMacroDiagnosticTarget():
+ // TODO(scheglov): Implement this
+ throw UnimplementedError('');
}
}
diff --git a/pkg/analyzer/lib/src/summary2/macro_application.dart b/pkg/analyzer/lib/src/summary2/macro_application.dart
index 170af8c..25191af 100644
--- a/pkg/analyzer/lib/src/summary2/macro_application.dart
+++ b/pkg/analyzer/lib/src/summary2/macro_application.dart
@@ -544,6 +544,10 @@
target = ElementMacroDiagnosticTarget(element: element);
case macro.TypeAnnotationDiagnosticTarget macroTarget:
target = _typeAnnotationTarget(application, macroTarget);
+ case macro.MetadataAnnotationDiagnosticTarget macroTarget:
+ target = ElementAnnotationMacroDiagnosticTarget(
+ annotation:
+ macroTarget.metadataAnnotation as ElementAnnotationImpl);
case null:
target = ApplicationMacroDiagnosticTarget(
annotationIndex: application.annotationIndex,
diff --git a/pkg/analyzer/lib/src/summary2/macro_application_error.dart b/pkg/analyzer/lib/src/summary2/macro_application_error.dart
index 522d3f2..b37821e 100644
--- a/pkg/analyzer/lib/src/summary2/macro_application_error.dart
+++ b/pkg/analyzer/lib/src/summary2/macro_application_error.dart
@@ -56,6 +56,15 @@
});
}
+final class ElementAnnotationMacroDiagnosticTarget
+ extends MacroDiagnosticTarget {
+ final ElementAnnotationImpl annotation;
+
+ ElementAnnotationMacroDiagnosticTarget({
+ required this.annotation,
+ });
+}
+
final class ElementMacroDiagnosticTarget extends MacroDiagnosticTarget {
final ElementImpl element;
diff --git a/pkg/analyzer/test/src/summary/element_text.dart b/pkg/analyzer/test/src/summary/element_text.dart
index 3de5ee9..bacdc98 100644
--- a/pkg/analyzer/test/src/summary/element_text.dart
+++ b/pkg/analyzer/test/src/summary/element_text.dart
@@ -830,6 +830,9 @@
_sink.withIndent(() {
writeTypeAnnotationLocation(target.location);
});
+ case ElementAnnotationMacroDiagnosticTarget():
+ // TODO(scheglov): Implement this
+ throw UnimplementedError();
}
}
diff --git a/tests/language/macros/augment/impl/class_declarations_macro.dart b/tests/language/macros/augment/impl/class_declarations_macro.dart
index 8837a1b..44a7ab2 100644
--- a/tests/language/macros/augment/impl/class_declarations_macro.dart
+++ b/tests/language/macros/augment/impl/class_declarations_macro.dart
@@ -16,7 +16,8 @@
}
}
-macro class ClassDeclarationsDeclareInLibrary implements ClassDeclarationsMacro {
+macro class ClassDeclarationsDeclareInLibrary
+ implements ClassDeclarationsMacro {
final String code;
const ClassDeclarationsDeclareInLibrary(this.code);
diff --git a/tests/language/macros/augment/impl/impl.dart b/tests/language/macros/augment/impl/impl.dart
index 7981e95..26b5d24 100644
--- a/tests/language/macros/augment/impl/impl.dart
+++ b/tests/language/macros/augment/impl/impl.dart
@@ -1,7 +1,9 @@
// Copyright (c) 2023, 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.
+//
// ignore_for_file: deprecated_member_use
+// ignore_for_file: deprecated_member_use_from_same_package
import 'package:_fe_analyzer_shared/src/macros/api.dart';
diff --git a/tests/language/macros/error/impl/throw_diagnostic_exception_macro.dart b/tests/language/macros/error/impl/throw_diagnostic_exception_macro.dart
index b0ebe00..14b79e7 100644
--- a/tests/language/macros/error/impl/throw_diagnostic_exception_macro.dart
+++ b/tests/language/macros/error/impl/throw_diagnostic_exception_macro.dart
@@ -8,14 +8,16 @@
final String atTypeDeclaration;
final String withMessage;
- const ThrowDiagnosticException({
- required this.atTypeDeclaration, required this.withMessage});
+ const ThrowDiagnosticException(
+ {required this.atTypeDeclaration, required this.withMessage});
Future<void> buildDeclarationsForClass(
ClassDeclaration clazz, MemberDeclarationBuilder builder) async {
- final identifier = await builder.resolveIdentifier(clazz.library.uri, atTypeDeclaration);
+ final identifier =
+ await builder.resolveIdentifier(clazz.library.uri, atTypeDeclaration);
final declaration = await builder.typeDeclarationOf(identifier);
- throw DiagnosticException(Diagnostic(DiagnosticMessage(
- withMessage, target: declaration.asDiagnosticTarget), Severity.error));
+ throw DiagnosticException(Diagnostic(
+ DiagnosticMessage(withMessage, target: declaration.asDiagnosticTarget),
+ Severity.error));
}
}
diff --git a/tests/language/macros/introspect/impl/assert_in_declarations_phase_macro.dart b/tests/language/macros/introspect/impl/assert_in_declarations_phase_macro.dart
index ba176e9..3ec4b07 100644
--- a/tests/language/macros/introspect/impl/assert_in_declarations_phase_macro.dart
+++ b/tests/language/macros/introspect/impl/assert_in_declarations_phase_macro.dart
@@ -1,7 +1,10 @@
// Copyright (c) 2023, 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.
+//
// ignore_for_file: deprecated_member_use
+// ignore_for_file: deprecated_member_use_from_same_package
+
import 'dart:async';
import 'package:_fe_analyzer_shared/src/macros/api.dart';
diff --git a/tests/language/macros/introspect/impl/assert_in_definitions_phase_macro.dart b/tests/language/macros/introspect/impl/assert_in_definitions_phase_macro.dart
index ba63772..56972dc 100644
--- a/tests/language/macros/introspect/impl/assert_in_definitions_phase_macro.dart
+++ b/tests/language/macros/introspect/impl/assert_in_definitions_phase_macro.dart
@@ -1,7 +1,10 @@
// Copyright (c) 2023, 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.
+//
// ignore_for_file: deprecated_member_use
+// ignore_for_file: deprecated_member_use_from_same_package
+
import 'dart:async';
import 'package:_fe_analyzer_shared/src/macros/api.dart';
diff --git a/tests/language/macros/introspect/impl/assert_in_types_phase_macro.dart b/tests/language/macros/introspect/impl/assert_in_types_phase_macro.dart
index 2bdb5c5..1a96cb5 100644
--- a/tests/language/macros/introspect/impl/assert_in_types_phase_macro.dart
+++ b/tests/language/macros/introspect/impl/assert_in_types_phase_macro.dart
@@ -1,7 +1,10 @@
// Copyright (c) 2023, 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.
+//
// ignore_for_file: deprecated_member_use
+// ignore_for_file: deprecated_member_use_from_same_package
+
import 'dart:async';
import 'package:_fe_analyzer_shared/src/macros/api.dart';
diff --git a/tests/language/macros/json/json_key.dart b/tests/language/macros/json/json_key.dart
new file mode 100644
index 0000000..d73dd7b
--- /dev/null
+++ b/tests/language/macros/json/json_key.dart
@@ -0,0 +1,17 @@
+// 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:meta/meta_meta.dart';
+
+/// An annotation used to specify how a field is serialized.
+@Target({TargetKind.field, TargetKind.getter})
+class JsonKey {
+ /// The key in a JSON map to use when reading and writing values corresponding
+ /// to the annotated fields.
+ ///
+ /// If `null`, the field name is used.
+ final String? name;
+
+ const JsonKey({this.name});
+}
diff --git a/tests/language/macros/json/json_serializable.dart b/tests/language/macros/json/json_serializable.dart
index 48ad15d..0f0d7bd 100644
--- a/tests/language/macros/json/json_serializable.dart
+++ b/tests/language/macros/json/json_serializable.dart
@@ -35,10 +35,7 @@
var mapStringObject = NamedTypeAnnotationCode(
name: map, typeArguments: [string, object.asNullable]);
- // TODO: This only works because the macro file lives right next to the file
- // it is applied to.
- var jsonSerializableUri =
- clazz.library.uri.resolve('json_serializable.dart');
+ var jsonSerializableUri = clazz.jsonSerializableUri;
builder.declareInType(DeclarationCode.fromParts([
' @',
@@ -119,7 +116,9 @@
field.type,
RawCode.fromParts([
jsonParam,
- '["${field.identifier.name}"]',
+ '[',
+ await field._jsonKeyName(builder),
+ ']',
]),
builder,
fromJsonData),
@@ -253,6 +252,31 @@
}
}
+extension _ on FieldDeclaration {
+ // TODO: Support `IdentifierMetadataAnnotation`s once we can do constant eval.
+ Future<Code> _jsonKeyName(DefinitionBuilder builder) async {
+ ConstructorMetadataAnnotation? jsonKey;
+ for (var annotation in metadata) {
+ if (annotation is! ConstructorMetadataAnnotation) continue;
+ if (annotation.type.name != 'JsonKey') continue;
+ var declaration = await builder.typeDeclarationOf(annotation.type);
+ if (declaration.library.uri != jsonKeyUri) continue;
+
+ if (jsonKey != null) {
+ builder.report(Diagnostic(
+ DiagnosticMessage(
+ 'Only one JsonKey annotation is allowed.',
+ target: annotation.asDiagnosticTarget),
+ Severity.error));
+ } else {
+ jsonKey = annotation;
+ }
+ }
+ return jsonKey?.namedArguments['name'] ??
+ RawCode.fromString('\'${identifier.name}\'');
+ }
+}
+
final class _FromJsonData {
final NamedTypeAnnotationCode jsonListCode;
final NamedTypeAnnotationCode jsonMapCode;
@@ -368,9 +392,8 @@
if (superclassHasToJson) '\n ...super.toJson(),',
for (var field in fields)
RawCode.fromParts([
- '\n \'',
- field.identifier.name,
- '\'',
+ '\n ',
+ await field._jsonKeyName(builder),
': ',
await _convertTypeToJson(field.type,
RawCode.fromParts([field.identifier]), builder, toJsonData),
@@ -561,3 +584,11 @@
}
}
}
+
+// TODO: These only work because the macro file lives right next to the file
+// it is applied to, we need a better solution at some point.
+extension _RelativeUris on Declaration {
+ Uri get jsonKeyUri => library.uri.resolve('json_key.dart');
+
+ Uri get jsonSerializableUri => library.uri.resolve('json_serializable.dart');
+}
diff --git a/tests/language/macros/json/json_serializable_test.dart b/tests/language/macros/json/json_serializable_test.dart
index 7a20464..15c46cd 100644
--- a/tests/language/macros/json/json_serializable_test.dart
+++ b/tests/language/macros/json/json_serializable_test.dart
@@ -6,6 +6,7 @@
import 'package:expect/expect.dart';
+import 'json_key.dart';
import 'json_serializable.dart';
void main() {
@@ -33,12 +34,12 @@
var rogerAccountJson = {
...rogerJson,
'login': {
- 'username': 'roger1',
+ 'account': 'roger1',
'password': 'theGoat',
},
};
(rogerAccountJson['friends'] as dynamic)[0]['login'] = {
- 'username': 'felixTheCat',
+ 'account': 'felixTheCat',
'password': '9Lives',
};
var rogerAccount = UserAccount.fromJson(rogerAccountJson);
@@ -63,6 +64,8 @@
@JsonSerializable()
class Login {
+ @JsonKey(name: 'account')
final String username;
+
final String password;
}