Geração de código no Dart. Parte 2. Anotações, source_gen e build_runner

Na primeira parte, descobrimos por que a geração de código é necessária e listamos as ferramentas necessárias para a geração de código no Dart. Na segunda parte, aprenderemos como criar e usar anotações no Dart, além de como usar o source_gen e o build_runner para iniciar a geração de código.



Anotações de dardo


As anotações são metadados sintáticos que podem ser adicionados ao código. Em outras palavras, é uma oportunidade de adicionar informações adicionais a qualquer componente do código, por exemplo, a uma classe ou método. As anotações são amplamente usadas no código Dart: usamos @required para indicar que um parâmetro nomeado é necessário e nosso código não será compilado se o parâmetro anotado não for especificado. Também usamos @override para indicar que uma determinada API definida em uma classe pai é implementada em uma classe filho. As anotações sempre começam com o símbolo @ .


Como criar sua própria anotação?


Embora a ideia de adicionar metadados ao código pareça um pouco exótica e complicada, as anotações são uma das coisas mais simples da linguagem Dart. Foi dito anteriormente que as anotações simplesmente trazem informações adicionais . Eles são semelhantes ao PODO (Plain Old Dart Objects). E qualquer classe pode servir como uma anotação se um construtor const estiver definido nela :


 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 {} 

Como você pode ver, as anotações são muito simples. E o que importa é o que faremos com essas anotações. Isso nos ajudará source_gen e build_runner .


Como usar o build_runner?


build_runner é um pacote Dart que nos ajudará a gerar arquivos usando o código Dart. Vamos configurar os arquivos do Builder usando o build.yaml . Quando configurado, o Builder será chamado em todos os comandos de build ou quando o arquivo for alterado. Também temos a oportunidade de analisar o código que foi modificado ou atende a determinados critérios.


source_gen para entender o código Dart


De certa forma, o build_runner é um mecanismo que responde à pergunta " Quando preciso gerar código?" Ao mesmo tempo, o source_gen responde à pergunta " Que código deve ser gerado?". source_gen fornece uma estrutura para criar Builders para o build_runner funcionar. O source_gen também fornece uma API conveniente para analisar e gerar código.


Juntando tudo: relatório TODO


No restante deste artigo, desmontaremos o projeto todo_reporter.dart , que pode ser encontrado aqui .


Há uma regra não escrita que todos os projetos que usam geração de código seguem: você precisa criar um pacote contendo anotações e um pacote separado para o gerador que usa essas anotações. Informações sobre como criar uma biblioteca de pacotes no Dart / Flutter podem ser encontradas aqui .


Primeiro, você precisa criar o diretório todo_reporter.dart . Dentro deste diretório, você precisa criar o diretório todo_reporter , que conterá a anotação, o diretório todo_reporter_generator para processar a anotação e, finalmente, o diretório de example contém uma demonstração dos recursos da biblioteca que está sendo criada.


O sufixo .dart foi adicionado ao nome do diretório raiz para maior clareza. Obviamente, isso não é necessário, mas eu gosto de seguir esta regra para indicar com precisão o fato de que este pacote pode ser usado em qualquer projeto Dart. Pelo contrário, se eu quisesse indicar que este pacote é apenas para o Flutter (como ozzie.flutter ), usaria um sufixo diferente. Isso não é necessário, é apenas uma convenção de nomenclatura que tento aderir.


Criando todo_reporter, nosso simples pacote de anotações


Vamos criar todo_reporter dentro de todo_reporter.dart . Para fazer isso, crie o arquivo pubspec.yaml e o diretório lib .


pubspec.yaml muito simples:


 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 

Não há dependências, exceto o pacote de test usado no processo de desenvolvimento.


