Code strukturell refaktorieren
Diese Anleitung zeigt, wie du einen Agenten mit struktureller Suche und Umschreibung eine breite mechanische Änderung machen lässt — einen Codemod, eine flächige API-Migration, ein Muster, das weg soll — und die Änderung zur Prüfung bereithältst, bevor irgendetwas geschrieben wird.
ast_grep trifft Code nach der Form. ast_edit schreibt ihn um. resolve übernimmt oder verwirft.
Bevor du anfängst
Abschnitt betitelt „Bevor du anfängst“Die strukturellen Werkzeuge decken die Sprachen ab, die der Code-Graph parst: Dart, JavaScript, TypeScript, TSX, PHP, Python, Rust, Zig, C, C++, Go, Java, Ruby, C#, Swift, Kotlin, R, Assembly, MATLAB und Ada. Sie brauchen die mitgelieferten tree-sitter-Grammatiken, und die sind in einem ausgelieferten Build immer da — eine fehlende Grammatik stoppt den Server, statt die Suche zu verschlechtern.
Finde zuerst die Form
Abschnitt betitelt „Finde zuerst die Form“Schreib nichts um, das du dir nicht angesehen hast. Bitte den Agenten, zu suchen:
Finde jede Stelle, an der wir
dispose()auf einem nullbaren Wert aufrufen, den wir gerade auf null geprüft haben.
ast_grep(pattern: "if ($X != null) $X.dispose()", language: "dart")Die Ausgabe ist file:line plus der getroffene Quelltext für jede Stelle.
Was die Mustersyntax bringt
Abschnitt betitelt „Was die Mustersyntax bringt“| Token | Trifft |
|---|---|
$NAME |
Ein beliebiger einzelner Knoten, erfasst |
$$$NAME |
Eine beliebige Folge von Geschwisterknoten — Argumente, Anweisungen |
$_ |
Beliebiges, unter keinem Namen erfasst |
Worauf es ankommt: ein wiederholtes $NAME muss denselben Text erfassen.
if ($X != null) $X.dispose()findet eine Absicherung, die das freigibt, was sie geprüft hat. if (a != null) b.dispose() findet sie nicht — und genau das ist meist der Fehler, den du suchst. Keine Textsuche kann das ausdrücken.
Wenn das Muster nichts findet
Abschnitt betitelt „Wenn das Muster nichts findet“Zwei Versuche, in dieser Reihenfolge:
- Mach es kleiner.
foo($X)vorif (a) { foo($X); }. Ein Muster, das für sich kein gültiger Code ist, ist oft nicht das Konstrukt, das du meintest. - Nenn die Sprache.
language:wird nur abgeleitet, wennpatheine einzelne Datei nennt.
Ein Muster, das kein Parse-Kontext annimmt, wird als nicht parsebar gemeldet — das Tool sagt das, statt „keine Treffer“ für etwas zu melden, das nie so gelesen wurde, wie es aussieht.
Umschreiben und hinsehen, bevor es landet
Abschnitt betitelt „Umschreiben und hinsehen, bevor es landet“ast_edit( pattern: "if ($X != null) $X.dispose()", rewrite: "$X?.dispose()", language: "dart",)Das Ergebnis ist ein Vorschlag:
(proposed) ast_edit: if ($X != null) $X.dispose() → $X?.dispose()12 replacements in 4 files: lib/features/a/x.dart (3) lib/features/b/y.dart (5) …Nothing has been written. Call resolve with edit_id "edit_1" and action"accept" to apply it, or "discard" to drop it.Auf der Platte liegt noch nichts. Lies die Liste, prüfe ein paar der Fundstellen, die die Suche gemeldet hat, dann:
resolve(edit_id: "edit_1", action: "accept")oder
resolve(edit_id: "edit_1", action: "discard")Prüfe deine Arbeit mit dem Compiler
Abschnitt betitelt „Prüfe deine Arbeit mit dem Compiler“Der Grund, das strukturell zu machen, ist die Breite. Breite Änderungen sind genau die Stelle, an der eine plausibel aussehende Umschreibung an einem Typ scheitert. Bitte nach dem resolve um Diagnosen:
lsp(action: "diagnostics", file: "*")file: "*" führt den eigenen Checker des Projekts aus (dart analyze, tsc --noEmit, cargo check, go build), nicht eine Abfrage pro Datei — die richtige Form der Frage nach einer Änderung, die vierzig Dateien berührt hat.
Ausführliches Beispiel: einen Aufruf migrieren
Abschnitt betitelt „Ausführliches Beispiel: einen Aufruf migrieren“Angenommen, log(msg, level) wird zu log(level: level, message: msg).
ast_grep(pattern: "log($MSG, $LEVEL)", language: "dart")Sieh dir die Treffer an. Sind einige davon ein anderes log mit zwei Argumenten, grenze das Muster ein, indem du path auf das Verzeichnis setzt, zu dem das gemeinte gehört, statt den Unterschied im Muster auszudrücken.
ast_edit( pattern: "log($MSG, $LEVEL)", rewrite: "log(level: $LEVEL, message: $MSG)", path: "lib/features/telemetry", language: "dart",)Dann resolve, dann lsp(action: "diagnostics", file: "*").
Wann du etwas anderes nimmst
Abschnitt betitelt „Wann du etwas anderes nimmst“| Situation | Besseres Werkzeug |
|---|---|
| Ein Symbol umbenennen | lsp_rename — es folgt Referenzen, nicht Formen |
| Eine Zeichenkette, einen Kommentar oder einen Dateinamen finden | search (Inhalt) / search_files (Namen) |
| „Was bricht, wenn ich das ändere?“ | Der Code-Graph |
| Eine Sprache, die die Grammatiken nicht abdecken | search plus edit |
Verwandt
Abschnitt betitelt „Verwandt“- Code-Intelligenz — warum strukturelles Treffen kein Regex mit Extra-Schritten ist
- Eingebaute Agenten-Tools — die vollständige Argumentliste
- Code mit dem Code-Graph suchen — die Seite für Symbole und Auswirkungen