Рефакторить код структурно
Это руководство показывает, как заставить агента выполнить широкое механическое изменение — кодмод, массовую миграцию 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 |
См. также
Заголовок раздела «См. также»- Интеллект кода — почему структурное сопоставление — не регулярное выражение с лишними шагами
- Встроенные инструменты агента — полный список аргументов
- Искать код через граф кода — сторона символов и влияния