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';
|
|
|
|
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
|
2019-04-30 15:44:01 +00:00
|
|
|
const TypeChecker aliasTypeChecker = TypeChecker.fromRuntime(Alias);
|
2017-09-15 00:20:36 +00:00
|
|
|
|
2019-04-30 15:44:01 +00:00
|
|
|
const TypeChecker dateTimeTypeChecker = TypeChecker.fromRuntime(DateTime);
|
2018-02-28 01:10:57 +00:00
|
|
|
|
2019-01-07 00:56:05 +00:00
|
|
|
// ignore: deprecated_member_use
|
2019-04-30 15:44:01 +00:00
|
|
|
const TypeChecker excludeTypeChecker = TypeChecker.fromRuntime(Exclude);
|
2017-09-15 00:20:36 +00:00
|
|
|
|
2019-01-07 00:56:05 +00:00
|
|
|
const TypeChecker serializableFieldTypeChecker =
|
2019-04-30 15:44:01 +00:00
|
|
|
TypeChecker.fromRuntime(SerializableField);
|
2019-01-07 00:56:05 +00:00
|
|
|
|
2017-09-15 00:20:36 +00:00
|
|
|
const TypeChecker serializableTypeChecker =
|
2019-04-30 15:44:01 +00:00
|
|
|
TypeChecker.fromRuntime(Serializable);
|
2017-09-15 00:20:36 +00:00
|
|
|
|
2018-07-11 15:45:45 +00:00
|
|
|
const TypeChecker generatedSerializableTypeChecker =
|
2019-04-30 15:44:01 +00:00
|
|
|
TypeChecker.fromRuntime(GeneratedSerializable);
|
2018-07-11 15:45:45 +00:00
|
|
|
|
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,
|
2019-04-30 15:44:01 +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-04-30 15:44:01 +00:00
|
|
|
var ctx = BuildContext(
|
2019-01-07 00:56:05 +00:00
|
|
|
annotation,
|
|
|
|
clazz,
|
|
|
|
originalClassName: clazz.name,
|
|
|
|
sourceFilename: p.basename(buildStep.inputId.path),
|
|
|
|
autoSnakeCaseNames: autoSnakeCaseNames,
|
|
|
|
includeAnnotations:
|
|
|
|
annotation.peek('includeAnnotations')?.listValue ?? <DartObject>[],
|
|
|
|
);
|
2019-07-04 18:30:45 +00:00
|
|
|
// var lib = await resolver.libraryFor(buildStep.inputId);
|
2017-06-20 22:13:04 +00:00
|
|
|
List<String> fieldNames = [];
|
2019-07-04 18:30:45 +00:00
|
|
|
var fields = <FieldElement>[];
|
|
|
|
|
|
|
|
// Crawl for classes from parent classes.
|
|
|
|
void crawlClass(InterfaceType t) {
|
|
|
|
while (t != null) {
|
|
|
|
fields.insertAll(0, t.element.fields);
|
|
|
|
t.interfaces.forEach(crawlClass);
|
|
|
|
t = t.superclass;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
crawlClass(clazz.type);
|
2017-06-20 22:13:04 +00:00
|
|
|
|
2019-07-04 18:30:45 +00:00
|
|
|
for (var field in fields) {
|
2018-07-11 15:45:45 +00:00
|
|
|
// 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);
|
2018-03-02 18:38:41 +00:00
|
|
|
|
2019-01-07 00:56:05 +00:00
|
|
|
// Check for @SerializableField
|
|
|
|
var fieldAnn = serializableFieldTypeChecker.firstAnnotationOf(el);
|
|
|
|
|
2019-04-04 21:40:36 +00:00
|
|
|
void handleSerializableField(SerializableFieldMirror sField) {
|
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) {
|
2019-04-30 15:44:01 +00:00
|
|
|
ctx.aliases[field.name] = ReCase(field.name).snakeCase;
|
2019-01-07 00:56:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
2019-04-30 15:44:01 +00:00
|
|
|
ctx.excluded[field.name] = Exclude(
|
2019-01-07 00:56:05 +00:00
|
|
|
canSerialize: sField.canSerialize,
|
|
|
|
canDeserialize: sField.canDeserialize,
|
|
|
|
);
|
|
|
|
}
|
2019-04-04 21:40:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (fieldAnn != null) {
|
|
|
|
var cr = ConstantReader(fieldAnn);
|
|
|
|
var excluded = cr.peek('exclude')?.boolValue ?? false;
|
|
|
|
var sField = SerializableFieldMirror(
|
|
|
|
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 ?? !excluded,
|
|
|
|
canDeserialize: cr.peek('canDeserialize')?.boolValue ?? false,
|
|
|
|
canSerialize: cr.peek('canSerialize')?.boolValue ?? false,
|
|
|
|
exclude: excluded,
|
|
|
|
serializesTo: cr.peek('serializesTo')?.typeValue,
|
|
|
|
);
|
|
|
|
|
|
|
|
handleSerializableField(sField);
|
2019-01-07 00:56:05 +00:00
|
|
|
|
|
|
|
// Apply
|
|
|
|
} else {
|
2019-04-04 21:40:36 +00:00
|
|
|
var foundNone = true;
|
2019-01-07 00:56:05 +00:00
|
|
|
// Skip if annotated with @exclude
|
|
|
|
var excludeAnnotation = excludeTypeChecker.firstAnnotationOf(el);
|
|
|
|
|
|
|
|
if (excludeAnnotation != null) {
|
2019-04-30 15:44:01 +00:00
|
|
|
var cr = ConstantReader(excludeAnnotation);
|
2019-04-04 21:40:36 +00:00
|
|
|
foundNone = false;
|
2019-01-07 00:56:05 +00:00
|
|
|
|
|
|
|
// ignore: deprecated_member_use
|
2019-04-30 15:44:01 +00:00
|
|
|
ctx.excluded[field.name] = Exclude(
|
2019-01-07 00:56:05 +00:00
|
|
|
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) {
|
2019-04-30 15:44:01 +00:00
|
|
|
var rev = ConstantReader(defAnn).revive().positionalArguments[0];
|
2019-01-07 00:56:05 +00:00
|
|
|
ctx.defaults[field.name] = rev;
|
2019-04-04 21:40:36 +00:00
|
|
|
foundNone = false;
|
2019-01-07 00:56:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check for alias
|
|
|
|
// ignore: deprecated_member_use
|
|
|
|
Alias alias;
|
|
|
|
var aliasAnn = aliasTypeChecker.firstAnnotationOf(el);
|
|
|
|
|
|
|
|
if (aliasAnn != null) {
|
|
|
|
// ignore: deprecated_member_use
|
2019-04-30 15:44:01 +00:00
|
|
|
alias = Alias(aliasAnn.getField('name').toStringValue());
|
2019-04-04 21:40:36 +00:00
|
|
|
foundNone = false;
|
2019-01-07 00:56:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (alias?.name?.isNotEmpty == true) {
|
|
|
|
ctx.aliases[field.name] = alias.name;
|
|
|
|
} else if (autoSnakeCaseNames != false) {
|
2019-04-30 15:44:01 +00:00
|
|
|
ctx.aliases[field.name] = ReCase(field.name).snakeCase;
|
2019-01-07 00:56:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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-04-30 15:44:01 +00:00
|
|
|
var cr = ConstantReader(required);
|
2019-01-07 00:56:05 +00:00
|
|
|
var reason = cr.peek('reason')?.stringValue ??
|
|
|
|
"Missing required field '${ctx.resolveFieldName(field.name)}' on ${ctx.modelClassName}.";
|
|
|
|
ctx.requiredFields[field.name] = reason;
|
2019-04-04 21:40:36 +00:00
|
|
|
foundNone = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (foundNone) {
|
|
|
|
var f = SerializableField();
|
|
|
|
var sField = SerializableFieldMirror(
|
|
|
|
alias: f.alias,
|
|
|
|
defaultValue: null,
|
|
|
|
serializer: f.serializer,
|
|
|
|
deserializer: f.deserializer,
|
|
|
|
errorMessage: f.errorMessage,
|
|
|
|
isNullable: f.isNullable,
|
|
|
|
canDeserialize: f.canDeserialize,
|
|
|
|
canSerialize: f.canSerialize,
|
|
|
|
exclude: f.exclude,
|
|
|
|
serializesTo: null,
|
|
|
|
);
|
|
|
|
handleSerializableField(sField);
|
2019-01-07 00:56:05 +00:00
|
|
|
}
|
2018-05-15 19:11:12 +00:00
|
|
|
}
|
|
|
|
|
2017-06-20 22:13:04 +00:00
|
|
|
ctx.fields.add(field);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-04 18:30:45 +00:00
|
|
|
// ShimFields are no longer used.
|
|
|
|
// if (const TypeChecker.fromRuntime(Model).isAssignableFromType(clazz.type)) {
|
|
|
|
// if (!fieldNames.contains('id')) {
|
|
|
|
// var idField = ShimFieldImpl('id', lib.context.typeProvider.stringType);
|
|
|
|
// ctx.fields.insert(0, idField);
|
|
|
|
// ctx.shimmed['id'] = true;
|
|
|
|
// }
|
|
|
|
|
|
|
|
// DartType dateTime;
|
|
|
|
// for (var key in ['createdAt', 'updatedAt']) {
|
|
|
|
// if (!fieldNames.contains(key)) {
|
|
|
|
// if (dateTime == null) {
|
|
|
|
// var coreLib =
|
|
|
|
// await resolver.libraries.singleWhere((lib) => lib.isDartCore);
|
|
|
|
// var dt = coreLib.getType('DateTime');
|
|
|
|
// dateTime = dt.type;
|
|
|
|
// }
|
|
|
|
|
|
|
|
// var field = ShimFieldImpl(key, dateTime);
|
|
|
|
// ctx.aliases[key] = ReCase(key).snakeCase;
|
|
|
|
// ctx.fields.add(field);
|
|
|
|
// ctx.shimmed[key] = true;
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
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
|
|
|
}
|