ADR 0090 — Más lenguajes que TypeScript y JavaScript
This content is not available in your language yet.
Estado: aceptado · Fecha: 2026-10-28
Contexto
Sección titulada «Contexto»La v1 se declaró TypeScript/JavaScript: el recorrido busca esas extensiones (extensions.ts),
el análisis pide un parser y languageForPath descarta lo que no conoce como lenguaje de la lente de
migración (ADR 0016). Un repositorio Java, Python, Go o C# se ve hoy
casi vacío: sus ficheros no existen para el mapa, no hay aristas y no hay complejidad. Rust es la excepción
parcial —se mide LOC/RLOC a mano, sin árbol—, y el resto no entra.
El coste de «soportar un lenguaje» tiene cuatro capas, y conviene no confundirlas:
| Capa | Qué hace hoy | Qué hace falta |
|---|---|---|
| Recorrido | solo extensiones de TS/JS | sumar las de cada lenguaje |
| Parseo | tree-sitter, gramática nativa y .wasm | una gramática y un analizador por lenguaje |
| Modelo | CcMigrationLanguage es de la migración | nada: el fichero es un nodo como los demás |
| Grafo | resolución de imports de TS/JS | una resolución por lenguaje |
La lente de migración (ADR 0016) modela una migración: de JS/TS a
Rust, con estados js, rust, bridge y native. No es «el lenguaje de cada fichero» y no debe convertirse
en eso: un status: "rust" sobre un .java sería una mentira.
Decisión
Sección titulada «Decisión»1. Cuatro lenguajes, con el análisis entero
Sección titulada «1. Cuatro lenguajes, con el análisis entero»Entran Java, Python, Go y C#. Para cada uno: métricas del fichero (loc, rloc, functions,
maxComplexity, maxFunctionLength), símbolos con su complejidad, miembros de clase donde el lenguaje los
tenga, e imports resueltos que se convierten en aristas del grafo. Es lo que hace útil el mapa: sin
aristas solo hay cajas con líneas.
Los lenguajes que quedan fuera de esta hornada —Kotlin, PHP, Ruby, C/C++, y Rust con árbol— no cambian de estado: siguen como están y no se anuncian como soportados.
2. El snapshot no cambia
Sección titulada «2. El snapshot no cambia»Un fichero de estos lenguajes es un nodo como los demás: métricas en la lente de métricas, símbolos en la
de símbolos, estructura donde la haya, aristas en la de dependencias. No hay un campo language nuevo en
cc.json, y no lo hay porque nadie lo necesita todavía: quien quiera saber el lenguaje lo deriva de la
extensión, como se hace hoy. Así el formato 2.x sigue siendo interoperable y un consumidor viejo ve ficheros
con métricas, que es lo que ya sabe leer.
La lente de migración no se toca: sigue teniendo solo ts, js, rust, wasm y node, y sus estados
siguen contando la migración a Rust. Un fichero Java no tiene nodo de migración, ni deuda, ni estado.
3. El parser se elige por extensión
Sección titulada «3. El parser se elige por extensión»El análisis deja de recibir un parser y recibe la lista de los que sabe usar; cada uno declara sus
extensiones y parserForPath decide. El recorrido recoge la unión de todas, y un fichero sin parser se
descarta como hoy.
La caché de parseo se firma por entrada: cada una guarda el nombre del parser que la produjo, y solo se reutiliza si coincide con el que le tocaría a esa ruta. El fichero de caché sube a formato 2; una caché de formato 1 se ignora entera —y se parsea—, que es la salida segura de siempre (ADR 0089). Un cambio de gramática o de analizador se resuelve subiendo el nombre o la versión del parser, no la caché a ciegas.
Invariante que no se mueve: un repositorio solo de TS/JS produce el mismo cc.json, byte a byte, con
y sin caché, antes y después de esta decisión. Añadir lenguajes no puede mover un número de lo que ya había.
4. Cada lenguaje resuelve sus imports a su manera
Sección titulada «4. Cada lenguaje resuelve sus imports a su manera»| Lenguaje | Se escribe | Se resuelve a |
|---|---|---|
| Java | import a.b.C; · import static a.b.C.m; | el fichero a/b/C.java, buscado por sufijo de ruta |
| Python | import a.b · from a.b import x · from . import x | a/b.py o a/b/__init__.py; con un nombre, primero a/b/x.py. Los relativos, desde el paquete del fichero |
| Go | import "mod/x/y" | el paquete: todos los .go de esa carpeta, con el prefijo del go.mod |
| C# | using A.B; | los ficheros que declaran namespace A.B |
- Java no lee el
pom.xmlni elbuild.gradle: buscar el sufijoa/b/C.javaresuelve igual las raíces de Maven y Gradle (src/main/java,src/test/java) y un proyecto plano. Los imports con comodín (a.b.*) no crean aristas; losstaticresuelven la clase, que es la parte que se puede nombrar. - Python resuelve contra la raíz y por sufijo, así que también vale un
src/. Un nombre defrom a import bpuede ser un submódulo (a/b.py) o un nombre del módulo (a/__init__.py): se prueban los dos en ese orden. La biblioteca estándar es builtin yfrom a import *no crea aristas. - Go importa paquetes, no ficheros: la arista va a cada
.gode la carpeta destino, ordenados por ruta. El módulo delgo.modse lee una vez por análisis; con él, un import que empieza por el módulo y no encuentra su carpeta está roto, y lo demás es de fuera —la biblioteca estándar no lleva punto en su primer segmento—. Singo.modlegible se intenta el sufijo del import. - C# indexa los
namespacedeclarados por fichero (con bloque o de fichero entero) y enlaza elusingcon los que coinciden exactamente. Los alias y losusing staticno crean aristas.
Los imports que no se resuelven se cuentan y se listan como hoy, con su línea: un import roto es información, no un fallo del análisis.
5. El determinismo es la prueba
Sección titulada «5. El determinismo es la prueba»- El orden de los ficheros ya es determinista; el de las aristas nuevas, también: se ordenan por ruta y se expanden los paquetes de Go en orden.
- Un repositorio con estos ficheros cambia de checksum respecto a antes, porque gana nodos. Es esperado y determinista: el mismo código da el mismo documento.
- La prueba que cierra la decisión es la del otro lado: un repositorio TS/JS da el mismo checksum que antes, fichero a fichero y byte a byte, con y sin caché.
6. Lo que no entra
Sección titulada «6. Lo que no entra»Kotlin, PHP, Ruby, C/C++ y Rust con árbol quedan fuera. Tampoco entran los comodines, los alias de import, los
using static, los reexports ni la lectura de pom.xml, build.gradle, go.mod o .csproj para deducir
raíces o nombres de proyecto: el nombre del proyecto sigue saliendo del package.json y, si no hay, de la
carpeta. Todo eso puede llegar después sin reabrir esta decisión, porque el modelo no cambia.
Consecuencias
Sección titulada «Consecuencias»- Un repositorio Java, Python, Go o C# deja de parecer vacío: peso, complejidad, ciclos, fan-in y fan-out funcionan igual que en TS/JS.
- Entran cuatro gramáticas nativas y sus cuatro
.wasm. El paquete del parser crece y hay que actualizarallowScriptscon las versiones exactas (regla 5 delAGENTS.md); el navegador gana cuatro assets, que se cargan solo al analizar. - La caché de parseo gana un campo por entrada y un formato nuevo. Las viejas se tiran sin avisar, como siempre.
languageForPathse queda para la lente de migración; el que decide si un fichero se analiza esparserForPath. Son dos preguntas distintas y dejan de confundirse.- El tiempo de análisis sube en monorepos con varios lenguajes —hay más ficheros—, pero la caché por parser mantiene barato el guardado del live.
- La documentación de uso deja de decir «TypeScript/JavaScript» donde ya no es verdad:
model.md, la guía y las páginas deanalyzey de la app.
Alternativas descartadas
Sección titulada «Alternativas descartadas»- Medirlos sin grafo (LOC/RLOC y complejidad, como Rust): es rápido, pero sin aristas el mapa no enseña arquitectura. Se descartó por el usuario al elegir el alcance.
- Parsear con expresiones regulares: la complejidad y los símbolos necesitan un árbol; un regex sobre código real da números que nadie puede defender.
- Meter estos lenguajes en la lente de migración: sus estados cuentan una migración concreta a Rust; reutilizarlos como «el lenguaje del fichero» obligaría a cambiar su significado y a romper a quien ya la lee.
- Un campo
languageen cada nodo ahora: cambiacc.jsonsin un consumidor que lo pida; la extensión ya lo dice y se puede derivar en la UI. Si algún día hace falta, será otro ADR. - Una herramienta universal tipo ctags: pierde la estructura y los imports de cada lenguaje, que es justo lo que se busca.
- Leer
pom.xmlogo.modpara deducir raíces y nombre: el sufijo de ruta resuelve sin ellos, y el nombre del proyecto no cambia el mapa.
Hoja de ruta
Sección titulada «Hoja de ruta»M61 — Más lenguajes que TS y JS
Sección titulada «M61 — Más lenguajes que TS y JS»- El análisis elige parser por extensión y la caché firma cada entrada con el suyo; un repositorio TS/JS da el mismo checksum byte a byte que antes.
- Java completo: gramática nativa y
.wasm, métricas, símbolos, miembros e imports resueltos por paquete, con sus tests. - Python completo: módulos y paquetes, relativos,
__init__.py, con sus tests. - Go completo: paquetes,
go.mody expansión a ficheros, con sus tests. - C# completo: namespaces declarados por fichero y
usingresuelto al final del análisis, con sus tests. - La documentación deja de prometer solo TS/JS:
model.md, elAGENTS.mdy las páginas de uso dicen los cinco lenguajes y cómo resuelve cada uno sus imports.
El registro de lo hecho vive en el feed de la guía.