Skip to content

Commit d0fbfb4

Browse files
committed
feat(language_server): support textDocument/diagnostic
1 parent 4b057c7 commit d0fbfb4

File tree

4 files changed

+79
-22
lines changed

4 files changed

+79
-22
lines changed

crates/oxc_language_server/src/capabilities.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub struct Capabilities {
1414
pub workspace_execute_command: bool,
1515
pub workspace_configuration: bool,
1616
pub dynamic_watchers: bool,
17+
pub pull_diagnostics: bool,
1718
}
1819

1920
impl From<ClientCapabilities> for Capabilities {
@@ -40,12 +41,18 @@ impl From<ClientCapabilities> for Capabilities {
4041
})
4142
});
4243

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

crates/oxc_language_server/src/main.rs

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ use tower_lsp_server::{
1313
DidChangeConfigurationParams, DidChangeTextDocumentParams, DidChangeWatchedFilesParams,
1414
DidChangeWatchedFilesRegistrationOptions, DidChangeWorkspaceFoldersParams,
1515
DidCloseTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams,
16-
ExecuteCommandParams, InitializeParams, InitializeResult, InitializedParams, Registration,
17-
ServerInfo, Unregistration, Uri, WorkspaceEdit,
16+
DocumentDiagnosticParams, DocumentDiagnosticReport, DocumentDiagnosticReportResult,
17+
ExecuteCommandParams, FullDocumentDiagnosticReport, InitializeParams, InitializeResult,
18+
InitializedParams, Registration, RelatedFullDocumentDiagnosticReport, ServerInfo,
19+
Unregistration, Uri, WorkspaceEdit,
1820
},
1921
};
2022
// #
@@ -466,7 +468,11 @@ impl LanguageServer for Backend {
466468
if !worker.should_lint_on_run_type(Run::OnSave).await {
467469
return;
468470
}
469-
if let Some(diagnostics) = worker.lint_file(uri, None).await {
471+
472+
// we no longer need the cached document, as it is saved to disk
473+
worker.remove_cached_document(uri);
474+
475+
if let Some(diagnostics) = worker.lint_file(uri).await {
470476
self.client
471477
.publish_diagnostics(
472478
uri.clone(),
@@ -489,7 +495,13 @@ impl LanguageServer for Backend {
489495
return;
490496
}
491497
let content = params.content_changes.first().map(|c| c.text.clone());
492-
if let Some(diagnostics) = worker.lint_file(uri, content).await {
498+
499+
if let Some(content) = content {
500+
// cache the document content for later use
501+
worker.cache_document(uri, content);
502+
}
503+
504+
if let Some(diagnostics) = worker.lint_file(uri).await {
493505
self.client
494506
.publish_diagnostics(
495507
uri.clone(),
@@ -507,8 +519,10 @@ impl LanguageServer for Backend {
507519
return;
508520
};
509521

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

531576
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);
@@ -149,16 +155,15 @@ impl WorkspaceWorker {
149155
diagnostics
150156
}
151157

152-
async fn lint_file_internal(
153-
&self,
154-
uri: &Uri,
155-
content: Option<String>,
156-
) -> Option<Vec<DiagnosticReport>> {
158+
async fn lint_file_internal(&self, uri: &Uri) -> Option<Vec<DiagnosticReport>> {
157159
let Some(server_linter) = &*self.server_linter.read().await else {
158160
return None;
159161
};
160162

161-
server_linter.run_single(uri, content)
163+
let cache = self.document_cache.pin();
164+
let content = cache.get(&uri.to_string());
165+
166+
server_linter.run_single(uri, content.cloned())
162167
}
163168

164169
fn update_diagnostics(&self, uri: &Uri, diagnostics: &[DiagnosticReport]) {
@@ -208,7 +213,7 @@ impl WorkspaceWorker {
208213
Some(value) => value,
209214
// code actions / commands can be requested without opening the file
210215
// we just internally lint and provide the code actions / commands without refreshing the diagnostic map.
211-
None => &self.lint_file_internal(uri, None).await.unwrap_or_default(),
216+
None => &self.lint_file_internal(uri).await.unwrap_or_default(),
212217
};
213218

214219
if value.is_empty() {
@@ -250,7 +255,7 @@ impl WorkspaceWorker {
250255
Some(value) => value,
251256
// code actions / commands can be requested without opening the file
252257
// we just internally lint and provide the code actions / commands without refreshing the diagnostic map.
253-
None => &self.lint_file_internal(uri, None).await.unwrap_or_default(),
258+
None => &self.lint_file_internal(uri).await.unwrap_or_default(),
254259
};
255260

256261
if value.is_empty() {

0 commit comments

Comments
 (0)