---
title: "JavaScript в VSCode: язык, инструмент и модель анализа"
date: 2025-12-24
---

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

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

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

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

Сегодня VSCode — де-факто [стандарт для
веб-разработки](https://survey.stackoverflow.co/2025/technology#1-dev-id-es).
Его используют независимо от языка, фреймворка или архитектуры проекта.
Это подтверждается и отраслевыми опросами, и практикой командной
разработки: VSCode установлен «по умолчанию».

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

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

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

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

Первый заметный сдвиг в поведении VSCode происходит после добавления
[файла](https://code.visualstudio.com/docs/languages/jsconfig)
`jsconfig.json`. Без него JavaScript-код анализируется фрагментарно. С
его появлением IDE начинает воспринимать проект как целостную структуру:
появляются связи между файлами, навигация, типовые подсказки.

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

``` json
{
  "compilerOptions": {
    "checkJs": true
  }
}
```

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

Важно зафиксировать простой факт: VSCode сам по себе не анализирует
JavaScript. Все подсказки, ошибки и навигация приходят от [отдельного
процесса](https://github.com/typescript-language-server/typescript-language-server)
— `tsserver`, входящего в состав TypeScript; VSCode в этой схеме
выступает клиентом.

Связь между редактором и анализатором осуществляется через [Language
Server Protocol](https://microsoft.github.io/language-server-protocol/).
VSCode отправляет содержимое файлов и получает результаты анализа. Эта
архитектура делает редактор универсальным, но переносит все ограничения
на используемый language server.

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

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

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

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

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

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

``` js
// 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. В таких случаях анализ либо опирается на
эвристики, либо прекращается на уровне синтаксической формы, не доходя
до реальной структуры взаимодействий.

``` js
// 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`.
Для анализатора важно, какой файл объявлен точкой входа типовой
информации пакета.

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

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

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

``` ts
// 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` при
анализе стороннего проекта.

``` ts
// 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. Осознание этих границ позволяет
выстраивать работу с инструментом более осмысленно, опираясь на его
реальные архитектурные предпосылки и экосистемную логику.

*Эта страница соответствует второй стадии по AFKP (Acceptance-First
Knowledge Principle) - "раскрытию". Четвёртую стадию ("осмысление")
можно реализовать в этом
[телеграм-канале](https://t.me/alexgusev_lab_ru/44) или непосредственно
<a href="https://habr.com/ru/articles/980286/" target="_blank">на
Хабре</a>.*
