Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to generate code through annotations in Dart

2025-01-16 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Internet Technology >

Share

Shulou(Shulou.com)06/01 Report--

Editor to share with you how to generate code through annotations in Dart. I hope you will get something after reading this article. Let's discuss it together.

Background recently used the annotation code generation technology in Dart in the project, which is still different from the previous APT+JavaPoet code generation technology in Java, such as

How can I get class-related information in Flutter when dart:mirror is disabled and reflection cannot be used?

Dart files are not limited to class, but can be function or class, so how do you get layers of information instead of just toplevel information when the scope of annotation scanning is different?

How do you generate complex template code when the annotation information is extracted?

How to solve the above problems in Flutter? The mystery will be unveiled step by step.

A simple example.

Let's start with a simple example of how to generate code through annotations in dart.

Declare a comment and use it

In Dart, just decorate the constructor with const. You can see that the annotation of Dart is relatively simple to declare, unlike in java, there have to be run types such as RunTime, Source, etc.

Generator for parsing annotations

In Dart, we generally use GeneratorForAnnotation in source_gen, which inherits from Generator, which is similar to the responsibility of processor in Java APT. We need to fill in the generics of GeneratorForAnnotation with the comments we need to deal with.

Builer that triggers the generator

With the above generator for generating annotations, we also need Builder to trigger

Create a profile build.yaml

Run builder

Because Flutter disables dart:mirror cannot use reflection, it can only be triggered at compile time through the command. Execute the following command, and you will see the generated code

Do you feel the peculiarity of the code generated by Dart annotations, such as Generator of AnnotationProcessor Tool in Java, but with Builder and build.yaml, so how do these work together to generate annotations?

Macroscopic concept

Use binoculars to get a macroscopic overview of the whole process

When we trigger build after using buildrunner's build, we will read the configuration information of the build.yaml file, which will eventually be read by the BuildConfig class in buildconfig.dart, and then trigger the annotation generator (TestGenerator) by reading to builder, the testBuilder of the above example. To extract information from the abstract syntax tree (because source_gen encapsulates the parsing library analysis and the resource processing library build, which actually blocks the parsing process), like java, they are all Element. For more information, please see the implementation class of the code.

To sum up, there are the following core parts:

User trigger-file scanning-lexical analysis-annotation extraction-code generation

Micro exploration

Then use a magnifying mirror to study the details:

Build.yaml configuration

In Java, we use AutoService annotations provided by Google to generate META-INF/services/javax.annotation.processing.Processor files associated with annotation processors, but dart annotations in Flutter can only be written at compile time, so we need a configuration to tell the compiler which builder is triggered, corresponding to the build.yaml file, first take a look at a build.yaml configuration and feel it.

The information of build.yaml configuration will eventually be read by the BuildConfig class in buildconfig.dart. At present, there is not much information about the parameter description. Here, we recommend the official description buildconfig, which can be parsed through the BuildeConfig under the buildconfig package.

The parsing entry is as follows

As you can see from build_config.dart, there are mainly four major parts to be parsed. Here are two commonly used ones for analysis.

Targets

You can see a description of the supporting attributes in build_target.dart#BuildTarget, including a builder attribute that is used more frequently.

There are three commonly used properties in TargetBuilderConfig

Enable

Whether the current builder is effective

Generate_for

This attribute is important to determine which files / folders are scanned or which files input_set.dart are excluded, using the following

You can also see the use of the generatefor attribute in its yaml file in jsonseriable's build.yaml

Options

This property allows you to carry some configuration data to the code generator in the form of key-value pairs, corresponding to the BuildOption parameter, which will be described again when interpreting builder.

Builder

Have a builder.

BuilderOptions can be extracted to the above option attribute configuration

As described above in the build.yaml file, Map

For more configuration, please see builder_definition.dart.

There are two important attributes that are explained separately.

Run_before

You can specify the running order of builder. If several buidler depend on each other, for example, you can use this attribute in Ali's routing framework annotationroute. You can take a look at its yaml file. Mustache4dart is mainly used in the routing framework to collect routing information to fill the template. Its solution is to use two builder, one for collecting information (routeWriteBuilder). After collection, combine the mustache4dart template with another builder (routeBuilder) to generate the required routing table. For more information, please see its routegenerator.dart.

Auto_apply

If you look at the text, it may be a little obscure to understand, so create a picture to explain it. For example, the annotation function is used in the libB above:

When we set auto_apply to dependents:

If the annotation package is directly dependent on libB, you can only use the annotation normally on libB. Although the top-level Package package relies on libB, it still cannot be used properly.

When we set autoapply to allpackages:

If the annotation package is directly dependent on libB, then annotations can be used normally on both libB and top-level Package

