


Modificar un compilador que genera JavaScript y su sistema de compilación, probando cada capa
Para cuando una IA cambia un monorepo de compilador que genera JavaScript. Compila con el ejecutor de tareas, no toca las definiciones congeladas del árbol sintáctico, prueba cada capa modificada, lleva las nuevas opciones hasta el final y mide el rendimiento en vez de seguir al linter.
Para usar estas reglas de IA
## Compila con el ejecutor de tareas, sin saltarte niveles
Usa el ejecutor de tareas del repositorio en lugar de invocar a mano las herramientas de compilación subyacentes. El objetivo por defecto compila la cadena de herramientas de la plataforma; hay objetivos separados que añaden la biblioteca estándar, ejecutan las suites de pruebas, formatean el código y comprueban el formato sin reescribirlo.
### Los objetivos se apoyan unos en otros
La instalación de dependencias se ejecuta automáticamente para cualquier objetivo que necesite el instrumental de JavaScript. La compilación por defecto produce los binarios de la cadena de herramientas —tanto los ejecutables del compilador como el binario separado del sistema de compilación— y los copia en el directorio del paquete específico de la plataforma. El objetivo de la biblioteca estándar consume esas salidas de la cadena de herramientas para compilar las fuentes del runtime. Los objetivos de pruebas reutilizan todo lo que está por debajo de ellos.
### No te saltes ningún nivel
Como cada objetivo depende del que tiene debajo, invocar directamente una herramienta de nivel inferior puede dejar binarios obsoletos en su sitio y producir resultados que no coinciden con una compilación limpia. Cuando un resultado de compilación parezca imposible, ejecuta el objetivo de limpieza y recompila desde arriba antes de seguir investigando.
## No toques el árbol de sintaxis congelado; actualiza los módulos de conversión
Algunos archivos están deliberadamente congelados porque las extensiones de análisis sintáctico externas dependen de su forma exacta. Nunca modifiques la versión congelada de la definición del árbol de sintaxis. Cuando cambie el árbol de sintaxis vivo, actualiza en su lugar los dos módulos de correspondencia que convierten entre la representación viva y la congelada, para que las extensiones sigan funcionando mientras el árbol principal queda libre para evolucionar.
### La compatibilidad con el fork original no es una restricción
El compilador empezó como un fork de otro compilador, pero el proyecto mantiene su propio árbol de sintaxis y puede introducir cambios incompatibles respecto al original. Decide según lo que mejor sirva a este objetivo de compilación, no según lo que haga el proyecto original.
### Evita reservar nuevas palabras clave
Prefiere una implementación que no requiera una nueva palabra reservada, ya que reservar una rompe el código existente que ya usa ese nombre. Trata las nuevas palabras clave como último recurso y busca primero una sintaxis que reutilice construcciones existentes.
## Corrige la causa en vez de silenciar avisos o usar comodines provisionales
Nunca uses un atributo de supresión de avisos para callar al compilador. Corrige el problema de fondo en su lugar: un aviso suprimido oculta exactamente la clase de error que ese aviso existe para detectar.
### Los comodines son una decisión, no un atajo
No uses patrones comodín como marcadores de posición para nuevas funcionalidades del lenguaje que luego quedan olvidadas. Usa uno solo cuando tengas la certeza de que el valor debe ignorarse en ese caso concreto, y confirma que cada nueva funcionalidad del lenguaje se maneja por completo en todas las capas del compilador.
### Descarta resultados de forma explícita y propaga el estado
Si una función se llama solo por sus efectos secundarios, usa la función de ignorar explícita en lugar de ligar el resultado a un comodín. Nunca descartes un resultado con estado: propágalo a lo largo de la cadena de llamadas.
### Pequeñas convenciones que mantienen honestos los patrones
No añadas un parámetro unitario final a una función que ya tiene un argumento etiquetado obligatorio; la etiqueta ya impide la aplicación parcial accidental. Omite el comodín final en los patrones de registro cuando el aviso al que apunta está desactivado en el código base. Vigila los constructores con nombres parecidos en distintas representaciones intermedias, porque el mismo nombre de variante puede significar cosas diferentes en distintas fases de compilación.
## Averigua qué capa del compilador cambias antes de editar
La tubería convierte el texto fuente en un árbol de sintaxis de superficie en el analizador sintáctico, aplica transformaciones de frontend, produce un árbol tipado en el verificador de tipos, lo baja a una representación intermedia de nivel lambda, la convierte en una representación intermedia de nivel JavaScript y, por último, emite el código. Identifica la capa que estás editando antes de tocar nada.
### Las capas se corresponden con directorios
El directorio de sintaxis es dueño del análisis sintáctico y de la sintaxis de superficie. El directorio de frontend es dueño de las transformaciones del árbol de sintaxis y del procesamiento de la interfaz de funciones externas. El directorio del verificador de tipos es dueño de la verificación de tipos y de las transformaciones del árbol tipado. El directorio core contiene tanto los módulos de nivel lambda como los de nivel JavaScript, distinguidos por el prefijo del archivo. Hay directorios separados para las utilidades extendidas y las estructuras de datos, para la generación de definiciones de tipos del ecosistema de destino y para el servidor de lenguaje y el instrumental.
### Los cambios suelen cruzar capas
Una nueva funcionalidad del lenguaje normalmente requiere trabajo en el analizador sintáctico, actualizaciones de la definición del árbol de sintaxis, soporte en la verificación de tipos, tratamiento a nivel lambda, generación de JavaScript y pruebas, en ese orden. Saltarse una capa produce una funcionalidad que se analiza sin problemas y luego compila mal.
## Depura imprimiendo la representación intermedia de cada etapa
El binario del compilador acepta opciones que imprimen cada etapa: el código fuente preprocesado, el árbol de análisis tras el análisis sintáctico, el árbol tipado tras la verificación de tipos y la representación lambda sin optimizar. Compara el volcado de cada etapa con lo que esperas antes de ponerte a leer el código fuente del compilador.
### Encamina el síntoma hacia la capa que lo posee
Los problemas de formato de salida corresponden al módulo de impresión formateada. Los errores de tipos corresponden a los módulos del verificador de tipos. Los resultados erróneos que solo aparecen tras la optimización corresponden a las pasadas de análisis de nivel lambda. El código emitido mal formado corresponde a los módulos de generación de JavaScript.
### Reproduce en pequeño
Compila un único archivo mínimo en lugar del proyecto entero mientras iteras. Añade impresiones temporales en el módulo impresor de lambda cuando haya que inspeccionar una representación, y elimina esa instrumentación antes de que el cambio se integre.
## Añade pruebas en cada capa en cuanto la cambies
Añade pruebas en cuanto modifiques cada capa del compilador, no después de completar todos los cambios. Probar tarde oculta qué capa introdujo la regresión y convierte una bisección pequeña en una investigación de todo el cambio.
### Casos que siempre necesitan una prueba
Las nuevas funcionalidades del lenguaje y las correcciones de errores siempre requieren pruebas. Modificar una pasada de análisis o cambiar la generación de JavaScript siempre requiere pruebas. Inspecciona siempre la calidad del JavaScript emitido, no solo su corrección.
### Cubre las suites correctas
Las pruebas del analizador sintáctico validan la sintaxis. Las pruebas de integración cubren el comportamiento de punta a punta. Las pruebas unitarias cubren funciones individuales del compilador. Las pruebas de compilación cubren casos de error y casos límite, incluidos los errores de tipos en un solo archivo y los errores entre módulos que requieren archivos de implementación e interfaz separados. Las suites de ida y vuelta, de tipos generados, de análisis y de instrumental tienen cada una su propio objetivo y deben ejecutarse cuando cambie el área correspondiente.
## Actualiza el catálogo de errores y avisos en el mismo cambio
El repositorio mantiene un catálogo por módulo de todos los errores y avisos que el compilador puede emitir, con cada entrada asociada o bien a un fixture o bien a una razón documentada de por qué es inalcanzable. El catálogo es la herramienta principal para encontrar huecos de cobertura y candidatos a eliminación de código muerto, así que las entradas obsoletas dificultan ambas tareas.
### Actualiza el catálogo en el mismo cambio que el código
Cuando añadas o elimines una variante de error, añade o elimina su fila en la sección del módulo correspondiente, fija su estado a cubierta, inalcanzable o pendiente, y enlaza el fixture que la cubre o registra por qué la variante no puede alcanzarse. Cuando añadas o elimines un fixture, actualiza la ruta del fixture y el estado de la fila afectada para que el catálogo y la suite de pruebas nunca se desvíen entre sí.
## Mantén rápida la compilación y legible el JavaScript generado
El compilador está diseñado para ciclos de retroalimentación rápidos y tiene que escalar a bases de código grandes, y su salida generada la leen personas. Ambas propiedades son criterios de revisión, no algo secundario.
### Mantén legible el código emitido
Evita emitir símbolos generados sin significado y mantén la salida legible. Un cambio que produce una salida correcta pero ilegible no está terminado.
### Mide antes y después
Considera el impacto de cada cambio en la velocidad de compilación, usa las pasadas de optimización adecuadas en los niveles lambda y JavaScript, y perfila antes y después de cualquier cambio motivado por el rendimiento en lugar de razonarlo a partir del código.
## Sigue las normas de nombres y anota en el changelog los cambios visibles
Ajústate a la convención de nomenclatura del lenguaje en el que escribes: el lenguaje de implementación del compilador usa snake case, mientras que las fuentes escritas en el propio lenguaje compilado usan camel case. Sigue los patrones existentes del código base, prefiere las funciones de utilidad existentes antes que reinventarlas, comenta los algoritmos complejos y la lógica no evidente, y preserva la compatibilidad hacia atrás cuando sea razonable hacerlo.
### Los commits conllevan obligaciones
Firma los commits en la forma que el proyecto exige, incluye las pruebas adecuadas con cada cambio y confirma que la compilación pasa antes de hacer commit.
### Registra los cambios visibles para el usuario
Añade una entrada al registro de cambios para cualquier cambio visible para el usuario, ya sea una corrección de error, una funcionalidad o un cambio incompatible. Ponla bajo la sección correspondiente de la versión no publicada actual y termina la línea con un enlace a la contribución. Se espera que las aportaciones incluyan una.
## Lleva cada nueva opción del sistema de compilación por todos los puntos que recorre
El sistema de compilación acompañante, escrito en un lenguaje de sistemas, proporciona compilaciones incrementales rápidas y mejores informes de errores. Sus módulos se dividen con claridad: estructuras de datos centrales como el estado de compilación y los registros de módulos, la compilación y la generación de argumentos del compilador, la generación del árbol de sintaxis y los argumentos del analizador sintáctico, el descubrimiento de paquetes y la resolución de dependencias, el grafo de dependencias entre módulos y la detección de ciclos, la limpieza de artefactos, el registro de la compilación y los informes de errores, la interfaz de línea de comandos, el análisis de la configuración del proyecto, el vigilante de archivos y el punto de entrada.
### Añade la opción en todos los sitios por los que viaja
Declara la opción tanto en el tipo de argumentos de compilación como en el de vigilancia y actualiza la conversión entre ambos. Extiende el analizador de configuración para cualquier campo nuevo de la configuración del proyecto. Modifica los módulos de compilación pertinentes. Después propaga el parámetro por toda la cadena, desde el punto de entrada hasta la función de compilación, hasta la inicialización de la compilación y hasta el estado de compilación, y también a lo largo de la ruta de vigilancia. Añade pruebas unitarias para el nuevo comportamiento.
### Resuelve de antemano la precedencia y el manejo de errores
Las opciones de línea de comandos tienen prioridad sobre el archivo de configuración del proyecto. Propaga los errores mediante el tipo de resultado estándar del proyecto que envuelve errores en lugar de provocar un pánico, y usa registro de nivel depuración para los diagnósticos de desarrollo.
## Ejecuta el sistema de compilación directamente, apuntando al compilador local
Ejecuta los propios comandos de la cadena de herramientas del sistema de compilación desde la raíz del repositorio apuntando a su archivo de manifiesto, tal como hace la integración continua, en lugar de cambiar de directorio al subproyecto. El ejecutor de tareas también expone atajos para compilarlo y para ejecutar sus pruebas de integración.
### Apunta el binario de desarrollo a la cadena de herramientas local
Si lo ejecutas directamente, el binario tomará en su lugar una versión publicada instalada. Exporta las variables de entorno para la ruta del ejecutable del compilador y para la ruta del runtime, ambas resueltas a rutas absolutas dentro de la copia de trabajo actual, antes de ejecutarlo. Ten en cuenta que el binario de desarrollo puede llevar el nombre del paquete en lugar del nombre de la herramienta, así que comprueba el nombre del archivo producido en vez de darlo por supuesto.
### Sube la verbosidad cuando el comportamiento no esté claro
El sistema de compilación acepta opciones de verbosidad creciente para el registro de nivel información y de nivel depuración. Así es como ves detalles como qué carpetas está vigilando realmente una sesión de vigilancia.
### El trazado sigue la especificación estándar
El trazado distribuido se configura por completo mediante las variables de entorno estándar de telemetría —punto final del recolector, sobrescritura del punto final específico de trazas, cabeceras del exportador, nombre del servicio, atributos del recurso y nivel de registro— sin ajustes específicos del proyecto. Lo que habilita la telemetría es establecer un punto final; si no hay ninguno establecido, el trazado no hace nada. Emite los eventos mediante las macros de trazado en lugar de las macros de registro normales cuando quieras que aparezcan en las trazas, porque ambas usan sistemas separados.
## Haz que las pruebas de CI esperen una condición, no un tiempo fijo
Los ejecutores de integración continua son más lentos que las máquinas de desarrollo, lo que hace frágiles las esperas fijas. Sondea la condición que de verdad te importa, como esperar a que aparezca un archivo, en lugar de dormir durante una duración adivinada.
### El apagado asíncrono no es finalización
Un ayudante que indica a un vigilante que salga —por ejemplo eliminando un archivo de bloqueo— solo envía la señal; no espera a que el proceso termine. Evita disparar eventos de cambio de configuración justo antes de salir, porque el vigilante puede iniciar una recompilación concurrente que no esperabas.
### No improvises ediciones de texto específicas de la plataforma
La edición de flujos in situ se comporta de forma distinta según el sistema operativo, así que usa los ayudantes de sustitución y normalización de rutas del repositorio en lugar de invocar editores en crudo en las pruebas de shell. Un script de integración individual puede ejecutarse por sí solo exportando las rutas del ejecutable y del runtime, cargando el archivo de ayudantes compartido e invocando ese único script, lo cual es útil para iterar sin ejecutar la suite completa.
## Juzga los consejos del linter según el rendimiento real
Las sugerencias del linter son una entrada, no un veredicto. Cuando una refactorización sugerida pueda perjudicar el rendimiento o la claridad, valora la compensación de forma explícita en lugar de aplicar la sugerencia mecánicamente.
### Acepta complejidad solo donde compense
Una estructura de parámetros no es automáticamente mejor que varios argumentos; permite localmente la regla del linter en funciones que legítimamente necesitan muchos parámetros y donde una estructura solo añadiría ceremonia. Una copia es aceptable cuando es pequeña, se hace una sola vez, la exigen las reglas de propiedad y queda fuera de una ruta caliente: no te sobreingenies para evitarla.
### Optimiza sobre evidencias
Perfila antes de optimizar. La mayoría de las preocupaciones de rendimiento en un sistema de compilación son despreciables frente al tiempo real de compilación. Donde la eficiencia sí importa, mantén los tipos de los parámetros coherentes a lo largo de una cadena de llamadas en lugar de convertir entre tipos de cadena en propiedad y prestados en cada frontera, ya que esas conversiones añaden reservas de memoria sin ningún beneficio. Recompila solo los módulos sucios, paraleliza el trabajo independiente, vigila la huella de memoria del estado de compilación en proyectos grandes y minimiza las operaciones del sistema de archivos.
