JavaScript в VSCode: язык, инструмент и модель анализа

Дата публикации: 2025-12-24

Долгое время я работал в IDE семейства JetBrains — в первую очередь в PhpStorm. Для JavaScript-кода я активно использовал JSDoc и воспринимал его как полноценный язык описания контрактов: типы, сигнатуры, навигация, автодополнение. Всё это работало предсказуемо и стабильно.

Поэтому переход в VSCode оказался неожиданно болезненным. Самая популярная IDE для веб-разработки демонстрировала менее предсказуемый анализ JavaScript-кода, чем IDE, исторически ориентированная на PHP. Это ощущение не исчезало со временем и требовало инженерного объяснения.

Ключевой тезис прост: JavaScript в VSCode анализируется как проекция TypeScript-модели через tsserver. Эта публикация — попытка зафиксировать наблюдаемое поведение VSCode при работе с JavaScript, понять, какие архитектурные решения за этим стоят, и почему проблема шире, чем «плохая поддержка JSDoc».

Почему JavaScript в VSCode ощущается «не своим»

Сегодня VSCode — де-факто стандарт для веб-разработки. Его используют независимо от языка, фреймворка или архитектуры проекта. Это подтверждается и отраслевыми опросами, и практикой командной разработки: VSCode установлен «по умолчанию».

Отсюда и восприятие: особенности его поведения становятся нормой экосистемы, а не частным случаем.

При работе с JavaScript в VSCode быстро проявляется характерное ощущение вторичности языка. В JavaScript-файлах появляются предупреждения и сообщения, сформулированные в терминах TypeScript. Анализ кода оказывается фрагментарным: часть конструкций понимается, часть — нет, особенно в библиотечном и инфраструктурном коде.

Для разработчика, привыкшего к JSDoc-ориентированному анализу, это выглядит как несоответствие ожиданий: IDE будто «ждёт» TypeScript даже там, где проект сознательно остаётся на JavaScript.

Как VSCode анализирует JavaScript

Первый заметный сдвиг в поведении VSCode происходит после добавления файла jsconfig.json. Без него JavaScript-код анализируется фрагментарно. С его появлением IDE начинает воспринимать проект как целостную структуру: появляются связи между файлами, навигация, типовые подсказки.

При этом для изменения поведения IDE достаточно минимальной конфигурации. В простейшем виде jsconfig.json может выглядеть так:

{
  "compilerOptions": {
    "checkJs": true
  }
}

jsconfig.json не участвует в выполнении кода и не относится к стандарту JavaScript. Его назначение ограничивается описанием границ проекта и параметров анализа для используемого language server’а.

Важно зафиксировать простой факт: VSCode сам по себе не анализирует JavaScript. Все подсказки, ошибки и навигация приходят от отдельного процессаtsserver, входящего в состав TypeScript; VSCode в этой схеме выступает клиентом.

Связь между редактором и анализатором осуществляется через Language Server Protocol. VSCode отправляет содержимое файлов и получает результаты анализа. Эта архитектура делает редактор универсальным, но переносит все ограничения на используемый language server.

В отличие от IDE семейства JetBrains, где анализаторы встроены и глубоко интегрированы, VSCode агрегирует внешние языковые сервисы. Это объясняет его гибкость и масштабируемость, но также объясняет, почему поведение анализа полностью определяется выбранным сервером.

Архитектурно VSCode допускает использование альтернативных language server’ов, но на практике для JavaScript реально используется только tsserver; JavaScript-first или JSDoc-first альтернатив в экосистеме нет.

tsserver строит модель проекта, опираясь на статические импорты в пределах границ, заданных jsconfig.json. Всё, что разрешается динамически или определяется на этапе выполнения, в эту модель не попадает. Я это хорошо ощущаю на собственных проектах: в моей архитектуре используется позднее связывание и внедрение зависимостей через конструктор, реализованное в библиотеке @teqfw/di. Статические импорты у меня применяются минимально — фактически только для загрузки самой DI-библиотеки, тогда как дальнейшее связывание модулей происходит через динамические импорты и остаётся за пределами анализа tsserver.

TypeScript как модель анализа JavaScript

Поведение VSCode в JavaScript-файлах во многом совпадает с его поведением в TypeScript. Это проявляется в одинаковых предупреждениях, правилах навигации и принципах построения подсказок. Такое сходство закономерно: JavaScript в VSCode анализируется через ту же типовую модель, которая лежит в основе TypeScript.

В первую очередь уверенно анализируются конструкции, которые легко и однозначно сводятся к типовой модели TypeScript. К ним относятся классы и функции с явными сигнатурами, объектные литералы с фиксированной структурой, а также зависимости, выраженные через статические импорты. В таких случаях анализатор способен построить устойчивую модель кода, обеспечить навигацию и вывести типовую информацию без дополнительных аннотаций.

// 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);
}

В этом примере форма кода хорошо укладывается в TypeScript-модель: видны экспортируемые сущности, структура класса, сигнатуры методов и возвращаемые значения. В VSCode это выражается в стабильной навигации по символам, подсказках по типам параметров и корректном автодополнении при использовании API.

Иная ситуация возникает там, где JavaScript начинает использовать свои динамические возможности. Позднее связывание, фабрики, вычисляемые зависимости и поведенческие контракты не имеют прямого представления в типовой модели TypeScript. В таких случаях анализ либо опирается на эвристики, либо прекращается на уровне синтаксической формы, не доходя до реальной структуры взаимодействий.

// example-dynamic.js

