Рефакторити код структурно
У цьому посібнику показано, як змусити агента виконати широку механічну зміну — codemod, масову міграцію API, патерн, якого ви хочете позбутися — за допомогою структурного пошуку й перезапису, причому зміну підготовлено для перегляду, перш ніж щось буде записано.
ast_grep зіставляє код за формою. ast_edit його переписує. resolve фіксує або відкидає.
Перш ніж почати
Розділ із назвою «Перш ніж почати»Структурні інструменти охоплюють мови, які розбирає граф коду: Dart, JavaScript, TypeScript, TSX, PHP, Python, Rust, Zig, C, C++, Go, Java, Ruby, C#, Swift, Kotlin, R, Assembly, MATLAB і Ada. Їм потрібні підготовлені граматики tree-sitter, а в готовій збірці вони завжди такі: відсутня граматика зупиняє сервер, а не погіршує пошук.
Спершу знайдіть форму
Розділ із назвою «Спершу знайдіть форму»Ніколи не переписуйте те, чого не бачили. Попросіть агента виконати пошук:
Знайди кожне місце, де ми викликаємо
dispose()у nullable, який щойно перевірили на null.
ast_grep(pattern: "if ($X != null) $X.dispose()", language: "dart")Вивід — це file:line плюс зіставлений вихідний код для кожного місця.
Що дає синтаксис шаблону
Розділ із назвою «Що дає синтаксис шаблону»| Токен | Зіставляє |
|---|---|
$NAME |
Будь-який окремий вузол, захоплений |
$$$NAME |
Будь-яка послідовність сусідів — аргументи, інструкції |
$_ |
Будь-що, захоплене без імені |
Найважливіше: повторений $NAME має захопити той самий текст.
if ($X != null) $X.dispose()знаходить захисну умову, яка звільняє саме те, що перевірила. Він не знаходить if (a != null) b.dispose() — а це зазвичай і є та помилка, яку ви шукали. Жоден текстовий пошук цього не виразить.
Коли шаблон нічого не знаходить
Розділ із назвою «Коли шаблон нічого не знаходить»Дві речі, які варто спробувати, у такому порядку:
- Зробіть його меншим. Спершу
foo($X), а вже потімif (a) { foo($X); }. Шаблон, який сам по собі не є коректним кодом, часто виявляється не тією конструкцією, яку ви мали на увазі. - Назвіть мову.
language:виводять лише тоді, колиpathуказує на один файл.
Шаблон, якого не приймає жоден контекст розбору, позначають як непридатний до розбору — інструмент каже про це прямо, а не повідомляє «збігів немає» для того, що ніколи не читалося так, як виглядає.
Перепишіть — і подивіться, перш ніж зміна потрапить на диск
Розділ із назвою «Перепишіть — і подивіться, перш ніж зміна потрапить на диск»ast_edit( pattern: "if ($X != null) $X.dispose()", rewrite: "$X?.dispose()", language: "dart",)Результат — це пропозиція:
(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.На диску поки нічого немає. Прочитайте список, перевірте кілька місць, які повідомив пошук, і тоді:
resolve(edit_id: "edit_1", action: "accept")або
resolve(edit_id: "edit_1", action: "discard")Перевірте свою роботу компілятором
Розділ із назвою «Перевірте свою роботу компілятором»Сенс робити це структурно в тому, що зміна широка. А широкі зміни — саме те місце, де правдоподібний на вигляд перезапис ламає тип. Попросіть діагностику після фіксації:
lsp(action: "diagnostics", file: "*")file: "*" запускає власний механізм перевірки проєкту (dart analyze, tsc --noEmit, cargo check, go build), а не запит до одного файлу — саме така форма питання доречна після зміни, що торкнулася сорока файлів.
Розібраний приклад: міграція виклику
Розділ із назвою «Розібраний приклад: міграція виклику»Припустімо, log(msg, level) стає log(level: level, message: msg).
ast_grep(pattern: "log($MSG, $LEVEL)", language: "dart")Погляньте на знайдені збіги. Якщо частина з них — інший log із двома аргументами, звузьте шаблон, обмеживши path каталогом, якому належить потрібний вам, а не намагайтеся виразити цю різницю в самому шаблоні.
ast_edit( pattern: "log($MSG, $LEVEL)", rewrite: "log(level: $LEVEL, message: $MSG)", path: "lib/features/telemetry", language: "dart",)Потім resolve, а потім lsp(action: "diagnostics", file: "*").
Коли варто скористатися чимось іншим
Розділ із назвою «Коли варто скористатися чимось іншим»| Ситуація | Кращий інструмент |
|---|---|
| Перейменування символу | lsp_rename — він іде за посиланнями, а не за формами |
| Пошук рядка, коментаря чи назви файлу | search (вміст) / search_files (назви) |
| «Що зламається, якщо я це зміню?» | Граф коду |
| Мова, яку не покривають граматики | search разом з edit |
Пов’язане
Розділ із назвою «Пов’язане»- Інтелект коду — чому структурне зіставлення — це не regex із зайвими кроками
- Вбудовані інструменти агента — повний список аргументів
- Шукати код через граф коду — символи та вплив