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
## 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
* Fixed `build.yaml` to use `angel3` packages

View file

@ -1,6 +1,6 @@
# 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)
[![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 'package:analyzer/dart/constant/value.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:angel3_model/angel3_model.dart';
import 'package:angel3_serialize/angel3_serialize.dart';
@ -38,9 +39,14 @@ Builder typescriptDefinitionBuilder(_) {
}
/// Converts a [DartType] to a [TypeReference].
TypeReference convertTypeReference(DartType? t) {
TypeReference convertTypeReference(DartType t, {bool forceNullable = false}) {
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) {
b.types.addAll(t.typeArguments.map(convertTypeReference));
@ -57,7 +63,7 @@ Expression convertObject(DartObject o) {
return CodeExpression(Code('#' + o.toSymbolValue()!));
}
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) {
return literalList(o.toListValue()!.map(convertObject));
}
@ -68,7 +74,7 @@ Expression convertObject(DartObject o) {
}
var rev = ConstantReader(o).revive();
Expression target = convertTypeReference(o.type);
Expression target = convertTypeReference(o.type!);
target = rev.accessor.isEmpty ? target : target.property(rev.accessor);
return target.call(rev.positionalArguments.map(convertObject),
rev.namedArguments.map((k, v) => MapEntry(k, convertObject(v))));
@ -107,7 +113,9 @@ String? dartObjectToString(DartObject v) {
'}';
}
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) {
// 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';
// ignore: deprecated_member_use
const TypeChecker aliasTypeChecker = TypeChecker.fromRuntime(Alias);
//const TypeChecker aliasTypeChecker = TypeChecker.fromRuntime(Alias);
const TypeChecker dateTimeTypeChecker = TypeChecker.fromRuntime(DateTime);
@ -89,11 +89,11 @@ Future<BuildContext?> buildContext(
ctx.fieldInfo[field.name] = sField;
if (sField.defaultValue != null) {
ctx.defaults[field.name] = sField.defaultValue;
ctx.defaults[field.name] = sField.defaultValue!;
}
if (sField.alias != null) {
ctx.aliases[field.name] = sField.alias;
ctx.aliases[field.name] = sField.alias!;
} else if (autoSnakeCaseNames != false) {
ctx.aliases[field.name] = ReCase(field.name).snakeCase;
}
@ -104,11 +104,11 @@ Future<BuildContext?> buildContext(
ctx.requiredFields[field.name] = reason;
}
if (sField.exclude!) {
if (sField.exclude) {
// ignore: deprecated_member_use
ctx.excluded[field.name] = Exclude(
canSerialize: sField.canSerialize!,
canDeserialize: sField.canDeserialize!,
canSerialize: sField.canSerialize,
canDeserialize: sField.canDeserialize,
);
}
}
@ -149,6 +149,7 @@ Future<BuildContext?> buildContext(
}
// Check for @DefaultValue()
/*
var defAnn =
// ignore: deprecated_member_use
const TypeChecker.fromRuntime(DefaultValue).firstAnnotationOf(el);
@ -157,9 +158,11 @@ Future<BuildContext?> buildContext(
ctx.defaults[field.name] = rev;
foundNone = false;
}
*/
// Check for alias
// ignore: deprecated_member_use
/*
Alias? alias;
var aliasAnn = aliasTypeChecker.firstAnnotationOf(el);
@ -168,12 +171,14 @@ Future<BuildContext?> buildContext(
alias = Alias(aliasAnn.getField('name')!.toStringValue()!);
foundNone = false;
}
if (alias?.name.isNotEmpty == true) {
ctx.aliases[field.name] = alias!.name;
} else if (autoSnakeCaseNames != false) {
ctx.aliases[field.name] = ReCase(field.name).snakeCase;
}
*/
// Check for @required
var required =

View file

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

View file

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

View file

@ -25,7 +25,8 @@ class SerializerGenerator extends GeneratorForAnnotation<Serializable> {
}
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);
});
@ -35,7 +36,7 @@ class SerializerGenerator extends GeneratorForAnnotation<Serializable> {
/// Generate a serializer class.
void generateClass(
List<int?> serializers, BuildContext ctx, LibraryBuilder file) {
List<int> serializers, BuildContext ctx, LibraryBuilder file) {
// Generate canonical codecs, etc.
var pascal = ctx.modelClassNameRecase.pascalCase,
camel = ctx.modelClassNameRecase.camelCase;

View file

@ -1,5 +1,5 @@
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.
homepage: https://angel3-framework.web.app/
repository: https://github.com/dukefirehawk/angel/tree/angel3/packages/serialize/angel_serialize_generator
@ -18,6 +18,7 @@ dependencies:
recase: ^4.0.0
source_gen: ^1.0.0
quiver: ^3.0.1
logging: ^1.0.0
dev_dependencies:
build_runner: ^2.0.1
collection: ^1.15.0