Skip to content

[RL-67] tsserver Language Service plugin for live .rs types (Milestone 3) - #44

Merged
yeskiy merged 3 commits into
mainfrom
rl-67-typed-rs-imports-m3
Jun 29, 2026
Merged

[RL-67] tsserver Language Service plugin for live .rs types (Milestone 3)#44
yeskiy merged 3 commits into
mainfrom
rl-67-typed-rs-imports-m3

Conversation

@yeskiy

@yeskiy yeskiy commented Jun 28, 2026

Copy link
Copy Markdown
Owner

What

Milestone 3 of typed .rs imports (RL-67): a TypeScript Language Service plugin (rust-wasmpack-loader/tsserver) that gives .rs imports their precise wasm-bindgen types live in any tsserver-based editor (VS Code, JetBrains, Neovim) with no editor extension - just a tsconfig.json plugins entry.

How

  • The plugin proxies the language-service host (the typescript-plugin-css-modules pattern): a .rs file resolves as a declaration, getScriptKind reports it as TS, and getScriptSnapshot serves the generated .d.ts.
  • Our generator is slow (a wasm-pack build), so synchronous LS calls never block: they serve the cached / on-disk-sidecar / floor types and enqueue a background build (deduped by content hash). On completion the per-file version bumps and markAsDirty() + refreshDiagnostics() fire, so the editor repaints with precise types.
  • It never writes to disk (the M1/M2 sidecar already serves tsc/CI/cold-start); it only reads that sidecar to warm its cache. It uses the typescript instance tsserver passes in.
  • generateTypes is split into buildTypedDts (raw .d.ts, no write) + the sidecar write, so the plugin types without touching disk.

Verified in CI (this PR also fixes a coverage gap)

The loader's src/ unit tests had no CI coverage before - the matrix only ran example tests, and tsc ignores plugins, so the plugin was untested in CI. This PR adds a root npm test and a . matrix entry, so all 31 src tests run across the matrix, including:

  • the plugin's programmatic proof: a real ts.createLanguageService sees the floor (0 diagnostics) before the build, then precise types after - lib.fibonacci(1) accepted, lib.nope() flagged.
  • the M1/M2 transform/emission units that were previously local-only.

Locally (GNU toolchain): 31/31 src tests, example/typed-imports 2/2, eslint clean, SonarCloud-safe.

Documented, not CI-gated (per the agreed scope)

An LS plugin only runs inside a live tsserver, so the editor repaint reaction to refreshDiagnostics() and per-IDE loading (VS Code's one-time "Use Workspace Version") can't be tested in CI. Both are documented in the src/tsserver.js header and the example's .vscode/settings.json.

Scope

Milestone 3 of the epic. Remaining: classes/structs typing, then docs.

yeskiy added 3 commits June 29, 2026 02:25
Ship a TypeScript Language Service plugin (`rust-wasmpack-loader/tsserver`) that
gives `.rs` imports their precise wasm-bindgen types live in any tsserver-based
editor, with no editor extension. It proxies the language-service host: a `.rs`
file resolves as a declaration and its snapshot is the generated `.d.ts`. The
generator is slow (a wasm-pack build), so synchronous calls never block - they
serve the cached, on-disk, or floor types and run the build in the background,
then bump the script version and refresh so the editor repaints. The plugin
never writes to disk; it seeds from the M1/M2 sidecar.

- src/tsserver.js + root tsserver.js re-export: the plugin
- generateTypes: extract `buildTypedDts` so the plugin types without writing
- ci: run the loader's src unit tests (incl. the plugin's programmatic LS proof)
  in the matrix; they had no CI coverage before
- example/typed-imports: the `plugins` tsconfig entry + a .vscode workspace-TS nudge

The live editor-refresh and per-IDE loading run only inside a real tsserver, so
they are documented in the plugin header rather than CI-gated.

Refs: RL-67
`node --test` runs test files in parallel, and several src tests spawn a
wasm-pack/cargo build; on a cold cargo cache (CI) the concurrent builds wedge on
the registry lock. Run with `--test-concurrency=1` so one build runs at a time,
add `--test-force-exit` against any lingering handle, and force-kill the
`--watch` test child with a bounded wait so teardown cannot hang.

Refs: RL-67
`Promise.allSettled` accepts an iterable, so spreading the Map values into an
array first is unnecessary (SonarCloud S7747).

Refs: RL-67
@yeskiy
yeskiy merged commit 2a3ccbe into main Jun 29, 2026
180 checks passed
@yeskiy
yeskiy deleted the rl-67-typed-rs-imports-m3 branch June 29, 2026 01:56
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant