Dart: generate code from annotations

Time:2021-11-24
introduction

Refuse to repeat work and pursue efficiency and performance. Dart based annotation processing librarysource_gen, let’s take a look at how to generate code using custom annotations.

Add reference

source_gen: used to parse annotations
build_runner: used to generate code

dependencies:
  flutter:
    sdk: flutter
  source_gen:
  build_runner:
  • Step 1: Customize annotation

New test_ Anotaion.dart file, the class parammetadata brought in by customization is used as annotation

//Bring in the user-defined annotation of the parameter
class ParamMetadata {
  final String name;
  final int id;

  const ParamMetadata(this.name, this.id);
}

Important: the construction method of annotation class requires const modification

  • Step 2: use annotations

After the annotation class is defined, create a new file model_ Class. Dart, create an entity class, and useParamMetadataAnnotate:

@ParamMetadata("annotationClass", 1)
class ClassModel {
  final String className;
  final List<Student> members;

  ClassModel(this.className, this.members);

  void funPrint(String content) {
    print("className = $className ; members = ${members.toString()}");
  }
}

class Student {
  final String name;
  const Student(this.name);
}
  • Step 3: parse the annotation and generate the code
  1. To create a parser, we need to inherit the generatorforannotation < T > generic t as the annotation class to be parsed, that is, our customized parammetadata.
import 'package:source_gen/source_gen.dart';
import 'package:test_anotation/anotation/test_anotation.dart';
import 'package:analyzer/dart/element/element.dart' as e;
import 'package:build/build.dart';
import 'package:test_anotation/generator/temp_code.dart';

class TestGenerator extends GeneratorForAnnotation<ParamMetadata> {
  @override
  generateForAnnotatedElement(e.Element element, ConstantReader annotation, BuildStep buildStep) {
    analyseBuildStep(buildStep);
    analyseAnnotation(annotation);
    return tempCode(element.name, analyseElement(element));
  }

  //Analysis element
  String analyseElement(e.Element element) {
    print("ElementKind : ${element.kind.name} \n");
    switch (element.kind) {
      case e.ElementKind.CLASS:
        //Comments are used on classes
        return _analyseElementForClass(element as e.ClassElement);
      case e.ElementKind.FUNCTION:
        //Annotations are used on methods
        return _analyseElementForMethod(element as e.FunctionElement);
      default:
        return "";
    }
  }

  //Annotations are used on classes
  String _analyseElementForClass(e.ClassElement classElement) {
    Var fieldstr = "the intercepted member fields in class are: \ n";
    for (var e in classElement.fields) {
      fieldStr += "   ${e.declaration}\n";
    }
    Var methodstr = "member methods intercepted in class: \ n";
    for (var e in classElement.methods) {
      methodStr += "   ${e.declaration}\n";
    }
    return fieldStr + "\n" + methodStr;
  }

  //Annotations are used on methods
  String _analyseElementForMethod(e.FunctionElement methodElement) {
    var result =
        "Method name: ${methodelement. Name}, method parameters: ${methodelement. Parameters [0]. Declaration} \ n";
    return result;
  }

  //Analysis annotation transfer parameter
  void analyseAnnotation(ConstantReader annotation) {
    print("analyseAnnotation \n");
    print("params - name : ${annotation.read("name")}\n");
    print("params - id : ${annotation.read("id")}\n");
  }

  //Analyze the input and output information of the build
  void analyseBuildStep(BuildStep buildStep) {
    Print ("current input source: ${buildstep. Inputid. Tostring()} \ n");
  }
}

//Templates for creating files
tempCode(String className, String content) {
  return """
      class ${className}APT {
        /**
        $content
        */
      }
      """;
}

Override generateforannotatedelement to intercept annotations of parammetadata type:

  • analyseElement(): get the analysis elements, distinguish the element types and handle them separately. See the code annotation for details. In the demo, the parsed content is sealed into a string, and the text result is finally displayed on the generated code file.
  • analyseBuildStep(): get the input and output information of the build, for example, it will be printed in the demo
    Current input source: Test_ anotation|lib/model/model_ class.dart
  • analyseAnnotation(): get the parameters of the analysis annotation, such as the name and ID parameter fields in our annotation construction method

A small partner should notice that it is used in the import referenceasKeyword. When using an element, it is directly referenced by an alias because the element has duplicate names in different librariesasKeyword processing error red.

import 'package:analyzer/dart/element/element.dart' as e;

...
generateForAnnotatedElement(e.Element element, ConstantReader annotation, BuildStep buildStep) 
...

  1. configuration file

We know that dart: mirror is disabled in shutter, so reflection cannot be used. Therefore, we can only trigger the operation during compilation through the command. Before executing the command, we need to implement another step to create the configuration file:

  • Create a global method with a return type of Builder: create a new file (arbitrary name), such as test_ Build.dart, as follows:
import 'package:source_gen/source_gen.dart';
import 'package:build/build.dart' as build;
import 'package:test_anotation/generator/annotation_class_generator.dart';

build.Builder testBuilder(build.BuilderOptions options) => LibraryBuilder(TestGenerator());
//Testgenerator is our parser
  • Create a build.yaml file in the root directory of the project. Its significance is to configure various parameters of the Builder:
builders:
  testBuilder:
    import: "package:test_anotation/builder/test_builder.dart"
    builder_factories: ["testBuilder"]
    build_extensions: {".dart": [".g.part"]}
    auto_apply: root_package
    build_to: source

Import: the specific path of the file where the builder’s global method is located
builder_ Factories: the method name of the builder’s global method


Build run run, (parsing annotation to generate code)

After completing the above work, you can directly run the following commands in the command line window of the project to start parsing annotations and generating code:

Clean up generated files:
flutter packages pub run build_runner clean

Generate code file:
flutter packages pub run build_runner build

According to model_ Class.dart file, and generate the file model in the same directory_ class.g.dart

Dart: generate code from annotations

image.png

According to the parser interception logic, the generated code is:

// GENERATED CODE - DO NOT MODIFY BY HAND

// **************************************************************************
// TestGenerator
// **************************************************************************

class ClassModelAPT {
  /**
          The intercepted member fields in class are:
   String* className
   List<Student*>* members

 Member methods intercepted in class:
   void funPrint(String* content)

        */
}
Summary tips:
  1. A generator can only parse one type of annotation. If there are multiple types of annotation, you need to create multiple parsers. (corresponding contents need to be added synchronously in the configuration file)

  2. Using source_ The default processor provided by Gen: generatorforannotation. The processor can only handle top-level elements, such as class, function, enums, etc. defined directly in the. Dart file, but annotations used on fields and functions inside the class cannot be intercepted.

Recommended Today

Ajax implementation comment submission

copy codecode show as below: document.write(' <DIV id=”loadingg”  style=”HEIGHT:65px; WIDTH: 205px;POSITION: absolute; Z-INDEX:1000;border:3px #fff solid;text-align:center; font-size:12px; font-family:Arial, Helvetica, sans-serif;color:#660000;background:#222;opacity:.7;-moz-opacity:.7;filter: alpha(opacity=70); display:none;”><br/><font color=”#FF6600″> <strong>Data is being read, please wait…</strong><br/><img src=”images/loading.gif”/></DIV> ')function showloading()  { var obj=document.getElementById(“loadingg”) if (obj.style.display!=””) { obj.style.left=((document.documentElement.clientWidth-parseFloat (obj.style.width))/2)+document.documentElement.scrollLeft+”px”; obj.style.top=((document.documentElement.clientHeight-parseFloat (obj.style.height))/2)+document.documentElement.scrollTop+”px”; obj.style.display=””; }else{obj.style.display=”none”;} } function $(id) {    return document.getElementById(id);    } function echo(obj,html){    $(obj).innerHTML=html;} function fopen(obj){$(obj).style.display=””;} function fclose(obj){$(obj).style.display=”none”;} function createxmlhttp(){     var xmlhttp=false;     try    {           xmlhttp = new ActiveXObject(“Msxml2.XMLHTTP”);      }      catch (e) {           try {                xmlhttp = new ActiveXObject(“Microsoft.XMLHTTP”);           }          catch (e) {                xmlhttp = false;          }      }     if (!xmlhttp && typeof XMLHttpRequest!=’undefined’) {           xmlhttp = new XMLHttpRequest(); if (xmlhttp.overrideMimeType) {//Set MiME category            xmlhttp.overrideMimeType(‘text/xml’);         }     }         return xmlhttp;     } function getdata(url,obj1,obj2) {        var xmlhttp=createxmlhttp();         if(!xmlhttp) […]