使用 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,
});
}
我需要能够生成我的代码的是以下信息:
- 用
FieldAnnotationMarker
的任何子类型注释的所有字段的名称(此处: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'。如何以正确的方式获取字段名称?
- 这让我得到了像
1
for int
或 string 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 的一部分,并且只是 analyzer
中 DartObject
class 的无状态包装。
How can I get the field names in a proper way?
通过从 DartObject
加载类型并参考定义 class:
- 得到
fieldAnnotation.type
- 检查它是
InterfaceType
。 InterfaceType
是 class 用于基于 Dart classes 的类型(相对于说 DynamicType
用于 dynamic
或 FunctionType
用于职能)。由于您正在使用 TypeChecker
来查找匹配的注释,因此您保证只会得到 InterfaceType
s,所以您不妨在那里投射。
- 使用
(fieldAnnotation.type as InterfaceType).accessors
获取为该类型定义的所有 getter 和设置器(请注意,字段也隐式定义了 getter)
- 对于每个访问器,您可以使用
accessor.variable.name
获取字段名称,然后您可以在常量 reader 中查找该名称。跳过 isSetter
为真(因为您只关心 getters)或 isSynthetic
为假(因为您只想获得由字段定义的 getters 而不要不关心在 class). 上定义的显式 get
函数
Is there a way to get the literals as they appear in code, e.g. the string true for bool
, 1
for int
s 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
我正在尝试实现一个基于 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,
});
}
我需要能够生成我的代码的是以下信息:
- 用
FieldAnnotationMarker
的任何子类型注释的所有字段的名称(此处: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'。如何以正确的方式获取字段名称? - 这让我得到了像
1
forint
或string value
forString
这样的值,但我需要从中生成代码,所以我需要最终将字符串值写在引号中。为此,我需要根据类型转换值。有没有办法获取代码中出现的文字,例如布尔值的字符串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 的一部分,并且只是 analyzer
中 DartObject
class 的无状态包装。
How can I get the field names in a proper way?
通过从 DartObject
加载类型并参考定义 class:
- 得到
fieldAnnotation.type
- 检查它是
InterfaceType
。InterfaceType
是 class 用于基于 Dart classes 的类型(相对于说DynamicType
用于dynamic
或FunctionType
用于职能)。由于您正在使用TypeChecker
来查找匹配的注释,因此您保证只会得到InterfaceType
s,所以您不妨在那里投射。 - 使用
(fieldAnnotation.type as InterfaceType).accessors
获取为该类型定义的所有 getter 和设置器(请注意,字段也隐式定义了 getter) - 对于每个访问器,您可以使用
accessor.variable.name
获取字段名称,然后您可以在常量 reader 中查找该名称。跳过isSetter
为真(因为您只关心 getters)或isSynthetic
为假(因为您只想获得由字段定义的 getters 而不要不关心在 class). 上定义的显式
get
函数
Is there a way to get the literals as they appear in code, e.g. the string true for
bool
,1
forint
s 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