El análisis estático es cualquier verificación que una herramienta puede ejecutar en tu código sin ejecutarlo: leer el código fuente, construir un modelo de él e informar problemas antes de que se ejecute una sola línea. En un proyecto Node.js, ese modelo se manifiesta como tres herramientas distintas que trabajan juntas: un linter (ESLint) que razona sobre la estructura y corrección del código, un formateador (Prettier) que razona sobre el diseño, y un verificador de tipos (tsc) que razona sobre las formas que fluyen a través de tu programa.
Esta página es el mapa antes del cómo. Conceptos básicos de linting pone en marcha una configuración plana en minutos, y Integración de Prettier, Verificación de tipos en CI, y Knip y código muerto profundizan en cada herramienta. Aquí, el objetivo es comprender por qué los proyectos Node recurren a varias herramientas específicas en lugar de una amplia, y qué está mirando realmente cada una cuando se ejecuta.
El análisis estático en Node.js se divide en preocupaciones independientes —estilo/diseño, corrección estructural y solidez de tipos— cada una manejada por una herramienta construida para un propósito específico en lugar de un verificador que lo hace todo.
Por qué es importante: Confundir estas preocupaciones es exactamente lo que produce herramientas ruidosas y contradictorias: un linter que lucha contra un formateador por la indentación, o una queja de "estilo" que en realidad esconde un error real.
Conceptos clave:árbol de sintaxis abstracta (AST), linting, formateo, verificación de tipos, severidad de la regla, autofix.
Cuándo usarlo: Al configurar las puertas de calidad de un nuevo proyecto, al decidir qué herramienta es propietaria de qué clase de problema, al depurar por qué ESLint y Prettier parecen estar en desacuerdo, o al explicar a un equipo por qué "simplemente ejecutar el formateador" no detecta todo.
Limitaciones / Compromisos: El análisis estático prueba que la forma del código es sólida, nunca que su comportamiento es correcto; una función perfectamente lintada y perfectamente tipada aún puede devolver la respuesta incorrecta.
Temas relacionados: Configuración plana de ESLint, Prettier, el compilador de TypeScript, reglas de límite de importación, detección de código muerto.
Cada herramienta de análisis estático comienza de la misma manera: analiza tu archivo fuente en un árbol de sintaxis abstracta, una representación estructurada de la gramática del código en lugar de su texto sin formato.
Una vez que una herramienta tiene ese árbol, puede hacer preguntas precisas que una búsqueda de texto plano nunca podría: "¿se lee esta variable alguna vez después de que se le asigna?", "¿tiene esta función una ruta de código sin return?", "¿se espera esta promesa en algún lugar?". Una verificación basada en expresiones regulares solo ve caracteres; una verificación basada en AST ve significado.
La historia del análisis estático de Node se divide en tres herramientas porque cada una responde a una pregunta genuinamente diferente sobre ese árbol:
Un formateador (Prettier) pregunta "¿cómo debe presentarse esto en la página?" —indentación, longitud de línea, estilo de comillas, comas finales. Tiene una opinión sobre la apariencia y nada más.
Un linter (ESLint) pregunta "¿está este código estructurado correctamente?" —variables no utilizadas, ramas inalcanzables, patrones prohibidos, dirección de importación. Tiene una opinión sobre la corrección y la convención.
Un verificador de tipos (tsc) pregunta "¿coinciden los valores que fluyen a través de este programa con las formas que se me dijo que esperara?" —tiene una opinión sobre la solidez, verificada contra los tipos declarados en lugar del comportamiento en tiempo de ejecución.
Una forma sencilla de distinguirlos: si un cambio en el código fuera invisible después de ejecutar git diff --ignore-all-space, ese es el trabajo del formateador. Si el cambio altera lo que el código hace sin alterar su apariencia, ese es el trabajo del linter o del verificador de tipos.
// Preocupación de formateo: espaciado/comillas - Prettier se encarga de estoconst x={a:1,b:2}// Preocupación de linting: variable no utilizada - ESLint se encarga de estoconst unused = computeSomething();// Preocupación de verificación de tipos: forma incorrecta - tsc se encarga de estofunction total(price: number): number { return price; }total("19.99"); // se pasa una cadena donde se requiere un número
La razón por la que los proyectos Node ejecutan estas herramientas por separado —en lugar de un verificador monolítico— se debe a lo diferente que necesitan funcionar.
El formateo está destinado a no dejar lugar a debate. Prettier admite deliberadamente pocas opciones de configuración, porque el objetivo no es un estilo "correcto", sino un estilo sobre el que nadie discute en la revisión de código. Ese es un objetivo de diseño diferente al de un linter, que se supone que tiene docenas de reglas configurables individualmente porque los equipos legítimamente no están de acuerdo sobre qué reglas de corrección les importan.
El linting se basa en reglas y está escalonado por severidad. Cada regla de ESLint informa en "off", "warn" o "error", y las reglas se componen de múltiples fuentes: eslint.configs.recommended para la corrección general de JavaScript, las reglas de typescript-eslint para patrones específicos de TypeScript, y reglas de plugins como import-x/no-restricted-paths para restricciones arquitectónicas. Un archivo de configuración plana es simplemente un array ordenado de estos conjuntos de reglas, donde las entradas posteriores anulan las anteriores para los archivos que coinciden, razón por la cual las anulaciones con ámbito de archivo (relajar no-explicit-any solo dentro de test/**) son un patrón normal y esperado en lugar de una solución alternativa.
La verificación de tipos funciona en un eje completamente diferente: no es un conjunto de reglas configurables, es una prueba coherente.tsc puede verificar que el tipo declarado de cada valor coincide con cómo se usa, o no puede; no hay un "desactivar este error de tipo" como se deshabilitaría una regla de lint, a menos que sea un escape explícito @ts-expect-error que debe justificarse en línea. Por eso los equipos ejecutan tsc --noEmit como un paso de CI propio: es una puerta de aprobación/falla, no una lista de advertencias ajustables.
Autofix existe para dos de los tres, y esa diferencia importa operativamente. La salida completa de Prettier es un autofix; no hay un "modo manual". ESLint puede autofixear un subconjunto de reglas de forma determinista (eslint --fix), pero muchas reglas (una variable no utilizada que representa un error real) requieren una decisión humana. Los errores de tipo nunca se autofixean: una falta de coincidencia de forma significa que la lógica del código necesita cambiar, no su sintaxis.
// eslint.config.js - cada entrada es una capa, las posteriores ganan para los archivos coincidentesexport default [ eslintRecommended, ...tseslintRecommended, { files: ["test/**/*.ts"], rules: { "@typescript-eslint/no-explicit-any": "off" } },];
El análisis estático solo tiene verdadera fuerza una vez que se aplica en un lugar donde ni el hábito ni las extensiones del editor pueden omitirse, lo que en la práctica significa CI, no la configuración local de un desarrollador. Un complemento de editor detecta un problema para la persona que lo tiene instalado y prestando atención; una puerta de CI lo detecta para todos, siempre, incluido el PR de alguien que deshabilitó su extensión de linter por accidente.
Ahí es también donde los diferentes costos de las tres herramientas se convierten en una verdadera compensación de ingeniería, no solo filosófica. Las verificaciones de formato son esencialmente gratuitas: comparar la salida con una forma canónica es rápido y se paraleliza trivialmente. El linting simple es barato porque solo necesita el AST de cada archivo. El linting consciente de tipos —reglas como "no promesas flotantes" que necesitan conocer el tipo real de un valor, no solo su sintaxis— es significativamente más lento, porque requiere que toda la maquinaria de verificación de tipos de TypeScript se ejecute debajo de ESLint en lugar de un analizador ligero. Los equipos suelen limitar las reglas conscientes de tipos a src/** y las omiten para scripts o código generado específicamente para mantener ese costo acotado.
Una cuarta categoría de análisis estático se encuentra junto a estas tres y es fácil de pasar por alto: el análisis estructural y de código muerto —herramientas como Knip o dependency-cruiser que no verifican ningún archivo individual de forma aislada, sino que construyen un gráfico de todo el proyecto para encontrar código que nada importa, dependencias que nada usa o direcciones de importación que violan una arquitectura prevista. Esto sigue siendo análisis estático (nada se ejecuta), pero opera a nivel de proyecto en lugar de a nivel de archivo, razón por la cual es una categoría de herramienta genuinamente diferente de ESLint, aunque las dos se configuran de manera similar.
Enfoque
Fortaleza
Debilidad
Mejor ajuste
Formateador (Prettier)
Elimina por completo el debate sobre el estilo; costo de ejecución casi nulo
No tiene ninguna opinión sobre la corrección
Todo proyecto, como base innegociable
Linter (ESLint, no consciente de tipos)
Rápido, detecta errores estructurales reales y desviaciones de convenciones
Ciego a las discrepancias de tipos
Puerta de corrección predeterminada en cada archivo
Linter consciente de tipos / tsc
Detecta discrepancias de forma y manejo inseguro de any/promesas
Notablemente más lento; necesita un tsconfig que funcione
Código de aplicación donde los errores de tipo en tiempo de ejecución son costosos
Análisis de grafos a nivel de proyecto (Knip, dependency-cruiser)
Encuentra código muerto y violaciones arquitectónicas que ninguna herramienta de un solo archivo ve
Necesita un recorrido de todo el proyecto; más configuración, falsos positivos ocasionales
Bases de código más grandes y monorepos que acumulan código obsoleto
La tendencia en el ecosistema de Node ha sido ejecutar más de esto antes y de manera más estricta: la configuración plana reemplazó la cascada .eslintrc anterior específicamente para hacer explícita la composición de reglas en lugar de la implícita de recorrido de directorios, y las políticas de CI de "cero advertencias" (--max-warnings 0) se han vuelto comunes precisamente porque una advertencia que nadie está obligado a corregir es una advertencia que nadie lee.
"ESLint y Prettier son herramientas que compiten, elige una." Verifican cosas completamente diferentes; el modo de fallo común es una regla de lint que también impone el estilo, lo que entra en conflicto con el formateador. La solución es desactivar cualquier regla estilística de ESLint y dejar que Prettier se encargue exclusivamente del diseño.
"Si el linter pasa, el código es correcto." El linting prueba que el código evita una lista específica de patrones conocidos como malos; no dice nada sobre si la lógica produce la respuesta correcta.
"La verificación de tipos es solo una forma más estricta de linting." Son estructuralmente diferentes: el linting es una lista de reglas configurables independientemente, la verificación de tipos es una prueba coherente sobre las formas declaradas de todo el programa.
"Autofix significa que no necesito leer el diff."--fix es determinista para reglas mecánicas, pero también puede cambiar silenciosamente el comportamiento para reglas con múltiples correcciones válidas; siempre revisa un diff de autofix antes de confirmarlo.
"Ejecutar esto localmente es suficiente, recordaré corregir las advertencias." La aplicación solo local es opcional por construcción; solo una puerta de CI aplica la misma regla a cada colaborador en cada cambio.
¿Cuál es la diferencia real entre un linter y un formateador?
Un formateador solo cambia la apariencia del código (espacios en blanco, comillas, saltos de línea) y nunca cambia el comportamiento. Un linter puede señalar —y a veces corregir— cosas que cambian lo que el código realmente hace, como una variable no utilizada o una rama inalcanzable.
¿Por qué ESLint necesita un AST en lugar de solo leer texto?
El texto por sí solo no puede responder preguntas estructurales como "¿se usa esta variable?" o "¿todas las rutas de código devuelven un valor?". Un AST le da a la herramienta un modelo real de la gramática del código, para que pueda razonar sobre las relaciones entre las partes del archivo, no solo los caracteres.
¿Por qué Prettier tiene deliberadamente pocas opciones de configuración?
Su propósito principal es poner fin a los debates sobre el estilo produciendo un diseño canónico para todos. Una configurabilidad extensa recrearía el mismo debate que existe para eliminar.
¿Es la verificación de tipos una forma de linting?
En realidad no; un linter ejecuta muchas reglas configurables independientemente sobre el AST de un archivo, mientras que un verificador de tipos prueba una cosa: que las formas de valor reales de un programa coinciden con sus tipos declarados, y lo hace utilizando tanto el árbol de sintaxis como la información de tipos de los archivos importados.
¿Por qué el linting consciente de tipos es más lento que el linting regular?
Las reglas conscientes de tipos necesitan la información de tipos completa del compilador de TypeScript, no solo el árbol de sintaxis de un archivo, por lo que ESLint tiene que construir y consultar ese modelo de tipos de todo el proyecto antes de poder evaluar la regla.
¿Puede autofix romper mi código?
Para la mayoría de las reglas mecánicas, no; la corrección es una transformación segura y determinista. Pero algunas reglas tienen más de una corrección válida, y aplicar una automáticamente puede cambiar el comportamiento de una manera que un revisor humano habría detectado, por lo que la salida de autofix aún debe revisarse.
¿Por qué ejecutar la verificación de tipos como un paso de CI separado en lugar de dentro de ESLint?
Porque es un tipo diferente de verificación con un perfil de costo diferente —una prueba de aprobación/falla sobre todo el programa en lugar de un conjunto de reglas ajustables con ámbito de archivo— por lo que la mayoría de los equipos lo aíslan (tsc --noEmit) para mantener su costo y su señal fáciles de razonar por sí mismos.
¿Qué significa "estático" en análisis estático?
Que la verificación ocurre sin ejecutar el programa; la herramienta solo lee y razona sobre el código fuente, a diferencia del análisis dinámico (pruebas, perfiladores) que observa el código mientras se ejecuta.
¿Es la detección de código muerto linting?
Es análisis estático, pero con un alcance diferente: un linter razona sobre el AST de un archivo, mientras que las herramientas de código muerto y de grafos de dependencia como Knip razonan sobre el grafo de importación de todo el proyecto para encontrar código o dependencias que nada usa realmente.
¿Por qué los equipos imponen "cero advertencias" en lugar de solo corregir errores?
Una advertencia sin resolver entrena a todos a ignorar las advertencias, incluida la siguiente que importa. Tratar las advertencias como fallos de compilación mantiene la señal significativa.
¿El análisis estático reemplaza las pruebas?
No, prueba que la forma del código es sólida (sin código no utilizado, tipos correctos, sin patrones prohibidos), nunca que su comportamiento coincide con la intención. Una función bien tipada y limpia de lint puede seguir calculando el resultado incorrecto, lo que solo una prueba puede detectar.
¿Por qué es importante la configuración plana para cómo se componen estas herramientas?
La configuración plana representa los conjuntos de reglas como un array explícito y ordenado donde las entradas posteriores anulan las anteriores para los archivos que coinciden, reemplazando el modelo de cascada de directorios implícito anterior, lo que deja claro exactamente qué reglas se aplican a qué archivos en lugar de depender de cómo se anidaban los archivos .eslintrc.