Skip to content

Commit c12bf89

Browse files
committed
feat(language_server): use linter runtime
1 parent 5e435f1 commit c12bf89

File tree

8 files changed

+96
-140
lines changed

8 files changed

+96
-140
lines changed

Cargo.lock

Lines changed: 0 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/oxc_language_server/Cargo.toml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,9 @@ test = true
2222
doctest = false
2323

2424
[dependencies]
25-
oxc_allocator = { workspace = true }
2625
oxc_data_structures = { workspace = true, features = ["rope"] }
2726
oxc_diagnostics = { workspace = true }
2827
oxc_linter = { workspace = true }
29-
oxc_parser = { workspace = true }
30-
oxc_semantic = { workspace = true }
3128

3229
cow-utils = { workspace = true }
3330
env_logger = { workspace = true, features = ["humantime"] }
Lines changed: 42 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,34 @@
11
use std::{
22
fs,
3-
path::Path,
4-
rc::Rc,
5-
sync::{Arc, OnceLock},
3+
path::{Path, PathBuf},
4+
sync::{Arc, OnceLock, mpsc},
65
};
76

87
use log::debug;
98
use rustc_hash::FxHashSet;
10-
use tower_lsp::lsp_types::{self, DiagnosticRelatedInformation, DiagnosticSeverity, Range};
9+
use tower_lsp::lsp_types::{self, DiagnosticRelatedInformation, DiagnosticSeverity};
1110

12-
use oxc_allocator::Allocator;
1311
use oxc_diagnostics::{Error, NamedSource};
14-
use oxc_linter::{
15-
LINTABLE_EXTENSIONS, Linter, ModuleRecord,
16-
loader::{JavaScriptSource, Loader},
17-
};
18-
use oxc_parser::{ParseOptions, Parser};
19-
use oxc_semantic::SemanticBuilder;
12+
use oxc_linter::{LINTABLE_EXTENSIONS, LintService, LintServiceOptions, Linter, loader::Loader};
2013

2114
use crate::DiagnosticReport;
22-
use crate::linter::error_with_position::{ErrorReport, ErrorWithPosition, FixedContent};
23-
use crate::linter::offset_to_position;
15+
use crate::linter::error_with_position::{ErrorReport, ErrorWithPosition};
16+
17+
/// smaller subset of LintServiceOptions, which is used by IsolatedLintHandler
18+
#[derive(Debug, Clone)]
19+
pub struct IsolatedLintHandlerOptions {
20+
pub use_cross_module: bool,
21+
pub root_path: PathBuf,
22+
}
2423

2524
pub struct IsolatedLintHandler {
2625
linter: Arc<Linter>,
27-
loader: Loader,
26+
options: Arc<IsolatedLintHandlerOptions>,
2827
}
2928

