@@ -10,12 +10,14 @@ use rustc_hash::FxHashMap;
1010use oxc_span:: { CompactStr , format_compact_str} ;
1111
1212use crate :: {
13- AllowWarnDeny , LintConfig , LintFilter , LintFilterKind , Oxlintrc , RuleCategory , RuleEnum ,
13+ AllowWarnDeny , ExternalPluginStore , LintConfig , LintFilter , LintFilterKind , Oxlintrc ,
14+ RuleCategory , RuleEnum ,
1415 config:: {
1516 ESLintRule , LintPlugins , OxlintOverrides , OxlintRules , overrides:: OxlintOverride ,
1617 plugins:: BuiltinLintPlugins ,
1718 } ,
1819 external_linter:: ExternalLinter ,
20+ external_plugin_store:: { ExternalRuleId , ExternalRuleLookupError } ,
1921 rules:: RULES ,
2022} ;
2123
@@ -24,6 +26,7 @@ use super::{Config, categories::OxlintCategories};
2426#[ must_use = "You dropped your builder without building a Linter! Did you mean to call .build()?" ]
2527pub struct ConfigStoreBuilder {
2628 pub ( super ) rules : FxHashMap < RuleEnum , AllowWarnDeny > ,
29+ pub ( super ) external_rules : FxHashMap < ExternalRuleId , AllowWarnDeny > ,
2730 config : LintConfig ,
2831 categories : OxlintCategories ,
2932 overrides : OxlintOverrides ,
@@ -47,11 +50,12 @@ impl ConfigStoreBuilder {
4750 pub fn empty ( ) -> Self {
4851 let config = LintConfig :: default ( ) ;
4952 let rules = FxHashMap :: default ( ) ;
53+ let external_rules = FxHashMap :: default ( ) ;
5054 let categories: OxlintCategories = OxlintCategories :: default ( ) ;
5155 let overrides = OxlintOverrides :: default ( ) ;
5256 let extended_paths = Vec :: new ( ) ;
5357
54- Self { rules, config, categories, overrides, extended_paths }
58+ Self { rules, external_rules , config, categories, overrides, extended_paths }
5559 }
5660
5761 /// Warn on all rules in all plugins and categories, including those in `nursery`.
@@ -64,8 +68,9 @@ impl ConfigStoreBuilder {
6468 let overrides = OxlintOverrides :: default ( ) ;
6569 let categories: OxlintCategories = OxlintCategories :: default ( ) ;
6670 let rules = RULES . iter ( ) . map ( |rule| ( rule. clone ( ) , AllowWarnDeny :: Warn ) ) . collect ( ) ;
71+ let external_rules = FxHashMap :: default ( ) ;
6772 let extended_paths = Vec :: new ( ) ;
68- Self { rules, config, categories, overrides, extended_paths }
73+ Self { rules, external_rules , config, categories, overrides, extended_paths }
6974 }
7075
7176 /// Create a [`ConfigStoreBuilder`] from a loaded or manually built [`Oxlintrc`].
@@ -90,6 +95,7 @@ impl ConfigStoreBuilder {
9095 start_empty : bool ,
9196 oxlintrc : Oxlintrc ,
9297 external_linter : Option < & ExternalLinter > ,
98+ external_plugin_store : & mut ExternalPluginStore ,
9399 ) -> Result < Self , ConfigBuilderError > {
94100 // TODO: this can be cached to avoid re-computing the same oxlintrc
95101 fn resolve_oxlintrc_config (
@@ -146,13 +152,13 @@ impl ConfigStoreBuilder {
146152 let resolver = oxc_resolver:: Resolver :: new ( ResolveOptions :: default ( ) ) ;
147153 #[ expect( clippy:: missing_panics_doc, reason = "infallible" ) ]
148154 let oxlintrc_dir_path = oxlintrc. path . parent ( ) . unwrap ( ) ;
149-
150155 for plugin_specifier in & plugins. external {
151156 Self :: load_external_plugin (
152157 oxlintrc_dir_path,
153158 plugin_specifier,
154159 external_linter,
155160 & resolver,
161+ external_plugin_store,
156162 ) ?;
157163 }
158164 }
@@ -179,8 +185,14 @@ impl ConfigStoreBuilder {
179185 path : Some ( oxlintrc. path ) ,
180186 } ;
181187
182- let mut builder =
183- Self { rules, config, categories, overrides : oxlintrc. overrides , extended_paths } ;
188+ let mut builder = Self {
189+ rules,
190+ external_rules : FxHashMap :: default ( ) ,
191+ config,
192+ categories,
193+ overrides : oxlintrc. overrides ,
194+ extended_paths,
195+ } ;
184196
185197 for filter in oxlintrc. categories . filters ( ) {
186198 builder = builder. with_filter ( & filter) ;
@@ -189,7 +201,15 @@ impl ConfigStoreBuilder {
189201 {
190202 let all_rules = builder. get_all_rules ( ) ;
191203
192- oxlintrc. rules . override_rules ( & mut builder. rules , & all_rules) ;
204+ oxlintrc
205+ . rules
206+ . override_rules (
207+ & mut builder. rules ,
208+ & mut builder. external_rules ,
209+ & all_rules,
210+ external_plugin_store,
211+ )
212+ . map_err ( ConfigBuilderError :: ExternalRuleLookupError ) ?;
193213 }
194214
195215 Ok ( builder)
@@ -346,7 +366,12 @@ impl ConfigStoreBuilder {
346366 . filter ( |( r, _) | plugins. contains ( r. plugin_name ( ) . into ( ) ) )
347367 . collect ( ) ;
348368 rules. sort_unstable_by_key ( |( r, _) | r. id ( ) ) ;
349- Config :: new ( rules, self . categories , self . config , self . overrides )
369+
370+ let mut external_rules: Vec < _ > = self . external_rules . into_iter ( ) . collect ( ) ;
371+ external_rules. sort_unstable_by_key ( |( r, _) | * r) ;
372+
373+ // TODO: fixme
374+ Config :: new ( rules, external_rules, self . categories , self . config , self . overrides )
350375 }
351376
352377 /// Warn for all correctness rules in the given set of plugins.
@@ -399,6 +424,7 @@ impl ConfigStoreBuilder {
399424 _plugin_specifier : & str ,
400425 _external_linter : & ExternalLinter ,
401426 _resolver : & Resolver ,
427+ _external_plugin_store : & mut ExternalPluginStore ,
402428 ) -> Result < ( ) , ConfigBuilderError > {
403429 unreachable ! ( )
404430 }
@@ -409,6 +435,7 @@ impl ConfigStoreBuilder {
409435 plugin_specifier : & str ,
410436 external_linter : & ExternalLinter ,
411437 resolver : & Resolver ,
438+ external_plugin_store : & mut ExternalPluginStore ,
412439 ) -> Result < ( ) , ConfigBuilderError > {
413440 use crate :: PluginLoadResult ;
414441
@@ -421,16 +448,27 @@ impl ConfigStoreBuilder {
421448 // TODO: We should support paths which are not valid UTF-8. How?
422449 let plugin_path = resolved. full_path ( ) . to_str ( ) . unwrap ( ) . to_string ( ) ;
423450
424- let result = tokio:: task:: block_in_place ( move || {
425- tokio:: runtime:: Handle :: current ( ) . block_on ( ( external_linter. load_plugin ) ( plugin_path) )
426- } )
427- . map_err ( |e| ConfigBuilderError :: PluginLoadFailed {
428- plugin_specifier : plugin_specifier. to_string ( ) ,
429- error : e. to_string ( ) ,
430- } ) ?;
451+ if external_plugin_store. is_plugin_registered ( & plugin_path) {
452+ return Ok ( ( ) ) ;
453+ }
454+
455+ let result = {
456+ let plugin_path = plugin_path. clone ( ) ;
457+ tokio:: task:: block_in_place ( move || {
458+ tokio:: runtime:: Handle :: current ( )
459+ . block_on ( ( external_linter. load_plugin ) ( plugin_path) )
460+ } )
461+ . map_err ( |e| ConfigBuilderError :: PluginLoadFailed {
462+ plugin_specifier : plugin_specifier. to_string ( ) ,
463+ error : e. to_string ( ) ,
464+ } )
465+ } ?;
431466
432467 match result {
433- PluginLoadResult :: Success => Ok ( ( ) ) ,
468+ PluginLoadResult :: Success { name, offset, rules } => {
469+ external_plugin_store. register_plugin ( plugin_path, name, offset, rules) ;
470+ Ok ( ( ) )
471+ }
434472 PluginLoadResult :: Failure ( e) => Err ( ConfigBuilderError :: PluginLoadFailed {
435473 plugin_specifier : plugin_specifier. to_string ( ) ,
436474 error : e,
@@ -452,7 +490,8 @@ impl TryFrom<Oxlintrc> for ConfigStoreBuilder {
452490
453491 #[ inline]
454492 fn try_from ( oxlintrc : Oxlintrc ) -> Result < Self , Self :: Error > {
455- Self :: from_oxlintrc ( false , oxlintrc, None )
493+ let mut external_plugin_store = ExternalPluginStore :: default ( ) ;
494+ Self :: from_oxlintrc ( false , oxlintrc, None , & mut external_plugin_store)
456495 }
457496}
458497
@@ -481,6 +520,7 @@ pub enum ConfigBuilderError {
481520 plugin_specifier : String ,
482521 error : String ,
483522 } ,
523+ ExternalRuleLookupError ( ExternalRuleLookupError ) ,
484524 NoExternalLinterConfigured ,
485525}
486526
@@ -509,6 +549,7 @@ impl Display for ConfigBuilderError {
509549 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." ) ?;
510550 Ok ( ( ) )
511551 }
552+ ConfigBuilderError :: ExternalRuleLookupError ( e) => std:: fmt:: Display :: fmt ( & e, f) ,
512553 }
513554 }
514555}
@@ -743,7 +784,11 @@ mod test {
743784 "# ,
744785 )
745786 . unwrap ( ) ;
746- let builder = ConfigStoreBuilder :: from_oxlintrc ( false , oxlintrc, None ) . unwrap ( ) ;
787+ let builder = {
788+ let mut external_plugin_store = ExternalPluginStore :: default ( ) ;
789+ ConfigStoreBuilder :: from_oxlintrc ( false , oxlintrc, None , & mut external_plugin_store)
790+ . unwrap ( )
791+ } ;
747792 for ( rule, severity) in & builder. rules {
748793 let name = rule. name ( ) ;
749794 let plugin = rule. plugin_name ( ) ;
@@ -912,14 +957,18 @@ mod test {
912957
913958 #[ test]
914959 fn test_extends_invalid ( ) {
915- let invalid_config = ConfigStoreBuilder :: from_oxlintrc (
916- true ,
917- Oxlintrc :: from_file ( & PathBuf :: from (
918- "fixtures/extends_config/extends_invalid_config.json" ,
919- ) )
920- . unwrap ( ) ,
921- None ,
922- ) ;
960+ let invalid_config = {
961+ let mut external_plugin_store = ExternalPluginStore :: default ( ) ;
962+ ConfigStoreBuilder :: from_oxlintrc (
963+ true ,
964+ Oxlintrc :: from_file ( & PathBuf :: from (
965+ "fixtures/extends_config/extends_invalid_config.json" ,
966+ ) )
967+ . unwrap ( ) ,
968+ None ,
969+ & mut external_plugin_store,
970+ )
971+ } ;
923972 let err = invalid_config. unwrap_err ( ) ;
924973 assert ! ( matches!( err, ConfigBuilderError :: InvalidConfigFile { .. } ) ) ;
925974 if let ConfigBuilderError :: InvalidConfigFile { file, reason } = err {
@@ -1072,18 +1121,26 @@ mod test {
10721121 }
10731122
10741123 fn config_store_from_path ( path : & str ) -> Config {
1124+ let mut external_plugin_store = ExternalPluginStore :: default ( ) ;
10751125 ConfigStoreBuilder :: from_oxlintrc (
10761126 true ,
10771127 Oxlintrc :: from_file ( & PathBuf :: from ( path) ) . unwrap ( ) ,
10781128 None ,
1129+ & mut external_plugin_store,
10791130 )
10801131 . unwrap ( )
10811132 . build ( )
10821133 }
10831134
10841135 fn config_store_from_str ( s : & str ) -> Config {
1085- ConfigStoreBuilder :: from_oxlintrc ( true , serde_json:: from_str ( s) . unwrap ( ) , None )
1086- . unwrap ( )
1087- . build ( )
1136+ let mut external_plugin_store = ExternalPluginStore :: default ( ) ;
1137+ ConfigStoreBuilder :: from_oxlintrc (
1138+ true ,
1139+ serde_json:: from_str ( s) . unwrap ( ) ,
1140+ None ,
1141+ & mut external_plugin_store,
1142+ )
1143+ . unwrap ( )
1144+ . build ( )
10881145 }
10891146}
0 commit comments