Skip to content

Commit 7b4da00

Browse files
committed
feat(language_server): support textDocument/diagnostic
1 parent d384b02 commit 7b4da00

File tree

4 files changed

+80
-23
lines changed

4 files changed

+80
-23
lines changed

crates/oxc_language_server/src/capabilities.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub struct Capabilities {
1515
pub workspace_execute_command: bool,
1616
pub workspace_configuration: bool,
1717
pub dynamic_watchers: bool,
18+
pub pull_diagnostics: bool,
1819
}
1920

2021
impl From<ClientCapabilities> for Capabilities {
@@ -41,12 +42,18 @@ impl From<ClientCapabilities> for Capabilities {
4142
})
4243
});
4344

45+
let pull_diagnostics = value
46+
.text_document
47+
.as_ref()
48+
.is_some_and(|text_document| text_document.diagnostic.is_some());
49+
4450
Self {
4551
code_action_provider,
4652
workspace_apply_edit,
4753
workspace_execute_command,
4854
workspace_configuration,
4955
dynamic_watchers,
56+
pull_diagnostics,
5057
}
5158
}
5259
}

crates/oxc_language_server/src/main.rs

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ use tower_lsp_server::{
1313
DidChangeConfigurationParams, DidChangeTextDocumentParams, DidChangeWatchedFilesParams,
1414
DidChangeWatchedFilesRegistrationOptions, DidChangeWorkspaceFoldersParams,
1515
DidCloseTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams,
16+
DocumentDiagnosticParams, DocumentDiagnosticReport, DocumentDiagnosticReportResult,
1617
ExecuteCommandParams, FullDocumentDiagnosticReport, InitializeParams, InitializeResult,
17-
InitializedParams, Registration, ServerInfo, Unregistration, Uri,
18-
WorkspaceDiagnosticParams, WorkspaceDiagnosticReport, WorkspaceDiagnosticReportResult,
19-
WorkspaceDocumentDiagnosticReport, WorkspaceEdit, WorkspaceFullDocumentDiagnosticReport,
18+
InitializedParams, Registration, RelatedFullDocumentDiagnosticReport, ServerInfo,
19+
Unregistration, Uri, WorkspaceDiagnosticParams, WorkspaceDiagnosticReport,
20+
WorkspaceDiagnosticReportResult, WorkspaceDocumentDiagnosticReport, WorkspaceEdit,
21+
WorkspaceFullDocumentDiagnosticReport,
2022
},
2123
};
2224
// #
@@ -467,7 +469,11 @@ impl LanguageServer for Backend {
467469
if !worker.should_lint_on_run_type(Run::OnSave).await {
468470
return;
469471
}
470-
if let Some(diagnostics) = worker.lint_file(uri, None).await {
472+
473+
// we no longer need the cached document, as it is saved to disk
474+
worker.remove_cached_document(uri);
475+
476+
if let Some(diagnostics) = worker.lint_file(uri).await {
471477
self.client
472478
.publish_diagnostics(
473479
uri.clone(),
@@ -490,7 +496,13 @@ impl LanguageServer for Backend {
490496
return;
491497
}
492498
let content = params.content_changes.first().map(|c| c.text.clone());
493-
if let Some(diagnostics) = worker.lint_file(uri, content).await {
499+
500+
if let Some(content) = content {
501+
// cache the document content for later use
502+
worker.cache_document(uri, content);
503+
}
504+
505+
if let Some(diagnostics) = worker.lint_file(uri).await {
494506
self.client
495507
.publish_diagnostics(
496508
uri.clone(),
@@ -508,8 +520,10 @@ impl LanguageServer for Backend {
508520
return;
509521
};
510522

511-
let content = params.text_document.text;
512-
if let Some(diagnostics) = worker.lint_file(uri, Some(content)).await {
523+
// cache the document content for later use
524+
worker.cache_document(uri, params.text_document.text);
525+
526+
if let Some(diagnostics) = worker.lint_file(uri).await {
513527
self.client
514528
.publish_diagnostics(
515529
uri.clone(),
@@ -526,7 +540,38 @@ impl LanguageServer for Backend {
526540
let Some(worker) = workers.iter().find(|worker| worker.is_responsible_for_uri(uri)) else {
527541
return;
528542
};
529-
worker.remove_diagnostics(&params.text_document.uri);
543+
worker.remove_cached_document(uri);
544+
worker.remove_diagnostics(uri);
545+
}
546+
547+
async fn diagnostic(
548+
&self,
549+
params: DocumentDiagnosticParams,
550+
) -> Result<DocumentDiagnosticReportResult> {
551+
let uri = &params.text_document.uri;
552+
let workers = self.workspace_workers.read().await;
553+
let Some(worker) = workers.iter().find(|worker| worker.is_responsible_for_uri(uri)) else {
554+
return Ok(DocumentDiagnosticReportResult::Report(DocumentDiagnosticReport::Full(
555+
RelatedFullDocumentDiagnosticReport::default(),
556+
)));
557+
};
558+
let diagnostics = worker.lint_file(uri).await;
559+
560+
if let Some(diagnostics) = diagnostics {
561+
Ok(DocumentDiagnosticReportResult::Report(DocumentDiagnosticReport::Full(
562+
RelatedFullDocumentDiagnosticReport {
563+
full_document_diagnostic_report: FullDocumentDiagnosticReport {
564+
items: diagnostics.into_iter().map(|d| d.diagnostic).collect(),
565+
..Default::default()
566+
},
567+
..Default::default()
568+
},
569+
)))
570+
} else {
571+
Ok(DocumentDiagnosticReportResult::Report(DocumentDiagnosticReport::Full(
572+
RelatedFullDocumentDiagnosticReport::default(),
573+
)))
574+
}
530575
}
531576

532577
async fn code_action(&self, params: CodeActionParams) -> Result<Option<CodeActionResponse>> {

crates/oxc_language_server/src/tester.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ impl Tester<'_> {
115115
let reports = tokio::runtime::Runtime::new().unwrap().block_on(async {
116116
self.create_workspace_worker()
117117
.await
118-
.lint_file(&uri, None)
118+
.lint_file(&uri)
119119
.await
120120
.expect("lint file is ignored")
121121
});

crates/oxc_language_server/src/worker.rs

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub struct WorkspaceWorker {
2727
root_uri: Uri,
2828
server_linter: RwLock<Option<ServerLinter>>,
2929
diagnostics_report_map: Arc<ConcurrentHashMap<String, Vec<DiagnosticReport>>>,
30+
document_cache: Arc<ConcurrentHashMap<String, String>>,
3031
options: Mutex<Options>,
3132
}
3233

@@ -36,6 +37,7 @@ impl WorkspaceWorker {
3637
root_uri,
3738
server_linter: RwLock::new(None),
3839
diagnostics_report_map: Arc::new(ConcurrentHashMap::default()),
40+
document_cache: Arc::new(ConcurrentHashMap::default()),
3941
options: Mutex::new(Options::default()),
4042
}
4143
}
@@ -56,6 +58,14 @@ impl WorkspaceWorker {
5658
*self.server_linter.write().await = Some(ServerLinter::new(&self.root_uri, options));
5759
}
5860

61+
pub fn cache_document(&self, uri: &Uri, content: String) {
62+
self.document_cache.pin().insert(uri.to_string(), content);
63+
}
64+
65+
pub fn remove_cached_document(&self, uri: &Uri) {
66+
self.document_cache.pin().remove(&uri.to_string());
67+
}
68+
5969
// WARNING: start all programs (linter, formatter) before calling this function
6070
// each program can tell us customized file watcher patterns
6171
pub async fn init_watchers(&self) -> Vec<FileSystemWatcher> {
@@ -135,12 +145,8 @@ impl WorkspaceWorker {
135145
run_level == current_run
136146
}
137147

138-
pub async fn lint_file(
139-
&self,
140-
uri: &Uri,
141-
content: Option<String>,
142-
) -> Option<Vec<DiagnosticReport>> {
143-
let diagnostics = self.lint_file_internal(uri, content).await;
148+
pub async fn lint_file(&self, uri: &Uri) -> Option<Vec<DiagnosticReport>> {
149+
let diagnostics = self.lint_file_internal(uri).await;
144150

145151
if let Some(diagnostics) = &diagnostics {
146152
self.update_diagnostics(uri, diagnostics);
@@ -159,16 +165,15 @@ impl WorkspaceWorker {
159165
server_linter.run_all()
160166
}
161167

162-
async fn lint_file_internal(
163-
&self,
164-
uri: &Uri,
165-
content: Option<String>,
166-
) -> Option<Vec<DiagnosticReport>> {
168+
async fn lint_file_internal(&self, uri: &Uri) -> Option<Vec<DiagnosticReport>> {
167169
let Some(server_linter) = &*self.server_linter.read().await else {
168170
return None;
169171
};
170172

171-
server_linter.run_single(uri, content)
173+
let cache = self.document_cache.pin();
174+
let content = cache.get(&uri.to_string());
175+
176+
server_linter.run_single(uri, content.cloned())
172177
}
173178

174179
fn update_diagnostics(&self, uri: &Uri, diagnostics: &[DiagnosticReport]) {
@@ -218,7 +223,7 @@ impl WorkspaceWorker {
218223
Some(value) => value,
219224
// code actions / commands can be requested without opening the file
220225
// we just internally lint and provide the code actions / commands without refreshing the diagnostic map.
221-
None => &self.lint_file_internal(uri, None).await.unwrap_or_default(),
226+
None => &self.lint_file_internal(uri).await.unwrap_or_default(),
222227
};
223228

224229
if value.is_empty() {
@@ -260,7 +265,7 @@ impl WorkspaceWorker {
260265
Some(value) => value,
261266
// code actions / commands can be requested without opening the file
262267
// we just internally lint and provide the code actions / commands without refreshing the diagnostic map.
263-
None => &self.lint_file_internal(uri, None).await.unwrap_or_default(),
268+
None => &self.lint_file_internal(uri).await.unwrap_or_default(),
264269
};
265270

266271
if value.is_empty() {

0 commit comments

Comments
 (0)