Ejercicio 5: Modificación de gofmt - Indentación y Transformación del AST
📖 ¿Quieres aprender más? Lee The Parser en Internals for Interns para profundizar en cómo Go construye y trabaja con los Árboles de Sintaxis Abstracta (AST).
En este ejercicio, modificarás la herramienta de formateo de Go gofmt para que use 4 espacios en lugar de tabulaciones, y luego añadirás una transformación personalizada del AST para reemplazar automáticamente la palabra “hello” por “helo” en cadenas de texto y comentarios. Esto te enseñará cómo funciona el formateador de Go, cómo los modos del printer controlan la indentación y cómo añadir transformaciones personalizadas al pipeline de procesamiento del AST.
Objetivos de Aprendizaje
Al finalizar este ejercicio, serás capaz de:
- Entender cómo gofmt controla la indentación y los modos del printer
- Aprender a modificar el comportamiento de formateo en gofmt y el paquete go/format
- Entender cómo gofmt procesa el código fuente de Go mediante la manipulación del AST
- Saber cómo modificar cadenas de texto y comentarios en el AST
- Explorar la estructura del AST (Abstract Syntax Tree) de Go
- Crear transformaciones de código fuente personalizadas
Introducción: ¿Qué es un AST?
Un Árbol de Sintaxis Abstracta (AST) es una representación en forma de árbol de tu código fuente donde cada nodo representa una construcción del lenguaje — funciones, declaraciones, expresiones, sentencias. El árbol captura las relaciones jerárquicas: un nodo de función contiene nodos de sentencias, que contienen nodos de expresiones, y así sucesivamente.
El parser (cubierto en los ejercicios 2-3) construye este árbol a partir del flujo de tokens. Pero el AST no solo lo usa el compilador — herramientas como gofmt, goimports y go vet también parsean código a un AST, lo manipulan y lo imprimen de vuelta.
Go expone su AST a través del paquete go/ast (en src/go/), que es independiente del AST interno del compilador. Este paquete público proporciona tipos como *ast.File (un archivo fuente completo), *ast.FuncDecl (una declaración de función), *ast.BasicLit (un literal como una cadena o número), y *ast.Comment. La función ast.Inspect() te permite recorrer todo el árbol, visitando cada nodo — que es exactamente lo que usaremos para encontrar y modificar cadenas de texto y comentarios.
Contexto: Cómo Funciona gofmt
gofmt opera a través de estas etapas:
- Parsear → Convertir el código fuente a AST (Abstract Syntax Tree)
- Transformar → Aplicar reglas de formateo al AST
- Imprimir → Convertir el AST modificado de vuelta a código fuente formateado con la indentación específica
El comportamiento de la indentación está controlado por dos constantes clave:
tabWidth→ Ancho de la indentación (por defecto: 8)printerMode→ Flags que controlan el comportamiento del espaciado:printer.UseSpaces→ Usar espacios para el rellenoprinter.TabIndent→ Usar tabulaciones para la indentaciónprinterNormalizeNumbers→ Normalizar literales numéricos
Estructura del AST
Go representa el código fuente como un árbol de nodos. Vamos a usar estos dos nodos:
*ast.BasicLit→ Cadenas de texto, números, etc.*ast.Comment→ Comentarios en el código fuente
Paso 1: Navegar al Código Fuente de gofmt
cd go/src/cmd/gofmt
ls -la
Archivos clave:
gofmt.go→ Lógica principal del programa y procesamiento de archivossimplify.go→ Transformaciones de simplificación del AST
Paso 2: Cambiar la Indentación a 4 Espacios
Antes de añadir transformaciones personalizadas, cambiemos gofmt para que use 4 espacios en lugar de tabulaciones para la indentación.
Modificar gofmt.go
Edita go/src/cmd/gofmt/gofmt.go:
Busca las constantes alrededor de la línea 50 (busca el comentario “Keep these in sync with go/format/format.go”):
const (
tabWidth = 8
printerMode = printer.UseSpaces | printer.TabIndent | printerNormalizeNumbers
Cambia a:
const (
tabWidth = 4
printerMode = printer.UseSpaces | printerNormalizeNumbers
Qué cambió:
tabWidth: Cambiado de8a4(4 espacios por nivel de indentación)printerMode: Eliminado el flagprinter.TabIndent(esto elimina los caracteres de tabulación y usa solo espacios)
Modificar el Paquete go/format
El paquete go/format también necesita actualizarse para mantener el comportamiento consistente.
Edita go/src/go/format/format.go:
Busca las constantes alrededor de la línea 29 (mismo comentario que arriba):
const (
tabWidth = 8
printerMode = printer.UseSpaces | printer.TabIndent | printerNormalizeNumbers
Cambia a:
const (
tabWidth = 4
printerMode = printer.UseSpaces | printerNormalizeNumbers
Entendiendo los Cambios
tabWidth = 4: Cada nivel de indentación usa 4 espacios- Eliminar
TabIndent: Sin este flag, el printer usa solo espacios (sin caracteres de tabulación) UseSpaces: Asegura que se usen espacios para el relleno y la alineación- Ambos archivos deben coincidir: gofmt y go/format deben usar la misma configuración para ser consistentes
Paso 3: Recompilar y Probar la Indentación
cd ../../../ # back to go/src
./make.bash
Crea un archivo de prueba indent_test.go:
package main
import "fmt"
func main() {
if true {
for i := 0; i < 10; i++ {
fmt.Println(i)
}
}
}
Prueba la nueva indentación:
cd .. # to go/ directory
./bin/gofmt indent_test.go
Salida esperada (observa los 4 espacios en cada nivel):
package main
import "fmt"
func main() {
if true {
for i := 0; i < 10; i++ {
fmt.Println(i)
}
}
}
Cada nivel de indentación ahora usa 4 espacios en lugar de tabulaciones.
Paso 4: Añadir la Transformación Hello→Helo
Edita gofmt.go:
Añade esta función de transformación alrededor de la línea 76 (después de la función usage()):
// transformHelloToHelo walks the AST and replaces "hello" with "helo"
// in string literals and comments.
func transformHelloToHelo(file *ast.File) {
ast.Inspect(file, func(n ast.Node) bool {
switch node := n.(type) {
case *ast.BasicLit:
// Handle string literals
if node.Kind == token.STRING {
if strings.Contains(node.Value, "hello") {
node.Value = strings.ReplaceAll(node.Value, "hello", "helo")
}
}
case *ast.Comment:
// Handle comments
if strings.Contains(node.Text, "hello") {
node.Text = strings.ReplaceAll(node.Text, "hello", "helo")
}
}
return true // continue traversing
})
}
Entendiendo el Código
ast.Inspect()- Recorre todos los nodos del AST*ast.BasicLit- Coincide con literales de cadena de textonode.Kind == token.STRING- Verifica que sea una cadena de texto (no un número)*ast.Comment- Coincide con comentariosstrings.ReplaceAll()- Realiza el reemplazo
Paso 5: Integrar la Transformación
Todavía en gofmt.go:
Busca la función processFile alrededor de la línea 238. Busca el bloque if *simplifyAST alrededor de la línea 263:
if *simplifyAST {
simplify(file)
}
Añade nuestra transformación justo después:
if *simplifyAST {
simplify(file)
}
// Apply our custom hello→helo transformation
transformHelloToHelo(file)
Paso 6: Recompilar gofmt
cd ../../../ # back to go/src
./make.bash
Paso 7: Probar Ambas Modificaciones Juntas
Crea un archivo hello_test.go:
package main
import "fmt"
func main() {
// Say hello to everyone
message := "hello world"
greeting := "Say hello!"
/* This is a hello comment block */
fmt.Println(message)
fmt.Println(greeting)
// Another hello comment
fmt.Printf("hello %s\n", "Go")
}
../go/bin/gofmt hello_test.go
Salida esperada (observa tanto la indentación de 4 espacios COMO la transformación hello→helo):
package main
import "fmt"
func main() {
// Say helo to everyone
message := "helo world"
greeting := "Say helo!"
/* This is a helo comment block */
fmt.Println(message)
fmt.Println(greeting)
// Another helo comment
fmt.Printf("helo %s\n", "Go")
}
Se aplicaron dos cambios:
- Todas las instancias de “hello” se reemplazaron por “helo”
- La indentación usa 4 espacios en lugar de tabulaciones
Paso 8: Probar el Formateo In-Place
# Format and overwrite the file
../go/bin/gofmt -w hello_test.go
# Verify the changes
cat hello_test.go
¡El archivo ahora está permanentemente transformado con “helo” en lugar de “hello” y usando indentación de 4 espacios!
Qué Hicimos
- Modificamos la Configuración del Printer: Cambiamos tabWidth y printerMode para usar 4 espacios
- Sincronizamos Dos Paquetes: Actualizamos tanto gofmt como go/format para mantener la consistencia
- Añadimos un Visitante del AST: Creamos una función para recorrer y modificar los nodos del AST
- Coincidencia de Patrones: Identificamos cadenas de texto y comentarios
- Reemplazo de Texto: Modificamos los valores de los nodos para reemplazar “hello” por “helo”
- Integración: Llamamos a la transformación durante el procesamiento de gofmt
- Pruebas: Verificamos los cambios tanto de indentación como de transformación
Lo que Aprendimos
- Configuración del Printer: Cómo gofmt controla la indentación mediante tabWidth y printerMode
- Consistencia entre Paquetes: Por qué gofmt y go/format deben mantenerse sincronizados
- Manipulación del AST: Cómo recorrer y modificar el Abstract Syntax Tree de Go
- Modificación de Herramientas: Cómo extender herramientas existentes de Go con múltiples cambios
- Transformación de Código: Implementar cambios sistemáticos en el código fuente
- Proceso de Compilación: Recompilar componentes de la cadena de herramientas de Go
- Pruebas: Verificar el comportamiento de herramientas personalizadas
Ideas de Extensión
Prueba estas modificaciones adicionales:
- Añadir un flag de línea de comandos para activar/desactivar la transformación
- Soportar múltiples reemplazos de palabras (hello→helo, world→universe)
- Añadir opción de sensibilidad a mayúsculas/minúsculas
- Reemplazar solo palabras completas (no subcadenas dentro de palabras)
- Hacer tabWidth configurable mediante un flag de línea de comandos
- Añadir opción para alternar entre tabulaciones y espacios
Ejemplo de adición de flag:
var replaceHello = flag.Bool("helo", false, "replace hello with helo")
// In processFile():
if *replaceHello {
transformHelloToHelo(file)
}
Limpieza
Para restaurar el gofmt original:
cd go/src/cmd/gofmt
git checkout gofmt.go
cd ../go/format
git checkout format.go
cd ../../../src
./make.bash
Resumen
¡Has modificado gofmt con éxito de dos formas poderosas!
Indentación: tabulaciones (ancho 8) → 4 espacios
Transformación: "hello world" → "helo world"
// Say hello → // Say helo
Cambios: tabWidth=4 + eliminar flag TabIndent
+ ast.Inspect() → coincidencia de patrones → reemplazar texto
Ahora entiendes cómo herramientas como gofmt, goimports y go fix funcionan tanto a nivel del printer como del AST.
Continúa con el Ejercicio 6 o vuelve al taller principal