Aller au contenu

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.

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.

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.

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.

Deux choses à essayer, dans l’ordre :

  1. Réduisez-le. foo($X) avant if (a) { foo($X); }. Un motif qui n’est pas du code valide à lui seul n’est souvent pas la construction que vous vouliez.
  2. Nommez le langage. language: n’est déduit que lorsque path dé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")

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.

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: "*").

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