Updated serialize generator

This commit is contained in:
thomashii 2021-08-08 11:10:35 +08:00
parent 2643436d62
commit d5c625d9f9
8 changed files with 101 additions and 49 deletions

View file

@ -1,5 +1,15 @@
# Change Log # Change Log
## 4.0.3
* Added `useNullSafetySyntax: true` to DartEmitter
* Fixed `JsonModelGenerator` class to produce correct NNBD code
* Replaced `@required` with `required`
* Fixed all none nullable field to be `required` in the constructor
* Fixed generated methods to return the correct type
* Fixed generated methods to be annnotated with `override` where applicable
* Removed redundant null checking in the generated code
## 4.0.2 ## 4.0.2
* Fixed `build.yaml` to use `angel3` packages * Fixed `build.yaml` to use `angel3` packages

View file

@ -1,6 +1,6 @@
# Angel3 Serialize Generator # Angel3 Serialize Generator
[![version](https://img.shields.io/badge/pub-v4.0.2-brightgreen)](https://pub.dartlang.org/packages/angel3_serialize_generator) [![version](https://img.shields.io/badge/pub-v4.0.3-brightgreen)](https://pub.dartlang.org/packages/angel3_serialize_generator)
[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety) [![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety)
[![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion) [![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion)

View file

@ -5,6 +5,7 @@ import 'dart:mirrors';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:analyzer/dart/constant/value.dart'; import 'package:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/element.dart'; 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/dart/element/type.dart';
import 'package:angel3_model/angel3_model.dart'; import 'package:angel3_model/angel3_model.dart';
import 'package:angel3_serialize/angel3_serialize.dart'; import 'package:angel3_serialize/angel3_serialize.dart';
@ -38,9 +39,14 @@ Builder typescriptDefinitionBuilder(_) {
} }
/// Converts a [DartType] to a [TypeReference]. /// Converts a [DartType] to a [TypeReference].
TypeReference convertTypeReference(DartType? t) { TypeReference convertTypeReference(DartType t, {bool forceNullable = false}) {
return TypeReference((b) { return TypeReference((b) {
b.symbol = t!.element?.displayName; b.symbol = t.element?.displayName;
// Generate nullable type
if (t.nullabilitySuffix == NullabilitySuffix.question || forceNullable) {
b.isNullable = true;
}
if (t is InterfaceType) { if (t is InterfaceType) {
b.types.addAll(t.typeArguments.map(convertTypeReference)); b.types.addAll(t.typeArguments.map(convertTypeReference));
@ -57,7 +63,7 @@ Expression convertObject(DartObject o) {
return CodeExpression(Code('#' + o.toSymbolValue()!)); return CodeExpression(Code('#' + o.toSymbolValue()!));
} }
if (o.toStringValue() != null) return literalString(o.toStringValue()!); if (o.toStringValue() != null) return literalString(o.toStringValue()!);
if (o.toTypeValue() != null) return convertTypeReference(o.toTypeValue()); if (o.toTypeValue() != null) return convertTypeReference(o.toTypeValue()!);
if (o.toListValue() != null) { if (o.toListValue() != null) {
return literalList(o.toListValue()!.map(convertObject)); return literalList(o.toListValue()!.map(convertObject));
} }
@ -68,7 +74,7 @@ Expression convertObject(DartObject o) {
} }
var rev = ConstantReader(o).revive(); var rev = ConstantReader(o).revive();
Expression target = convertTypeReference(o.type); Expression target = convertTypeReference(o.type!);
target = rev.accessor.isEmpty ? target : target.property(rev.accessor); target = rev.accessor.isEmpty ? target : target.property(rev.accessor);
return target.call(rev.positionalArguments.map(convertObject), return target.call(rev.positionalArguments.map(convertObject),
rev.namedArguments.map((k, v) => MapEntry(k, convertObject(v)))); rev.namedArguments.map((k, v) => MapEntry(k, convertObject(v))));
@ -107,7 +113,9 @@ String? dartObjectToString(DartObject v) {
'}'; '}';
} }
if (v.toStringValue() != null) { if (v.toStringValue() != null) {
return literalString(v.toStringValue()!).accept(DartEmitter()).toString(); return literalString(v.toStringValue()!)
.accept(DartEmitter(useNullSafetySyntax: true))
.toString();
} }
if (type is InterfaceType && type.element.isEnum) { if (type is InterfaceType && type.element.isEnum) {
// Find the index of the enum, then find the member. // Find the index of the enum, then find the member.

View file

@ -12,7 +12,7 @@ import 'package:source_gen/source_gen.dart';
import 'context.dart'; import 'context.dart';
// ignore: deprecated_member_use // ignore: deprecated_member_use
const TypeChecker aliasTypeChecker = TypeChecker.fromRuntime(Alias); //const TypeChecker aliasTypeChecker = TypeChecker.fromRuntime(Alias);
const TypeChecker dateTimeTypeChecker = TypeChecker.fromRuntime(DateTime); const TypeChecker dateTimeTypeChecker = TypeChecker.fromRuntime(DateTime);
@ -89,11 +89,11 @@ Future<BuildContext?> buildContext(
ctx.fieldInfo[field.name] = sField; ctx.fieldInfo[field.name] = sField;
if (sField.defaultValue != null) { if (sField.defaultValue != null) {
ctx.defaults[field.name] = sField.defaultValue; ctx.defaults[field.name] = sField.defaultValue!;
} }
if (sField.alias != null) { if (sField.alias != null) {
ctx.aliases[field.name] = sField.alias; ctx.aliases[field.name] = sField.alias!;
} else if (autoSnakeCaseNames != false) { } else if (autoSnakeCaseNames != false) {
ctx.aliases[field.name] = ReCase(field.name).snakeCase; ctx.aliases[field.name] = ReCase(field.name).snakeCase;
} }
@ -104,11 +104,11 @@ Future<BuildContext?> buildContext(
ctx.requiredFields[field.name] = reason; ctx.requiredFields[field.name] = reason;
} }
if (sField.exclude!) { if (sField.exclude) {
// ignore: deprecated_member_use // ignore: deprecated_member_use
ctx.excluded[field.name] = Exclude( ctx.excluded[field.name] = Exclude(
canSerialize: sField.canSerialize!, canSerialize: sField.canSerialize,
canDeserialize: sField.canDeserialize!, canDeserialize: sField.canDeserialize,
); );
} }
} }
@ -149,6 +149,7 @@ Future<BuildContext?> buildContext(
} }
// Check for @DefaultValue() // Check for @DefaultValue()
/*
var defAnn = var defAnn =
// ignore: deprecated_member_use // ignore: deprecated_member_use
const TypeChecker.fromRuntime(DefaultValue).firstAnnotationOf(el); const TypeChecker.fromRuntime(DefaultValue).firstAnnotationOf(el);
@ -157,9 +158,11 @@ Future<BuildContext?> buildContext(
ctx.defaults[field.name] = rev; ctx.defaults[field.name] = rev;
foundNone = false; foundNone = false;
} }
*/
// Check for alias // Check for alias
// ignore: deprecated_member_use // ignore: deprecated_member_use
/*
Alias? alias; Alias? alias;
var aliasAnn = aliasTypeChecker.firstAnnotationOf(el); var aliasAnn = aliasTypeChecker.firstAnnotationOf(el);
@ -169,11 +172,13 @@ Future<BuildContext?> buildContext(
foundNone = false; foundNone = false;
} }
if (alias?.name.isNotEmpty == true) { if (alias?.name.isNotEmpty == true) {
ctx.aliases[field.name] = alias!.name; ctx.aliases[field.name] = alias!.name;
} else if (autoSnakeCaseNames != false) { } else if (autoSnakeCaseNames != false) {
ctx.aliases[field.name] = ReCase(field.name).snakeCase; ctx.aliases[field.name] = ReCase(field.name).snakeCase;
} }
*/
// Check for @required // Check for @required
var required = var required =

View file

@ -15,10 +15,10 @@ class BuildContext {
final Map<String, String> requiredFields = {}; final Map<String, String> requiredFields = {};
/// A map of field names to resolved names from `@Alias()` declarations. /// A map of field names to resolved names from `@Alias()` declarations.
final Map<String, String?> aliases = {}; final Map<String, String> aliases = {};
/// A map of field names to their default values. /// A map of field names to their default values.
final Map<String, DartObject?> defaults = {}; final Map<String, DartObject> defaults = {};
/// A map of fields to their related information. /// A map of fields to their related information.
final Map<String, SerializableFieldMirror> fieldInfo = {}; final Map<String, SerializableFieldMirror> fieldInfo = {};
@ -30,7 +30,8 @@ class BuildContext {
/// A map of "synthetic" fields, i.e. `id` and `created_at` injected automatically. /// A map of "synthetic" fields, i.e. `id` and `created_at` injected automatically.
final Map<String, bool> shimmed = {}; final Map<String, bool> shimmed = {};
final bool? autoIdAndDateFields, autoSnakeCaseNames; final bool autoIdAndDateFields;
final bool autoSnakeCaseNames;
final String? originalClassName, sourceFilename; final String? originalClassName, sourceFilename;
@ -52,18 +53,23 @@ class BuildContext {
BuildContext(this.annotation, this.clazz, BuildContext(this.annotation, this.clazz,
{this.originalClassName, {this.originalClassName,
this.sourceFilename, this.sourceFilename,
this.autoSnakeCaseNames, this.autoSnakeCaseNames = true,
this.autoIdAndDateFields, this.autoIdAndDateFields = true,
this.includeAnnotations = const <DartObject>[]}); this.includeAnnotations = const <DartObject>[]});
/// The name of the generated class. /// The name of the generated class.
String? get modelClassName => originalClassName!.startsWith('_') String? get modelClassName => originalClassName?.startsWith('_') == true
? originalClassName!.substring(1) ? originalClassName?.substring(1)
: originalClassName; : originalClassName;
/// A [ReCase] instance reflecting on the [modelClassName]. /// A [ReCase] instance reflecting on the [modelClassName].
ReCase get modelClassNameRecase => ReCase get modelClassNameRecase {
if (modelClassName == null) {
throw ArgumentError('Model class cannot be null');
}
_modelClassNameRecase ??= ReCase(modelClassName!); _modelClassNameRecase ??= ReCase(modelClassName!);
return _modelClassNameRecase!;
}
TypeReference get modelClassType => TypeReference get modelClassType =>
_modelClassType ??= TypeReference((b) => b.symbol = modelClassName); _modelClassType ??= TypeReference((b) => b.symbol = modelClassName);
@ -92,7 +98,10 @@ class SerializableFieldMirror {
final DartObject? defaultValue; final DartObject? defaultValue;
final Symbol? serializer, deserializer; final Symbol? serializer, deserializer;
final String? errorMessage; final String? errorMessage;
final bool? isNullable, canDeserialize, canSerialize, exclude; final bool isNullable;
final bool canDeserialize;
final bool canSerialize;
final bool exclude;
final DartType? serializesTo; final DartType? serializesTo;
SerializableFieldMirror( SerializableFieldMirror(
@ -101,9 +110,9 @@ class SerializableFieldMirror {
this.serializer, this.serializer,
this.deserializer, this.deserializer,
this.errorMessage, this.errorMessage,
this.isNullable, this.isNullable = false,
this.canDeserialize, this.canDeserialize = true,
this.canSerialize, this.canSerialize = true,
this.exclude, this.exclude = false,
this.serializesTo}); this.serializesTo});
} }

View file

@ -13,20 +13,26 @@ class JsonModelGenerator extends GeneratorForAnnotation<Serializable> {
var ctx = await buildContext(element as ClassElement, annotation, buildStep, var ctx = await buildContext(element as ClassElement, annotation, buildStep,
buildStep.resolver, true); buildStep.resolver, true);
if (ctx == null) {
log.severe('Invalid builder context');
throw 'Invalid builder context';
}
var lib = Library((b) { var lib = Library((b) {
generateClass(ctx, b, annotation); generateClass(ctx, b, annotation);
}); });
var buf = lib.accept(DartEmitter()); var buf = lib.accept(DartEmitter(useNullSafetySyntax: true));
return buf.toString(); return buf.toString();
} }
/// Generate an extended model class. /// Generate an extended model class.
void generateClass( void generateClass(
BuildContext? ctx, LibraryBuilder file, ConstantReader annotation) { BuildContext ctx, LibraryBuilder file, ConstantReader annotation) {
file.body.add(Class((clazz) { file.body.add(Class((clazz) {
log.fine('Generate Class: ${ctx.modelClassNameRecase.pascalCase}');
clazz clazz
..name = ctx!.modelClassNameRecase.pascalCase ..name = ctx.modelClassNameRecase.pascalCase
..annotations.add(refer('generatedSerializable')); ..annotations.add(refer('generatedSerializable'));
for (var ann in ctx.includeAnnotations) { for (var ann in ctx.includeAnnotations) {
@ -42,23 +48,28 @@ class JsonModelGenerator extends GeneratorForAnnotation<Serializable> {
//if (ctx.importsPackageMeta) //if (ctx.importsPackageMeta)
// clazz.annotations.add(CodeExpression(Code('immutable'))); // clazz.annotations.add(CodeExpression(Code('immutable')));
// Generate the fields for the class
for (var field in ctx.fields) { for (var field in ctx.fields) {
log.fine('Generate Field: ${field.name}');
clazz.fields.add(Field((b) { clazz.fields.add(Field((b) {
b b
..name = field.name ..name = field.name
// ..modifier = FieldModifier.final$ //..modifier = FieldModifier.final$
..annotations.add(CodeExpression(Code('override'))) //..annotations.add(CodeExpression(Code('override')))
..annotations.add(refer('override'))
..type = convertTypeReference(field.type); ..type = convertTypeReference(field.type);
// Fields should only be forced-final if the original field has no setter. // Fields should only be forced-final if the original field has no setter.
if (field.setter == null && field is! ShimFieldImpl) { //log.fine('Final: ${field.isFinal}');
b.modifier = FieldModifier.final$; //if (field.setter == null && field is! ShimFieldImpl) {
} //if (field.isFinal) {
// b.modifier = FieldModifier.final$;
//}
for (var el in [field.getter, field]) { for (var el in [field.getter, field]) {
if (el?.documentationComment != null) { //if (el?.documentationComment != null) {
b.docs.addAll(el!.documentationComment!.split('\n')); b.docs.addAll(el?.documentationComment?.split('\n') ?? []);
} //}
} }
})); }));
} }
@ -92,16 +103,17 @@ class JsonModelGenerator extends GeneratorForAnnotation<Serializable> {
/// Generate a constructor with named parameters. /// Generate a constructor with named parameters.
void generateConstructor( void generateConstructor(
BuildContext? ctx, ClassBuilder clazz, LibraryBuilder file) { BuildContext ctx, ClassBuilder clazz, LibraryBuilder file) {
clazz.constructors.add(Constructor((constructor) { clazz.constructors.add(Constructor((constructor) {
// Add all `super` params // Add all `super` params
constructor.constant = (ctx!.clazz.unnamedConstructor?.isConst == true || constructor.constant = (ctx.clazz.unnamedConstructor?.isConst == true ||
shouldBeConstant(ctx)) && shouldBeConstant(ctx)) &&
ctx.fields.every((f) { ctx.fields.every((f) {
return f.setter == null && f is! ShimFieldImpl; return f.setter == null && f is! ShimFieldImpl;
}); });
for (var param in ctx.constructorParameters) { for (var param in ctx.constructorParameters) {
//log.fine('Contructor Parameter: ${param.name}');
constructor.requiredParameters.add(Parameter((b) => b constructor.requiredParameters.add(Parameter((b) => b
..name = param.name ..name = param.name
..type = convertTypeReference(param.type))); ..type = convertTypeReference(param.type)));
@ -126,7 +138,10 @@ class JsonModelGenerator extends GeneratorForAnnotation<Serializable> {
} }
} }
// Generate the parameters for the constructor
for (var field in ctx.fields) { for (var field in ctx.fields) {
//log.fine('Contructor Field: ${field.name}');
constructor.optionalParameters.add(Parameter((b) { constructor.optionalParameters.add(Parameter((b) {
b b
..toThis = shouldBeConstant(ctx) ..toThis = shouldBeConstant(ctx)
@ -146,8 +161,10 @@ class JsonModelGenerator extends GeneratorForAnnotation<Serializable> {
} }
if (ctx.requiredFields.containsKey(field.name) && if (ctx.requiredFields.containsKey(field.name) &&
b.defaultTo == null) { b.defaultTo == null ||
b.annotations.add(CodeExpression(Code('required'))); (field.type.nullabilitySuffix != NullabilitySuffix.question)) {
//b.annotations.add(CodeExpression(Code('required')));
b.required = true;
} }
})); }));
} }
@ -164,11 +181,11 @@ class JsonModelGenerator extends GeneratorForAnnotation<Serializable> {
/// Generate a `copyWith` method. /// Generate a `copyWith` method.
void generateCopyWithMethod( void generateCopyWithMethod(
BuildContext? ctx, ClassBuilder clazz, LibraryBuilder file) { BuildContext ctx, ClassBuilder clazz, LibraryBuilder file) {
clazz.methods.add(Method((method) { clazz.methods.add(Method((method) {
method method
..name = 'copyWith' ..name = 'copyWith'
..returns = ctx!.modelClassType; ..returns = ctx.modelClassType;
// Add all `super` params // Add all `super` params
if (ctx.constructorParameters.isNotEmpty) { if (ctx.constructorParameters.isNotEmpty) {
@ -192,7 +209,7 @@ class JsonModelGenerator extends GeneratorForAnnotation<Serializable> {
b b
..name = field.name ..name = field.name
..named = true ..named = true
..type = convertTypeReference(field.type); ..type = convertTypeReference(field.type, forceNullable: true);
})); }));
if (i++ > 0) buf.write(', '); if (i++ > 0) buf.write(', ');
@ -260,13 +277,13 @@ class JsonModelGenerator extends GeneratorForAnnotation<Serializable> {
..returns = refer('String') ..returns = refer('String')
..annotations.add(refer('override')) ..annotations.add(refer('override'))
..body = Block((b) { ..body = Block((b) {
var buf = StringBuffer('\"${ctx!.modelClassName}('); var buf = StringBuffer('\'${ctx!.modelClassName}(');
var i = 0; var i = 0;
for (var field in ctx.fields) { for (var field in ctx.fields) {
if (i++ > 0) buf.write(', '); if (i++ > 0) buf.write(', ');
buf.write('${field.name}=\$${field.name}'); buf.write('${field.name}=\$${field.name}');
} }
buf.write(')\"'); buf.write(')\'');
b.addExpression(CodeExpression(Code(buf.toString())).returned); b.addExpression(CodeExpression(Code(buf.toString())).returned);
}); });
})); }));
@ -277,6 +294,7 @@ class JsonModelGenerator extends GeneratorForAnnotation<Serializable> {
clazz.methods.add(Method((method) { clazz.methods.add(Method((method) {
method method
..name = 'operator ==' ..name = 'operator =='
..annotations.add(refer('override'))
..returns = Reference('bool') ..returns = Reference('bool')
..requiredParameters.add(Parameter((b) => b.name = 'other')); ..requiredParameters.add(Parameter((b) => b.name = 'other'));

View file

@ -25,7 +25,8 @@ class SerializerGenerator extends GeneratorForAnnotation<Serializable> {
} }
var lib = Library((b) { var lib = Library((b) {
generateClass(serializers.map((s) => s.toIntValue()).toList(), ctx!, b); generateClass(
serializers.map((s) => s.toIntValue() ?? 0).toList(), ctx!, b);
generateFieldsClass(ctx, b); generateFieldsClass(ctx, b);
}); });
@ -35,7 +36,7 @@ class SerializerGenerator extends GeneratorForAnnotation<Serializable> {
/// Generate a serializer class. /// Generate a serializer class.
void generateClass( void generateClass(
List<int?> serializers, BuildContext ctx, LibraryBuilder file) { List<int> serializers, BuildContext ctx, LibraryBuilder file) {
// Generate canonical codecs, etc. // Generate canonical codecs, etc.
var pascal = ctx.modelClassNameRecase.pascalCase, var pascal = ctx.modelClassNameRecase.pascalCase,
camel = ctx.modelClassNameRecase.camelCase; camel = ctx.modelClassNameRecase.camelCase;

View file

@ -1,5 +1,5 @@
name: angel3_serialize_generator name: angel3_serialize_generator
version: 4.0.2 version: 4.0.3
description: Angel3 model serialization generators, designed for use with Angel. Combine with angel_serialize for flexible modeling. description: Angel3 model serialization generators, designed for use with Angel. Combine with angel_serialize for flexible modeling.
homepage: https://angel3-framework.web.app/ homepage: https://angel3-framework.web.app/
repository: https://github.com/dukefirehawk/angel/tree/angel3/packages/serialize/angel_serialize_generator repository: https://github.com/dukefirehawk/angel/tree/angel3/packages/serialize/angel_serialize_generator
@ -18,6 +18,7 @@ dependencies:
recase: ^4.0.0 recase: ^4.0.0
source_gen: ^1.0.0 source_gen: ^1.0.0
quiver: ^3.0.1 quiver: ^3.0.1
logging: ^1.0.0
dev_dependencies: dev_dependencies:
build_runner: ^2.0.1 build_runner: ^2.0.1
collection: ^1.15.0 collection: ^1.15.0