Présentation
Chaque jour, lorsque vous travaillez sur le code, sur le chemin de la mise en œuvre d'une fonctionnalité utile à l'utilisateur, des modifications forcées (inévitables ou simplement souhaitables) du code deviennent. Cela peut être la refactorisation, la mise à jour d'une bibliothèque ou d'un framework vers une nouvelle version majeure, la mise à jour de la syntaxe JavaScript (ce qui n'est pas rare récemment). Même si la bibliothèque fait partie d'un projet de travail, le changement est inévitable. La plupart de ces changements sont routiniers. Il n'y a rien d'intéressant pour le développeur en eux, d'une part, d'autre part, cela n'apporte rien à l'entreprise, et troisièmement, pendant le processus de mise à jour, vous devez être très prudent pour ne pas casser le bois de chauffage et ne pas casser la fonctionnalité. Ainsi, nous arrivons à la conclusion qu'il est préférable de déplacer une telle routine sur les épaules des programmes afin qu'ils fassent tout eux-mêmes et que la personne, à son tour, contrôle si tout a été fait correctement. C'est ce qui sera discuté dans l'article d'aujourd'hui.
AST
Pour le traitement de code de programme, il est nécessaire de le traduire en une représentation spéciale, avec laquelle il serait pratique pour les programmes de fonctionner. Une telle représentation existe, elle s'appelle Arbre de Syntaxe Abstraite (AST).
Pour l'obtenir, utilisez des analyseurs. L'AST résultant peut être transformé comme vous le souhaitez, puis pour enregistrer le résultat, vous avez besoin d'un générateur de code. Examinons plus en détail chacune des étapes. Commençons par l'analyseur.
Analyseur
Et nous avons donc le code:
a + b
Les analyseurs sont généralement divisés en deux parties:
Décompose le code en jetons, chacun décrivant une partie du code:
[{ "type": "Identifier", "value": "a" }, { "type": "Punctuator", "value": "+", }, { "type": "Identifier", "value": "b" }]
Construit un arbre de syntaxe à partir de jetons:
{ "type": "BinaryExpression", "left": { "type": "Identifier", "name": "a" }, "operator": "+", "right": { "type": "Identifier", "name": "b" } }
Et maintenant, nous avons déjà cette idée même, avec laquelle vous pouvez travailler par programmation. Il convient de préciser qu'il existe un grand nombre d'analyseurs JavaScript
, en voici quelques-uns:
- babel-parser - un analyseur qui utilise
babel
; - espree - un analyseur qui utilise
eslint
; - gland - l'analyseur sur lequel les deux précédents sont basés;
- esprima - un analyseur populaire qui prend en charge JavaScript jusqu'à EcmaScript 2017;
- cherow est un nouveau joueur parmi les analyseurs JavaScript qui prétend être le plus rapide;
Il existe un analyseur JavaScript standard, il est appelé ESTree et définit les nœuds à analyser.
Pour une analyse plus détaillée du processus d'implémentation de l'analyseur (ainsi que du transformateur et du générateur), vous pouvez lire le super-minuscule-compilateur .
Afin de transformer l'arborescence AST, vous pouvez utiliser le modèle Visitor , par exemple, en utilisant la bibliothèque @ babel / traverse . Le code suivant affichera les noms de tous les identificateurs de code JavaScript à partir de la variable de code
.
import * as parser from "@babel/parser"; import traverse from "@babel/traverse"; const code = `function square(n) { return n * n; }`; const ast = parser.parse(code); traverse(ast, { Identifier(path) { console.log(path.node.name); } });
Générateur
Vous pouvez générer du code, par exemple, en utilisant @ babel / generator , de cette façon:
import {parse} from '@babel/parser'; import generate from '@babel/generator'; const code = 'class Example {}'; const ast = parse(code); const output = generate(ast, code);
Et donc, à ce stade, le lecteur aurait dû avoir une idée de base de ce qui est nécessaire pour transformer le code JavaScript, et avec quels outils il est implémenté.
Il vaut également la peine d'ajouter un outil en ligne comme astexplorer , il combine un grand nombre d'analyseurs, de transformateurs et de générateurs.
Putout
Putout est un transformateur de code compatible avec les plugins. En fait, c'est un croisement entre eslint et babel , combinant les avantages des deux outils.
Comment eslint
putout
affiche les zones à problème dans le code, mais contrairement à eslint
putout
, le comportement du code est modifié, c'est-à-dire qu'il est capable de corriger toutes les erreurs qu'il peut trouver.
Comme babel
putout
convertit le code, mais essaie de le modifier de façon minimale, afin qu'il puisse être utilisé pour travailler avec du code stocké dans le référentiel.
Prettier mérite également d'être mentionné, il s'agit d'un outil de formatage et il diffère radicalement.
Jscodeshift est situé non loin de putout
, mais il ne prend pas en charge les plugins, n'affiche pas de messages d'erreur et utilise également ast-types au lieu de @ babel / types .
Histoire d'apparence
Dans le processus, eslint
m'aide beaucoup avec mes conseils. Mais parfois je veux plus de lui. Par exemple, pour supprimer le débogueur , corrigez test.only et supprimez également les variables inutilisées. Le dernier point a constitué la base de la mise au putout
, au cours du processus de développement, il est devenu clair que ce n'est pas facile et que de nombreuses autres transformations sont beaucoup plus faciles à mettre en œuvre. Ainsi, putout
passé d'une fonction au système de plugins. La suppression des variables inutilisées est désormais le processus le plus difficile, mais cela ne nous empêche pas de développer et de prendre en charge de nombreuses autres transformations tout aussi utiles.
Comment fonctionne le putout à l'intérieur
putout
travail de mise au point peut être divisé en deux parties: le moteur et les plugins. Cette architecture vous permet de ne pas être distrait par les transformations lorsque vous travaillez avec le moteur, et lorsque vous travaillez sur des plug-ins, vous vous concentrerez autant que possible sur leur objectif.
Plugins intégrés
putout
est construit sur un système de plugins. Chaque plugin représente une règle. En utilisant les règles intégrées, vous pouvez effectuer les opérations suivantes:
- Appliquer la déstructuration:
- Combinez les propriétés de déstructuration:
Chaque plugin est construit selon la philosophie Unix , c'est-à-dire qu'ils sont aussi simples que possible, chacun effectue une action, ce qui les rend faciles à combiner, car ils sont, par essence, des filtres.
Par exemple, avoir le code suivant:
const name = user.name; const password = user.password;
Il est d'abord converti en utilisant la déstructuration d'application pour:
const {name} = user; const {password} = user;
Ensuite, en utilisant les propriétés de fusion-destruction, il est converti en:
const { name, password } = user;
Ainsi, les plugins peuvent fonctionner à la fois séparément et ensemble. Lors de la création de vos propres plug-ins, il est recommandé de respecter cette règle et d'implémenter un plug-in avec des fonctionnalités minimales, en ne faisant que ce dont vous avez besoin, et les plug-ins intégrés et personnalisés se chargeront du reste.
Exemple d'utilisation
Après nous être familiarisés avec les règles intégrées, nous pouvons considérer un exemple utilisant putout
.
Créez un fichier example.js
avec le contenu suivant:
const x = 1, y = 2; const name = user.name; const password = user.password; console.log(name, password);
Exécutez maintenant putout
en passant example.js
comme argument:
coderaiser@cloudcmd:~/example$ putout example.js /home/coderaiser/example/example.js 1:6 error "x" is defined but never used remove-unused-variables 1:13 error "y" is defined but never used remove-unused-variables 6:0 error Unexpected "console" call remove-console 1:0 error variables should be declared separately split-variable-declarations 3:6 error Object destructuring should be used apply-destructuring 4:6 error Object destructuring should be used apply-destructuring 6 errors in 1 files fixable with the `--fix` option
Nous recevrons des informations contenant 6 erreurs, examinées plus en détail ci-dessus, nous allons maintenant les corriger et voir ce qui s'est passé:
coderaiser@cloudcmd:~/example$ putout example.js --fix coderaiser@cloudcmd:~/example$ cat example.js const { name, password } = user;
À la suite de la correction, les variables inutilisées et les appels console.log
ont été supprimés et la déstructuration a également été appliquée.
Paramètres
Les paramètres par défaut peuvent ne pas toujours et pas pour tout le monde, donc putout
prend en charge le fichier de configuration .putout.json
, il se compose des sections suivantes:
- Les règles
- Ignorer
- Match
- Plugins
Les règles
La section des rules
contient un système de règles. Par défaut, les règles sont définies comme suit:
{ "rules": { "remove-unused-variables": true, "remove-debugger": true, "remove-only": true, "remove-skip": true, "remove-process-exit": false, "remove-console": true, "split-variable-declarations": true, "remove-empty": true, "remove-empty-pattern": true, "convert-esm-to-commonjs": false, "apply-destructuring": true, "merge-destructuring-properties": true } }
Pour activer remove-process-exit
définissez-le simplement sur true
dans le fichier .putout.json
:
{ "rules": { "remove-process-exit": true } }
Ce sera suffisant pour signaler tous les appels process.exit
trouvés dans le code et les supprimer si l'option --fix
est --fix
.
Ignorer
Si vous devez ajouter des dossiers à la liste des exceptions, ajoutez simplement la section ignore
:
{ "ignore": [ "test/fixture" ] }
Match
Si vous avez besoin d'un système de règles ramifié, par exemple, activez process.exit
pour le répertoire bin
, utilisez simplement la section de match
:
{ "match": { "bin": { "remove-process-exit": true, } } }
Plugins
Si vous utilisez des plugins qui ne sont pas intégrés et qui ont le préfixe putout-plugin-
, vous devez les inclure dans la section plugins
avant de les activer dans la section rules
. Par exemple, pour connecter le putout-plugin-add-hello-world
et activer la règle add-hello-world
, spécifiez simplement:
{ "rules": { "add-hello-world": true }, "plugins": [ "add-hello-world" ] }
Moteur de mise hors tension
Le moteur putout
est un outil en ligne de commande qui lit les paramètres, analyse les fichiers, charge et exécute les plugins, puis écrit le résultat des plugins.
Il utilise la bibliothèque de refonte , qui aide à effectuer une tâche très importante: après l'analyse et la transformation, collecter le code dans un état aussi similaire que possible au précédent.
Pour l'analyse, un ESTree
compatible ESTree
est utilisé ( babel
est actuellement avec le plugin estree
, mais des modifications sont possibles à l'avenir), et les outils babel
sont utilisés pour la transformation. Pourquoi babel
exactement? Tout est simple. Le fait est que c'est un produit très populaire, beaucoup plus populaire que d'autres outils similaires, et qu'il se développe beaucoup plus rapidement. Chaque nouvelle proposition dans le standard EcmaScript n'est pas complète sans un plugin babel . Babel a également un livre, Babel Handbook , qui décrit très bien toutes les fonctionnalités et les outils pour traverser et transformer un arbre AST.
Plugin personnalisé pour Putout
Le système de plugins putout
est assez simple, et très similaire aux plugins eslint , ainsi qu'aux plugins babel . Certes, au lieu d'une fonction, putout
plugin devrait exporter 3. Cela est fait afin d'augmenter la réutilisation du code, car la duplication de la fonctionnalité dans 3 fonctions n'est pas très pratique, il est beaucoup plus facile de la mettre dans des fonctions séparées et de simplement l'appeler aux bons endroits.
Structure du plugin
Putout
plugin Putout
comprend donc 3 fonctions:
report
- renvoie un message;find
- recherche des lieux avec des erreurs et les renvoie;fix
- corrige ces endroits;
Le principal point à retenir lors de la création d'un plugin pour putout
est son nom, il doit commencer par putout-plugin-
. Ensuite peut être le nom de l'opération que le plugin effectue, par exemple, le plugin remove-wrong
bad devrait être appelé comme ceci: putout-plugin-remove-wrong
.
Vous devez également ajouter les mots: putout
et putout-plugin
à la section package.json
dans la section des keywords
- keywords
, et spécifier "putout": ">=3.10"
dans peerDependencies
"putout": ">=3.10"
, ou la version qui sera la dernière au moment de l'écriture du plug-in.
Exemple de plugin pour Putout
Écrivons un exemple de plugin qui supprimera le mot debugger
du code. Un tel plugin existe déjà, c'est @ putout / plugin-remove-debugger et il est assez simple pour le considérer maintenant.
Cela ressemble à ceci:
Si la règle remove-debugger
est incluse dans .putout.json
, le @putout/plugin-remove-debugger
sera chargé. Tout d'abord, la fonction find
est appelée qui, à l'aide de la fonction traverse
, contournera les nœuds de l'arbre AST et enregistrera tous les emplacements nécessaires.
L'étape suivante se transforme en report
pour obtenir le message souhaité.
Si l'indicateur --fix
est utilisé, la fonction fix
du plugin sera appelée et la transformation sera effectuée, dans ce cas, le nœud sera supprimé.
Exemple de test de plugin
Afin de simplifier le test des plugins, l'outil @ putout / test a été écrit. À la base, ce n'est rien de plus qu'un wrapper sur bande , avec plusieurs méthodes pour la commodité et la simplification des tests.
Le test du plugin remove-debugger
pourrait ressembler à ceci:
const removeDebugger = require('..'); const test = require('@putout/test')(__dirname, { 'remove-debugger': removeDebugger, });
Codemods
Toutes les transformations ne doivent pas être utilisées tous les jours, pour les transformations ponctuelles, il suffit de faire la même chose, mais au lieu de publier sur npm
placez-la dans le ~/.putout
. Au démarrage, putout
va chercher dans ce dossier, ramasser et démarrer la transformation.
Voici un exemple de transformation qui remplace la connexion de tape
et de tentative de bande par un appel de supertape : convert-tape-to-supertape .
eslint-plugin-putout
En fin de compte, cela vaut la peine d'ajouter un point: putout
essaie de modifier le code de manière minimale, mais s'il arrive à un ami que certaines règles de formatage ne respectent pas, eslint --fix est toujours prêt à eslint --fix
, et à cet effet, il existe un plugin eslint-plugin-putout spécial. Il peut égayer de nombreuses erreurs de formatage et, bien sûr, il peut être personnalisé en fonction des préférences des développeurs sur un projet spécifique. La connexion est simple:
{ "extends": [ "plugin:putout/recommended", ], "plugins": [ "putout" ] }
Jusqu'à présent, il n'y a qu'une seule règle: la one-line-destructuring
, elle fait ce qui suit:
Il y a beaucoup plus de règles eslint
incluses que vous pouvez vous familiariser plus en détail .
Conclusion
Je remercie le lecteur de l'attention portée à ce texte. J'espère sincèrement que le sujet des transformations AST deviendra plus populaire et que les articles sur ce processus fascinant apparaîtront plus souvent. Je serais très reconnaissant pour tout commentaire et suggestion concernant le développement ultérieur de la putout
. Créez un problème , envoyez un pool de demandes , testez, écrivez les règles que vous souhaitez voir et comment programmer votre code par programme, nous travaillerons ensemble pour améliorer l'outil de transformation AST.