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 : }
|