@@ -14,8 +14,8 @@ use tower_lsp_server::{
1414} ;
1515
1616use oxc_linter:: {
17- AllowWarnDeny , Config , ConfigStore , ConfigStoreBuilder , ExternalPluginStore , FixKind ,
18- LintIgnoreMatcher , LintOptions , Oxlintrc ,
17+ AllowWarnDeny , Config , ConfigStore , ConfigStoreBuilder , ExternalLinter , ExternalPluginStore ,
18+ FixKind , LintIgnoreMatcher , LintOptions , Oxlintrc ,
1919} ;
2020
2121use crate :: {
@@ -36,12 +36,17 @@ use crate::{
3636 utils:: normalize_path,
3737} ;
3838
39- pub struct ServerLinterBuilder ;
39+ pub struct ServerLinterBuilder {
40+ external_linter : Option < ExternalLinter > ,
41+ }
4042
4143impl ServerLinterBuilder {
44+ pub const fn new ( external_linter : Option < ExternalLinter > ) -> Self {
45+ Self { external_linter }
46+ }
4247 /// # Panics
4348 /// Panics if the root URI cannot be converted to a file path.
44- pub fn build ( root_uri : & Uri , options : serde_json:: Value ) -> ServerLinter {
49+ pub fn build ( & self , root_uri : & Uri , options : serde_json:: Value ) -> ServerLinter {
4550 let options = match serde_json:: from_value :: < LSPLintOptions > ( options) {
4651 Ok ( opts) => opts,
4752 Err ( e) => {
@@ -52,9 +57,18 @@ impl ServerLinterBuilder {
5257 }
5358 } ;
5459 let root_path = root_uri. to_file_path ( ) . unwrap ( ) ;
60+ let mut external_plugin_store = ExternalPluginStore :: new ( self . external_linter . is_some ( ) ) ;
61+
62+ let mut external_linter = self . external_linter . clone ( ) ;
5563 let mut nested_ignore_patterns = Vec :: new ( ) ;
56- let ( nested_configs, mut extended_paths) =
57- Self :: create_nested_configs ( & root_path, & options, & mut nested_ignore_patterns) ;
64+ let ( nested_configs, mut extended_paths) = Self :: create_nested_configs (
65+ & root_path,
66+ & options,
67+ external_linter. as_ref ( ) ,
68+ & mut external_plugin_store,
69+ & mut nested_ignore_patterns,
70+ ) ;
71+
5872 let config_path = options. config_path . as_ref ( ) . map_or ( LINT_CONFIG_FILE , |v| v) ;
5973 let config = normalize_path ( root_path. join ( config_path) ) ;
6074 let oxlintrc = if config. try_exists ( ) . is_ok_and ( |exists| exists) {
@@ -73,11 +87,13 @@ impl ServerLinterBuilder {
7387 } ;
7488
7589 let base_patterns = oxlintrc. ignore_patterns . clone ( ) ;
76-
77- let mut external_plugin_store = ExternalPluginStore :: new ( false ) ;
78- let config_builder =
79- ConfigStoreBuilder :: from_oxlintrc ( false , oxlintrc, None , & mut external_plugin_store)
80- . unwrap_or_default ( ) ;
90+ let config_builder = ConfigStoreBuilder :: from_oxlintrc (
91+ false ,
92+ oxlintrc,
93+ external_linter. as_ref ( ) ,
94+ & mut external_plugin_store,
95+ )
96+ . unwrap_or_default ( ) ;
8197
8298 // TODO(refactor): pull this into a shared function, because in oxlint we have the same functionality.
8399 let use_nested_config = options. use_nested_configs ( ) ;
@@ -90,9 +106,14 @@ impl ServerLinterBuilder {
90106 extended_paths. extend ( config_builder. extended_paths . clone ( ) ) ;
91107 let base_config = config_builder. build ( & external_plugin_store) . unwrap_or_else ( |err| {
92108 warn ! ( "Failed to build config: {err}" ) ;
93- ConfigStoreBuilder :: empty ( ) . build ( & external_plugin_store ) . unwrap ( )
109+ ConfigStoreBuilder :: empty ( ) . build ( & ExternalPluginStore :: new ( false ) ) . unwrap ( )
94110 } ) ;
95111
112+ // If no external rules, discard `ExternalLinter`
113+ if external_plugin_store. is_empty ( ) {
114+ external_linter = None ;
115+ }
116+
96117 let lint_options = LintOptions {
97118 fix : fix_kind,
98119 report_unused_directive : match options. unused_disable_directives {
@@ -119,6 +140,7 @@ impl ServerLinterBuilder {
119140 let isolated_linter = IsolatedLintHandler :: new (
120141 lint_options,
121142 config_store,
143+ external_linter. clone ( ) ,
122144 & IsolatedLintHandlerOptions {
123145 use_cross_module,
124146 type_aware : options. type_aware ,
@@ -135,6 +157,7 @@ impl ServerLinterBuilder {
135157 options. run ,
136158 root_path. to_path_buf ( ) ,
137159 isolated_linter,
160+ external_linter,
138161 LintIgnoreMatcher :: new ( & base_patterns, & root_path, nested_ignore_patterns) ,
139162 Self :: create_ignore_glob ( & root_path) ,
140163 extended_paths,
@@ -150,7 +173,7 @@ impl ToolBuilder for ServerLinterBuilder {
150173 vec ! [ FIX_ALL_COMMAND_ID . to_string( ) ]
151174 }
152175 fn build_boxed ( & self , root_uri : & Uri , options : serde_json:: Value ) -> Box < dyn Tool > {
153- Box :: new ( ServerLinterBuilder :: build ( root_uri, options) )
176+ Box :: new ( self . build ( root_uri, options) )
154177 }
155178}
156179
@@ -160,6 +183,8 @@ impl ServerLinterBuilder {
160183 fn create_nested_configs (
161184 root_path : & Path ,
162185 options : & LSPLintOptions ,
186+ external_linter : Option < & ExternalLinter > ,
187+ external_plugin_store : & mut ExternalPluginStore ,
163188 nested_ignore_patterns : & mut Vec < ( Vec < String > , PathBuf ) > ,
164189 ) -> ( ConcurrentHashMap < PathBuf , Config > , FxHashSet < PathBuf > ) {
165190 let mut extended_paths = FxHashSet :: default ( ) ;
@@ -184,20 +209,29 @@ impl ServerLinterBuilder {
184209 } ;
185210 // Collect ignore patterns and their root
186211 nested_ignore_patterns. push ( ( oxlintrc. ignore_patterns . clone ( ) , dir_path. to_path_buf ( ) ) ) ;
187- let mut external_plugin_store = ExternalPluginStore :: new ( false ) ;
188- let Ok ( config_store_builder) = ConfigStoreBuilder :: from_oxlintrc (
212+
213+ let Ok ( config_store_builder) = ( match ConfigStoreBuilder :: from_oxlintrc (
189214 false ,
190215 oxlintrc,
191- None ,
192- & mut external_plugin_store,
193- ) else {
194- warn ! ( "Skipping config (builder failed): {}" , file_path. display( ) ) ;
216+ external_linter,
217+ external_plugin_store,
218+ ) {
219+ Ok ( builder) => Ok ( builder) ,
220+ Err ( err) => {
221+ warn ! (
222+ "Failed to create ConfigStoreBuilder for {}: {:?}" ,
223+ dir_path. display( ) ,
224+ err
225+ ) ;
226+ Err ( err)
227+ }
228+ } ) else {
195229 continue ;
196230 } ;
197231 extended_paths. extend ( config_store_builder. extended_paths . clone ( ) ) ;
198- let config = config_store_builder. build ( & external_plugin_store) . unwrap_or_else ( |err| {
232+ let config = config_store_builder. build ( external_plugin_store) . unwrap_or_else ( |err| {
199233 warn ! ( "Failed to build nested config for {}: {:?}" , dir_path. display( ) , err) ;
200- ConfigStoreBuilder :: empty ( ) . build ( & external_plugin_store ) . unwrap ( )
234+ ConfigStoreBuilder :: empty ( ) . build ( & ExternalPluginStore :: new ( false ) ) . unwrap ( )
201235 } ) ;
202236 nested_configs. pin ( ) . insert ( dir_path. to_path_buf ( ) , config) ;
203237 }
@@ -244,6 +278,7 @@ pub struct ServerLinter {
244278 run : Run ,
245279 cwd : PathBuf ,
246280 isolated_linter : IsolatedLintHandler ,
281+ external_linter : Option < ExternalLinter > ,
247282 ignore_matcher : LintIgnoreMatcher ,
248283 gitignore_glob : Vec < Gitignore > ,
249284 extended_paths : FxHashSet < PathBuf > ,
@@ -299,7 +334,8 @@ impl Tool for ServerLinter {
299334
300335 // get the cached files before refreshing the linter, and revalidate them after
301336 let cached_files = self . get_cached_files_of_diagnostics ( ) ;
302- let new_linter = ServerLinterBuilder :: build ( root_uri, new_options_json. clone ( ) ) ;
337+ let new_linter = ServerLinterBuilder :: new ( self . external_linter . clone ( ) )
338+ . build ( root_uri, new_options_json. clone ( ) ) ;
303339 let diagnostics = Some ( new_linter. revalidate_diagnostics ( cached_files) ) ;
304340
305341 let patterns = {
@@ -353,7 +389,8 @@ impl Tool for ServerLinter {
353389 options : serde_json:: Value ,
354390 ) -> ToolRestartChanges {
355391 // TODO: Check if the changed file is actually a config file (including extended paths)
356- let new_linter = ServerLinterBuilder :: build ( root_uri, options) ;
392+ let new_linter =
393+ ServerLinterBuilder :: new ( self . external_linter . clone ( ) ) . build ( root_uri, options) ;
357394
358395 // get the cached files before refreshing the linter, and revalidate them after
359396 let cached_files = self . get_cached_files_of_diagnostics ( ) ;
@@ -508,6 +545,7 @@ impl ServerLinter {
508545 run : Run ,
509546 cwd : PathBuf ,
510547 isolated_linter : IsolatedLintHandler ,
548+ external_linter : Option < ExternalLinter > ,
511549 ignore_matcher : LintIgnoreMatcher ,
512550 gitignore_glob : Vec < Gitignore > ,
513551 extended_paths : FxHashSet < PathBuf > ,
@@ -516,6 +554,7 @@ impl ServerLinter {
516554 run,
517555 cwd,
518556 isolated_linter,
557+ external_linter,
519558 ignore_matcher,
520559 gitignore_glob,
521560 extended_paths,
@@ -611,6 +650,7 @@ fn range_overlaps(a: Range, b: Range) -> bool {
611650mod test {
612651 use std:: path:: { Path , PathBuf } ;
613652
653+ use oxc_linter:: ExternalPluginStore ;
614654 use serde_json:: json;
615655
616656 use crate :: linter:: {
@@ -622,9 +662,12 @@ mod test {
622662 #[ test]
623663 fn test_create_nested_configs_with_disabled_nested_configs ( ) {
624664 let mut nested_ignore_patterns = Vec :: new ( ) ;
665+ let mut external_plugin_store = ExternalPluginStore :: default ( ) ;
625666 let ( configs, _) = ServerLinterBuilder :: create_nested_configs (
626667 Path :: new ( "/root/" ) ,
627668 & LintOptions { disable_nested_config : true , ..LintOptions :: default ( ) } ,
669+ None ,
670+ & mut external_plugin_store,
628671 & mut nested_ignore_patterns,
629672 ) ;
630673
@@ -634,9 +677,13 @@ mod test {
634677 #[ test]
635678 fn test_create_nested_configs ( ) {
636679 let mut nested_ignore_patterns = Vec :: new ( ) ;
680+ let mut external_plugin_store = ExternalPluginStore :: default ( ) ;
681+
637682 let ( configs, _) = ServerLinterBuilder :: create_nested_configs (
638683 & get_file_path ( "fixtures/linter/init_nested_configs" ) ,
639684 & LintOptions :: default ( ) ,
685+ None ,
686+ & mut external_plugin_store,
640687 & mut nested_ignore_patterns,
641688 ) ;
642689 let configs = configs. pin ( ) ;
0 commit comments