Durante mucho tiempo trabajé en IDEs de la familia JetBrains, sobre todo en PhpStorm. Para el código JavaScript usaba activamente JSDoc y lo percibía como un lenguaje completo para describir contratos: tipos, firmas, navegación y autocompletado. Todo eso funcionaba de manera predecible y estable.
Por eso la transición a VSCode resultó inesperadamente dolorosa. El IDE más popular para desarrollo web mostró un análisis de JavaScript menos predecible que un IDE históricamente orientado a PHP. Esa sensación no desapareció con el tiempo y requería una explicación de ingeniería.
La tesis clave es simple: JavaScript en VSCode se analiza como una
proyección del modelo de TypeScript a través de tsserver. Esta
publicación intenta fijar el comportamiento observado de VSCode al
trabajar con JavaScript, entender qué decisiones arquitectónicas hay
detrás y por qué el problema es más amplio que una "mala compatibilidad
con JSDoc".
Por qué JavaScript en VSCode se siente "ajeno"
Hoy VSCode es el estándar de facto para el desarrollo web. Se usa independientemente del lenguaje, el framework o la arquitectura del proyecto. Esto lo confirman tanto las encuestas del sector como la práctica de los equipos: VSCode se instala "por defecto".
De ahí la percepción: las particularidades de su comportamiento se convierten en norma del ecosistema, no en un caso aislado.
Al trabajar con JavaScript en VSCode aparece rápidamente una sensación característica de secundariedad del lenguaje. En los archivos JavaScript surgen advertencias y mensajes formulados en términos de TypeScript. El análisis del código se vuelve fragmentario: algunas construcciones se entienden, otras no, sobre todo en código de bibliotecas e infraestructura.
Para un desarrollador acostumbrado a un análisis orientado a JSDoc, esto se ve como un desajuste de expectativas: el IDE parece "esperar" TypeScript incluso donde el proyecto se mantiene deliberadamente en JavaScript.
Cómo analiza VSCode JavaScript
El primer cambio visible en el comportamiento de VSCode aparece después
de añadir el
archivo
jsconfig.json. Sin él, el código JavaScript se analiza de forma
fragmentaria. Con su aparición, el IDE empieza a percibir el proyecto
como una estructura coherente: aparecen vínculos entre archivos,
navegación y sugerencias de tipos.
Aun así, para cambiar el comportamiento del IDE basta con una
configuración mínima. En su forma más simple, jsconfig.json puede
verse así:
{
"compilerOptions": {
"checkJs": true
}
}
jsconfig.json no participa en la ejecución del código y no pertenece
al estándar de JavaScript. Su propósito se limita a describir los
límites del proyecto y los parámetros de análisis para el language
server utilizado.
Es importante fijar un hecho simple: VSCode no analiza JavaScript por sí
mismo. Todas las sugerencias, errores y navegación provienen de un
proceso
separado -
tsserver, incluido en TypeScript; VSCode actúa como cliente en este
esquema.
La conexión entre el editor y el analizador se realiza mediante el Language Server Protocol. VSCode envía el contenido de los archivos y recibe los resultados de análisis. Esta arquitectura hace que el editor sea universal, pero traslada todas las limitaciones al language server elegido.
A diferencia de los IDEs de la familia JetBrains, donde los analizadores están integrados y profundamente acoplados, VSCode agrega servicios de lenguaje externos. Esto explica su flexibilidad y escalabilidad, pero también explica por qué el comportamiento del análisis está completamente determinado por el servidor elegido.
En términos arquitectónicos, VSCode permite usar language servers
alternativos, pero en la práctica para JavaScript se utiliza solo
tsserver; no existen alternativas JavaScript-first o JSDoc-first en el
ecosistema.
tsserver construye un modelo del proyecto apoyándose en importaciones
estáticas dentro de los límites definidos por jsconfig.json. Todo lo
que se resuelve de forma dinámica o se determina en tiempo de ejecución
no entra en ese modelo. Lo percibo bien en mis propios proyectos: en mi
arquitectura se utiliza enlace tardío e inyección de dependencias a
través del constructor, implementado en la biblioteca
@teqfw/di. Las importaciones estáticas se
usan de forma mínima: en la práctica solo para cargar la biblioteca DI,
mientras que el enlace posterior de módulos se hace mediante
importaciones dinámicas y queda fuera del análisis de tsserver.
TypeScript como modelo de análisis para JavaScript
El comportamiento de VSCode en archivos JavaScript coincide en gran medida con su comportamiento en TypeScript. Esto se refleja en advertencias idénticas, reglas de navegación y principios para construir sugerencias. Esa similitud es natural: JavaScript en VSCode se analiza a través del mismo modelo de tipos que sustenta TypeScript.
En primer lugar se analizan con seguridad las construcciones que se pueden reducir de forma fácil y unívoca al modelo de TypeScript. Entre ellas están las clases y funciones con firmas explícitas, los literales de objetos con estructura fija y las dependencias expresadas mediante importaciones estáticas. En esos casos el analizador puede construir un modelo estable del código, proporcionar navegación y ofrecer información de tipos sin anotaciones adicionales.
// example-good.js
export class UserService {
constructor(apiClient) {
this.apiClient = apiClient;
}
getUser(id) {
return this.apiClient.fetch(`/users/${id}`);
}
}
export function createService(apiClient) {
return new UserService(apiClient);
}
En este ejemplo la forma del código encaja bien en el modelo TypeScript: se ven las entidades exportadas, la estructura de la clase, las firmas de los métodos y los valores devueltos. En VSCode esto se expresa en una navegación estable por símbolos, sugerencias de tipos para los parámetros y autocompletado correcto al usar la API.
La situación cambia cuando JavaScript empieza a usar sus capacidades dinámicas. El enlace tardío, las fábricas, las dependencias calculadas y los contratos de comportamiento no tienen una representación directa en el modelo de tipos TypeScript. En esos casos el análisis se apoya en heurísticas o se detiene en la forma sintáctica, sin llegar a la estructura real de interacción.
// example-dynamic.js
export function createContainer(loader) {
return {
get(name) {
const Module = loader(name);
return new Module();
},
};
}
// uso
const container = createContainer(loadModule);
const service = container.get("userService");
Aquí una parte significativa de la arquitectura se forma en tiempo de ejecución: las dependencias concretas, sus tipos y sus relaciones se determinan de forma dinámica. Para el analizador esto permanece opaco, porque esas construcciones no se reducen a un modelo de tipos estático. Como resultado, VSCode ve solo la forma de los objetos y las llamadas: las sugerencias de tipos se vuelven genéricas y la navegación por los contratos reales entre partes del sistema queda limitada.
En conjunto, esto lleva a que en VSCode no exista un modo separado de análisis de "JavaScript puro". Existe un modelo único basado en TypeScript, y el código JavaScript se analiza solo en la medida en que pueda reducirse a ese modelo. Todo lo que queda fuera es parcialmente visible o queda totalmente excluido del análisis.
Los contratos como base para entender el código
En VSCode, el análisis de JavaScript se apoya en el contrato público de
una biblioteca. Para tsserver, las descripciones declarativas del API
fijan el límite entre el módulo y su uso, determinando qué información
se incluye en el modelo de análisis del proyecto consumidor. El código
fuente de la dependencia se utiliza sobre todo para navegación y no
participa en la construcción del modelo de tipos.
Ese contrato lo cumplen los archivos de declaraciones .d.ts. En ellos
se describen las entidades exportadas y sus firmas, y es en ese formato
como la información de la biblioteca se incorpora al análisis. La
presencia de la implementación en node_modules, incluido el código
JavaScript con anotaciones JSDoc, no afecta a la formación del modelo de
tipos del proyecto externo.
La conexión del contrato público se realiza a través de package.json.
Para el analizador es importante qué archivo se declara como punto de
entrada de la información de tipos del paquete.
{
"name": "@teqfw/di",
"version": "x.y.z",
"main": "src/index.js",
"types": "types.d.ts"
}
El campo types indica a tsserver qué archivo debe utilizar como
contrato público de la biblioteca. La información de tipos fuera de ese
archivo permanece como parte de la implementación interna y no se toma
en cuenta al analizar un proyecto externo.
Dentro de las declaraciones suelen distinguirse dos niveles de descripciones de tipos. El primer nivel se aplica durante el desarrollo de la biblioteca y refleja su estructura interna: tipos auxiliares, detalles de implementación y acuerdos de servicio. Estos tipos permanecen locales al módulo y se usan dentro del proyecto.
// src/Api/Container/types.d.ts
export interface ParserContext {
readonly source: string;
readonly options?: Record<string, unknown>;
}
export interface ParserResult {
readonly ast: object;
readonly errors: readonly Error[];
}
El segundo nivel forma el contrato externo de la biblioteca: el conjunto
de tipos y firmas disponible para los consumidores y usado por
tsserver al analizar un proyecto de terceros.
// index.d.ts
declare global {
type TeqFw_Di_Api_Container_Parser = import("./src/Api/Container/Parser.js").default;
}
export {};
Esta separación corresponde al modelo de análisis de tsserver, en el
que se distinguen los tipos usados para el desarrollo interno del
paquete y los tipos que definen su contrato externo. El proyecto
consumidor trabaja solo con los tipos explícitamente incluidos en la
capa pública, independientemente de la disponibilidad del código fuente
de la biblioteca.
JSDoc en este esquema se utiliza dentro del mismo modelo de tipos. En el análisis orientado a TypeScript se consideran únicamente las anotaciones que se proyectan de forma inequívoca al modelo de tipos. Ayudan a mejorar la navegación y el autocompletado al desarrollar la biblioteca, pero no amplían el contrato público para el análisis externo.
Como resultado, la comprensión del código en VSCode se construye
alrededor de contratos descritos de forma explícita. Esto convierte a
los archivos .d.ts en el elemento clave del análisis de tipos en
proyectos JavaScript y define el rol de JSDoc como una herramienta
auxiliar de documentación dentro del modelo tsserver elegido.
Conclusión
El comportamiento de JavaScript en VSCode está determinado por la arquitectura y la lógica de producto del ecosistema en el que el editor evoluciona. VSCode y TypeScript se diseñan y evolucionan como herramientas interconectadas, por lo que el análisis de JavaScript está integrado en el instrumental de TypeScript y se apoya en su modelo de tipos, reglas de interpretación y prioridades de desarrollo.
Para un ecosistema así, la apuesta por TypeScript parece natural. Un modelo formal de análisis único, un único language server y un conjunto de herramientas coordinado permiten un comportamiento predecible del IDE, un DX estable y escalabilidad a nivel de toda la plataforma. En esta configuración, JavaScript se considera como un código que puede analizarse a través del modelo TypeScript en distintos grados, según cuánto su estructura y anotaciones encajen en ese modelo.
El soporte de JSDoc en VSCode se construye en la misma lógica. Las
anotaciones se usan como fuente de información de tipos si se proyectan
de forma inequívoca en el modelo TypeScript. Los aspectos semánticos,
descriptivos y de comportamiento de JSDoc quedan fuera del análisis
porque no se incluyen en la proyección formal de tipos con la que
trabaja tsserver. Esta situación es consecuencia del modelo de
análisis elegido y no de la calidad o expresividad de JSDoc.
El desarrollo de una combinación completa ES6 + JSDoc como base independiente para el análisis de JavaScript no está entre las prioridades estratégicas de Microsoft. Mantener un único modelo de análisis en torno a TypeScript simplifica la evolución de las herramientas y reduce la fragmentación del ecosistema. Como resultado, JSDoc permanece como un mecanismo auxiliar que complementa el modelo TypeScript, pero no forma una dirección de desarrollo independiente.
En definitiva, JavaScript en VSCode se percibe y se analiza a través del prisma del instrumental de TypeScript. Esto fija límites claros de lo posible y explica el comportamiento observado del IDE. Comprender estos límites permite trabajar con la herramienta de manera más consciente, apoyándose en sus supuestos arquitectónicos reales y en la lógica del ecosistema.