Refactoriser le code structurellement
Ce guide vous montre comment amener un agent à effectuer un large changement mécanique — un codemod, une migration d’API en bloc, un motif dont vous voulez vous débarrasser — au moyen de la recherche et de la réécriture structurelles, le changement étant préparé pour révision avant que quoi que ce soit ne soit écrit.
ast_grep fait correspondre du code par sa forme. ast_edit le réécrit. resolve valide ou écarte.
Avant de commencer
Section intitulée “Avant de commencer”Les outils structurels couvrent les langages que le graphe de code analyse : Dart, JavaScript, TypeScript, TSX, PHP, Python, Rust, Zig, C, C++, Go, Java, Ruby, C#, Swift, Kotlin, R, Assembly, MATLAB et Ada. Ils ont besoin des grammaires tree-sitter installées, ce qui est toujours le cas dans une version livrée — une grammaire manquante arrête le serveur plutôt que de dégrader la recherche.
Trouver la forme d’abord
Section intitulée “Trouver la forme d’abord”Ne réécrivez jamais quelque chose que vous n’avez pas regardé. Demandez à l’agent de chercher :
Trouve chaque endroit où nous appelons
dispose()sur une valeur nullable dont nous venons de vérifier qu’elle n’est pas null.
ast_grep(pattern: "if ($X != null) $X.dispose()", language: "dart")La sortie donne file:line ainsi que la source correspondante pour chaque site.
Ce que la syntaxe des motifs vous apporte
Section intitulée “Ce que la syntaxe des motifs vous apporte”| Jeton | Correspond à |
|---|---|
$NAME |
N’importe quel nœud unique, capturé |
$$$NAME |
Toute suite d’éléments frères — arguments, instructions |
$_ |
N’importe quoi, capturé sans nom |
Le détail qui compte : un $NAME répété doit capturer le même texte.
if ($X != null) $X.dispose()trouve une garde qui dispose ce qu’elle a testé. Il ne trouve pas if (a != null) b.dispose() — qui est habituellement le bogue que vous cherchiez. Aucune recherche textuelle ne peut exprimer cela.
Quand le motif ne trouve rien
Section intitulée “Quand le motif ne trouve rien”Deux choses à essayer, dans l’ordre :
- Réduisez-le.
foo($X)avantif (a) { foo($X); }. Un motif qui n’est pas du code valide à lui seul n’est souvent pas la construction que vous vouliez. - Nommez le langage.
language:n’est déduit que lorsquepathdésigne un seul fichier.
Un motif qu’aucun contexte d’analyse n’accepte est signalé comme non analysable — l’outil le dit plutôt que de rapporter « aucune correspondance » pour quelque chose qui n’était jamais lu de la façon dont il en a l’air.
Réécrire, et regarder avant que le changement n’atterrisse
Section intitulée “Réécrire, et regarder avant que le changement n’atterrisse”ast_edit( pattern: "if ($X != null) $X.dispose()", rewrite: "$X?.dispose()", language: "dart",)Le résultat est une proposition :
(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.Rien n’est encore sur le disque. Lisez la liste, vérifiez quelques-uns des sites signalés par la recherche, puis :
resolve(edit_id: "edit_1", action: "accept")ou
resolve(edit_id: "edit_1", action: "discard")Vérifier votre travail avec le compilateur
Section intitulée “Vérifier votre travail avec le compilateur”L’intérêt de procéder structurellement, c’est l’ampleur. Les changements de grande ampleur sont exactement là où une réécriture d’apparence plausible casse un type. Demandez les diagnostics après avoir validé :
lsp(action: "diagnostics", file: "*")file: "*" lance le vérificateur du projet lui-même (dart analyze, tsc --noEmit, cargo check, go build) plutôt qu’une requête fichier par fichier — la bonne forme de question après un changement qui a touché quarante fichiers.
Exemple concret : migrer un appel
Section intitulée “Exemple concret : migrer un appel”Supposons que log(msg, level) devienne log(level: level, message: msg).
ast_grep(pattern: "log($MSG, $LEVEL)", language: "dart")Examinez les résultats. Si certains correspondent à un autre log à deux arguments, restreignez le motif en limitant path au répertoire qui contient celui que vous visez, plutôt que de tenter d’exprimer la différence dans le motif.
ast_edit( pattern: "log($MSG, $LEVEL)", rewrite: "log(level: $LEVEL, message: $MSG)", path: "lib/features/telemetry", language: "dart",)Puis resolve, puis lsp(action: "diagnostics", file: "*").
Quand utiliser autre chose
Section intitulée “Quand utiliser autre chose”| Situation | Meilleur outil |
|---|---|
| Renommer un symbole | lsp_rename — il suit les références, pas les formes |
| Trouver une chaîne, un commentaire ou un nom de fichier | search (contenu) / search_files (noms) |
| « Qu’est-ce qui casse si je change ceci ? » | Le graphe de code |
| Un langage que les grammaires ne couvrent pas | search et edit |
Voir aussi
Section intitulée “Voir aussi”- Intelligence du code — pourquoi la correspondance structurelle n’est pas une expression régulière avec des étapes en plus
- Outils d’agent intégrés — la liste complète des arguments
- Rechercher du code avec le graphe de code — le côté symboles et impact