Skip to content

Editor support: spans, the TSX projection with mappings, and a .rx language server (CP-8) - #7

Merged
ryansolid merged 11 commits into
mainfrom
claude/project-thread-jfc9a3
Oct 27, 2026
Merged

ryansolid merged 11 commits into
mainfrom
claude/project-thread-jfc9a3

Conversation

@ryansolid

@ryansolid ryansolid commented Oct 27, 2026 •

Copy link
Copy Markdown
Owner

Requested by Ryan · project thread

This PR covers phases 2 to 4 of the editor support plan, plus packaging and releases. It now targets main, which it has merged (#6, #10). Main's onSettled/onFlush (EF-7, EF-8) is ported to the span-carrying AST and to the projection.

What's here

  • Spans (new spec/11 CP-8a/b). Every expression and statement in the crate's AST is now a kind plus the byte range it was parsed from. Declarations, markup items and attributes also carry their ranges. compiler/src/spans.rs checks them. ref/ keeps lines, because it stays the grammar's spec.
  • rxc check --json (CP-8d). Diagnostics and parse errors now come with ranges: 1-based lines, 0-based UTF-16 columns (what LSP uses), and byte offsets. A diagnostic's range covers its line's nodes (decision 9).
  • The projection in the crate (CP-8c). project.rs ports ref/types/project.ts text for text. It maps every piece it copies from the .rx (decision 8). It also records coarse ranges for what it writes itself, so type errors there still land on the .rx. rxc project app.rx prints it.
  • The editor (CP-8e).
    • Parser recovery: a broken declaration becomes a parse error on its line and is skipped, and a missing expression becomes an empty name. On valid modules the recovering parse gives the strict tree with no errors, which rxc conform checks on every program.
    • rxc serve (serve.rs) answers a language server's requests. wasm.rs answers the same requests from the crate built for wasm32-unknown-unknown.
    • tooling/rx-language-server is a Volar server that runs the WebAssembly build in process (about 1 ms per analysis), or a native rxc when $RXC is set. It gives TypeScript's hover, definition, references, rename, completion and type errors on .rx, plus the crate's diagnostics with their rule IDs. The README covers Neovim, Helix and JetBrains.
    • tooling/vscode-rx keeps the existing grammar and adds a client. npm run package builds one .vsix for every platform.
  • CI and releases. .github/workflows/editor.yml builds the crate both ways and runs the LSP tests on the server (WebAssembly and native) and on the extension's bundle. It also packs the .vsix and the npm package.
    • Releasing is a version bump merged to main, and only happens once the repo variable RELEASES is on. Then:
      • @reactivescript/language-server (0.1.0) goes to npm by trusted publishing.
      • The extension goes to the VS Marketplace (an Entra identity or VSCE_PAT) and to Open VSX (OVSX_PAT).
    • Each published package is tagged. Setup is in tooling/RELEASING.md.
  • Spec. spec/11 adds CP-8a–e. Decision 6 is marked superseded for the AST and diagnostics. spec/13 TY-1c and the READMEs are updated.

Verification (after merging main)

check result
npm --prefix ref run crate 286/286 agree with ref/; every span holds; the recovering parse equals the strict one
npm --prefix ref run crate -- --project 170/170 give the same projection as types/project.ts; 264 files, 10,372 mappings, all holding
RX_CRATE=1 npm --prefix ref run types 137 pass, 0 fail on the crate's projection
npm --prefix ref run crate -- --lower 166/166 identical, 642 source maps checked
RX_CRATE=1 npm --prefix ref run compiled / ssr 99/0 (9 divergences) and 92/0/7 skip
cargo test, cargo check --features node, the wasm32 build pass
node tooling/rx-language-server/test/run.js 16/16 over LSP, on the server and on the bundled dist/server.js; CI also runs it on native rxc

The extension has not been tried inside VS Code itself, because the download is blocked in my sandbox.

Decisions taken by default (veto in spec/11)

  1. The projection maps only text it copies exactly. What it writes itself gets a coarse range that is used only to place errors.
  2. A diagnostic's range is its line's.
  3. The language server runs the crate as WebAssembly in process, so one .vsix covers all platforms. This build answers only the editor's requests. It is not decision 7's missing WebAssembly fallback for compiling.

Not here

  • Lowered source maps are still line-level.
  • There is no Zed extension yet.

🤖 Generated with Claude Code

https://claude.ai/code/session_0117BoWP4zNW5bnup8XRB8md

…item and attribute (CP-8)

Expr and Stmt become a kind plus the byte range it was parsed from; Decl,
Markup and Attr carry theirs. spans.rs checks what the spans promise (inside
the source and their parent, on the line the node records, a leaf's text is
its name) and ref/crate.ts runs it on all 280 programs. rxc check --json
gives each diagnostic and parse error a range (line-precise, as the checker
is: CN-1a).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117BoWP4zNW5bnup8XRB8md
project.rs ports ref/types/project.ts text for text and records where each
name, member, literal, written type, tag and attribute it copies came from.
rxc project prints the files, their mappings and the prelude. ref/crate.ts
--project diffs the crate's projection against ref/'s on every accepted
program and checks every mapping joins the same text (168/168, 10,337
mappings); RX_CRATE=1 npm run types checks the scenarios on it (135/135).

spec/11 CP-8 records spans and the projection; decision 6 (lines) is
superseded for the AST and diagnostics; decisions 7 (map only copied text)
and 8 (a diagnostic's range is its line's) are taken by default.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117BoWP4zNW5bnup8XRB8md
@ryansolid ryansolid self-assigned this Oct 27, 2026
parse_program_recovering leaves out a declaration that does not parse and
reads a missing member name or expression as an empty one, so half-typed
code still projects; ref/crate.ts checks it gives the same tree, with no
errors, on every program that parses. load_modules_recovering loads with
it. rxc serve answers a language server, a JSON request a line: a file's
projection with UTF-16 mappings, and its diagnostics.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117BoWP4zNW5bnup8XRB8md
tooling/rx-language-server: Volar over the crate's TSX projection, through
`rxc serve`. TypeScript's hover, definition, references, rename, completion
and type errors map back onto .rx (exact mappings; ranges place type errors
that fall between them), and the crate's parse errors and diagnostics come
with their rules. test/run.js drives it over LSP on examples/shop: 16/16.

tooling/vscode-rx: an IPC client that starts the server for .rx documents.

spec/11: CP-8e, and decision 9 (rxc as a process per analysis, vetoable).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117BoWP4zNW5bnup8XRB8md
@ryansolid ryansolid changed the title Editor groundwork: spans in the crate and the TSX projection with mappings (CP-8) Editor support: spans, the TSX projection with mappings, and a .rx language server (CP-8) Oct 27, 2026
compiler: serve.rs holds rxc serve's requests; wasm.rs answers them as
WebAssembly (wasm32-unknown-unknown, a C ABI with the host reading files).

rx-language-server: loads the WebAssembly build in process (about 1 ms per
analysis), or runs $RXC as before; the same answers either way, and the LSP
tests pass on both. README: running it over stdio, and setup for Neovim,
Helix and JetBrains (LSP4IJ).

vscode-rx: build.js bundles the client, the server and rxc.wasm into dist/;
npm run package makes one .vsix for every platform. README: installing,
building, publishing.

.github/workflows/editor.yml: builds the crate both ways, runs the LSP
tests on the server and on the bundle, uploads the .vsix; a vscode-v* tag
publishes to the VS Marketplace and Open VSX when their tokens are set.

spec/11: decision 9 now WebAssembly in process (vetoable).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117BoWP4zNW5bnup8XRB8md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117BoWP4zNW5bnup8XRB8md
… diagnostics whole

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117BoWP4zNW5bnup8XRB8md
… document's URI

Volar hands a service the root virtual code's document; its URI encodes the
source's (volar-embedded-content://root/<file uri>). Decoding it with fsPath
gave a path under /, which only worked where / was writable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117BoWP4zNW5bnup8XRB8md
The publish job signs in to Microsoft Entra with the job's OIDC token
(azure/login, a federated credential on an identity that is a member of the
publisher) and runs vsce publish --azure-credential: no stored token. Open
VSX keeps a token until its CLI supports trusted publishing. The job runs in
the release environment. Fixes the Open VSX step's if, which could not see a
step-level env.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117BoWP4zNW5bnup8XRB8md
…n or Entra

editor.yml: on main (once the variable RELEASES is on), publishes each
package whose version is new and tags it. The language server goes to npm as
@reactivescript/language-server by trusted publishing (OIDC, provenance);
the extension to the VS Marketplace (an Entra identity if configured, else
VSCE_PAT) and Open VSX (OVSX_PAT). The build packs both.

rx-language-server: publishable (prepack puts rxc.wasm in src/), 0.1.0.
tooling/RELEASING.md: the one-time setup and how to release.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117BoWP4zNW5bnup8XRB8md
Conflicts: Cargo.toml's crate-type comment (both builds use the cdylib),
emit.rs's EF-7/EF-8 call and check.rs's returns_closure/hook check ported to
the span-carrying AST (ExprKind/StmtKind), spec/11's decisions renumbered
(main's 7 stays; the editor's are 8, 9, 10). The projection's BUILTINS and
prelude take onSettled/onFlush (ref/crate.ts --project 170/170).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117BoWP4zNW5bnup8XRB8md
@ryansolid
ryansolid changed the base branch from claude/project-thread-axchtp to main October 27, 2026 18:31
@ryansolid
ryansolid merged commit 057b96c into main Oct 27, 2026
14 checks passed
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.

2 participants