diff --git a/README.md b/README.md index 01d9549..288351c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,8 @@ -Provides [source_gen](https://pub.dev/packages/source_gen) `Generator` for generating `copyWith` extensions. For more info on this package check out [my blog article](https://www.alexander-kirsch.com/blog/dart-extensions/) or the [main documentation file](https://pub.dev/packages/copy_with_extension_gen). +Provides [Dart Build System](https://pub.dev/packages/build) builder for generating `copyWith` extensions for classes annotated with [copy_with_extension](https://pub.dev/packages/copy_with_extension). + +For more information on how to use this package, see [copy_with_extension_gen](https://pub.dev/packages/copy_with_extension_gen). + +For more information on how this package works, see [my blog article](https://alexander-kirsch.com/blog/dart-extensions). ## copy_with_extension * Package: [copy_with_extension](https://pub.dev/packages/copy_with_extension) diff --git a/copy_with_extension/CHANGELOG.md b/copy_with_extension/CHANGELOG.md index 2af98c9..3f4a3bf 100644 --- a/copy_with_extension/CHANGELOG.md +++ b/copy_with_extension/CHANGELOG.md @@ -1,8 +1,12 @@ +## 4.0.0 +* **BREAKING** `copyWith` function now correctly supports nullification of nullable fields like so `copyWith(id: null)`. +* **BREAKING** `CopyWith` annotation for named constructor `namedConstructor` is renamed to `constructor` to be in sync with [json_serializable](https://pub.dev/packages/json_serializable). + ## 3.0.0 * Updating `analyzer` to `>=2.0.0 <4.0.0` * Named constructor support. * Better error reporting. -* Introduction of the new `copyWith` function with nullability support. The default behaviour of this library assumes that this function is used as follows: `myInstance.copyWith.value("newValue")`. The old functionality is still available. +* Introduction of the new `copyWith` function with nullability support that can be used like so: `myInstance.copyWith.value("newValue")`. The old functionality is still available. * **BREAKING** `generateCopyWithNull` is renamed to `copyWithNull`. ## 2.0.3 Dependency update diff --git a/copy_with_extension/example/example.dart b/copy_with_extension/example/example.dart index 8f7f177..1a5989d 100644 --- a/copy_with_extension/example/example.dart +++ b/copy_with_extension/example/example.dart @@ -26,7 +26,7 @@ class SimpleObjectImmutableField { } /// Allows the use of a private constructor. -@CopyWith(namedConstructor: "_") +@CopyWith(constructor: "_") class SimpleObjectPrivateConstructor { @CopyWithField(immutable: true) final String? id; diff --git a/copy_with_extension/lib/copy_with_extension.dart b/copy_with_extension/lib/copy_with_extension.dart index e4d47a7..a414269 100644 --- a/copy_with_extension/lib/copy_with_extension.dart +++ b/copy_with_extension/lib/copy_with_extension.dart @@ -1,5 +1,4 @@ -/// Provides annotation class to use with -/// [copy_with_extension_gen](https://pub.dev/packages/copy_with_extension_gen). +/// Provides `CopyWith` annotation class used by [copy_with_extension_gen](https://pub.dev/packages/copy_with_extension_gen). library copy_with_extension; /// Annotation used to indicate that the `copyWith` extension should be generated. @@ -7,23 +6,28 @@ class CopyWith { const CopyWith({ this.copyWithNull = false, this.skipFields = false, - this.namedConstructor, + this.constructor, }); - /// Set `copyWithNull` to `true` if you want to use `copyWithNull` function that allows you to nullify the fields. E.g. `myInstance.copyWithNull(id: true, name: true)`. Otherwise it will be still generated for internal use but marked as private. + /// Set `copyWithNull` to `true` if you want to use `copyWithNull` function that allows you to nullify the fields. E.g. `myInstance.copyWithNull(id: true, name: true)`. final bool copyWithNull; - /// Prevent the library from generating copyWith function for individual filelds. If you want to use only copyWith(...) function. + /// Prevent the library from generating `copyWith` functions for individual filelds e.g. `instance.copyWith.id("123")`. If you want to use only copyWith(...) function. final bool skipFields; - /// Set `namedConstructor` if you want to use a named constructor instead. The generated fields will be derived from this constructor. - final String? namedConstructor; + /// Set `constructor` if you want to use a named constructor. The generated fields will be derived from this constructor. + final String? constructor; } /// Additional field related options for the `CopyWith`. class CopyWithField { const CopyWithField({this.immutable = false}); - /// Indicates that the field should be hidden in the generated `copyWith` method. By setting this flag to `true` the field will always be copied as it is e.g. `userID` field. + /// Indicates that the field should be hidden in the generated `copyWith` method. By setting this flag to `true` the field will always be copied as it and excluded from `copyWith` interface. final bool immutable; } + +/// This placeholder object is a default value for nullable fields to handle cases when the user wants to nullify the value. +class $CopyWithPlaceholder { + const $CopyWithPlaceholder(); +} diff --git a/copy_with_extension/pubspec.yaml b/copy_with_extension/pubspec.yaml index 4f30eba..bee54ba 100644 --- a/copy_with_extension/pubspec.yaml +++ b/copy_with_extension/pubspec.yaml @@ -1,5 +1,5 @@ name: copy_with_extension -version: 3.0.0 +version: 4.0.0 description: Annotation for generating `copyWith` extensions code using `copy_with_extension_gen`. homepage: https://github.com/numen31337/copy_with_extension/tree/master/copy_with_extension repository: https://github.com/numen31337/copy_with_extension @@ -9,4 +9,4 @@ environment: sdk: ">=2.12.0 <3.0.0" dev_dependencies: - flutter_lints: ^1.0.0 \ No newline at end of file + flutter_lints: ^1.0.0 diff --git a/copy_with_extension_gen/CHANGELOG.md b/copy_with_extension_gen/CHANGELOG.md index 2af98c9..3f4a3bf 100644 --- a/copy_with_extension_gen/CHANGELOG.md +++ b/copy_with_extension_gen/CHANGELOG.md @@ -1,8 +1,12 @@ +## 4.0.0 +* **BREAKING** `copyWith` function now correctly supports nullification of nullable fields like so `copyWith(id: null)`. +* **BREAKING** `CopyWith` annotation for named constructor `namedConstructor` is renamed to `constructor` to be in sync with [json_serializable](https://pub.dev/packages/json_serializable). + ## 3.0.0 * Updating `analyzer` to `>=2.0.0 <4.0.0` * Named constructor support. * Better error reporting. -* Introduction of the new `copyWith` function with nullability support. The default behaviour of this library assumes that this function is used as follows: `myInstance.copyWith.value("newValue")`. The old functionality is still available. +* Introduction of the new `copyWith` function with nullability support that can be used like so: `myInstance.copyWith.value("newValue")`. The old functionality is still available. * **BREAKING** `generateCopyWithNull` is renamed to `copyWithNull`. ## 2.0.3 Dependency update diff --git a/copy_with_extension_gen/README.md b/copy_with_extension_gen/README.md index 041b960..7139fb2 100644 --- a/copy_with_extension_gen/README.md +++ b/copy_with_extension_gen/README.md @@ -5,9 +5,9 @@ Provides [Dart Build System](https://pub.dev/packages/build) builder for generat This library allows you to copy instances of immutable classes modifying specific fields like so: ```dart -myInstance.copyWith.fieldName("test") // Preferred way with nullability support. +myInstance.copyWith.fieldName("test") // Change a single field. -myInstance.copyWith(fieldName: "test", anotherField: "test") // Change multiple fields at once without nullability support. +myInstance.copyWith(fieldName: "test", anotherField: "test", nullableField: null) // Change multiple fields at once. myInstance.copyWithNull(fieldName: true, anotherField: true) // Nullify multiple fields at once. ``` @@ -16,8 +16,8 @@ myInstance.copyWithNull(fieldName: true, anotherField: true) // Nullify multiple ## Usage #### In your `pubspec.yaml` file: -- Add to `dependencies` section `copy_with_extension: ^3.0.0` -- Add to `dev_dependencies` section `copy_with_extension_gen: ^3.0.0` +- Add to `dependencies` section `copy_with_extension: ^4.0.0` +- Add to `dev_dependencies` section `copy_with_extension_gen: ^4.0.0` - Add to `dev_dependencies` section `build_runner: ^2.1.7` - Set `environment` to at least Dart `2.12.0` version like so: `">=2.12.0 <3.0.0"` @@ -33,12 +33,12 @@ environment: dependencies: ... - copy_with_extension: ^3.0.0 + copy_with_extension: ^4.0.0 dev_dependencies: ... build_runner: ^2.1.7 - copy_with_extension_gen: ^3.0.0 + copy_with_extension_gen: ^4.0.0 ``` #### Annotate your class with `CopyWith` annotation: @@ -69,18 +69,19 @@ flutter pub run build_runner build ```dart const result = BasicClass(id: "id") -final copied = result.copyWith.text("test") // Results in BasicClass(id: "id", text: "test"); +final copiedOne = result.copyWith.text("test") // Results in BasicClass(id: "id", text: "test"); +final copiedTwo = result.copyWith(id: "foo", text: null) // Results in BasicClass(id: "foo", text: null); ``` ## Additional features #### Change several fields at once with copyWith() -You can modify multiple fields at once using `copyWith` as a function like so: `myInstance.copyWith(fieldName: "test", anotherField: "test")`. Be aware that this kind of usage does not support nullification and all passed `null` values will be ignored. +You can modify multiple fields at once using `copyWith` as a function like so: `myInstance.copyWith(fieldName: "test", anotherField: "test")`. Passing the `null` value to `non-nullable` fields will be ignored. #### Nullifying instance fields: -The `copyWith` method ignores any `null` values that are passed to it. In order to nullify the class fields, an additional `copyWithNull` function can be generated. To achieve this, simply pass an additional parameter to your class annotation `@CopyWith(generateCopyWithNull: true)`. +In order to nullify the class fields, an additional `copyWithNull` function can be generated. To make use of it, pass an additional parameter to your class annotation `@CopyWith(generateCopyWithNull: true)`. #### Immutable fields @@ -91,4 +92,8 @@ If you want to prevent a particular field from modifying with `copyWith` method final int myImmutableField; ``` -By adding this annotation you forcing your generated `copyWith` to always copy this field as it is, without allowing its modification. +By adding this annotation you forcing your generated `copyWith` to always copy this field as it is, without exposing it in the function interface. + +## How this library is better than `freezed`? + +It isn't. This library is a non-intrusive alternative for those who only need the `copyWith` functionality and do not want to maintain the codebase in the way implied by the framework. This library only requires from your side the annotation of your class with `CopyWith()` and an indication of the `.part` file, everything else is up to you. [`freezed`](https://pub.dev/packages/freezed), on the other hand, offers many more code generation features but requires your models to be written in a framework-specific manner. diff --git a/copy_with_extension_gen/analysis_options.yaml b/copy_with_extension_gen/analysis_options.yaml index 0ffcdb2..35d4188 100644 --- a/copy_with_extension_gen/analysis_options.yaml +++ b/copy_with_extension_gen/analysis_options.yaml @@ -3,4 +3,8 @@ include: package:flutter_lints/flutter.yaml analyzer: strong-mode: implicit-casts: false - implicit-dynamic: false \ No newline at end of file + implicit-dynamic: false + +linter: + rules: + cast_nullable_to_non_nullable: true \ No newline at end of file diff --git a/copy_with_extension_gen/example/example.dart b/copy_with_extension_gen/example/example.dart index 8f7f177..1a5989d 100644 --- a/copy_with_extension_gen/example/example.dart +++ b/copy_with_extension_gen/example/example.dart @@ -26,7 +26,7 @@ class SimpleObjectImmutableField { } /// Allows the use of a private constructor. -@CopyWith(namedConstructor: "_") +@CopyWith(constructor: "_") class SimpleObjectPrivateConstructor { @CopyWithField(immutable: true) final String? id; diff --git a/copy_with_extension_gen/lib/src/copy_with_generator.dart b/copy_with_extension_gen/lib/src/copy_with_generator.dart index caf9ca9..bff4a05 100644 --- a/copy_with_extension_gen/lib/src/copy_with_generator.dart +++ b/copy_with_extension_gen/lib/src/copy_with_generator.dart @@ -25,27 +25,26 @@ class CopyWithGenerator extends GeneratorForAnnotation { final classAnnotation = readClassAnnotation(annotation); final sortedFields = - sortedConstructorFields(classElement, classAnnotation.namedConstructor); + sortedConstructorFields(classElement, classAnnotation.constructor); final typeParametersAnnotation = typeParametersString(classElement, false); final typeParametersNames = typeParametersString(classElement, true); final typeAnnotation = classElement.name + typeParametersNames; return ''' ${_copyWithProxyPart( - classAnnotation.namedConstructor, + classAnnotation.constructor, classElement.name, typeParametersAnnotation, typeParametersNames, sortedFields, - !classAnnotation.copyWithNull, classAnnotation.skipFields, )} - extension ${classElement.name}CopyWith$typeParametersAnnotation on ${classElement.name}$typeParametersNames { - /// CopyWith feature provided by `copy_with_extension_gen` library. Returns a callable class and can be used as follows: `instanceOf$classElement.name.copyWith(...)`. Be aware that this kind of usage does not support nullification and all passed `null` values will be ignored.${classAnnotation.skipFields ? "" : " Prefer to copy the instance with a specific field change that handles nullification of fields correctly, e.g. like this:`instanceOf$classElement.name.copyWith.fieldName(...)`"} - ${"_${classElement.name}CWProxy$typeParametersNames get copyWith => _${classElement.name}CWProxy$typeParametersNames(this);"} + extension \$${classElement.name}CopyWith$typeParametersAnnotation on ${classElement.name}$typeParametersNames { + /// Returns a callable class that can be used as follows: `instanceOf$classElement.name.copyWith(...)`${classAnnotation.skipFields ? "" : " or like so:`instanceOf$classElement.name.copyWith.fieldName(...)`"}. + ${"_\$${classElement.name}CWProxy$typeParametersNames get copyWith => _\$${classElement.name}CWProxyImpl$typeParametersNames(this);"} - ${_copyWithNullPart(typeAnnotation, sortedFields, classAnnotation.namedConstructor, !classAnnotation.copyWithNull, classAnnotation.skipFields)} + ${classAnnotation.copyWithNull ? _copyWithNullPart(typeAnnotation, sortedFields, classAnnotation.constructor, classAnnotation.skipFields) : ""} } '''; } @@ -54,41 +53,49 @@ class CopyWithGenerator extends GeneratorForAnnotation { String _copyWithValuesPart( String typeAnnotation, List sortedFields, - String? namedConstructor, + String? constructor, bool skipFields, + bool isAbstract, ) { final constructorInput = sortedFields.fold( '', (r, v) { - if (v.immutable) { - return r; - } else { + if (v.immutable) return r; // Skip the field + + if (isAbstract) { final type = v.type.endsWith('?') ? v.type : '${v.type}?'; return '$r $type ${v.name},'; + } else { + return '$r Object? ${v.name} = const \$CopyWithPlaceholder(),'; } }, ); final paramsInput = sortedFields.fold( '', (r, v) { - if (v.immutable) { - return '$r ${v.name}: _value.${v.name},'; - } else { - return '$r ${v.name}: ${v.name} ?? _value.${v.name},'; - } + if (v.immutable) return '$r ${v.name}: _value.${v.name},'; + + return ''' + $r ${v.name}: + ${v.name} == const \$CopyWithPlaceholder() + ? _value.${v.name} + // ignore: cast_nullable_to_non_nullable + : ${v.name} as ${v.type},'''; }, ); + final constructorBody = isAbstract + ? "" + : "{ return ${constructorFor(typeAnnotation, constructor)}($paramsInput); }"; + return ''' - /// This function does not support nullification of optional types, all `null` values passed to this function will be ignored. For nullification, use `$typeAnnotation(...).copyWithNull(...)` to set certain fields to `null`.${skipFields ? "" : " Prefer `$typeAnnotation(...).copyWith.fieldName(...)` to override fields one at a time with nullification support."} + /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored.${skipFields ? "" : " You can also use `$typeAnnotation(...).copyWith.fieldName(...)` to override fields one at a time with nullification support."} /// /// Usage /// ```dart /// $typeAnnotation(...).copyWith(id: 12, name: "My name") /// ```` - $typeAnnotation call({$constructorInput}) { - return ${constructorFor(typeAnnotation, namedConstructor)}($paramsInput); - } + $typeAnnotation call({$constructorInput}) $constructorBody '''; } @@ -96,8 +103,7 @@ class CopyWithGenerator extends GeneratorForAnnotation { String _copyWithNullPart( String typeAnnotation, List sortedFields, - String? namedConstructor, - bool private, + String? constructor, bool skipFields, ) { /// Return if there is no nullable fields @@ -126,60 +132,60 @@ class CopyWithGenerator extends GeneratorForAnnotation { }, ); - final description = private - ? "" - : ''' - /// Copies the object with the specific fields set to `null`. If you pass `false` as a parameter, nothing will be done and it will be ignored. Don't do it.${skipFields ? "" : " Prefer `$typeAnnotation(...).copyWith.fieldName(...)` to override fields one at a time with nullification support."} - /// - /// Usage - /// ```dart - /// $typeAnnotation(...).copyWithNull(firstField: true, secondField: true) - /// ````'''; + final description = ''' + /// Copies the object with the specific fields set to `null`. If you pass `false` as a parameter, nothing will be done and it will be ignored. Don't do it. Prefer `copyWith(field: null)`${skipFields ? "" : " or `$typeAnnotation(...).copyWith.fieldName(...)` to override fields one at a time with nullification support"}. + /// + /// Usage + /// ```dart + /// $typeAnnotation(...).copyWithNull(firstField: true, secondField: true) + /// ````'''; return ''' $description - $typeAnnotation ${private ? "_" : ""}copyWithNull({$nullConstructorInput}) { - return ${constructorFor(typeAnnotation, namedConstructor)}($nullParamsInput); + $typeAnnotation copyWithNull({$nullConstructorInput}) { + return ${constructorFor(typeAnnotation, constructor)}($nullParamsInput); } - '''; + '''; } /// Generates a `CopyWithProxy` class. String _copyWithProxyPart( - String? namedConstructor, + String? constructor, String type, String typeParameters, String typeParameterNames, List sortedFields, - bool privateCopyWithNull, bool skipFields, ) { final typeAnnotation = type + typeParameterNames; final filteredFields = sortedFields.where((e) => !e.immutable); - final nonNullableFields = filteredFields.where((e) => !e.nullable); - final nullableFields = - filteredFields.where((e) => !nonNullableFields.contains(e)); - final nonNullableFunctions = - skipFields ? "" : nonNullableFields.map((e) => ''' + final nonNullableFunctions = skipFields ? "" : filteredFields.map((e) => ''' + @override $type$typeParameterNames ${e.name}(${e.type} ${e.name}) => this(${e.name}: ${e.name}); ''').join("\n"); - final nullableFunctions = skipFields ? "" : nullableFields.map((e) => ''' - $type$typeParameterNames ${e.name}(${e.type} ${e.name}) => ${e.name} == null ? _value.${privateCopyWithNull ? "_" : ""}copyWithNull(${e.name}: true) : this(${e.name}: ${e.name}); + final nonNullableFunctionsInterface = + skipFields ? "" : filteredFields.map((e) => ''' + $type$typeParameterNames ${e.name}(${e.type} ${e.name}); ''').join("\n"); return ''' - /// Proxy class for `CopyWith` functionality. This is a callable class and can be used as follows: `instanceOf$type.copyWith(...)`. Be aware that this kind of usage does not support nullification and all passed `null` values will be ignored.${skipFields ? "" : " Prefer to copy the instance with a specific field change that handles nullification of fields correctly, e.g. like this:`instanceOf$type.copyWith.fieldName(...)`"} - class _${type}CWProxy$typeParameters { - final $type$typeParameterNames _value; + abstract class _\$${type}CWProxy$typeParameters { + $nonNullableFunctionsInterface - const _${type}CWProxy(this._value); + ${_copyWithValuesPart(typeAnnotation, sortedFields, constructor, skipFields, true)}; + } - ${_copyWithValuesPart(typeAnnotation, sortedFields, namedConstructor, skipFields)} + /// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOf$type.copyWith(...)`.${skipFields ? "" : " Additionally contains functions for specific fields e.g. `instanceOf$type.copyWith.fieldName(...)`"} + class _\$${type}CWProxyImpl$typeParameters implements _\$${type}CWProxy$typeParameterNames { + final $type$typeParameterNames _value; - $nullableFunctions + const _\$${type}CWProxyImpl(this._value); $nonNullableFunctions + + @override + ${_copyWithValuesPart(typeAnnotation, sortedFields, constructor, skipFields, false)} } '''; } diff --git a/copy_with_extension_gen/lib/src/field_info.dart b/copy_with_extension_gen/lib/src/field_info.dart index cf2fff0..a3ab429 100644 --- a/copy_with_extension_gen/lib/src/field_info.dart +++ b/copy_with_extension_gen/lib/src/field_info.dart @@ -40,8 +40,10 @@ class FieldInfo { } final reader = ConstantReader(annotation); - final immutable = reader.read('immutable').literalValue as bool; + final immutable = reader.read('immutable').literalValue as bool?; - return CopyWithField(immutable: immutable); + return CopyWithField( + immutable: immutable ?? const CopyWithField().immutable, + ); } } diff --git a/copy_with_extension_gen/lib/src/helpers.dart b/copy_with_extension_gen/lib/src/helpers.dart index c03d0b6..c8c4852 100644 --- a/copy_with_extension_gen/lib/src/helpers.dart +++ b/copy_with_extension_gen/lib/src/helpers.dart @@ -9,16 +9,16 @@ import 'package:source_gen/source_gen.dart' /// The resulting array is sorted by the field name. `Throws` on error. List sortedConstructorFields( ClassElement element, - String? constructorName, + String? constructor, ) { - final constructor = constructorName != null - ? element.getNamedConstructor(constructorName) + final targetConstructor = constructor != null + ? element.getNamedConstructor(constructor) : element.unnamedConstructor; - if (constructor is! ConstructorElement) { - if (constructorName != null) { + if (targetConstructor is! ConstructorElement) { + if (constructor != null) { throw InvalidGenerationSourceError( - 'Named Constructor "$constructorName" constructor is missing.', + 'Named Constructor "$constructor" constructor is missing.', element: element, ); } else { @@ -29,7 +29,7 @@ List sortedConstructorFields( } } - final parameters = constructor.parameters; + final parameters = targetConstructor.parameters; if (parameters.isEmpty) { throw InvalidGenerationSourceError( 'Unnamed constructor for ${element.name} has no parameters or missing.', @@ -39,9 +39,9 @@ List sortedConstructorFields( for (final parameter in parameters) { if (!parameter.isNamed) { - final constructorName = constructor.name.isEmpty + final constructorName = targetConstructor.name.isEmpty ? 'Unnamed constructor' - : 'Constructor "${constructor.name}"'; + : 'Constructor "${targetConstructor.name}"'; throw InvalidGenerationSourceError( '$constructorName for "${element.name}" contains unnamed parameter "${parameter.name}". Constructors annotated with "CopyWith" can contain only named parameters.', element: element, @@ -59,11 +59,11 @@ List sortedConstructorFields( CopyWith readClassAnnotation(ConstantReader reader) { final generateCopyWithNull = reader.read('copyWithNull').boolValue; final skipFields = reader.read('skipFields').boolValue; - final useNamedConstructor = reader.peek('namedConstructor')?.stringValue; + final constructor = reader.peek('constructor')?.stringValue; return CopyWith( copyWithNull: generateCopyWithNull, - namedConstructor: useNamedConstructor, + constructor: constructor, skipFields: skipFields, ); } diff --git a/copy_with_extension_gen/pubspec.yaml b/copy_with_extension_gen/pubspec.yaml index 9ffc028..08ad1cd 100644 --- a/copy_with_extension_gen/pubspec.yaml +++ b/copy_with_extension_gen/pubspec.yaml @@ -1,5 +1,5 @@ name: copy_with_extension_gen -version: 3.0.0 +version: 4.0.0 description: Automatically generating `copyWith` extensions code for classes with `@CopyWith()` annotation. repository: https://github.com/numen31337/copy_with_extension homepage: https://github.com/numen31337/copy_with_extension/tree/master/copy_with_extension_gen @@ -9,13 +9,13 @@ environment: sdk: ">=2.12.0 <3.0.0" dependencies: - analyzer: '>=2.0.0 <4.0.0' + analyzer: ">=2.0.0 <4.0.0" build: ^2.0.0 source_gen: ^1.0.0 - copy_with_extension: ^3.0.0 + copy_with_extension: ^4.0.0 dev_dependencies: flutter_lints: ^1.0.0 build_runner: ">=1.11.5 <3.0.0" test: ^1.20.1 - source_gen_test: ^1.0.1 \ No newline at end of file + source_gen_test: ^1.0.1 diff --git a/copy_with_extension_gen/test/gen_basic_functionality_test.dart b/copy_with_extension_gen/test/gen_basic_functionality_test.dart index 83db0f0..6964ff8 100644 --- a/copy_with_extension_gen/test/gen_basic_functionality_test.dart +++ b/copy_with_extension_gen/test/gen_basic_functionality_test.dart @@ -67,6 +67,11 @@ void main() { const CopyWithValuesOptional(id: "test").copyWith.id(null).id, null, ); + + expect( + const CopyWithValuesOptional(id: "test").copyWith(id: null).id, + null, + ); }); test('CopyWithProxy', () { diff --git a/copy_with_extension_gen/test/gen_inheritance_test.dart b/copy_with_extension_gen/test/gen_inheritance_test.dart index 2df61a2..84e8ff4 100644 --- a/copy_with_extension_gen/test/gen_inheritance_test.dart +++ b/copy_with_extension_gen/test/gen_inheritance_test.dart @@ -93,18 +93,13 @@ void main() { id: "testid", date: date, listWithTypedType: const [], - )._copyWithNull().copyWith(); + ).copyWith(); expect( result.copyWith().runtimeType, ComplexSubClass(id: "", date: date).runtimeType, ); - expect( - result._copyWithNull().runtimeType, - ComplexSubClass(id: "", date: date).runtimeType, - ); - expect( result.copyWith.id("").runtimeType, ComplexSubClass(id: "", date: date).runtimeType, @@ -112,7 +107,7 @@ void main() { expect( result.copyWith.runtimeType, - _ComplexSubClassCWProxy( + _$ComplexSubClassCWProxyImpl( ComplexSubClass(id: "", date: date), ).runtimeType, ); diff --git a/copy_with_extension_gen/test/gen_named_constructor_test.dart b/copy_with_extension_gen/test/gen_named_constructor_test.dart index ba5b41d..a332051 100644 --- a/copy_with_extension_gen/test/gen_named_constructor_test.dart +++ b/copy_with_extension_gen/test/gen_named_constructor_test.dart @@ -3,7 +3,7 @@ import 'package:test/test.dart' show test, expect; part 'gen_named_constructor_test.g.dart'; -@CopyWith(namedConstructor: "_") +@CopyWith(constructor: "_") class CopyWithNamedConstructor { final String? id; @@ -12,7 +12,7 @@ class CopyWithNamedConstructor { }); } -@CopyWith(namedConstructor: "first") +@CopyWith(constructor: "first") class CopyWithTwoThreeConstructors { final String? id; final String field; @@ -32,7 +32,7 @@ class CopyWithTwoThreeConstructors { }); } -@CopyWith(namedConstructor: "_") +@CopyWith(constructor: "_") class DefaultValuesConstructor { final String? id = "test"; final String? field; diff --git a/copy_with_extension_gen/test/generated_code_test_cases/test_case_basic_usage.dart b/copy_with_extension_gen/test/generated_code_test_cases/test_case_basic_usage.dart index f5ac8a8..2a762ef 100644 --- a/copy_with_extension_gen/test/generated_code_test_cases/test_case_basic_usage.dart +++ b/copy_with_extension_gen/test/generated_code_test_cases/test_case_basic_usage.dart @@ -1,13 +1,12 @@ part of 'source_gen_entrypoint.dart'; @ShouldGenerate(r''' -/// Proxy class for `CopyWith` functionality. This is a callable class and can be used as follows: `instanceOfBasicClass.copyWith(...)`. Be aware that this kind of usage does not support nullification and all passed `null` values will be ignored. Prefer to copy the instance with a specific field change that handles nullification of fields correctly, e.g. like this:`instanceOfBasicClass.copyWith.fieldName(...)` -class _BasicClassCWProxy> { - final BasicClass _value; +abstract class _$BasicClassCWProxy> { + BasicClass id(String id); - const _BasicClassCWProxy(this._value); + BasicClass optional(T? optional); - /// This function does not support nullification of optional types, all `null` values passed to this function will be ignored. For nullification, use `BasicClass(...).copyWithNull(...)` to set certain fields to `null`. Prefer `BasicClass(...).copyWith.fieldName(...)` to override fields one at a time with nullification support. + /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `BasicClass(...).copyWith.fieldName(...)` to override fields one at a time with nullification support. /// /// Usage /// ```dart @@ -16,37 +15,53 @@ class _BasicClassCWProxy> { BasicClass call({ String? id, T? optional, - }) { - return BasicClass( - id: id ?? _value.id, - immutable: _value.immutable, - nullableImmutable: _value.nullableImmutable, - optional: optional ?? _value.optional, - ); - } + }); +} - BasicClass optional(T? optional) => optional == null - ? _value._copyWithNull(optional: true) - : this(optional: optional); +/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfBasicClass.copyWith(...)`. Additionally contains functions for specific fields e.g. `instanceOfBasicClass.copyWith.fieldName(...)` +class _$BasicClassCWProxyImpl> + implements _$BasicClassCWProxy { + final BasicClass _value; + + const _$BasicClassCWProxyImpl(this._value); + @override BasicClass id(String id) => this(id: id); -} -extension BasicClassCopyWith> on BasicClass { - /// CopyWith feature provided by `copy_with_extension_gen` library. Returns a callable class and can be used as follows: `instanceOfclass BasicClass>.name.copyWith(...)`. Be aware that this kind of usage does not support nullification and all passed `null` values will be ignored. Prefer to copy the instance with a specific field change that handles nullification of fields correctly, e.g. like this:`instanceOfclass BasicClass>.name.copyWith.fieldName(...)` - _BasicClassCWProxy get copyWith => _BasicClassCWProxy(this); + @override + BasicClass optional(T? optional) => this(optional: optional); - BasicClass _copyWithNull({ - bool optional = false, + @override + + /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `BasicClass(...).copyWith.fieldName(...)` to override fields one at a time with nullification support. + /// + /// Usage + /// ```dart + /// BasicClass(...).copyWith(id: 12, name: "My name") + /// ```` + BasicClass call({ + Object? id = const $CopyWithPlaceholder(), + Object? optional = const $CopyWithPlaceholder(), }) { return BasicClass( - id: id, - immutable: immutable, - nullableImmutable: nullableImmutable, - optional: optional == true ? null : this.optional, + id: id == const $CopyWithPlaceholder() + ? _value.id + // ignore: cast_nullable_to_non_nullable + : id as String, + immutable: _value.immutable, + nullableImmutable: _value.nullableImmutable, + optional: optional == const $CopyWithPlaceholder() + ? _value.optional + // ignore: cast_nullable_to_non_nullable + : optional as T?, ); } } + +extension $BasicClassCopyWith> on BasicClass { + /// Returns a callable class that can be used as follows: `instanceOfclass BasicClass>.name.copyWith(...)` or like so:`instanceOfclass BasicClass>.name.copyWith.fieldName(...)`. + _$BasicClassCWProxy get copyWith => _$BasicClassCWProxyImpl(this); +} ''') @CopyWith() class BasicClass> { diff --git a/copy_with_extension_gen/test/generated_code_test_cases/test_case_copy_with_null.dart b/copy_with_extension_gen/test/generated_code_test_cases/test_case_copy_with_null.dart index 515b088..e9bd8c0 100644 --- a/copy_with_extension_gen/test/generated_code_test_cases/test_case_copy_with_null.dart +++ b/copy_with_extension_gen/test/generated_code_test_cases/test_case_copy_with_null.dart @@ -1,34 +1,52 @@ part of 'source_gen_entrypoint.dart'; @ShouldGenerate(r''' -/// Proxy class for `CopyWith` functionality. This is a callable class and can be used as follows: `instanceOfWithNullableWithoutFields.copyWith(...)`. Be aware that this kind of usage does not support nullification and all passed `null` values will be ignored. -class _WithNullableWithoutFieldsCWProxy> { +abstract class _$WithNullableWithoutFieldsCWProxy> { + /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. + /// + /// Usage + /// ```dart + /// WithNullableWithoutFields(...).copyWith(id: 12, name: "My name") + /// ```` + WithNullableWithoutFields call({ + T? nullable, + }); +} + +/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfWithNullableWithoutFields.copyWith(...)`. +class _$WithNullableWithoutFieldsCWProxyImpl> + implements _$WithNullableWithoutFieldsCWProxy { final WithNullableWithoutFields _value; - const _WithNullableWithoutFieldsCWProxy(this._value); + const _$WithNullableWithoutFieldsCWProxyImpl(this._value); - /// This function does not support nullification of optional types, all `null` values passed to this function will be ignored. For nullification, use `WithNullableWithoutFields(...).copyWithNull(...)` to set certain fields to `null`. + @override + + /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. /// /// Usage /// ```dart /// WithNullableWithoutFields(...).copyWith(id: 12, name: "My name") /// ```` WithNullableWithoutFields call({ - T? nullable, + Object? nullable = const $CopyWithPlaceholder(), }) { return WithNullableWithoutFields( - nullable: nullable ?? _value.nullable, + nullable: nullable == const $CopyWithPlaceholder() + ? _value.nullable + // ignore: cast_nullable_to_non_nullable + : nullable as T?, ); } } -extension WithNullableWithoutFieldsCopyWith> +extension $WithNullableWithoutFieldsCopyWith> on WithNullableWithoutFields { - /// CopyWith feature provided by `copy_with_extension_gen` library. Returns a callable class and can be used as follows: `instanceOfclass WithNullableWithoutFields>.name.copyWith(...)`. Be aware that this kind of usage does not support nullification and all passed `null` values will be ignored. - _WithNullableWithoutFieldsCWProxy get copyWith => - _WithNullableWithoutFieldsCWProxy(this); + /// Returns a callable class that can be used as follows: `instanceOfclass WithNullableWithoutFields>.name.copyWith(...)`. + _$WithNullableWithoutFieldsCWProxy get copyWith => + _$WithNullableWithoutFieldsCWProxyImpl(this); - /// Copies the object with the specific fields set to `null`. If you pass `false` as a parameter, nothing will be done and it will be ignored. Don't do it. + /// Copies the object with the specific fields set to `null`. If you pass `false` as a parameter, nothing will be done and it will be ignored. Don't do it. Prefer `copyWith(field: null)`. /// /// Usage /// ```dart diff --git a/copy_with_extension_gen/test/generated_code_test_cases/test_cases_exceptions.dart b/copy_with_extension_gen/test/generated_code_test_cases/test_cases_exceptions.dart index 95134d8..17299f1 100644 --- a/copy_with_extension_gen/test/generated_code_test_cases/test_cases_exceptions.dart +++ b/copy_with_extension_gen/test/generated_code_test_cases/test_cases_exceptions.dart @@ -15,7 +15,7 @@ class NoConstructor { } @ShouldThrow('Named Constructor "test" constructor is missing.') -@CopyWith(namedConstructor: "test") +@CopyWith(constructor: "test") class WrongConstructor {} @ShouldThrow('Default constructor for "NoDefaultConstructor" is missing.') @@ -49,7 +49,7 @@ class ConstructorWithAllUnnamedFields { @ShouldThrow( 'Constructor "test" for "NamedConstructorWithSomeUnnamedFields" contains unnamed parameter "value". Constructors annotated with "CopyWith" can contain only named parameters.', ) -@CopyWith(namedConstructor: "test") +@CopyWith(constructor: "test") class NamedConstructorWithSomeUnnamedFields { int? test; int value;