11use std:: {
22 fmt:: { self , Debug , Display } ,
3- path:: PathBuf ,
3+ path:: { Path , PathBuf } ,
44} ;
55
66use itertools:: Itertools ;
7+ use oxc_resolver:: { ResolveOptions , Resolver } ;
78use rustc_hash:: FxHashMap ;
89
910use oxc_span:: { CompactStr , format_compact_str} ;
@@ -88,7 +89,7 @@ impl ConfigStoreBuilder {
8889 pub fn from_oxlintrc (
8990 start_empty : bool ,
9091 oxlintrc : Oxlintrc ,
91- _external_linter : Option < & ExternalLinter > ,
92+ external_linter : Option < & ExternalLinter > ,
9293 ) -> Result < Self , ConfigBuilderError > {
9394 // TODO: this can be cached to avoid re-computing the same oxlintrc
9495 fn resolve_oxlintrc_config (
@@ -138,9 +139,14 @@ impl ConfigStoreBuilder {
138139 let ( oxlintrc, extended_paths) = resolve_oxlintrc_config ( oxlintrc) ?;
139140
140141 if let Some ( plugins) = oxlintrc. plugins . as_ref ( ) {
141- # [ expect ( clippy :: never_loop ) ]
142+ let resolver = oxc_resolver :: Resolver :: new ( ResolveOptions :: default ( ) ) ;
142143 for plugin_name in & plugins. external {
143- return Err ( ConfigBuilderError :: UnknownPlugin ( plugin_name. clone ( ) ) ) ;
144+ Self :: load_external_plugin (
145+ & oxlintrc. path ,
146+ plugin_name,
147+ external_linter,
148+ & resolver,
149+ ) ?;
144150 }
145151 }
146152 let plugins = oxlintrc. plugins . unwrap_or_default ( ) ;
@@ -378,6 +384,53 @@ impl ConfigStoreBuilder {
378384 oxlintrc. rules = OxlintRules :: new ( new_rules) ;
379385 serde_json:: to_string_pretty ( & oxlintrc) . unwrap ( )
380386 }
387+
388+ #[ cfg( not( feature = "oxlint2" ) ) ]
389+ fn load_external_plugin (
390+ _oxlintrc_path : & Path ,
391+ _plugin_name : & str ,
392+ _external_linter : Option < & ExternalLinter > ,
393+ _resolver : & Resolver ,
394+ ) -> Result < ( ) , ConfigBuilderError > {
395+ Err ( ConfigBuilderError :: NoExternalLinterConfigured )
396+ }
397+
398+ #[ cfg( feature = "oxlint2" ) ]
399+ fn load_external_plugin (
400+ oxlintrc_path : & Path ,
401+ plugin_name : & str ,
402+ external_linter : Option < & ExternalLinter > ,
403+ resolver : & Resolver ,
404+ ) -> Result < ( ) , ConfigBuilderError > {
405+ use crate :: PluginLoadResult ;
406+ let Some ( linter) = external_linter else {
407+ return Err ( ConfigBuilderError :: NoExternalLinterConfigured ) ;
408+ } ;
409+ let resolved =
410+ resolver. resolve ( oxlintrc_path. parent ( ) . unwrap ( ) , plugin_name) . map_err ( |e| {
411+ ConfigBuilderError :: PluginLoadFailed {
412+ plugin_name : plugin_name. into ( ) ,
413+ error : e. to_string ( ) ,
414+ }
415+ } ) ?;
416+
417+ let result = tokio:: task:: block_in_place ( move || {
418+ tokio:: runtime:: Handle :: current ( )
419+ . block_on ( ( linter. load_plugin ) ( resolved. full_path ( ) . to_str ( ) . unwrap ( ) . to_string ( ) ) )
420+ } )
421+ . map_err ( |e| ConfigBuilderError :: PluginLoadFailed {
422+ plugin_name : plugin_name. into ( ) ,
423+ error : e. to_string ( ) ,
424+ } ) ?;
425+
426+ match result {
427+ PluginLoadResult :: Success => Ok ( ( ) ) ,
428+ PluginLoadResult :: Failure ( e) => Err ( ConfigBuilderError :: PluginLoadFailed {
429+ plugin_name : plugin_name. into ( ) ,
430+ error : e,
431+ } ) ,
432+ }
433+ }
381434}
382435
383436fn get_name ( plugin_name : & str , rule_name : & str ) -> CompactStr {
@@ -418,7 +471,11 @@ pub enum ConfigBuilderError {
418471 file : String ,
419472 reason : String ,
420473 } ,
421- UnknownPlugin ( String ) ,
474+ PluginLoadFailed {
475+ plugin_name : String ,
476+ error : String ,
477+ } ,
478+ NoExternalLinterConfigured ,
422479}
423480
424481impl Display for ConfigBuilderError {
@@ -438,8 +495,13 @@ impl Display for ConfigBuilderError {
438495 ConfigBuilderError :: InvalidConfigFile { file, reason } => {
439496 write ! ( f, "invalid config file {file}: {reason}" )
440497 }
441- ConfigBuilderError :: UnknownPlugin ( plugin_name) => {
442- write ! ( f, "unknown plugin: {plugin_name}" )
498+ ConfigBuilderError :: PluginLoadFailed { plugin_name, error } => {
499+ write ! ( f, "Failed to load external plugin: {plugin_name}\n {error}" ) ?;
500+ Ok ( ( ) )
501+ }
502+ ConfigBuilderError :: NoExternalLinterConfigured => {
503+ f. write_str ( "Failed to load external plugin because no external linter was configured. This means the Oxlint binary was executed directly rather than via napi bindings." ) ?;
504+ Ok ( ( ) )
443505 }
444506 }
445507 }
0 commit comments