From 1d44eed26087a3a5acf0c64bad58d1dc52dd2bd5 Mon Sep 17 00:00:00 2001 From: Vasiliy Ditsyak Date: Sat, 8 Jun 2024 22:46:30 +0200 Subject: [PATCH] nested form groups --- packages/generator_tests/pubspec.lock | 4 +- .../test/doc/animated_url_list_test.dart | 52 ++ .../docs/animated_url_list/url_output.dart | 27 + .../lib/reactive_forms_generator.dart | 35 +- .../lib/src/extensions.dart | 35 +- .../lib/src/form_generator.dart | 74 ++- .../lib/src/library_builder.dart | 2 +- .../lib/src/output/extensions.dart | 34 ++ .../rf_annotation_arguments_visitor.dart | 13 +- .../src/output/rf_declaration_visitor.dart | 23 + .../lib/src/output/rf_paramater_visitor.dart | 54 -- .../lib/src/output/x.dart | 570 ++++++++++++++++++ .../field_value_method.dart | 54 +- .../lib/src/types.dart | 8 + 14 files changed, 853 insertions(+), 132 deletions(-) create mode 100644 packages/generator_tests/test/doc/animated_url_list_test.dart create mode 100644 packages/reactive_forms_generator/example/lib/docs/animated_url_list/url_output.dart create mode 100644 packages/reactive_forms_generator/lib/src/output/rf_declaration_visitor.dart create mode 100644 packages/reactive_forms_generator/lib/src/output/x.dart diff --git a/packages/generator_tests/pubspec.lock b/packages/generator_tests/pubspec.lock index 1819367..3adc3c5 100644 --- a/packages/generator_tests/pubspec.lock +++ b/packages/generator_tests/pubspec.lock @@ -436,14 +436,14 @@ packages: path: "../reactive_forms_annotations" relative: true source: path - version: "5.0.0" + version: "6.0.0-beta.1" reactive_forms_generator: dependency: "direct dev" description: path: "../reactive_forms_generator" relative: true source: path - version: "5.0.1" + version: "6.0.0-beta.3" recase: dependency: "direct main" description: diff --git a/packages/generator_tests/test/doc/animated_url_list_test.dart b/packages/generator_tests/test/doc/animated_url_list_test.dart new file mode 100644 index 0000000..f11052e --- /dev/null +++ b/packages/generator_tests/test/doc/animated_url_list_test.dart @@ -0,0 +1,52 @@ +@Timeout(Duration(seconds: 145)) +import 'package:test/test.dart'; + +import '../helpers.dart'; + +const fileName = 'animated_url_list'; + +void main() { + group('reactive_forms_generator', () { + test( + 'Animated URL list', + () async { + return testGenerator( + fileName: fileName, + model: ''' + import 'package:flutter/material.dart'; + import 'package:reactive_forms/reactive_forms.dart'; + import 'package:reactive_forms_annotations/reactive_forms_annotations.dart'; + + @Rf() + class UrlO { + final List urlList; + + UrlO({@RfArray() this.urlList = const []}); + } + + @RfGroup() + class UrlEntityO { + final String? label; + final String? url; + + UrlEntityO({ + @RfControl(validators: [ + RequiredValidator(), + ]) + this.label, + @RfControl(validators: [ + RequiredValidator(), + ]) + this.url, + }); + } + ''', + generatedFile: generatedFile, + ); + }, + ); + }); +} + +const generatedFile = r''' +'''; diff --git a/packages/reactive_forms_generator/example/lib/docs/animated_url_list/url_output.dart b/packages/reactive_forms_generator/example/lib/docs/animated_url_list/url_output.dart new file mode 100644 index 0000000..5b29521 --- /dev/null +++ b/packages/reactive_forms_generator/example/lib/docs/animated_url_list/url_output.dart @@ -0,0 +1,27 @@ +import 'package:reactive_forms_annotations/reactive_forms_annotations.dart'; + +part 'url_output.gform.dart'; + +@Rf() +class UrlO { + final List urlList; + + UrlO({@RfArray() this.urlList = const []}); +} + +@RfGroup() +class UrlEntityO { + final String? label; + final String? url; + + UrlEntityO({ + @RfControl(validators: [ + RequiredValidator(), + ]) + this.label, + @RfControl(validators: [ + RequiredValidator(), + ]) + this.url, + }); +} diff --git a/packages/reactive_forms_generator/lib/reactive_forms_generator.dart b/packages/reactive_forms_generator/lib/reactive_forms_generator.dart index 48debb1..97be6d8 100644 --- a/packages/reactive_forms_generator/lib/reactive_forms_generator.dart +++ b/packages/reactive_forms_generator/lib/reactive_forms_generator.dart @@ -4,7 +4,6 @@ library reactive_forms_generator; import 'dart:async'; -import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:build/build.dart'; import 'package:code_builder/code_builder.dart'; @@ -41,49 +40,49 @@ class ReactiveFormsGenerator extends Generator { } final lib = Library( - (b) => b + (b) => + b ..body.addAll(specList.mergeDuplicatesBy( - (i) => i is Class ? i.name : i, - (a, b) => a, + (i) => i is Class ? i.name : i, + (a, b) => a, )), ); // final x = lib.accept(emitter).toString(); final generatedValue = - DartFormatter().format(lib.accept(emitter).toString()); + DartFormatter().format(lib.accept(emitter).toString()); final values = []; await for (var value in normalizeGeneratorOutput(generatedValue)) { - assert(value.length == value.trim().length); + assert(value.length == value + .trim() + .length); values.add(value); } return values.join('\n\n'); } - Future> generateForAnnotatedElement( - Element element, - ConstantReader annotation, - BuildStep buildStep, - ) async { + Future> generateForAnnotatedElement(Element element, + ConstantReader annotation, + BuildStep buildStep,) async { throwIf( element is! ClassElement, '${element.name} is not a class element', element: element, ); - final ast = await buildStep.resolver.astNodeFor(element, resolve: true); + final astElement = element.enclosingElement; + + final ast = await buildStep.resolver.astNodeFor( + astElement ?? element, + resolve: true, + ); if (ast == null) { throw InvalidGenerationSourceError('Ast not found', element: element); } - if (ast is! Declaration) { - throw InvalidGenerationSourceError( - 'Ast is not a Declaration', - element: element, - ); - } return generateLibrary(element as ClassElement, ast); } diff --git a/packages/reactive_forms_generator/lib/src/extensions.dart b/packages/reactive_forms_generator/lib/src/extensions.dart index 6b2f282..c5e2322 100644 --- a/packages/reactive_forms_generator/lib/src/extensions.dart +++ b/packages/reactive_forms_generator/lib/src/extensions.dart @@ -8,16 +8,17 @@ import 'package:recase/recase.dart'; import '../utils.dart'; extension ConstructorElementExt on ConstructorElement { - bool get hasReactiveFormAnnotatedParameters => parameters.any( - (e) => true, + bool get hasReactiveFormAnnotatedParameters => + parameters.any( + (e) => true, ); } extension ClassElementExt on ClassElement { String get fullTypeName => thisType.toString(); - String get fullTypeNameOutput => - '${thisType.toString()}${output ? 'Output' : ''}'; + // String get fullTypeNameOutput => + // '${thisType.toString()}${output ? 'Output' : ''}'; String get generics { final generics = genericTypes.map((e) => e.symbol).join(', '); @@ -36,19 +37,19 @@ extension ClassElementExt on ClassElement { Iterable get genericTypes { return thisType.typeArguments.map( - (e) => Reference(e.getDisplayString(withNullability: false)), + (e) => Reference(e.getDisplayString(withNullability: false)), ); } Iterable get fullGenericTypes { return thisType.typeArguments.map( - (e) => Reference(e.element?.getDisplayString(withNullability: false)), + (e) => Reference(e.element?.getDisplayString(withNullability: false)), ); } List get annotatedParameters { final annotatedConstructors = - constructors.where((e) => e.hasReactiveFormAnnotatedParameters); + constructors.where((e) => e.hasReactiveFormAnnotatedParameters); if (annotatedConstructors.isNotEmpty) { return annotatedConstructors.first.parameters; @@ -146,7 +147,7 @@ extension ParameterElementExt on ParameterElement { final type = this.type; final typeArguments = - type is ParameterizedType ? type.typeArguments : const []; + type is ParameterizedType ? type.typeArguments : const []; final typeParameter = typeArguments.first; @@ -161,13 +162,13 @@ extension ParameterElementExt on ParameterElement { final type = this.type; final typeArguments = - type is ParameterizedType ? type.typeArguments : const []; + type is ParameterizedType ? type.typeArguments : const []; final typeParameter = typeArguments.first; return (typeParameter.element is ClassElement || - typeParameter.element is EnumElement || - typeParameter.element is TypeDefiningElement) && + typeParameter.element is EnumElement || + typeParameter.element is TypeDefiningElement) && !typeParameter.element!.hasRfGroupAnnotation; } @@ -211,8 +212,8 @@ extension FieldElementExt on FieldElement { typedef IterableFunction = U Function(T i); typedef MergeableFunction = T Function(T oldT, T newT); -Iterable _mergeDuplicatesBy( - Iterable list, IterableFunction fn, MergeableFunction mergeFn) { +Iterable _mergeDuplicatesBy(Iterable list, + IterableFunction fn, MergeableFunction mergeFn) { final values = {}; for (var i in list) { final value = fn(i); @@ -221,8 +222,8 @@ Iterable _mergeDuplicatesBy( return values.values.toList(); } -Iterable _removeDuplicatedBy( - Iterable list, IterableFunction fn) { +Iterable _removeDuplicatedBy(Iterable list, + IterableFunction fn) { final values = {}; return list.where((i) { final value = fn(i); @@ -235,8 +236,8 @@ Iterable _removeDuplicatedBy( extension ExtensionsOnIterable on Iterable { /// Merge multiple values from an iterable given a predicate without modifying /// the original iterable. - Iterable mergeDuplicatesBy( - IterableFunction fn, MergeableFunction mergeFn) => + Iterable mergeDuplicatesBy(IterableFunction fn, + MergeableFunction mergeFn) => _mergeDuplicatesBy(this, fn, mergeFn); /// Remove duplicated values from an iterable given a predicate without diff --git a/packages/reactive_forms_generator/lib/src/form_generator.dart b/packages/reactive_forms_generator/lib/src/form_generator.dart index 90e0934..b97ef6a 100644 --- a/packages/reactive_forms_generator/lib/src/form_generator.dart +++ b/packages/reactive_forms_generator/lib/src/form_generator.dart @@ -12,6 +12,7 @@ import 'package:reactive_forms_generator/src/form_elements/form_array_generator. import 'package:reactive_forms_generator/src/form_elements/form_group_generator.dart'; import 'package:reactive_forms_generator/src/output/helpers.dart'; import 'package:reactive_forms_generator/src/output/rf_annotation_arguments_visitor.dart'; +import 'package:reactive_forms_generator/src/output/rf_declaration_visitor.dart'; import 'package:reactive_forms_generator/src/output/rf_paramater_visitor.dart'; import 'package:reactive_forms_generator/src/reactive_forms/reactive_form_update_value_method.dart'; import 'package:reactive_forms_generator/src/reactive_forms/reactive_forms_clear_method.dart'; @@ -45,7 +46,7 @@ class FormGenerator { final DartType? type; - final u.Declaration ast; + final u.AstNode ast; final Map formGroupGenerators = {}; @@ -433,9 +434,12 @@ class FormGenerator { return '${e.fieldName}:${e.fieldValueName}'; }).whereType(); + final referenceType = + root.output ? element.toReferenceType : element.fullTypeName; + b ..name = 'model' - ..returns = Reference(element.fullTypeNameOutput) + ..returns = Reference(referenceType) ..annotations.add(const CodeExpression(Code('override'))) ..type = MethodType.getter ..body = Code(''' @@ -444,7 +448,7 @@ class FormGenerator { if (!isValid) { debugPrintStack(label: '[\${path ?? '$classNameFull'}]\\nā”—ā” Avoid calling `model` on invalid form. Possible exceptions for non-nullable fields which should be guarded by `required` validator.'); } - return ${element.fullTypeNameOutput}(${parameterValues.join(', ')}); + return $referenceType(${parameterValues.join(', ')}); '''); }, ); @@ -558,7 +562,7 @@ class FormGenerator { ..named = true ..required = true ..type = Reference( - 'void Function(${element.fullTypeNameOutput} model)'), + 'void Function(${root.output ? element.toReferenceType : element.fullTypeName} model)'), ), Parameter( (b) => b @@ -621,17 +625,27 @@ class FormGenerator { } Code test() { + final classDeclarationVisitor = ClassDeclarationVisitor(element); + ast.visitChildren(classDeclarationVisitor); + + final classNode = classDeclarationVisitor.classDeclaration; + + if (classNode == null) { + return const Code(''); + } + var rfParameterVisitor = RfParameterVisitor(); + // var rfAnnotationCollectorVisitor = RfAnnotationCollectorVisitor(); - ast.visitChildren(rfParameterVisitor); + classNode.visitChildren(rfParameterVisitor); replaceR( rfParameterVisitor.fieldDeclaration, rfParameterVisitor.fieldFormalParameter, ); - final astDeclaration = ast as u.ClassDeclarationImpl; + final astDeclaration = classNode as u.ClassDeclarationImpl; final renamedClass = ClassRenameVisitor(astDeclaration.name.lexeme); - ast.accept(renamedClass); + classNode.accept(renamedClass); // renamedClass.updatedClass?.visitChildren(rfAnnotationCollectorVisitor); @@ -645,13 +659,13 @@ class FormGenerator { List get generate { return [ - if (element.output) test(), + if (root.output) test(), Class( (b) => b ..name = className ..types.addAll(element.fullGenericTypes) ..implements.add(Reference( - 'FormModel<${element.fullTypeName}, ${element.fullTypeNameOutput}>')) + 'FormModel<${element.fullTypeName}, ${root.output ? element.toReferenceType : element.fullTypeName}>')) ..fields.addAll( [ ...staticFieldNameList, @@ -824,68 +838,64 @@ class FormGenerator { } Iterable get fieldContainsMethodList => all - .map((e) => ContainsMethod(e, element.output).method()) + .map((e) => ContainsMethod(e, root.output).method()) .whereType(); Iterable get fieldValueMethodList => all - .map((e) => FieldValueMethod(e, element.output).method()) + .map((e) => FieldValueMethod(e, root.output).method()) .whereType(); Iterable get fieldControlNameMethodList => all - .map((e) => ControlPathMethod(e, element.output).method()) + .map((e) => ControlPathMethod(e, root.output).method()) .whereType(); Iterable get staticFieldNameList => annotatedParameters.map(staticFieldName); - Iterable get fieldErrorsMethodList => all - .map((e) => ErrorsMethod(e, element.output).method()) - .whereType(); + Iterable get fieldErrorsMethodList => + all.map((e) => ErrorsMethod(e, root.output).method()).whereType(); Iterable get fieldNameList => annotatedParameters.map(field); - Iterable get fieldFocusMethodList => all - .map((e) => FocusMethod(e, element.output).method()) - .whereType(); + Iterable get fieldFocusMethodList => + all.map((e) => FocusMethod(e, root.output).method()).whereType(); - Iterable get fieldRemoveMethodList => all - .map((e) => RemoveMethod(e, element.output).method()) - .whereType(); + Iterable get fieldRemoveMethodList => + all.map((e) => RemoveMethod(e, root.output).method()).whereType(); Iterable get fieldUpdateMethodList => all - .map((e) => ReactiveFormUpdateValueMethod(e, element.output).method()) + .map((e) => ReactiveFormUpdateValueMethod(e, root.output).method()) .whereType(); Iterable get fieldInsertMethodList => all - .map((e) => ReactiveFormInsertMethod(e, element.output).method()) + .map((e) => ReactiveFormInsertMethod(e, root.output).method()) .whereType(); Iterable get fieldClearMethodList => all - .map((e) => ReactiveFormClearMethod(e, element.output).method()) + .map((e) => ReactiveFormClearMethod(e, root.output).method()) .whereType(); Iterable get fieldPatchMethodList => all - .map((e) => ReactiveFormPatchValueMethod(e, element.output).method()) + .map((e) => ReactiveFormPatchValueMethod(e, root.output).method()) .whereType(); - Iterable get fieldResetMethodList => all - .map((e) => ResetMethod(e, element.output).method()) - .whereType(); + Iterable get fieldResetMethodList => + all.map((e) => ResetMethod(e, root.output).method()).whereType(); Iterable get controlMethodList => all - .map((e) => ControlMethod(e, element.output).method()) + .map((e) => ControlMethod(e, root.output).method()) .whereType(); Iterable get controlPrivateMethodList => all - .map((e) => ControlPrivateMethod(e, element.output).method()) + .map((e) => ControlPrivateMethod(e, root.output).method()) .whereType(); Iterable get controlSetDisabledMethodList => all - .map((e) => ControlSetDisableMethod(e, element.output).method()) + .map((e) => ControlSetDisableMethod(e, root.output).method()) .whereType(); Iterable get extendedControlMethodList => all - .map((e) => ExtendedControlMethod(e, element.output).method()) + .map((e) => ExtendedControlMethod(e, root.output).method()) .whereType(); Iterable get addArrayControlMethodList => diff --git a/packages/reactive_forms_generator/lib/src/library_builder.dart b/packages/reactive_forms_generator/lib/src/library_builder.dart index 0b2a013..4dcf5e9 100644 --- a/packages/reactive_forms_generator/lib/src/library_builder.dart +++ b/packages/reactive_forms_generator/lib/src/library_builder.dart @@ -15,7 +15,7 @@ const formGroupRef = Reference('FormGroup'); List generateLibrary( ClassElement element, - Declaration ast, + AstNode ast, ) { final formGenerator = FormGenerator(element, element, null, ast); final reactiveInheritedStreamer = ReactiveInheritedStreamer(formGenerator); diff --git a/packages/reactive_forms_generator/lib/src/output/extensions.dart b/packages/reactive_forms_generator/lib/src/output/extensions.dart index fe6cf34..741ddcf 100644 --- a/packages/reactive_forms_generator/lib/src/output/extensions.dart +++ b/packages/reactive_forms_generator/lib/src/output/extensions.dart @@ -4,6 +4,7 @@ import 'package:analyzer/dart/ast/token.dart'; import 'package:analyzer/src/dart/ast/token.dart'; import 'package:analyzer/src/dart/ast/ast.dart'; import 'package:analyzer/src/generated/utilities_dart.dart'; +import 'package:reactive_forms_generator/src/types.dart'; extension ElementExt on Element { // Future clone() async { @@ -125,6 +126,39 @@ extension TypeAnnotationImplExt on TypeAnnotationImpl { final RecordTypeAnnotationImpl _ => this, }; } + + TypeAnnotationImpl get newTypeO { + final type = this; + + return switch (type) { + GenericFunctionTypeImpl() => throw UnimplementedError(), + NamedTypeImpl() => NamedTypeImpl( + importPrefix: type.importPrefix, + name2: type.element?.hasRfGroupAnnotation == true + ? StringToken( + TokenType.STRING, + '${type.name2.lexeme}Output', + 0, + ) + : type.name2, + typeArguments: type.typeArguments?.newTypeArguments, + question: type.question, + ), + RecordTypeAnnotationImpl() => throw UnimplementedError(), + }; + } +} + +extension TypeArgumentListImplExt on TypeArgumentListImpl { + TypeArgumentListImpl get newTypeArguments { + return TypeArgumentListImpl( + leftBracket: leftBracket, + arguments: arguments.map((e) { + return e.newTypeO; + }).toList(), + rightBracket: rightBracket, + ); + } } extension FieldDeclarationImplExt on FieldDeclarationImpl { diff --git a/packages/reactive_forms_generator/lib/src/output/rf_annotation_arguments_visitor.dart b/packages/reactive_forms_generator/lib/src/output/rf_annotation_arguments_visitor.dart index 8ff171b..9ef5409 100644 --- a/packages/reactive_forms_generator/lib/src/output/rf_annotation_arguments_visitor.dart +++ b/packages/reactive_forms_generator/lib/src/output/rf_annotation_arguments_visitor.dart @@ -4,6 +4,7 @@ import 'package:analyzer/dart/ast/visitor.dart'; import 'package:analyzer/dart/ast/token.dart'; import 'package:analyzer/src/dart/ast/token.dart'; import 'package:analyzer/src/dart/ast/utilities.dart'; +import 'package:reactive_forms_generator/src/output/extensions.dart'; class RfAnnotationArgumentsVisitor extends RecursiveAstVisitor { final arguments = {}; @@ -175,7 +176,17 @@ class ClassRenameVisitor extends RecursiveAstVisitor { covariantKeyword: e.covariantKeyword, externalKeyword: e.externalKeyword, staticKeyword: e.staticKeyword, - fieldList: e.fields, + fieldList: VariableDeclarationListImpl( + comment: null, + metadata: e.fields.metadata, + lateKeyword: e.fields.lateKeyword, + keyword: e.fields.keyword, + type: e.fields.type?.newTypeO, + variables: e.fields.variables.map((e) { + return e; + }).toList(), + ), + //e.fields semicolon: e.semicolon, ), final MethodDeclarationImpl _ => MethodDeclarationImpl( diff --git a/packages/reactive_forms_generator/lib/src/output/rf_declaration_visitor.dart b/packages/reactive_forms_generator/lib/src/output/rf_declaration_visitor.dart new file mode 100644 index 0000000..16ec282 --- /dev/null +++ b/packages/reactive_forms_generator/lib/src/output/rf_declaration_visitor.dart @@ -0,0 +1,23 @@ +// ignore_for_file: implementation_imports +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/src/dart/ast/ast.dart'; +import 'package:analyzer/dart/ast/visitor.dart'; + +class ClassDeclarationVisitor extends RecursiveAstVisitor { + final ClassElement classElement; + + ClassDeclaration? _classDeclaration; + + ClassDeclarationVisitor(this.classElement); + + ClassDeclaration? get classDeclaration => _classDeclaration; + + @override + void visitClassDeclaration(ClassDeclaration node) { + if (node.name.lexeme == classElement.name) { + _classDeclaration = node; + } + super.visitClassDeclaration(node); + } +} diff --git a/packages/reactive_forms_generator/lib/src/output/rf_paramater_visitor.dart b/packages/reactive_forms_generator/lib/src/output/rf_paramater_visitor.dart index 82ffba1..24db422 100644 --- a/packages/reactive_forms_generator/lib/src/output/rf_paramater_visitor.dart +++ b/packages/reactive_forms_generator/lib/src/output/rf_paramater_visitor.dart @@ -21,39 +21,6 @@ class RfParameterVisitor extends RecursiveAstVisitor { super.visitFieldDeclaration(node); } - // @override - // visitFieldFormalParameterList(FieldFormalParameter node) { - // node.visitChildren(this); - // return null; - // } - - // @override - // visitSimpleFormalParameter(SimpleFormalParameter node) { - // final rfAnnotationVisitor = RfAnnotationVisitor(); - // final rfAnnotationArguments = RfAnnotationArgumentsVisitor(); - // node.visitChildren(rfAnnotationVisitor); - // - // // if (rfAnnotationVisitor.rfAnnotation != null) { - // // node.accept(rfAnnotationArguments); - // // } - // // - // // if (rfAnnotationArguments.arguments.containsKey('validators') && - // // rfAnnotationArguments.arguments['validators'] - // // ?.contains('RequiredValidator()') == - // // true) { - // // fieldFormalParameter[node.name.toString()] = node; - // // } - // // - // node.visitChildren(this); - // return null; - // } - - // @override - // visitFieldFormalParameter(FieldFormalParameter node) { - // node.visitChildren(this); - // return null; - // } - @override visitFormalParameterList(FormalParameterList node) { for (var e in node.parameters) { @@ -76,27 +43,6 @@ class RfParameterVisitor extends RecursiveAstVisitor { node.visitChildren(this); return null; } - -// @override -// visitDefaultFormalParameter(DefaultFormalParameter node) { -// final rfAnnotationVisitor = RfAnnotationVisitor(); -// final rfAnnotationArguments = RfAnnotationArgumentsVisitor(); -// node.visitChildren(rfAnnotationVisitor); -// -// if (rfAnnotationVisitor.rfAnnotation != null) { -// node.accept(rfAnnotationArguments); -// } -// -// if (rfAnnotationArguments.arguments.containsKey('validators') && -// rfAnnotationArguments.arguments['validators'] -// ?.contains('RequiredValidator()') == -// true) { -// fieldFormalParameter[node.name.toString()] = node; -// } -// -// node.visitChildren(this); -// return null; -// } } class RfEParameterVisitor extends RecursiveElementVisitor { diff --git a/packages/reactive_forms_generator/lib/src/output/x.dart b/packages/reactive_forms_generator/lib/src/output/x.dart new file mode 100644 index 0000000..753870f --- /dev/null +++ b/packages/reactive_forms_generator/lib/src/output/x.dart @@ -0,0 +1,570 @@ +// ignore_for_file: implementation_imports +import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/nullability_suffix.dart'; +import 'package:analyzer/dart/element/type.dart'; +import 'package:analyzer/src/dart/element/type.dart'; +import 'package:analyzer/src/dart/element/display_string_builder.dart'; +import 'package:analyzer/src/dart/element/element.dart'; +import 'package:analyzer/src/dart/resolver/variance.dart'; +import 'package:analyzer/src/dart/element/type_algebra.dart'; +import 'package:reactive_forms_generator/src/types.dart'; + +class ElementDisplayStringBuilder2 extends ElementDisplayStringBuilder { + final StringBuffer _buffer = StringBuffer(); + + /// Whether to include the nullability ('?' characters) in a display string. + final bool _withNullability; + + /// Whether to allow a display string to be written in multiple lines. + final bool _multiline; + + ElementDisplayStringBuilder2({ + required bool withNullability, + bool multiline = false, + }) : _withNullability = withNullability, + _multiline = multiline, + super(withNullability: false); + + @override + String toString() => _buffer.toString(); + + @override + void writeAbstractElement(ElementImpl element) { + _write(element.name ?? ''); + } + + @override + void writeClassElement(ClassElementImpl element) { + if (element.isAugmentation) { + _write('augment '); + } + + if (element.isSealed) { + _write('sealed '); + } else if (element.isAbstract) { + _write('abstract '); + } + if (element.isBase) { + _write('base '); + } else if (element.isInterface) { + _write('interface '); + } else if (element.isFinal) { + _write('final '); + } + if (element.isMixinClass) { + _write('mixin '); + } + + _write('class '); + _write(element.displayName); + + _writeTypeParameters(element.typeParameters); + + _writeTypeIfNotObject(' extends ', element.supertype); + _writeTypesIfNotEmpty(' with ', element.mixins); + _writeTypesIfNotEmpty(' implements ', element.interfaces); + } + + @override + void writeCompilationUnitElement(CompilationUnitElementImpl element) { + var path = element.source.fullName; + _write(path); + } + + @override + void writeConstructorElement(ConstructorElement element) { + _writeType(element.returnType); + _write(' '); + + _write(element.displayName); + + _writeFormalParameters( + element.parameters, + forElement: true, + allowMultiline: true, + ); + } + + @override + void writeDynamicType() { + _write('dynamic'); + } + + @override + void writeEnumElement(EnumElement element) { + _write('enum '); + _write(element.displayName); + _writeTypeParameters(element.typeParameters); + _writeTypesIfNotEmpty(' with ', element.mixins); + _writeTypesIfNotEmpty(' implements ', element.interfaces); + } + + @override + void writeExecutableElement(ExecutableElement element, String name) { + if (element.isAugmentation) { + _write('augment '); + } + + if (element.kind != ElementKind.SETTER) { + _writeType(element.returnType); + _write(' '); + } + + _write(name); + + if (element.kind != ElementKind.GETTER) { + _writeTypeParameters(element.typeParameters); + _writeFormalParameters( + element.parameters, + forElement: true, + allowMultiline: true, + ); + } + } + + @override + void writeExportElement(LibraryExportElementImpl element) { + _write('export '); + _writeDirectiveUri(element.uri); + } + + @override + void writeExtensionElement(ExtensionElement element) { + _write('extension '); + _write(element.displayName); + _writeTypeParameters(element.typeParameters); + _write(' on '); + _writeType(element.extendedType); + } + + @override + void writeFormalParameter(ParameterElement element) { + if (element.isRequiredPositional) { + _writeWithoutDelimiters(element, forElement: true); + } else if (element.isOptionalPositional) { + _write('['); + _writeWithoutDelimiters(element, forElement: true); + _write(']'); + } else if (element.isNamed) { + _write('{'); + _writeWithoutDelimiters(element, forElement: true); + _write('}'); + } + } + + @override + void writeFunctionType(FunctionType type) { + type = _uniqueTypeParameters(type); + + _writeType(type.returnType); + _write(' Function'); + _writeTypeParameters(type.typeFormals); + _writeFormalParameters(type.parameters, forElement: false); + _writeNullability(type.nullabilitySuffix); + } + + @override + void writeGenericFunctionTypeElement(GenericFunctionTypeElementImpl element) { + _writeType(element.returnType); + _write(' Function'); + _writeTypeParameters(element.typeParameters); + _writeFormalParameters(element.parameters, forElement: true); + } + + @override + void writeImportElement(LibraryImportElementImpl element) { + _write('import '); + _writeDirectiveUri(element.uri); + } + + @override + void writeInterfaceType(InterfaceType type) { + final namePostfix = + type.element.hasRfGroupAnnotation || type.element.hasRfAnnotation + ? 'Output' + : ''; + _write('${type.element.name}$namePostfix'); + _writeTypeArguments(type.typeArguments); + + _writeNullability(type.nullabilitySuffix); + } + + @override + void writeInvalidType() { + _write('InvalidType'); + } + + @override + void writeLibraryElement(LibraryElementImpl element) { + _write('library '); + _write('${element.source.uri}'); + } + + @override + void writeMixinElement(MixinElementImpl element) { + if (element.isAugmentation) { + _write('augment '); + } + if (element.isBase) { + _write('base '); + } + _write('mixin '); + _write(element.displayName); + _writeTypeParameters(element.typeParameters); + _writeTypesIfNotEmpty(' on ', element.superclassConstraints); + _writeTypesIfNotEmpty(' implements ', element.interfaces); + } + + @override + void writeNeverType(NeverType type) { + _write('Never'); + _writeNullability(type.nullabilitySuffix); + } + + @override + void writePartElement(PartElementImpl element) { + _write('part '); + _writeDirectiveUri(element.uri); + } + + @override + void writePrefixElement(PrefixElementImpl element) { + _write('as '); + _write(element.displayName); + } + + @override + void writeRecordType(RecordType type) { + final positionalFields = type.positionalFields; + final namedFields = type.namedFields; + final fieldCount = positionalFields.length + namedFields.length; + _write('('); + + var index = 0; + for (final field in positionalFields) { + _writeType(field.type); + if (index++ < fieldCount - 1) { + _write(', '); + } + } + + if (namedFields.isNotEmpty) { + _write('{'); + for (final field in namedFields) { + _writeType(field.type); + _write(' '); + _write(field.name); + if (index++ < fieldCount - 1) { + _write(', '); + } + } + _write('}'); + } + + // Add trailing comma for record types with only one position field. + if (positionalFields.length == 1 && namedFields.isEmpty) { + _write(','); + } + + _write(')'); + _writeNullability(type.nullabilitySuffix); + } + + @override + void writeTypeAliasElement(TypeAliasElementImpl element) { + _write('typedef '); + _write(element.displayName); + _writeTypeParameters(element.typeParameters); + _write(' = '); + + var aliasedElement = element.aliasedElement; + if (aliasedElement != null) { + aliasedElement.appendTo(this); + } else { + _writeType(element.aliasedType); + } + } + + @override + void writeTypeParameter(TypeParameterElement element) { + if (element is TypeParameterElementImpl) { + var variance = element.variance; + if (!element.isLegacyCovariant && variance != Variance.unrelated) { + _write(variance.toKeywordString()); + _write(' '); + } + } + + _write(element.displayName); + + var bound = element.bound; + if (bound != null) { + _write(' extends '); + _writeType(bound); + } + } + + @override + void writeTypeParameterType(TypeParameterTypeImpl type) { + final promotedBound = type.promotedBound; + if (promotedBound != null) { + final hasSuffix = type.nullabilitySuffix != NullabilitySuffix.none; + if (hasSuffix) { + _write('('); + } + _write(type.element.displayName); + _write(' & '); + _writeType(promotedBound); + if (hasSuffix) { + _write(')'); + } + } else { + _write(type.element.displayName); + } + _writeNullability(type.nullabilitySuffix); + } + + @override + void writeUnknownInferredType() { + _write('_'); + } + + @override + void writeVariableElement(VariableElement element) { + switch (element) { + case FieldElement(isAugmentation: true): + case TopLevelVariableElement(isAugmentation: true): + _write('augment '); + } + + _writeType(element.type); + _write(' '); + _write(element.displayName); + } + + @override + void writeVoidType() { + _write('void'); + } + + void _write(String str) { + _buffer.write(str); + } + + void _writeDirectiveUri(DirectiveUri uri) { + if (uri is DirectiveUriWithUnitImpl) { + _write('unit ${uri.unit.source.uri}'); + } else if (uri is DirectiveUriWithSourceImpl) { + _write('source ${uri.source}'); + } else { + _write(''); + } + } + + void _writeFormalParameters( + List parameters, { + required bool forElement, + bool allowMultiline = false, + }) { + // Assume the display string looks better wrapped when there are at least + // three parameters. This avoids having to pre-compute the single-line + // version and know the length of the function name/return type. + var multiline = allowMultiline && _multiline && parameters.length >= 3; + + // The prefix for open groups is included in separator for single-line but + // not for multiline so must be added explicitly. + var openGroupPrefix = multiline ? ' ' : ''; + var separator = multiline ? ',' : ', '; + var trailingComma = multiline ? ',\n' : ''; + var parameterPrefix = multiline ? '\n ' : ''; + + _write('('); + + _WriteFormalParameterKind? lastKind; + var lastClose = ''; + + void openGroup(_WriteFormalParameterKind kind, String open, String close) { + if (lastKind != kind) { + _write(lastClose); + if (lastKind != null) { + // We only need to include the space before the open group if there + // was a previous parameter, otherwise it goes immediately after the + // open paren. + _write(openGroupPrefix); + } + _write(open); + lastKind = kind; + lastClose = close; + } + } + + for (var i = 0; i < parameters.length; i++) { + if (i != 0) { + _write(separator); + } + + var parameter = parameters[i]; + if (parameter.isRequiredPositional) { + openGroup(_WriteFormalParameterKind.requiredPositional, '', ''); + } else if (parameter.isOptionalPositional) { + openGroup(_WriteFormalParameterKind.optionalPositional, '[', ']'); + } else if (parameter.isNamed) { + openGroup(_WriteFormalParameterKind.named, '{', '}'); + } + _write(parameterPrefix); + _writeWithoutDelimiters(parameter, forElement: forElement); + } + + _write(trailingComma); + _write(lastClose); + _write(')'); + } + + void _writeNullability(NullabilitySuffix nullabilitySuffix) { + if (_withNullability) { + switch (nullabilitySuffix) { + case NullabilitySuffix.question: + _write('?'); + case NullabilitySuffix.star: + _write('*'); + case NullabilitySuffix.none: + } + } + } + + void _writeType(DartType type) { + (type as TypeImpl).appendTo(this); + } + + void _writeTypeArguments(List typeArguments) { + if (typeArguments.isEmpty) { + return; + } + + _write('<'); + for (var i = 0; i < typeArguments.length; i++) { + if (i != 0) { + _write(', '); + } + (typeArguments[i] as TypeImpl).appendTo(this); + } + _write('>'); + } + + void _writeTypeIfNotObject(String prefix, DartType? type) { + if (type != null && !type.isDartCoreObject) { + _write(prefix); + _writeType(type); + } + } + + void _writeTypeParameters(List elements) { + if (elements.isEmpty) return; + + _write('<'); + for (var i = 0; i < elements.length; i++) { + if (i != 0) { + _write(', '); + } + (elements[i] as TypeParameterElementImpl).appendTo(this); + } + _write('>'); + } + + void _writeTypes(List types) { + for (var i = 0; i < types.length; i++) { + if (i != 0) { + _write(', '); + } + _writeType(types[i]); + } + } + + void _writeTypesIfNotEmpty(String prefix, List types) { + if (types.isNotEmpty) { + _write(prefix); + _writeTypes(types); + } + } + + void _writeWithoutDelimiters( + ParameterElement element, { + required bool forElement, + }) { + if (element.isRequiredNamed) { + _write('required '); + } + + _writeType(element.type); + + if (forElement || element.isNamed) { + _write(' '); + _write(element.displayName); + } + + if (forElement) { + var defaultValueCode = element.defaultValueCode; + if (defaultValueCode != null) { + _write(' = '); + _write(defaultValueCode); + } + } + } + + static FunctionType _uniqueTypeParameters(FunctionType type) { + if (type.typeFormals.isEmpty) { + return type; + } + + var referencedTypeParameters = {}; + + void collectTypeParameters(DartType? type) { + if (type is TypeParameterType) { + referencedTypeParameters.add(type.element); + } else if (type is FunctionType) { + for (var typeParameter in type.typeFormals) { + collectTypeParameters(typeParameter.bound); + } + for (var parameter in type.parameters) { + collectTypeParameters(parameter.type); + } + collectTypeParameters(type.returnType); + } else if (type is InterfaceType) { + for (var typeArgument in type.typeArguments) { + collectTypeParameters(typeArgument); + } + } + } + + collectTypeParameters(type); + referencedTypeParameters.removeAll(type.typeFormals); + + var namesToAvoid = {}; + for (var typeParameter in referencedTypeParameters) { + namesToAvoid.add(typeParameter.displayName); + } + + var newTypeParameters = []; + for (var typeParameter in type.typeFormals) { + var name = typeParameter.name; + for (var counter = 0; !namesToAvoid.add(name); counter++) { + const unicodeSubscriptZero = 0x2080; + const unicodeZero = 0x30; + + var subscript = String.fromCharCodes('$counter'.codeUnits.map((n) { + return unicodeSubscriptZero + (n - unicodeZero); + })); + + name = typeParameter.name + subscript; + } + + var newTypeParameter = TypeParameterElementImpl(name, -1); + newTypeParameter.bound = typeParameter.bound; + newTypeParameters.add(newTypeParameter); + } + + return replaceTypeParameters(type as FunctionTypeImpl, newTypeParameters); + } +} + +enum _WriteFormalParameterKind { requiredPositional, optionalPositional, named } diff --git a/packages/reactive_forms_generator/lib/src/reactive_forms_generator/field_value_method.dart b/packages/reactive_forms_generator/lib/src/reactive_forms_generator/field_value_method.dart index 682af82..d91e014 100644 --- a/packages/reactive_forms_generator/lib/src/reactive_forms_generator/field_value_method.dart +++ b/packages/reactive_forms_generator/lib/src/reactive_forms_generator/field_value_method.dart @@ -1,8 +1,14 @@ +// ignore_for_file: implementation_imports + +import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/nullability_suffix.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:code_builder/code_builder.dart'; +import 'package:analyzer/src/dart/element/type.dart'; import 'package:reactive_forms_generator/src/extensions.dart'; +import 'package:reactive_forms_generator/src/output/x.dart'; import 'package:reactive_forms_generator/src/reactive_form_generator_method.dart'; +import 'package:reactive_forms_generator/src/types.dart'; class FieldValueMethod extends ReactiveFormGeneratorMethod { FieldValueMethod(super.field, super.output); @@ -65,12 +71,46 @@ class FieldValueMethod extends ReactiveFormGeneratorMethod { } Method get methodEntity => Method( - (b) => b - ..name = field.fieldValueName - ..lambda = true - ..type = MethodType.getter - ..returns = Reference( - field.type.getDisplayString(withNullability: !toOutput), - ), + (b) { + b + ..name = field.fieldValueName + ..lambda = true + ..type = MethodType.getter + ..returns = Reference( + output ? field.toReferenceType : field.type.toString(), + ); + }, ); } + +extension Care on ClassElement { + String get toReferenceType { + var builder = ElementDisplayStringBuilder2(withNullability: true); + (thisType as TypeImpl).appendTo(builder); + return builder.toString(); + } +} + +extension Pare on ParameterElement { + String get toReferenceType { + if (hasRfControlAnnotation && + annotationParams(formControlChecker).hasRequiredValidator) { + return type.getDisplayString(withNullability: false); + } + + var builder = ElementDisplayStringBuilder2(withNullability: true); + (type as TypeImpl).appendTo(builder); + return builder.toString(); + } +} + +// class RfElementDisplayStringBuilder extends ElementDisplayStringBuilder { +// RfElementDisplayStringBuilder({required super.withNullability}); +// +// @override +// void writeInterfaceType(InterfaceType type) { +// _write(type.element.name); +// _writeTypeArguments(type.typeArguments); +// _writeNullability(type.nullabilitySuffix); +// } +// } diff --git a/packages/reactive_forms_generator/lib/src/types.dart b/packages/reactive_forms_generator/lib/src/types.dart index 4bb9a1f..e5ba467 100644 --- a/packages/reactive_forms_generator/lib/src/types.dart +++ b/packages/reactive_forms_generator/lib/src/types.dart @@ -42,6 +42,14 @@ extension ElementRfExt on Element { return formGroupChecker.hasAnnotationOfExact(this); } + bool get hasRfAnnotation { + return _formChecker.hasAnnotationOfExact(this); + } + + bool get hasRfControlAnnotation { + return formControlChecker.hasAnnotationOfExact(this); + } + Map annotationParams(TypeChecker? typeChecker) { final result = {}; final annotation = typeChecker?.firstAnnotationOf(this);