3029
impl IsolatedLintHandler {
31-
pub fn new(linter: Arc<Linter>) -> Self {
32-
Self { linter, loader: Loader }
30+
pub fn new(linter: Arc<Linter>, options: Arc<IsolatedLintHandlerOptions>) -> Self {
31+
Self { linter, options }
3332
}
3433

3534
pub fn run_single(
@@ -100,89 +99,31 @@ impl IsolatedLintHandler {
10099
return None;
101100
}
102101
let source_text = source_text.or_else(|| fs::read_to_string(path).ok())?;
103-
let javascript_sources = match self.loader.load_str(path, &source_text) {
104-
Ok(s) => s,
105-
Err(e) => {
106-
debug!("failed to load {path:?}: {e}");
107-
return None;
108-
}
109-
};
110102

111103
debug!("lint {path:?}");
112-
let mut diagnostics = vec![];
113-
for source in javascript_sources {
114-
let JavaScriptSource {
115-
source_text: javascript_source_text, source_type, start, ..
116-
} = source;
117-
let allocator = Allocator::default();
118-
let ret = Parser::new(&allocator, javascript_source_text, source_type)
119-
.with_options(ParseOptions {
120-
allow_return_outside_function: true,
121-
parse_regular_expression: true,
122-
..ParseOptions::default()
123-
})
124-
.parse();
125-
126-
if !ret.errors.is_empty() {
127-
let reports = ret
128-
.errors
129-
.into_iter()
130-
.map(|diagnostic| ErrorReport {
131-
error: Error::from(diagnostic),
132-
fixed_content: None,
133-
})
134-
.collect();
135-
return Some(Self::wrap_diagnostics(path, &source_text, reports, start));
104+
let mut diagnostics_result = vec![];
105+
106+
let (sender, receiver) = mpsc::channel();
107+
let lint_service_options = LintServiceOptions::new(
108+
self.options.root_path.clone(),
109+
vec![Arc::from(path.as_os_str())],
110+
)
111+
.with_cross_module(self.options.use_cross_module);
112+
// ToDo: do not clone the linter
113+
let mut lint_service = LintService::new((*self.linter).clone(), lint_service_options);
114+
tokio::task::block_in_place(move || {
115+
lint_service.run(&sender);
116+
});
117+
118+
while let Ok(Some((path, diagnostics))) = receiver.recv() {
119+
for diagnostic in diagnostics {
120+
// ToDo: reimplement fixed_content
121+
let report = ErrorReport { error: Error::from(diagnostic), fixed_content: None };
122+
diagnostics_result.push(Self::wrap_diagnostics(&path, &source_text, report));
136123
}
137-
138-
let semantic_ret = SemanticBuilder::new()
139-
.with_cfg(true)
140-
.with_scope_tree_child_ids(true)
141-
.with_check_syntax_error(true)
142-
.build(&ret.program);
143-
144-
if !semantic_ret.errors.is_empty() {
145-
let reports = semantic_ret
146-
.errors
147-
.into_iter()
148-
.map(|diagnostic| ErrorReport {
149-
error: Error::from(diagnostic),
150-
fixed_content: None,
151-
})
152-
.collect();
153-
return Some(Self::wrap_diagnostics(path, &source_text, reports, start));
154-
}
155-
156-
let mut semantic = semantic_ret.semantic;
157-
semantic.set_irregular_whitespaces(ret.irregular_whitespaces);
158-
let module_record = Arc::new(ModuleRecord::new(path, &ret.module_record, &semantic));
159-
let result = self.linter.run(path, Rc::new(semantic), module_record);
160-
161-
let reports = result
162-
.into_iter()
163-
.map(|msg| {
164-
let fixed_content = msg.fix.map(|f| FixedContent {
165-
message: f.message.map(|m| m.to_string()),
166-
code: f.content.to_string(),
167-
range: Range {
168-
start: offset_to_position(
169-
(f.span.start + start) as usize,
170-
source_text.as_str(),
171-
),
172-
end: offset_to_position(
173-
(f.span.end + start) as usize,
174-
source_text.as_str(),
175-
),
176-
},
177-
});
178-
179-
ErrorReport { error: Error::from(msg.error), fixed_content }
180-
})
181-
.collect::<Vec<ErrorReport>>();
182-
diagnostics.extend(Self::wrap_diagnostics(path, &source_text, reports, start));
183124
}
184125

185-
Some(diagnostics)
126+
Some(diagnostics_result)
186127
}
187128

188129
fn should_lint_path(path: &Path) -> bool {
@@ -195,24 +136,14 @@ impl IsolatedLintHandler {
195136
.is_some_and(|ext| wanted_exts.contains(ext))
196137
}
197138

198-
fn wrap_diagnostics(
199-
path: &Path,
200-
source_text: &str,
201-
reports: Vec<ErrorReport>,
202-
start: u32,
203-
) -> Vec<ErrorWithPosition> {
139+
fn wrap_diagnostics(path: &Path, source_text: &str, report: ErrorReport) -> ErrorWithPosition {
204140
let source = Arc::new(NamedSource::new(path.to_string_lossy(), source_text.to_owned()));
205141

206-
reports
207-
.into_iter()
208-
.map(|report| {
209-
ErrorWithPosition::new(
210-
report.error.with_source_code(Arc::clone(&source)),
211-
source_text,
212-
report.fixed_content,
213-
start as usize,
214-
)
215-
})
216-
.collect()
142+
ErrorWithPosition::new(
143+
report.error.with_source_code(Arc::clone(&source)),
144+
source_text,
145+
report.fixed_content,
146+
0,
147+
)
217148
}
218149
}

crates/oxc_language_server/src/linter/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use tower_lsp::lsp_types::Position;
33

44
pub mod config_walker;
55
pub mod error_with_position;
6-
mod isolated_lint_handler;
6+
pub mod isolated_lint_handler;
77
pub mod server_linter;
88

99
#[cfg(test)]

crates/oxc_language_server/src/linter/server_linter.rs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,32 @@ use std::sync::Arc;
22

33
use tower_lsp::lsp_types::Url;
44

5-
use oxc_linter::{ConfigStoreBuilder, FixKind, LintOptions, Linter};
5+
use oxc_linter::{ConfigStoreBuilder, LintOptions, Linter};
66

77
use crate::linter::error_with_position::DiagnosticReport;
88
use crate::linter::isolated_lint_handler::IsolatedLintHandler;
99

10+
use super::isolated_lint_handler::IsolatedLintHandlerOptions;
11+
1012
pub struct ServerLinter {
1113
linter: Arc<Linter>,
14+
options: Arc<IsolatedLintHandlerOptions>,
1215
}
1316

1417
impl ServerLinter {
15-
pub fn new() -> Self {
18+
pub fn new(options: IsolatedLintHandlerOptions) -> Self {
1619
let config_store =
1720
ConfigStoreBuilder::default().build().expect("Failed to build config store");
18-
let linter = Linter::new(LintOptions::default(), config_store).with_fix(FixKind::SafeFix);
19-
Self { linter: Arc::new(linter) }
21+
let linter = Linter::new(LintOptions::default(), config_store);
22+
Self { linter: Arc::new(linter), options: Arc::new(options) }
2023
}
2124

22-
pub fn new_with_linter(linter: Linter) -> Self {
23-
Self { linter: Arc::new(linter) }
25+
pub fn new_with_linter(linter: Linter, options: IsolatedLintHandlerOptions) -> Self {
26+
Self { linter: Arc::new(linter), options: Arc::new(options) }
2427
}
2528

2629
pub fn run_single(&self, uri: &Url, content: Option<String>) -> Option<Vec<DiagnosticReport>> {
27-
IsolatedLintHandler::new(Arc::clone(&self.linter))
30+
IsolatedLintHandler::new(Arc::clone(&self.linter), Arc::clone(&self.options))
2831
.run_single(&uri.to_file_path().unwrap(), content)
2932
}
3033
}
@@ -50,7 +53,7 @@ mod test {
5053
.with_filter(&LintFilter::deny(LintFilterKind::parse("no-console".into()).unwrap()))
5154
.build()
5255
.unwrap();
53-
let linter = Linter::new(LintOptions::default(), config_store).with_fix(FixKind::SafeFix);
56+
let linter = Linter::new(LintOptions::default(), config_store);
5457

5558
Tester::new_with_linter(linter)
5659
.with_snapshot_suffix("deny_no_console")
@@ -68,7 +71,7 @@ mod test {
6871
.unwrap()
6972
.build()
7073
.unwrap();
71-
let linter = Linter::new(LintOptions::default(), config_store).with_fix(FixKind::SafeFix);
74+
let linter = Linter::new(LintOptions::default(), config_store);
7275

7376
Tester::new_with_linter(linter)
7477
.test_and_snapshot_single_file("fixtures/linter/issue_9958/issue.ts");
@@ -85,7 +88,7 @@ mod test {
8588
.unwrap()
8689
.build()
8790
.unwrap();
88-
let linter = Linter::new(LintOptions::default(), config_store).with_fix(FixKind::SafeFix);
91+
let linter = Linter::new(LintOptions::default(), config_store);
8992

9093
Tester::new_with_linter(linter)
9194
.test_and_snapshot_single_file("fixtures/linter/regexp_feature/index.ts");

crates/oxc_language_server/src/linter/tester.rs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ use std::fmt::Write;
33
use oxc_linter::Linter;
44
use tower_lsp::lsp_types::{CodeDescription, NumberOrString, Url};
55

6-
use super::{error_with_position::DiagnosticReport, server_linter::ServerLinter};
6+
use super::{
7+
error_with_position::DiagnosticReport, isolated_lint_handler::IsolatedLintHandlerOptions,
8+
server_linter::ServerLinter,
9+
};
710

811
/// Given a file path relative to the crate root directory, return the URI of the file.
912
pub fn get_file_uri(relative_file_path: &str) -> Url {
@@ -86,11 +89,26 @@ pub struct Tester<'t> {
8689

8790
impl Tester<'_> {
8891
pub fn new() -> Self {
89-
Self { snapshot_suffix: None, server_linter: ServerLinter::new() }
92+
Self {
93+
snapshot_suffix: None,
94+
server_linter: ServerLinter::new(IsolatedLintHandlerOptions {
95+
use_cross_module: false,
96+
root_path: std::env::current_dir().expect("could not get current dir"),
97+
}),
98+
}
9099
}
91100

92101
pub fn new_with_linter(linter: Linter) -> Self {
93-
Self { snapshot_suffix: None, server_linter: ServerLinter::new_with_linter(linter) }
102+
Self {
103+
snapshot_suffix: None,
104+
server_linter: ServerLinter::new_with_linter(
105+
linter,
106+
IsolatedLintHandlerOptions {
107+
use_cross_module: false,
108+
root_path: std::env::current_dir().expect("could not get current dir"),
109+
},
110+
),
111+
}
94112
}
95113

96114
pub fn with_snapshot_suffix(mut self, suffix: &'static str) -> Self {

crates/oxc_language_server/src/main.rs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use commands::LSP_COMMANDS;
22
use futures::future::join_all;
33
use globset::Glob;
44
use ignore::gitignore::Gitignore;
5-
use linter::config_walker::ConfigWalker;
5+
use linter::{config_walker::ConfigWalker, isolated_lint_handler::IsolatedLintHandlerOptions};
66
use log::{debug, error, info};
77
use oxc_linter::{ConfigStore, ConfigStoreBuilder, FixKind, LintOptions, Linter, Oxlintrc};
88
use rustc_hash::{FxBuildHasher, FxHashMap};
@@ -580,22 +580,29 @@ impl Backend {
580580
.build()
581581
.expect("failed to build config");
582582

583-
let lint_options =
584-
LintOptions { fix: self.options.lock().await.fix_kind(), ..Default::default() };
583+
// ToDo: add support for tsconfig
584+
// ToDo: fix-kind should be applied when running code action or commands -- fix: self.options.lock().await.fix_kind()
585+
let lint_options = LintOptions { fix: FixKind::None, ..Default::default() };
585586

586-
let linter = if self.options.lock().await.disable_nested_configs() {
587-
Linter::new(lint_options, config_store)
587+
let (linter, use_cross_module) = if self.options.lock().await.disable_nested_configs() {
588+
(Linter::new(lint_options, config_store), oxlintrc.plugins.has_import())
588589
} else {
589590
let nested_configs = self.nested_configs.pin();
590591
let nested_configs_copy: FxHashMap<PathBuf, ConfigStore> = nested_configs
591592
.iter()
592593
.map(|(key, value)| (key.clone(), value.clone()))
593594
.collect::<FxHashMap<_, _>>();
594595

595-
Linter::new_with_nested_configs(lint_options, config_store, nested_configs_copy)
596+
(
597+
Linter::new_with_nested_configs(lint_options, config_store, nested_configs_copy),
598+
nested_configs.values().any(|config| config.plugins().has_import()),
599+
)
596600
};
597601

598-
*self.server_linter.write().await = ServerLinter::new_with_linter(linter);
602+
*self.server_linter.write().await = ServerLinter::new_with_linter(
603+
linter,
604+
IsolatedLintHandlerOptions { use_cross_module, root_path },
605+
);
599606

600607
Some(oxlintrc.clone())
601608
}
@@ -649,7 +656,10 @@ async fn main() {
649656
let stdin = tokio::io::stdin();
650657
let stdout = tokio::io::stdout();
651658

652-
let server_linter = ServerLinter::new();
659+
let server_linter = ServerLinter::new(IsolatedLintHandlerOptions {
660+
use_cross_module: false,
661+
root_path: PathBuf::new(),
662+
});
653663
let diagnostics_report_map = ConcurrentHashMap::default();
654664

655665
let (service, socket) = LspService::build(|client| Backend {

0 commit comments

Comments
 (0)