Génération de code dans Dart. Partie 2. Annotations, source_gen et build_runner

Dans la première partie, nous avons découvert pourquoi la génération de code est nécessaire et répertorié les outils nécessaires pour la génération de code dans Dart. Dans la deuxième partie, nous apprendrons comment créer et utiliser des annotations dans Dart, ainsi que comment utiliser source_gen et build_runner pour démarrer la génération de code.



Annotations de fléchettes


Les annotations sont des métadonnées syntaxiques qui peuvent être ajoutées au code. En d'autres termes, c'est l'occasion d'ajouter des informations supplémentaires à n'importe quel composant du code, par exemple, à une classe ou une méthode. Les annotations sont largement utilisées dans le code Dart: nous utilisons @required pour indiquer qu'un paramètre nommé est requis, et notre code ne se compilera pas si le paramètre annoté n'est pas spécifié. Nous utilisons également @override pour indiquer qu'une API donnée définie dans une classe parent est implémentée dans une classe enfant. Les annotations commencent toujours par le symbole @ .


Comment créer votre propre annotation?


Bien que l'idée d'ajouter des métadonnées au code semble un peu exotique et compliquée, les annotations sont l'une des choses les plus simples du langage Dart. Il a été dit précédemment que les annotations contiennent simplement des informations supplémentaires . Ils sont similaires aux PODO (Plain Old Dart Objects). Et n'importe quelle classe peut servir d'annotation si un constructeur const est défini :


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

Comme vous pouvez le voir, les annotations sont très simples. Et ce qui compte, c'est ce que nous ferons de ces annotations. Cela nous aidera à source_gen et build_runner .


Comment utiliser build_runner?


build_runner est un package Dart qui nous aidera à générer des fichiers à l'aide du code Dart. Nous allons configurer les fichiers Builder en utilisant build.yaml . Lorsqu'il est configuré, Builder sera appelé à chaque commande de build ou lorsque le fichier est modifié. Nous avons également la possibilité d'analyser du code qui a été modifié ou qui répond à certains critères.


source_gen pour comprendre le code Dart


Dans un sens, build_runner est un mécanisme qui répond à la question " Quand dois-je générer du code?" Dans le même temps, source_gen répond à la question « Quel code doit être généré?». source_gen fournit un cadre pour créer des build_runner pour que build_runner fonctionne. source_gen fournit également une API pratique pour analyser et générer du code.


Tout mettre ensemble: rapport TODO


Dans la suite de cet article, nous démonterons le projet todo_reporter.dart , qui se trouve ici .


Il existe une règle non écrite que tous les projets qui utilisent la génération de code suivent: vous devez créer un package contenant des annotations et un package distinct pour le générateur qui utilise ces annotations. Vous trouverez ici des informations sur la création d'une bibliothèque de packages dans Dart / Flutter.


Vous devez d'abord créer le répertoire todo_reporter.dart . Dans ce répertoire, vous devez créer le répertoire todo_reporter , qui contiendra l'annotation, le répertoire todo_reporter_generator pour traiter l'annotation et, enfin, l' example répertoire contenant une démonstration des capacités de la bibliothèque en cours de création.


Le suffixe .dart été ajouté au nom du répertoire racine pour plus de clarté. Bien sûr, ce n'est pas nécessaire, mais j'aime suivre cette règle pour indiquer avec précision le fait que ce package peut être utilisé dans n'importe quel projet Dart. Au contraire, si je voulais indiquer que ce package est uniquement pour Flutter (comme ozzie.flutter ), j'utiliserais un suffixe différent. Ce n'est pas nécessaire, c'est juste une convention de dénomination à laquelle j'essaie d'adhérer.


Création de todo_reporter, notre package d'annotation simple


Nous allons créer todo_reporter dans todo_reporter.dart . Pour ce faire, créez le fichier pubspec.yaml et le répertoire lib .


pubspec.yaml très simple:


 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 

Il n'y a pas de dépendances à l'exception du package de test utilisé dans le processus de développement.


