Open-Source Wikis

/

TypeScript

/

Systems

/

Project system

microsoft/TypeScript

Project system

How tsserver decides which files belong to which compilation, manages the open-file lifecycle, and serves the right project for each editor query.

Source

Almost entirely under src/server/:

File Lines Role
editorServices.ts 5,727 The ProjectService — the project graph manager
project.ts 3,227 Project base + ConfiguredProject, InferredProject, ExternalProject, AutoImportProviderProject
scriptInfo.ts 700 Per-file editor state
scriptVersionCache.ts 800 Versioned text snapshots with edit-history compression
packageJsonCache.ts 100 Per-server package.json cache
moduleSpecifierCache.ts 130 Auto-import path memoisation

Purpose

tsc works on one program at a time. tsserver typically owns several:

  • A project per tsconfig.json the user has opened (a ConfiguredProject).
  • A loose-files project for .ts / .js files outside any tsconfig (InferredProject).
  • A project per Visual Studio "external" project descriptor (ExternalProject).
  • A derived auto-import provider per project (AutoImportProviderProject) that scans node_modules for completion candidates.

The project service decides which project owns each open file, when to load and unload projects, when to reload tsconfig.json, when to create the auto-import provider, and how to share state across projects.

Key abstractions

Symbol File Role
ProjectService editorServices.ts The graph manager — every command goes through it
Project (abstract) project.ts One compilation; owns a LanguageService
ConfiguredProject project.ts Backed by a tsconfig.json/jsconfig.json
InferredProject project.ts Files not in any config
ExternalProject project.ts Driven by openExternalProject request from VS-style hosts
AutoImportProviderProject project.ts A read-only scan over a project's node_modules for auto-import candidates
ScriptInfo scriptInfo.ts A file in the editor — open/closed, snapshot, attached projects
ScriptVersionCache scriptVersionCache.ts Edit-history compression so version diffs are cheap
WildcardWatcher, ChangedFilesWatcher editorServices.ts File-system watchers wired into project invalidation

How it works

graph TD
    Editor["Editor"] -->|open foo.ts| Session["Session"]
    Session --> PS["ProjectService"]
    PS --> Find["find tsconfig from foo.ts upward"]
    Find --> Has{"tsconfig found?"}
    Has -->|yes| CP["ConfiguredProject created/reused"]
    Has -->|no| IP["InferredProject"]
    CP --> SI["ScriptInfo attached"]
    IP --> SI
    SI --> LS["Project.languageService"]
    LS --> Program["program.ts"]
    PS -->|background| AIP["AutoImportProviderProject (per ConfiguredProject)"]

When a file is opened:

  1. ProjectService.openClientFile is invoked.
  2. The service walks parents looking for tsconfig.json / jsconfig.json. If found, it creates or reuses a ConfiguredProject; otherwise it picks an InferredProject.
  3. A ScriptInfo is created for the file (or fetched if already present), and attached to the project.
  4. The project's LanguageService becomes the answer for any future query about that file.

When a tsconfig file changes (or a directory it references adds files):

  1. The service invalidates the cached project graph.
  2. The next query rebuilds the project's program with oldProgram reuse.
  3. Affected ScriptInfo mappings update.

ScriptInfo and snapshots

ScriptInfo represents a file's editor-side state. Every open file has one. Important fields:

  • fileName, path (canonical), containingProjects: Project[].
  • textStorage — a ScriptVersionCache of edit history.
  • isOpen — was the editor told to track edits to this file? Closed files are still served from disk.

ScriptVersionCache stores compressed edit histories so the server can produce a snapshot for any version cheaply. This is what powers getQuickInfoAtPosition from a "history" cursor — tsserver can ask for the file as it was three edits ago without re-walking the entire text.

AutoImportProviderProject

When a ConfiguredProject becomes large, naively walking its node_modules for every completion is too slow. The service derives an AutoImportProviderProject — a tightly scoped program that includes only package.json-listed dependency index.d.ts entry points — and uses it to populate completion auto-imports asynchronously. The behaviour is tuned by compilerOptions.maximumHoverLength, disableAutoImports, and similar flags.

Diagnostics scheduling

tsserver debounces diagnostic computation. When the user types, the server schedules a geterr request after a short delay; if more typing happens it pushes the deadline. When the deadline fires, the server walks open files in priority order (most recently focused first), asks the project for diagnostics, and pushes events back to the editor.

Watch wiring

The project service installs file watchers for:

  • Each project's source files and tsconfig.
  • Each project's package.json files.
  • node_modules/ directories that participate in resolution.
  • Glob patterns from tsconfig.json's include/exclude (resolved to wildcard watchers).

watchGuard (see apps/watch-guard) is consulted before turning on recursive watching on Windows.

Integration points

Entry points for modification

  • Project-graph rules — editorServices.ts, specifically openClientFile, closeClientFile, findOrCreateConfigFileForOpenFile, assignProjectToOpenedScriptInfo, cleanupProjectsAndScriptInfos.
  • Project subclasses — project.ts.
  • Snapshot / version-cache changes — scriptVersionCache.ts.

See apps/tsserver for the wrapping process and systems/language-service for what each project ultimately serves.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Project system – TypeScript wiki | Factory