Einführung
Jeden Tag, wenn an dem Code gearbeitet wird, werden auf dem Weg zur Implementierung einer für den Benutzer nützlichen Funktion erzwungene (unvermeidliche oder einfach wünschenswerte) Änderungen am Code. Dies kann das Refactoring, das Aktualisieren einer Bibliothek oder eines Frameworks auf eine neue Hauptversion und das Aktualisieren der JavaScript-Syntax sein (was in letzter Zeit nicht ungewöhnlich ist). Selbst wenn die Bibliothek Teil eines Arbeitsprojekts ist, sind Änderungen unvermeidlich. Die meisten dieser Änderungen sind Routine. Es gibt nichts Interessantes für den Entwickler in ihnen, einerseits bringt es nichts für das Geschäft, und andererseits müssen Sie während des Aktualisierungsprozesses sehr vorsichtig sein, um Brennholz und Funktionalität nicht zu beschädigen. Wir kommen daher zu dem Schluss, dass es besser ist, eine solche Routine auf die Schultern von Programmen zu verlagern, damit diese alles selbst tun und die Person wiederum kontrolliert, ob alles richtig gemacht wurde. Dies wird im heutigen Artikel diskutiert.
AST
Für die Verarbeitung von Programmcode ist es erforderlich, ihn in eine spezielle Darstellung zu übersetzen, mit der Programme bequem arbeiten können. Eine solche Darstellung existiert, sie heißt Abstract Syntax Tree (AST).
Verwenden Sie Parser, um es zu erhalten. Der resultierende AST kann beliebig transformiert werden. Um das Ergebnis zu speichern, benötigen Sie einen Codegenerator. Lassen Sie uns jeden der Schritte genauer betrachten. Beginnen wir mit dem Parser.
Parser
Und so haben wir den Code:
a + b
Parser sind normalerweise in zwei Teile unterteilt:
Bricht den Code in Token auf, von denen jeder einen Teil des Codes beschreibt:
[{ "type": "Identifier", "value": "a" }, { "type": "Punctuator", "value": "+", }, { "type": "Identifier", "value": "b" }]
Erstellt einen Syntaxbaum aus Token:
{ "type": "BinaryExpression", "left": { "type": "Identifier", "name": "a" }, "operator": "+", "right": { "type": "Identifier", "name": "b" } }
Und jetzt haben wir bereits genau diese Idee, mit der Sie programmgesteuert arbeiten können. Es sollte klargestellt werden, dass es eine große Anzahl von JavaScript
Parsern gibt. Hier einige davon:
- babel-parser - ein Parser, der
babel
; - espree - ein Parser, der
eslint
; - Eichel - der Parser, auf dem die beiden vorherigen basieren;
- esprima - ein beliebter Parser, der JavaScript bis EcmaScript 2017 unterstützt;
- cherow ist ein neuer Player unter den JavaScript-Parsern, der behauptet, der schnellste zu sein.
Es gibt einen Standard-JavaScript-Parser, der ESTree heißt und definiert, welche Knoten analysiert werden sollen.
Für eine detailliertere Analyse des Implementierungsprozesses des Parsers (sowie des Transformators und des Generators) können Sie den Super-Winzling-Compiler lesen.
Um den AST-Baum zu transformieren, können Sie das Besuchermuster verwenden , z. B. die Bibliothek @ babel / traverse . Der folgende Code gibt die Namen aller JavaScript-Code-IDs aus der Codevariablen aus.
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); } });
Generator
Sie können Code beispielsweise mit @ babel / generator auf folgende Weise generieren:
import {parse} from '@babel/parser'; import generate from '@babel/generator'; const code = 'class Example {}'; const ast = parse(code); const output = generate(ast, code);
Zu diesem Zeitpunkt sollte der Leser eine grundlegende Vorstellung davon haben, was zum Transformieren von JavaScript-Code erforderlich ist und mit welchen Tools er implementiert wird.
Es lohnt sich auch, ein Online-Tool wie astexplorer hinzuzufügen , das eine große Anzahl von Parsern, Transformatoren und Generatoren kombiniert.
Putout
Putout ist ein Plugin-fähiger Codetransformator . Tatsächlich ist es eine Kreuzung zwischen Eslint und Babel , die die Vorteile beider Werkzeuge kombiniert.
Wie eslint
putout
Problembereiche im Code eslint
, aber im Gegensatz zu eslint
putout
ändert es das Verhalten des Codes, putout
es kann alle gefundenen Fehler beheben.
Wie babel
putout
konvertiert es den Code, versucht ihn jedoch nur minimal zu ändern, sodass er für die Arbeit mit Code verwendet werden kann, der im Repository gespeichert ist.
Hübscher ist auch erwähnenswert, es ist ein Formatierungswerkzeug und es unterscheidet sich radikal.
Jscodeshift befindet sich nicht weit von putout
, unterstützt jedoch keine Plugins, zeigt keine Fehlermeldungen an und verwendet anstelle von @ babel / types auch Ast- Typen .
Erscheinungsgeschichte
Dabei hilft mir eslint
sehr mit meinen Tipps. Aber manchmal will ich mehr von ihm. Um beispielsweise den Debugger zu entfernen , korrigieren Sie test.only und löschen Sie auch nicht verwendete Variablen. Der letzte Punkt bildete die Grundlage für das putout
. Während des Entwicklungsprozesses wurde klar, dass dies nicht einfach ist und viele andere Transformationen viel einfacher zu implementieren sind. Somit wuchs putout
reibungslos von einer Funktion zum Plugin-System. Das Entfernen nicht verwendeter Variablen ist jetzt der schwierigste Prozess, aber dies hindert uns nicht daran, viele andere gleichermaßen nützliche Transformationen zu entwickeln und zu unterstützen.
Wie Putout im Inneren funktioniert
putout
Arbeit kann in zwei Teile unterteilt werden: Motor und Plugins. Diese Architektur ermöglicht es Ihnen, sich bei der Arbeit mit der Engine nicht von Transformationen ablenken zu lassen, und bei der Arbeit an Plug-Ins konzentrieren Sie sich so weit wie möglich auf deren Zweck.
Eingebaute Plugins
putout
basiert auf einem Plugin-System. Jedes Plugin repräsentiert eine Regel. Mit den integrierten Regeln können Sie Folgendes tun:
- Destrukturierung anwenden:
- Destruktionseigenschaften kombinieren:
Jedes Plugin basiert auf der Unix-Philosophie , das heißt, sie sind so einfach wie möglich, jedes führt eine Aktion aus und lässt sich leicht kombinieren, da es sich im Wesentlichen um Filter handelt.
Zum Beispiel mit folgendem Code:
const name = user.name; const password = user.password;
Es wird zuerst mithilfe von Apply-Destructuring konvertiert in:
const {name} = user; const {password} = user;
Anschließend wird mithilfe von Merge-Destructuring-Eigenschaften Folgendes konvertiert:
const { name, password } = user;
Somit können Plugins sowohl einzeln als auch zusammen arbeiten. Wenn Sie Ihre eigenen Plug-Ins erstellen, wird empfohlen, diese Regel einzuhalten und ein Plug-In mit minimaler Funktionalität zu implementieren, das nur das tut, was Sie benötigen. Den Rest erledigen integrierte und benutzerdefinierte Plug-Ins.
Anwendungsbeispiel
Nachdem wir uns mit den integrierten Regeln vertraut gemacht haben, können wir ein Beispiel mit putout
.
Erstellen Sie eine example.js
Datei mit folgendem Inhalt:
const x = 1, y = 2; const name = user.name; const password = user.password; console.log(name, password);
Führen putout
nun putout
aus, indem Sie example.js
als Argument übergeben:
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
Wir erhalten Informationen mit 6 Fehlern, die oben genauer betrachtet wurden. Jetzt werden wir sie korrigieren und sehen, was passiert ist:
coderaiser@cloudcmd:~/example$ putout example.js --fix coderaiser@cloudcmd:~/example$ cat example.js const { name, password } = user;
Infolge der Korrektur wurden nicht verwendete Variablen und Aufrufe von console.log
gelöscht und auch eine Destrukturierung angewendet.
Einstellungen
Die Standardeinstellungen sind möglicherweise nicht immer und nicht für alle putout
.putout.json
putout
unterstützt .putout.json
Konfigurationsdatei .putout.json
. Sie besteht aus den folgenden Abschnitten:
- Regeln
- Ignorieren
- Match
- Plugins
Regeln
Der Abschnitt rules
enthält ein Regelsystem. Die Regeln sind standardmäßig wie folgt festgelegt:
{ "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 } }
Um remove-process-exit
zu aktivieren remove-process-exit
setzen Sie es einfach in der Datei .putout.json
auf true
:
{ "rules": { "remove-process-exit": true } }
Dies reicht aus, um alle im Code gefundenen Aufrufe von --fix
zu melden und zu löschen, wenn die Option --fix
.
Ignorieren
Wenn Sie der Liste der Ausnahmen einige Ordner hinzufügen müssen, fügen Sie einfach den Abschnitt zum ignore
:
{ "ignore": [ "test/fixture" ] }
Match
Wenn Sie beispielsweise ein verzweigtes Regelsystem benötigen, aktivieren Sie process.exit
für das bin
Verzeichnis. Verwenden Sie dazu einfach den Abschnitt match
:
{ "match": { "bin": { "remove-process-exit": true, } } }
Plugins
Wenn Sie Plugins verwenden, die nicht integriert sind und das Präfix putout-plugin-
, müssen Sie diese in den Abschnitt " plugins
" aufnehmen, bevor Sie sie im Abschnitt " rules
aktivieren. Um beispielsweise das putout-plugin-add-hello-world
zu verbinden und die putout-plugin-add-hello-world
aktivieren, geben Sie einfach Folgendes an:
{ "rules": { "add-hello-world": true }, "plugins": [ "add-hello-world" ] }
Motor ausschalten
Die putout
Engine ist ein Befehlszeilentool, das Einstellungen liest, Dateien analysiert, Plugins lädt und putout
und dann das Ergebnis der Plugins schreibt.
Er verwendet die neu zusammengestellte Bibliothek, mit deren Hilfe eine sehr wichtige Aufgabe ausgeführt werden kann: Sammeln Sie nach dem Parsen und der Transformation den Code in einem Zustand, der dem vorherigen so ähnlich wie möglich ist.
Zum Parsen wird ein ESTree
kompatibler Parser verwendet ( babel
ist derzeit mit dem estree
Plugin verbunden, Änderungen sind jedoch in Zukunft möglich), und babel
Tools werden für die Transformation verwendet. Warum genau babel
? Alles ist einfach. Tatsache ist, dass dies ein sehr beliebtes Produkt ist, das viel beliebter als andere ähnliche Tools ist und sich viel schneller entwickelt. Jeder neue Vorschlag im EcmaScript-Standard ist ohne ein Babel-Plugin nicht vollständig . Babel hat auch ein Buch, Babel Handbook , das alle Funktionen und Werkzeuge zum Durchlaufen und Transformieren eines AST-Baums sehr gut beschreibt.
Benutzerdefiniertes Plugin für Putout
Das putout
Plugin-System ist recht einfach und den Eslint-Plugins sowie den Babel-Plugins sehr ähnlich. putout
, anstelle einer Funktion sollte das putout
Plugin 3 exportieren. Dies geschieht, um die Wiederverwendung von Code zu erhöhen, da das Duplizieren von Funktionen in 3 Funktionen nicht sehr praktisch ist. Es ist viel einfacher, sie in separate Funktionen zu integrieren und sie einfach an den richtigen Stellen aufzurufen.
Plugin Struktur
Das Putout
Plugin besteht also aus 3 Funktionen:
report
- gibt eine Nachricht zurück;find
- sucht nach fehlerhaften Orten und gibt sie zurück;fix
- behebt diese Stellen;
Der wichtigste Punkt, den Sie beim Erstellen eines Plugins für putout
ist der Name. Es sollte mit putout-plugin-
. Als nächstes kann der Name der Operation angegeben werden, die das Plugin ausführt. Beispielsweise sollte das Plugin zum remove-wrong
putout-plugin-remove-wrong
wie putout-plugin-remove-wrong
aufgerufen werden: putout-plugin-remove-wrong
.
Sie sollten auch die Wörter putout
und putout-plugin
zum Abschnitt package.json
im keywords
"putout": ">=3.10"
und "putout": ">=3.10"
in peerDependencies
"putout": ">=3.10"
oder die Version, die zum Zeitpunkt des Schreibens des Plugins die letzte sein wird.
Beispiel-Plugin für Putout
Lassen Sie uns ein Beispiel-Plugin schreiben, das das Wort debugger
aus dem Code entfernt. Ein solches Plugin existiert bereits, es ist @ putout / plugin-remove-debugger und es ist einfach genug, es jetzt in Betracht zu ziehen.
Es sieht so aus:
Wenn die .putout.json
remove-debugger
Regel in .putout.json
, wird das @putout/plugin-remove-debugger
geladen. Zunächst wird die find
aufgerufen, die mit der traverse
Funktion die Knoten des AST-Baums umgeht und alle erforderlichen Stellen speichert.
Im nächsten Schritt wird die report
putout
, um die gewünschte Nachricht zu erhalten.
Wenn das Flag --fix
verwendet wird, wird die Plugin- fix
Funktion aufgerufen und die Transformation durchgeführt. In diesem Fall wird der Knoten gelöscht.
Plugin Test Beispiel
Um das Testen von Plugins zu vereinfachen, wurde das Tool @ putout / test geschrieben. Im Kern ist es nichts weiter als ein Wrapper-over- Tape mit verschiedenen Methoden zur Bequemlichkeit und Vereinfachung des Testens.
Der Test für das remove-debugger
Plugin könnte folgendermaßen aussehen:
const removeDebugger = require('..'); const test = require('@putout/test')(__dirname, { 'remove-debugger': removeDebugger, });
Codemods
Nicht jede Transformation muss jeden Tag verwendet werden. Für einmalige Transformationen reicht es aus, dasselbe zu tun. Statt sie in npm
legen Sie sie im ~/.putout
. Beim Start putout
in diesem Ordner, nimmt die Transformation auf und startet sie.
Hier ist eine Beispieltransformation, die die tape
und Try-to-Tape- Verbindung durch einen Supertape- Aufruf ersetzt: Konvertieren von Band zu Supertape .
eslint-plugin-putout
Am Ende lohnt es sich, einen Punkt hinzuzufügen: putout
versucht, den Code minimal zu ändern, aber wenn einem Freund passiert, dass einige Formatierungsregeln nicht eingehalten werden, ist eslint --fix immer bereit zu eslint --fix
, und zu diesem Zweck gibt es ein spezielles eslint-plugin-putout-Plugin . Es kann viele Formatierungsfehler aufhellen und natürlich an die Vorlieben der Entwickler für ein bestimmtes Projekt angepasst werden. Das Anschließen ist einfach:
{ "extends": [ "plugin:putout/recommended", ], "plugins": [ "putout" ] }
Bisher gibt es nur eine Regel: one-line-destructuring
, die folgende:
Es gibt viele weitere eslint
Regeln, mit denen Sie sich genauer vertraut machen können.
Fazit
Ich möchte dem Leser für die Aufmerksamkeit danken, die diesem Text geschenkt wurde. Ich hoffe aufrichtig, dass das Thema AST-Transformationen populärer wird und Artikel über diesen faszinierenden Prozess häufiger erscheinen. Ich wäre sehr dankbar für Kommentare und Vorschläge im Zusammenhang mit der Weiterentwicklung von putout
. Erstellen Sie ein Problem , senden Sie einen Pool von Anforderungen , testen Sie, schreiben Sie, welche Regeln Sie sehen möchten und wie Sie Ihren Code programmgesteuert programmieren. Wir werden zusammenarbeiten, um das AST-Transformationstool zu verbessern.