使用 source_gen 生成代码时如何获取注释字段名称

How to get annotation field names in when generating code with source_gen

我正在尝试实现一个基于 source_gen 的生成器。它将处理用 ClassAnnotation 注释的 classes 并作用于用各种注释注释的字段,所有这些都是 'marker' 接口 FieldAnnotationMarker 的子类型。这是 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,
  });
}

这里有一个注释的例子 class:

@immutable
@ClassAnnotation()
class Person {
  @FieldAnnotationA(fooString: 'foo', barInt: 17)
  @FieldAnnotationB(bazBool: true)
  final int age;

  const Person({
    required this.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. 这让我得到了像 1 for intstring value for String 这样的值,但我需要从中生成代码,所以我需要最终将字符串值写在引号中。为此,我需要根据类型转换值。有没有办法获取代码中出现的文字,例如布尔值的字符串 true,整数的 1 和字符串的 'string value'(带引号)?这将允许我在生成代码时直接粘贴此值。

理想情况下,我可以获得一个完整的字符串,例如FieldAnnotationA(fooString: 'foo', barInt: 17) 而不是必须收集部分(类型名称、字段名称、值),然后再次将所有部分连接在一起 - 这可能吗?

Here I want to fetch annotations that are subtypes of FieldAnnotationMarker - this seems to work for my basic tests but I'm not sure if this is the right way?

对注释使用 TypeChecker 也将匹配子类型,因此这将起作用并且是查找特定类型的注释的一个不错的解决方案。

Is it Ok to just instantiate my own ConstantReader?

当然!它是 source_gen 的 public API 的一部分,并且只是 analyzerDartObject class 的无状态包装。

How can I get the field names in a proper way?

通过从 DartObject 加载类型并参考定义 class:

  1. 得到fieldAnnotation.type
  2. 检查它是 InterfaceTypeInterfaceType 是 class 用于基于 Dart classes 的类型(相对于说 DynamicType 用于 dynamicFunctionType 用于职能)。由于您正在使用 TypeChecker 来查找匹配的注释,因此您保证只会得到 InterfaceTypes,所以您不妨在那里投射。
  3. 使用 (fieldAnnotation.type as InterfaceType).accessors 获取为该类型定义的所有 getter 和设置器(请注意,字段也隐式定义了 getter)
  4. 对于每个访问器,您可以使用 accessor.variable.name 获取字段名称,然后您可以在常量 reader 中查找该名称。跳过 isSetter 为真(因为您只关心 getters)或 isSynthetic 为假(因为您只想获得由字段定义的 getters 而不要不关心在 class).
  5. 上定义的显式 get 函数

Is there a way to get the literals as they appear in code, e.g. the string true for bool, 1 for ints and 'string value' (with the quotes) for strings?

对于除字符串以外的所有内容,您只需使用 toString() 即可获得有效文字。对于字符串,您可以手动编写一些内容:

String asDartLiteral(String value) {
  final escaped = escapeForDart(value);
  return "'$escaped'";
}

String escapeForDart(String value) {
  return value
      .replaceAll('\', '\\')
      .replaceAll("'", "\'")
      .replaceAll('$', '\$')
      .replaceAll('\r', '\r')
      .replaceAll('\n', '\n');
}

或者使用一个为你做 code-generation 的包,比如 package:code_builder.

中的 literalString