LCOV - code coverage report
Current view: top level - src - class_generator.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 284 293 96.9 %
Date: 2020-01-29 07:35:46 Functions: 0 0 -

          Line data    Source code
       1             : import 'package:analyzer/dart/constant/value.dart';
       2             : import 'package:analyzer/dart/element/element.dart';
       3             : import 'package:code_builder/code_builder.dart';
       4             : import 'package:dart_style/dart_style.dart';
       5             : import 'package:source_gen/source_gen.dart';
       6             : import 'package:super_enum/super_enum.dart';
       7             : import 'package:super_enum_generator/src/extension.dart';
       8             : import 'package:super_enum_generator/src/references.dart' as references;
       9             : import 'package:super_enum_generator/src/type_processor.dart' as type_processor;
      10             : 
      11             : class ClassGenerator {
      12             :   final ClassElement element;
      13             : 
      14           1 :   const ClassGenerator(this.element);
      15             : 
      16           4 :   Iterable<FieldElement> get _fields => element.fields.skip(2);
      17             : 
      18           3 :   bool get _isNamespaceGeneric => _fields.any(type_processor.isGeneric);
      19             : 
      20           1 :   String generate(DartFormatter _dartFmt) {
      21           4 :     if (!element.isEnum || !element.isPrivate) {
      22           0 :       throw InvalidGenerationSourceError(
      23           0 :           '${element.name} must be a private Enum');
      24             :     }
      25             : 
      26             :     try {
      27           2 :       final cls = Class((c) => c
      28           5 :         ..name = ('${element.name.replaceFirst('_', '')}')
      29           2 :         ..annotations.add(references.immutable)
      30           5 :         ..types.addAll(_isNamespaceGeneric ? [references.generic_T] : [])
      31           1 :         ..abstract = true
      32           1 :         ..extend = references.equatable
      33           4 :         ..fields.add(Field((f) => f
      34           1 :           ..name = '_type'
      35           1 :           ..modifier = FieldModifier.final$
      36           4 :           ..type = refer(element.name)
      37           1 :           ..build()))
      38           3 :         ..constructors.addAll(_generateClassConstructors)
      39           3 :         ..methods.add(_generateWhenMethod)
      40           3 :         ..methods.add(_generateWhenOrElseMethod)
      41           3 :         ..methods.add(_generateWhenPartialMethod)
      42           4 :         ..methods.add(Method((m) {
      43             :           return m
      44           1 :             ..name = 'props'
      45           1 :             ..lambda = true
      46           1 :             ..returns = references.dynamic_list
      47           2 :             ..annotations.add(references.override)
      48           1 :             ..type = MethodType.getter
      49           2 :             ..body = Code('const []')
      50           1 :             ..build();
      51             :         }))
      52           1 :         ..build());
      53             : 
      54           1 :       final emitter = DartEmitter();
      55           4 :       return _dartFmt.format('${cls.accept(emitter)}$_generateDerivedClasses');
      56             :     } catch (e, stackTrace) {
      57           0 :       return "/*$e*/";
      58             :     }
      59             :   }
      60             : 
      61           1 :   Method get _generateWhenMethod {
      62           1 :     final List<Parameter> _params = [];
      63           1 :     final StringBuffer _bodyBuffer = StringBuffer();
      64             : 
      65             :     final assertionCondition =
      66           7 :         _fields.map((f) => '${getCamelCase(f.name)} == null').join(' || ');
      67             : 
      68           1 :     _bodyBuffer.write(
      69             :       "assert(() {"
      70             :       "if ($assertionCondition) throw 'check for all possible cases';"
      71             :       "return true;"
      72             :       "}());",
      73             :     );
      74             : 
      75           1 :     _bodyBuffer.writeln('switch(this._type){');
      76             : 
      77           2 :     for (var field in _fields) {
      78             :       final DartObject usedClass =
      79           1 :           type_processor.usedClassFromAnnotation(field);
      80           5 :       _bodyBuffer.writeln('case ${element.name}.${field.name}:');
      81             :       if (usedClass != null) {
      82           3 :         _bodyBuffer.writeln('return ${getCamelCase(field.name)}'
      83           2 :             '((this as ${usedClass.toTypeValue().name}Wrapper)'
      84           3 :             '.${getCamelCase(usedClass.toTypeValue().name)});');
      85             :       } else {
      86           1 :         _bodyBuffer.writeln(
      87           4 :             'return ${getCamelCase(field.name)}(this as ${field.name});');
      88             :       }
      89             : 
      90             :       final callbackArgType = usedClass != null
      91           2 :           ? '${usedClass.toTypeValue().name}'
      92           3 :           : '${field.name}';
      93           3 :       _params.add(Parameter((p) {
      94             :         return p
      95           3 :           ..name = '${getCamelCase(field.name)}'
      96           2 :           ..named = true
      97           2 :           ..annotations.add(references.required)
      98           3 :           ..type = refer('FutureOr<R> Function(${callbackArgType})')
      99           1 :           ..build();
     100             :       }));
     101             :     }
     102             : 
     103           1 :     _bodyBuffer.writeln('}');
     104             : 
     105           2 :     return Method((m) => m
     106           1 :       ..name = 'when'
     107           2 :       ..types.add(references.generic_R)
     108           1 :       ..returns = references.futureOr_Generic_R
     109           2 :       ..docs.add('//ignore: missing_return')
     110           2 :       ..optionalParameters.addAll(_params)
     111           3 :       ..body = Code(_bodyBuffer.toString())
     112           1 :       ..build());
     113             :   }
     114             : 
     115           1 :   Method get _generateWhenOrElseMethod {
     116           1 :     final List<Parameter> _params = [];
     117           1 :     final StringBuffer _bodyBuffer = StringBuffer();
     118             : 
     119           1 :     _bodyBuffer.write(
     120             :       "assert(() {"
     121             :       "if (orElse == null) throw 'Missing orElse case';"
     122             :       "return true;"
     123             :       "}());",
     124             :     );
     125             : 
     126           1 :     _bodyBuffer.writeln('switch(this._type){');
     127             : 
     128           2 :     for (var field in _fields) {
     129             :       final DartObject usedClass =
     130           1 :           type_processor.usedClassFromAnnotation(field);
     131           5 :       _bodyBuffer.writeln('case ${element.name}.${field.name}:');
     132           4 :       _bodyBuffer.writeln('if (${getCamelCase(field.name)} == null) break;');
     133             :       if (usedClass != null) {
     134           3 :         _bodyBuffer.writeln('return ${getCamelCase(field.name)}'
     135           2 :             '((this as ${usedClass.toTypeValue().name}Wrapper)'
     136           3 :             '.${getCamelCase(usedClass.toTypeValue().name)});');
     137             :       } else {
     138           1 :         _bodyBuffer.writeln(
     139           4 :             'return ${getCamelCase(field.name)}(this as ${field.name});');
     140             :       }
     141             : 
     142             :       final callbackArgType = usedClass != null
     143           2 :           ? '${usedClass.toTypeValue().name}'
     144           3 :           : '${field.name}';
     145           3 :       _params.add(Parameter((p) => p
     146           3 :         ..name = '${getCamelCase(field.name)}'
     147           2 :         ..named = true
     148           3 :         ..type = refer('FutureOr<R> Function($callbackArgType)')
     149           1 :         ..build()));
     150             :     }
     151             : 
     152           3 :     _params.add(Parameter((p) => p
     153           1 :       ..name = 'orElse'
     154           1 :       ..named = true
     155           2 :       ..annotations.add(references.required)
     156           1 :       ..type =
     157           5 :           refer('FutureOr<R> Function(${element.name.replaceFirst('_', '')})')
     158           1 :       ..build()));
     159             : 
     160           1 :     _bodyBuffer.write(
     161             :       '}'
     162             :       'return orElse(this);',
     163             :     );
     164             : 
     165           2 :     return Method((m) => m
     166           1 :       ..name = 'whenOrElse'
     167           2 :       ..types.add(references.generic_R)
     168           1 :       ..returns = references.futureOr_Generic_R
     169           2 :       ..optionalParameters.addAll(_params)
     170           3 :       ..body = Code(_bodyBuffer.toString())
     171           1 :       ..build());
     172             :   }
     173             : 
     174           1 :   Method get _generateWhenPartialMethod {
     175           1 :     final List<Parameter> _params = [];
     176           1 :     final StringBuffer _bodyBuffer = StringBuffer();
     177             : 
     178             :     final assertionCondition =
     179           7 :         _fields.map((f) => '${getCamelCase(f.name)} == null').join(' && ');
     180             : 
     181           1 :     _bodyBuffer.write(
     182             :       "assert(() {"
     183             :       "if ($assertionCondition) throw 'provide at least one branch';"
     184             :       "return true;"
     185             :       "}());",
     186             :     );
     187             : 
     188           1 :     _bodyBuffer.writeln('switch(this._type){');
     189             : 
     190           2 :     for (var field in _fields) {
     191             :       final DartObject usedClass =
     192           1 :           type_processor.usedClassFromAnnotation(field);
     193           5 :       _bodyBuffer.writeln('case ${element.name}.${field.name}:');
     194           4 :       _bodyBuffer.writeln('if (${getCamelCase(field.name)} == null) break;');
     195             :       if (usedClass != null) {
     196           3 :         _bodyBuffer.writeln('return ${getCamelCase(field.name)}'
     197           2 :             '((this as ${usedClass.toTypeValue().name}Wrapper)'
     198           3 :             '.${getCamelCase(usedClass.toTypeValue().name)});');
     199             :       } else {
     200           1 :         _bodyBuffer.writeln(
     201           4 :             'return ${getCamelCase(field.name)}(this as ${field.name});');
     202             :       }
     203             : 
     204             :       final callbackArgType = usedClass != null
     205           2 :           ? '${usedClass.toTypeValue().name}'
     206           3 :           : '${field.name}';
     207           3 :       _params.add(Parameter((p) => p
     208           3 :         ..name = '${getCamelCase(field.name)}'
     209           2 :         ..named = true
     210           3 :         ..type = refer('FutureOr<void> Function($callbackArgType)')
     211           1 :         ..build()));
     212             :     }
     213             : 
     214           1 :     _bodyBuffer.writeln('}');
     215             : 
     216           2 :     return Method((m) => m
     217           1 :       ..name = 'whenPartial'
     218           1 :       ..returns = references.futureOr
     219           2 :       ..optionalParameters.addAll(_params)
     220           3 :       ..body = Code(_bodyBuffer.toString())
     221           1 :       ..build());
     222             :   }
     223             : 
     224           1 :   Iterable<Constructor> get _generateClassConstructors {
     225           2 :     final defaultConstructor = Constructor((constructor) => constructor
     226           1 :       ..constant = true
     227           4 :       ..requiredParameters.add(Parameter((p) => p
     228           1 :         ..name = 'this._type'
     229           1 :         ..build()))
     230           1 :       ..build());
     231           3 :     final fieldConstructors = _fields.map((field) {
     232             :       final annotation =
     233           2 :           TypeChecker.fromRuntime(UseClass).firstAnnotationOfExact(field);
     234             :       var redirectConstructorName =
     235           3 :           '${field.name}${_isNamespaceGeneric ? '<T>' : ''}';
     236           1 :       final reqParams = <Parameter>[];
     237             :       if (annotation != null) {
     238           1 :         final DartObject usedClass = annotation.getField('type');
     239           3 :         redirectConstructorName = "${usedClass.toTypeValue().name}Wrapper";
     240           3 :         reqParams.add(Parameter((p) => p
     241           4 :           ..name = '${getCamelCase(usedClass.toTypeValue().name)}'
     242           5 :           ..type = Reference(usedClass.toTypeValue().name)
     243           1 :           ..build()));
     244             :       }
     245           2 :       return Constructor((constructor) => constructor
     246           1 :         ..factory = true
     247           3 :         ..name = '${getCamelCase(field.name)}'
     248           4 :         ..optionalParameters.addAll(type_processor.hasAnnotation<Data>(field)
     249           1 :             ? _generateClassConstructorFields(field)
     250           1 :             : [])
     251           2 :         ..requiredParameters.addAll(reqParams)
     252           2 :         ..redirect = refer(redirectConstructorName)
     253           1 :         ..build());
     254             :     });
     255           2 :     return [defaultConstructor].followedBy(fieldConstructors);
     256             :   }
     257             : 
     258           1 :   Iterable<Parameter> _generateClassConstructorFields(FieldElement element) {
     259           1 :     final fields = type_processor.listTypeFieldOf<Data>(element, 'fields');
     260           4 :     return fields.map((e) => Parameter((f) => f
     261           2 :       ..name = '${type_processor.dataFieldName(e)}'
     262           4 :       ..type = type_processor.dataFieldType(e) != "Generic"
     263           2 :           ? refer(type_processor.dataFieldType(e))
     264             :           : references.generic_T
     265           1 :       ..named = true
     266           2 :       ..annotations.add(references.required)
     267           1 :       ..build()));
     268             :   }
     269             : 
     270           2 :   String get _generateDerivedClasses => _fields
     271           2 :       .map((field) {
     272           1 :         if (type_processor.hasAnnotation<Object>(field)) {
     273           4 :           return '${_generateObjectClass(field).accept(DartEmitter())}';
     274           1 :         } else if (type_processor.hasAnnotation<Data>(field)) {
     275           2 :           if (type_processor.listTypeFieldOf<Data>(field, 'fields')?.isEmpty ??
     276             :               true) {
     277           0 :             throw InvalidGenerationSourceError(
     278             :                 'Data annotation must contain at least one DataField');
     279             :           }
     280           4 :           return '${_generateDataClass(field).accept(DartEmitter())}';
     281           1 :         } else if (type_processor.hasAnnotation<UseClass>(field)) {
     282           4 :           return '${_generateClassWrapper(field).accept(DartEmitter())}';
     283             :         } else {
     284             :           return null;
     285             :         }
     286             :       })
     287           2 :       .where((e) => e != null)
     288           1 :       .join('');
     289             : 
     290           1 :   Class _generateObjectClass(FieldElement field) {
     291           1 :     final isGeneric = type_processor.isGeneric(field);
     292             : 
     293             :     if (isGeneric) {
     294           0 :       throw InvalidGenerationSourceError(
     295             :           'Can\'t use @generic on object classes');
     296             :     }
     297             : 
     298           2 :     return Class((c) => c
     299           2 :       ..name = '${field.name}'
     300           5 :       ..types.addAll(_isNamespaceGeneric ? [references.generic_T] : [])
     301           4 :       ..fields.add(Field((f) => f
     302           1 :         ..name = '_instance'
     303           1 :         ..static = true
     304           4 :         ..type = refer('${field.name}')
     305           1 :         ..build()))
     306           4 :       ..constructors.add(Constructor((c) => c
     307           1 :         ..constant = true
     308           1 :         ..name = '_'
     309           7 :         ..initializers.add(Code('super(${element.name}.${field.name})'))
     310           1 :         ..build()))
     311           4 :       ..constructors.add(Constructor((c) => c
     312           1 :         ..factory = true
     313           2 :         ..body = Code('''
     314           1 :         _instance ??= ${field.name}._();
     315             :         return _instance;
     316           1 :         ''')
     317           1 :         ..build()))
     318           2 :       ..extend = refer(
     319           5 :           '${element.name.replaceFirst('_', '')}${_isNamespaceGeneric ? '<T>' : ''}')
     320           2 :       ..annotations.add(references.immutable)
     321           1 :       ..build());
     322             :   }
     323             : 
     324           1 :   Class _generateDataClass(FieldElement field) {
     325           1 :     final _classFields = type_processor.listTypeFieldOf<Data>(field, 'fields');
     326           1 :     final isGeneric = type_processor.isGeneric(field);
     327             : 
     328           2 :     Method toString = Method((m) {
     329             :       final String values = _classFields
     330           2 :           .map((f) =>
     331           3 :               '${type_processor.dataFieldName(f)}:\${this.${type_processor.dataFieldName(f)}}')
     332           1 :           .join(',');
     333             :       return m
     334           1 :         ..name = 'toString'
     335           1 :         ..lambda = true
     336           2 :         ..annotations.add(references.override)
     337           4 :         ..body = Code("'${field.name}($values)'")
     338           1 :         ..returns = references.string
     339           1 :         ..build();
     340             :     });
     341             : 
     342           2 :     Method getProps = Method((m) {
     343             :       final String values = _classFields
     344           4 :           .map((f) => '${type_processor.dataFieldName(f)}')
     345           1 :           .join(',');
     346             :       return m
     347           1 :         ..name = 'props'
     348           1 :         ..lambda = true
     349           1 :         ..returns = references.dynamic_list
     350           2 :         ..annotations.add(references.override)
     351           1 :         ..type = MethodType.getter
     352           3 :         ..body = Code('[$values]')
     353           1 :         ..build();
     354             :     });
     355             : 
     356             :     if (isGeneric) {
     357             :       if (_classFields
     358           4 :           .every((e) => type_processor.dataFieldType(e) != "Generic")) {
     359           0 :         throw InvalidGenerationSourceError(
     360           0 :             '${field.name} must have atleast one Generic field');
     361             :       }
     362             :     }
     363             : 
     364           4 :     if (_classFields.any((e) => type_processor.dataFieldType(e) == "Generic")) {
     365             :       if (!isGeneric) {
     366           0 :         throw InvalidGenerationSourceError(
     367           0 :             '${field.name} must be annotated with @generic');
     368             :       }
     369             :     }
     370             : 
     371           2 :     return Class((c) => c
     372           2 :       ..name = '${field.name}'
     373           3 :       ..extend = refer(
     374           5 :           '${element.name.replaceFirst('_', '')}${_isNamespaceGeneric ? '<T>' : ''}')
     375           2 :       ..annotations.add(references.immutable)
     376           3 :       ..methods.addAll([toString, getProps])
     377           4 :       ..types.addAll(_isNamespaceGeneric ? [references.generic_T] : [])
     378           6 :       ..fields.addAll(_classFields.map((e) => Field((f) => f
     379           2 :         ..name = type_processor.dataFieldName(e)
     380           1 :         ..modifier = FieldModifier.final$
     381           3 :         ..type = type_processor.dataFieldType(e) != "Generic"
     382           2 :             ? refer(type_processor.dataFieldType(e))
     383             :             : references.generic_T
     384           1 :         ..build())))
     385           4 :       ..constructors.add(Constructor((constructor) => constructor
     386           1 :         ..constant = true
     387           7 :         ..initializers.add(Code('super(${element.name}.${field.name})'))
     388           6 :         ..optionalParameters.addAll(_classFields.map((e) => Parameter((f) => f
     389           2 :           ..name = 'this.${type_processor.dataFieldName(e)}'
     390           2 :           ..named = true
     391           2 :           ..annotations.add(references.required)
     392           1 :           ..build())))
     393           1 :         ..build()))
     394           1 :       ..build());
     395             :   }
     396             : 
     397           1 :   Class _generateClassWrapper(FieldElement field) {
     398           1 :     final usedClass = type_processor.usedClassFromAnnotation(field);
     399           2 :     final usedClassType = usedClass.toTypeValue().name;
     400             : 
     401           2 :     Method toString = Method((m) {
     402             :       return m
     403           1 :         ..name = 'toString'
     404           1 :         ..lambda = true
     405           2 :         ..annotations.add(references.override)
     406           1 :         ..body =
     407           3 :             Code("'${usedClassType}Wrapper(\$${getCamelCase(usedClassType)})'")
     408           1 :         ..returns = references.string
     409           1 :         ..build();
     410             :     });
     411             : 
     412           2 :     Method getProps = Method((m) {
     413             :       return m
     414           1 :         ..name = 'props'
     415           1 :         ..lambda = true
     416           1 :         ..returns = references.dynamic_list
     417           2 :         ..annotations.add(references.override)
     418           1 :         ..type = MethodType.getter
     419           4 :         ..body = Code('[${getCamelCase(usedClassType)}]')
     420           1 :         ..build();
     421             :     });
     422             : 
     423           2 :     return Class((c) => c
     424           1 :       ..name = '${usedClassType}Wrapper'
     425           3 :       ..annotations.add(references.immutable)
     426           4 :       ..fields.add(Field((f) {
     427             :         return f
     428           4 :           ..name = getCamelCase(usedClass.toTypeValue().name)
     429           1 :           ..modifier = FieldModifier.final$
     430           4 :           ..type = Reference(usedClass.toTypeValue().name);
     431             :       }))
     432           3 :       ..methods.addAll([toString, getProps])
     433           6 :       ..extend = refer('${element.name.replaceFirst('_', '')}')
     434           4 :       ..constructors.add(Constructor((constructor) {
     435             :         return constructor
     436           1 :           ..constant = true
     437           7 :           ..initializers.add(Code('super(${element.name}.${field.name})'))
     438           2 :           ..requiredParameters.add(
     439           2 :             Parameter((p) => p
     440           2 :               ..name = "this.${getCamelCase(usedClassType)}"
     441           2 :               ..named = false
     442           1 :               ..build()),
     443             :           );
     444             :       }))
     445           1 :       ..build());
     446             :   }
     447             : }

Generated by: LCOV version 1.13