Skip to content

Commit e8c2373

Browse files
committed
refactor(language_server): use LintRunner
1 parent 8819d40 commit e8c2373

File tree

6 files changed

+57
-136
lines changed

6 files changed

+57
-136
lines changed

crates/oxc_language_server/src/linter/isolated_lint_handler.rs

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use tower_lsp_server::{UriExt, lsp_types::Uri};
1111
use oxc_allocator::Allocator;
1212
use oxc_linter::{
1313
AllowWarnDeny, ConfigStore, DirectivesStore, DisableDirectives, LINTABLE_EXTENSIONS,
14-
LintOptions, LintService, LintServiceOptions, Linter, RuntimeFileSystem,
14+
LintOptions, LintRunner, LintRunnerBuilder, LintServiceOptions, Linter, RuntimeFileSystem,
1515
create_unused_directives_diagnostics, read_to_arena_str, read_to_string,
1616
};
1717

@@ -25,12 +25,13 @@ use super::error_with_position::{
2525
#[derive(Debug, Clone)]
2626
pub struct IsolatedLintHandlerOptions {
2727
pub use_cross_module: bool,
28+
pub type_aware: bool,
2829
pub root_path: PathBuf,
2930
pub tsconfig_path: Option<PathBuf>,
3031
}
3132

3233
pub struct IsolatedLintHandler {
33-
service: LintService,
34+
runner: LintRunner,
3435
directives_coordinator: DirectivesStore,
3536
unused_directives_severity: Option<AllowWarnDeny>,
3637
}
@@ -83,11 +84,11 @@ impl IsolatedLintHandler {
8384
lint_service_options = lint_service_options.with_tsconfig(tsconfig_path);
8485
}
8586

86-
let mut service = LintService::new(linter, lint_service_options);
87-
service.set_disable_directives_map(directives_coordinator.map());
88-
8987
Self {
90-
service,
88+
runner: LintRunnerBuilder::new(lint_service_options, linter)
89+
.with_type_aware(options.type_aware)
90+
.build()
91+
.unwrap(),
9192
directives_coordinator,
9293
unused_directives_severity: lint_options.report_unused_directive,
9394
}
@@ -125,14 +126,14 @@ impl IsolatedLintHandler {
125126
debug!("lint {}", path.display());
126127
let rope = &Rope::from_str(source_text);
127128

129+
let fs = Box::new(IsolatedLintHandlerFileSystem::new(
130+
path.to_path_buf(),
131+
Arc::from(source_text),
132+
));
133+
128134
let mut messages: Vec<MessageWithPosition<'a>> = self
129-
.service
130-
.with_file_system(Box::new(IsolatedLintHandlerFileSystem::new(
131-
path.to_path_buf(),
132-
Arc::from(source_text),
133-
)))
134-
.with_paths(vec![Arc::from(path.as_os_str())])
135-
.run_source(allocator)
135+
.runner
136+
.run_source(allocator, &Arc::from(path.as_os_str()), source_text.to_string(), fs)
136137
.into_iter()
137138
.map(|message| message_to_message_with_position(message, source_text, rope))
138139
.collect();

crates/oxc_language_server/src/linter/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,3 @@ pub mod error_with_position;
33
pub mod isolated_lint_handler;
44
pub mod options;
55
pub mod server_linter;
6-
pub mod tsgo_linter;

crates/oxc_language_server/src/linter/server_linter.rs

Lines changed: 9 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ use crate::linter::{
1919
error_with_position::DiagnosticReport,
2020
isolated_lint_handler::{IsolatedLintHandler, IsolatedLintHandlerOptions},
2121
options::{LintOptions as LSPLintOptions, Run},
22-
tsgo_linter::TsgoLinter,
2322
};
2423
use crate::{ConcurrentHashMap, OXC_CONFIG_FILE};
2524

@@ -34,7 +33,6 @@ pub enum ServerLinterRun {
3433

3534
pub struct ServerLinter {
3635
isolated_linter: Arc<Mutex<IsolatedLintHandler>>,
37-
tsgo_linter: Arc<Option<TsgoLinter>>,
3836
ignore_matcher: LintIgnoreMatcher,
3937
gitignore_glob: Vec<Gitignore>,
4038
lint_on_run: Run,
@@ -45,7 +43,6 @@ pub struct ServerLinter {
4543
#[derive(Debug, Default)]
4644
struct ServerLinterDiagnostics {
4745
isolated_linter: Arc<ConcurrentHashMap<String, Option<Vec<DiagnosticReport>>>>,
48-
tsgo_linter: Arc<ConcurrentHashMap<String, Option<Vec<DiagnosticReport>>>>,
4946
}
5047

5148
impl ServerLinterDiagnostics {
@@ -58,29 +55,15 @@ impl ServerLinterDiagnostics {
5855
reports.extend(diagnostics.clone());
5956
}
6057
}
61-
if let Some(entry) = self.tsgo_linter.pin().get(path) {
62-
found = true;
63-
if let Some(diagnostics) = entry {
64-
reports.extend(diagnostics.clone());
65-
}
66-
}
6758
if found { Some(reports) } else { None }
6859
}
6960

7061
pub fn remove_diagnostics(&self, path: &str) {
7162
self.isolated_linter.pin().remove(path);
72-
self.tsgo_linter.pin().remove(path);
7363
}
7464

7565
pub fn get_cached_files_of_diagnostics(&self) -> Vec<String> {
76-
let isolated_files = self.isolated_linter.pin().keys().cloned().collect::<Vec<_>>();
77-
let tsgo_files = self.tsgo_linter.pin().keys().cloned().collect::<Vec<_>>();
78-
79-
let mut files = Vec::with_capacity(isolated_files.len() + tsgo_files.len());
80-
files.extend(isolated_files);
81-
files.extend(tsgo_files);
82-
files.dedup();
83-
files
66+
self.isolated_linter.pin().keys().cloned().collect::<Vec<_>>()
8467
}
8568
}
8669

@@ -152,9 +135,10 @@ impl ServerLinter {
152135

153136
let isolated_linter = IsolatedLintHandler::new(
154137
lint_options,
155-
config_store.clone(), // clone because tsgo linter needs it
138+
config_store,
156139
&IsolatedLintHandlerOptions {
157140
use_cross_module,
141+
type_aware: options.type_aware,
158142
root_path: root_path.to_path_buf(),
159143
tsconfig_path: options.ts_config_path.as_ref().map(|path| {
160144
let path = Path::new(path).to_path_buf();
@@ -174,11 +158,6 @@ impl ServerLinter {
174158
extended_paths,
175159
lint_on_run: options.run,
176160
diagnostics: ServerLinterDiagnostics::default(),
177-
tsgo_linter: if options.type_aware {
178-
Arc::new(Some(TsgoLinter::new(&root_path, config_store)))
179-
} else {
180-
Arc::new(None)
181-
},
182161
}
183162
}
184163

@@ -333,10 +312,7 @@ impl ServerLinter {
333312
(ServerLinterRun::OnType, Run::OnSave) => (false, false),
334313
// In onType mode, only TypeScript type checking runs on save
335314
// If type_aware is disabled (tsgo_linter is None), skip everything to preserve diagnostics
336-
(ServerLinterRun::OnSave, Run::OnType) => {
337-
let should_run_tsgo = self.tsgo_linter.as_ref().is_some();
338-
(false, should_run_tsgo)
339-
}
315+
(ServerLinterRun::OnSave, Run::OnType) => (false, true),
340316
};
341317

342318
// return `None` when both tools do not want to be used
@@ -356,13 +332,6 @@ impl ServerLinter {
356332
self.diagnostics.isolated_linter.pin().insert(uri.to_string(), diagnostics);
357333
}
358334

359-
if tsgolint && let Some(tsgo_linter) = self.tsgo_linter.as_ref() {
360-
self.diagnostics
361-
.tsgo_linter
362-
.pin()
363-
.insert(uri.to_string(), tsgo_linter.lint_file(uri, content.clone()));
364-
}
365-
366335
self.diagnostics.get_diagnostics(&uri.to_string())
367336
}
368337

@@ -463,31 +432,23 @@ mod test {
463432
fn test_get_diagnostics_found_and_none_entries() {
464433
let key = "file:///test.js".to_string();
465434

466-
// Case 1: Both entries present, Some diagnostics
435+
// Case 1: Entry present, Some diagnostics
467436
let diag = DiagnosticReport::default();
468437
let diag_map = ConcurrentHashMap::default();
469438
diag_map.pin().insert(key.clone(), Some(vec![diag.clone()]));
470-
let tsgo_map = ConcurrentHashMap::default();
471-
tsgo_map.pin().insert(key.clone(), Some(vec![diag]));
472439

473-
let server_diag = super::ServerLinterDiagnostics {
474-
isolated_linter: std::sync::Arc::new(diag_map),
475-
tsgo_linter: std::sync::Arc::new(tsgo_map),
476-
};
440+
let server_diag =
441+
super::ServerLinterDiagnostics { isolated_linter: std::sync::Arc::new(diag_map) };
477442
let result = server_diag.get_diagnostics(&key);
478443
assert!(result.is_some());
479444
assert_eq!(result.unwrap().len(), 2);
480445

481446
// Case 2: Entry present, but value is None
482447
let diag_map_none = ConcurrentHashMap::default();
483448
diag_map_none.pin().insert(key.clone(), None);
484-
let tsgo_map_none = ConcurrentHashMap::default();
485-
tsgo_map_none.pin().insert(key.clone(), None);
486449

487-
let server_diag_none = ServerLinterDiagnostics {
488-
isolated_linter: std::sync::Arc::new(diag_map_none),
489-
tsgo_linter: std::sync::Arc::new(tsgo_map_none),
490-
};
450+
let server_diag_none =
451+
ServerLinterDiagnostics { isolated_linter: std::sync::Arc::new(diag_map_none) };
491452
let result_none = server_diag_none.get_diagnostics(&key);
492453
assert!(result_none.is_some());
493454
assert_eq!(result_none.unwrap().len(), 0);

crates/oxc_language_server/src/linter/tsgo_linter.rs

Lines changed: 0 additions & 73 deletions
This file was deleted.

crates/oxc_linter/src/lint_runner.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ use crate::{
1313
AllowWarnDeny, DisableDirectives, LintService, LintServiceOptions, Linter, TsGoLintState,
1414
};
1515

16+
#[cfg(feature = "language_server")]
17+
use crate::Message;
18+
1619
/// Unified runner that orchestrates both regular (oxc) and type-aware (tsgolint) linting
1720
/// with centralized disable directives handling.
1821
pub struct LintRunner {
@@ -220,6 +223,36 @@ impl LintRunner {
220223
Ok(self)
221224
}
222225

226+
/// Run both regular and type-aware linting on files
227+
/// # Errors
228+
/// Returns an error if type-aware linting fails.
229+
#[cfg(feature = "language_server")]
230+
pub fn run_source<'a>(
231+
&mut self,
232+
allocator: &'a mut oxc_allocator::Allocator,
233+
file: &Arc<OsStr>,
234+
source_text: String,
235+
file_system: Box<dyn crate::RuntimeFileSystem + Sync + Send>,
236+
) -> Vec<Message<'a>> {
237+
// Phase 1: Regular linting (collects disable directives)
238+
let directives_map = self.directives_store.map();
239+
240+
self.lint_service.with_paths(vec![file.clone()]);
241+
self.lint_service.set_disable_directives_map(directives_map);
242+
243+
// Set custom file system if provided
244+
self.lint_service.with_file_system(file_system);
245+
let mut messages = self.lint_service.run_source(allocator);
246+
247+
if let Some(type_aware_linter) = self.type_aware_linter.take()
248+
&& let Ok(tso_messages) = type_aware_linter.lint_source(file, source_text)
249+
{
250+
messages.extend(tso_messages);
251+
}
252+
253+
messages
254+
}
255+
223256
/// Report unused disable directives
224257
pub fn report_unused_directives(
225258
&self,

crates/oxc_linter/src/tsgolint.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ impl TsGoLintState {
291291
&self,
292292
path: &Arc<OsStr>,
293293
source_text: String,
294-
) -> Result<Vec<Message<'_>>, String> {
294+
) -> Result<Vec<Message<'static>>, String> {
295295
let mut resolved_configs: FxHashMap<PathBuf, ResolvedLinterState> = FxHashMap::default();
296296

297297
let json_input = self.json_input(std::slice::from_ref(path), &mut resolved_configs);

0 commit comments

Comments
 (0)