diff --git a/.github/workflows/integration_class_to_string.yml b/.github/workflows/integration_class_to_string.yml index 1990c3a..7b05769 100644 --- a/.github/workflows/integration_class_to_string.yml +++ b/.github/workflows/integration_class_to_string.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/checkout@v3 - uses: dart-lang/setup-dart@v1 with: - sdk: 3.0.0 + sdk: 3.8.0 - name: Resolve dependencies run: dart pub get diff --git a/.github/workflows/integration_mek_data_class.yml b/.github/workflows/integration_mek_data_class.yml index 4a7cff9..d368fb0 100644 --- a/.github/workflows/integration_mek_data_class.yml +++ b/.github/workflows/integration_mek_data_class.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/checkout@v3 - uses: dart-lang/setup-dart@v1 with: - sdk: 3.0.0 + sdk: 3.8.0 - name: Resolve dependencies run: dart pub get diff --git a/.github/workflows/integration_mek_data_class_generator.yml b/.github/workflows/integration_mek_data_class_generator.yml index bdadd2a..833f02c 100644 --- a/.github/workflows/integration_mek_data_class_generator.yml +++ b/.github/workflows/integration_mek_data_class_generator.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/checkout@v3 - uses: dart-lang/setup-dart@v1 with: - sdk: 3.0.0 + sdk: 3.8.0 - name: Resolve dependencies run: dart pub get diff --git a/README.md b/README.md index b2a6293..07563f9 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Auto generation of: - [x] `hashCode` and `==` methods - [x] pretty `toString` method - [x] `copyWith` method with support a `null` values +- [x] `merge` to merge current instance values with another - [x] `*Changes` class to updated your data class - [x] `*Builder` class to build or update your data class diff --git a/class_to_string/lib/src/class_to_flat_string.dart b/class_to_string/lib/src/class_to_flat_string.dart index 94c2fd2..1594cd8 100644 --- a/class_to_string/lib/src/class_to_flat_string.dart +++ b/class_to_string/lib/src/class_to_flat_string.dart @@ -6,7 +6,7 @@ import 'package:class_to_string/src/utils.dart'; class ClassToFlatString extends ClassToStringBase { StringBuffer? _result = StringBuffer(); - var _hasPreviousField = false; + var _isEmpty = true; ClassToFlatString(String className, [Iterable types = const []]) { _result!.write(className); @@ -24,12 +24,15 @@ class ClassToFlatString extends ClassToStringBase { @override void add(String name, Object? value) { - if (_hasPreviousField) _result!.write(','); + if (_isEmpty) { + _isEmpty = false; + } else { + _result!.write(','); + } _result! ..write(name) ..write(':') ..writeValue(value); - _hasPreviousField = true; } @override @@ -37,6 +40,7 @@ class ClassToFlatString extends ClassToStringBase { _result!.write(')'); final stringResult = _result.toString(); _result = null; + _isEmpty = true; return stringResult; } } diff --git a/class_to_string/lib/src/class_to_indent_string.dart b/class_to_string/lib/src/class_to_indent_string.dart index 5308150..b2de0ce 100644 --- a/class_to_string/lib/src/class_to_indent_string.dart +++ b/class_to_string/lib/src/class_to_indent_string.dart @@ -46,6 +46,7 @@ class ClassToIndentString extends ClassToStringBase { _result!.write(')'); final stringResult = _result.toString(); _result = null; + _isEmpty = true; return stringResult; } } diff --git a/example/lib/basic_example.dart b/example/lib/basic_example.dart index 4522c8b..29f3ed0 100644 --- a/example/lib/basic_example.dart +++ b/example/lib/basic_example.dart @@ -49,7 +49,7 @@ class ProductEquality implements Equality { bool isValidKey(Object? o) => throw UnimplementedError(); } -@DataClass(changeable: true) +@DataClass(buildable: true, copyable: true, mergeable: true, changeable: true) class EmptyClass with _$EmptyClass { const EmptyClass._(); } diff --git a/example/lib/basic_example.g.dart b/example/lib/basic_example.g.dart index c93c8b9..dd85e6d 100644 --- a/example/lib/basic_example.g.dart +++ b/example/lib/basic_example.g.dart @@ -156,11 +156,20 @@ class ProductChanges { mixin _$EmptyClass { EmptyClass get _self => this as EmptyClass; + EmptyClass copyWith() => _self; + + EmptyClass merge(covariant EmptyClass? other) => _self; + EmptyClass change(void Function(EmptyClassChanges c) updates) => (toChanges()..update(updates)).build(); EmptyClassChanges toChanges() => EmptyClassChanges._(_self); + EmptyClass rebuild(void Function(EmptyClassBuilder b) updates) => + (toBuilder()..update(updates)).build(); + + EmptyClassBuilder toBuilder() => EmptyClassBuilder()..replace(_self); + @override bool operator ==(Object other) => identical(this, other) || @@ -185,3 +194,13 @@ class EmptyClassChanges { EmptyClass build() => _original; } + +class EmptyClassBuilder { + void update(void Function(EmptyClassBuilder b) updates) => updates(this); + + EmptyClass build() { + return const EmptyClass._(); + } + + void replace(covariant EmptyClass other) {} +} diff --git a/example/lib/edge_cases.dart b/example/lib/edge_cases.dart index c3e0d07..77ddfa8 100644 --- a/example/lib/edge_cases.dart +++ b/example/lib/edge_cases.dart @@ -2,7 +2,7 @@ import 'package:mek_data_class/mek_data_class.dart'; part 'edge_cases.g.dart'; -@DataClass(changeable: true, buildable: true, copyable: true) +@DataClass(buildable: true, copyable: true, mergeable: true, changeable: true) class $Dollar with _$$Dollar { final $Dollar $dollar; final $Dollar? euro; diff --git a/example/lib/edge_cases.g.dart b/example/lib/edge_cases.g.dart index 9b5b03a..1d288a3 100644 --- a/example/lib/edge_cases.g.dart +++ b/example/lib/edge_cases.g.dart @@ -26,6 +26,16 @@ mixin _$$Dollar { ); } + $Dollar merge(covariant $Dollar? other) { + if (other == null) return _self; + return $Dollar( + $dollar: other.$dollar, + euro: other.euro ?? _self.euro, + privateAndPublic: other.privateAndPublic, + private: other._private, + ); + } + $Dollar change(void Function($DollarChanges c) updates) => (toChanges()..update(updates)).build(); diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 2354cb8..3f8ebf8 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -14,16 +14,13 @@ dependencies: json_annotation: ^4.9.0 dependency_overrides: - class_to_string: - path: ../class_to_string - mek_data_class: - path: ../mek_data_class + class_to_string: { path: ../class_to_string } + mek_data_class: { path: ../mek_data_class } dev_dependencies: mek_lints: ^2.0.0 test: ^1.21.6 build_runner: ^2.8.0 - mek_data_class_generator: - path: ../mek_data_class_generator + mek_data_class_generator: { path: ../mek_data_class_generator } json_serializable: ^6.11.1 diff --git a/mek_data_class/CHANGELOG.md b/mek_data_class/CHANGELOG.md index 4f2fa28..0838662 100644 --- a/mek_data_class/CHANGELOG.md +++ b/mek_data_class/CHANGELOG.md @@ -1,4 +1,7 @@ +## 2.2.0 +- feat: added `DataClass.mergeable` which generates the `merge` method to copy the values of another instance into the current instance + ## 2.1.0 - chore: bumped min dart version to `3.8.0` diff --git a/mek_data_class/lib/mek_data_class.dart b/mek_data_class/lib/mek_data_class.dart index 9c7b589..47c9a05 100644 --- a/mek_data_class/lib/mek_data_class.dart +++ b/mek_data_class/lib/mek_data_class.dart @@ -33,6 +33,10 @@ class DataClass { /// Default: `false` final bool? copyable; + /// Adds the `merge` method to the class. + /// Default: `false` + final bool? mergeable; + /// Adds the `change` and `toChanges` method to the class. /// Default: `false` final bool? changeable; @@ -46,8 +50,9 @@ class DataClass { const DataClass({ this.comparable, this.stringify, - this.copyable, this.buildable, + this.copyable, + this.mergeable, this.changeable, this.equalities = const [], }); diff --git a/mek_data_class/pubspec.yaml b/mek_data_class/pubspec.yaml index 7dde1fe..bccd8d4 100644 --- a/mek_data_class/pubspec.yaml +++ b/mek_data_class/pubspec.yaml @@ -1,7 +1,7 @@ name: mek_data_class description: > Generate `hashCode`, `==`, `toString`, `copyWith`, `change` methods and `Builder` class with low code -version: 2.1.0 +version: 2.2.0 homepage: https://github.com/BreX900/data_class/tree/main/mek_data_class repository: https://github.com/BreX900/data_class topics: diff --git a/mek_data_class_generator/CHANGELOG.md b/mek_data_class_generator/CHANGELOG.md index 4f8e7b7..8af118b 100644 --- a/mek_data_class_generator/CHANGELOG.md +++ b/mek_data_class_generator/CHANGELOG.md @@ -1,4 +1,11 @@ + +## 4.2.0 +- feat: added the ability to generate the `merge` method to copy the values of another instance into the current instance +- fix: fixed missing space when generating an empty const constructor +- chore: `copyWith` and `merge` methods return the original data class instance if constructor has no parameters +- chore: allowed analyzer `>=8.1.1 <10.0.0` + ## 4.1.1 - build: require `analyzer: ^9.0.0` - build: require `build: ^4.0.3` diff --git a/mek_data_class_generator/lib/mek_data_class_generator.dart b/mek_data_class_generator/lib/mek_data_class_generator.dart index da15110..1c7e4a8 100644 --- a/mek_data_class_generator/lib/mek_data_class_generator.dart +++ b/mek_data_class_generator/lib/mek_data_class_generator.dart @@ -10,6 +10,7 @@ import 'package:mek_data_class_generator/src/helpers/change_helper.dart'; import 'package:mek_data_class_generator/src/helpers/copy_with_helper.dart'; import 'package:mek_data_class_generator/src/helpers/equatable_helper.dart'; import 'package:mek_data_class_generator/src/helpers/helper_core.dart'; +import 'package:mek_data_class_generator/src/helpers/merge_helper.dart'; import 'package:mek_data_class_generator/src/helpers/to_string_helper.dart'; import 'package:source_gen/source_gen.dart' hide LibraryBuilder; import 'package:source_helper/source_helper.dart'; @@ -44,7 +45,7 @@ class DataClassGenerator extends GeneratorForAnnotation { } class _RegistryHelper extends HelperCore - with CopyWithHelper, ChangeHelper, BuilderHelper, EquatableHelper, ToStringHelper { + with CopyWithHelper, MergeHelper, ChangeHelper, BuilderHelper, EquatableHelper, ToStringHelper { final _libraryBody = []; final _mixinMethods = []; var _shouldCreateSelfMixinGetter = false; diff --git a/mek_data_class_generator/lib/src/configs/class_config.dart b/mek_data_class_generator/lib/src/configs/class_config.dart index 7b635eb..2bd6854 100644 --- a/mek_data_class_generator/lib/src/configs/class_config.dart +++ b/mek_data_class_generator/lib/src/configs/class_config.dart @@ -12,14 +12,16 @@ class ClassConfig { final bool stringify; final bool buildable; final bool copyable; + final bool mergeable; final bool changeable; final List equalities; const ClassConfig({ required this.comparable, required this.stringify, - required this.copyable, required this.buildable, + required this.copyable, + required this.mergeable, required this.changeable, required this.equalities, }); @@ -32,6 +34,7 @@ class ClassConfig { stringify: false, buildable: false, copyable: false, + mergeable: false, changeable: false, equalities: [], ); @@ -45,6 +48,7 @@ class ClassConfig { stringify: annotation.get('stringify')?.boolValue ?? config.stringify, buildable: annotation.get('buildable')?.boolValue ?? config.buildable, copyable: annotation.get('copyable')?.boolValue ?? config.copyable, + mergeable: annotation.get('mergeable')?.boolValue ?? config.mergeable, changeable: annotation.get('changeable')?.boolValue ?? config.changeable, equalities: annotation.read('equalities').listValue, ); diff --git a/mek_data_class_generator/lib/src/configs/options.dart b/mek_data_class_generator/lib/src/configs/options.dart index a151a4b..3e31efb 100644 --- a/mek_data_class_generator/lib/src/configs/options.dart +++ b/mek_data_class_generator/lib/src/configs/options.dart @@ -5,21 +5,30 @@ part 'options.g.dart'; @JsonSerializable() class Options { final Object? runOnlyIfTriggered; + @JsonKey(defaultValue: true) final bool equatable; + @JsonKey(defaultValue: true) final bool stringify; + @JsonKey(defaultValue: true) final bool stringifyIfNull; + @JsonKey(defaultValue: false) final bool buildable; + @JsonKey(defaultValue: false) final bool copyable; + @JsonKey(defaultValue: false) + final bool mergeable; + @JsonKey(defaultValue: false) final bool changeable; const Options({ - this.runOnlyIfTriggered, - this.equatable = true, - this.stringify = true, - this.stringifyIfNull = true, - this.buildable = false, - this.copyable = false, - this.changeable = false, + required this.runOnlyIfTriggered, + required this.equatable, + required this.stringify, + required this.stringifyIfNull, + required this.buildable, + required this.copyable, + required this.mergeable, + required this.changeable, }); factory Options.fromJson(Map map) => _$OptionsFromJson(map); diff --git a/mek_data_class_generator/lib/src/configs/options.g.dart b/mek_data_class_generator/lib/src/configs/options.g.dart index 43c613c..4dc3886 100644 --- a/mek_data_class_generator/lib/src/configs/options.g.dart +++ b/mek_data_class_generator/lib/src/configs/options.g.dart @@ -19,6 +19,7 @@ Options _$OptionsFromJson(Map json) => $checkedCreate( 'stringify_if_null', 'buildable', 'copyable', + 'mergeable', 'changeable', ], ); @@ -26,9 +27,13 @@ Options _$OptionsFromJson(Map json) => $checkedCreate( runOnlyIfTriggered: $checkedConvert('run_only_if_triggered', (v) => v), equatable: $checkedConvert('equatable', (v) => v as bool? ?? true), stringify: $checkedConvert('stringify', (v) => v as bool? ?? true), - stringifyIfNull: $checkedConvert('stringify_if_null', (v) => v as bool? ?? true), + stringifyIfNull: $checkedConvert( + 'stringify_if_null', + (v) => v as bool? ?? true, + ), buildable: $checkedConvert('buildable', (v) => v as bool? ?? false), copyable: $checkedConvert('copyable', (v) => v as bool? ?? false), + mergeable: $checkedConvert('mergeable', (v) => v as bool? ?? false), changeable: $checkedConvert('changeable', (v) => v as bool? ?? false), ); return val; diff --git a/mek_data_class_generator/lib/src/helpers/copy_with_helper.dart b/mek_data_class_generator/lib/src/helpers/copy_with_helper.dart index 595f722..937f0d1 100644 --- a/mek_data_class_generator/lib/src/helpers/copy_with_helper.dart +++ b/mek_data_class_generator/lib/src/helpers/copy_with_helper.dart @@ -19,31 +19,35 @@ mixin CopyWithHelper on HelperCore { Method _createCopyWithMethod(Iterable parameters) { Code? body; if (!element.isAbstract) { - body = lazyCode(() { - final buffer = StringBuffer('return '); - writeNewInstance(buffer, (parameter) { - if (parameterConfigOf(parameter).updatable) { - buffer.write('Unspecified.resolve(_self.'); - buffer.write(parameterConfigOf(parameter).accessor); - buffer.write(', '); - buffer.write(parameter.displayName); - buffer.write(')'); - } else { - buffer.write('_self.'); - buffer.write(parameterConfigOf(parameter).accessor); - } + if (parameters.isEmpty) { + body = const Code('_self'); + } else { + body = lazyCode(() { + final buffer = StringBuffer('return '); + writeNewInstance(buffer, (parameter) { + if (parameterConfigOf(parameter).updatable) { + buffer.write('Unspecified.resolve(_self.'); + buffer.write(parameterConfigOf(parameter).accessor); + buffer.write(', '); + buffer.write(parameter.displayName); + buffer.write(')'); + } else { + buffer.write('_self.'); + buffer.write(parameterConfigOf(parameter).accessor); + } + }); + buffer.write(';'); + return Code(buffer.toString()); }); - buffer.write(';'); - return Code(buffer.toString()); - }); + } } return Method( (b) => b ..returns = Reference(element.thisType.getDisplayString()) ..name = 'copyWith' ..optionalParameters.addAll( - parameters.map( - (parameter) => Parameter( + parameters.map((parameter) { + return Parameter( (b) => b ..named = true ..type = TypeReference( @@ -53,9 +57,10 @@ mixin CopyWithHelper on HelperCore { ) ..name = parameter.displayName ..defaultTo = const Code('const Unspecified()'), - ), - ), + ); + }), ) + ..lambda = body is StaticCode ..body = body, ); } diff --git a/mek_data_class_generator/lib/src/helpers/merge_helper.dart b/mek_data_class_generator/lib/src/helpers/merge_helper.dart new file mode 100644 index 0000000..21f2c50 --- /dev/null +++ b/mek_data_class_generator/lib/src/helpers/merge_helper.dart @@ -0,0 +1,66 @@ +import 'package:analyzer/dart/element/element.dart'; +import 'package:code_builder/code_builder.dart'; +import 'package:mek_data_class_generator/src/helpers/helper_core.dart'; +import 'package:source_helper/source_helper.dart'; + +mixin MergeHelper on HelperCore { + @override + void register() { + super.register(); + + if (!config.mergeable) return; + + final parameters = this.parameters.where((e) => parameterConfigOf(e).updatable); + if (!element.isAbstract) registerMixinSelfGetter(); + + registerMixinMethod(_createCopyWithMethod(parameters)); + } + + Method _createCopyWithMethod(Iterable parameters) { + Code? body; + if (!element.isAbstract) { + if (parameters.isEmpty) { + body = const Code('_self'); + } else { + body = lazyCode(() { + final buffer = StringBuffer('if (other == null) return _self;'); + buffer.write('return '); + writeNewInstance(buffer, (parameter) { + if (parameterConfigOf(parameter).updatable) { + buffer.write('other.'); + buffer.write(parameterConfigOf(parameter).accessor); + if (parameter.type.isNullableType) { + buffer.write(' ?? _self.'); + buffer.write(parameterConfigOf(parameter).accessor); + } + } else { + buffer.write('_self.'); + buffer.write(parameterConfigOf(parameter).accessor); + } + }); + buffer.write(';'); + return Code(buffer.toString()); + }); + } + } + return Method( + (b) => b + ..returns = Reference(element.thisType.getDisplayString()) + ..name = 'merge' + ..requiredParameters.add( + Parameter( + (b) => b + ..covariant = true + ..type = TypeReference( + (b) => b + ..isNullable = true + ..symbol = element.thisType.getDisplayString(), + ) + ..name = 'other', + ), + ) + ..lambda = body is StaticCode + ..body = body, + ); + } +} diff --git a/mek_data_class_generator/pubspec.yaml b/mek_data_class_generator/pubspec.yaml index 4ea4d94..9148081 100644 --- a/mek_data_class_generator/pubspec.yaml +++ b/mek_data_class_generator/pubspec.yaml @@ -1,5 +1,5 @@ name: mek_data_class_generator -version: 4.1.1 +version: 4.2.0 description: > Code generator for data_class to generate `hashCode`, `==`, `toString`, `copyWith` and `change` methods @@ -20,23 +20,21 @@ scripts: generate-code: dart run build_runner watch --delete-conflicting-outputs dependencies: - mek_data_class: ^2.0.0 + mek_data_class: ^2.2.0 - analyzer: ^9.0.0 - build: ^4.0.3 - source_gen: ^4.1.1 + analyzer: '>=8.1.1 <10.0.0' + build: ^4.0.2 + source_gen: ^4.0.0 code_builder: ^4.4.0 - source_helper: ^1.3.9 + source_helper: ^1.3.8 meta: ^1.16.0 collection: ^1.16.0 json_annotation: ^4.9.0 dependency_overrides: - class_to_string: - path: ../class_to_string - mek_data_class: - path: ../mek_data_class + class_to_string: { path: ../class_to_string } + mek_data_class: { path: ../mek_data_class } dev_dependencies: mek_lints: ^4.0.0