@@ -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 :: default ( ) ;
61+
62+ let mut external_linter = self . external_linter . as_ref ( ) ;
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,
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,
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. cloned ( ) ,
122144 & IsolatedLintHandlerOptions {
123145 use_cross_module,
124146 type_aware : options. type_aware ,
@@ -150,7 +172,7 @@ impl ToolBuilder for ServerLinterBuilder {
150172 vec ! [ FIX_ALL_COMMAND_ID . to_string( ) ]
151173 }
152174 fn build_boxed ( & self , root_uri : & Uri , options : serde_json:: Value ) -> Box < dyn Tool > {
153- Box :: new ( ServerLinterBuilder :: build ( root_uri, options) )
175+ Box :: new ( self . build ( root_uri, options) )
154176 }
155177}
156178
@@ -160,6 +182,8 @@ impl ServerLinterBuilder {
160182 fn create_nested_configs (
161183 root_path : & Path ,
162184 options : & LSPLintOptions ,
185+ external_linter : Option < & ExternalLinter > ,
186+ external_plugin_store : & mut ExternalPluginStore ,
163187 nested_ignore_patterns : & mut Vec < ( Vec < String > , PathBuf ) > ,
164188 ) -> ( ConcurrentHashMap < PathBuf , Config > , FxHashSet < PathBuf > ) {
165189 let mut extended_paths = FxHashSet :: default ( ) ;
@@ -184,20 +208,29 @@ impl ServerLinterBuilder {
184208 } ;
185209 // Collect ignore patterns and their root
186210 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 (
211+
212+ let Ok ( config_store_builder) = ( match ConfigStoreBuilder :: from_oxlintrc (
189213 false ,
190214 oxlintrc,
191- None ,
192- & mut external_plugin_store,
193- ) else {
194- warn ! ( "Skipping config (builder failed): {}" , file_path. display( ) ) ;
215+ external_linter,
216+ external_plugin_store,
217+ ) {
218+ Ok ( builder) => Ok ( builder) ,
219+ Err ( err) => {
220+ warn ! (
221+ "Failed to create ConfigStoreBuilder for {}: {:?}" ,
222+ dir_path. display( ) ,
223+ err
224+ ) ;
225+ Err ( err)
226+ }
227+ } ) else {
195228 continue ;
196229 } ;
197230 extended_paths. extend ( config_store_builder. extended_paths . clone ( ) ) ;
198- let config = config_store_builder. build ( & external_plugin_store) . unwrap_or_else ( |err| {
231+ let config = config_store_builder. build ( external_plugin_store) . unwrap_or_else ( |err| {
199232 warn ! ( "Failed to build nested config for {}: {:?}" , dir_path. display( ) , err) ;
200- ConfigStoreBuilder :: empty ( ) . build ( & external_plugin_store ) . unwrap ( )
233+ ConfigStoreBuilder :: empty ( ) . build ( & ExternalPluginStore :: new ( false ) ) . unwrap ( )
201234 } ) ;
202235 nested_configs. pin ( ) . insert ( dir_path. to_path_buf ( ) , config) ;
203236 }
@@ -299,7 +332,8 @@ impl Tool for ServerLinter {
299332
300333 // get the cached files before refreshing the linter, and revalidate them after
301334 let cached_files = self . get_cached_files_of_diagnostics ( ) ;
302- let new_linter = ServerLinterBuilder :: build ( root_uri, new_options_json. clone ( ) ) ;
335+ // TODO: external linter should also be passed here
336+ let new_linter = ServerLinterBuilder :: new ( None ) . build ( root_uri, new_options_json. clone ( ) ) ;
303337 let diagnostics = Some ( new_linter. revalidate_diagnostics ( cached_files) ) ;
304338
305339 let patterns = {
@@ -353,7 +387,8 @@ impl Tool for ServerLinter {
353387 options : serde_json:: Value ,
354388 ) -> ToolRestartChanges {
355389 // TODO: Check if the changed file is actually a config file (including extended paths)
356- let new_linter = ServerLinterBuilder :: build ( root_uri, options) ;
390+ // TODO: external linter should also be passed here
391+ let new_linter = ServerLinterBuilder :: new ( None ) . build ( root_uri, options) ;
357392
358393 // get the cached files before refreshing the linter, and revalidate them after
359394 let cached_files = self . get_cached_files_of_diagnostics ( ) ;
@@ -611,6 +646,7 @@ fn range_overlaps(a: Range, b: Range) -> bool {
611646mod test {
612647 use std:: path:: { Path , PathBuf } ;
613648
649+ use oxc_linter:: ExternalPluginStore ;
614650 use serde_json:: json;
615651
616652 use crate :: linter:: {
@@ -622,9 +658,12 @@ mod test {
622658 #[ test]
623659 fn test_create_nested_configs_with_disabled_nested_configs ( ) {
624660 let mut nested_ignore_patterns = Vec :: new ( ) ;
661+ let mut external_plugin_store = ExternalPluginStore :: default ( ) ;
625662 let ( configs, _) = ServerLinterBuilder :: create_nested_configs (
626663 Path :: new ( "/root/" ) ,
627664 & LintOptions { disable_nested_config : true , ..LintOptions :: default ( ) } ,
665+ None ,
666+ & mut external_plugin_store,
628667 & mut nested_ignore_patterns,
629668 ) ;
630669
@@ -634,9 +673,13 @@ mod test {
634673 #[ test]
635674 fn test_create_nested_configs ( ) {
636675 let mut nested_ignore_patterns = Vec :: new ( ) ;
676+ let mut external_plugin_store = ExternalPluginStore :: default ( ) ;
677+
637678 let ( configs, _) = ServerLinterBuilder :: create_nested_configs (
638679 & get_file_path ( "fixtures/linter/init_nested_configs" ) ,
639680 & LintOptions :: default ( ) ,
681+ None ,
682+ & mut external_plugin_store,
640683 & mut nested_ignore_patterns,
641684 ) ;
642685 let configs = configs. pin ( ) ;
0 commit comments