No diretório lib , faça o seguinte:


  • Você precisa criar o arquivo todo_reporter.dart , no qual, usando a export , todas as classes que possuem uma API pública serão especificadas. Essa é uma boa prática, já que qualquer classe em nosso pacote pode ser importada usando import 'package:todo_reporter/todo_reporter.dart'; . Você pode ver esta aula aqui .
  • Dentro do diretório lib , criaremos o diretório src contendo todo o código - público e não público.

No nosso caso, tudo o que precisamos adicionar é uma anotação. Vamos criar um arquivo todo.dart com nossa anotação:


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

Então, isso é tudo o que é preciso para anotar. Eu disse que será simples. Mas isso não é tudo. Vamos adicionar testes de unidade ao diretório de 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); }); }); } 

É tudo o que precisamos para criar a anotação. Você pode encontrar o código aqui . Agora podemos ir ao gerador.


Fazendo um trabalho interessante: todo_reporter_generator


Agora que sabemos como criar pacotes, vamos criar o pacote todo_reporter_generator . Dentro deste pacote devem estar os build.yaml e build.yaml e um diretório lib . O diretório lib deve ter o diretório src e o arquivo builder.dart . Nosso todo_reporter_generator é considerado um pacote separado que será adicionado como dev_dependency a outros projetos. Isso ocorre porque a geração de código é necessária apenas no estágio de desenvolvimento e não precisa ser adicionada ao aplicativo finalizado.


pubspec.yaml o seguinte:


 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 

Agora vamos criar o build.yaml . Este arquivo contém a configuração necessária para nossos construtores . Mais detalhes podem ser encontrados aqui . build.yaml o seguinte:


 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"] 

A propriedade import aponte para o arquivo que contém o Builder e a propriedade builder_factories aponte para os métodos que irão gerar o código.


Agora podemos criar o arquivo builder.dart no diretório lib :


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

E o arquivo todo_reporter_generator.dart no diretório src :


 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!"; } } 

Como você pode ver, no arquivo todoReporter , definimos o método todoReporter que o Builder cria. Builder é criado usando o SharedPartBuilder , que usa o TodoReporterGenerator . Então build_runner e source_gen trabalham juntos.


Nosso TodoReporterGenerator é uma subclasse de GeneratorForAnnotation , portanto, o método generateForAnnotatedElement será executado apenas quando esta anotação ( @Todo no nosso caso) for encontrada no código.


O método generateForAnnotatedElement retorna uma sequência que contém nosso código gerado. Se o código gerado não for compilado, toda a fase de construção falhará . Isso é muito útil, pois evita erros no futuro.


Assim, a cada geração de código, nosso todo_repoter_generator criará um arquivo de part , com o comentário // Hey! Annotation found! // Hey! Annotation found! No próximo artigo, aprenderemos como processar anotações.


Juntando tudo: usando todo_reporter


Agora você pode demonstrar como todo_reporter.dart . É uma boa prática adicionar um projeto de example ao trabalhar com pacotes. Portanto, outros desenvolvedores poderão ver como a API pode ser usada em um projeto real.


Vamos criar um projeto e adicionar as dependências necessárias ao pubspec.yaml . No nosso caso, criaremos um projeto Flutter dentro do diretório de example e adicionaremos as dependências:


 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/ 

Depois de receber os pacotes (pacotes flutter packages get ), podemos usar nossa anotação:


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

Agora que tudo está no lugar, execute nosso gerador:


 $ flutter packages pub run build_runner build 

Após a conclusão do comando, você notará um novo arquivo em nosso projeto: todo.g.dart . Ele conterá o seguinte:


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

Conseguimos o que queríamos! Agora podemos gerar o arquivo Dart correto para cada anotação @Todo em nosso código. Experimente e crie quantas você precisar.


No próximo artigo


Agora, temos as configurações corretas para gerar arquivos. No próximo artigo, aprenderemos como usar anotações para que o código gerado possa fazer coisas realmente interessantes. Afinal, o código que está sendo gerado agora não faz muito sentido.

Source: https://habr.com/ru/post/pt446264/


All Articles