Dart中的代码生成。 第2部分。批注,source_gen和build_runner

第一部分中,我们了解了为什么需要代码生成,并列出了Dart中代码生成的必要工具。 在第二部分中,我们将学习如何在Dart中创建和使用注释,以及如何使用source_genbuild_runner开始代码生成。



飞镖注解


注释是可以添加到代码中的语法元数据。 换句话说,这是将附加信息添加到代码的任何组件(例如添加到类或方法)的机会。 注解在Dart代码中被广泛使用:我们使用@required来表示需要一个命名参数,如果未指定注解参数,我们的代码将不会编译。 我们还使用@override指示在父类中定义的给定API是在子类中实现的。 注释始终以@符号开头。


如何创建自己的注释?


尽管将元数据添加到代码中的想法听起来有些奇怪和复杂,但是注释是Dart语言中最简单的事情之一。 以前曾说过, 注释只是携带其他信息 。 它们类似于PODO (普通的旧式Dart对象)。 如果在其中定义了const构造函数,则任何类都可以用作注释


 class { final String name; final String todoUrl; const Todo(this.name, {this.todoUrl}) : assert(name != null); } @Todo('hello first annotation', todoUrl: 'https://www.google.com') class HelloAnnotations {} 

如您所见,注释非常简单。 重要的是我们将如何使用这些注释。 这将帮助我们source_genbuild_runner


如何使用build_runner?


build_runner是Dart软件包,它将帮助我们使用Dart代码生成文件。 我们将使用build.yaml配置Builder文件。 配置完成后,将在每个build命令或更改文件时调用Builder 。 我们也有机会解析已修改或符合某些条件的代码。


source_gen用于理解Dart代码


从某种意义上说, build_runner是一种回答“ 何时需要生成代码?”问题的机制。 同时, source_gen回答问题“应生成什么代码?”。 source_gen提供了一个框架,用于创建供build_runner使用的Builders 。 此外, source_gen还提供了一个方便的API,用于解析和生成代码。


放在一起:TODO报告


在本文的其余部分,我们将分解todo_reporter.dart项目,该项目可以在此处找到。


有一个不成文的规则,所有使用代码生成的项目都必须遵循:您需要创建一个包含批注 ,并使用这些批注的生成器创建一个单独的包 。 有关如何在Dart / Flutter中创建软件包库的信息,请参见此处


首先,您需要创建todo_reporter.dart目录。 在此目录中,您需要创建todo_reporter目录,该目录将包含注释, todo_reporter_generator目录以处理注释,最后是example目录,其中包含正在创建的库功能的演示。


为了清楚起见, .dart后缀.dart添加到根目录名称。 当然,这不是必须的,但是我喜欢遵循此规则以准确表明该程序包可以在任何Dart项目中使用的事实。 相反,如果我想指出此软件包仅适用于Flutter(如ozzie.flutter ),我将使用其他后缀。 这不是必须的,这只是我尝试遵守的命名约定。


创建我们的简单注释包todo_reporter


我们将在todo_reporter.dart创建todo_reporter.dart 。 为此,请创建pubspec.yaml文件和lib目录。


pubspec.yaml非常简单:


 name: todo_reporter description: Keep track of all your TODOs. version: 1.0.0 author: Jorge Coca <jcocaramos@gmail.com> homepage: https://github.com/jorgecoca/todo_reporter.dart environment: sdk: ">=2.0.0 <3.0.0" dependencies: dev_dependencies: test: 1.3.4 

除了在开发过程中使用的test包之外,没有其他依赖项。


lib目录中,执行以下操作:


  • 您需要创建todo_reporter.dart文件,在其中使用export ,将指定所有具有公共API的类。 这是一个好习惯,因为可以使用import 'package:todo_reporter/todo_reporter.dart';任何类import 'package:todo_reporter/todo_reporter.dart'; 。 您可以在这里看到这堂课。
  • lib目录中,我们将创建src目录,其中包含所有代码-public和non-public。

在我们的例子中,我们需要添加的只是注释。 让我们用注释创建一个todo.dart文件:


 class Todo { final String name; final String todoUrl; const Todo(this.name, {this.todoUrl}) : assert(name != null); } 

这样便足以进行注释。 我说过这很简单。 但这还不是全部。 让我们将单元测试添加到test目录:


todo_test.dart
 import 'package:test/test.dart'; import 'package:todo_reporter/todo_reporter.dart'; void main() { group('Todo annotation', () { test('must have a non-null name', () { expect(() => Todo(null), throwsA(TypeMatcher<AssertionError>())); }); test('does not need to have a todoUrl', () { final todo = Todo('name'); expect(todo.todoUrl, null); }); test('if it is a given a todoUrl, it will be part of the model', () { final givenUrl = 'http://url.com'; final todo = Todo('name', todoUrl: givenUrl); expect(todo.todoUrl, givenUrl); }); }); } 

这就是我们创建注释所需的全部。 您可以在此处找到代码。 现在我们可以去发电机了。


做一个很酷的工作:todo_reporter_generator


