platform/packages/serialize/angel_serialize_generator/lib/model.dart

339 lines
11 KiB
Dart
Raw Normal View History

2021-05-15 14:37:52 +00:00
part of angel3_serialize_generator;
2018-02-27 19:42:06 +00:00
2018-02-28 00:36:53 +00:00
class JsonModelGenerator extends GeneratorForAnnotation<Serializable> {
2019-01-09 19:25:05 +00:00
const JsonModelGenerator();
2018-02-28 00:36:53 +00:00
@override
Future<String> generateForAnnotatedElement(
Element element, ConstantReader annotation, BuildStep buildStep) async {
2019-07-04 18:32:34 +00:00
if (element.kind != ElementKind.CLASS) {
2018-02-28 00:36:53 +00:00
throw 'Only classes can be annotated with a @Serializable() annotation.';
2019-07-04 18:32:34 +00:00
}
2018-02-28 00:36:53 +00:00
var ctx = await buildContext(element as ClassElement, annotation, buildStep,
2021-05-18 11:32:47 +00:00
buildStep.resolver, true);
2018-02-28 00:36:53 +00:00
2021-08-08 03:10:35 +00:00
if (ctx == null) {
2022-02-27 04:16:31 +00:00
log.fine('Invalid builder context');
2021-08-08 03:10:35 +00:00
throw 'Invalid builder context';
}
2019-04-30 15:44:01 +00:00
var lib = Library((b) {
2018-02-28 01:49:14 +00:00
generateClass(ctx, b, annotation);
2018-02-28 00:36:53 +00:00
});
2021-08-08 03:10:35 +00:00
var buf = lib.accept(DartEmitter(useNullSafetySyntax: true));
2018-02-28 00:36:53 +00:00
return buf.toString();
}
/// Generate an extended model class.
2018-02-28 01:49:14 +00:00
void generateClass(
2021-08-08 03:10:35 +00:00
BuildContext ctx, LibraryBuilder file, ConstantReader annotation) {
2019-04-30 15:44:01 +00:00
file.body.add(Class((clazz) {
2021-08-15 08:04:21 +00:00
//log.fine('Generate Class: ${ctx.modelClassNameRecase.pascalCase}');
clazz
2021-08-08 03:10:35 +00:00
..name = ctx.modelClassNameRecase.pascalCase
..annotations.add(refer('generatedSerializable'));
2018-06-27 05:36:57 +00:00
2019-01-07 00:56:05 +00:00
for (var ann in ctx.includeAnnotations) {
clazz.annotations.add(convertObject(ann));
}
2018-06-27 05:36:57 +00:00
if (shouldBeConstant(ctx)) {
2019-04-30 15:44:01 +00:00
clazz.implements.add(Reference(ctx.originalClassName));
2018-06-27 05:36:57 +00:00
} else {
2019-04-30 15:44:01 +00:00
clazz.extend = Reference(ctx.originalClassName);
2018-06-27 05:36:57 +00:00
}
2018-02-28 00:36:53 +00:00
2018-05-15 19:33:57 +00:00
//if (ctx.importsPackageMeta)
2019-04-30 15:44:01 +00:00
// clazz.annotations.add(CodeExpression(Code('immutable')));
2018-05-15 19:33:57 +00:00
2021-08-08 03:10:35 +00:00
// Generate the fields for the class
2018-02-28 00:36:53 +00:00
for (var field in ctx.fields) {
2021-08-15 08:04:21 +00:00
//log.fine('Generate Field: ${field.name}');
2019-04-30 15:44:01 +00:00
clazz.fields.add(Field((b) {
2018-02-28 00:36:53 +00:00
b
..name = field.name
2021-08-08 03:10:35 +00:00
//..modifier = FieldModifier.final$
//..annotations.add(CodeExpression(Code('override')))
..annotations.add(refer('override'))
2018-02-28 00:36:53 +00:00
..type = convertTypeReference(field.type);
2019-04-08 15:00:04 +00:00
2019-07-04 18:03:20 +00:00
// Fields should only be forced-final if the original field has no setter.
2021-08-08 03:10:35 +00:00
//log.fine('Final: ${field.isFinal}');
//if (field.setter == null && field is! ShimFieldImpl) {
//if (field.isFinal) {
// b.modifier = FieldModifier.final$;
//}
2019-07-04 18:03:20 +00:00
2019-04-08 15:00:04 +00:00
for (var el in [field.getter, field]) {
2021-08-08 03:10:35 +00:00
//if (el?.documentationComment != null) {
b.docs.addAll(el?.documentationComment?.split('\n') ?? []);
//}
2019-04-08 15:00:04 +00:00
}
2018-02-28 00:36:53 +00:00
}));
}
generateConstructor(ctx, clazz, file);
generateCopyWithMethod(ctx, clazz, file);
2018-05-13 18:02:47 +00:00
generateEqualsOperator(ctx, clazz, file);
2018-11-03 07:36:59 +00:00
generateHashCode(ctx, clazz);
2019-04-08 15:00:04 +00:00
generateToString(ctx, clazz);
2018-02-28 01:49:14 +00:00
// Generate toJson() method if necessary
var serializers = annotation.peek('serializers')?.listValue ?? [];
if (serializers.any((o) => o.toIntValue() == Serializers.json)) {
2019-04-30 15:44:01 +00:00
clazz.methods.add(Method((method) {
2018-02-28 01:49:14 +00:00
method
..name = 'toJson'
2019-04-30 15:44:01 +00:00
..returns = Reference('Map<String, dynamic>')
..body = Code('return ${clazz.name}Serializer.toMap(this);');
2018-02-28 01:49:14 +00:00
}));
}
2018-02-28 00:36:53 +00:00
}));
}
2018-06-27 05:36:57 +00:00
bool shouldBeConstant(BuildContext ctx) {
// Check if all fields are without a getter
2021-02-14 05:22:25 +00:00
return !isAssignableToModel(ctx.clazz.thisType) &&
2018-06-27 05:51:21 +00:00
ctx.clazz.fields.every((f) =>
f.getter?.isAbstract != false && f.setter?.isAbstract != false);
2018-06-27 05:36:57 +00:00
}
2018-02-28 00:36:53 +00:00
/// Generate a constructor with named parameters.
void generateConstructor(
2021-08-08 03:10:35 +00:00
BuildContext ctx, ClassBuilder clazz, LibraryBuilder file) {
2019-04-30 15:44:01 +00:00
clazz.constructors.add(Constructor((constructor) {
2018-05-13 17:23:40 +00:00
// Add all `super` params
2021-08-15 08:04:21 +00:00
//constructor.constant = (ctx.clazz.unnamedConstructor?.isConst == true ||
// shouldBeConstant(ctx)) &&
// ctx.fields.every((f) {
// return f.setter == null && f is! ShimFieldImpl;
// });
2018-05-15 19:01:13 +00:00
for (var param in ctx.constructorParameters) {
2021-08-08 03:10:35 +00:00
//log.fine('Contructor Parameter: ${param.name}');
2019-04-30 15:44:01 +00:00
constructor.requiredParameters.add(Parameter((b) => b
2018-05-15 19:01:13 +00:00
..name = param.name
..type = convertTypeReference(param.type)));
}
2021-08-17 04:52:08 +00:00
// Generate intializers
2018-05-15 19:01:13 +00:00
for (var field in ctx.fields) {
2018-06-27 05:45:46 +00:00
if (!shouldBeConstant(ctx) && isListOrMapType(field.type)) {
2021-02-14 05:22:25 +00:00
var typeName = const TypeChecker.fromRuntime(List)
2018-05-15 19:01:13 +00:00
.isAssignableFromType(field.type)
? 'List'
: 'Map';
2021-05-02 06:02:08 +00:00
String? defaultValue = typeName == 'List' ? '[]' : '{}';
2018-12-31 01:18:44 +00:00
2022-02-27 04:16:31 +00:00
var existingDefault = ctx.defaults[field.name];
2018-12-31 01:18:44 +00:00
if (existingDefault != null) {
defaultValue = dartObjectToString(existingDefault);
}
2021-08-17 04:52:08 +00:00
if (field.type.nullabilitySuffix != NullabilitySuffix.question) {
constructor.initializers.add(Code('''
${field.name} =
$typeName.unmodifiable(${field.name})'''));
} else {
constructor.initializers.add(Code('''
${field.name} =
2019-04-30 15:44:01 +00:00
$typeName.unmodifiable(${field.name} ?? $defaultValue)'''));
2021-08-17 04:52:08 +00:00
}
2018-05-13 17:23:40 +00:00
}
2018-05-15 19:01:13 +00:00
}
2018-05-13 17:23:40 +00:00
2021-08-08 03:10:35 +00:00
// Generate the parameters for the constructor
2018-02-28 00:36:53 +00:00
for (var field in ctx.fields) {
2021-08-08 03:10:35 +00:00
//log.fine('Contructor Field: ${field.name}');
2019-04-30 15:44:01 +00:00
constructor.optionalParameters.add(Parameter((b) {
2018-02-28 00:36:53 +00:00
b
2018-06-27 05:45:46 +00:00
..toThis = shouldBeConstant(ctx)
2018-02-28 00:36:53 +00:00
..name = field.name
2018-05-15 19:01:13 +00:00
..named = true;
2018-12-31 01:18:44 +00:00
var existingDefault = ctx.defaults[field.name];
if (existingDefault != null) {
2021-08-17 04:52:08 +00:00
var d = dartObjectToString(existingDefault);
if (d != null) {
b.defaultTo = Code(d);
}
2018-12-31 01:18:44 +00:00
}
2021-08-17 04:52:08 +00:00
//log.fine(
// 'Constructor => ${field.name} ${field.type.nullabilitySuffix}');
2019-07-04 18:32:34 +00:00
if (!isListOrMapType(field.type)) {
2018-05-15 19:01:13 +00:00
b.toThis = true;
2021-08-17 04:52:08 +00:00
} else if (isListType(field.type)) {
if (!b.toThis) {
b.type = convertTypeReference(field.type);
}
2022-02-27 04:16:31 +00:00
// Get the default if presence
var existingDefault = ctx.defaults[field.name];
if (existingDefault != null) {
var defaultValue = dartObjectToString(existingDefault);
b.defaultTo = Code('$defaultValue');
} else {
b.defaultTo = Code('const []');
}
2019-07-04 18:36:39 +00:00
} else if (!b.toThis) {
2018-05-15 19:01:13 +00:00
b.type = convertTypeReference(field.type);
2021-08-17 04:52:08 +00:00
} else {
log.fine('Contructor: ${field.name} pass through');
2018-05-15 19:01:13 +00:00
}
2021-08-17 04:52:08 +00:00
if ((ctx.requiredFields.containsKey(field.name) ||
field.type.nullabilitySuffix != NullabilitySuffix.question) &&
b.defaultTo == null) {
2021-08-08 03:10:35 +00:00
//b.annotations.add(CodeExpression(Code('required')));
b.required = true;
}
2018-02-28 00:36:53 +00:00
}));
}
2018-06-27 05:45:46 +00:00
if (ctx.constructorParameters.isNotEmpty) {
if (!shouldBeConstant(ctx) ||
2019-07-04 18:32:34 +00:00
ctx.clazz.unnamedConstructor?.isConst == true) {
2019-04-30 15:44:01 +00:00
constructor.initializers.add(Code(
'super(${ctx.constructorParameters.map((p) => p.name).join(',')})'));
2019-07-04 18:32:34 +00:00
}
2018-06-27 05:45:46 +00:00
}
2018-02-28 00:36:53 +00:00
}));
}
/// Generate a `copyWith` method.
void generateCopyWithMethod(
2021-08-08 03:10:35 +00:00
BuildContext ctx, ClassBuilder clazz, LibraryBuilder file) {
2019-04-30 15:44:01 +00:00
clazz.methods.add(Method((method) {
2018-02-28 00:36:53 +00:00
method
..name = 'copyWith'
2021-08-08 03:10:35 +00:00
..returns = ctx.modelClassType;
2018-05-13 18:02:47 +00:00
2018-05-13 17:23:40 +00:00
// Add all `super` params
if (ctx.constructorParameters.isNotEmpty) {
for (var param in ctx.constructorParameters) {
2019-04-30 15:44:01 +00:00
method.requiredParameters.add(Parameter((b) => b
2018-05-13 17:23:40 +00:00
..name = param.name
..type = convertTypeReference(param.type)));
}
}
2018-02-28 00:36:53 +00:00
2019-04-30 15:44:01 +00:00
var buf = StringBuffer('return ${ctx.modelClassName}(');
2021-05-18 11:32:47 +00:00
var i = 0;
2018-05-13 17:23:40 +00:00
for (var param in ctx.constructorParameters) {
if (i++ > 0) buf.write(', ');
buf.write(param.name);
}
2018-02-28 00:36:53 +00:00
// Add named parameters
for (var field in ctx.fields) {
2019-04-30 15:44:01 +00:00
method.optionalParameters.add(Parameter((b) {
2018-02-28 00:36:53 +00:00
b
..name = field.name
..named = true
2021-08-08 03:10:35 +00:00
..type = convertTypeReference(field.type, forceNullable: true);
2018-02-28 00:36:53 +00:00
}));
if (i++ > 0) buf.write(', ');
buf.write('${field.name}: ${field.name} ?? this.${field.name}');
}
buf.write(');');
2019-04-30 15:44:01 +00:00
method.body = Code(buf.toString());
2018-02-28 00:36:53 +00:00
}));
}
2018-05-13 18:02:47 +00:00
2021-05-02 06:02:08 +00:00
static String? generateEquality(DartType type, [bool nullable = false]) {
if (type is InterfaceType) {
if (const TypeChecker.fromRuntime(List).isAssignableFromType(type)) {
2021-02-14 05:22:25 +00:00
if (type.typeArguments.length == 1) {
var eq = generateEquality(type.typeArguments[0]);
2022-11-18 17:59:34 +00:00
return 'ListEquality<${type.typeArguments[0].element!.name}>($eq)';
2019-07-04 18:32:34 +00:00
} else {
2019-04-30 15:44:01 +00:00
return 'ListEquality()';
2019-07-04 18:32:34 +00:00
}
} else if (const TypeChecker.fromRuntime(Map)
.isAssignableFromType(type)) {
2021-02-14 05:22:25 +00:00
if (type.typeArguments.length == 2) {
var keq = generateEquality(type.typeArguments[0]),
veq = generateEquality(type.typeArguments[1]);
2022-11-18 17:59:34 +00:00
return 'MapEquality<${type.typeArguments[0].element!.name}, ${type.typeArguments[1].element!.name}>(keys: $keq, values: $veq)';
2019-07-04 18:32:34 +00:00
} else {
2019-04-30 15:44:01 +00:00
return 'MapEquality()';
2019-07-04 18:32:34 +00:00
}
}
2018-05-13 18:02:47 +00:00
2022-11-18 17:59:34 +00:00
return nullable ? null : 'DefaultEquality<${type.element.name}>()';
} else {
2019-04-30 15:44:01 +00:00
return 'DefaultEquality()';
}
2018-05-13 18:02:47 +00:00
}
static String Function(String, String) generateComparator(DartType type) {
2022-11-18 17:59:34 +00:00
if (type is! InterfaceType || type.element.displayName == 'dynamic') {
return (a, b) => '$a == $b';
2019-07-04 18:32:34 +00:00
}
2018-05-13 18:02:47 +00:00
var eq = generateEquality(type, true);
if (eq == null) return (a, b) => '$a == $b';
return (a, b) => '$eq.equals($a, $b)';
}
2021-05-02 06:02:08 +00:00
void generateHashCode(BuildContext? ctx, ClassBuilder clazz) {
2021-05-18 11:32:47 +00:00
clazz.methods.add(Method((method) {
method
..name = 'hashCode'
..type = MethodType.getter
..returns = refer('int')
..annotations.add(refer('override'))
..body = refer('hashObjects')
.call([literalList(ctx!.fields.map((f) => refer(f.name)))])
.returned
.statement;
}));
2018-11-03 07:36:59 +00:00
}
2021-05-02 06:02:08 +00:00
void generateToString(BuildContext? ctx, ClassBuilder clazz) {
2019-04-08 15:00:04 +00:00
clazz.methods.add(Method((b) {
b
..name = 'toString'
..returns = refer('String')
..annotations.add(refer('override'))
..body = Block((b) {
2021-08-08 03:10:35 +00:00
var buf = StringBuffer('\'${ctx!.modelClassName}(');
2019-04-08 15:00:04 +00:00
var i = 0;
for (var field in ctx.fields) {
if (i++ > 0) buf.write(', ');
buf.write('${field.name}=\$${field.name}');
}
2021-08-08 03:10:35 +00:00
buf.write(')\'');
2019-04-08 15:00:04 +00:00
b.addExpression(CodeExpression(Code(buf.toString())).returned);
});
}));
}
2018-05-13 18:02:47 +00:00
void generateEqualsOperator(
2021-05-02 06:02:08 +00:00
BuildContext? ctx, ClassBuilder clazz, LibraryBuilder file) {
2019-04-30 15:44:01 +00:00
clazz.methods.add(Method((method) {
2018-05-13 18:02:47 +00:00
method
..name = 'operator =='
2021-08-08 03:10:35 +00:00
..annotations.add(refer('override'))
2019-04-30 15:44:01 +00:00
..returns = Reference('bool')
..requiredParameters.add(Parameter((b) => b.name = 'other'));
2018-05-13 18:02:47 +00:00
2021-05-02 06:02:08 +00:00
var buf = ['other is ${ctx!.originalClassName}'];
2018-05-13 18:02:47 +00:00
buf.addAll(ctx.fields.map((f) {
return generateComparator(f.type)('other.${f.name}', f.name);
}));
2019-04-30 15:44:01 +00:00
method.body = Code('return ${buf.join('&&')};');
2018-05-13 18:02:47 +00:00
}));
}
2018-02-28 00:36:53 +00:00
}