Dans le répertoire lib , procédez comme suit:


  • Vous devez créer un fichier todo_reporter.dart dans lequel, à l'aide de l' export , toutes les classes qui ont une API publique seront spécifiées. C'est une bonne pratique, car n'importe quelle classe de notre package peut être importée en utilisant import 'package:todo_reporter/todo_reporter.dart'; . Vous pouvez voir cette classe ici .
  • À l'intérieur du répertoire lib , nous allons créer le répertoire src contenant tout le code - public et non public.

Dans notre cas, il suffit d'ajouter une annotation. Créons un fichier todo.dart avec notre annotation:


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

C'est tout ce qu'il faut pour annoter. J'ai dit que ce sera simple. Mais ce n'est pas tout. Ajoutons des tests unitaires au répertoire 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); }); }); } 

C'est tout ce dont nous avons besoin pour créer l'annotation. Vous pouvez trouver le code ici . Maintenant, nous pouvons aller au générateur.


Faire un travail sympa: todo_reporter_generator


Maintenant que nous savons comment créer des packages, créons le package todo_reporter_generator . A l'intérieur de ce paquet devrait se build.yaml pubspec.yaml et build.yaml et un répertoire lib . Le répertoire lib doit avoir le répertoire src et le fichier builder.dart . Notre todo_reporter_generator est considéré comme un package séparé qui sera ajouté en tant que dev_dependency à d'autres projets. En effet, la génération de code n'est nécessaire qu'au stade du développement et n'a pas besoin d'être ajoutée à l'application terminée.


pubspec.yaml le suivant:


 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 maintenant build.yaml . Ce fichier contient la configuration nécessaire à nos constructeurs . Plus de détails peuvent être trouvés ici . build.yaml le suivant:


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

La propriété import pointe vers le fichier qui contient le builder_factories et la propriété builder_factories pointe vers les méthodes qui généreront le code.


Nous pouvons maintenant créer le fichier builder.dart dans le répertoire 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'); 

Et le fichier todo_reporter_generator.dart dans le répertoire 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!"; } } 

Comme vous pouvez le voir, dans le fichier builder.dart , nous avons défini la méthode todoReporter le todoReporter . Builder SharedPartBuilder est créé à l'aide de SharedPartBuilder , qui utilise notre TodoReporterGenerator . Donc build_runner et source_gen fonctionnent ensemble.


Notre TodoReporterGenerator est une sous-classe de GeneratorForAnnotation , donc la méthode generateForAnnotatedElement ne sera exécutée que lorsque cette annotation ( @Todo dans notre cas) se trouve dans le code.


La méthode generateForAnnotatedElement renvoie une chaîne contenant notre code généré. Si le code généré ne se compile pas, la phase de génération entière échouera . Ceci est très utile car il évite les erreurs à l'avenir.


Ainsi, à chaque génération de code, notre todo_repoter_generator créera un fichier part , avec commentaire // Hey! Annotation found! // Hey! Annotation found! Dans le prochain article, nous apprendrons comment traiter les annotations.


Mettre tout cela ensemble: en utilisant todo_reporter


Vous pouvez maintenant montrer comment todo_reporter.dart . Il est recommandé d'ajouter un example projet lorsque vous travaillez avec des packages. Ainsi, d'autres développeurs pourront voir comment l'API peut être utilisée dans un vrai projet.


Créons un projet et ajoutons les dépendances requises à pubspec.yaml . Dans notre cas, nous allons créer un projet Flutter dans le répertoire d' example et ajouter les dépendances:


 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/ 

Après avoir reçu les paquets (les paquets flutter packages get ), nous pouvons utiliser notre annotation:


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

Maintenant que tout est en place, lancez notre générateur:


 $ flutter packages pub run build_runner build 

Une fois la commande terminée, vous remarquerez un nouveau fichier dans notre projet: todo.g.dart . Il contiendra les éléments suivants:


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

Nous avons réalisé ce que nous voulions! Nous pouvons maintenant générer le fichier Dart correct pour chaque annotation @Todo dans notre code. Essayez d'en créer autant que vous en avez besoin.


Dans le prochain article


Nous avons maintenant les bons paramètres pour générer des fichiers. Dans le prochain article, nous apprendrons à utiliser les annotations pour que le code généré puisse faire des choses vraiment cool. Après tout, le code généré actuellement n'a pas beaucoup de sens.

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


All Articles