Skip to content

Commit 5f81e63

Browse files
committed
refactor(language_server): split ServerLinter creation with *Builder pattern (#15282)
> This PR refactors the ServerLinter initialization to use a builder pattern, separating construction logic from the linter instance itself. The main purpose is to improve code organization and API design.
1 parent 000a891 commit 5f81e63

File tree

2 files changed

+62
-37
lines changed

2 files changed

+62
-37
lines changed

crates/oxc_language_server/src/linter/server_linter.rs

Lines changed: 53 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -25,23 +25,24 @@ use crate::{ConcurrentHashMap, LINT_CONFIG_FILE};
2525

2626
use super::config_walker::ConfigWalker;
2727

28-
pub struct ServerLinter {
29-
isolated_linter: Arc<Mutex<IsolatedLintHandler>>,
30-
ignore_matcher: LintIgnoreMatcher,
31-
gitignore_glob: Vec<Gitignore>,
32-
diagnostics: Arc<ConcurrentHashMap<String, Option<Vec<DiagnosticReport>>>>,
33-
extended_paths: FxHashSet<PathBuf>,
28+
pub struct ServerLinterBuilder {
29+
root_uri: Uri,
30+
options: LSPLintOptions,
3431
}
3532

36-
impl ServerLinter {
33+
impl ServerLinterBuilder {
34+
pub fn new(root_uri: Uri, options: LSPLintOptions) -> Self {
35+
Self { root_uri, options }
36+
}
37+
3738
/// # Panics
3839
/// Panics if the root URI cannot be converted to a file path.
39-
pub fn new(root_uri: &Uri, options: &LSPLintOptions) -> Self {
40-
let root_path = root_uri.to_file_path().unwrap();
40+
pub fn build(&self) -> ServerLinter {
41+
let root_path = self.root_uri.to_file_path().unwrap();
4142
let mut nested_ignore_patterns = Vec::new();
4243
let (nested_configs, mut extended_paths) =
43-
Self::create_nested_configs(&root_path, options, &mut nested_ignore_patterns);
44-
let config_path = options.config_path.as_ref().map_or(LINT_CONFIG_FILE, |v| v);
44+
Self::create_nested_configs(&root_path, &self.options, &mut nested_ignore_patterns);
45+
let config_path = self.options.config_path.as_ref().map_or(LINT_CONFIG_FILE, |v| v);
4546
let config = normalize_path(root_path.join(config_path));
4647
let oxlintrc = if config.try_exists().is_ok_and(|exists| exists) {
4748
if let Ok(oxlintrc) = Oxlintrc::from_file(&config) {
@@ -66,8 +67,8 @@ impl ServerLinter {
6667
.unwrap_or_default();
6768

6869
// TODO(refactor): pull this into a shared function, because in oxlint we have the same functionality.
69-
let use_nested_config = options.use_nested_configs();
70-
let fix_kind = FixKind::from(options.fix_kind.clone());
70+
let use_nested_config = self.options.use_nested_configs();
71+
let fix_kind = FixKind::from(self.options.fix_kind.clone());
7172

7273
let use_cross_module = config_builder.plugins().has_import()
7374
|| (use_nested_config
@@ -81,7 +82,7 @@ impl ServerLinter {
8182

8283
let lint_options = LintOptions {
8384
fix: fix_kind,
84-
report_unused_directive: match options.unused_disable_directives {
85+
report_unused_directive: match self.options.unused_disable_directives {
8586
UnusedDisableDirectives::Allow => None, // or AllowWarnDeny::Allow, should be the same?
8687
UnusedDisableDirectives::Warn => Some(AllowWarnDeny::Warn),
8788
UnusedDisableDirectives::Deny => Some(AllowWarnDeny::Deny),
@@ -107,27 +108,22 @@ impl ServerLinter {
107108
config_store,
108109
&IsolatedLintHandlerOptions {
109110
use_cross_module,
110-
type_aware: options.type_aware,
111-
fix_kind: FixKind::from(options.fix_kind.clone()),
111+
type_aware: self.options.type_aware,
112+
fix_kind: FixKind::from(self.options.fix_kind.clone()),
112113
root_path: root_path.to_path_buf(),
113-
tsconfig_path: options.ts_config_path.as_ref().map(|path| {
114+
tsconfig_path: self.options.ts_config_path.as_ref().map(|path| {
114115
let path = Path::new(path).to_path_buf();
115116
if path.is_relative() { root_path.join(path) } else { path }
116117
}),
117118
},
118119
);
119120

120-
Self {
121-
isolated_linter: Arc::new(Mutex::new(isolated_linter)),
122-
ignore_matcher: LintIgnoreMatcher::new(
123-
&base_patterns,
124-
&root_path,
125-
nested_ignore_patterns,
126-
),
127-
gitignore_glob: Self::create_ignore_glob(&root_path),
121+
ServerLinter::new(
122+
Arc::new(Mutex::new(isolated_linter)),
123+
LintIgnoreMatcher::new(&base_patterns, &root_path, nested_ignore_patterns),
124+
Self::create_ignore_glob(&root_path),
128125
extended_paths,
129-
diagnostics: Arc::new(ConcurrentHashMap::default()),
130-
}
126+
)
131127
}
132128

133129
/// Searches inside root_uri recursively for the default oxlint config files
@@ -213,6 +209,33 @@ impl ServerLinter {
213209

214210
gitignore_globs
215211
}
212+
}
213+
214+
pub struct ServerLinter {
215+
isolated_linter: Arc<Mutex<IsolatedLintHandler>>,
216+
ignore_matcher: LintIgnoreMatcher,
217+
gitignore_glob: Vec<Gitignore>,
218+
extended_paths: FxHashSet<PathBuf>,
219+
diagnostics: Arc<ConcurrentHashMap<String, Option<Vec<DiagnosticReport>>>>,
220+
}
221+
222+
impl ServerLinter {
223+
/// # Panics
224+
/// Panics if the root URI cannot be converted to a file path.
225+
pub fn new(
226+
isolated_linter: Arc<Mutex<IsolatedLintHandler>>,
227+
ignore_matcher: LintIgnoreMatcher,
228+
gitignore_glob: Vec<Gitignore>,
229+
extended_paths: FxHashSet<PathBuf>,
230+
) -> Self {
231+
Self {
232+
isolated_linter,
233+
ignore_matcher,
234+
gitignore_glob,
235+
extended_paths,
236+
diagnostics: Arc::new(ConcurrentHashMap::default()),
237+
}
238+
}
216239

217240
pub fn remove_diagnostics(&self, uri: &Uri) {
218241
self.diagnostics.pin().remove(&uri.to_string());
@@ -336,14 +359,14 @@ mod test {
336359
use serde_json::json;
337360

338361
use crate::{
339-
linter::{options::LintOptions, server_linter::ServerLinter},
362+
linter::{options::LintOptions, server_linter::ServerLinterBuilder},
340363
tester::{Tester, get_file_path},
341364
};
342365

343366
#[test]
344367
fn test_create_nested_configs_with_disabled_nested_configs() {
345368
let mut nested_ignore_patterns = Vec::new();
346-
let (configs, _) = ServerLinter::create_nested_configs(
369+
let (configs, _) = ServerLinterBuilder::create_nested_configs(
347370
Path::new("/root/"),
348371
&LintOptions { disable_nested_config: true, ..LintOptions::default() },
349372
&mut nested_ignore_patterns,
@@ -355,7 +378,7 @@ mod test {
355378
#[test]
356379
fn test_create_nested_configs() {
357380
let mut nested_ignore_patterns = Vec::new();
358-
let (configs, _) = ServerLinter::create_nested_configs(
381+
let (configs, _) = ServerLinterBuilder::create_nested_configs(
359382
&get_file_path("fixtures/linter/init_nested_configs"),
360383
&LintOptions::default(),
361384
&mut nested_ignore_patterns,

crates/oxc_language_server/src/worker.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use crate::{
1616
linter::{
1717
error_with_position::DiagnosticReport,
1818
options::{LintOptions, Run},
19-
server_linter::ServerLinter,
19+
server_linter::{ServerLinter, ServerLinterBuilder},
2020
},
2121
options::Options,
2222
};
@@ -74,7 +74,9 @@ impl WorkspaceWorker {
7474
*self.options.lock().await = Some(options.clone());
7575
let options = serde_json::from_value::<Options>(options.clone()).unwrap_or_default();
7676

77-
*self.server_linter.write().await = Some(ServerLinter::new(&self.root_uri, &options.lint));
77+
*self.server_linter.write().await =
78+
Some(ServerLinterBuilder::new(self.root_uri.clone(), options.lint).build());
79+
7880
if options.format.experimental {
7981
debug!("experimental formatter enabled");
8082
*self.server_formatter.write().await =
@@ -175,8 +177,8 @@ impl WorkspaceWorker {
175177
/// Refresh the server linter with the current options
176178
/// This will recreate the linter and re-read the config files.
177179
/// Call this when the options have changed and the linter needs to be updated.
178-
async fn refresh_server_linter(&self, lint_options: &LintOptions) {
179-
let server_linter = ServerLinter::new(&self.root_uri, lint_options);
180+
async fn refresh_server_linter(&self, lint_options: LintOptions) {
181+
let server_linter = ServerLinterBuilder::new(self.root_uri.clone(), lint_options).build();
180182

181183
*self.server_linter.write().await = Some(server_linter);
182184
}
@@ -346,10 +348,10 @@ impl WorkspaceWorker {
346348
if options.format.experimental {
347349
tokio::join!(
348350
self.refresh_server_formatter(&options.format),
349-
self.refresh_server_linter(&options.lint)
351+
self.refresh_server_linter(options.lint)
350352
);
351353
} else {
352-
self.refresh_server_linter(&options.lint).await;
354+
self.refresh_server_linter(options.lint).await;
353355
}
354356

355357
Some(self.revalidate_diagnostics(files).await)
@@ -461,7 +463,7 @@ impl WorkspaceWorker {
461463
.map(|linter: &ServerLinter| linter.get_cached_files_of_diagnostics())
462464
};
463465

464-
self.refresh_server_linter(&changed_options.lint).await;
466+
self.refresh_server_linter(changed_options.lint.clone()).await;
465467

466468
// Get the Watch patterns (including the files from oxlint `extends`)
467469
let patterns = {

0 commit comments

Comments
 (0)