Aller au contenu

Refactoriser le code structurellement

Ce guide montre comment amener un agent à effectuer une modification mécanique de grande ampleur — un codemod, une migration d’API en masse, un motif dont vous voulez vous débarrasser — au moyen de la recherche et de la réécriture structurelles, la modification étant préparée pour examen 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 abandonne.

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 que les grammaires tree-sitter soient préparées, ce qui est toujours le cas dans une version livrée — une grammaire manquante empêche le serveur de démarrer 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 un nullable dont nous venons de tester la nullité.

ast_grep(pattern: "if ($X != null) $X.dispose()", language: "dart")

La sortie est file:line plus le source correspondant pour chaque emplacement.

Jeton Correspond à
$NAME N’importe quel nœud unique, capturé
$$$NAME N’importe quelle suite de frères — arguments, instructions
$_ N’importe quoi, capturé sans nom

Celui qui compte : un $NAME répété doit capturer le même texte.

if ($X != null) $X.dispose()

trouve une garde qui libère ce qu’elle a testé. Il ne trouve pas if (a != null) b.dispose() — qui est généralement le bug 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 « aucun résultat » pour quelque chose qui n’a jamais été lu comme il en a l’air.

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 deux ou trois des emplacements 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 modifications de grande ampleur sont exactement là où une réécriture d’apparence plausible casse un type. Demandez les diagnostics après la validation :

lsp(action: "diagnostics", file: "*")

file: "*" exécute le vérificateur propre au projet (dart analyze, tsc --noEmit, cargo check, go build) plutôt qu’une requête fichier par fichier — la bonne forme de question après une modification qui a touché quarante fichiers.

Supposons que log(msg, level) devienne log(level: level, message: msg).

ast_grep(pattern: "log($MSG, $LEVEL)", language: "dart")

Regardez les occurrences. Si certaines correspondent à un log à deux arguments différent, restreignez le motif en limitant path au répertoire auquel appartient celui que vous visez, plutôt que d’essayer 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 que ça casse si je modifie ceci ? » Le graphe de code
Un langage que les grammaires ne couvrent pas search plus edit