Skip to content

Commit 671e0fd

Browse files
committed
refactor(language_server): only store one instance of a diagnostic (#13514)
Now the `ServerLinter` is the only struct owning the diagnostics.
1 parent b677376 commit 671e0fd

File tree

3 files changed

+150
-114
lines changed

3 files changed

+150
-114
lines changed

crates/oxc_language_server/src/linter/server_linter.rs

Lines changed: 71 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::path::{Component, Path, PathBuf};
2+
use std::str::FromStr;
23
use std::sync::Arc;
34

45
use ignore::gitignore::Gitignore;
@@ -37,26 +38,44 @@ pub struct ServerLinter {
3738
ignore_matcher: LintIgnoreMatcher,
3839
gitignore_glob: Vec<Gitignore>,
3940
lint_on_run: Run,
40-
diagnostics: Arc<ServerLinterDiagnostics>,
41+
diagnostics: ServerLinterDiagnostics,
4142
pub extended_paths: Vec<PathBuf>,
4243
}
4344

4445
#[derive(Debug, Default)]
4546
struct ServerLinterDiagnostics {
46-
isolated_linter: Arc<ConcurrentHashMap<String, Vec<DiagnosticReport>>>,
47-
tsgo_linter: Arc<ConcurrentHashMap<String, Vec<DiagnosticReport>>>,
47+
isolated_linter: Arc<ConcurrentHashMap<String, Option<Vec<DiagnosticReport>>>>,
48+
tsgo_linter: Arc<ConcurrentHashMap<String, Option<Vec<DiagnosticReport>>>>,
4849
}
4950

5051
impl ServerLinterDiagnostics {
51-
pub fn all_diagnostics(&self, path: &str) -> Vec<DiagnosticReport> {
52-
let mut diagnostics = Vec::new();
53-
if let Some(reports) = self.isolated_linter.pin().get(path) {
54-
diagnostics.extend_from_slice(reports);
52+
pub fn get_diagnostics(&self, path: &str) -> Option<Vec<DiagnosticReport>> {
53+
let mut reports = Vec::new();
54+
let mut found = false;
55+
if let Some(Some(diagnostics)) = self.isolated_linter.pin().get(path) {
56+
reports.extend(diagnostics.clone());
57+
found = true;
5558
}
56-
if let Some(reports) = self.tsgo_linter.pin().get(path) {
57-
diagnostics.extend_from_slice(reports);
59+
if let Some(Some(diagnostics)) = self.tsgo_linter.pin().get(path) {
60+
reports.extend(diagnostics.clone());
61+
found = true;
5862
}
59-
diagnostics
63+
if found { Some(reports) } else { None }
64+
}
65+
66+
pub fn remove_diagnostics(&self, path: &str) {
67+
self.isolated_linter.pin().remove(path);
68+
self.tsgo_linter.pin().remove(path);
69+
}
70+
71+
pub fn get_cached_files_of_diagnostics(&self) -> Vec<String> {
72+
let mut files = Vec::new();
73+
let isolated_files = self.isolated_linter.pin().keys().cloned().collect::<Vec<_>>();
74+
let tsgo_files = self.tsgo_linter.pin().keys().cloned().collect::<Vec<_>>();
75+
files.extend(isolated_files);
76+
files.extend(tsgo_files);
77+
files.dedup();
78+
files
6079
}
6180
}
6281

@@ -154,7 +173,7 @@ impl ServerLinter {
154173
gitignore_glob: Self::create_ignore_glob(&root_path),
155174
extended_paths,
156175
lint_on_run: options.run,
157-
diagnostics: Arc::new(ServerLinterDiagnostics::default()),
176+
diagnostics: ServerLinterDiagnostics::default(),
158177
tsgo_linter: if options.type_aware {
159178
Arc::new(Some(TsgoLinter::new(&root_path, config_store)))
160179
} else {
@@ -247,6 +266,35 @@ impl ServerLinter {
247266
gitignore_globs
248267
}
249268

269+
pub fn remove_diagnostics(&self, uri: &Uri) {
270+
self.diagnostics.remove_diagnostics(&uri.to_string());
271+
}
272+
273+
pub fn get_cached_diagnostics(&self, uri: &Uri) -> Option<Vec<DiagnosticReport>> {
274+
self.diagnostics.get_diagnostics(&uri.to_string())
275+
}
276+
277+
pub fn get_cached_files_of_diagnostics(&self) -> Vec<Uri> {
278+
self.diagnostics
279+
.get_cached_files_of_diagnostics()
280+
.into_iter()
281+
.filter_map(|s| Uri::from_str(&s).ok())
282+
.collect()
283+
}
284+
285+
pub async fn revalidate_diagnostics(
286+
&self,
287+
uris: Vec<Uri>,
288+
) -> ConcurrentHashMap<String, Vec<DiagnosticReport>> {
289+
let map = ConcurrentHashMap::default();
290+
for uri in uris {
291+
if let Some(diagnostics) = self.run_single(&uri, None, ServerLinterRun::Always).await {
292+
map.pin().insert(uri.to_string(), diagnostics);
293+
}
294+
}
295+
map
296+
}
297+
250298
fn is_ignored(&self, uri: &Uri) -> bool {
251299
let Some(uri_path) = uri.to_file_path() else {
252300
return true;
@@ -297,26 +345,22 @@ impl ServerLinter {
297345
return None;
298346
}
299347

300-
if oxlint
301-
&& let Some(oxlint_reports) =
302-
self.isolated_linter.lock().await.run_single(uri, content.clone())
303-
{
304-
self.diagnostics.isolated_linter.pin().insert(uri.to_string(), oxlint_reports);
305-
}
306-
307-
if !tsgolint {
308-
return Some(self.diagnostics.all_diagnostics(&uri.to_string()));
348+
if oxlint {
349+
let diagnostics = {
350+
let mut isolated_linter = self.isolated_linter.lock().await;
351+
isolated_linter.run_single(uri, content.clone())
352+
};
353+
self.diagnostics.isolated_linter.pin().insert(uri.to_string(), diagnostics);
309354
}
310355

311-
let Some(tsgo_linter) = &*self.tsgo_linter else {
312-
return Some(self.diagnostics.all_diagnostics(&uri.to_string()));
313-
};
314-
315-
if let Some(tsgo_reports) = tsgo_linter.lint_file(uri, content) {
316-
self.diagnostics.tsgo_linter.pin().insert(uri.to_string(), tsgo_reports);
356+
if tsgolint && let Some(tsgo_linter) = self.tsgo_linter.as_ref() {
357+
self.diagnostics
358+
.tsgo_linter
359+
.pin()
360+
.insert(uri.to_string(), tsgo_linter.lint_file(uri, content.clone()));
317361
}
318362

319-
Some(self.diagnostics.all_diagnostics(&uri.to_string()))
363+
self.diagnostics.get_diagnostics(&uri.to_string())
320364
}
321365
}
322366

crates/oxc_language_server/src/main.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,7 @@ impl LanguageServer for Backend {
376376
else {
377377
continue;
378378
};
379-
cleared_diagnostics.extend(worker.get_clear_diagnostics());
379+
cleared_diagnostics.extend(worker.get_clear_diagnostics().await);
380380
removed_registrations.push(Unregistration {
381381
id: format!("watcher-{}", worker.get_root_uri().as_str()),
382382
method: "workspace/didChangeWatchedFiles".to_string(),
@@ -500,7 +500,7 @@ impl LanguageServer for Backend {
500500
let Some(worker) = workers.iter().find(|worker| worker.is_responsible_for_uri(uri)) else {
501501
return;
502502
};
503-
worker.remove_diagnostics(&params.text_document.uri);
503+
worker.remove_diagnostics(&params.text_document.uri).await;
504504
}
505505

506506
async fn code_action(&self, params: CodeActionParams) -> Result<Option<CodeActionResponse>> {
@@ -598,8 +598,9 @@ impl Backend {
598598
// clears all diagnostics for workspace folders
599599
async fn clear_all_diagnostics(&self) {
600600
let mut cleared_diagnostics = vec![];
601-
for worker in self.workspace_workers.read().await.iter() {
602-
cleared_diagnostics.extend(worker.get_clear_diagnostics());
601+
let workers = &*self.workspace_workers.read().await;
602+
for worker in workers {
603+
cleared_diagnostics.extend(worker.get_clear_diagnostics().await);
603604
}
604605
self.publish_all_diagnostics(&cleared_diagnostics).await;
605606
}

0 commit comments

Comments
 (0)