From f1f5e96644c4db432086b8dc56a267883ff506c7 Mon Sep 17 00:00:00 2001 From: Kilian Schulte Date: Sat, 19 Oct 2024 18:43:46 +0200 Subject: [PATCH] enable reusing annotation constants --- .../test/custom_mapper/annotation_test.dart | 50 ++++ .../custom_mapper/annotation_test.mapper.dart | 228 ++++++++++++++++++ .../initializer/init_package_test.init.dart | 1 + .../elements/class/class_mapper_element.dart | 2 +- .../mixins/inherited_elements_mixin.dart | 2 +- .../class/target_class_mapper_element.dart | 6 +- .../lib/src/elements/mapper_element.dart | 14 +- .../param/record_mapper_param_element.dart | 2 +- .../record/alias_record_mapper_element.dart | 6 +- .../dart_mappable_builder/lib/src/utils.dart | 55 +++-- 10 files changed, 330 insertions(+), 36 deletions(-) create mode 100644 packages/dart_mappable/test/custom_mapper/annotation_test.mapper.dart diff --git a/packages/dart_mappable/test/custom_mapper/annotation_test.dart b/packages/dart_mappable/test/custom_mapper/annotation_test.dart index e69de29b..4e3167e3 100644 --- a/packages/dart_mappable/test/custom_mapper/annotation_test.dart +++ b/packages/dart_mappable/test/custom_mapper/annotation_test.dart @@ -0,0 +1,50 @@ +import 'package:dart_mappable/dart_mappable.dart'; +import 'package:test/test.dart'; + +import 'custom_mapper_test.dart'; + +part 'annotation_test.mapper.dart'; + +const mappable = MappableClass( + uniqueId: 'ABC', + ignoreNull: true, + includeCustomMappers: [PrivateClassMapper()], +); + +@mappable +class PrivateContainer with PrivateContainerMappable { + PrivateContainer(this.value); + + final MyPrivateClass value; +} + +const mappable2 = mappable; + +@mappable2 +class PrivateContainer2 with PrivateContainer2Mappable { + PrivateContainer2(this.value); + + final MyPrivateClass value; +} + +void main() { + group('Custom Annotation', () { + tearDown(() { + MapperContainer.globals.unuse(); + }); + + test('from constant', () { + expect( + PrivateContainer(MyPrivateClass('abc')).toMap(), + equals({'value': 'abc'}), + ); + }); + + test('from nested constant', () { + expect( + PrivateContainer2(MyPrivateClass('abc')).toMap(), + equals({'value': 'abc'}), + ); + }); + }); +} diff --git a/packages/dart_mappable/test/custom_mapper/annotation_test.mapper.dart b/packages/dart_mappable/test/custom_mapper/annotation_test.mapper.dart new file mode 100644 index 00000000..fa42a6cf --- /dev/null +++ b/packages/dart_mappable/test/custom_mapper/annotation_test.mapper.dart @@ -0,0 +1,228 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member +// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter + +part of 'annotation_test.dart'; + +class PrivateContainerMapper extends ClassMapperBase { + PrivateContainerMapper._(); + + static PrivateContainerMapper? _instance; + static PrivateContainerMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = PrivateContainerMapper._()); + MapperContainer.globals.useAll([PrivateClassMapper()]); + } + return _instance!; + } + + @override + final String id = 'ABC'; + + static MyPrivateClass _$value(PrivateContainer v) => v.value; + static const Field _f$value = + Field('value', _$value); + + @override + final MappableFields fields = const { + #value: _f$value, + }; + @override + final bool ignoreNull = true; + + static PrivateContainer _instantiate(DecodingData data) { + return PrivateContainer(data.dec(_f$value)); + } + + @override + final Function instantiate = _instantiate; + + static PrivateContainer fromMap(Map map) { + return ensureInitialized().decodeMap(map); + } + + static PrivateContainer fromJson(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin PrivateContainerMappable { + String toJson() { + return PrivateContainerMapper.ensureInitialized() + .encodeJson(this as PrivateContainer); + } + + Map toMap() { + return PrivateContainerMapper.ensureInitialized() + .encodeMap(this as PrivateContainer); + } + + PrivateContainerCopyWith + get copyWith => _PrivateContainerCopyWithImpl( + this as PrivateContainer, $identity, $identity); + @override + String toString() { + return PrivateContainerMapper.ensureInitialized() + .stringifyValue(this as PrivateContainer); + } + + @override + bool operator ==(Object other) { + return PrivateContainerMapper.ensureInitialized() + .equalsValue(this as PrivateContainer, other); + } + + @override + int get hashCode { + return PrivateContainerMapper.ensureInitialized() + .hashValue(this as PrivateContainer); + } +} + +extension PrivateContainerValueCopy<$R, $Out> + on ObjectCopyWith<$R, PrivateContainer, $Out> { + PrivateContainerCopyWith<$R, PrivateContainer, $Out> + get $asPrivateContainer => + $base.as((v, t, t2) => _PrivateContainerCopyWithImpl(v, t, t2)); +} + +abstract class PrivateContainerCopyWith<$R, $In extends PrivateContainer, $Out> + implements ClassCopyWith<$R, $In, $Out> { + $R call({MyPrivateClass? value}); + PrivateContainerCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t); +} + +class _PrivateContainerCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, PrivateContainer, $Out> + implements PrivateContainerCopyWith<$R, PrivateContainer, $Out> { + _PrivateContainerCopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = + PrivateContainerMapper.ensureInitialized(); + @override + $R call({MyPrivateClass? value}) => + $apply(FieldCopyWithData({if (value != null) #value: value})); + @override + PrivateContainer $make(CopyWithData data) => + PrivateContainer(data.get(#value, or: $value.value)); + + @override + PrivateContainerCopyWith<$R2, PrivateContainer, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _PrivateContainerCopyWithImpl($value, $cast, t); +} + +class PrivateContainer2Mapper extends ClassMapperBase { + PrivateContainer2Mapper._(); + + static PrivateContainer2Mapper? _instance; + static PrivateContainer2Mapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = PrivateContainer2Mapper._()); + MapperContainer.globals.useAll([PrivateClassMapper()]); + } + return _instance!; + } + + @override + final String id = 'ABC'; + + static MyPrivateClass _$value(PrivateContainer2 v) => v.value; + static const Field _f$value = + Field('value', _$value); + + @override + final MappableFields fields = const { + #value: _f$value, + }; + @override + final bool ignoreNull = true; + + static PrivateContainer2 _instantiate(DecodingData data) { + return PrivateContainer2(data.dec(_f$value)); + } + + @override + final Function instantiate = _instantiate; + + static PrivateContainer2 fromMap(Map map) { + return ensureInitialized().decodeMap(map); + } + + static PrivateContainer2 fromJson(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin PrivateContainer2Mappable { + String toJson() { + return PrivateContainer2Mapper.ensureInitialized() + .encodeJson(this as PrivateContainer2); + } + + Map toMap() { + return PrivateContainer2Mapper.ensureInitialized() + .encodeMap(this as PrivateContainer2); + } + + PrivateContainer2CopyWith + get copyWith => _PrivateContainer2CopyWithImpl( + this as PrivateContainer2, $identity, $identity); + @override + String toString() { + return PrivateContainer2Mapper.ensureInitialized() + .stringifyValue(this as PrivateContainer2); + } + + @override + bool operator ==(Object other) { + return PrivateContainer2Mapper.ensureInitialized() + .equalsValue(this as PrivateContainer2, other); + } + + @override + int get hashCode { + return PrivateContainer2Mapper.ensureInitialized() + .hashValue(this as PrivateContainer2); + } +} + +extension PrivateContainer2ValueCopy<$R, $Out> + on ObjectCopyWith<$R, PrivateContainer2, $Out> { + PrivateContainer2CopyWith<$R, PrivateContainer2, $Out> + get $asPrivateContainer2 => + $base.as((v, t, t2) => _PrivateContainer2CopyWithImpl(v, t, t2)); +} + +abstract class PrivateContainer2CopyWith<$R, $In extends PrivateContainer2, + $Out> implements ClassCopyWith<$R, $In, $Out> { + $R call({MyPrivateClass? value}); + PrivateContainer2CopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t); +} + +class _PrivateContainer2CopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, PrivateContainer2, $Out> + implements PrivateContainer2CopyWith<$R, PrivateContainer2, $Out> { + _PrivateContainer2CopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = + PrivateContainer2Mapper.ensureInitialized(); + @override + $R call({MyPrivateClass? value}) => + $apply(FieldCopyWithData({if (value != null) #value: value})); + @override + PrivateContainer2 $make(CopyWithData data) => + PrivateContainer2(data.get(#value, or: $value.value)); + + @override + PrivateContainer2CopyWith<$R2, PrivateContainer2, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _PrivateContainer2CopyWithImpl($value, $cast, t); +} diff --git a/packages/dart_mappable/test/initializer/init_package_test.init.dart b/packages/dart_mappable/test/initializer/init_package_test.init.dart index 9a35ca98..3d2010ed 100644 --- a/packages/dart_mappable/test/initializer/init_package_test.init.dart +++ b/packages/dart_mappable/test/initializer/init_package_test.init.dart @@ -78,6 +78,7 @@ void initializeMappers() { p7.VMapper.ensureInitialized(); p7.WMapper.ensureInitialized(); p8.PrivateContainerMapper.ensureInitialized(); + p8.PrivateContainer2Mapper.ensureInitialized(); p9.TestObjMapper.ensureInitialized(); p10.ClassAMapper.ensureInitialized(); p10.EnumAMapper.ensureInitialized(); diff --git a/packages/dart_mappable_builder/lib/src/elements/class/class_mapper_element.dart b/packages/dart_mappable_builder/lib/src/elements/class/class_mapper_element.dart index d9afeeca..793f4cea 100644 --- a/packages/dart_mappable_builder/lib/src/elements/class/class_mapper_element.dart +++ b/packages/dart_mappable_builder/lib/src/elements/class/class_mapper_element.dart @@ -72,7 +72,7 @@ abstract class ClassMapperElement extends InterfaceMapperElement late final String? hookForClass = () { var hook = annotation.value?.read('hook'); if (hook != null && !hook.isNull) { - var node = annotation.annotation?.getPropertyNode('hook'); + var node = annotation.getPropertyNode('hook'); if (node != null) { return node.toSource(); } diff --git a/packages/dart_mappable_builder/lib/src/elements/class/mixins/inherited_elements_mixin.dart b/packages/dart_mappable_builder/lib/src/elements/class/mixins/inherited_elements_mixin.dart index c126d360..2f3c7823 100644 --- a/packages/dart_mappable_builder/lib/src/elements/class/mixins/inherited_elements_mixin.dart +++ b/packages/dart_mappable_builder/lib/src/elements/class/mixins/inherited_elements_mixin.dart @@ -32,7 +32,7 @@ mixin InheritedElementsMixin on MapperElement { superElement?.discriminatorKey; late String? discriminatorValueCode = - annotation.annotation?.getPropertyNode('discriminatorValue')?.toSource(); + annotation.getPropertyNode('discriminatorValue')?.toSource(); List getSubClasses() { return annotation.value diff --git a/packages/dart_mappable_builder/lib/src/elements/class/target_class_mapper_element.dart b/packages/dart_mappable_builder/lib/src/elements/class/target_class_mapper_element.dart index d346b346..1611b842 100644 --- a/packages/dart_mappable_builder/lib/src/elements/class/target_class_mapper_element.dart +++ b/packages/dart_mappable_builder/lib/src/elements/class/target_class_mapper_element.dart @@ -4,7 +4,6 @@ import 'package:dart_mappable/dart_mappable.dart'; import '../../builder_options.dart'; import '../../mapper_group.dart'; -import '../../utils.dart'; import '../constructor/constructor_mapper_element.dart'; import '../mapper_element.dart'; import 'class_mapper_element.dart'; @@ -27,9 +26,8 @@ class TargetClassMapperElement extends ClassMapperElement late final String prefixedDecodingClassName = prefixedClassName; - late final String? customMappers = annotation.annotation - ?.getPropertyNode('includeCustomMappers') - ?.toSource(); + late final String? customMappers = + annotation.getPropertyNode('includeCustomMappers')?.toSource(); late final List customTypes = () { var types = []; diff --git a/packages/dart_mappable_builder/lib/src/elements/mapper_element.dart b/packages/dart_mappable_builder/lib/src/elements/mapper_element.dart index 92c47494..757141d2 100644 --- a/packages/dart_mappable_builder/lib/src/elements/mapper_element.dart +++ b/packages/dart_mappable_builder/lib/src/elements/mapper_element.dart @@ -14,20 +14,24 @@ import 'field/mapper_field_element.dart'; class MapperAnnotation { const MapperAnnotation.empty(this.element) : node = null, - annotation = null, + arguments = null, value = null; - const MapperAnnotation(this.element, this.node, this.annotation, this.value); + const MapperAnnotation(this.element, this.node, this.arguments, this.value); final Element element; final AstNode? node; - final Annotation? annotation; + final ArgumentList? arguments; final DartObject? value; static Future from(Element element) async { var node = await element.getResolvedNode(); - var annotation = getAnnotation(node, T); + var arguments = await getAnnotationArguments(node, T); var value = TypeChecker.fromRuntime(T).firstAnnotationOf(element); - return MapperAnnotation(element, node, annotation, value); + return MapperAnnotation(element, node, arguments, value); + } + + AstNode? getPropertyNode(dynamic /* String | int */ property) { + return arguments?.getArgument(property); } } diff --git a/packages/dart_mappable_builder/lib/src/elements/param/record_mapper_param_element.dart b/packages/dart_mappable_builder/lib/src/elements/param/record_mapper_param_element.dart index ca54af64..1b6be5a9 100644 --- a/packages/dart_mappable_builder/lib/src/elements/param/record_mapper_param_element.dart +++ b/packages/dart_mappable_builder/lib/src/elements/param/record_mapper_param_element.dart @@ -22,7 +22,7 @@ class RecordMapperParamElement extends MapperParamElement { @override Future getHook() async { - var node = getAnnotationProperty(param, MappableField, 'hook'); + var node = await getAnnotationProperty(param, MappableField, 'hook'); return node?.toSource(); } diff --git a/packages/dart_mappable_builder/lib/src/elements/record/alias_record_mapper_element.dart b/packages/dart_mappable_builder/lib/src/elements/record/alias_record_mapper_element.dart index 3ee644a9..0cfe95d2 100644 --- a/packages/dart_mappable_builder/lib/src/elements/record/alias_record_mapper_element.dart +++ b/packages/dart_mappable_builder/lib/src/elements/record/alias_record_mapper_element.dart @@ -12,7 +12,7 @@ import 'record_mapper_element.dart'; class RecordMapperAnnotation extends MapperAnnotation { RecordMapperAnnotation(super.element, RecordTypeAnnotation super.node, - super.annotation, super.value); + super.arguments, super.value); @override RecordTypeAnnotation get node => super.node as RecordTypeAnnotation; @@ -25,11 +25,11 @@ class RecordMapperAnnotation extends MapperAnnotation { .where((d) => d.name.lexeme == element.name) .first; - var annotation = getAnnotation(node, MappableRecord); + var arguments = await getAnnotationArguments(node, MappableRecord); var value = TypeChecker.fromRuntime(MappableRecord).firstAnnotationOf(element); return RecordMapperAnnotation( - element, node.type as RecordTypeAnnotation, annotation, value); + element, node.type as RecordTypeAnnotation, arguments, value); } } diff --git a/packages/dart_mappable_builder/lib/src/utils.dart b/packages/dart_mappable_builder/lib/src/utils.dart index 480c3aad..3cb1cb3f 100644 --- a/packages/dart_mappable_builder/lib/src/utils.dart +++ b/packages/dart_mappable_builder/lib/src/utils.dart @@ -32,7 +32,8 @@ extension GetNode on Element { } } -Annotation? getAnnotation(AstNode? node, Type annotationType) { +Future getAnnotationArguments( + AstNode? node, Type annotationType) async { if (node == null) { return null; } @@ -57,32 +58,38 @@ Annotation? getAnnotation(AstNode? node, Type annotationType) { return null; } - var type = annotationType.toString(); - return annotations.where((a) => a.name.name == type).firstOrNull; + var checker = TypeChecker.fromRuntime(annotationType); + var annotation = annotations.where((a) { + var type = a.elementAnnotation?.computeConstantValue()?.type; + return type != null && checker.isAssignableFromType(type); + }).firstOrNull; + var arguments = annotation?.arguments; + if (arguments != null) { + return arguments; + } else { + return getArgumentsFromElement(annotation?.element); + } } -extension AnnotationProperty on Annotation { - AstNode? getPropertyNode(dynamic property) { - for (var i = 0; i < arguments!.arguments.length; i++) { - var arg = arguments!.arguments[i]; - if (arg is NamedExpression && property is String) { - if (arg.name.label.name == property) { - return arg.expression; - } - } else if (property is int && i == property) { - return arg; +Future getArgumentsFromElement(Element? element) async { + if (element case PropertyAccessorElement elem) { + var node = await elem.variable.getResolvedNode(); + if (node is VariableDeclaration) { + var exp = node.initializer; + if (exp is InstanceCreationExpression) { + return exp.argumentList; + } else if (exp is SimpleIdentifier) { + return getArgumentsFromElement(exp.staticElement); } } - return null; } + return null; } -AstNode? getAnnotationProperty( - AstNode? node, Type annotationType, dynamic property) { - var annotation = getAnnotation(node, annotationType); - if (annotation != null) { - for (var i = 0; i < annotation.arguments!.arguments.length; i++) { - var arg = annotation.arguments!.arguments[i]; +extension ArgumentProperty on ArgumentList { + AstNode? getArgument(dynamic property) { + for (var i = 0; i < arguments.length; i++) { + var arg = arguments[i]; if (arg is NamedExpression && property is String) { if (arg.name.label.name == property) { return arg.expression; @@ -91,8 +98,14 @@ AstNode? getAnnotationProperty( return arg; } } + return null; } - return null; +} + +Future getAnnotationProperty( + AstNode? node, Type annotationType, dynamic property) async { + var arguments = await getAnnotationArguments(node, annotationType); + return arguments?.getArgument(property); } Future getAnnotationNode(