Как получить имена полей аннотаций при генерации кода с помощью source_gen
Я пытаюсь реализовать генератор на основе
source_gen
. Он будет обрабатывать классы, аннотированные с помощью
ClassAnnotation
и воздействовать на поля, аннотированные различными аннотациями, все из которых являются подтипами интерфейса «маркер». Вот пример 2 таких аннотаций:
@immutable
@Target({TargetKind.classType})
class ClassAnnotation {
const ClassAnnotation();
}
abstract class FieldAnnotationMarker {}
@immutable
@Target({TargetKind.field})
class FieldAnnotationA implements FieldAnnotationMarker {
final String fooString;
final int barInt;
const FieldAnnotationA({
required this.fooString,
required this.barInt,
});
}
@immutable
@Target({TargetKind.field})
class FieldAnnotationB implements FieldAnnotationMarker {
final bool bazBool;
const FieldAnnotationB({
required this.bazBool,
});
}
Вот пример аннотированного класса:
@immutable
@ClassAnnotation()
class Person {
@FieldAnnotationA(fooString: 'foo', barInt: 17)
@FieldAnnotationB(bazBool: true)
final int age;
const Person({
required this.age,
});
}
Что мне нужно, чтобы иметь возможность генерировать мой код, так это следующая информация:
- Имена всех полей, аннотированные любым подтипом (здесь:
age
). - Для каждого поля: список аннотаций (мне нужен тип, его имена полей и значения во входном исходном файле).
Например, я мог бы сгенерировать такой код:
import 'person.dart';
void function(Person person) {
// person.age
final ageAnnotations = [
const FieldAnnotationA(fooString: 'foo', barInt: 17),
const FieldAnnotationB(bazBool: true),
];
for (final annotation in ageAnnotations) {
final processor = annotationProcessors[annotation.runtimeType]!; // annotationProcessors will be imported, is a Map<Type, AnnotationProcessor>
processor.process(person.age, annotation); // Each processor gets field value and the annotation and knows what to do with this information.
}
// Other annotated fields, if exist, according to the pattern.
}
Вот мой код на данный момент - я могу собирать метаданные, но делаю это очень отрывочно, и это не очень удобно в использовании:
class MyGenerator extends GeneratorForAnnotation<ClassAnnotation> {
@override
String? generateForAnnotatedElement(Element element, ConstantReader annotation, BuildStep buildStep) {
final fieldVisitor = _FieldVisitor();
element.visitChildren(fieldVisitor);
print(fieldVisitor.fields); // Will generate code based on collected data.
return null;
}
}
class _FieldVisitor extends SimpleElementVisitor<void> {
final Map<String, List<_Annotation>> fields = {};
@override
void visitFieldElement(FieldElement element) {
final annotations = _fieldAnnotationTypeChecker.annotationsOf(element).map( // <<< 1
(fieldAnnotation) {
final typeName = fieldAnnotation.type!.element!.name!;
final annotationReader = ConstantReader(fieldAnnotation); // <<< 2
final impl = fieldAnnotation as DartObjectImpl; // <<< 3
final fields = impl.fields!.map(
(fieldName, field) {
return MapEntry(
fieldName,
annotationReader.read(fieldName).literalValue, // <<< 4
);
},
);
return _Annotation(typeName, fields);
},
).toList(growable: false);
fields[element.name] = annotations;
}
}
const _fieldAnnotationTypeChecker =
TypeChecker.fromRuntime(FieldAnnotationMarker);
@immutable
class _Annotation {
final String typeName;
final Map<String, Object?> fields;
const _Annotation(this.typeName, this.fields);
}
Вот вопросы к строкам, отмеченным
<<< <number>
:
- Здесь я хочу получить аннотации, которые являются подтипами
FieldAnnotationMarker
- кажется, это работает для моих основных тестов, но я не уверен, что это правильный путь? - Можно ли просто создать экземпляр моего собственного
ConstantReader
? - Я не хочу жестко кодировать конкретные типы в генераторе (пользователи могут регистрировать пользовательские аннотации и их процессоры), поэтому мне нужен способ динамического получения всех полей и значений из аннотаций. Я вижу все, что мне нужно, в отладчике, но в коде единственный способ получить поля аннотаций, который я нашел, - это приведение к
DartObjectImpl
который я ненавижу и который также заставляет меня импортировать внутренний 'analyzer/src'. Как правильно получить имена полей? - Это дает мне значение, как для
int
или жеstring value
заString
, но мне нужно будет сгенерировать код из этого, поэтому в конечном итоге мне нужно будет написать строковое значение в кавычках. Для этого мне нужно преобразовать значение на основе типа. Есть ли способ получить литералы, как они появляются в коде, например, строкуtrue
для бул,1
для целых и'string value'
(с кавычками) для строк? Это позволило бы мне просто вставить это значение непосредственно при создании кода.
В идеале я мог бы просто получить целую строку, содержащую, например,
FieldAnnotationA(fooString: 'foo', barInt: 17)
вместо того, чтобы собирать части (имя типа, имена полей, значения), а затем снова соединять все это вместе - возможно ли это?