@@ -5,7 +5,7 @@ use std::{
55
66use itertools:: Itertools ;
77use oxc_resolver:: Resolver ;
8- use rustc_hash:: FxHashMap ;
8+ use rustc_hash:: { FxHashMap , FxHashSet } ;
99
1010use oxc_span:: { CompactStr , format_compact_str} ;
1111
@@ -21,7 +21,11 @@ use crate::{
2121 rules:: RULES ,
2222} ;
2323
24- use super :: { Config , categories:: OxlintCategories } ;
24+ use super :: {
25+ Config ,
26+ categories:: OxlintCategories ,
27+ config_store:: { ResolvedOxlintOverride , ResolvedOxlintOverrideRules , ResolvedOxlintOverrides } ,
28+ } ;
2529
2630#[ must_use = "You dropped your builder without building a Linter! Did you mean to call .build()?" ]
2731pub struct ConfigStoreBuilder {
@@ -144,35 +148,36 @@ impl ConfigStoreBuilder {
144148
145149 let ( oxlintrc, extended_paths) = resolve_oxlintrc_config ( oxlintrc) ?;
146150
151+ // Collect external plugins from both base config and overrides
152+ let mut external_plugins = FxHashSet :: default ( ) ;
153+
147154 if let Some ( base_plugins) = oxlintrc. plugins . as_ref ( ) {
148- let mut external_plugins = base_plugins. external . clone ( ) ;
149- for r#override in & oxlintrc. overrides {
150- if let Some ( override_plugins) = & r#override. plugins {
151- external_plugins. extend ( override_plugins. external . iter ( ) . cloned ( ) ) ;
152- }
155+ external_plugins. extend ( base_plugins. external . iter ( ) . cloned ( ) ) ;
156+ }
157+
158+ for r#override in & oxlintrc. overrides {
159+ if let Some ( override_plugins) = & r#override. plugins {
160+ external_plugins. extend ( override_plugins. external . iter ( ) . cloned ( ) ) ;
153161 }
162+ }
154163
155- if !external_plugins. is_empty ( ) {
156- let external_linter =
157- external_linter. ok_or ( ConfigBuilderError :: NoExternalLinterConfigured ) ?;
158-
159- let resolver = Resolver :: default ( ) ;
160-
161- #[ expect(
162- clippy:: missing_panics_doc,
163- reason = "oxlintrc.path is always a file path"
164- ) ]
165- let oxlintrc_dir = oxlintrc. path . parent ( ) . unwrap ( ) ;
166-
167- for plugin_specifier in & external_plugins {
168- Self :: load_external_plugin (
169- oxlintrc_dir,
170- plugin_specifier,
171- external_linter,
172- & resolver,
173- external_plugin_store,
174- ) ?;
175- }
164+ if !external_plugins. is_empty ( ) {
165+ let external_linter =
166+ external_linter. ok_or ( ConfigBuilderError :: NoExternalLinterConfigured ) ?;
167+
168+ let resolver = Resolver :: default ( ) ;
169+
170+ #[ expect( clippy:: missing_panics_doc, reason = "oxlintrc.path is always a file path" ) ]
171+ let oxlintrc_dir = oxlintrc. path . parent ( ) . unwrap ( ) ;
172+
173+ for plugin_specifier in & external_plugins {
174+ Self :: load_external_plugin (
175+ oxlintrc_dir,
176+ plugin_specifier,
177+ external_linter,
178+ & resolver,
179+ external_plugin_store,
180+ ) ?;
176181 }
177182 }
178183 let plugins = oxlintrc. plugins . unwrap_or_default ( ) ;
@@ -320,11 +325,19 @@ impl ConfigStoreBuilder {
320325 /// re-configured, while those that are not are added. Affects rules where `query` returns
321326 /// `true`.
322327 fn get_all_rules ( & self ) -> Vec < RuleEnum > {
323- if self . config . plugins . builtin . is_all ( ) {
324- RULES . clone ( )
328+ self . get_all_rules_for_plugins ( None )
329+ }
330+
331+ fn get_all_rules_for_plugins ( & self , override_plugins : Option < & LintPlugins > ) -> Vec < RuleEnum > {
332+ let mut builtin_plugins = if let Some ( override_plugins) = override_plugins {
333+ self . config . plugins . builtin | override_plugins. builtin
325334 } else {
326- let mut builtin_plugins = self . plugins ( ) . builtin ;
335+ self . config . plugins . builtin
336+ } ;
327337
338+ if builtin_plugins. is_all ( ) {
339+ RULES . clone ( )
340+ } else {
328341 // we need to include some jest rules when vitest is enabled, see [`VITEST_COMPATIBLE_JEST_RULES`]
329342 if builtin_plugins. contains ( BuiltinLintPlugins :: VITEST ) {
330343 builtin_plugins = builtin_plugins. union ( BuiltinLintPlugins :: JEST ) ;
@@ -359,7 +372,13 @@ impl ConfigStoreBuilder {
359372 }
360373 }
361374
362- pub fn build ( self ) -> Config {
375+ /// Builds a [`Config`] from the current state of the builder.
376+ /// # Errors
377+ /// Returns [`ConfigBuilderError::UnknownRules`] if there are rules that could not be matched.
378+ pub fn build (
379+ mut self ,
380+ external_plugin_store : & ExternalPluginStore ,
381+ ) -> Result < Config , ConfigBuilderError > {
363382 // When a plugin gets disabled before build(), rules for that plugin aren't removed until
364383 // with_filters() gets called. If the user never calls it, those now-undesired rules need
365384 // to be taken out.
@@ -370,6 +389,11 @@ impl ConfigStoreBuilder {
370389 plugins = plugins. union ( BuiltinLintPlugins :: JEST ) ;
371390 }
372391
392+ let overrides = std:: mem:: take ( & mut self . overrides ) ;
393+ let resolved_overrides = self
394+ . resolve_overrides ( overrides, external_plugin_store)
395+ . map_err ( ConfigBuilderError :: ExternalRuleLookupError ) ?;
396+
373397 let mut rules: Vec < _ > = self
374398 . rules
375399 . into_iter ( )
@@ -380,7 +404,47 @@ impl ConfigStoreBuilder {
380404 let mut external_rules: Vec < _ > = self . external_rules . into_iter ( ) . collect ( ) ;
381405 external_rules. sort_unstable_by_key ( |( r, _) | * r) ;
382406
383- Config :: new ( rules, external_rules, self . categories , self . config , self . overrides )
407+ Ok ( Config :: new ( rules, external_rules, self . categories , self . config , resolved_overrides) )
408+ }
409+
410+ fn resolve_overrides (
411+ & self ,
412+ overrides : OxlintOverrides ,
413+ external_plugin_store : & ExternalPluginStore ,
414+ ) -> Result < ResolvedOxlintOverrides , ExternalRuleLookupError > {
415+ let resolved = overrides
416+ . into_iter ( )
417+ . map ( |override_config| {
418+ let mut builtin_rules = Vec :: new ( ) ;
419+ let mut external_rules = Vec :: new ( ) ;
420+ let mut rules_map = FxHashMap :: default ( ) ;
421+ let mut external_rules_map = FxHashMap :: default ( ) ;
422+
423+ let all_rules = self . get_all_rules_for_plugins ( override_config. plugins . as_ref ( ) ) ;
424+
425+ // Resolve rules for this override
426+ override_config. rules . override_rules (
427+ & mut rules_map,
428+ & mut external_rules_map,
429+ & all_rules,
430+ external_plugin_store,
431+ ) ?;
432+
433+ // Convert to vectors
434+ builtin_rules. extend ( rules_map. into_iter ( ) ) ;
435+ external_rules. extend ( external_rules_map. into_iter ( ) ) ;
436+
437+ Ok ( ResolvedOxlintOverride {
438+ files : override_config. files ,
439+ env : override_config. env ,
440+ globals : override_config. globals ,
441+ plugins : override_config. plugins ,
442+ rules : ResolvedOxlintOverrideRules { builtin_rules, external_rules } ,
443+ } )
444+ } )
445+ . collect :: < Result < Vec < _ > , _ > > ( ) ?;
446+
447+ Ok ( ResolvedOxlintOverrides :: new ( resolved) )
384448 }
385449
386450 /// Warn for all correctness rules in the given set of plugins.
@@ -707,8 +771,11 @@ mod test {
707771 let mut desired_plugins = LintPlugins :: default ( ) ;
708772 desired_plugins. builtin . set ( BuiltinLintPlugins :: TYPESCRIPT , false ) ;
709773
710- let linter =
711- ConfigStoreBuilder :: default ( ) . with_builtin_plugins ( desired_plugins. builtin ) . build ( ) ;
774+ let external_plugin_store = ExternalPluginStore :: default ( ) ;
775+ let linter = ConfigStoreBuilder :: default ( )
776+ . with_builtin_plugins ( desired_plugins. builtin )
777+ . build ( & external_plugin_store)
778+ . unwrap ( ) ;
712779 for ( rule, _) in linter. base . rules . iter ( ) {
713780 let name = rule. name ( ) ;
714781 let plugin = rule. plugin_name ( ) ;
@@ -1129,7 +1196,8 @@ mod test {
11291196 & mut external_plugin_store,
11301197 )
11311198 . unwrap ( )
1132- . build ( )
1199+ . build ( & external_plugin_store)
1200+ . unwrap ( )
11331201 }
11341202
11351203 fn config_store_from_str ( s : & str ) -> Config {
@@ -1141,6 +1209,7 @@ mod test {
11411209 & mut external_plugin_store,
11421210 )
11431211 . unwrap ( )
1144- . build ( )
1212+ . build ( & external_plugin_store)
1213+ . unwrap ( )
11451214 }
11461215}
0 commit comments