char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somexperiment: use native typescript lsp supportc7a9569

main
8.0 KiB141 linesraw

avril LSP

avril-lsp contains the Rust native TypeScript adapter and stdio LSP frontend. avril lsp supplies avril's live project service without initializing a guest runtime. A small in-tree patch exposes the pinned TypeScript 7.1.0-dev.20261013.1 native language service through --api. Build the executable using the backend instructions.

cargo build -p avril-cli --bin avril
target/debug/avril lsp --typescript=/absolute/path/to/avril-tsc \
  --project=/absolute/path/to/workspace

The project directory defaults to the current directory. With unrestricted execution authority, workspace JS/TS files are graph roots (respecting ignore files, excluding hidden directories, node_modules, cache and vendor directories, and not following directory symlinks). With restricted authority, only explicit open documents are roots; discovery never broadens that authority. Imports grow the graph automatically, including unsaved import edits. No manifest or generated tsconfig is needed. The VS Code client launches this mode.

avril check --typescript=PATH <module> reuses the same project service and backend without an LSP session: it builds one generation rooted at the entry module, prints every diagnostic, and exits nonzero on errors. See the check documentation.

Resolution and policy

The live service reuses ModuleGraph, LocalPackages, RemotePackages, and PackageStore, not a parallel implementation of avril resolution. The in-memory type collector in runtime/src/type_graph.rs feeds live generations. It follows type-only/dynamic literal imports, package exports, per-referrer maps, and @ts-self-types declaration redirects. Builtin declarations are embedded and need no source checkout.

  • Existing avril.conf ownership, dependency bindings, and private import maps remain authoritative. TypeScript paths are not resolution authority.
  • Cache, lock selection/integrity, vendor input, frozen/offline behavior, and capability checks use the runtime implementation. Flags include --offline, --frozen, --jsr-registry=URL, --cache-dir=PATH, --lock=PATH, --vendor=PATH, -0, and --allow-*/--deny-*. There is no implicit entry grant in LSP mode.
  • The editor may acquire permitted dependencies into the cache. It never writes lockfiles, generated tsconfigs, editor snapshots, or application sources. Use an explicit runtime cache/update/vendor command to change those artifacts.
  • Unsaved contents do not bypass js-exec checks. Dependencies and configs use execution/acquisition authority, not guest filesystem read permission.
  • The shared scoped JSX/JSONC-extends loader supplies per-file compiler settings. Separate native programs provide checking contexts for differing JSX settings. Other TypeScript checking settings currently use the adapter host defaults.

Document changes and close/revert rebuild the graph. Directory watches cover the workspace, known source/config parents, explicit lock and vendor inputs, and external tsconfig extends (including failed config probes). Watch invalidation is coalesced on a 500 ms tick. Cache writes are excluded to avoid refresh loops. Under restricted execution capabilities, native directory watches are disabled; client workspace/didChangeWatchedFiles notifications still refresh the graph. The VS Code client supplies workspace file notifications. Malformed/unavailable inputs produce resolver diagnostics rather than silently retaining old edges.

Language service and boundaries

The native backend supplies rich hover and signature help, completion and resolve (including snippets, rewrites and graph-scoped auto-imports), definitions/type definitions/implementations, references, prepare-rename/rename, document and workspace symbols, highlights, folding/selection ranges, semantic tokens, inlay hints, call hierarchy, code lenses, JSX linked editing, formatting, and native quick fixes/fix-all/organize-import actions. Capabilities and the method allowlist come from the patch, not a parallel Rust feature catalog. Diagnostics include native suggestion tags and related locations as well as Avril's resolver errors.

  • ProjectService produces immutable ProjectInput generations from overlays. Acquisition/configuration stay in the host. Resolved imports contain canonical module URIs and separate import/require targets. The adapter installs a static TypeScript resolution table with no fallback discovery; resolution never calls back into the runtime. Authorized auto-import candidates are supplied separately.
  • project owns compiler-only per-module directories, snapshots and checking contexts. Original source text is unchanged. Cross-program searches visit only explicit host-created compiler-option groups.
  • bridge maps document identities and native LSP results, stores bounded opaque continuations, and rejects stale resolves with ContentModified (-32801). A rename touching a read-only source is rejected as a whole, never partially applied. No AST decoding, signature synthesis, or completion reconstruction remains in Rust.
  • backend supervises bidirectional requests and callbacks. Cancellation reaches native request contexts; disconnect and shutdown kill/reap the subprocess with bounded cleanup.
  • Auto-import indexing uses acquired programs only. The runtime reverse-resolves local candidates through its normal scoped resolver; the patch uses those explicit specifiers instead of inventing imports from compiler-only paths.
  • Workspace locations retain canonical file URIs. Acquired npm files use read-only avril-builtin:/dependencies/ identities, and their overlays are ignored even when opened through their underlying file paths. Remote and builtin sources use read-only avril-https:, avril-http:, and avril-builtin: providers. Embedded TypeScript libraries use avril-builtin:/typescript/lib.*.d.ts and are readable only when present in an explicit program.

