Salta ai contenuti

Esegui un refactoring strutturale del codice

Questa guida mostra come chiedere a un agente di apportare una modifica meccanica su larga scala — un codemod, una migrazione API in blocco, un pattern che vuoi eliminare — usando la ricerca e la riscrittura strutturale, con la modifica preparata per la revisione prima che venga scritto alcunché.

ast_grep trova il codice in base alla forma. ast_edit lo riscrive. resolve conferma o scarta.

Gli strumenti strutturali coprono le lingue che il grafo del codice analizza: Dart, JavaScript, TypeScript, TSX, PHP, Python, Rust, Zig, C, C++, Go, Java, Ruby, C#, Swift, Kotlin, R, Assembly, MATLAB e Ada. Richiedono che le grammatiche tree-sitter siano predisposte, cosa che in una build distribuita avviene sempre: una grammatica mancante blocca il server invece di degradare la ricerca.

Non riscrivere mai qualcosa che non hai guardato. Chiedi all’agente di cercare:

Trova ogni punto in cui chiamiamo dispose() su un nullable di cui abbiamo appena verificato che non sia null.

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

L’output è file:line più il codice sorgente corrispondente per ogni occorrenza.

Token Corrisponde
$NAME Qualsiasi singolo nodo, catturato
$$$NAME Qualsiasi sequenza di nodi adiacenti — argomenti, istruzioni
$_ Qualsiasi cosa, catturata senza nome

Quello che conta: un $NAME ripetuto deve catturare lo stesso testo.

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

trova una guardia che elimina ciò che ha verificato. Non trova if (a != null) b.dispose(), che di solito è il bug che stavi cercando. Nessuna ricerca testuale può esprimere questo.

Due cose da provare, in ordine:

  1. Rendilo più piccolo. foo($X) prima di if (a) { foo($X); }. Un pattern che non è codice valido da solo spesso non è il costrutto che intendevi.
  2. Indica la lingua. language: viene dedotto solo quando path indica un singolo file.

Un pattern che nessun contesto di analisi accetta viene segnalato come non analizzabile: lo strumento lo dice, invece di riportare “nessuna corrispondenza” per qualcosa che non veniva letto affatto come sembra.

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

Il risultato è una proposta:

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

Non c’è ancora nulla su disco. Leggi l’elenco, controlla un paio delle occorrenze segnalate dalla ricerca, poi:

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

oppure

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

Il senso di farlo in modo strutturale è che la modifica è ampia. Le modifiche ampie sono esattamente il punto in cui una riscrittura dall’aspetto plausibile rompe un tipo. Chiedi la diagnostica dopo aver eseguito resolve:

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

file: "*" esegue il checker del progetto (dart analyze, tsc --noEmit, cargo check, go build) invece di una query per singolo file: la forma giusta di domanda dopo una modifica che ha toccato quaranta file.

Supponiamo che log(msg, level) stia diventando log(level: level, message: msg).

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

Guarda le occorrenze. Se alcune sono un log a due argomenti diverso, restringi il pattern limitando path alla directory che contiene quello che intendi, invece di provare a esprimere la differenza nel pattern.

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

Poi resolve, quindi lsp(action: "diagnostics", file: "*").

Situazione Strumento migliore
Rinominare un simbolo lsp_rename — segue i riferimenti, non le forme
Trovare una stringa, un commento o un nome di file search (contenuti) / search_files (nomi)
“Cosa si rompe se modifico questo?” Il grafo del codice
Una lingua non coperta dalle grammatiche search più edit