Refaktoryzuj kod strukturalnie
W tym poradniku pokazujemy, jak skłonić agenta do szerokiej, mechanicznej zmiany — codemodu, masowej migracji API, wzorca, którego chcesz się pozbyć — za pomocą wyszukiwania i przepisywania strukturalnego, przy czym zmiana trafia do poczekalni, zanim cokolwiek zostanie zapisane.
ast_grep dopasowuje kod po kształcie. ast_edit go przepisuje. resolve zatwierdza albo odrzuca.
Zanim zaczniesz
Dział zatytułowany „Zanim zaczniesz”Narzędzia strukturalne obejmują języki, które parsuje graf kodu: Dart, JavaScript, TypeScript, TSX, PHP, Python, Rust, Zig, C, C++, Go, Java, Ruby, C#, Swift, Kotlin, R, Assembly, MATLAB i Ada. Wymagają przygotowanych gramatyk tree-sittera, co w wydanym buildzie zawsze ma miejsce — brakująca gramatyka zatrzymuje serwer, zamiast degradować wyszukiwanie.
Najpierw znajdź kształt
Dział zatytułowany „Najpierw znajdź kształt”Nigdy nie przepisuj czegoś, czego nie obejrzałeś. Poproś agenta o wyszukanie:
Znajdź każde miejsce, w którym wywołujemy
dispose()na wartości nullowalnej, którą dopiero co sprawdziliśmy pod kątem null.
ast_grep(pattern: "if ($X != null) $X.dispose()", language: "dart")Wynik to file:line oraz dopasowany kod źródłowy dla każdego miejsca.
Co daje składnia wzorca
Dział zatytułowany „Co daje składnia wzorca”| Token | Dopasowuje |
|---|---|
$NAME |
Dowolny pojedynczy węzeł, przechwycony |
$$$NAME |
Dowolny ciąg rodzeństwa — argumenty, instrukcje |
$_ |
Cokolwiek, przechwycone bez nazwy |
Najważniejsze jest to: powtórzony $NAME musi przechwycić ten sam tekst.
if ($X != null) $X.dispose()znajduje warunek ochronny, który zwalnia to, co sprawdził. Nie znajdzie natomiast if (a != null) b.dispose() — a to zwykle właśnie ten błąd, którego szukałeś. Żadne wyszukiwanie tekstowe nie potrafi tego wyrazić.
Gdy wzorzec nic nie znajduje
Dział zatytułowany „Gdy wzorzec nic nie znajduje”Dwie rzeczy do wypróbowania, po kolei:
- Zmniejsz go.
foo($X)przedif (a) { foo($X); }. Wzorzec, który sam w sobie nie jest poprawnym kodem, często nie jest tą konstrukcją, o którą Ci chodziło. - Nazwij język.
language:jest wywnioskowany tylko wtedy, gdypathwskazuje pojedynczy plik.
Wzorzec, którego nie przyjmuje żaden kontekst parsowania, jest zgłaszany jako nieparsowalny — narzędzie mówi o tym wprost, zamiast raportować „brak dopasowań” dla czegoś, co nigdy nie było czytane tak, jak wygląda.
Przepisz i obejrzyj, zanim zmiana trafi na miejsce
Dział zatytułowany „Przepisz i obejrzyj, zanim zmiana trafi na miejsce”ast_edit( pattern: "if ($X != null) $X.dispose()", rewrite: "$X?.dispose()", language: "dart",)Wynikiem jest propozycja:
(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.Na dysku nie ma jeszcze nic. Przeczytaj listę, sprawdź kilka miejsc zgłoszonych przez wyszukiwanie, a następnie:
resolve(edit_id: "edit_1", action: "accept")albo
resolve(edit_id: "edit_1", action: "discard")Sprawdź swoją pracę kompilatorem
Dział zatytułowany „Sprawdź swoją pracę kompilatorem”Sens robienia tego strukturalnie polega na tym, że jest to zmiana szeroka. Szerokie zmiany to dokładnie to miejsce, w którym wiarygodnie wyglądające przepisanie psuje typ. Po zatwierdzeniu poproś o diagnostykę:
lsp(action: "diagnostics", file: "*")file: "*" uruchamia własne narzędzie sprawdzające projektu (dart analyze, tsc --noEmit, cargo check, go build), a nie zapytanie o pojedynczy plik — to właściwy kształt pytania po zmianie, która dotknęła czterdziestu plików.
Przykład: migracja wywołania
Dział zatytułowany „Przykład: migracja wywołania”Załóżmy, że log(msg, level) zmienia się w log(level: level, message: msg).
ast_grep(pattern: "log($MSG, $LEVEL)", language: "dart")Przyjrzyj się trafieniom. Jeśli niektóre z nich to inny dwuargumentowy log, zawęź wzorzec, ograniczając path do katalogu, do którego należy ten właściwy, zamiast próbować wyrazić różnicę w samym wzorcu.
ast_edit( pattern: "log($MSG, $LEVEL)", rewrite: "log(level: $LEVEL, message: $MSG)", path: "lib/features/telemetry", language: "dart",)Potem resolve, a następnie lsp(action: "diagnostics", file: "*").
Kiedy użyć czegoś innego
Dział zatytułowany „Kiedy użyć czegoś innego”| Sytuacja | Lepsze narzędzie |
|---|---|
| Zmiana nazwy symbolu | lsp_rename — podąża za odwołaniami, a nie za kształtami |
| Znalezienie łańcucha znaków, komentarza albo nazwy pliku | search (zawartość) / search_files (nazwy) |
| „Co się zepsuje, jeśli to zmienię?” | Graf kodu |
| Język, którego gramatyki nie obejmują | search plus edit |
Zobacz też
Dział zatytułowany „Zobacz też”- Inteligencja kodu — dlaczego dopasowywanie strukturalne to nie wyrażenie regularne z dodatkowymi krokami
- Wbudowane narzędzia agentów — pełna lista argumentów
- Szukaj kodu w grafie kodu — strona symboli i wpływu