Einblick in den AST: Wie Tools Code verstehen
Jedes Mal, wenn ESLint ein Problem meldet, bevor Sie Ihren Code ausführen, oder Prettier eine unordentliche Funktion im Moment des Speicherns neu formatiert, passiert etwas Präzises im Hintergrund. Diese Tools lesen Ihren Quellcode nicht als Text. Sie lesen ihn als strukturierten Baum. Das Verständnis dieses Baums — des Abstract Syntax Tree (AST) — erklärt, wie nahezu jedes moderne Entwicklungstool funktioniert.
Wichtigste Erkenntnisse
- Ein AST ist eine baumartige Darstellung der Struktur Ihres Codes, oft bereinigt um oberflächliche Details wie Leerzeichen und Gruppierungsklammern.
- Linter, Formatierer und Compiler arbeiten alle, indem sie Quellcode in einen AST parsen, ihn durchlaufen und Regeln oder Transformationen auf jeden Knoten anwenden.
- Das Visitor-Pattern ist der vorherrschende Ansatz: Tools registrieren Handler für bestimmte Knotentypen und reagieren, während der Baum durchlaufen wird.
- Ein Concrete Syntax Tree (CST) behält jeden Token bei, was ihn besser für Editoren geeignet macht, die inkrementelles Parsen und vollständige Darstellungen benötigen.
- Rust-basierte Tools wie Biome und Oxc verwenden dasselbe Parse-Traverse-Act-Modell, übertreffen aber die Performance vieler JavaScript-basierter Parser deutlich.
Was ist ein Abstract Syntax Tree?
Wenn ein Tool Ihren Quellcode parst, durchläuft es zwei Phasen.
Zunächst zerlegt ein Lexer (oder Tokenizer) den Rohtext in Tokens: Schlüsselwörter, Bezeichner, Operatoren, Interpunktion. Dann nimmt ein Parser diese Tokens und baut einen Baum auf, der die grammatikalische Struktur Ihres Codes darstellt.
Dieser Baum ist der AST. Er wird „abstrakt” genannt, weil er oft oberflächliche Details weglässt — Leerzeichen, die meiste Interpunktion und Klammern, die rein zur Gruppierung verwendet werden — während er das semantisch Bedeutsame behält.
Nehmen Sie diese Zeile:
const x = 5 + 3
Ein Parser erzeugt etwa Folgendes:
{
"type": "VariableDeclaration",
"kind": "const",
"declarations": [{
"type": "VariableDeclarator",
"id": { "type": "Identifier", "name": "x" },
"init": {
"type": "BinaryExpression",
"operator": "+",
"left": { "type": "Literal", "value": 5 },
"right": { "type": "Literal", "value": 3 }
}
}]
}
Jeder Knoten hat einen type. Kindknoten sind in Elternknoten verschachtelt. Das gesamte Programm wird zu einem Baum mit einem Program-Knoten als Wurzel.
Sie können dies direkt mit dem AST Explorer erkunden, der es Ihnen ermöglicht, beliebigen JavaScript- oder TypeScript-Code einzufügen und den resultierenden Baum in Echtzeit zu inspizieren.
Wie Linter und Formatierer ASTs verwenden
Sobald ein Tool den AST hat, kann es etwas Nützliches tun: ihn durchlaufen.
Die meisten Tools verwenden das Visitor-Pattern. Sie registrieren eine Funktion für einen bestimmten Knotentyp, und die Traversierungs-Engine ruft diese Funktion jedes Mal auf, wenn sie auf einen passenden Knoten trifft.
ESLint funktioniert genau auf diese Weise. Jede Lint-Regel ist ein Visitor-Objekt. Wenn ESLint den AST durchläuft, ruft es die relevanten Regel-Handler bei jedem Knoten auf. Eine Regel, die == zugunsten von === verbietet, lauscht einfach auf BinaryExpression-Knoten und prüft die operator-Eigenschaft.
Babel verwendet denselben Ansatz für Code-Transformationen. Es parst den Quellcode in einen AST, wendet Visitor-basierte Transformationen an, die Knoten modifizieren, und druckt dann den modifizierten Baum zurück in Quellcode. So kompiliert es moderne JavaScript-Syntax in ältere Äquivalente.
Prettier verfolgt einen anderen Ansatz. Es parst Code in einen AST, verwirft die gesamte ursprüngliche Formatierung und druckt den Baum gemäß seinen eigenen Layout-Regeln neu. Der AST ist die Quelle der Wahrheit — nicht der ursprüngliche Text.
Discover how at OpenReplay.com.
AST vs. CST: Wenn Struktur wichtiger ist
Ein AST lässt Tokens weg, die syntaktisch erforderlich, aber semantisch redundant sind. Ein Concrete Syntax Tree (CST) behält die vollständige syntaktische Struktur des Quellcodes bei und bewahrt Tokens und ihre Positionen.
Tree-sitter erzeugt einen Concrete Syntax Tree, der für inkrementelles Parsen optimiert ist. Da er nur den vom Edit betroffenen Teil des Baums aktualisieren kann, eignet er sich gut für Syntax-Highlighting, Code-Folding und strukturelle Bearbeitung in modernen Editoren wie Neovim und Zed.
Der Wandel zu Hochleistungs-Parsing
Neuere Tools im JavaScript-Ökosystem treiben die Parsing-Performance deutlich weiter. Projekte wie Biome und Oxc sind in Rust implementiert und bauen ihre eigenen Parser und AST-Darstellungen von Grund auf neu.
Sie bewältigen Linting und Formatierung mit Geschwindigkeiten, die JavaScript-basierte Parser oft übertreffen, während sie moderne Syntax wie Import-Attribute und aktuelle TypeScript-Features unterstützen.
Das zugrunde liegende Modell ist dasselbe — Parsen zu einem Baum, Durchlaufen, Anwenden von Analyse oder Transformation — aber die Implementierung ist für Skalierung optimiert.
Fazit
Ob Sie eine benutzerdefinierte ESLint-Regel schreiben, ein Codemod mit jscodeshift erstellen oder einfach nur verstehen möchten, warum sich ein Tool so verhält, wie es sich verhält — der AST ist der richtige Ausgangspunkt. Er ist die strukturierte Darstellung, auf der jedes ernsthafte Entwicklungstool aufbaut. Sobald Sie einen Syntaxbaum lesen können, hört das Verhalten von Lintern, Formatierern und Compilern auf, wie Magie zu wirken.
Häufig gestellte Fragen
Nicht für den alltäglichen Gebrauch. Diese Tools funktionieren out of the box mit vernünftigen Standardeinstellungen. Aber wenn Sie benutzerdefinierte Lint-Regeln schreiben, Codemods erstellen oder unerwartetes Tool-Verhalten debuggen möchten, gibt Ihnen das Verständnis, wie ASTs Ihren Code darstellen, ein klares mentales Modell davon, was das Tool bei jedem Schritt tatsächlich tut.
Ein AST entfernt syntaktisch erforderliche, aber semantisch redundante Tokens wie Kommas, Klammern und Leerzeichen. Ein CST behält die vollständige syntaktische Struktur des Quellcodes bei. CSTs werden in Editoren und IDEs bevorzugt, wo vollständige Darstellung und inkrementelles Parsen wichtig sind, während ASTs typischerweise von Lintern, Compilern und Formatierern verwendet werden.
Der einfachste Weg ist der AST Explorer unter astexplorer.net. Fügen Sie ein beliebiges JavaScript- oder TypeScript-Snippet ein, wählen Sie einen Parser wie acorn, babel oder typescript, und das Tool zeigt den vollständigen Baum in Echtzeit an. Es unterstützt auch andere Sprachen und ermöglicht es Ihnen, Transformationen direkt im Browser auszuprobieren.
Rust gibt diesen Tools direkte Kontrolle über Speicherallokation und -layout, vermeidet Garbage-Collection-Pausen und kompiliert zu hochoptimiertem nativem Code. Das bedeutet, dass Parsen, Traversierung und Analyse alle deutlich schneller ablaufen, was besonders in großen Codebasen mit Tausenden von Dateien spürbar wird.
Understand every bug
Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.