现在我们知道了如何创建包,让我们创建todo_reporter_generator包。 该软件包中应该包含pubspec.yamlbuild.yaml以及一个lib目录。 lib目录必须具有src目录和builder.dart文件。 我们的todo_reporter_generator被视为一个单独的程序包,将作为dev_dependency添加到其他项目中。 这是因为仅在开发阶段才需要代码生成,并且不需要将其添加到完成的应用程序中。


pubspec.yaml如下:


 name: todo_reporter_generator description: An annotation processor for @Todo annotations. version: 1.0.0 author: Jorge Coca <jcocaramos@gmail.com> homepage: https://github.com/jorgecoca/todo_reporter.dart environment: sdk: ">=2.0.0 <3.0.0" dependencies: build: '>=0.12.0 <2.0.0' source_gen: ^0.9.0 todo_reporter: path: ../todo_reporter/ dev_dependencies: build_test: ^0.10.0 build_runner: '>=0.9.0 <0.11.0' test: ^1.0.0 

现在让我们创建build.yaml 。 该文件包含构建器所需的配置。 可以在这里找到更多详细信息。 build.yaml如下:


 targets: $default: builders: todo_reporter_generator|todo_reporter: enabled: true builders: todo_reporter: target: ":todo_reporter_generator" import: "package:todo_reporter_generator/builder.dart" builder_factories: ["todoReporter"] build_extensions: {".dart": [".todo_reporter.g.part"]} auto_apply: dependents build_to: cache applies_builders: ["source_gen|combining_builder"] 

import属性指向包含Builder的文件, builder_factories属性指向将生成代码的方法。


现在,我们可以在lib目录中创建builder.dart文件:


 import 'package:build/build.dart'; import 'package:source_gen/source_gen.dart'; import 'package:todo_reporter_generator/src/todo_reporter_generator.dart'; Builder todoReporter(BuilderOptions options) => SharedPartBuilder([TodoReporterGenerator()], 'todo_reporter'); 

以及src目录中的todo_reporter_generator.dart文件:


 import 'dart:async'; import 'package:analyzer/dart/element/element.dart'; import 'package:build/src/builder/build_step.dart'; import 'package:source_gen/source_gen.dart'; import 'package:todo_reporter/todo_reporter.dart'; class TodoReporterGenerator extends GeneratorForAnnotation<Todo> { @override FutureOr<String> generateForAnnotatedElement( Element element, ConstantReader annotation, BuildStep buildStep) { return "// Hey! Annotation found!"; } } 

如您所见,在builder.dart文件中,我们定义了Builder创建的todoReporter方法。 使用SharedPartBuilder创建Builder ,该SharedPartBuilder Builder使用我们的TodoReporterGenerator 。 因此build_runnersource_gen可以一起工作。


我们的TodoReporterGeneratorGeneratorForAnnotation的子类,因此只有在代码中找到此注释(在本例中为@Todo )时,才会执行generateForAnnotatedElement方法。


generateForAnnotatedElement方法返回一个包含我们生成的代码的字符串。 如果生成的代码未编译,则整个构建阶段将失败 。 这非常有用,因为它可以避免将来出现错误。


因此,对于每一代代码,我们的todo_repoter_generator将创建一个带有注释的part文件// Hey! Annotation found! // Hey! Annotation found! 在下一篇文章中,我们将学习如何处理注释。


放在一起:使用todo_reporter


现在,您可以演示todo_reporter.dart 。 在使用软件包时,最好添加一个example项目。 因此,其他开发人员将能够看到如何在实际项目中使用该API。


让我们创建一个项目,并将所需的依赖项添加到pubspec.yaml 。 在我们的例子中,我们将在example目录中创建一个Flutter项目并添加依赖项:


 dependencies: flutter: sdk: flutter todo_reporter: path: ../todo_reporter/ dev_dependencies: build_runner: 1.0.0 flutter_test: sdk: flutter todo_reporter_generator: path: ../todo_reporter_generator/ 

收到软件包( flutter packages get )后,我们可以使用我们的注释:


 import 'package:todo_reporter/todo_reporter.dart'; @Todo('Complete implementation of TestClass') class TestClass {} 

现在一切就绪,运行我们的生成器:


 $ flutter packages pub run build_runner build 

命令完成后,您将在我们的项目中注意到一个新文件: todo.g.dart 。 它将包含以下内容:


 // GENERATED CODE - DO NOT MODIFY BY HAND part of 'todo.dart'; // ***************************************************************** // TodoReporterGenerator // ******************************************************************** // Hey! Annotation found! 

我们实现了我们想要的! 现在,我们可以为代码中的每个@Todo注释生成正确的Dart文件。 尝试创建所需数量的对象。


在下一篇文章中


现在我们有了用于生成文件的正确设置。 在下一篇文章中,我们将学习如何使用批注,以便所生成的代码可以做一些非常酷的事情。 毕竟,现在生成的代码没有多大意义。

Source: https://habr.com/ru/post/zh-CN446264/


All Articles