char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
c7a9569
main
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 avriltarget/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.confownership, dependency bindings, and private import maps remain authoritative. TypeScriptpathsare 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-execchecks. 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.
ProjectServiceproduces immutableProjectInputgenerations 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.projectowns 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.bridgemaps document identities and native LSP results, stores bounded opaque continuations, and rejects stale resolves withContentModified(-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.backendsupervises 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-onlyavril-https:,avril-http:, andavril-builtin:providers. Embedded TypeScript libraries useavril-builtin:/typescript/lib.*.d.tsand 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 warningscd 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 39remain authoritative. TypeScript `paths` are not resolution authority. 40- Cache, lock selection/integrity, vendor input, frozen/offline behavior, and 41capability 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 44grant in LSP mode. 45- The editor may acquire permitted dependencies into the cache. It never writes 46lockfiles, generated tsconfigs, editor snapshots, or application sources. Use 47an explicit runtime cache/update/vendor command to change those artifacts. 48- Unsaved contents do not bypass `js-exec` checks. Dependencies and configs use 49execution/acquisition authority, not guest filesystem read permission. 50- The shared scoped JSX/JSONC-extends loader supplies per-file compiler 51settings. Separate native programs provide checking contexts for differing JSX 52settings. Other TypeScript checking settings currently use the adapter host 53defaults. 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. 77Acquisition/configuration stay in the host. Resolved imports contain canonical 78module URIs and separate import/require targets. The adapter installs a static 79TypeScript resolution table with no fallback discovery; resolution never calls 80back into the runtime. Authorized auto-import candidates are supplied separately. 81- `project` owns compiler-only per-module directories, snapshots and checking 82contexts. Original source text is unchanged. Cross-program searches visit only 83explicit host-created compiler-option groups. 84- `bridge` maps document identities and native LSP results, stores bounded opaque 85continuations, and rejects stale resolves with `ContentModified` (-32801). 86A rename touching a read-only source is rejected as a whole, never partially 87applied. No AST decoding, signature synthesis, or completion reconstruction 88remains in Rust. 89- `backend` supervises bidirectional requests and callbacks. Cancellation reaches 90native request contexts; disconnect and shutdown kill/reap the subprocess with 91bounded cleanup. 92- Auto-import indexing uses acquired programs only. The runtime reverse-resolves 93local candidates through its normal scoped resolver; the patch uses those 94explicit specifiers instead of inventing imports from compiler-only paths. 95- Workspace locations retain canonical file URIs. Acquired npm files use 96read-only `avril-builtin:/dependencies/` identities, and their overlays are 97ignored even when opened through their underlying file paths. Remote and builtin 98sources use read-only `avril-https:`, `avril-http:`, and `avril-builtin:` providers. 99Embedded TypeScript libraries use `avril-builtin:/typescript/lib.*.d.ts` and 100are 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).