Skip to content

Commit bf998de

Browse files
committed
fix(oxlint/lsp): report diagnostics referencing another file
1 parent fabb716 commit bf998de

File tree

6 files changed

+159
-121
lines changed

6 files changed

+159
-121
lines changed

crates/oxc_language_server/src/linter/isolated_lint_handler.rs

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::{
33
sync::{Arc, OnceLock},
44
};
55

6-
use log::{debug, warn};
6+
use log::{debug, error, warn};
77
use oxc_data_structures::rope::Rope;
88
use rustc_hash::{FxHashMap, FxHashSet};
99
use tower_lsp_server::ls_types::Uri;
@@ -111,34 +111,46 @@ impl IsolatedLintHandler {
111111
let source_text =
112112
if let Some(content) = content { content } else { &read_to_string(&path).ok()? };
113113

114-
let mut diagnostics = self.lint_path(&path, uri, source_text);
114+
let mut diagnostics = self.lint_path(&path, source_text);
115115
diagnostics.append(&mut generate_inverted_diagnostics(&diagnostics, uri));
116116
Some(diagnostics)
117117
}
118118

119-
fn lint_path(&self, path: &Path, uri: &Uri, source_text: &str) -> Vec<DiagnosticReport> {
119+
fn lint_path(&self, path: &Path, source_text: &str) -> Vec<DiagnosticReport> {
120120
debug!("lint {}", path.display());
121121
let rope = &Rope::from_str(source_text);
122122

123123
let mut fs = IsolatedLintHandlerFileSystem::default();
124124
fs.add_file(path.to_path_buf(), Arc::from(source_text));
125125

126-
let mut messages: Vec<DiagnosticReport> = self
127-
.runner
128-
.run_source(&[Arc::from(path.as_os_str())], &fs)
129-
.into_iter()
130-
.map(|message| message_to_lsp_diagnostic(message, uri, source_text, rope))
131-
.collect();
126+
let results = self.runner.run_source(&[Arc::from(path.as_os_str())], &fs);
132127

133-
// Add unused directives if configured
134-
if let Some(severity) = self.unused_directives_severity
135-
&& let Some(directives) = self.runner.directives_coordinator().get(path)
136-
{
128+
let mut messages: Vec<DiagnosticReport> = Vec::new();
129+
130+
for (msg_path_os, msgs) in results {
131+
// Convert Arc<OsStr> to &Path for coordinator lookup
132+
let msg_path = Path::new(msg_path_os.as_ref());
133+
let Some(uri) = Uri::from_file_path(msg_path) else {
134+
error!("Failed to convert path to URI: {}", msg_path.display());
135+
continue;
136+
};
137+
138+
// Map rule diagnostics to LSP diagnostics
137139
messages.extend(
138-
create_unused_directives_messages(&directives, severity, source_text)
139-
.into_iter()
140-
.map(|message| message_to_lsp_diagnostic(message, uri, source_text, rope)),
140+
msgs.into_iter()
141+
.map(|message| message_to_lsp_diagnostic(message, &uri, source_text, rope)),
141142
);
143+
144+
// Add unused directives for this file if configured
145+
if let Some(severity) = self.unused_directives_severity
146+
&& let Some(directives) = self.runner.directives_coordinator().get(msg_path)
147+
{
148+
messages.extend(
149+
create_unused_directives_messages(&directives, severity, source_text)
150+
.into_iter()
151+
.map(|message| message_to_lsp_diagnostic(message, &uri, source_text, rope)),
152+
);
153+
}
142154
}
143155

144156
messages

crates/oxc_language_server/src/linter/server_linter.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -555,11 +555,15 @@ impl ServerLinter {
555555
}
556556

557557
fn get_code_actions_for_uri(&self, uri: &Uri) -> Option<Vec<LinterCodeAction>> {
558-
if let Some(cached_code_actions) = self.code_actions.pin().get(uri) {
558+
// for code actions we need to make a roundtrip to `from_file_path` and `to_file_path`.
559+
// The editor URI schema might differ from our own produced URIs.
560+
let uri = Uri::from_file_path(uri.to_file_path()?)?;
561+
562+
if let Some(cached_code_actions) = self.code_actions.pin().get(&uri) {
559563
cached_code_actions.clone()
560564
} else {
561-
self.run_file(uri, None);
562-
self.code_actions.pin().get(uri).and_then(std::clone::Clone::clone)
565+
self.run_file(&uri, None);
566+
self.code_actions.pin().get(&uri).and_then(std::clone::Clone::clone)
563567
}
564568
}
565569

crates/oxc_linter/src/lint_runner.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,9 @@ impl LintRunner {
237237
&self,
238238
files: &[Arc<OsStr>],
239239
file_system: &(dyn crate::RuntimeFileSystem + Sync + Send),
240-
) -> Vec<Message> {
240+
) -> Vec<(Arc<OsStr>, Vec<crate::Message>)> {
241+
debug_assert!(!files.is_empty());
242+
241243
let mut messages = self.lint_service.run_source(file_system, files.to_owned());
242244

243245
if let Some(type_aware_linter) = &self.type_aware_linter {
@@ -248,9 +250,15 @@ impl LintRunner {
248250
) {
249251
Ok(msgs) => msgs,
250252
Err(err) => {
251-
vec![Message::new(
252-
OxcDiagnostic::warn(format!("Failed to run type-aware linting: `{err}`",)),
253-
PossibleFixes::None,
253+
vec![(
254+
// Report error for the first file only
255+
Arc::clone(&files[0]),
256+
vec![Message::new(
257+
OxcDiagnostic::warn(format!(
258+
"Failed to run type-aware linting: `{err}`",
259+
)),
260+
PossibleFixes::None,
261+
)],
254262
)]
255263
}
256264
};

crates/oxc_linter/src/service/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ impl LintService {
9191
&self,
9292
file_system: &(dyn RuntimeFileSystem + Sync + Send),
9393
paths: Vec<Arc<OsStr>>,
94-
) -> Vec<crate::Message> {
94+
) -> Vec<(Arc<OsStr>, Vec<crate::Message>)> {
9595
self.runtime.run_source(file_system, paths)
9696
}
9797

crates/oxc_linter/src/service/runtime.rs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -659,13 +659,13 @@ impl Runtime {
659659
&self,
660660
file_system: &(dyn RuntimeFileSystem + Sync + Send),
661661
paths: Vec<Arc<OsStr>>,
662-
) -> Vec<Message> {
662+
) -> Vec<(Arc<OsStr>, Vec<crate::Message>)> {
663663
use std::sync::Mutex;
664664

665665
self.modules_by_path.pin().reserve(paths.len());
666666
let paths_set: IndexSet<Arc<OsStr>, FxBuildHasher> = paths.into_iter().collect();
667667

668-
let messages = Mutex::new(Vec::<Message>::new());
668+
let messages = Mutex::new(Vec::<(Arc<OsStr>, Vec<crate::Message>)>::new());
669669
rayon::scope(|scope| {
670670
self.resolve_modules(
671671
file_system,
@@ -696,11 +696,12 @@ impl Runtime {
696696
}
697697
Err(diagnostics) => {
698698
if !diagnostics.is_empty() {
699-
messages.lock().unwrap().extend(
699+
messages.lock().unwrap().push((
700+
Arc::clone(&module_to_lint.path),
700701
diagnostics.into_iter().map(|diagnostic| {
701702
Message::new(diagnostic, PossibleFixes::None)
702-
}),
703-
);
703+
}).collect(),
704+
));
704705
}
705706
None
706707
}
@@ -715,17 +716,19 @@ impl Runtime {
715716
let (section_messages, disable_directives) = me
716717
.linter
717718
.run_with_disable_directives(path, context_sub_hosts, allocator_guard);
718-
719719
if let Some(disable_directives) = disable_directives {
720720
me.disable_directives_map
721721
.lock()
722722
.expect("disable_directives_map mutex poisoned")
723723
.insert(path.to_path_buf(), disable_directives);
724724
}
725725

726-
messages.lock().unwrap().extend(
727-
section_messages
728-
);
726+
if !section_messages.is_empty() {
727+
messages.lock().unwrap().push((
728+
Arc::clone(&module_to_lint.path),
729+
section_messages
730+
));
731+
}
729732
},
730733
);
731734
},

0 commit comments

Comments
 (0)