When we set autoapply to rootpackage:

If the annotation package is directly dependent on the libB, then you can only use the annotation on the top-level Package. Although it is a dependency on the libB, it just cannot be used. However, when the annotation package is directly dependent on the top-level Package, it can be used normally regardless of whether the autoapply is set to dependents, allpackages or root_package.

About source_gen

Brief introduction

After understanding the yaml file for the basic configuration, I have to mention source_gen, a powerful library

Sourcegen provides a series of friendly wrappers based on the official analysis/build, while sourcegen is based on analyzer and build libraries, where

Build library is mainly the processing of resource files.

Analyser library is the dart file generation syntax structure source_gen mainly deal with dart source code, you can generate code through annotations.

Introduction to core classes

Sourcegen derives its own builder from the Builder provided by the build library and encapsulates three

Builder (builder.dart) | _ Builder (builder.dart) |-LibraryBuilder (builder.dart) |-SharedPartBuilder (builder.dart) |-PartBuilder (builder.dart)

SharedPartBuilder

Generate .g.dart files, just like jsonseriable, where you need to use part of references, so the biggest advantage is that you don't need to pay too much attention to referencing issues, but note that you need to use sourcegen | combining_builder, which merges all .g files.

LibraryBuilder generates separate files

PartBuilder Custom part File

Generator Generator

And source_gen encapsulates a set of Generator, the above buidler receives the collection of Generator, collects the output of Generator to generate a file, Generator is just an abstract class, and the concrete implementation class is GeneratorForAnnotation, which can only intercept elements at the top-level level (explained later) by default, and will be accepted by the annotation generator as a specified annotation type, that is, GeneratorForAnnotation is a single annotation processor, for example

Because analyser provides the abstract element Element and its metadata field of the syntax node, corresponding to ElementAnnotation, the annotation generator can check whether the metadata type of the element matches the declared annotation type, so as to find out the information about the annotated element and its context, and then wrap the information to the user.

The core method generateForAnnotatedElement, for example, we have an annotation code like this.

From the above, we can see that the generateForAnnotatedElement method is mainly overridden, with three key parameters

Element element

The element modified by annotation can obtain the name, metadata, visibility, and so on.

For more api, check element.

About toplevel comments

As mentioned earlier, only elements at the toplevel level can be intercepted, so none of the internal methods in class can be scanned. This is because unlike java, a file can only correspond to one class, and the dart file can be function, class or others, so it can only be intercepted to the top-level level by default. Later, developers need to handle it manually. For example, ClassElement provides methods and fields to give developers the opportunity to further deal with annotations. The following shows the methods in the parsing class, and the properties are similar.

In addition to ClassElementImpl, Element also has a number of derivatives such as FunctionElementImpl, ParamElementImpl, etc., which can be checked on your own.

ConstantReader annotation

Represents the annotation object, through which you can extract annotation-related information and parameter values in two key ways

Read

Peek

The difference is that if the read method reads a parameter name that does not exist, it throws an exception, while peek does not return null.

BuildStep buildStep

Through the information built this time, you can get some input and output information, such as input file name and so on.

Core code analysis

Source_gen is also encapsulated from the Builder of the build library.

Sourcegen implements its own Builder according to Builder, and derives SharedPartBuilder, LibraryBuilder and PartBuilder according to different characteristics.

There's a core Generator in it.

When Builder runs, the generate method of Generator is called, passing in two important parameters:

Library can get source code information as well as annotation information

BuildStep represents a step in the build process, through which we can get the input and output information of some files.

Among them, the source code information contained in library is an Element element, Element is just an abstract class, concrete or a ClassElementImpl, FuncationElementImpl and so on. Source_gen implements this class of GeneratorForAnnotation

In the second point, library.annotatedWith (typeChecker) goes in to have a look.

Code generation

Pure string concatenation

Using three quotation mark syntax, this can only solve some low-level builds

Mustach

Prefabricated template, through certain rules, extract information and then fill it into the template. A typical example is as follows

The learning cost is low, and it is suitable for some fixed format code generation, such as routing table, which is adopted by Ali's annotation_route framework. You can take a look at its template tpl.

Then two generators are used, one to collect information and the other to inject the collected information into the mustach template

Code_builder

Very powerful, friends who have played with java annotations to generate code must be familiar with javapoet, the two are very similar, code_builder can be subdivided into expressions, statements, functions, classes, etc., but the learning cost is relatively high, so you need to generate the corresponding code according to its syntax, such as generating a class.

Generate an expression

Comparison with the code generated by java comments

After reading this article, I believe you have a certain understanding of "how to generate code through annotations in Dart". If you want to know more about it, you are welcome to follow the industry information channel. Thank you for reading!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Internet Technology

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report