Ir al contenido

Refactorizar código de forma estructural

Esta guía te muestra cómo conseguir que un agente haga un cambio mecánico y extenso —un codemod, una migración masiva de API, un patrón que quieres eliminar— mediante búsqueda y reescritura estructurales, con el cambio preparado para su revisión antes de que se escriba nada.

ast_grep encuentra código por su forma. ast_edit lo reescribe. resolve lo confirma o lo descarta.

Las herramientas estructurales cubren los lenguajes que analiza el grafo de código: Dart, JavaScript, TypeScript, TSX, PHP, Python, Rust, Zig, C, C++, Go, Java, Ruby, C#, Swift, Kotlin, R, Assembly, MATLAB y Ada. Necesitan que las gramáticas de tree-sitter estén preparadas, y en una compilación oficial siempre lo están: una gramática faltante detiene al servidor en lugar de degradar la búsqueda.

No reescribas nunca algo que no hayas mirado. Pídele al agente que busque:

Encuentra todos los lugares donde llamamos a dispose() sobre algo que puede ser nulo y del que acabamos de comprobar que no lo es.

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

La salida es file:line más el código fuente coincidente de cada sitio.

Token Coincide con
$NAME Cualquier nodo individual, capturado
$$$NAME Cualquier secuencia de hermanos: argumentos, sentencias
$_ Cualquier cosa, capturada sin nombre

La que importa: un $NAME repetido debe capturar el mismo texto.

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

encuentra una guarda que libera lo que comprobó. No encuentra if (a != null) b.dispose(), que suele ser el error que buscabas. Ninguna búsqueda textual puede expresar eso.

Dos cosas que probar, en este orden:

  1. Hazlo más pequeño. foo($X) antes que if (a) { foo($X); }. Un patrón que por sí solo no es código válido a menudo no es la construcción que pretendías.
  2. Indica el lenguaje. language: solo se infiere cuando path nombra un único archivo.

Un patrón que ningún contexto de análisis acepta se reporta como no analizable: la herramienta lo dice, en lugar de reportar «ninguna coincidencia» para algo que nunca se estaba leyendo como parece.

Reescribe, y míralo antes de que se aplique

Sección titulada «Reescribe, y míralo antes de que se aplique»
ast_edit(
pattern: "if ($X != null) $X.dispose()",
rewrite: "$X?.dispose()",
language: "dart",
)

El resultado es una propuesta:

(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.

Todavía no hay nada en disco. Lee la lista, comprueba un par de los sitios que reportó la búsqueda y, después:

resolve(edit_id: "edit_1", action: "accept")

o

resolve(edit_id: "edit_1", action: "discard")

El punto de hacerlo de forma estructural es que abarca mucho. Los cambios extensos son justo donde una reescritura que parece plausible rompe un tipo. Pide los diagnósticos después de confirmar:

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

file: "*" ejecuta el comprobador del propio proyecto (dart analyze, tsc --noEmit, cargo check, go build) en lugar de una consulta por archivo: la forma correcta de pregunta después de un cambio que tocó cuarenta archivos.

Supongamos que log(msg, level) se está convirtiendo en log(level: level, message: msg).

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

Mira las coincidencias. Si algunas son un log distinto de dos argumentos, acota el patrón limitando path al directorio al que pertenece el que te interesa, en lugar de intentar expresar la diferencia en el patrón.

ast_edit(
pattern: "log($MSG, $LEVEL)",
rewrite: "log(level: $LEVEL, message: $MSG)",
path: "lib/features/telemetry",
language: "dart",
)

Después resolve, y luego lsp(action: "diagnostics", file: "*").

Situación Herramienta más adecuada
Cambiar el nombre de un símbolo lsp_rename: sigue las referencias, no las formas
Encontrar una cadena, un comentario o un nombre de archivo search (contenido) / search_files (nombres)
«¿Qué se rompe si cambio esto?» El grafo de código
Un lenguaje que las gramáticas no cubren search más edit