diff --git a/README.md b/README.md index c513a45..01d9549 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -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/). +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). ## 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 9469031..23fc605 100644 --- a/copy_with_extension/CHANGELOG.md +++ b/copy_with_extension/CHANGELOG.md @@ -1,3 +1,6 @@ +## 1.4.0 Improving genetic compatibility +* Fixes issue with generating code for some classes with generic type parameters. + ## 1.3.0 Immutable Fields * Fixes the `boolean-expression-must-not-be-null-exception` issue * Introduces `immutable` field annotation diff --git a/copy_with_extension/pubspec.yaml b/copy_with_extension/pubspec.yaml index 962129c..e29a97a 100644 --- a/copy_with_extension/pubspec.yaml +++ b/copy_with_extension/pubspec.yaml @@ -1,5 +1,5 @@ name: copy_with_extension -version: 1.3.0 +version: 1.4.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 @@ -12,4 +12,4 @@ dev_dependencies: pedantic: ^1.9.0 build_runner: ^1.10.3 test: ^1.0.0 - copy_with_extension_gen: ^1.3.0 \ No newline at end of file + copy_with_extension_gen: ^1.4.0 \ No newline at end of file diff --git a/copy_with_extension_gen/CHANGELOG.md b/copy_with_extension_gen/CHANGELOG.md index a790d45..d73da64 100644 --- a/copy_with_extension_gen/CHANGELOG.md +++ b/copy_with_extension_gen/CHANGELOG.md @@ -1,3 +1,6 @@ +## 1.4.0 Improving genetic compatibility +* Fixes issue with generating code for some classes with generic type parameters. + ## 1.3.1 README Update * Update README.md diff --git a/copy_with_extension_gen/README.md b/copy_with_extension_gen/README.md index 4ca95d7..bdf4418 100644 --- a/copy_with_extension_gen/README.md +++ b/copy_with_extension_gen/README.md @@ -5,8 +5,8 @@ Provides [Dart Build System](https://pub.dev/packages/build) builder for generat ## Usage #### In your `pubspec.yaml` file: -- Add to `dependencies` section `copy_with_extension: ^1.3.0` -- Add to `dev_dependencies` section `copy_with_extension_gen: ^1.3.0` +- Add to `dependencies` section `copy_with_extension: ^1.4.0` +- Add to `dev_dependencies` section `copy_with_extension_gen: ^1.4.0` - Add to `dev_dependencies` section `build_runner: ^1.10.3` - Set `environment` to at least Dart 2.9.0 version like so: `">=2.9.0 <3.0.0"` @@ -22,12 +22,12 @@ environment: dependencies: ... - copy_with_extension: ^1.3.0 + copy_with_extension: ^1.4.0 dev_dependencies: ... build_runner: ^1.10.3 - copy_with_extension_gen: ^1.3.0 + copy_with_extension_gen: ^1.4.0 ``` #### Annotate your class with `CopyWith` annotation: 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 a3a6db3..661fd0f 100644 --- a/copy_with_extension_gen/lib/src/copy_with_generator.dart +++ b/copy_with_extension_gen/lib/src/copy_with_generator.dart @@ -20,25 +20,38 @@ class CopyWithGenerator extends GeneratorForAnnotation { BuildStep buildStep, ) { if (element is! ClassElement) throw '$element is not a ClassElement'; + final classElement = element as ClassElement; final sortedFields = _sortedConstructorFields(classElement); final generateCopyWithNull = annotation.read('generateCopyWithNull').boolValue; - final typeParametersAnnotation = _typeParametersAnnotation(classElement); - final typeParametersNames = _typeParametersNames(classElement); + final typeParametersAnnotation = _typeParametersString(classElement, false); + final typeParametersNames = _typeParametersString(classElement, true); final typeAnnotation = classElement.name + typeParametersNames; return ''' - extension ${classElement.name}CopyWithExtension$typeParametersAnnotation on ${classElement.name}$typeParametersNames { + extension ${classElement.name}CopyWith$typeParametersAnnotation on ${classElement.name}$typeParametersNames { ${_copyWithPart(typeAnnotation, sortedFields)} ${generateCopyWithNull ? _copyWithNullPart(typeAnnotation, sortedFields) : ""} } '''; } - String _typeParametersNames(ClassElement classElement) { - final names = classElement.typeParameters.map((e) => e.name).join(','); + ///Returns parameter names or full parameters declaration declared by this class or an empty string. + /// + ///If `nameOnly` is `true`: `class MyClass` returns ``. + /// + ///If `nameOnly` is `false`: `class MyClass` returns ``. + String _typeParametersString(ClassElement classElement, bool nameOnly) { + assert(classElement is ClassElement); + assert(nameOnly is bool); + + final names = classElement.typeParameters + .map( + (e) => nameOnly ? e.name : e.getDisplayString(withNullability: false), + ) + .join(','); if (names.isNotEmpty) { return '<$names>'; } else { @@ -46,26 +59,13 @@ class CopyWithGenerator extends GeneratorForAnnotation { } } - String _typeParametersAnnotation(ClassElement classElement) { - final classDisplayString = - classElement.getDisplayString(withNullability: false); - final startIndex = classDisplayString.indexOf('<'); - final endIndex = classDisplayString.indexOf('>'); - - if (startIndex != -1 && endIndex != -1) { - return classDisplayString.substring( - startIndex, - endIndex + 1, - ); - } else { - return ''; - } - } - + ///Generates the complete `copyWith` function. String _copyWithPart( String typeAnnotation, List<_FieldInfo> sortedFields, ) { + assert(typeAnnotation is String && sortedFields is List<_FieldInfo>); + final constructorInput = sortedFields.fold( '', (r, v) { @@ -94,10 +94,13 @@ class CopyWithGenerator extends GeneratorForAnnotation { '''; } + ///Generates the complete `copyWithNull` function. String _copyWithNullPart( String typeAnnotation, List<_FieldInfo> sortedFields, ) { + assert(typeAnnotation is String && sortedFields is List<_FieldInfo>); + final nullConstructorInput = sortedFields.fold( '', (r, v) { @@ -126,6 +129,8 @@ class CopyWithGenerator extends GeneratorForAnnotation { '''; } + ///Generates a list of `_FieldInfo` for each class field that will be a part of the code generation process. + ///The resulting array is sorted by the field name. `Throws` on error. List<_FieldInfo> _sortedConstructorFields(ClassElement element) { assert(element is ClassElement); @@ -152,6 +157,7 @@ class CopyWithGenerator extends GeneratorForAnnotation { } } +///Represents a single class field with the additional metadata needed for code generation. class _FieldInfo { final String name; final String type; @@ -160,12 +166,18 @@ class _FieldInfo { _FieldInfo(ParameterElement element, ClassElement classElement) : name = element.name, type = element.type.getDisplayString(withNullability: false), - immutable = _readFieldOptions(element, classElement).immutable; + immutable = _readFieldOptions(element, classElement).immutable, + assert(element.name is String), + assert(element.type.getDisplayString(withNullability: false) is String), + assert(_readFieldOptions(element, classElement).immutable is bool); static CopyWithField _readFieldOptions( ParameterElement element, ClassElement classElement, ) { + assert(element is Element); + assert(classElement is ClassElement); + final fieldElement = classElement.getField(element.name); if (fieldElement is! FieldElement) { return CopyWithField(); diff --git a/copy_with_extension_gen/pubspec.yaml b/copy_with_extension_gen/pubspec.yaml index 75d86c6..00c9c70 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: 1.3.1 +version: 1.4.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 @@ -12,7 +12,7 @@ dependencies: analyzer: ">=0.39.0 <1.0.0" build: ^1.0.0 source_gen: ">=0.9.0 <1.0.0" - copy_with_extension: ^1.3.0 + copy_with_extension: ^1.4.0 dev_dependencies: pedantic: ^1.9.0 diff --git a/copy_with_extension_gen/test/constants.dart b/copy_with_extension_gen/test/constants.dart index 16bb289..20f43e1 100644 --- a/copy_with_extension_gen/test/constants.dart +++ b/copy_with_extension_gen/test/constants.dart @@ -39,8 +39,7 @@ part of 'test_case_class.dart'; // CopyWithGenerator // ************************************************************************** -extension Test_Case_ClassCopyWithExtension - on Test_Case_Class { +extension Test_Case_ClassCopyWith on Test_Case_Class { Test_Case_Class copyWith({ T id, }) { diff --git a/copy_with_extension_test/lib/basic_test_case.g.dart b/copy_with_extension_test/lib/basic_test_case.g.dart index 7483729..da5290c 100644 --- a/copy_with_extension_test/lib/basic_test_case.g.dart +++ b/copy_with_extension_test/lib/basic_test_case.g.dart @@ -6,7 +6,7 @@ part of 'basic_test_case.dart'; // CopyWithGenerator // ************************************************************************** -extension BasicClassCopyWithExtension on BasicClass { +extension BasicClassCopyWith on BasicClass { BasicClass copyWith({ String id, }) { diff --git a/copy_with_extension_test/lib/immutable_test_case.g.dart b/copy_with_extension_test/lib/immutable_test_case.g.dart index 427221d..153245b 100644 --- a/copy_with_extension_test/lib/immutable_test_case.g.dart +++ b/copy_with_extension_test/lib/immutable_test_case.g.dart @@ -6,7 +6,7 @@ part of 'immutable_test_case.dart'; // CopyWithGenerator // ************************************************************************** -extension TestCaseClassCopyWithExtension on TestCaseClass { +extension TestCaseClassCopyWith on TestCaseClass { TestCaseClass copyWith({ T id, }) { diff --git a/copy_with_extension_test/lib/implements_test_case.dart b/copy_with_extension_test/lib/implements_test_case.dart new file mode 100644 index 0000000..f0bac6a --- /dev/null +++ b/copy_with_extension_test/lib/implements_test_case.dart @@ -0,0 +1,81 @@ +import 'package:meta/meta.dart'; +import 'package:copy_with_extension/copy_with_extension.dart'; + +//Won't work without it! +part 'implements_test_case.g.dart'; + +abstract class Abstract { + final String aField; + Abstract(this.aField); +} + +abstract class AbstractWithType { + final T tField; + AbstractWithType(this.tField); +} + +abstract class AbstractWithType1 { + final T t1Field; + AbstractWithType1(this.t1Field); +} + +abstract class AbstractWithType2 { + final T saField; + final Y sa1Field; + AbstractWithType2(this.saField, this.sa1Field); +} + +@immutable +@CopyWith() +class Basic implements Abstract { + Basic({this.aField}); + + @override + final String aField; +} + +@immutable +@CopyWith() +class WithGenericType implements AbstractWithType { + WithGenericType({this.tField}); + + @override + final T tField; +} + +@immutable +@CopyWith() +class WithSpecificType implements AbstractWithType { + WithSpecificType({this.tField}); + + @override + final String tField; +} + +@immutable +@CopyWith() +class WithBoth + implements + Abstract, + AbstractWithType, + AbstractWithType1, + AbstractWithType2 { + WithBoth({ + this.aField, + this.tField, + this.t1Field, + this.saField, + this.sa1Field, + }); + + @override + final String aField; + @override + final T tField; + @override + final int t1Field; + @override + final String saField; + @override + final Y sa1Field; +} diff --git a/copy_with_extension_test/lib/implements_test_case.g.dart b/copy_with_extension_test/lib/implements_test_case.g.dart new file mode 100644 index 0000000..b8254e1 --- /dev/null +++ b/copy_with_extension_test/lib/implements_test_case.g.dart @@ -0,0 +1,55 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'implements_test_case.dart'; + +// ************************************************************************** +// CopyWithGenerator +// ************************************************************************** + +extension BasicCopyWith on Basic { + Basic copyWith({ + String aField, + }) { + return Basic( + aField: aField ?? this.aField, + ); + } +} + +extension WithGenericTypeCopyWith on WithGenericType { + WithGenericType copyWith({ + T tField, + }) { + return WithGenericType( + tField: tField ?? this.tField, + ); + } +} + +extension WithSpecificTypeCopyWith on WithSpecificType { + WithSpecificType copyWith({ + String tField, + }) { + return WithSpecificType( + tField: tField ?? this.tField, + ); + } +} + +extension WithBothCopyWith on WithBoth { + WithBoth copyWith({ + String aField, + Y sa1Field, + String saField, + int t1Field, + T tField, + }) { + return WithBoth( + aField: aField ?? this.aField, + sa1Field: sa1Field ?? this.sa1Field, + saField: saField ?? this.saField, + t1Field: t1Field ?? this.t1Field, + tField: tField ?? this.tField, + ); + } +} diff --git a/copy_with_extension_test/lib/subclass_test_case.g.dart b/copy_with_extension_test/lib/subclass_test_case.g.dart index 74aec4f..a3efb61 100644 --- a/copy_with_extension_test/lib/subclass_test_case.g.dart +++ b/copy_with_extension_test/lib/subclass_test_case.g.dart @@ -6,7 +6,7 @@ part of 'subclass_test_case.dart'; // CopyWithGenerator // ************************************************************************** -extension BasicBaseClassCopyWithExtension on BasicBaseClass { +extension BasicBaseClassCopyWith on BasicBaseClass { BasicBaseClass copyWith({ String id, }) { @@ -16,7 +16,7 @@ extension BasicBaseClassCopyWithExtension on BasicBaseClass { } } -extension BasicBaseSubClassCopyWithExtension on BasicBaseSubClass { +extension BasicBaseSubClassCopyWith on BasicBaseSubClass { BasicBaseSubClass copyWith({ String id, T item, @@ -28,7 +28,7 @@ extension BasicBaseSubClassCopyWithExtension on BasicBaseSubClass { } } -extension SubClassCopyWithExtension on SubClass { +extension SubClassCopyWith on SubClass { SubClass copyWith({ String aString, DateTime date,