Sari la conținut

Refactorează codul structural

Acest ghid îți arată cum să pui un agent să facă o modificare mecanică de mare amploare — un codemod, o migrare în bloc a unui API, un șablon de care vrei să scapi — folosind căutarea și rescrierea structurală, cu modificarea pregătită pentru revizuire înainte de a se scrie ceva.

ast_grep potrivește codul după formă. ast_edit îl rescrie. resolve comite sau renunță.

Instrumentele structurale acoperă limbajele pe care le analizează sintactic graful de cod: Dart, JavaScript, TypeScript, TSX, PHP, Python, Rust, Zig, C, C++, Go, Java, Ruby, C#, Swift, Kotlin, R, Assembly, MATLAB și Ada. Au nevoie de gramaticile tree-sitter pregătite, ceea ce se întâmplă întotdeauna într-o versiune livrată — o gramatică lipsă oprește serverul, în loc să degradeze căutarea.

Nu rescrie niciodată ceva ce nu ai privit. Cere-i agentului să caute:

Găsește fiecare loc în care apelăm dispose() pe un nullable pentru care tocmai am verificat că nu este null.

ast_grep(pattern: "if ($X != null) $X.dispose()", language: "dart")

Rezultatul este file:line plus sursa potrivită pentru fiecare loc.

Token Potrivește
$NAME Orice nod individual, capturat
$$$NAME Orice serie de noduri surori — argumente, instrucțiuni
$_ Orice, capturat fără nume

Cel care contează: un $NAME repetat trebuie să capteze același text.

if ($X != null) $X.dispose()

găsește o gardă care eliberează exact ce a testat. Nu găsește if (a != null) b.dispose() — care este de obicei chiar bug-ul pe care îl căutai. Nicio căutare textuală nu poate exprima asta.

Două lucruri de încercat, în ordine:

  1. Fă-l mai mic. foo($X) înainte de if (a) { foo($X); }. Un șablon care nu este cod valid de unul singur nu este adesea construcția pe care o aveai în minte.
  2. Numește limbajul. language: este dedus numai când path indică un singur fișier.

Un șablon pe care nu îl acceptă niciun context de analiză este raportat ca neanalizabil — instrumentul spune asta, în loc să raporteze „nicio potrivire” pentru ceva ce nu era citit niciodată așa cum arată.

ast_edit(
pattern: "if ($X != null) $X.dispose()",
rewrite: "$X?.dispose()",
language: "dart",
)

Rezultatul este o propunere:

(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.

Încă nu este nimic pe disc. Citește lista, verifică câteva dintre locurile raportate de căutare, apoi:

resolve(edit_id: "edit_1", action: "accept")

sau

resolve(edit_id: "edit_1", action: "discard")

Rostul de a face asta structural este că este larg. Modificările largi sunt exact locul unde o rescriere care pare plauzibilă strică un tip. Cere diagnostice după ce rezolvi:

lsp(action: "diagnostics", file: "*")

file: "*" rulează verificatorul propriu al proiectului (dart analyze, tsc --noEmit, cargo check, go build) în locul unei interogări per fișier — forma corectă de întrebare după o modificare care a atins patruzeci de fișiere.

Să presupunem că log(msg, level) devine log(level: level, message: msg).

ast_grep(pattern: "log($MSG, $LEVEL)", language: "dart")

Privește potrivirile. Dacă unele dintre ele sunt un log diferit cu două argumente, restrânge șablonul limitând path la directorul care îl conține pe cel vizat, în loc să încerci să exprimi diferența în șablon.

ast_edit(
pattern: "log($MSG, $LEVEL)",
rewrite: "log(level: $LEVEL, message: $MSG)",
path: "lib/features/telemetry",
language: "dart",
)

Apoi resolve, apoi lsp(action: "diagnostics", file: "*").

Situație Instrument mai bun
Redenumirea unui simbol lsp_rename — urmărește referințe, nu forme
Găsirea unui șir, a unui comentariu sau a unui nume de fișier search (conținut) / search_files (nume)
„Ce se strică dacă modific asta?” Graful de cod
Un limbaj pe care gramaticile nu îl acoperă search plus edit