Native TypeScript preferences can be provided in initialize.initializationOptions.preferences and replaced with workspace/didChangeConfiguration.settings.preferences. These include inlay hint and code-lens preferences (disabled by upstream defaults), formatting, and completion preferences. Missing properties preserve snapshot defaults.

Remaining limits

This is not a claim of complete classic tsserver parity. The pinned native backend does not implement the full classic refactor catalog or type hierarchy. File-move/import-update operations, project-reference discovery, untitled documents without file identities, and acquisition of otherwise unreachable packages solely for auto-import suggestions are not implemented. Unsupported providers are not advertised; unknown methods return MethodNotFound.

Document notifications are coalesced, and failed backends recover before the next query. Generations still rebuild the host graph and native session, favoring straightforward invalidation over incremental performance. Directory watches are coarse; deferred results must be requested again after a refresh. Compiler library globals are not yet curated to Avril's exact runtime.

The native executable must be trusted. Its environment is cleared and filesystem callbacks restrict compiler inputs, but they are not an OS sandbox. Project code and external compiler plugins are not executed. Guest npm/Node compatibility and automatic backend distribution are not introduced.

Validation

AVRIL_TEST_TYPESCRIPT="$TSC" cargo nextest run --workspace
cargo test --workspace --doc
cargo clippy -p avril-lsp -p avril-runtime --all-targets -- -D warnings
cd editors/vscode && pnpm test

$TSC is the backend's absolute path; tests that need it are skipped when AVRIL_TEST_TYPESCRIPT is unset. Lifecycle tests inspect Linux /proc. The pinned backend is the test suite's only external dependency. Actual editor-host tests and the required success marker are described in the client README.

