【问题标题】:How to generate Dart code for annotations at fields?如何为字段的注释生成 Dart 代码?
【发布时间】:2020-02-29 04:36:42
【问题描述】:

我正在使用 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"]

【问题讨论】:

    标签: dart build-runner


    【解决方案1】:

    至少根据我的经验,您的文件“example.dart”需要在类定义上方至少有一个注释才能由 GeneratorForAnnotation 解析。

    example.dart:

    import 'package:my_annotation/my_annotation.dart';
    
    part 'example.g.dart';
    
    @MyAnnotation()
    class Fruit {
     @MyFieldAnnotation()
     int number;
    }
    

    要访问类字段或类方法上方的注释,您可以使用访问者“访问”每个子元素并提取源代码信息。 例如,要获取有关类字段的信息,您可以覆盖方法 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 返回一个注释语句。如果没有 cmets,您将需要返回格式良好的 dart 源代码,否则,构建器将因错误而终止。

    有关详细信息,请参阅: 源代码生成和编写自己的包(无聊的 Flutter 开发秀,第 22 集)https://www.youtube.com/watch?v=mYDFOdl-aWM&t=459s

    【讨论】:

    • 太好了,谢谢!我最终没有使用只能检测顶级注释的GeneratorForAnnotation,而是生成了一个自定义的Generator,它解析了所有字段。你可以在这里看到它的实际效果:pub.dev/packages/has_is_getters
    • @Marcel,感谢您提供项目链接。请考虑在这里简要回答您自己的问题。虽然可以在 github.com 上找到代码生成示例,但仍然缺乏针对那些开始使用 Dart 源代码生成的文档。
    • 元数据不包括字段注释,这是有意的,如果没有,我们如何访问字段注释?
    • 当我尝试这个时,metadata 总是空的。后来我意识到我是在注释 getter 而不是字段。因此,如果您也遇到此问题,请检查您是否对字段进行了注释。如果您有 getter,则需要在访问者中实现 visitPropertyAccessorElement(PropertyAccessorElement element)
    【解决方案2】:

    内置的GeneratorForAnnotation 使用LibraryElementannotatedWith(...) 方法,它只检查顶级注释。 要同时检测字段上的注释,您需要编写一些自定义内容。

    这是我为我的项目写的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);
    }
    

    【讨论】:

    • 你是如何使用这个的?我们如何将这些结果嵌套在 GeneratorForAnnotation 的结果中?考虑一个带有注释的类,其中包含带有注释的字段
    【解决方案3】:

    我有一个非常相似的问题,试图在我的注释类中定位特定方法。受您的回答启发,我稍微修改了类注解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 类并为类和方法数组生成。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-07-05
      • 1970-01-01
      • 2010-11-03
      • 1970-01-01
      • 2015-11-26
      • 1970-01-01
      • 1970-01-01
      • 2017-04-21
      相关资源
      最近更新 更多