Skip to content

Commit dfd2979

Browse files
committed
feat(language_server): support textDocument/diagnostic
1 parent f0047ac commit dfd2979

File tree

11 files changed

+360
-135
lines changed

11 files changed

+360
-135
lines changed

crates/oxc_language_server/README.md

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,33 @@ This crate provides an [LSP](https://microsoft.github.io/language-server-protoco
1414
- `quickfix`
1515
- `source.fixAll.oxc`, behaves the same as `quickfix` only used when the `CodeActionContext#only` contains
1616
`source.fixAll.oxc`.
17+
- [Diagnostic Provider](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_pullDiagnostics)
18+
- Only when [Diagnostics Refresh](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnostic_refresh) is supported by your client
1719

1820
## Workspace Options
1921

2022
These options can be passed with [initialize](#initialize), [workspace/didChangeConfiguration](#workspace/didChangeConfiguration) and [workspace/configuration](#workspace/configuration).
2123

22-
| Option Key | Value(s) | Default | Description |
23-
| ------------ | ---------------------- | ---------- | ---------------------------------------------------------------------------------------------------- |
24-
| `run` | `"onSave" \| "onType"` | `"onType"` | Should the server lint the files when the user is typing or saving |
25-
| `configPath` | `<string>` \| `null` | `null` | Path to a oxlint configuration file, passing a string will disable nested configuration |
26-
| `flags` | `Map<string, string>` | `<empty>` | Special oxc language server flags, currently only one flag key is supported: `disable_nested_config` |
24+
| Option Key | Value(s) | Default | Description |
25+
| ------------ | --------------------- | --------- | ---------------------------------------------------------------------------------------------------- |
26+
| `configPath` | `<string>` \| `null` | `null` | Path to a oxlint configuration file, passing a string will disable nested configuration |
27+
| `flags` | `Map<string, string>` | `<empty>` | Special oxc language server flags, currently only one flag key is supported: `disable_nested_config` |
28+
29+
### Flags
30+
31+
- `key: disable_nested_config`: Disabled nested configuration and searches only for `configPath`
32+
- `key: fix_kind`: default: `"safe_fix"`, possible values `"safe_fix" | "safe_fix_or_suggestion" | "dangerous_fix" | "dangerous_fix_or_suggestion" | "none" | "all"`
33+
34+
### Workspace Options without Diagnostic Provider
35+
36+
| Option Key | Value(s) | Default | Description |
37+
| ---------- | ---------------------- | ---------- | ------------------------------------------------------------------ |
38+
| `run` | `"onSave" \| "onType"` | `"onType"` | Should the server lint the files when the user is typing or saving |
39+
40+
## Diagnostics Modes
41+
42+
Depending on the client, the server will push diagnostics, or will wait for a pull request from the client.
43+
The server will prefer pull diagnostics when the client supports it and able to support [textDocument/diagnostic/refresh](#textdocumentdiagnosticrefresh).
2744

2845
## Supported LSP Specifications from Server
2946

@@ -45,11 +62,6 @@ The client can pass the workspace options like following:
4562
}
4663
```
4764

48-
#### Flags
49-
50-
- `key: disable_nested_config`: Disabled nested configuration and searches only for `configPath`
51-
- `key: fix_kind`: default: `"safe_fix"`, possible values `"safe_fix" | "safe_fix_or_suggestion" | "dangerous_fix" | "dangerous_fix_or_suggestion" | "none" | "all"`
52-
5365
### [initialized](https://microsoft.github.io/language-server-protocol/specification#initialized)
5466

5567
When the client did not pass the workspace configuration in [initialize](#initialize), the server will request the configuration for every workspace with [workspace/configuration](#workspaceconfiguration).
@@ -121,17 +133,17 @@ When the configuration `run` is set to `onType`, the server will validate the fi
121133

122134
It will remove the reference internal.
123135

124-
#### [textDocument/codeAction](https://microsoft.github.io/language-server-protocol/specification#textDocument_codeAction)
136+
#### [textDocument/diagnostics](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_diagnostic)
125137

126-
Returns a list of [CodeAction](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_codeAction)
138+
ToDo:
127139

128-
## Expected LSP Specification from Client
140+
#### [textDocument/diagnostic/refresh](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnostic_refresh)
129141

130-
### TextDocument
142+
ToDo:
131143

132-
#### [textDocument/publishDiagnostics](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_publishDiagnostics)
144+
#### [textDocument/codeAction](https://microsoft.github.io/language-server-protocol/specification#textDocument_codeAction)
133145

134-
Returns a [PublishDiagnostic object](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#publishDiagnosticsParams)
146+
Returns a list of [CodeAction](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_codeAction)
135147

136148
## Optional LSP Specifications from Client
137149

@@ -145,6 +157,12 @@ The server will send this request to watch for specific files. The method `works
145157

146158
The server will send this request to stop watching for specific files. The `id` will match from [client/registerCapability](#clientregistercapability).
147159

160+
### TextDocument
161+
162+
#### [textDocument/publishDiagnostics](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_publishDiagnostics)
163+
164+
ToDo:
165+
148166
### Workspace
149167

150168
#### [workspace/configuration](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_configuration)

crates/oxc_language_server/src/capabilities.rs

Lines changed: 84 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,40 @@
11
use tower_lsp_server::lsp_types::{
22
ClientCapabilities, CodeActionKind, CodeActionOptions, CodeActionProviderCapability,
3-
ExecuteCommandOptions, OneOf, SaveOptions, ServerCapabilities, TextDocumentSyncCapability,
4-
TextDocumentSyncKind, TextDocumentSyncOptions, TextDocumentSyncSaveOptions,
5-
WorkDoneProgressOptions, WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities,
3+
DiagnosticOptions, DiagnosticServerCapabilities, ExecuteCommandOptions, OneOf, SaveOptions,
4+
ServerCapabilities, TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions,
5+
TextDocumentSyncSaveOptions, WorkDoneProgressOptions, WorkspaceFoldersServerCapabilities,
6+
WorkspaceServerCapabilities,
67
};
78

89
use crate::{code_actions::CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC, commands::FIX_ALL_COMMAND_ID};
910

10-
#[derive(Clone, Default)]
11+
/// Represents the capabilities of the client that the server can use to determine
12+
/// which features to enable or disable.
13+
#[derive(Clone, Default, Debug)]
1114
pub struct Capabilities {
1215
pub code_action_provider: bool,
1316
pub workspace_apply_edit: bool,
1417
pub workspace_execute_command: bool,
1518
pub workspace_configuration: bool,
19+
/// Whether the client supports dynamic registration of file watchers.
1620
pub dynamic_watchers: bool,
21+
/// Whether the client supports pull diagnostics.
22+
pull_diagnostics: bool,
23+
/// Whether the client supports the `workspace/diagnostic/refresh` request.
24+
refresh_diagnostics: bool,
1725
}
1826

19-
impl From<ClientCapabilities> for Capabilities {
20-
fn from(value: ClientCapabilities) -> Self {
27+
impl Capabilities {
28+
/// The server supports pull and push diagnostics.
29+
/// Only use push diagnostics if the client does not support pull diagnostics,
30+
/// or we can not the client to refresh diagnostics.
31+
pub fn use_push_diagnostics(&self) -> bool {
32+
!self.pull_diagnostics || !self.refresh_diagnostics
33+
}
34+
}
35+
36+
impl From<&ClientCapabilities> for Capabilities {
37+
fn from(value: &ClientCapabilities) -> Self {
2138
// check if the client support some code action literal support
2239
let code_action_provider = value.text_document.as_ref().is_some_and(|capability| {
2340
capability.code_action.as_ref().is_some_and(|code_action| {
@@ -34,18 +51,33 @@ impl From<ClientCapabilities> for Capabilities {
3451
.workspace
3552
.as_ref()
3653
.is_some_and(|workspace| workspace.configuration.is_some_and(|config| config));
37-
let dynamic_watchers = value.workspace.is_some_and(|workspace| {
38-
workspace.did_change_watched_files.is_some_and(|watched_files| {
39-
watched_files.dynamic_registration.is_some_and(|dynamic| dynamic)
54+
let dynamic_watchers = value.workspace.as_ref().is_some_and(|workspace| {
55+
workspace.did_change_watched_files.as_ref().is_some_and(|watched_files| {
56+
watched_files.dynamic_registration.as_ref().is_some_and(|dynamic| *dynamic)
4057
})
4158
});
4259

60+
let pull_diagnostics = value
61+
.text_document
62+
.as_ref()
63+
.is_some_and(|text_document| text_document.diagnostic.is_some());
64+
65+
// BUG: see https://github.com/tower-lsp-community/tower-lsp-server/issues/50
66+
// let refresh_diagnostics = value.workspace.as_ref().is_some_and(|workspace| {
67+
// workspace
68+
// .diagnostic
69+
// .as_ref()
70+
// .is_some_and(|diagnostic| diagnostic.refresh_support.is_some_and(|refresh| refresh))
71+
// });
72+
4373
Self {
4474
code_action_provider,
4575
workspace_apply_edit,
4676
workspace_execute_command,
4777
workspace_configuration,
4878
dynamic_watchers,
79+
pull_diagnostics,
80+
refresh_diagnostics: true,
4981
}
5082
}
5183
}
@@ -92,6 +124,11 @@ impl From<Capabilities> for ServerCapabilities {
92124
} else {
93125
None
94126
},
127+
diagnostic_provider: if value.use_push_diagnostics() {
128+
None
129+
} else {
130+
Some(DiagnosticServerCapabilities::Options(DiagnosticOptions::default()))
131+
},
95132
..ServerCapabilities::default()
96133
}
97134
}
@@ -108,6 +145,37 @@ mod test {
108145

109146
use super::Capabilities;
110147

148+
#[test]
149+
fn test_use_push_diagnostics() {
150+
let capabilities = Capabilities {
151+
pull_diagnostics: true,
152+
refresh_diagnostics: true,
153+
..Default::default()
154+
};
155+
assert!(!capabilities.use_push_diagnostics());
156+
157+
let capabilities = Capabilities {
158+
pull_diagnostics: false,
159+
refresh_diagnostics: true,
160+
..Default::default()
161+
};
162+
assert!(capabilities.use_push_diagnostics());
163+
164+
let capabilities = Capabilities {
165+
pull_diagnostics: true,
166+
refresh_diagnostics: false,
167+
..Default::default()
168+
};
169+
assert!(capabilities.use_push_diagnostics());
170+
171+
let capabilities = Capabilities {
172+
pull_diagnostics: false,
173+
refresh_diagnostics: false,
174+
..Default::default()
175+
};
176+
assert!(capabilities.use_push_diagnostics());
177+
}
178+
111179
#[test]
112180
fn test_code_action_provider_vscode() {
113181
let client_capabilities = ClientCapabilities {
@@ -138,7 +206,7 @@ mod test {
138206
..ClientCapabilities::default()
139207
};
140208

141-
let capabilities = Capabilities::from(client_capabilities);
209+
let capabilities = Capabilities::from(&client_capabilities);
142210

143211
assert!(capabilities.code_action_provider);
144212
}
@@ -168,7 +236,7 @@ mod test {
168236
..ClientCapabilities::default()
169237
};
170238

171-
let capabilities = Capabilities::from(client_capabilities);
239+
let capabilities = Capabilities::from(&client_capabilities);
172240

173241
assert!(capabilities.code_action_provider);
174242
}
@@ -201,7 +269,7 @@ mod test {
201269
..ClientCapabilities::default()
202270
};
203271

204-
let capabilities = Capabilities::from(client_capabilities);
272+
let capabilities = Capabilities::from(&client_capabilities);
205273

206274
assert!(capabilities.code_action_provider);
207275
}
@@ -219,7 +287,7 @@ mod test {
219287
..ClientCapabilities::default()
220288
};
221289

222-
let capabilities = Capabilities::from(client_capabilities);
290+
let capabilities = Capabilities::from(&client_capabilities);
223291

224292
assert!(capabilities.workspace_execute_command);
225293
}
@@ -235,7 +303,7 @@ mod test {
235303
..ClientCapabilities::default()
236304
};
237305

238-
let capabilities = Capabilities::from(client_capabilities);
306+
let capabilities = Capabilities::from(&client_capabilities);
239307

240308
assert!(capabilities.workspace_apply_edit);
241309
}
@@ -253,7 +321,7 @@ mod test {
253321
..Default::default()
254322
};
255323

256-
let capabilities = Capabilities::from(client_capabilities);
324+
let capabilities = Capabilities::from(&client_capabilities);
257325
assert!(capabilities.dynamic_watchers);
258326
}
259327

@@ -270,7 +338,7 @@ mod test {
270338
..Default::default()
271339
};
272340

273-
let capabilities = Capabilities::from(client_capabilities);
341+
let capabilities = Capabilities::from(&client_capabilities);
274342
assert!(capabilities.dynamic_watchers);
275343
}
276344
}

0 commit comments

Comments
 (0)