diff --git a/packages/serialize/angel_serialize_generator/CHANGELOG.md b/packages/serialize/angel_serialize_generator/CHANGELOG.md index 78539904..2c83e160 100644 --- a/packages/serialize/angel_serialize_generator/CHANGELOG.md +++ b/packages/serialize/angel_serialize_generator/CHANGELOG.md @@ -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 diff --git a/packages/serialize/angel_serialize_generator/README.md b/packages/serialize/angel_serialize_generator/README.md index 9e8ef769..140111c7 100644 --- a/packages/serialize/angel_serialize_generator/README.md +++ b/packages/serialize/angel_serialize_generator/README.md @@ -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) diff --git a/packages/serialize/angel_serialize_generator/lib/angel3_serialize_generator.dart b/packages/serialize/angel_serialize_generator/lib/angel3_serialize_generator.dart index e7e4e73f..86c84406 100644 --- a/packages/serialize/angel_serialize_generator/lib/angel3_serialize_generator.dart +++ b/packages/serialize/angel_serialize_generator/lib/angel3_serialize_generator.dart @@ -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. diff --git a/packages/serialize/angel_serialize_generator/lib/build_context.dart b/packages/serialize/angel_serialize_generator/lib/build_context.dart index 289397e2..abb9ee56 100644 --- a/packages/serialize/angel_serialize_generator/lib/build_context.dart +++ b/packages/serialize/angel_serialize_generator/lib/build_context.dart @@ -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( 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( 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( } // Check for @DefaultValue() + /* var defAnn = // ignore: deprecated_member_use const TypeChecker.fromRuntime(DefaultValue).firstAnnotationOf(el); @@ -157,9 +158,11 @@ Future 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( 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 = diff --git a/packages/serialize/angel_serialize_generator/lib/context.dart b/packages/serialize/angel_serialize_generator/lib/context.dart index 0e25a564..516541cf 100644 --- a/packages/serialize/angel_serialize_generator/lib/context.dart +++ b/packages/serialize/angel_serialize_generator/lib/context.dart @@ -15,10 +15,10 @@ class BuildContext { final Map requiredFields = {}; /// A map of field names to resolved names from `@Alias()` declarations. - final Map aliases = {}; + final Map aliases = {}; /// A map of field names to their default values. - final Map defaults = {}; + final Map defaults = {}; /// A map of fields to their related information. final Map fieldInfo = {}; @@ -30,7 +30,8 @@ class BuildContext { /// A map of "synthetic" fields, i.e. `id` and `created_at` injected automatically. final Map 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 []}); /// 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}); } diff --git a/packages/serialize/angel_serialize_generator/lib/model.dart b/packages/serialize/angel_serialize_generator/lib/model.dart index d8450ff4..a9995edf 100644 --- a/packages/serialize/angel_serialize_generator/lib/model.dart +++ b/packages/serialize/angel_serialize_generator/lib/model.dart @@ -13,20 +13,26 @@ class JsonModelGenerator extends GeneratorForAnnotation { 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 { //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 { /// 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 { } } + // 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 { } 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 { /// 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 { 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 { ..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 { clazz.methods.add(Method((method) { method ..name = 'operator ==' + ..annotations.add(refer('override')) ..returns = Reference('bool') ..requiredParameters.add(Parameter((b) => b.name = 'other')); diff --git a/packages/serialize/angel_serialize_generator/lib/serialize.dart b/packages/serialize/angel_serialize_generator/lib/serialize.dart index d6c1720c..b60f81e5 100644 --- a/packages/serialize/angel_serialize_generator/lib/serialize.dart +++ b/packages/serialize/angel_serialize_generator/lib/serialize.dart @@ -25,7 +25,8 @@ class SerializerGenerator extends GeneratorForAnnotation { } 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 { /// Generate a serializer class. void generateClass( - List serializers, BuildContext ctx, LibraryBuilder file) { + List serializers, BuildContext ctx, LibraryBuilder file) { // Generate canonical codecs, etc. var pascal = ctx.modelClassNameRecase.pascalCase, camel = ctx.modelClassNameRecase.camelCase; diff --git a/packages/serialize/angel_serialize_generator/pubspec.yaml b/packages/serialize/angel_serialize_generator/pubspec.yaml index ab253f89..68629be3 100644 --- a/packages/serialize/angel_serialize_generator/pubspec.yaml +++ b/packages/serialize/angel_serialize_generator/pubspec.yaml @@ -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