Updated serialize generator
This commit is contained in:
parent
2643436d62
commit
d5c625d9f9
8 changed files with 101 additions and 49 deletions
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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});
|
||||
}
|
||||
|
|
|
@ -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'));
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue