Как получить имена полей аннотаций при генерации кода с помощью 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>:

  1. Здесь я хочу получить аннотации, которые являются подтипами FieldAnnotationMarker- кажется, это работает для моих основных тестов, но я не уверен, что это правильный путь?
  2. Можно ли просто создать экземпляр моего собственного ConstantReader?
  3. Я не хочу жестко кодировать конкретные типы в генераторе (пользователи могут регистрировать пользовательские аннотации и их процессоры), поэтому мне нужен способ динамического получения всех полей и значений из аннотаций. Я вижу все, что мне нужно, в отладчике, но в коде единственный способ получить поля аннотаций, который я нашел, - это приведение к DartObjectImplкоторый я ненавижу и который также заставляет меня импортировать внутренний 'analyzer/src'. Как правильно получить имена полей?
  4. Это дает мне значение, как для intили же string valueза String, но мне нужно будет сгенерировать код из этого, поэтому в конечном итоге мне нужно будет написать строковое значение в кавычках. Для этого мне нужно преобразовать значение на основе типа. Есть ли способ получить литералы, как они появляются в коде, например, строку trueдля бул, 1для целых и 'string value'(с кавычками) для строк? Это позволило бы мне просто вставить это значение непосредственно при создании кода.

В идеале я мог бы просто получить целую строку, содержащую, например, FieldAnnotationA(fooString: 'foo', barInt: 17)вместо того, чтобы собирать части (имя типа, имена полей, значения), а затем снова соединять все это вместе - возможно ли это?

0 ответов

Другие вопросы по тегам