2.0.9 - add enums

This commit is contained in:
Tobe O 2018-06-27 01:36:57 -04:00
parent 403e2e600e
commit bdb7b30bc4
14 changed files with 217 additions and 36 deletions

View file

@ -0,0 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="tests in enum_test.dart" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true" nameIsGenerated="true">
<option name="filePath" value="$PROJECT_DIR$/angel_serialize_generator/test/enum_test.dart" />
<option name="testRunnerOptions" value="-j4" />
<method />
</configuration>
</component>

View file

@ -61,9 +61,20 @@ part 'book.g.dart';
@serializable @serializable
abstract class _Book extends Model { abstract class _Book extends Model {
String get author; String get author;
String get title; String get title;
String get description; String get description;
int get pageCount; int get pageCount;
BookType get type;
}
/// It even supports enums!
enum BookType {
fiction,
nonFiction
} }
``` ```

View file

@ -1,3 +1,8 @@
# 2.0.9
* Now supports de/serialization of `enum` types.
* Generate `const` constructors when possible.
* Remove `whereType`, perform manual coercion.
# 2.0.8 # 2.0.8
* Generate a `fromMap` with typecasting, for Dart 2's sake. * Generate a `fromMap` with typecasting, for Dart 2's sake.

View file

@ -23,6 +23,7 @@ targets:
_book: _book:
sources: sources:
- "test/models/book.dart" - "test/models/book.dart"
- "test/models/with_enum.dart"
_typescript_definition: _typescript_definition:
sources: sources:
- "lib/*.dart" - "lib/*.dart"

View file

