Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

30. Node and TypeScript, scoped to the VS Code client only

  • Status: Accepted
  • Date: 2026-09-05

Context

editors/vscode/ has existed since before this milestone as a grammar-only extension: a package.json manifest, a TextMate grammar, and a language configuration file — no build step, no scripts, no devDependencies, no CI coverage. Every other part of this repository, including the website (korrin-web/, hand-written HTML/CSS plus mdbook, a Rust tool) and the docs site build, is Rust or plain static files. CI is, and through three milestones has been, entirely cargo commands.

A real VS Code extension client that starts korrin-lsp and shows what it sends — live diagnostics, hover, go-to-definition — is written against the vscode extension API, which is JavaScript/TypeScript only; there is no alternative. This is not a preference to weigh against a Rust option, the way ADR 0026 weighed lsp-server against tower-lsp — VS Code extensions cannot be written in Rust today, so Node and TypeScript enter this repository's tooling for the first time out of genuine necessity, not convenience.

Decision

Node and TypeScript are scoped to editors/vscode/ only, with its own package.json, package-lock.json, and build step, kept as separate and self-contained from the Rust workspace as korrin-web/'s mdbook build already is from it.

  • TypeScript, strict mode (strict: true plus noUnusedLocals, noUnusedParameters, noImplicitReturns, noFallthroughCasesInSwitch), compiled with esbuild rather than tsc for the actual output — esbuild bundles vscode-languageclient and its own dependencies into a single out/extension.js, so the packaged extension has no node_modules to ship and no runtime dependency resolution to fail. tsc --noEmit still runs, in CI and by hand, purely for type-checking.
  • vscode-languageclient is the standard client library for exactly this: starting a language server as a subprocess and speaking the LSP over its stdio, translating between vscode's own API types and the protocol's wire types. Writing that translation layer by hand would be reimplementing a solved, boring problem for no benefit.
  • A dedicated CI job (vscode-extension, using actions/setup-node + npm ci + tsc --noEmit + the esbuild compile), separate from every Rust job. None of the existing test/lint/msrv/docs jobs touch this directory or would ever catch a TypeScript error; a broken extension client should fail its own check, not slip through unnoticed because nothing looked.
  • No dependency here is added to the Rust workspace's dependency policy (ADR 0013) or docs/internals/dependencies.md — that policy governs Cargo dependencies of Rust crates, and npm's dependency tree is a genuinely separate concern with its own separate ecosystem norms. It is not silently exempt from scrutiny; it is scoped to one small, isolated corner of the repository and reviewable there on its own terms.

Consequences

  • Building or contributing to crates/korrin, crates/korrin-cli, or crates/korrin-lsp needs nothing but cargo — Node is only required for someone touching the VS Code client specifically, the same way mdbook is only required for someone touching the docs site.
  • The repository now has two package managers with two different sets of norms (Cargo's committed Cargo.lock for a workspace that ships binaries, npm's committed package-lock.json for reproducible CI installs) rather than one. A real, ongoing cost, accepted because a VS Code extension has no Rust-only path around it.
  • The packaged .vsix does not bundle a prebuilt korrin-lsp binary for any platform (per ADR 0026's Milestone 4 scope) — a user installs the language server themselves via cargo install. Multi-platform binary packaging is real, separate work for a later milestone if this project ever wants a from-the-marketplace "it just works" install experience.

Alternatives considered

  • A webview-based or LSP-free extension avoiding TypeScript entirely. Not viable: textDocument/publishDiagnostics, hover, and go-to-definition are integrated into VS Code's own editor chrome (the Problems panel, the hover popup, F12) through the extension API, which has no non-JS/TS surface. There is no version of "show live diagnostics in the editor" that avoids this.
  • Hand-rolled JSON-RPC in the client, skipping vscode-languageclient. Symmetric to korrin-lsp not hand-rolling its own transport (ADR 0026) — the framing, cancellation, and API-translation work is exactly the kind of solved problem worth depending on a library for.
  • Fold the Node/TypeScript check into an existing CI job rather than a new one. Rejected: none of the existing jobs' names, caching strategy, or failure semantics have anything to do with this, and a Rust contributor reading a red test job should not have to wonder whether their Rust change broke something or a TypeScript file did.