Gå til innholdet

Refaktorer kode strukturelt

Denne veiledningen viser deg hvordan du får en agent til å gjøre en bred, mekanisk endring — en codemod, en massemigrering av et API, et mønster du vil bli kvitt — ved hjelp av strukturelt søk og strukturell omskriving, med endringen mellomlagret for gjennomgang før noe som helst skrives.

ast_grep matcher kode etter form. ast_edit skriver den om. resolve sender inn eller forkaster.

Strukturelle verktøy dekker språkene kodegrafen parser: Dart, JavaScript, TypeScript, TSX, PHP, Python, Rust, Zig, C, C++, Go, Java, Ruby, C#, Swift, Kotlin, R, Assembly, MATLAB og Ada. De trenger tree-sitter-grammatikkene på plass, og det er de alltid i et ferdiglevert bygg — en manglende grammatikk stopper serveren i stedet for å svekke søket.

Skriv aldri om noe du ikke har sett på. Be agenten søke:

Finn hvert sted der vi kaller dispose() på en nullable som vi nettopp har null-sjekket.

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

Utdataene er file:line pluss kildekoden som ble matchet, for hvert sted.

Token Matcher
$NAME Enhver enkelt node, fanget
$$$NAME Enhver sekvens av søsken — argumenter, setninger
$_ Hva som helst, fanget uten navn

Det som betyr noe: et gjentatt $NAME må fange den samme teksten.

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

finner en vakt som frigjør det den testet. Den finner ikke if (a != null) b.dispose() — som vanligvis er feilen du lette etter. Det kan ikke noe tekstbasert søk uttrykke.

To ting du kan prøve, i denne rekkefølgen:

  1. Gjør det mindre. foo($X) før if (a) { foo($X); }. Et mønster som ikke er gyldig kode alene, er ofte ikke det konstruktet du mente.
  2. Oppgi språket. language: utledes bare når path peker på én enkelt fil.

Et mønster som ingen parsekontekst godtar, rapporteres som uparsbart — verktøyet sier det i stedet for å rapportere «ingen treff» for noe som aldri ble lest slik det ser ut.

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

Resultatet er et forslag:

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

Ingenting er på disken ennå. Les lista, sjekk et par av stedene søket rapporterte, og kjør deretter:

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

eller

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

Poenget med å gjøre dette strukturelt, er at det er bredt. Brede endringer er nettopp der en omskriving som ser troverdig ut, ødelegger en type. Be om diagnostikk etter at du har sendt inn:

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

file: "*" kjører prosjektets egen sjekker (dart analyze, tsc --noEmit, cargo check, go build) i stedet for en spørring per fil — den riktige formen på spørsmålet etter en endring som berørte førti filer.

Si at log(msg, level) er i ferd med å bli log(level: level, message: msg).

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

Se på treffene. Hvis noen av dem er en annen log med to argumenter, avgrens mønsteret ved å sette path til mappa som eier den du mener, i stedet for å prøve å uttrykke forskjellen i mønsteret.

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

Deretter resolve, så lsp(action: "diagnostics", file: "*").

Situasjon Bedre verktøy
Å gi et symbol nytt navn lsp_rename — den følger referanser, ikke former
Å finne en streng, en kommentar eller et filnavn search (innhold) / search_files (navn)
«Hva går i stykker hvis jeg endrer dette?» Kodegrafen
Et språk grammatikkene ikke dekker search pluss edit