Commit Graph

522 Commits

Author SHA1 Message Date
Sysix a5a4ac8811 refactor(lsp): ToolTrait consume TextDocument & CodeActionParams (#26339)
Refactors the `oxc_language_server::Tool` trait and associated LSP plumbing to pass `TextDocument` and `CodeActionParams` by value, aligning tool APIs with “consume” semantics and simplifying call sites that previously had to keep borrowed values alive.
2026-09-05 18:33:58 +00:00
Sysix ca7104f775 refactor(lsp): dont create temporary map in collect_diagnostics_with (#26341)
<summary>Pull request overview</summary>

This PR refactors the language server’s `WorkspaceWorker::collect_diagnostics_with` to avoid building an intermediate map when collecting tool diagnostics, returning the tool’s `DiagnosticResult` directly.

This was an old code path when `oxc_language_server` did used both tools (`oxlint` & `oxfmt`) at the same time
2026-09-04 21:36:42 +00:00
Sysix 3b7e45399d refactor(oxlint/lsp): remove offset_to_position (#26336)
With https://github.com/oxc-project/oxc/pull/23329 the conversion from offset to line/column position is now done by a helper function in `oxc_language_server`.
There was a helper function inside `oxlint` to call the function. Instead, call it directly now.
Moved the tests to the correct place, and removed duplicated ones.
2026-09-04 14:00:48 +00:00
Alexander S. 409b4e27f3 perf(lsp): increase concurrency level by available threads (#25153)
> Adjusts the Oxc language server startup to scale Tower LSP request
handling concurrency based on the machine’s available parallelism,
aiming to improve throughput on higher-core systems without fully
saturating the host.

The saturation is from personal feeling and did not get benchmarked.

---------

Signed-off-by: Alexander S. <sysix@sysix-coding.de>
Signed-off-by: Yuji Sugiura <6259812+leaysgur@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Yuji Sugiura <6259812+leaysgur@users.noreply.github.com>
2026-08-31 00:27:18 +00:00
Sysix 0e969e8520 docs(lsp): update LanguageId docs to target the file extension first (#26068)
Updated the `LanguageId` docs, to reflect how the real implementation works.
We did a "rollback" to check for the file extension first, to align with the CLI.
This was specially a problem for jsonc LanguageId with json file extension, where a trailing comma was always added by the editor.

Related https://github.com/oxc-project/oxc/issues/25893
2026-08-24 16:37:20 +00:00
Hamody We d893fd4cab fix(lsp): detach workspace/diagnostic/refresh from notification handlers to prevent deadlock (#25438)
## What this PR does

Fixes the permanent language-server deadlock reported in #24955.

`workspace/diagnostic/refresh` is a server-to-client *request*, and a
spec-compliant client may re-pull `textDocument/diagnostic` from this
same server before replying to it. Three notification handlers
(`initialized`, `did_change_configuration`, `did_change_watched_files`)
awaited that round-trip inside the handler itself, pinning one of the
transport's concurrency slots (tower-lsp-server defaults to 4) for as
long as the client took to reply. Four rapid watcher events — e.g. four
saves of `.oxlintrc.json` or any `tsconfig*.json` in pull-diagnostic
mode — pinned all four slots on handlers waiting for the client, while
the client waited on diagnostic pulls the server could no longer
service. Circular wait, no timeout, no recovery until the process is
killed.

The refresh is advisory — nothing consumes its result beyond logging a
warning — so this PR detaches it: a new
`Backend::spawn_diagnostic_refresh()` clones the client and spawns the
round-trip, returning the handler's slot immediately. The server keeps
answering requests with any number of refresh replies outstanding. This
is the issue's first suggested fix; raising the concurrency level
(#25153) only widens the window rather than closing it, as the issue
notes.

## How it's tested

`test_outstanding_diagnostic_refreshes_do_not_wedge_the_server` runs in
the crate's existing wire-level `TestServer` harness — real LSP framing
over duplex streams, the real `Server` at its real default concurrency
of 4. It initializes in pull mode with dynamic watchers, fires four
watched-file events, reads each resulting `workspace/diagnostic/refresh`
request **without replying** (what a busy client looks like), then sends
`shutdown` and requires a response within a 10s timeout.

Verified both directions: against the previous code the test fails by
timeout at exactly the reported wedge point; with the fix, the full
crate suite passes (85/85). `cargo fmt` and `clippy --all-targets` are
clean for the changed files. The only other change is adding tokio's
`time` feature to dev-dependencies for the test's timeout.

## AI disclosure

Investigated, implemented, and verified with AI assistance (Claude
Code), per the contributing guide's disclosure policy. I traced the root
cause to the transport's concurrency accounting, confirmed the circular
wait matches the reporter's reproduction, and validated the fix with the
deadlock-reproducing regression test above before submission.

Co-authored-by: Alexander S. <sysix@sysix-coding.de>
2026-08-24 01:22:58 +02:00
Sysix 288f9a5188 refactor(lsp): reduce client messages to the client to a max of 5 (#25962)
> ## Pull request overview
>
> This PR refactors the language server’s client-notification flow to avoid flooding editors with too many `window/showMessage` notifications by capping the number of messages sent in a batch to 5.
>

Test generated with GPT-5.3-Codex 🤖
2026-08-22 14:06:19 +00:00
Sysix 7e2f193dc2 refactor(lsp): allow tools to send multiple ClientMessages (#25961)
> ## Pull request overview
>
> Refactors the language-server tool initialization/restart plumbing to support emitting multiple `ClientMessage`s (instead of a single optional message), so tools can surface more than one warning/error to the client during startup and config changes.
>
2026-08-22 10:17:51 +00:00
Boshen 794891d93a perf(lsp): deduplicate server future (#25767)
Type-erase the shared LSP server future so CLI and NAPI reuse one async state machine. This removes duplicate LSP machinery and reduced the measured release binary by about 1.3 MiB.

AI assistance was used to implement this change.
2026-08-17 07:38:12 +00:00
Sysix a2619a9d78 refactor(lsp): show ClientMessage on dynamic worker creation (#25407)
> ## Pull request overview
>
> This PR updates the language server’s dynamic worker startup paths to surface `ClientMessage` warnings/errors to the client when a worker is created/started dynamically, and adds a regression test for single-file mode.
>
2026-08-09 12:02:18 +00:00
Sysix 2eb3916a45 fix(lsp): remove capability check for window/showMessage notification (#25402)
The `window/showMessage` notification is a baseline feature in LSP and does not need Client capability `window.showMessage`. This capability is only needed for the `window/showMessageRequest` request.

- https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#window_showMessage
- https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#window_showMessageRequest
2026-08-08 23:51:33 +00:00
Alexander S. 36d4d9b0cb feat(lsp): send tool client messages to lsp client (#25401)
The newly created `ClientMessage` will now be passed into `Backend` and
sent to the client.
There is atm no real implementation in `oxlint` & `oxfmt`, but the LSP
integration is done (expect dynamic worker creation, see #25407).
2026-08-08 22:24:33 +00:00
Alexander S. c7583238a0 refactor(lsp): add ToolBuildResult struct for optional client message (#25386)
Added a new `ToolBuildResult` struct, so an optional `ClientMessage` can
be passed to `Backend` (in a later PR).
This allows us later to send the custom message to the clients interface
when some config errors happened on the (re)creation.

There is no message sent yet, this will be integrated in another PR too.
This PR is just the base for the `ToolInterface`.
2026-08-08 17:02:23 +00:00
leaysgur 725d106490 docs(language_server): update options doc (#24967) 2026-07-27 01:37:30 +00:00
Sysix 74b5b484c2 test(lsp): add response buffer (#24086)
Working on the stack https://github.com/oxc-project/oxc/pull/23768 I created a local test with many responses in a quick time. This code change did resolve the issue.

Auto-merge because it only effects tests, even if not perfect :)

Autogenerated by Codex AI Agent 🤖
2026-07-26 15:20:27 +00:00
Sysix 7be5cf08f3 perf(oxlint/lsp): only invoke lint on code actions when document is not opened (#24676)
Some clients can send code actions for documents, which are not opened and already analyzed with a lint process.
A good example is the IntelliJ plugin, which allows the "fix all" code action from the file explorer.

> This PR refactors the language-server → tool code-action API to pass a single request struct (including an “is document open” flag), enabling the oxlint LSP linter to avoid re-running lint for code-action requests when the document is already open (and presumably already linted/cached).

This is mostly needed for https://github.com/oxc-project/oxc/pull/23768
2026-07-19 17:16:22 +00:00
camc314 66e1a25c0e chore(all): fix clippy::manual_assert_eq (#24351) 2026-07-10 09:57:05 +00:00
Sysix d86f60b61c fix(lsp): normalize user config path to watch pattern (#23723)
When a user defined a custom config path, like `fmt.configPath` or `configPath`, the watch pattern returned the same path.
VS Code does not respect this as a valid pattern and does not watch for files defined like `./define-config.ts`.
Resulting into an LSP client, which will not send a watched-file-change requests to the server, when the user edited an important config path.
2026-06-25 00:15:37 +00:00
Sysix a401de8a7b test(lsp): fix unused imports for non-unix platforms (#23399) 2026-06-14 11:59:06 +00:00
konh 956309aa26 fix(lsp): use protocol line breaks for positions (#23329)
Fixes #23314

## Summary

- add an LSP-specific UTF-8 byte offset to `Position` converter
- use it for oxfmt formatter `TextEdit` ranges
- route oxlint LSP positions through the same converter

## Details

The existing conversion used the shared rope helper, which treats
Unicode line
and paragraph separators as line breaks. That is correct for some
JavaScript
source-location semantics, but LSP only treats CR, LF, and CRLF as line
breaks.

This keeps the shared rope helper unchanged and isolates the
protocol-specific
behavior in `oxc_language_server`.

## Tests

- `cargo fmt --all`
- `cargo test -p oxc_language_server position --lib`
- `cargo test -p oxfmt --lib lsp::server_formatter`
- `cargo test -p oxlint --lib lsp::error_with_position`
- `cargo clippy -p oxc_language_server -p oxfmt -p oxlint --lib --tests
--no-deps -- -D warnings`
- `git diff --check`

---------

Co-authored-by: xiaokhkh <10765730+xiaokhkh@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Sysix <sysix@sysix-coding.de>
2026-06-12 14:33:53 +02:00
Sysix 5a2981bb43 fix(lsp): notifiy every workspace for a watched file change (#22839)
When a watched file changes, it can affect multiple workspace folders. On a nested workspace set up the root oxlint config could be used multiple times.
The same for nested workspaces where only "one" config path is targeted with LSP settings.

related https://github.com/oxc-project/oxc-vscode/issues/97
closes https://github.com/oxc-project/oxc/issues/17515
2026-05-31 20:36:29 +00:00
Sysix 668f0d72e4 fix(lsp): return absolute path patterns without relative base uri (#22832)
When editors are sending absolute config paths like `configPath: "/home/user/.../.oxlintrc.json`, the relative information is not needed.
Do not know how editors will handle absolute path with a relative base URI. To make sure, everyone knows how to look it up, absolute patterns are not tied to base URI.
2026-05-31 20:29:37 +00:00
Sysix a769b33b01 perf(lsp): avoid some clones (#22813)
Found some places where `clone()` was too much and the ownership could be moved instead.
2026-05-29 09:25:26 +00:00
Sysix bbc410c5df refactor(lsp): WorkspaceWorker.init_watchers return single registration (#22809)
`init_watchers` was returning a `Vec<Registration>`, this was needed where the Worker need to manager multiple tools. Now it only owns one tool and can use `Option<Registration>` instead.
2026-05-28 19:27:24 +00:00
Sysix 5a4414d0fa feat(oxlint/lsp): support rulesCustomization lsp option (#21858)
> Adds support for an LSP workspace option (`rulesCustomization`) that lets clients customize rule severity and control whether specific rules participate in “fix all” code actions.

related https://github.com/oxc-project/oxc-vscode/issues/22
closes https://github.com/oxc-project/oxc/issues/21538
2026-05-05 09:57:06 +00:00
Sysix 19c1d5bd8f chore(lsp): create dynamic worker on start_manager (#21656)
Because `DiagnosticMode` can only be evaluated with the `ClientCapabilities` from the editor, we need to delay the creation of the dynamic worker.
It works atm, because we know `oxfmt` is always `DiagnosticMode::None`, but I have plans to make `oxlint` compatible with the `ManagerMode::DynamicWithWorkspaces`.

Bonus: The dynamic worker is only created once, use `OnceCell` instead of a lock
2026-04-27 10:03:02 +00:00
Sysix e37cfc46b3 feat(oxfmt/lsp): format files outside workspace folders (#21481)
closes https://github.com/oxc-project/oxc-vscode/issues/180

`oxfmt` config architecture allows running a second instance of the formatter. This worker is responsible now for single file mode and files outside the current workspace.

> In `oxfmt` LSP context, user want to use the server without a context of a project.
They want just open a file with `code /Users/local-settings/file.json`, which we already supported with the `single_file_mode` flag.
But they also expect to format such files when you have already opened a project.

It will not create watchers for this manager because they would be too expensive, atm the architecture only works with the root path of the workspace. In the example of the dynamic workspace, it would watch the complete file system.

This will "break" the single file mode in `oxfmt --lsp`, when a user edits the oxfmt config on the same level where an edited file is formatted.

Tested it with VS Code and WSL2 (linux) and Win10 native. Beside #21654 everything works as expected 🥳
2026-04-27 09:53:41 +00:00
Dragan Okanovic 71f3286cb4 docs(linter): Fix the FixKind default value (#21721)
Updates the README.md to match what's in the source code:

https://github.com/oxc-project/oxc/blob/main/apps/oxlint/src/lsp/options.rs#L40-L48


https://github.com/oxc-project/oxc/blob/2ccede2ea75b9d2c3d0b56f3ef58eca8363189a6/apps/oxlint/src/lsp/options.rs#L40-L48

---------

Signed-off-by: Dragan Okanovic <1355455+abstractalgo@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Cameron <cameron.clark@hey.com>
2026-04-24 20:56:23 +01:00
Sysix d46ab7a01a refactor(lsp): remove RwLock on file_system property (#21704)
`LSPFileSystem` only holds a `papaya::HashMap` which concurrent hash-table without any locks.
Having `RwLock` here does only creates overhead
2026-04-24 11:50:00 +00:00
Sysix cf38a0bffb refactor(lsp): delay/avoid worker lock on Backend (#21691)
This was a performance PR at first, but I am not sure if this will be noticeable.
Downsides first:
- We now always write to the file system, even if a worker could not be found

Upsides:
- Editing a file from another workspace, and then requesting formatting, did result into an invalid state (the in memory state was not set yet)
- on `textDocument/didOpen` & `textDocument/didSave` we avoid the worker lock completely if
  - Diagnostic mode is "none" (for `oxfmt`)
  - Diagnostic mode is "pull" (for `oxlint`, where the client does not support `textDocument/diagnostic` request)
2026-04-24 09:05:20 +00:00
Sysix 7816aa2d6c refactor(lsp): remove self reference in find_worker_for_uri (#21688)
Thought that the self reference will be used in a later stack, but the current implementation of the dynamic workspace worker looks good at the moment.
Little refactoring
2026-04-23 20:06:27 +00:00
Sysix 63eb98b6f2 chore(lsp): don't register dynamic worker watch patterns (#21620) 2026-04-21 20:23:10 +00:00
Sysix 5a6dab2149 chore(lsp): dynamic worker: watch pattern & execute command (#21545)
Added logic for dynamic worker when first initializing the watch patterns. These will never change, not like other watch pattern which can be later be changed with `workspace/didChangeConfiguration`.
Dynamic Workers are not linked into this method, because it is not directly an LSP concept, instead something the LSP server needs to handle internally.
`workspace/executeCommand` is respected too. These are the only missing code paths :)
2026-04-20 09:06:25 +00:00
Sysix 428fa25e95 chore(lsp): add WorkerManager::start_manager and stop_manager (#21528) 2026-04-20 08:59:56 +00:00
Sysix b0817ae645 refactor(lsp): remove Tool::name trait method (#21530)
This method was used to create unique watcher IDs for each tool.
Because `oxc_language_server::run_server` now accepts only one tool, we can safely remove this method.
2026-04-17 15:18:24 +00:00
Sysix 667ce50dc8 docs(lsp): document Errors on run_ worker methods (#21480)
Added some error docs, for later when we "maybe" make the Worker & Manager public.
Should not be wrong to have it even when we not make it public :)
2026-04-17 14:24:46 +00:00
Sysix 32c6f2665d test(lsp): implement ManagerMode::DynamicWithWorkspaces (#21479)
First code for the `DynamicWithWorkspaces`. This is just for `get_worker_for_uri` code path. File watchers and more are not supported atm.
This is all behind a test flag atm, later we use it inside `oxfmt`
2026-04-17 14:04:24 +00:00
Sysix 7c75e43aa6 chore(lsp): make find_worker_for_uri non-public (#21478)
We want to use `get_worker_for_uri` which can later return `ManagerMode::DynamicWithWorkspaces` worker.
2026-04-17 13:50:34 +00:00
Sysix 2c74edc4b5 chore(lsp): define ManagerMode enum (#21477)
The language server needs to support "modes" of workspaces management.
This could probably be refactored out later when we want to align `oxlint` with `oxfmt`.

The main reason why this check of "modes" is needed:
In `oxfmt` LSP context, user want to use the server without a context of a project.
They want just open a file with `code /Users/local-settings/file.json`, which we already supported with the `single_file_mode` flag.
But they also expect to format such files when you have already opened a project.

Created the enum so we can switch between 3 modes. One is not used atm, but simplifies the logic a bit. Maybe `oxlint` will drop its mode. VS Code will not request diagnostics in single file mode. Need to check what other editors do.
2026-04-17 13:24:58 +00:00
Sysix 6b8ab6a37d fix(lsp): check for real uri path when finding workspace worker (#21194)
fixes https://github.com/oxc-project/oxc-vscode/issues/197
2026-04-13 16:16:51 +00:00
overlookmotel 9a45a10d5c fix(language_server): fix clippy warning in MacOS (#21378)
Clippy was producing an error for this code, but only on MacOS - hence why CI lint task did not catch it.
2026-04-13 13:00:23 +00:00
Sysix f54325df8c chore(lsp): add ResolvedPath struct (#21192)
> Adds a `ResolvedPath` wrapper and new fixtures/tests intended to validate consistent path resolution across mixed-case `file://` URIs (notably from VS Code), to avoid mismatches when mapping URIs to filesystem/workspace entities.

Canonicalized only on windows macOS where case-sensitive file are present.
Not only linux, because `Text.txt` and `test.txt` in the same directory are different files.
2026-04-13 08:16:45 +00:00
Sysix 3746946973 perf(lsp): reduce worker lock on workspace/executeCommand (#21272)
Found by claude opus 4.6🤖, reviewed by me.
2026-04-10 20:49:09 +00:00
Sysix 1550561217 fix(oxlint/lsp): remove uri cache on document change (#21039)
This does not fix the stale diagnostics, but we now have at least a clean state on the linter side, when the document changed, but code actions are requested without a diagnostic request before.

Was originally found by https://github.com/oxc-project/oxc/pull/19793 and moved into an own PR for here.
Thank you, @wagenet
2026-04-05 12:22:08 +00:00
Sysix da6874ad71 perf(lsp): avoid String cloning for fs.get_document (#21040) 2026-04-04 15:36:06 +00:00
Sysix 3ef37da1fc fix(oxlint/lsp): error on unknown command (#20841)
> ## Pull request overview
>
> Adjusts LSP workspace command execution so unknown/unsupported commands surface as errors (instead of silently returning `null`), by removing the pre-dispatch “responsible tool” check and updating tests accordingly.
2026-03-29 19:51:11 +00:00
Sysix 58012db7e7 refactor(lsp): accept only one Tool & ToolBuilder (#20840)
> ## Pull request overview
>
> Refactors the `oxc_language_server` LSP implementation to operate with a single `Tool`/`ToolBuilder` instead of managing multiple tools per workspace, simplifying worker creation and capability wiring.
2026-03-29 16:21:36 +00:00
copilot-swe-agent dbfb51d592 refactor(lsp): extract WorkerManager from Backend (#20576)
The `Backend` struct in `backend.rs` was directly owning and managing all worker lifecycle logic — including the worker list, single-file mode flag, tool builders, dynamic worker creation, and workspace folder transitions — making the struct unwieldy and difficult to reason about.

## Changes

**New `WorkerManager` struct (`worker_manager.rs`)**
- Owns `workers: RwLock<Vec<WorkspaceWorker>>`, `single_file_mode: AtomicBool`, and `tool_builders: Arc<[Box<dyn ToolBuilder>]>`
- Manages two modes: *workspace mode* (root URI or workspace folders provided at `initialize`; workers created once and updated only on `didChangeWorkspaceFolders`) and *single-file mode* (no workspace provided; workers created dynamically on `didOpen` and torn down on `didClose`)
- Hosts all worker-lookup logic: `find_worker_for_uri`, `get_parent_dir_uri`, `assert_workspaces_are_valid_paths`
- Adds `WorkerGuard<'_>` — a RAII type that holds the read lock and dereferences to the matched `WorkspaceWorker`, returned by the new `get_worker_for_uri` async helper
- Encapsulates single-file-mode operations: `ensure_worker_for_file_uri` (returns `Option<Vec<Registration>>` for caller to forward to client), `try_shutdown_empty_workspace` (takes `open_uris` snapshot from caller to avoid cross-lock deadlocks)
- Encapsulates `update_workspace_folders` — the write-lock phase of `didChangeWorkspaceFolders` that handles mode transitions and worker extraction

**Simplified `Backend` (`backend.rs`)**
- Replaces the three fields `workspace_workers`, `single_file_mode`, `tool_builders` with a single `worker_manager: WorkerManager`
- All LSP handlers that look up a single worker now use `get_worker_for_uri` instead of the previous two-step `read_workers()` + `find_worker_for_uri` pattern:

```rust
// Before
let workers = self.worker_manager.read_workers().await;
let Some(worker) = WorkerManager::find_worker_for_uri(&workers, &uri) else { return; };

// After
let Some(worker) = self.worker_manager.get_worker_for_uri(&uri).await else { return; };
```

- All LSP handlers delegate worker lookup and lifecycle to `WorkerManager`; only client I/O (diagnostics publishing, capability registration) stays in `Backend`

```rust
// Before
pub struct Backend {
    tool_builders: Arc<[Box<dyn ToolBuilder>]>,
    workspace_workers: Arc<RwLock<Vec<WorkspaceWorker>>>,
    single_file_mode: AtomicBool,
    // ...
}

// After
pub struct Backend {
    worker_manager: WorkerManager,
    // ...
}
```

Workers are never cloned; ownership is transferred via `swap_remove` / `drain` as before.

<!-- START COPILOT CODING AGENT TIPS -->
---

⚡ Quickly spin up Copilot coding agent tasks from anywhere on your macOS or Windows machine with [Raycast](https://gh.io/cca-raycast-docs).
2026-03-25 21:42:44 +00:00
Sysix f1ee22442a refactor(lsp): pass TextDocument struct to tools instead of multiple params (#20390)
> ## Pull request overview
>
> Refactors the language-server tool interface to pass a single `TextDocument` struct (uri + language id + optional text) instead of multiple parameters, and updates LSP backend/worker call sites and tests accordingly.
>
> **Changes:**
>
>     * Introduce `TextDocument` in `oxc_language_server` and thread it through `Tool`/`WorkspaceWorker` APIs.
>
>     * Update `LSPFileSystem` to provide `get_document()` and migrate backend/worker/tool implementations to use it.
>
>     * Update LSP-related tools/tests in `apps/oxlint` and `apps/oxfmt` to the new API.
2026-03-24 20:47:43 +00:00
Sysix 9c9234e616 test(lsp): skip test_get_parent_dir_uri in windows because of UNIX paths (#20622)
https://github.com/oxc-project/oxc/actions/runs/23403866474/job/68079394830
2026-03-22 13:48:36 +00:00