Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to generate Dart code for annotations at fields?

I'm writing a code generator for Dart using the build_runner, but my builder is not being called for annotations at fields, although it does work for annotations at classes.

Is it possible to also call the generator for annotations at fields (or at any place for that matter)?

For example, the builder is called for the following file:

import 'package:my_annotation/my_annotation.dart';

part 'example.g.dart';

@MyAnnotation()
class Fruit {
  int number;
}

But not for this one:

import 'package:my_annotation/my_annotation.dart';

part 'example.g.dart';

class Fruit {
  @MyAnnotation()
  int number;
}

Here's the definition of the annotation:

class MyAnnotation {
  const MyAnnotation();
}

And this is how the generator is defined. For now, it just aborts whenever it's called, causing an error message to be printed.

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.');
}

Here's the build.yaml configuration:

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"]
like image 758
Marcel Avatar asked Nov 03 '19 19:11

Marcel


People also ask

How do you make an annotation in darts?

First, create a class that will be used as metadata annotation. Then, use that class as annotation by calling the constructor preceded by @ . You can add annotation at class, field, method, or constructor.

How do you generate a code on flutter?

Generate code for assetsRun flutter packages pub run build_runner build in your root app directory. This command will generate the files for related assets in the lib/gen folder.

How do you use Dart metadata?

In dart a metadata annotation starts with @ symbol, followed by a call to a constant constructor or a compile-time constant such as override. @override and @deprecated are available to all dart codes.

How do you use a build runner?

To learn how to use build_runner with a specific package, see the documentation for that package. If you're a web developer, use the webdev tool to build and serve web apps. The build_runner commands work with builders—packages that use the Dart build system to generate output files from input files.


2 Answers

At least from my experience, your file 'example.dart' would need at least one annotation above the class definition to be parsed by GeneratorForAnnotation.

example.dart:

import 'package:my_annotation/my_annotation.dart';

part 'example.g.dart';

@MyAnnotation()
class Fruit {
 @MyFieldAnnotation()
 int number;
}

To access annotations above class fields or class methods you could use a visitor to "visit" each child element and extract the source code information. For example, to get information about the class fields you could override the method visitFieldElement and then access any annotations using the 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;
  }
}

Note: In this example, _generateSource returns a commented statement. Without comments you would need to return well-formed dart source code, otherwise, the builder will terminate with an error.

For more information see: Source Generation and Writing Your Own Package (The Boring Flutter Development Show, Ep. 22) https://www.youtube.com/watch?v=mYDFOdl-aWM&t=459s

like image 190
Dan R Avatar answered Oct 07 '22 21:10

Dan R


The built-in GeneratorForAnnotation uses the LibraryElement's annotatedWith(...) method, which only checks for top-level annotations. To also detect annotations on fields, you'll need to write something custom.

Here's the Generator I wrote for my project:

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);
}
like image 3
Marcel Avatar answered Oct 07 '22 19:10

Marcel