platform/angel_serialize_generator/lib/build_context.dart

211 lines
7.1 KiB
Dart
Raw Normal View History

2017-09-15 00:36:28 +00:00
import 'dart:async';
2019-01-07 00:56:05 +00:00
import 'package:analyzer/dart/constant/value.dart';
2017-06-20 22:13:04 +00:00
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/element/element.dart';
2019-01-09 19:25:05 +00:00
import 'package:angel_model/angel_model.dart';
2017-06-20 22:13:04 +00:00
import 'package:angel_serialize/angel_serialize.dart';
import 'package:build/build.dart';
2018-05-15 19:11:12 +00:00
import 'package:meta/meta.dart';
2017-06-20 22:13:04 +00:00
import 'package:path/path.dart' as p;
import 'package:recase/recase.dart';
2017-09-15 00:20:36 +00:00
import 'package:source_gen/source_gen.dart';
2017-06-20 22:13:04 +00:00
import 'context.dart';
2019-01-07 00:56:05 +00:00
// ignore: deprecated_member_use
2017-09-15 00:20:36 +00:00
const TypeChecker aliasTypeChecker = const TypeChecker.fromRuntime(Alias);
2018-02-28 01:10:57 +00:00
const TypeChecker dateTimeTypeChecker = const TypeChecker.fromRuntime(DateTime);
2019-01-07 00:56:05 +00:00
// ignore: deprecated_member_use
2017-09-15 00:20:36 +00:00
const TypeChecker excludeTypeChecker = const TypeChecker.fromRuntime(Exclude);
2019-01-07 00:56:05 +00:00
const TypeChecker serializableFieldTypeChecker =
const TypeChecker.fromRuntime(SerializableField);
2017-09-15 00:20:36 +00:00
const TypeChecker serializableTypeChecker =
2017-09-15 00:36:28 +00:00
const TypeChecker.fromRuntime(Serializable);
2017-09-15 00:20:36 +00:00
const TypeChecker generatedSerializableTypeChecker =
const TypeChecker.fromRuntime(GeneratedSerializable);
2019-01-07 01:38:04 +00:00
final Map<String, BuildContext> _cache = {};
2018-02-27 19:22:30 +00:00
/// Create a [BuildContext].
2019-01-25 14:44:58 +00:00
Future<BuildContext> buildContext(ClassElement clazz, ConstantReader annotation,
BuildStep buildStep, Resolver resolver, bool autoSnakeCaseNames,
2017-09-15 00:36:28 +00:00
{bool heedExclude: true}) async {
2019-01-07 01:38:04 +00:00
var id = clazz.location.components.join('-');
if (_cache.containsKey(id)) {
return _cache[id];
}
2018-02-28 00:50:16 +00:00
// Check for autoIdAndDateFields, autoSnakeCaseNames
autoSnakeCaseNames =
annotation.peek('autoSnakeCaseNames')?.boolValue ?? autoSnakeCaseNames;
2019-01-07 00:56:05 +00:00
var ctx = new BuildContext(
annotation,
clazz,
originalClassName: clazz.name,
sourceFilename: p.basename(buildStep.inputId.path),
autoSnakeCaseNames: autoSnakeCaseNames,
includeAnnotations:
annotation.peek('includeAnnotations')?.listValue ?? <DartObject>[],
);
2017-09-15 00:36:28 +00:00
var lib = await resolver.libraryFor(buildStep.inputId);
2017-06-20 22:13:04 +00:00
List<String> fieldNames = [];
for (var field in clazz.fields) {
// Skip private fields
if (field.name.startsWith('_')) {
continue;
}
2018-05-13 16:50:59 +00:00
if (field.getter != null &&
(field.setter != null || field.getter.isAbstract)) {
var el = field.setter == null ? field.getter : field;
2017-06-20 22:13:04 +00:00
fieldNames.add(field.name);
2019-01-07 00:56:05 +00:00
// Check for @SerializableField
var fieldAnn = serializableFieldTypeChecker.firstAnnotationOf(el);
if (fieldAnn != null) {
var cr = ConstantReader(fieldAnn);
2019-01-09 19:25:05 +00:00
var sField = SerializableFieldMirror(
2019-01-07 00:56:05 +00:00
alias: cr.peek('alias')?.stringValue,
defaultValue: cr.peek('defaultValue')?.objectValue,
serializer: cr.peek('serializer')?.symbolValue,
deserializer: cr.peek('deserializer')?.symbolValue,
errorMessage: cr.peek('errorMessage')?.stringValue,
isNullable: cr.peek('isNullable')?.boolValue ?? true,
canDeserialize: cr.peek('canDeserialize')?.boolValue ?? false,
canSerialize: cr.peek('canSerialize')?.boolValue ?? false,
exclude: cr.peek('exclude')?.boolValue ?? false,
2019-01-09 19:25:05 +00:00
serializesTo: cr.peek('serializesTo')?.typeValue,
);
2018-05-15 19:11:12 +00:00
2019-01-07 00:56:05 +00:00
ctx.fieldInfo[field.name] = sField;
if (sField.defaultValue != null) {
2019-01-09 19:25:05 +00:00
ctx.defaults[field.name] = sField.defaultValue;
2019-01-07 00:56:05 +00:00
}
if (sField.alias != null) {
ctx.aliases[field.name] = sField.alias;
} else if (autoSnakeCaseNames != false) {
ctx.aliases[field.name] = new ReCase(field.name).snakeCase;
}
if (sField.isNullable == false) {
var reason = sField.errorMessage ??
"Missing required field '${ctx.resolveFieldName(field.name)}' on ${ctx.modelClassName}.";
ctx.requiredFields[field.name] = reason;
}
if (sField.exclude) {
// ignore: deprecated_member_use
ctx.excluded[field.name] = new Exclude(
canSerialize: sField.canSerialize,
canDeserialize: sField.canDeserialize,
);
}
// Apply
} else {
// Skip if annotated with @exclude
var excludeAnnotation = excludeTypeChecker.firstAnnotationOf(el);
if (excludeAnnotation != null) {
var cr = new ConstantReader(excludeAnnotation);
// ignore: deprecated_member_use
ctx.excluded[field.name] = new Exclude(
canSerialize: cr.read('canSerialize').boolValue,
canDeserialize: cr.read('canDeserialize').boolValue,
);
}
// Check for @DefaultValue()
var defAnn =
// ignore: deprecated_member_use
const TypeChecker.fromRuntime(DefaultValue).firstAnnotationOf(el);
if (defAnn != null) {
var rev = new ConstantReader(defAnn).revive().positionalArguments[0];
ctx.defaults[field.name] = rev;
}
// Check for alias
// ignore: deprecated_member_use
Alias alias;
var aliasAnn = aliasTypeChecker.firstAnnotationOf(el);
if (aliasAnn != null) {
// ignore: deprecated_member_use
alias = new Alias(aliasAnn.getField('name').toStringValue());
}
if (alias?.name?.isNotEmpty == true) {
ctx.aliases[field.name] = alias.name;
} else if (autoSnakeCaseNames != false) {
ctx.aliases[field.name] = new ReCase(field.name).snakeCase;
}
// Check for @required
var required =
const TypeChecker.fromRuntime(Required).firstAnnotationOf(el);
if (required != null) {
2019-01-07 01:38:04 +00:00
log.warning(
'Using @required on fields (like ${clazz.name}.${field.name}) is now deprecated; use @SerializableField(isNullable: false) instead.');
2019-01-07 00:56:05 +00:00
var cr = new ConstantReader(required);
var reason = cr.peek('reason')?.stringValue ??
"Missing required field '${ctx.resolveFieldName(field.name)}' on ${ctx.modelClassName}.";
ctx.requiredFields[field.name] = reason;
}
2018-05-15 19:11:12 +00:00
}
2017-06-20 22:13:04 +00:00
ctx.fields.add(field);
}
}
2019-01-09 19:25:05 +00:00
if (const TypeChecker.fromRuntime(Model).isAssignableFromType(clazz.type)) {
2017-06-20 22:13:04 +00:00
if (!fieldNames.contains('id')) {
2018-02-27 19:42:06 +00:00
var idField =
new ShimFieldImpl('id', lib.context.typeProvider.stringType);
2017-06-20 22:13:04 +00:00
ctx.fields.insert(0, idField);
ctx.shimmed['id'] = true;
}
DartType dateTime;
2017-09-15 00:36:28 +00:00
for (var key in ['createdAt', 'updatedAt']) {
2017-06-20 22:13:04 +00:00
if (!fieldNames.contains(key)) {
if (dateTime == null) {
2017-09-15 00:36:28 +00:00
var coreLib =
await resolver.libraries.singleWhere((lib) => lib.isDartCore);
2017-06-20 22:13:04 +00:00
var dt = coreLib.getType('DateTime');
dateTime = dt.type;
}
2018-02-27 19:22:30 +00:00
var field = new ShimFieldImpl(key, dateTime);
2017-06-20 22:13:04 +00:00
ctx.aliases[key] = new ReCase(key).snakeCase;
ctx.fields.add(field);
ctx.shimmed[key] = true;
}
2017-09-15 00:36:28 +00:00
}
2017-06-20 22:13:04 +00:00
}
2018-05-13 17:23:40 +00:00
// Get constructor params, if any
ctx.constructorParameters.addAll(clazz.unnamedConstructor.parameters);
2017-06-20 22:13:04 +00:00
return ctx;
}
2018-02-27 19:22:30 +00:00
/// A manually-instantiated [FieldElement].
class ShimFieldImpl extends FieldElementImpl {
2017-06-20 22:13:04 +00:00
@override
final DartType type;
2018-02-27 19:42:06 +00:00
2018-02-27 19:22:30 +00:00
ShimFieldImpl(String name, this.type) : super(name, -1);
2017-06-20 22:13:04 +00:00
}