如何为字段的注解生成 Dart 代码?
How to generate Dart code for annotations at fields?
我正在使用 build_runner
为 Dart 编写代码生成器,但是我的构建器没有被调用以在字段中进行注释,尽管它确实适用于 类 中的注释。
是否也可以在字段(或任何地方)调用生成器进行注释?
例如,为以下文件调用生成器:
import 'package:my_annotation/my_annotation.dart';
part 'example.g.dart';
@MyAnnotation()
class Fruit {
int number;
}
但不是这个:
import 'package:my_annotation/my_annotation.dart';
part 'example.g.dart';
class Fruit {
@MyAnnotation()
int number;
}
注释的定义如下:
class MyAnnotation {
const MyAnnotation();
}
这就是生成器的定义方式。目前,它只是在调用时中止,导致打印错误消息。
library my_annotation_generator;
import 'package:analyzer/dart/element/element.dart';
import 'package:build/build.dart';
import 'package:my_annotation/my_annotation.dart';
import 'package:source_gen/source_gen.dart';
Builder generateAnnotation(BuilderOptions options) =>
SharedPartBuilder([MyAnnotationGenerator()], 'my_annotation');
class MyAnnotationGenerator extends GeneratorForAnnotation<MyAnnotation> {
@override
generateForAnnotatedElement(Element element, ConstantReader annotation, _) {
throw CodeGenError('Generating code for annotation is not implemented yet.');
}
这是 build.yaml
配置:
targets:
$default:
builders:
my_annotation_generator|my_annotation:
enabled: true
builders:
my_annotation:
target: ":my_annotation_generator"
import: "package:my_annotation/my_annotation.dart"
builder_factories: ["generateAnnotation"]
build_extensions: { ".dart": [".my_annotation.g.part"] }
auto_apply: dependents
build_to: cache
applies_builders: ["source_gen|combining_builder"]
至少根据我的经验,您的文件 'example.dart' 需要在 class 定义之上至少有一个注释才能被 GeneratorForAnnotation 解析。
example.dart:
import 'package:my_annotation/my_annotation.dart';
part 'example.g.dart';
@MyAnnotation()
class Fruit {
@MyFieldAnnotation()
int number;
}
要访问 class 字段或 class 方法上方的注释,您可以使用访问者访问 "visit" 每个子元素并提取源代码信息。
例如,要获取有关 class 字段的信息,您可以覆盖方法 visitFieldElement,然后使用 getter 访问任何注释:element.metadata.
builder.dart:
import 'dart:async';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/visitor.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:build/src/builder/build_step.dart';
import 'package:source_gen/source_gen.dart';
import 'package:my_annotation/my_annotation.dart';
class MyAnnotationGenerator extends
GeneratorForAnnotation<MyAnnotation> {
@override
FutureOr<String> generateForAnnotatedElement(
Element element,
ConstantReader annotation,
BuildStep buildStep,){
return _generateSource(element);
}
String _generateSource(Element element) {
var visitor = ModelVisitor();
element.visitChildren(visitor);
return '''
// ${visitor.className}
// ${visitor.fields}
// ${visitor.metaData}
''';
}
}
class ModelVisitor extends SimpleElementVisitor {
DartType className;
Map<String, DartType> fields = {};
Map<String, dynamic> metaData = {};
@override
visitConstructorElement(ConstructorElement element) {
className = element.type.returnType;
}
@override
visitFieldElement(FieldElement element) {
fields[element.name] = element.type;
metaData[element.name] = element.metadata;
}
}
注意:在这个例子中,_generateSource return是一个注释语句。如果没有注释,您将需要 return 格式良好的 dart 源代码,否则构建器将因错误而终止。
有关详细信息,请参阅:
源代码生成和编写自己的程序包(无聊的 Flutter 开发秀,第 22 集)https://www.youtube.com/watch?v=mYDFOdl-aWM&t=459s
内置的 GeneratorForAnnotation
使用 LibraryElement
的 annotatedWith(...)
方法,它只检查顶级注释。
要同时检测字段上的注释,您需要编写一些自定义内容。
这是我为我的项目写的Generator
:
abstract class GeneratorForAnnotatedField<AnnotationType> extends Generator {
/// Returns the annotation of type [AnnotationType] of the given [element],
/// or [null] if it doesn't have any.
DartObject getAnnotation(Element element) {
final annotations =
TypeChecker.fromRuntime(AnnotationType).annotationsOf(element);
if (annotations.isEmpty) {
return null;
}
if (annotations.length > 1) {
throw Exception(
"You tried to add multiple @$AnnotationType() annotations to the "
"same element (${element.name}), but that's not possible.");
}
return annotations.single;
}
@override
String generate(LibraryReader library, BuildStep buildStep) {
final values = <String>{};
for (final element in library.allElements) {
if (element is ClassElement && !element.isEnum) {
for (final field in element.fields) {
final annotation = getAnnotation(field);
if (annotation != null) {
values.add(generateForAnnotatedField(
field,
ConstantReader(annotation),
));
}
}
}
}
return values.join('\n\n');
}
String generateForAnnotatedField(
FieldElement field, ConstantReader annotation);
}
我遇到了一个非常相似的问题,试图在我带注释的 classes 中定位特定方法。受您回答的启发,我稍微修改了 class 注释 model_visitor
以在选择元素之前检查方法注释。
class ClassAnnotationModelVisitor extends SimpleElementVisitor<dynamic> {
String className;
Map<String, String> methods = <String, String>{};
Map<String, String> parameters = <String, String>{};
@override
dynamic visitConstructorElement(ConstructorElement element) {
final elementReturnType = element.type.returnType.toString();
className = elementReturnType.replaceFirst('*', '');
}
@override
dynamic visitMethodElement(MethodElement element) {
if (methodHasAnnotation(MethodAnnotation, element)) {
final functionReturnType = element.type.returnType.toString();
methods[element.name] = functionReturnType.replaceFirst('*', '');
parameters[element.name] = element.parameters.map((e) => e.name).join(' ,');
}
}
bool methodHasAnnotation(Type annotationType, MethodElement element) {
final annotations = TypeChecker.fromRuntime(annotationType).annotationsOf(element);
return !annotations.isEmpty;
}
}
然后,我可以使用基本的 GeneratorForAnnotation
class 并生成 class 和 methodsArray.
我正在使用 build_runner
为 Dart 编写代码生成器,但是我的构建器没有被调用以在字段中进行注释,尽管它确实适用于 类 中的注释。
是否也可以在字段(或任何地方)调用生成器进行注释?
例如,为以下文件调用生成器:
import 'package:my_annotation/my_annotation.dart';
part 'example.g.dart';
@MyAnnotation()
class Fruit {
int number;
}
但不是这个:
import 'package:my_annotation/my_annotation.dart';
part 'example.g.dart';
class Fruit {
@MyAnnotation()
int number;
}
注释的定义如下:
class MyAnnotation {
const MyAnnotation();
}
这就是生成器的定义方式。目前,它只是在调用时中止,导致打印错误消息。
library my_annotation_generator;
import 'package:analyzer/dart/element/element.dart';
import 'package:build/build.dart';
import 'package:my_annotation/my_annotation.dart';
import 'package:source_gen/source_gen.dart';
Builder generateAnnotation(BuilderOptions options) =>
SharedPartBuilder([MyAnnotationGenerator()], 'my_annotation');
class MyAnnotationGenerator extends GeneratorForAnnotation<MyAnnotation> {
@override
generateForAnnotatedElement(Element element, ConstantReader annotation, _) {
throw CodeGenError('Generating code for annotation is not implemented yet.');
}
这是 build.yaml
配置:
targets:
$default:
builders:
my_annotation_generator|my_annotation:
enabled: true
builders:
my_annotation:
target: ":my_annotation_generator"
import: "package:my_annotation/my_annotation.dart"
builder_factories: ["generateAnnotation"]
build_extensions: { ".dart": [".my_annotation.g.part"] }
auto_apply: dependents
build_to: cache
applies_builders: ["source_gen|combining_builder"]
至少根据我的经验,您的文件 'example.dart' 需要在 class 定义之上至少有一个注释才能被 GeneratorForAnnotation 解析。
example.dart:
import 'package:my_annotation/my_annotation.dart';
part 'example.g.dart';
@MyAnnotation()
class Fruit {
@MyFieldAnnotation()
int number;
}
要访问 class 字段或 class 方法上方的注释,您可以使用访问者访问 "visit" 每个子元素并提取源代码信息。 例如,要获取有关 class 字段的信息,您可以覆盖方法 visitFieldElement,然后使用 getter 访问任何注释:element.metadata.
builder.dart:
import 'dart:async';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/visitor.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:build/src/builder/build_step.dart';
import 'package:source_gen/source_gen.dart';
import 'package:my_annotation/my_annotation.dart';
class MyAnnotationGenerator extends
GeneratorForAnnotation<MyAnnotation> {
@override
FutureOr<String> generateForAnnotatedElement(
Element element,
ConstantReader annotation,
BuildStep buildStep,){
return _generateSource(element);
}
String _generateSource(Element element) {
var visitor = ModelVisitor();
element.visitChildren(visitor);
return '''
// ${visitor.className}
// ${visitor.fields}
// ${visitor.metaData}
''';
}
}
class ModelVisitor extends SimpleElementVisitor {
DartType className;
Map<String, DartType> fields = {};
Map<String, dynamic> metaData = {};
@override
visitConstructorElement(ConstructorElement element) {
className = element.type.returnType;
}
@override
visitFieldElement(FieldElement element) {
fields[element.name] = element.type;
metaData[element.name] = element.metadata;
}
}
注意:在这个例子中,_generateSource return是一个注释语句。如果没有注释,您将需要 return 格式良好的 dart 源代码,否则构建器将因错误而终止。
有关详细信息,请参阅: 源代码生成和编写自己的程序包(无聊的 Flutter 开发秀,第 22 集)https://www.youtube.com/watch?v=mYDFOdl-aWM&t=459s
内置的 GeneratorForAnnotation
使用 LibraryElement
的 annotatedWith(...)
方法,它只检查顶级注释。
要同时检测字段上的注释,您需要编写一些自定义内容。
这是我为我的项目写的Generator
:
abstract class GeneratorForAnnotatedField<AnnotationType> extends Generator {
/// Returns the annotation of type [AnnotationType] of the given [element],
/// or [null] if it doesn't have any.
DartObject getAnnotation(Element element) {
final annotations =
TypeChecker.fromRuntime(AnnotationType).annotationsOf(element);
if (annotations.isEmpty) {
return null;
}
if (annotations.length > 1) {
throw Exception(
"You tried to add multiple @$AnnotationType() annotations to the "
"same element (${element.name}), but that's not possible.");
}
return annotations.single;
}
@override
String generate(LibraryReader library, BuildStep buildStep) {
final values = <String>{};
for (final element in library.allElements) {
if (element is ClassElement && !element.isEnum) {
for (final field in element.fields) {
final annotation = getAnnotation(field);
if (annotation != null) {
values.add(generateForAnnotatedField(
field,
ConstantReader(annotation),
));
}
}
}
}
return values.join('\n\n');
}
String generateForAnnotatedField(
FieldElement field, ConstantReader annotation);
}
我遇到了一个非常相似的问题,试图在我带注释的 classes 中定位特定方法。受您回答的启发,我稍微修改了 class 注释 model_visitor
以在选择元素之前检查方法注释。
class ClassAnnotationModelVisitor extends SimpleElementVisitor<dynamic> {
String className;
Map<String, String> methods = <String, String>{};
Map<String, String> parameters = <String, String>{};
@override
dynamic visitConstructorElement(ConstructorElement element) {
final elementReturnType = element.type.returnType.toString();
className = elementReturnType.replaceFirst('*', '');
}
@override
dynamic visitMethodElement(MethodElement element) {
if (methodHasAnnotation(MethodAnnotation, element)) {
final functionReturnType = element.type.returnType.toString();
methods[element.name] = functionReturnType.replaceFirst('*', '');
parameters[element.name] = element.parameters.map((e) => e.name).join(' ,');
}
}
bool methodHasAnnotation(Type annotationType, MethodElement element) {
final annotations = TypeChecker.fromRuntime(annotationType).annotationsOf(element);
return !annotations.isEmpty;
}
}
然后,我可以使用基本的 GeneratorForAnnotation
class 并生成 class 和 methodsArray.