Skip to content

Commit 45a6921

Browse files
committed
fix(oxlint/lsp): don't register textDocument/formatting capability
1 parent 0146b4c commit 45a6921

File tree

5 files changed

+274
-224
lines changed

5 files changed

+274
-224
lines changed

crates/oxc_language_server/src/backend.rs

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,16 @@ use tower_lsp_server::{
1212
DidChangeConfigurationParams, DidChangeTextDocumentParams, DidChangeWatchedFilesParams,
1313
DidChangeWorkspaceFoldersParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
1414
DidSaveTextDocumentParams, DocumentFormattingParams, ExecuteCommandParams,
15-
InitializeParams, InitializeResult, InitializedParams, Registration, ServerInfo, TextEdit,
16-
Uri,
15+
InitializeParams, InitializeResult, InitializedParams, ServerInfo, TextEdit, Uri,
1716
},
1817
};
1918

2019
use crate::{
21-
ConcurrentHashMap, ToolBuilder, capabilities::Capabilities, file_system::LSPFileSystem,
22-
options::WorkspaceOption, worker::WorkspaceWorker,
20+
ConcurrentHashMap, ToolBuilder,
21+
capabilities::{Capabilities, server_capabilities},
22+
file_system::LSPFileSystem,
23+
options::WorkspaceOption,
24+
worker::WorkspaceWorker,
2325
};
2426

2527
/// The Backend implements the LanguageServer trait to handle LSP requests and notifications.
@@ -133,7 +135,12 @@ impl LanguageServer for Backend {
133135

134136
*self.workspace_workers.write().await = workers;
135137

136-
self.capabilities.set(capabilities.clone()).map_err(|err| {
138+
let mut server_capabilities = server_capabilities();
139+
for tool_builder in &self.tool_builders {
140+
tool_builder.server_capabilities(&mut server_capabilities);
141+
}
142+
143+
self.capabilities.set(capabilities).map_err(|err| {
137144
let message = match err {
138145
SetError::AlreadyInitializedError(_) => {
139146
"capabilities are already initialized".into()
@@ -150,7 +157,7 @@ impl LanguageServer for Backend {
150157
version: Some(server_version.to_string()),
151158
}),
152159
offset_encoding: None,
153-
capabilities: capabilities.server_capabilities(&self.tool_builders),
160+
capabilities: server_capabilities,
154161
})
155162
}
156163

@@ -200,14 +207,6 @@ impl LanguageServer for Backend {
200207
}
201208
}
202209

203-
if capabilities.dynamic_formatting {
204-
registrations.push(Registration {
205-
id: "dynamic-formatting".to_string(),
206-
method: "textDocument/formatting".to_string(),
207-
register_options: None,
208-
});
209-
}
210-
211210
if registrations.is_empty() {
212211
return;
213212
}

crates/oxc_language_server/src/capabilities.rs

Lines changed: 21 additions & 190 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,21 @@
11
use tower_lsp_server::lsp_types::{
2-
ClientCapabilities, CodeActionKind, CodeActionOptions, CodeActionProviderCapability,
3-
ExecuteCommandOptions, OneOf, SaveOptions, ServerCapabilities, TextDocumentSyncCapability,
2+
ClientCapabilities, OneOf, SaveOptions, ServerCapabilities, TextDocumentSyncCapability,
43
TextDocumentSyncKind, TextDocumentSyncOptions, TextDocumentSyncSaveOptions,
5-
WorkDoneProgressOptions, WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities,
4+
WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities,
65
};
76

8-
use crate::ToolBuilder;
9-
107
#[derive(Clone, Default)]
118
pub struct Capabilities {
12-
pub code_action_provider: bool,
139
pub workspace_apply_edit: bool,
14-
pub workspace_execute_command: bool,
1510
pub workspace_configuration: bool,
1611
pub dynamic_watchers: bool,
1712
pub dynamic_formatting: bool,
1813
}
1914

2015
impl From<ClientCapabilities> for Capabilities {
2116
fn from(value: ClientCapabilities) -> Self {
22-
// check if the client support some code action literal support
23-
let code_action_provider = value.text_document.as_ref().is_some_and(|capability| {
24-
capability.code_action.as_ref().is_some_and(|code_action| {
25-
code_action.code_action_literal_support.as_ref().is_some_and(|literal_support| {
26-
!literal_support.code_action_kind.value_set.is_empty()
27-
})
28-
})
29-
});
3017
let workspace_apply_edit =
3118
value.workspace.as_ref().is_some_and(|workspace| workspace.apply_edit.is_some());
32-
let workspace_execute_command =
33-
value.workspace.as_ref().is_some_and(|workspace| workspace.execute_command.is_some());
3419
let workspace_configuration = value
3520
.workspace
3621
.as_ref()
@@ -46,193 +31,39 @@ impl From<ClientCapabilities> for Capabilities {
4631
})
4732
});
4833

49-
Self {
50-
code_action_provider,
51-
workspace_apply_edit,
52-
workspace_execute_command,
53-
workspace_configuration,
54-
dynamic_watchers,
55-
dynamic_formatting,
56-
}
34+
Self { workspace_apply_edit, workspace_configuration, dynamic_watchers, dynamic_formatting }
5735
}
5836
}
5937