1# avril LSP
2
3`avril-lsp` contains the Rust native TypeScript adapter and stdio LSP frontend.
4`avril lsp` supplies avril's live project service without initializing a guest
5runtime. A small [in-tree patch](typescript/README.md) exposes the pinned
6TypeScript `7.1.0-dev.20261013.1` native language service through `--api`.
7Build the executable using the [backend instructions](BACKEND.md#build).
8
9```sh
10cargo build -p avril-cli --bin avril
11target/debug/avril lsp --typescript=/absolute/path/to/avril-tsc \
12  --project=/absolute/path/to/workspace
13```
14
15The project directory defaults to the current directory. With unrestricted
16execution authority, workspace JS/TS files are graph roots (respecting ignore
17files, excluding hidden directories, node_modules, cache and vendor directories,
18and not following directory symlinks). With restricted authority, only explicit
19open documents are roots; discovery never broadens that authority. Imports grow
20the graph automatically, including unsaved import edits. No manifest or generated
21tsconfig is needed. The
22[VS Code client](../../editors/vscode/README.md) launches this mode.
23
24`avril check --typescript=PATH <module>` reuses the same project service and
25backend without an LSP session: it builds one generation rooted at the entry
26module, prints every diagnostic, and exits nonzero on errors. See
27[the check documentation](../../docs/check.md).
28
29## Resolution and policy
30
31The live service reuses `ModuleGraph`, `LocalPackages`, `RemotePackages`, and
32`PackageStore`, not a parallel implementation of avril resolution. The in-memory
33type collector in `runtime/src/type_graph.rs` feeds live generations. It follows
34type-only/dynamic literal imports, package exports, per-referrer maps, and
35`@ts-self-types` declaration redirects.
36Builtin declarations are embedded and need no source checkout.
37
38- Existing `avril.conf` ownership, dependency bindings, and private import maps
39  remain authoritative. TypeScript `paths` are not resolution authority.
40- Cache, lock selection/integrity, vendor input, frozen/offline behavior, and
41  capability checks use the runtime implementation. Flags include `--offline`,
42  `--frozen`, `--jsr-registry=URL`, `--cache-dir=PATH`, `--lock=PATH`,
43  `--vendor=PATH`, `-0`, and `--allow-*`/`--deny-*`. There is no implicit entry
44  grant in LSP mode.
45- The editor may acquire permitted dependencies into the cache. It never writes
46  lockfiles, generated tsconfigs, editor snapshots, or application sources. Use
47  an explicit runtime cache/update/vendor command to change those artifacts.
48- Unsaved contents do not bypass `js-exec` checks. Dependencies and configs use
49  execution/acquisition authority, not guest filesystem read permission.
50- The shared scoped JSX/JSONC-extends loader supplies per-file compiler
51  settings. Separate native programs provide checking contexts for differing JSX
52  settings. Other TypeScript checking settings currently use the adapter host
53  defaults.
54
55Document changes and close/revert rebuild the graph. Directory watches cover the
56workspace, known source/config parents, explicit lock and vendor inputs, and
57external tsconfig extends (including failed config probes). Watch invalidation
58is coalesced on a 500 ms tick. Cache writes are excluded to avoid refresh loops.
59Under restricted execution capabilities, native directory watches are disabled;
60client `workspace/didChangeWatchedFiles` notifications still refresh the graph.
61The VS Code client supplies workspace file notifications. Malformed/unavailable
62inputs produce resolver diagnostics rather than silently retaining old edges.
63
64## Language service and boundaries
65
66The native backend supplies rich hover and signature help, completion and
67resolve (including snippets, rewrites and graph-scoped auto-imports),
68definitions/type definitions/implementations, references, prepare-rename/rename,
69document and workspace symbols, highlights, folding/selection ranges, semantic
70tokens, inlay hints, call hierarchy, code lenses, JSX linked editing, formatting,
71and native quick fixes/fix-all/organize-import actions. Capabilities and the
72method allowlist come from the patch, not a parallel Rust feature catalog.
73Diagnostics include native suggestion tags and related locations as well as
74Avril's resolver errors.
75
76- `ProjectService` produces immutable `ProjectInput` generations from overlays.
77  Acquisition/configuration stay in the host. Resolved imports contain canonical
78  module URIs and separate import/require targets. The adapter installs a static
79  TypeScript resolution table with no fallback discovery; resolution never calls
80  back into the runtime. Authorized auto-import candidates are supplied separately.
81- `project` owns compiler-only per-module directories, snapshots and checking
82  contexts. Original source text is unchanged. Cross-program searches visit only
83  explicit host-created compiler-option groups.
84- `bridge` maps document identities and native LSP results, stores bounded opaque
85  continuations, and rejects stale resolves with `ContentModified` (-32801).
86  A rename touching a read-only source is rejected as a whole, never partially
87  applied. No AST decoding, signature synthesis, or completion reconstruction
88  remains in Rust.
89- `backend` supervises bidirectional requests and callbacks. Cancellation reaches
90  native request contexts; disconnect and shutdown kill/reap the subprocess with
91  bounded cleanup.
92- Auto-import indexing uses acquired programs only. The runtime reverse-resolves
93  local candidates through its normal scoped resolver; the patch uses those
94  explicit specifiers instead of inventing imports from compiler-only paths.
95- Workspace locations retain canonical file URIs. Acquired npm files use
96  read-only `avril-builtin:/dependencies/` identities, and their overlays are
97  ignored even when opened through their underlying file paths. Remote and builtin
98  sources use read-only `avril-https:`, `avril-http:`, and `avril-builtin:` providers.
99  Embedded TypeScript libraries use `avril-builtin:/typescript/lib.*.d.ts` and
100  are readable only when present in an explicit program.
101
102Native TypeScript preferences can be provided in
103`initialize.initializationOptions.preferences` and replaced with
104`workspace/didChangeConfiguration.settings.preferences`. These include inlay
105hint and code-lens preferences (disabled by upstream defaults), formatting, and
106completion preferences. Missing properties preserve snapshot defaults.
107
108### Remaining limits
109
110This is not a claim of complete classic tsserver parity. The pinned native
111backend does not implement the full classic refactor catalog or type hierarchy.
112File-move/import-update operations, project-reference discovery, untitled
113documents without file identities, and acquisition of otherwise unreachable
114packages solely for auto-import suggestions are not implemented. Unsupported
115providers are not advertised; unknown methods return MethodNotFound.
116
117Document notifications are coalesced, and failed backends recover before the
118next query. Generations still rebuild the host graph and native session, favoring
119straightforward invalidation over incremental performance. Directory watches
120are coarse; deferred results must be requested again after a refresh. Compiler
121library globals are not yet curated to Avril's exact runtime.
122
123The native executable must be trusted. Its environment is cleared and filesystem
124callbacks restrict compiler inputs, but they are **not an OS sandbox**. Project
125code and external compiler plugins are not executed. Guest npm/Node
126compatibility and automatic backend distribution are not introduced.
127
128## Validation
129
130```sh
131AVRIL_TEST_TYPESCRIPT="$TSC" cargo nextest run --workspace
132cargo test --workspace --doc
133cargo clippy -p avril-lsp -p avril-runtime --all-targets -- -D warnings
134cd editors/vscode && pnpm test
135```
136
137`$TSC` is the backend's absolute path; tests that need it are skipped when
138`AVRIL_TEST_TYPESCRIPT` is unset. Lifecycle tests inspect Linux `/proc`.
139The pinned backend is the test suite's only external dependency. Actual
140editor-host tests and the required success marker are described in
141[the client README](../../editors/vscode/README.md#validation).