@ -3,6 +3,7 @@ library angel_serialize_generator;
import 'dart:async'; import 'dart:async';
import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart'; import 'package:analyzer/dart/element/type.dart';
import 'package:angel_model/angel_model.dart';
import 'package:angel_serialize/angel_serialize.dart'; import 'package:angel_serialize/angel_serialize.dart';
import 'package:build/build.dart'; import 'package:build/build.dart';
import 'package:code_buffer/code_buffer.dart'; import 'package:code_buffer/code_buffer.dart';
@ -66,6 +67,14 @@ bool isListOrMapType(DartType t) {
const TypeChecker.fromRuntime(Map).isAssignableFromType(t); const TypeChecker.fromRuntime(Map).isAssignableFromType(t);
} }
bool isEnumType(DartType t) {
if (t is InterfaceType) {
return t.element.isEnum;
}
return false;
}
/// Determines if a [DartType] is a `List` with the first type argument being a `Model`. /// Determines if a [DartType] is a `List` with the first type argument being a `Model`.
bool isListModelType(InterfaceType t) { bool isListModelType(InterfaceType t) {
return const TypeChecker.fromRuntime(List).isAssignableFromType(t) && return const TypeChecker.fromRuntime(List).isAssignableFromType(t) &&
@ -80,6 +89,8 @@ bool isMapToModelType(InterfaceType t) {
isModelClass(t.typeArguments[1]); isModelClass(t.typeArguments[1]);
} }
bool isAssignableToModel(DartType type) => const TypeChecker.fromRuntime(Model).isAssignableFromType(type);
/// Compute a [String] representation of a [type]. /// Compute a [String] representation of a [type].
String typeToString(DartType type) { String typeToString(DartType type) {
if (type is InterfaceType) { if (type is InterfaceType) {

View file

@ -26,9 +26,13 @@ class JsonModelGenerator extends GeneratorForAnnotation<Serializable> {
void generateClass( void generateClass(
BuildContext ctx, LibraryBuilder file, ConstantReader annotation) { BuildContext ctx, LibraryBuilder file, ConstantReader annotation) {
file.body.add(new Class((clazz) { file.body.add(new Class((clazz) {
clazz clazz..name = ctx.modelClassNameRecase.pascalCase;
..name = ctx.modelClassNameRecase.pascalCase
..extend = new Reference(ctx.originalClassName); if (shouldBeConstant(ctx)) {
clazz.implements.add(new Reference(ctx.originalClassName));
} else {
clazz.extend = new Reference(ctx.originalClassName);
}
//if (ctx.importsPackageMeta) //if (ctx.importsPackageMeta)
// clazz.annotations.add(new CodeExpression(new Code('immutable'))); // clazz.annotations.add(new CodeExpression(new Code('immutable')));
@ -61,11 +65,19 @@ class JsonModelGenerator extends GeneratorForAnnotation<Serializable> {
})); }));
} }
bool shouldBeConstant(BuildContext ctx) {
// Check if all fields are without a getter
return !isAssignableToModel(ctx.clazz.type) &&
ctx.clazz.fields.every((f) => f.getter == null || f.setter == null);
}
/// 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(new Constructor((constructor) { clazz.constructors.add(new Constructor((constructor) {
// Add all `super` params // Add all `super` params
constructor.constant = ctx.clazz.unnamedConstructor?.isConst == true ||
shouldBeConstant(ctx);
for (var param in ctx.constructorParameters) { for (var param in ctx.constructorParameters) {
constructor.requiredParameters.add(new Parameter((b) => b constructor.requiredParameters.add(new Parameter((b) => b

View file

@ -112,6 +112,12 @@ class SerializerGenerator extends GeneratorForAnnotation<Serializable> {
return map..[key] = ${rc.pascalCase}Serializer.toMap(model.${field return map..[key] = ${rc.pascalCase}Serializer.toMap(model.${field
.name}[key]); .name}[key]);
})'''; })''';
} else if (t.element.isEnum) {
serializedRepresentation = '''
model.${field.name} == null ?
null
: ${t.name}.values.indexOf(model.${field.name})
''';
} }
} }
@ -193,19 +199,31 @@ class SerializerGenerator extends GeneratorForAnnotation<Serializable> {
if (isListModelType(t)) { if (isListModelType(t)) {
var rc = new ReCase(t.typeArguments[0].name); var rc = new ReCase(t.typeArguments[0].name);
deserializedRepresentation = "map['$alias'] is Iterable" deserializedRepresentation = "map['$alias'] is Iterable"
" ? new List.unmodifiable((map['$alias'] as Iterable).whereType<Map>().map(${rc " ? new List.unmodifiable(((map['$alias'] as Iterable)"
.pascalCase}Serializer.fromMap))" ".where((x) => x is Map) as Iterable<Map>)"
".map(${rc.pascalCase}Serializer.fromMap))"
" : null"; " : null";
} else if (isMapToModelType(t)) { } else if (isMapToModelType(t)) {
var rc = new ReCase(t.typeArguments[1].name); var rc = new ReCase(t.typeArguments[1].name);
deserializedRepresentation = ''' deserializedRepresentation = '''
map['$alias'] is Map map['$alias'] is Map
? new Map.unmodifiable((map['$alias'] as Map).keys.fold({}, (out, key) { ? new Map.unmodifiable((map['$alias'] as Map).keys.fold({}, (out, key) {
return out..[key] = ${rc return out..[key] = ${rc.pascalCase}Serializer
.pascalCase}Serializer.fromMap((map['$alias'] as Map)[key]); .fromMap((map['$alias'] as Map)[key]);
})) }))
: null : null
'''; ''';
} else if (t.element.isEnum) {
deserializedRepresentation = '''
map['$alias'] is ${t.name}
? map['$alias']
:
(
map['$alias'] is int
? ${t.name}.values[map['$alias']]
: null
)
''';
} }
} }

View file

@ -16,5 +16,5 @@ dependencies:
recase: ^1.0.0 recase: ^1.0.0
source_gen: ^0.7.0 source_gen: ^0.7.0
dev_dependencies: dev_dependencies:
build_runner: ^0.9.0 build_runner: ^0.8.0
test: ^1.0.0 test: ^1.0.0

View file

@ -0,0 +1,48 @@
import 'package:test/test.dart';
import 'models/with_enum.dart';
const WithEnum aWithEnum = const WithEnum(type: WithEnumType.a);
const WithEnum aWithEnum2 = const WithEnum(type: WithEnumType.a);
void main() {
test('enum serializes to int', () {
var w = new WithEnum(type: WithEnumType.b).toJson();
expect(w[WithEnumFields.type], WithEnumType.values.indexOf(WithEnumType.b));
});
test('enum serializes null if null', () {
var w = new WithEnum(type: null).toJson();
expect(w[WithEnumFields.type], null);
});
test('enum deserializes to null from null', () {
var map = {WithEnumFields.type: null};
var w = WithEnumSerializer.fromMap(map);
expect(w.type, isNull);
});
test('enum deserializes from int', () {
var map = {
WithEnumFields.type: WithEnumType.values.indexOf(WithEnumType.b)
};
var w = WithEnumSerializer.fromMap(map);
expect(w.type, WithEnumType.b);
});
test('enum deserializes from value', () {
var map = {WithEnumFields.type: WithEnumType.c};
var w = WithEnumSerializer.fromMap(map);
expect(w.type, WithEnumType.c);
});
test('equality', () {
expect(
new WithEnum(type: WithEnumType.a), new WithEnum(type: WithEnumType.a));
expect(new WithEnum(type: WithEnumType.a),
isNot(new WithEnum(type: WithEnumType.b)));
});
test('const', () {
expect(identical(aWithEnum, aWithEnum2), true);
});
}

View file

@ -17,24 +17,26 @@ abstract class AuthorSerializer {
} }
return new Author( return new Author(
id: map['id'], id: map['id'] as String,
name: map['name'], name: map['name'] as String,
age: map['age'], age: map['age'] as int,
books: map['books'] is Iterable books: map['books'] is Iterable
? new List.unmodifiable(map['books'].map(BookSerializer.fromMap)) ? new List.unmodifiable(((map['books'] as Iterable)
.where((x) => x is Map) as Iterable<Map>)
.map(BookSerializer.fromMap))
: null, : null,
newestBook: map['newest_book'] != null newestBook: map['newest_book'] != null
? BookSerializer.fromMap(map['newest_book']) ? BookSerializer.fromMap(map['newest_book'])
: null, : null,
obscured: map['obscured'], obscured: map['obscured'] as String,
createdAt: map['created_at'] != null createdAt: map['created_at'] != null
? (map['created_at'] is DateTime ? (map['created_at'] is DateTime
? map['created_at'] ? (map['created_at'] as DateTime)
: DateTime.parse(map['created_at'])) : DateTime.parse(map['created_at']))
: null, : null,
updatedAt: map['updated_at'] != null updatedAt: map['updated_at'] != null
? (map['updated_at'] is DateTime ? (map['updated_at'] is DateTime
? map['updated_at'] ? (map['updated_at'] as DateTime)
: DateTime.parse(map['updated_at'])) : DateTime.parse(map['updated_at']))
: null); : null);
} }
@ -83,21 +85,23 @@ abstract class AuthorFields {
abstract class LibrarySerializer { abstract class LibrarySerializer {
static Library fromMap(Map map) { static Library fromMap(Map map) {
return new Library( return new Library(
id: map['id'], id: map['id'] as String,
collection: map['collection'] is Map collection: map['collection'] is Map
? new Map.unmodifiable(map['collection'].keys.fold({}, (out, key) { ? new Map.unmodifiable(
(map['collection'] as Map).keys.fold({}, (out, key) {
return out return out
..[key] = BookSerializer.fromMap(map['collection'][key]); ..[key] =
BookSerializer.fromMap((map['collection'] as Map)[key]);
})) }))
: null, : null,
createdAt: map['created_at'] != null createdAt: map['created_at'] != null
? (map['created_at'] is DateTime ? (map['created_at'] is DateTime
? map['created_at'] ? (map['created_at'] as DateTime)
: DateTime.parse(map['created_at'])) : DateTime.parse(map['created_at']))
: null, : null,
updatedAt: map['updated_at'] != null updatedAt: map['updated_at'] != null
? (map['updated_at'] is DateTime ? (map['updated_at'] is DateTime
? map['updated_at'] ? (map['updated_at'] as DateTime)
: DateTime.parse(map['updated_at'])) : DateTime.parse(map['updated_at']))
: null); : null);
} }
@ -131,18 +135,18 @@ abstract class BookmarkSerializer {
} }
return new Bookmark(book, return new Bookmark(book,
id: map['id'], id: map['id'] as String,
history: map['history'], history: map['history'] as List<int>,
page: map['page'], page: map['page'] as int,
comment: map['comment'], comment: map['comment'] as String,
createdAt: map['created_at'] != null createdAt: map['created_at'] != null
? (map['created_at'] is DateTime ? (map['created_at'] is DateTime
? map['created_at'] ? (map['created_at'] as DateTime)
: DateTime.parse(map['created_at'])) : DateTime.parse(map['created_at']))
: null, : null,
updatedAt: map['updated_at'] != null updatedAt: map['updated_at'] != null
? (map['updated_at'] is DateTime ? (map['updated_at'] is DateTime
? map['updated_at'] ? (map['updated_at'] as DateTime)
: DateTime.parse(map['updated_at'])) : DateTime.parse(map['updated_at']))
: null); : null);
} }

View file

@ -9,21 +9,21 @@ part of angel_serialize.test.models.book;
abstract class BookSerializer { abstract class BookSerializer {
static Book fromMap(Map map) { static Book fromMap(Map map) {
return new Book( return new Book(
id: map['id'], id: map['id'] as String,
author: map['author'], author: map['author'] as String,
title: map['title'], title: map['title'] as String,
description: map['description'], description: map['description'] as String,
pageCount: map['page_count'], pageCount: map['page_count'] as int,
notModels: map['not_models'], notModels: map['not_models'] as List<double>,
camelCaseString: map['camelCase'], camelCaseString: map['camelCase'] as String,
createdAt: map['created_at'] != null createdAt: map['created_at'] != null
? (map['created_at'] is DateTime ? (map['created_at'] is DateTime
? map['created_at'] ? (map['created_at'] as DateTime)
: DateTime.parse(map['created_at'])) : DateTime.parse(map['created_at']))
: null, : null,
updatedAt: map['updated_at'] != null updatedAt: map['updated_at'] != null
? (map['updated_at'] is DateTime ? (map['updated_at'] is DateTime
? map['updated_at'] ? (map['updated_at'] as DateTime)
: DateTime.parse(map['updated_at'])) : DateTime.parse(map['updated_at']))
: null); : null);
} }

View file

@ -0,0 +1,11 @@
import 'package:angel_model/angel_model.dart';
import 'package:angel_serialize/angel_serialize.dart';
part 'with_enum.g.dart';
part 'with_enum.serializer.g.dart';
@Serializable(autoIdAndDateFields: false)
abstract class _WithEnum {
WithEnumType get type;
}
enum WithEnumType { a, b, c }

View file

@ -0,0 +1,26 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'with_enum.dart';
// **************************************************************************
// Generator: JsonModelGenerator
// **************************************************************************
class WithEnum implements _WithEnum {
const WithEnum({this.type});
@override
final WithEnumType type;
WithEnum copyWith({WithEnumType type}) {
return new WithEnum(type: type ?? this.type);
}
bool operator ==(other) {
return other is _WithEnum && other.type == type;
}
Map<String, dynamic> toJson() {
return WithEnumSerializer.toMap(this);
}
}

View file

@ -0,0 +1,27 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'with_enum.dart';
// **************************************************************************
// Generator: SerializerGenerator
// **************************************************************************
abstract class WithEnumSerializer {
static WithEnum fromMap(Map map) {
return new WithEnum(
type: map['type'] is WithEnumType
? map['type']
: (map['type'] is int ? WithEnumType.values[map['type']] : null));
}
static Map<String, dynamic> toMap(WithEnum model) {
return {
'type':
model.type == null ? null : WithEnumType.values.indexOf(model.type)
};
}
}
abstract class WithEnumFields {
static const String type = 'type';
}