60-
impl Capabilities {
61-
pub fn server_capabilities(&self, tools: &[Box<dyn ToolBuilder>]) -> ServerCapabilities {
62-
let code_action_kinds: Vec<CodeActionKind> =
63-
tools.iter().flat_map(|tool| tool.provided_code_action_kinds()).collect();
64-
65-
let commands: Vec<String> =
66-
tools.iter().flat_map(|tool| tool.provided_commands()).collect();
67-
68-
ServerCapabilities {
69-
text_document_sync: Some(TextDocumentSyncCapability::Options(
70-
TextDocumentSyncOptions {
71-
change: Some(TextDocumentSyncKind::FULL),
72-
open_close: Some(true),
73-
save: Some(TextDocumentSyncSaveOptions::SaveOptions(SaveOptions {
74-
include_text: Some(false),
75-
})),
76-
..Default::default()
77-
},
78-
)),
79-
workspace: Some(WorkspaceServerCapabilities {
80-
workspace_folders: Some(WorkspaceFoldersServerCapabilities {
81-
supported: Some(true),
82-
change_notifications: Some(OneOf::Left(true)),
83-
}),
84-
file_operations: None,
38+
pub fn server_capabilities() -> ServerCapabilities {
39+
ServerCapabilities {
40+
text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions {
41+
change: Some(TextDocumentSyncKind::FULL),
42+
open_close: Some(true),
43+
save: Some(TextDocumentSyncSaveOptions::SaveOptions(SaveOptions {
44+
include_text: Some(false),
45+
})),
46+
..Default::default()
47+
})),
48+
workspace: Some(WorkspaceServerCapabilities {
49+
workspace_folders: Some(WorkspaceFoldersServerCapabilities {
50+
supported: Some(true),
51+
change_notifications: Some(OneOf::Left(true)),
8552
}),
86-
code_action_provider: if self.code_action_provider && !code_action_kinds.is_empty() {
87-
Some(CodeActionProviderCapability::Options(CodeActionOptions {
88-
code_action_kinds: Some(code_action_kinds),
89-
work_done_progress_options: WorkDoneProgressOptions {
90-
work_done_progress: None,
91-
},
92-
resolve_provider: None,
93-
}))
94-
} else {
95-
None
96-
},
97-
execute_command_provider: if self.workspace_execute_command && !commands.is_empty() {
98-
Some(ExecuteCommandOptions { commands, ..Default::default() })
99-
} else {
100-
None
101-
},
102-
// the server supports formatting, but it will tell the client if he enabled the setting
103-
document_formatting_provider: None,
104-
..ServerCapabilities::default()
105-
}
53+
file_operations: None,
54+
}),
55+
..ServerCapabilities::default()
10656
}
10757
}
10858

10959
#[cfg(test)]
11060
mod test {
11161
use tower_lsp_server::lsp_types::{
112-
ClientCapabilities, CodeActionClientCapabilities, CodeActionKindLiteralSupport,
113-
CodeActionLiteralSupport, DidChangeWatchedFilesClientCapabilities,
114-
DynamicRegistrationClientCapabilities, TextDocumentClientCapabilities,
115-
WorkspaceClientCapabilities,
62+
ClientCapabilities, DidChangeWatchedFilesClientCapabilities, WorkspaceClientCapabilities,
11663
};
11764

11865
use super::Capabilities;
11966

120-
#[test]
121-
fn test_code_action_provider_vscode() {
122-
let client_capabilities = ClientCapabilities {
123-
text_document: Some(TextDocumentClientCapabilities {
124-
code_action: Some(CodeActionClientCapabilities {
125-
code_action_literal_support: Some(CodeActionLiteralSupport {
126-
code_action_kind: CodeActionKindLiteralSupport {
127-
// this is from build (see help, about):
128-
// Version: 1.95.3 (user setup)
129-
// Commit: f1a4fb101478ce6ec82fe9627c43efbf9e98c813
130-
value_set: vec![
131-
#[expect(clippy::manual_string_new)]
132-
"".into(),
133-
"quickfix".into(),
134-
"refactor".into(),
135-
"refactor.extract".into(),
136-
"refactor.inline".into(),
137-
"refactor.rewrite".into(),
138-
"source".into(),
139-
"source.organizeImports".into(),
140-
],
141-
},
142-
}),
143-
..CodeActionClientCapabilities::default()
144-
}),
145-
..TextDocumentClientCapabilities::default()
146-
}),
147-
..ClientCapabilities::default()
148-
};
149-
150-
let capabilities = Capabilities::from(client_capabilities);
151-
152-
assert!(capabilities.code_action_provider);
153-
}
154-
155-
#[test]
156-
fn test_code_action_provider_intellij() {
157-
let client_capabilities = ClientCapabilities {
158-
text_document: Some(TextDocumentClientCapabilities {
159-
code_action: Some(CodeActionClientCapabilities {
160-
code_action_literal_support: Some(CodeActionLiteralSupport {
161-
code_action_kind: CodeActionKindLiteralSupport {
162-
// this is from build (see help, about):
163-
// Build #IU-243.22562.145, built on December 8, 2024
164-
value_set: vec![
165-
"quickfix".into(),
166-
#[expect(clippy::manual_string_new)]
167-
"".into(),
168-
"source".into(),
169-
"refactor".into(),
170-
],
171-
},
172-
}),
173-
..CodeActionClientCapabilities::default()
174-
}),
175-
..TextDocumentClientCapabilities::default()
176-
}),
177-
..ClientCapabilities::default()
178-
};
179-
180-
let capabilities = Capabilities::from(client_capabilities);
181-
182-
assert!(capabilities.code_action_provider);
183-
}
184-
185-
#[test]
186-
fn test_code_action_provider_nvim() {
187-
let client_capabilities = ClientCapabilities {
188-
text_document: Some(TextDocumentClientCapabilities {
189-
code_action: Some(CodeActionClientCapabilities {
190-
code_action_literal_support: Some(CodeActionLiteralSupport {
191-
code_action_kind: CodeActionKindLiteralSupport {
192-
// nvim 0.10.3
193-
value_set: vec![
194-
#[expect(clippy::manual_string_new)]
195-
"".into(),
196-
"quickfix".into(),
197-
"refactor".into(),
198-
"refactor.extract".into(),
199-
"refactor.inline".into(),
200-
"refactor.rewrite".into(),
201-
"source".into(),
202-
"source.organizeImports".into(),
203-
],
204-
},
205-
}),
206-
..CodeActionClientCapabilities::default()
207-
}),
208-
..TextDocumentClientCapabilities::default()
209-
}),
210-
..ClientCapabilities::default()
211-
};
212-
213-
let capabilities = Capabilities::from(client_capabilities);
214-
215-
assert!(capabilities.code_action_provider);
216-
}
217-
218-
// This tests code, intellij and neovim (at least nvim 0.10.0+), as they all support dynamic registration.
219-
#[test]
220-
fn test_workspace_execute_command() {
221-
let client_capabilities = ClientCapabilities {
222-
workspace: Some(WorkspaceClientCapabilities {
223-
execute_command: Some(DynamicRegistrationClientCapabilities {
224-
dynamic_registration: Some(true),
225-
}),
226-
..WorkspaceClientCapabilities::default()
227-
}),
228-
..ClientCapabilities::default()
229-
};
230-
231-
let capabilities = Capabilities::from(client_capabilities);
232-
233-
assert!(capabilities.workspace_execute_command);
234-
}
235-
23667
#[test]
23768
fn test_workspace_edit_nvim() {
23869
let client_capabilities = ClientCapabilities {

crates/oxc_language_server/src/formatter/server_formatter.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use oxc_formatter::{
1111
use oxc_parser::Parser;
1212
use tower_lsp_server::{
1313
UriExt,
14-
lsp_types::{Pattern, Position, Range, TextEdit, Uri},
14+
lsp_types::{Pattern, Position, Range, ServerCapabilities, TextEdit, Uri},
1515
};
1616

1717
use crate::{
@@ -67,6 +67,10 @@ impl ServerFormatterBuilder {
6767
}
6868

6969
impl ToolBuilder for ServerFormatterBuilder {
70+
fn server_capabilities(&self, capabilities: &mut ServerCapabilities) {
71+
capabilities.document_formatting_provider =
72+
Some(tower_lsp_server::lsp_types::OneOf::Left(true));
73+
}
7074
fn build_boxed(&self, root_uri: &Uri, options: serde_json::Value) -> Box<dyn Tool> {
7175
Box::new(ServerFormatterBuilder::build(root_uri, options))
7276
}
@@ -380,6 +384,23 @@ fn load_ignore_paths(cwd: &Path) -> Vec<PathBuf> {
380384
.collect::<Vec<_>>()
381385
}
382386

387+
#[cfg(test)]
388+
mod tests_builder {
389+
use crate::{ServerFormatterBuilder, ToolBuilder};
390+
391+
#[test]
392+
fn test_server_capabilities() {
393+
use tower_lsp_server::lsp_types::{OneOf, ServerCapabilities};
394+
395+
let builder = ServerFormatterBuilder;
396+
let mut capabilities = ServerCapabilities::default();
397+
398+
builder.server_capabilities(&mut capabilities);
399+
400+
assert_eq!(capabilities.document_formatting_provider, Some(OneOf::Left(true)));
401+
}
402+
}
403+
383404
#[cfg(test)]
384405
mod tests {
385406
use serde_json::json;

0 commit comments

Comments
 (0)