export function createContainer(loader) {
  return {
    get(name) {
      const Module = loader(name);
      return new Module();
    },
  };
}

// использование
const container = createContainer(loadModule);
const service = container.get("userService");

Здесь значимая часть архитектуры формируется во время выполнения: конкретные зависимости, их типы и связи определяются динамически. Для анализатора это остаётся непрозрачным, поскольку такие конструкции не сводятся к статической типовой модели. В результате VSCode видит лишь форму объектов и вызовов: подсказки по типам становятся обобщёнными, а навигация по фактическим контрактам между частями системы оказывается ограниченной.

В совокупности это приводит к тому, что в VSCode отсутствует отдельный режим анализа «чистого JavaScript». Существует единая модель, основанная на TypeScript, и JavaScript-код анализируется ровно в той мере, в какой он может быть приведён к этой модели. Всё, что выходит за её пределы, оказывается либо частично видимым, либо полностью исключённым из анализа.

Контракты как основа понимания кода

В VSCode анализ JavaScript-кода опирается на публичный контракт библиотеки. Для tsserver именно декларативные описания API задают границу между модулем и его использованием, определяя, какие сведения включаются в модель анализа потребляющего проекта. Исходный код зависимости при этом используется преимущественно для навигации и не участвует в построении типовой модели.

Роль такого контракта выполняют файлы деклараций .d.ts. В них описываются экспортируемые сущности и их сигнатуры, и именно в этом виде информация о библиотеке подключается к анализу. Наличие реализации в node_modules, включая JavaScript-код с JSDoc-аннотациями, не влияет на формирование типовой модели внешнего проекта.

Подключение публичного контракта осуществляется через package.json. Для анализатора важно, какой файл объявлен точкой входа типовой информации пакета.

{
  "name": "@teqfw/di",
  "version": "x.y.z",
  "main": "src/index.js",
  "types": "types.d.ts"
}

Поле types указывает tsserver, какой файл следует использовать в качестве публичного контракта библиотеки. Типовая информация, находящаяся за пределами этого файла, остаётся частью внутренней реализации и не учитывается при анализе внешнего проекта.

Внутри самих деклараций обычно выделяются два уровня типовых описаний. Первый уровень применяется при разработке библиотеки и отражает её внутреннюю структуру: вспомогательные типы, детали реализации и служебные соглашения. Эти типы остаются локальными для модуля и используются внутри проекта.

// 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[];
}

Второй уровень формирует внешний контракт библиотеки — набор типов и сигнатур, который доступен потребителям и используется tsserver при анализе стороннего проекта.

// index.d.ts

declare global {
  type TeqFw_Di_Api_Container_Parser = import("./src/Api/Container/Parser.js").default;
}

export {};

Такое разделение соответствует модели анализа tsserver, в которой различаются типы, применяемые для внутренней разработки пакета, и типы, задающие его внешний контракт. При этом потребляющий проект работает только с теми типами, которые явно включены в публичный слой, независимо от доступности исходного кода библиотеки.

JSDoc в этой схеме используется в пределах той же типовой модели. В TypeScript-ориентированном анализе учитываются только те аннотации, которые однозначно сводятся к типовой проекции. Они помогают улучшить навигацию и автодополнение при разработке самой библиотеки, но не расширяют публичный контракт для внешнего анализа.

В результате понимание кода в VSCode формируется вокруг явно описанных контрактов. Это делает файлы .d.ts ключевым элементом типового анализа JavaScript-проектов и определяет роль JSDoc как вспомогательного средства документации внутри выбранной модели tsserver.

Заключение

Поведение JavaScript в VSCode определяется архитектурой и продуктовой логикой экосистемы, в которой редактор развивается. VSCode и TypeScript проектируются и эволюционируют как взаимосвязанные инструменты, поэтому анализ JavaScript изначально встроен в TypeScript-инструментарий и опирается на его модель типов, правила интерпретации и приоритеты развития.

Для такой экосистемы ставка на TypeScript выглядит естественной. Единая формальная модель анализа, единый language server и согласованный инструментарий позволяют обеспечить предсказуемое поведение IDE, устойчивый DX и масштабируемость на уровне всей платформы. В этой конфигурации JavaScript рассматривается как код, который может быть проанализирован через TypeScript-модель с разной степенью полноты, в зависимости от того, насколько его структура и аннотации укладываются в эту модель.

Поддержка JSDoc в VSCode выстраивается в том же ключе. Аннотации используются как источник типовой информации, если они однозначно проецируются на модель TypeScript. Семантические, описательные и поведенческие аспекты JSDoc остаются вне зоны анализа, поскольку они не включаются в формальную типовую проекцию, с которой работает tsserver. Такое положение дел является следствием выбранной модели анализа, а не качества или выразительности самого JSDoc.

Развитие полноценной связки ES6 + JSDoc как самостоятельной основы анализа JavaScript не входит в стратегические приоритеты Microsoft. Поддержание единой модели анализа вокруг TypeScript упрощает эволюцию инструментов и снижает фрагментацию экосистемы. В результате JSDoc остаётся вспомогательным механизмом, дополняющим TypeScript-модель, но не формирующим отдельное направление развития.

В итоге JavaScript в VSCode воспринимается и анализируется через призму TypeScript-инструментария. Это задаёт понятные границы возможного и объясняет наблюдаемое поведение IDE. Осознание этих границ позволяет выстраивать работу с инструментом более осмысленно, опираясь на его реальные архитектурные предпосылки и экосистемную логику.