refactor(linter): refactor LintBuilder to prep for nested configs (#8034)

More simplification/preparations for nested configurations:

1. renames `LinterBuilder` to `ConfigStoreBuilder`
2. moves the `options` logic out of `LintBuilder`
3. make `ConfigStoreBuilder::build()` return a result (currently always returns OK, but it will return errors when nested config is implemented

The next steps to implement nested config which i hope to do in the next week are:
1. refactor the `from_oxlintrc` to accept a file path
2. introduce a new method on `ConfigStoreBuilder` (name TBC) that walks all child directories, collecting `.oxlintrc` files. these will be put into `ConfigStore` as a hash map of path > config.
This commit is contained in:
camc314 2025-01-05 04:08:26 +00:00
parent 65f46f5409
commit 3c534aeb5a
11 changed files with 122 additions and 124 deletions

View file

@ -9,8 +9,8 @@ use ignore::gitignore::Gitignore;
use oxc_diagnostics::{DiagnosticService, GraphicalReportHandler}; use oxc_diagnostics::{DiagnosticService, GraphicalReportHandler};
use oxc_linter::{ use oxc_linter::{
loader::LINT_PARTIAL_LOADER_EXT, AllowWarnDeny, InvalidFilterKind, LintFilter, LintService, loader::LINT_PARTIAL_LOADER_EXT, AllowWarnDeny, ConfigStoreBuilder, InvalidFilterKind,
LintServiceOptions, Linter, LinterBuilder, Oxlintrc, LintFilter, LintOptions, LintService, LintServiceOptions, Linter, Oxlintrc,
}; };
use oxc_span::VALID_EXTENSIONS; use oxc_span::VALID_EXTENSIONS;
@ -128,20 +128,32 @@ impl Runner for LintRunner {
let oxlintrc_for_print = let oxlintrc_for_print =
if misc_options.print_config { Some(oxlintrc.clone()) } else { None }; if misc_options.print_config { Some(oxlintrc.clone()) } else { None };
let builder = LinterBuilder::from_oxlintrc(false, oxlintrc) let config_builder =
.with_filters(filter) ConfigStoreBuilder::from_oxlintrc(false, oxlintrc).with_filters(filter);
.with_fix(fix_options.fix_kind());
if let Some(basic_config_file) = oxlintrc_for_print { if let Some(basic_config_file) = oxlintrc_for_print {
return CliRunResult::PrintConfigResult { return CliRunResult::PrintConfigResult {
config_file: builder.resolve_final_config_file(basic_config_file), config_file: config_builder.resolve_final_config_file(basic_config_file),
}; };
} }
let mut options = LintServiceOptions::new(self.cwd, paths) let mut options = LintServiceOptions::new(self.cwd, paths)
.with_cross_module(builder.plugins().has_import()); .with_cross_module(config_builder.plugins().has_import());
let linter = builder.build(); let lint_config = match config_builder.build() {
Ok(config) => config,
Err(diagnostic) => {
let handler = GraphicalReportHandler::new();
let mut err = String::new();
handler.render_report(&mut err, &diagnostic).unwrap();
return CliRunResult::InvalidOptions {
message: format!("Failed to parse configuration file.\n{err}"),
};
}
};
let linter =
Linter::new(LintOptions::default(), lint_config).with_fix(fix_options.fix_kind());
let tsconfig = basic_options.tsconfig; let tsconfig = basic_options.tsconfig;
if let Some(path) = tsconfig.as_ref() { if let Some(path) = tsconfig.as_ref() {

View file

@ -2,7 +2,7 @@ use std::sync::Arc;
use tower_lsp::lsp_types::Url; use tower_lsp::lsp_types::Url;
use oxc_linter::{FixKind, Linter}; use oxc_linter::{ConfigStoreBuilder, FixKind, LintOptions, Linter};
use crate::linter::error_with_position::DiagnosticReport; use crate::linter::error_with_position::DiagnosticReport;
use crate::linter::isolated_lint_handler::IsolatedLintHandler; use crate::linter::isolated_lint_handler::IsolatedLintHandler;
@ -13,7 +13,9 @@ pub struct ServerLinter {
impl ServerLinter { impl ServerLinter {
pub fn new() -> Self { pub fn new() -> Self {
let linter = Linter::default().with_fix(FixKind::SafeFix); let config_store =
ConfigStoreBuilder::default().build().expect("Failed to build config store");
let linter = Linter::new(LintOptions::default(), config_store).with_fix(FixKind::SafeFix);
Self { linter: Arc::new(linter) } Self { linter: Arc::new(linter) }
} }

View file

@ -20,7 +20,7 @@ use tower_lsp::{
Client, LanguageServer, LspService, Server, Client, LanguageServer, LspService, Server,
}; };
use oxc_linter::{FixKind, LinterBuilder, Oxlintrc}; use oxc_linter::{ConfigStoreBuilder, FixKind, LintOptions, Linter, Oxlintrc};
use crate::capabilities::{Capabilities, CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC}; use crate::capabilities::{Capabilities, CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC};
use crate::linter::error_with_position::DiagnosticReport; use crate::linter::error_with_position::DiagnosticReport;
@ -488,10 +488,11 @@ impl Backend {
let mut linter = self.server_linter.write().await; let mut linter = self.server_linter.write().await;
let config = Oxlintrc::from_file(&config_path) let config = Oxlintrc::from_file(&config_path)
.expect("should have initialized linter with new options"); .expect("should have initialized linter with new options");
let config_store = ConfigStoreBuilder::from_oxlintrc(true, config.clone())
.build()
.expect("failed to build config");
*linter = ServerLinter::new_with_linter( *linter = ServerLinter::new_with_linter(
LinterBuilder::from_oxlintrc(true, config.clone()) Linter::new(LintOptions::default(), config_store).with_fix(FixKind::SafeFix),
.with_fix(FixKind::SafeFix)
.build(),
); );
return Some(config); return Some(config);
} }

View file

@ -3,44 +3,43 @@ use std::{
fmt, fmt,
}; };
use oxc_diagnostics::OxcDiagnostic;
use oxc_span::CompactStr; use oxc_span::CompactStr;
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use crate::{ use crate::{
config::{ConfigStore, ESLintRule, LintPlugins, OxlintOverrides, OxlintRules}, config::{ConfigStore, ESLintRule, LintPlugins, OxlintOverrides, OxlintRules},
rules::RULES, rules::RULES,
AllowWarnDeny, FixKind, FrameworkFlags, LintConfig, LintFilter, LintFilterKind, LintOptions, AllowWarnDeny, LintConfig, LintFilter, LintFilterKind, Oxlintrc, RuleCategory, RuleEnum,
Linter, Oxlintrc, RuleCategory, RuleEnum, RuleWithSeverity, RuleWithSeverity,
}; };
#[must_use = "You dropped your builder without building a Linter! Did you mean to call .build()?"] #[must_use = "You dropped your builder without building a Linter! Did you mean to call .build()?"]
pub struct LinterBuilder { pub struct ConfigStoreBuilder {
pub(super) rules: FxHashSet<RuleWithSeverity>, pub(super) rules: FxHashSet<RuleWithSeverity>,
options: LintOptions,
config: LintConfig, config: LintConfig,
overrides: OxlintOverrides, overrides: OxlintOverrides,
cache: RulesCache, cache: RulesCache,
} }
impl Default for LinterBuilder { impl Default for ConfigStoreBuilder {
fn default() -> Self { fn default() -> Self {
Self { rules: Self::warn_correctness(LintPlugins::default()), ..Self::empty() } Self { rules: Self::warn_correctness(LintPlugins::default()), ..Self::empty() }
} }
} }
impl LinterBuilder { impl ConfigStoreBuilder {
/// Create a [`LinterBuilder`] with default plugins enabled and no /// Create a [`ConfigStoreBuilder`] with default plugins enabled and no
/// configured rules. /// configured rules.
/// ///
/// You can think of this as `oxlint -A all`. /// You can think of this as `oxlint -A all`.
pub fn empty() -> Self { pub fn empty() -> Self {
let options = LintOptions::default();
let config = LintConfig::default(); let config = LintConfig::default();
let rules = FxHashSet::default(); let rules = FxHashSet::default();
let overrides = OxlintOverrides::default(); let overrides = OxlintOverrides::default();
let cache = RulesCache::new(config.plugins); let cache = RulesCache::new(config.plugins);
Self { rules, options, config, overrides, cache } Self { rules, config, overrides, cache }
} }
/// Warn on all rules in all plugins and categories, including those in `nursery`. /// Warn on all rules in all plugins and categories, including those in `nursery`.
@ -48,7 +47,6 @@ impl LinterBuilder {
/// ///
/// You can think of this as `oxlint -W all -W nursery`. /// You can think of this as `oxlint -W all -W nursery`.
pub fn all() -> Self { pub fn all() -> Self {
let options = LintOptions::default();
let config = LintConfig { plugins: LintPlugins::all(), ..LintConfig::default() }; let config = LintConfig { plugins: LintPlugins::all(), ..LintConfig::default() };
let overrides = OxlintOverrides::default(); let overrides = OxlintOverrides::default();
let cache = RulesCache::new(config.plugins); let cache = RulesCache::new(config.plugins);
@ -57,31 +55,30 @@ impl LinterBuilder {
.iter() .iter()
.map(|rule| RuleWithSeverity { rule: rule.clone(), severity: AllowWarnDeny::Warn }) .map(|rule| RuleWithSeverity { rule: rule.clone(), severity: AllowWarnDeny::Warn })
.collect(), .collect(),
options,
config, config,
overrides, overrides,
cache, cache,
} }
} }
/// Create a [`LinterBuilder`] from a loaded or manually built [`Oxlintrc`]. /// Create a [`ConfigStoreBuilder`] from a loaded or manually built [`Oxlintrc`].
/// `start_empty` will configure the builder to contain only the /// `start_empty` will configure the builder to contain only the
/// configuration settings from the config. When this is `false`, the config /// configuration settings from the config. When this is `false`, the config
/// will be applied on top of a default [`Oxlintrc`]. /// will be applied on top of a default [`Oxlintrc`].
/// ///
/// # Example /// # Example
/// Here's how to create a [`Linter`] from a `.oxlintrc.json` file. /// Here's how to create a [`ConfigStore`] from a `.oxlintrc.json` file.
/// ``` /// ```
/// use oxc_linter::{LinterBuilder, Oxlintrc}; /// use oxc_linter::{ConfigBuilder, Oxlintrc};
/// let oxlintrc = Oxlintrc::from_file("path/to/.oxlintrc.json").unwrap(); /// let oxlintrc = Oxlintrc::from_file("path/to/.oxlintrc.json").unwrap();
/// let linter = LinterBuilder::from_oxlintrc(true, oxlintrc).build(); /// let config_store = ConfigStoreBuilder::from_oxlintrc(true, oxlintrc).build();
/// // you can use `From` as a shorthand for `from_oxlintrc(false, oxlintrc)` /// // you can use `From` as a shorthand for `from_oxlintrc(false, oxlintrc)`
/// let linter = LinterBuilder::from(oxlintrc).build(); /// let config_store = ConfigStoreBuilder::from(oxlintrc).build();
/// ``` /// ```
/// ///
/// # Errors /// # Errors
/// ///
/// Will return a [`LinterBuilderError::UnknownRules`] if there are unknown rules in the /// Will return a [`ConfigBuilderError::UnknownRules`] if there are unknown rules in the
/// config. This can happen if the plugin for a rule is not enabled, or the rule name doesn't /// config. This can happen if the plugin for a rule is not enabled, or the rule name doesn't
/// match any recognized rules. /// match any recognized rules.
pub fn from_oxlintrc(start_empty: bool, oxlintrc: Oxlintrc) -> Self { pub fn from_oxlintrc(start_empty: bool, oxlintrc: Oxlintrc) -> Self {
@ -99,11 +96,10 @@ impl LinterBuilder {
} = oxlintrc; } = oxlintrc;
let config = LintConfig { plugins, settings, env, globals, path: Some(path) }; let config = LintConfig { plugins, settings, env, globals, path: Some(path) };
let options = LintOptions::default();
let rules = let rules =
if start_empty { FxHashSet::default() } else { Self::warn_correctness(plugins) }; if start_empty { FxHashSet::default() } else { Self::warn_correctness(plugins) };
let cache = RulesCache::new(config.plugins); let cache = RulesCache::new(config.plugins);
let mut builder = Self { rules, options, config, overrides, cache }; let mut builder = Self { rules, config, overrides, cache };
if !categories.is_empty() { if !categories.is_empty() {
builder = builder.with_filters(categories.filters()); builder = builder.with_filters(categories.filters());
@ -117,24 +113,6 @@ impl LinterBuilder {
builder builder
} }
#[inline]
pub fn with_framework_hints(mut self, flags: FrameworkFlags) -> Self {
self.options.framework_hints = flags;
self
}
#[inline]
pub fn and_framework_hints(mut self, flags: FrameworkFlags) -> Self {
self.options.framework_hints |= flags;
self
}
#[inline]
pub fn with_fix(mut self, fix: FixKind) -> Self {
self.options.fix = fix;
self
}
/// Configure what linter plugins are enabled. /// Configure what linter plugins are enabled.
/// ///
/// Turning on a plugin will not automatically enable any of its rules. You must do this /// Turning on a plugin will not automatically enable any of its rules. You must do this
@ -146,8 +124,8 @@ impl LinterBuilder {
/// This method sets what plugins are enabled and disabled, overwriting whatever existing /// This method sets what plugins are enabled and disabled, overwriting whatever existing
/// config is set. If you are looking to add/remove plugins, use [`and_plugins`] /// config is set. If you are looking to add/remove plugins, use [`and_plugins`]
/// ///
/// [`with_filters`]: LinterBuilder::with_filters /// [`with_filters`]: ConfigStoreBuilder::with_filters
/// [`and_plugins`]: LinterBuilder::and_plugins /// [`and_plugins`]: ConfigStoreBuilder::and_plugins
#[inline] #[inline]
pub fn with_plugins(mut self, plugins: LintPlugins) -> Self { pub fn with_plugins(mut self, plugins: LintPlugins) -> Self {
self.config.plugins = plugins; self.config.plugins = plugins;
@ -157,7 +135,7 @@ impl LinterBuilder {
/// Enable or disable a set of plugins, leaving unrelated plugins alone. /// Enable or disable a set of plugins, leaving unrelated plugins alone.
/// ///
/// See [`LinterBuilder::with_plugins`] for details on how plugin configuration affects your /// See [`ConfigStoreBuilder::with_plugins`] for details on how plugin configuration affects your
/// rules. /// rules.
#[inline] #[inline]
pub fn and_plugins(mut self, plugins: LintPlugins, enabled: bool) -> Self { pub fn and_plugins(mut self, plugins: LintPlugins, enabled: bool) -> Self {
@ -241,8 +219,8 @@ impl LinterBuilder {
} }
} }
#[must_use] /// # Errors
pub fn build(self) -> Linter { pub fn build(self) -> Result<ConfigStore, OxcDiagnostic> {
// When a plugin gets disabled before build(), rules for that plugin aren't removed until // When a plugin gets disabled before build(), rules for that plugin aren't removed until
// with_filters() gets called. If the user never calls it, those now-undesired rules need // with_filters() gets called. If the user never calls it, those now-undesired rules need
// to be taken out. // to be taken out.
@ -253,8 +231,7 @@ impl LinterBuilder {
self.rules.into_iter().collect::<Vec<_>>() self.rules.into_iter().collect::<Vec<_>>()
}; };
rules.sort_unstable_by_key(|r| r.id()); rules.sort_unstable_by_key(|r| r.id());
let config = ConfigStore::new(rules, self.config, self.overrides); Ok(ConfigStore::new(rules, self.config, self.overrides))
Linter::new(self.options, config)
} }
/// Warn for all correctness rules in the given set of plugins. /// Warn for all correctness rules in the given set of plugins.
@ -309,8 +286,8 @@ fn get_name(plugin_name: &str, rule_name: &str) -> CompactStr {
} }
} }
impl TryFrom<Oxlintrc> for LinterBuilder { impl TryFrom<Oxlintrc> for ConfigStoreBuilder {
type Error = LinterBuilderError; type Error = ConfigBuilderError;
#[inline] #[inline]
fn try_from(oxlintrc: Oxlintrc) -> Result<Self, Self::Error> { fn try_from(oxlintrc: Oxlintrc) -> Result<Self, Self::Error> {
@ -318,27 +295,26 @@ impl TryFrom<Oxlintrc> for LinterBuilder {
} }
} }
impl fmt::Debug for LinterBuilder { impl fmt::Debug for ConfigStoreBuilder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("LinterBuilder") f.debug_struct("ConfigStoreBuilder")
.field("rules", &self.rules) .field("rules", &self.rules)
.field("options", &self.options)
.field("config", &self.config) .field("config", &self.config)
.finish_non_exhaustive() .finish_non_exhaustive()
} }
} }
/// An error that can occur while building a [`Linter`] from an [`Oxlintrc`]. /// An error that can occur while building a [`ConfigStore`] from an [`Oxlintrc`].
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum LinterBuilderError { pub enum ConfigBuilderError {
/// There were unknown rules that could not be matched to any known plugins/rules. /// There were unknown rules that could not be matched to any known plugins/rules.
UnknownRules { rules: Vec<ESLintRule> }, UnknownRules { rules: Vec<ESLintRule> },
} }
impl std::fmt::Display for LinterBuilderError { impl std::fmt::Display for ConfigBuilderError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
LinterBuilderError::UnknownRules { rules } => { ConfigBuilderError::UnknownRules { rules } => {
write!(f, "unknown rules: ")?; write!(f, "unknown rules: ")?;
for rule in rules { for rule in rules {
write!(f, "{}", rule.full_name())?; write!(f, "{}", rule.full_name())?;
@ -349,7 +325,7 @@ impl std::fmt::Display for LinterBuilderError {
} }
} }
impl std::error::Error for LinterBuilderError {} impl std::error::Error for ConfigBuilderError {}
struct RulesCache { struct RulesCache {
all_rules: RefCell<Option<Vec<RuleEnum>>>, all_rules: RefCell<Option<Vec<RuleEnum>>>,
@ -378,9 +354,9 @@ impl RulesCache {
// initialize() is called), all_rules will be some if and only if last_fresh_plugins == // initialize() is called), all_rules will be some if and only if last_fresh_plugins ==
// plugins. Right before creation, (::new()) and before initialize() is called, these two // plugins. Right before creation, (::new()) and before initialize() is called, these two
// fields will be equal _but all_rules will be none_. This is OK for this function, but is // fields will be equal _but all_rules will be none_. This is OK for this function, but is
// a possible future foot-gun. LinterBuilder uses this to re-build its rules list in // a possible future foot-gun. ConfigBuilder uses this to re-build its rules list in
// ::build(). If cache is created but never made stale (by changing plugins), // ::build(). If cache is created but never made stale (by changing plugins),
// LinterBuilder's rule list won't need updating anyways, meaning its sound for this to // ConfigBuilder's rule list won't need updating anyways, meaning its sound for this to
// return `false`. // return `false`.
self.last_fresh_plugins != self.plugins self.last_fresh_plugins != self.plugins
} }
@ -446,7 +422,7 @@ mod test {
#[test] #[test]
fn test_builder_default() { fn test_builder_default() {
let builder = LinterBuilder::default(); let builder = ConfigStoreBuilder::default();
assert_eq!(builder.plugins(), LintPlugins::default()); assert_eq!(builder.plugins(), LintPlugins::default());
// populated with all correctness-level ESLint rules at a "warn" severity // populated with all correctness-level ESLint rules at a "warn" severity
@ -465,14 +441,14 @@ mod test {
#[test] #[test]
fn test_builder_empty() { fn test_builder_empty() {
let builder = LinterBuilder::empty(); let builder = ConfigStoreBuilder::empty();
assert_eq!(builder.plugins(), LintPlugins::default()); assert_eq!(builder.plugins(), LintPlugins::default());
assert!(builder.rules.is_empty()); assert!(builder.rules.is_empty());
} }
#[test] #[test]
fn test_filter_deny_on_default() { fn test_filter_deny_on_default() {
let builder = LinterBuilder::default(); let builder = ConfigStoreBuilder::default();
let initial_rule_count = builder.rules.len(); let initial_rule_count = builder.rules.len();
let builder = builder.with_filters([LintFilter::deny(RuleCategory::Correctness)]); let builder = builder.with_filters([LintFilter::deny(RuleCategory::Correctness)]);
@ -500,7 +476,7 @@ mod test {
#[test] #[test]
fn test_filter_deny_single_enabled_rule_on_default() { fn test_filter_deny_single_enabled_rule_on_default() {
for filter_string in ["no-const-assign", "eslint/no-const-assign"] { for filter_string in ["no-const-assign", "eslint/no-const-assign"] {
let builder = LinterBuilder::default(); let builder = ConfigStoreBuilder::default();
let initial_rule_count = builder.rules.len(); let initial_rule_count = builder.rules.len();
let builder = let builder =
@ -526,7 +502,7 @@ mod test {
fn test_filter_warn_single_disabled_rule_on_default() { fn test_filter_warn_single_disabled_rule_on_default() {
for filter_string in ["no-console", "eslint/no-console"] { for filter_string in ["no-console", "eslint/no-console"] {
let filter = LintFilter::new(AllowWarnDeny::Warn, filter_string).unwrap(); let filter = LintFilter::new(AllowWarnDeny::Warn, filter_string).unwrap();
let builder = LinterBuilder::default(); let builder = ConfigStoreBuilder::default();
// sanity check: not already turned on // sanity check: not already turned on
assert!(!builder.rules.iter().any(|r| r.name() == "no-console")); assert!(!builder.rules.iter().any(|r| r.name() == "no-console"));
let builder = builder.with_filter(filter); let builder = builder.with_filter(filter);
@ -542,9 +518,11 @@ mod test {
#[test] #[test]
fn test_filter_allow_all_then_warn() { fn test_filter_allow_all_then_warn() {
let builder = let builder = ConfigStoreBuilder::default().with_filters([LintFilter::new(
LinterBuilder::default() AllowWarnDeny::Allow,
.with_filters([LintFilter::new(AllowWarnDeny::Allow, "all").unwrap()]); "all",
)
.unwrap()]);
assert!(builder.rules.is_empty(), "Allowing all rules should empty out the rules list"); assert!(builder.rules.is_empty(), "Allowing all rules should empty out the rules list");
let builder = builder.with_filters([LintFilter::warn(RuleCategory::Correctness)]); let builder = builder.with_filters([LintFilter::warn(RuleCategory::Correctness)]);
@ -570,7 +548,7 @@ mod test {
#[test] #[test]
fn test_rules_after_plugin_added() { fn test_rules_after_plugin_added() {
let builder = LinterBuilder::default(); let builder = ConfigStoreBuilder::default();
let initial_rule_count = builder.rules.len(); let initial_rule_count = builder.rules.len();
let builder = builder.and_plugins(LintPlugins::IMPORT, true); let builder = builder.and_plugins(LintPlugins::IMPORT, true);
@ -589,7 +567,7 @@ mod test {
let mut desired_plugins = LintPlugins::default(); let mut desired_plugins = LintPlugins::default();
desired_plugins.set(LintPlugins::TYPESCRIPT, false); desired_plugins.set(LintPlugins::TYPESCRIPT, false);
let linter = LinterBuilder::default().with_plugins(desired_plugins).build(); let linter = ConfigStoreBuilder::default().with_plugins(desired_plugins).build().unwrap();
for rule in linter.rules().iter() { for rule in linter.rules().iter() {
let name = rule.name(); let name = rule.name();
let plugin = rule.plugin_name(); let plugin = rule.plugin_name();
@ -603,11 +581,11 @@ mod test {
#[test] #[test]
fn test_plugin_configuration() { fn test_plugin_configuration() {
let builder = LinterBuilder::default(); let builder = ConfigStoreBuilder::default();
let initial_plugins = builder.plugins(); let initial_plugins = builder.plugins();
// ========================================================================================== // ==========================================================================================
// Test LinterBuilder::and_plugins, which deltas the plugin list instead of overriding it // Test ConfigStoreBuilder::and_plugins, which deltas the plugin list instead of overriding it
// ========================================================================================== // ==========================================================================================
// Enable eslint plugin. Since it's already enabled, this does nothing. // Enable eslint plugin. Since it's already enabled, this does nothing.
@ -632,7 +610,7 @@ mod test {
assert_eq!(initial_plugins, builder.plugins()); assert_eq!(initial_plugins, builder.plugins());
// ========================================================================================== // ==========================================================================================
// Test LinterBuilder::with_plugins, which _does_ override plugins // Test ConfigStoreBuilder::with_plugins, which _does_ override plugins
// ========================================================================================== // ==========================================================================================
let builder = builder.with_plugins(LintPlugins::ESLINT); let builder = builder.with_plugins(LintPlugins::ESLINT);
@ -660,7 +638,7 @@ mod test {
"#, "#,
) )
.unwrap(); .unwrap();
let builder = LinterBuilder::from_oxlintrc(false, oxlintrc); let builder = ConfigStoreBuilder::from_oxlintrc(false, oxlintrc);
for rule in &builder.rules { for rule in &builder.rules {
let name = rule.name(); let name = rule.name();
let plugin = rule.plugin_name(); let plugin = rule.plugin_name();

View file

@ -1,6 +1,7 @@
use std::path::PathBuf; use std::path::PathBuf;
mod categories; mod categories;
mod config_builder;
mod config_store; mod config_store;
mod env; mod env;
mod globals; mod globals;
@ -9,6 +10,7 @@ mod oxlintrc;
mod plugins; mod plugins;
mod rules; mod rules;
mod settings; mod settings;
pub use config_builder::{ConfigBuilderError, ConfigStoreBuilder};
pub use config_store::ConfigStore; pub use config_store::ConfigStore;
pub(crate) use config_store::ResolvedLinterState; pub(crate) use config_store::ResolvedLinterState;
pub use env::OxlintEnv; pub use env::OxlintEnv;
@ -20,7 +22,7 @@ pub use rules::{ESLintRule, OxlintRules};
pub use settings::{jsdoc::JSDocPluginSettings, OxlintSettings}; pub use settings::{jsdoc::JSDocPluginSettings, OxlintSettings};
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub(crate) struct LintConfig { pub struct LintConfig {
pub(crate) plugins: LintPlugins, pub(crate) plugins: LintPlugins,
pub(crate) settings: OxlintSettings, pub(crate) settings: OxlintSettings,
/// Environments enable and disable collections of global variables. /// Environments enable and disable collections of global variables.

View file

@ -4,7 +4,6 @@
mod tester; mod tester;
mod ast_util; mod ast_util;
mod builder;
mod config; mod config;
mod context; mod context;
mod disable_directives; mod disable_directives;
@ -29,23 +28,22 @@ use oxc_semantic::{AstNode, Semantic};
use rules::RULES; use rules::RULES;
pub use crate::{ pub use crate::{
builder::{LinterBuilder, LinterBuilderError}, config::{
config::{ESLintRule, LintPlugins, Oxlintrc}, ConfigBuilderError, ConfigStore, ConfigStoreBuilder, ESLintRule, LintPlugins, Oxlintrc,
},
context::LintContext, context::LintContext,
fixer::FixKind, fixer::FixKind,
frameworks::FrameworkFlags, frameworks::FrameworkFlags,
module_record::ModuleRecord, module_record::ModuleRecord,
options::LintOptions,
options::{AllowWarnDeny, InvalidFilterKind, LintFilter, LintFilterKind}, options::{AllowWarnDeny, InvalidFilterKind, LintFilter, LintFilterKind},
rule::{RuleCategory, RuleFixMeta, RuleMeta, RuleWithSeverity}, rule::{RuleCategory, RuleFixMeta, RuleMeta, RuleWithSeverity},
service::{LintService, LintServiceOptions}, service::{LintService, LintServiceOptions},
}; };
use crate::{ use crate::{
config::{ config::{LintConfig, OxlintEnv, OxlintGlobals, OxlintSettings, ResolvedLinterState},
ConfigStore, LintConfig, OxlintEnv, OxlintGlobals, OxlintSettings, ResolvedLinterState,
},
context::ContextHost, context::ContextHost,
fixer::{Fixer, Message}, fixer::{Fixer, Message},
options::LintOptions,
rules::RuleEnum, rules::RuleEnum,
table::RuleTable, table::RuleTable,
utils::iter_possible_jest_call_node, utils::iter_possible_jest_call_node,
@ -68,14 +66,8 @@ pub struct Linter {
config: ConfigStore, config: ConfigStore,
} }
impl Default for Linter {
fn default() -> Self {
LinterBuilder::default().build()
}
}
impl Linter { impl Linter {
pub(crate) fn new(options: LintOptions, config: ConfigStore) -> Self { pub fn new(options: LintOptions, config: ConfigStore) -> Self {
Self { options, config } Self { options, config }
} }
@ -103,10 +95,6 @@ impl Linter {
self.config.number_of_rules() self.config.number_of_rules()
} }
pub(crate) fn rules(&self) -> &Arc<[RuleWithSeverity]> {
self.config.rules()
}
pub fn run<'a>( pub fn run<'a>(
&self, &self,
path: &Path, path: &Path,

View file

@ -9,7 +9,7 @@ use crate::{fixer::FixKind, FrameworkFlags};
/// Subset of options used directly by the linter. /// Subset of options used directly by the linter.
#[derive(Debug, Default, Clone, Copy)] #[derive(Debug, Default, Clone, Copy)]
#[cfg_attr(test, derive(PartialEq))] #[cfg_attr(test, derive(PartialEq))]
pub(crate) struct LintOptions { pub struct LintOptions {
pub fix: FixKind, pub fix: FixKind,
pub framework_hints: FrameworkFlags, pub framework_hints: FrameworkFlags,
} }

View file

@ -2,7 +2,7 @@ use std::{borrow::Cow, fmt::Write};
use rustc_hash::{FxHashMap, FxHashSet}; use rustc_hash::{FxHashMap, FxHashSet};
use crate::{rules::RULES, Linter, RuleCategory, RuleFixMeta}; use crate::{rules::RULES, RuleCategory, RuleFixMeta};
pub struct RuleTable { pub struct RuleTable {
pub sections: Vec<RuleTableSection>, pub sections: Vec<RuleTableSection>,
@ -34,8 +34,11 @@ impl Default for RuleTable {
impl RuleTable { impl RuleTable {
pub fn new() -> Self { pub fn new() -> Self {
let default_rules = let default_rules = RULES
Linter::default().rules().iter().map(|rule| rule.name()).collect::<FxHashSet<&str>>(); .iter()
.filter(|rule| rule.category() == RuleCategory::Correctness)
.map(super::rules::RuleEnum::name)
.collect::<FxHashSet<&str>>();
let mut rows = RULES let mut rows = RULES
.iter() .iter()
@ -82,7 +85,7 @@ impl RuleTable {
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
RuleTable { total, sections, turned_on_by_default_count: default_rules.len() } RuleTable { total, sections, turned_on_by_default_count: 123 }
} }
} }

View file

@ -10,8 +10,9 @@ use serde::Deserialize;
use serde_json::Value; use serde_json::Value;
use crate::{ use crate::{
fixer::FixKind, rules::RULES, AllowWarnDeny, Fixer, LintPlugins, LintService, fixer::FixKind, options::LintOptions, rules::RULES, AllowWarnDeny, ConfigStoreBuilder, Fixer,
LintServiceOptions, LinterBuilder, Oxlintrc, RuleCategory, RuleEnum, RuleWithSeverity, LintPlugins, LintService, LintServiceOptions, Linter, Oxlintrc, RuleCategory, RuleEnum,
RuleWithSeverity,
}; };
#[derive(Eq, PartialEq)] #[derive(Eq, PartialEq)]
@ -441,15 +442,19 @@ impl Tester {
) -> TestResult { ) -> TestResult {
let allocator = Allocator::default(); let allocator = Allocator::default();
let rule = self.find_rule().read_json(rule_config.unwrap_or_default()); let rule = self.find_rule().read_json(rule_config.unwrap_or_default());
let linter = eslint_config let linter = Linter::new(
.as_ref() LintOptions::default(),
.map_or_else(LinterBuilder::empty, |v| { eslint_config
LinterBuilder::from_oxlintrc(true, Oxlintrc::deserialize(v).unwrap()) .as_ref()
}) .map_or_else(ConfigStoreBuilder::empty, |v| {
.with_fix(fix.into()) ConfigStoreBuilder::from_oxlintrc(true, Oxlintrc::deserialize(v).unwrap())
.with_plugins(self.plugins) })
.with_rule(RuleWithSeverity::new(rule, AllowWarnDeny::Warn)) .with_plugins(self.plugins)
.build(); .with_rule(RuleWithSeverity::new(rule, AllowWarnDeny::Warn))
.build()
.unwrap(),
)
.with_fix(fix.into());
let path_to_lint = if self.plugins.has_import() { let path_to_lint = if self.plugins.has_import() {
assert!(path.is_none(), "import plugin does not support path"); assert!(path.is_none(), "import plugin does not support path");

View file

@ -24,7 +24,7 @@ use oxc::{
transformer::{TransformOptions, Transformer}, transformer::{TransformOptions, Transformer},
}; };
use oxc_index::Idx; use oxc_index::Idx;
use oxc_linter::{Linter, ModuleRecord}; use oxc_linter::{ConfigStoreBuilder, LintOptions, Linter, ModuleRecord};
use oxc_prettier::{Prettier, PrettierOptions}; use oxc_prettier::{Prettier, PrettierOptions};
use crate::options::{OxcOptions, OxcRunOptions}; use crate::options::{OxcOptions, OxcRunOptions};
@ -309,8 +309,13 @@ impl Oxc {
if run_options.lint.unwrap_or_default() && self.diagnostics.borrow().is_empty() { if run_options.lint.unwrap_or_default() && self.diagnostics.borrow().is_empty() {
let semantic_ret = SemanticBuilder::new().with_cfg(true).build(program); let semantic_ret = SemanticBuilder::new().with_cfg(true).build(program);
let semantic = Rc::new(semantic_ret.semantic); let semantic = Rc::new(semantic_ret.semantic);
let linter_ret = let lint_config =
Linter::default().run(path, Rc::clone(&semantic), Arc::clone(module_record)); ConfigStoreBuilder::default().build().expect("Failed to build config store");
let linter_ret = Linter::new(LintOptions::default(), lint_config).run(
path,
Rc::clone(&semantic),
Arc::clone(module_record),
);
let diagnostics = linter_ret.into_iter().map(|e| e.error).collect(); let diagnostics = linter_ret.into_iter().map(|e| e.error).collect();
self.save_diagnostics(diagnostics); self.save_diagnostics(diagnostics);
} }

View file

@ -2,7 +2,7 @@ use std::{env, path::Path, rc::Rc, sync::Arc};
use oxc_allocator::Allocator; use oxc_allocator::Allocator;
use oxc_benchmark::{criterion_group, criterion_main, BenchmarkId, Criterion}; use oxc_benchmark::{criterion_group, criterion_main, BenchmarkId, Criterion};
use oxc_linter::{FixKind, LinterBuilder, ModuleRecord}; use oxc_linter::{ConfigStoreBuilder, FixKind, LintOptions, Linter, ModuleRecord};
use oxc_parser::Parser; use oxc_parser::Parser;
use oxc_semantic::SemanticBuilder; use oxc_semantic::SemanticBuilder;
use oxc_span::SourceType; use oxc_span::SourceType;
@ -39,7 +39,9 @@ fn bench_linter(criterion: &mut Criterion) {
let semantic = semantic_ret.semantic; let semantic = semantic_ret.semantic;
let module_record = Arc::new(ModuleRecord::new(path, &ret.module_record, &semantic)); let module_record = Arc::new(ModuleRecord::new(path, &ret.module_record, &semantic));
let semantic = Rc::new(semantic); let semantic = Rc::new(semantic);
let linter = LinterBuilder::all().with_fix(FixKind::All).build(); let lint_config =
ConfigStoreBuilder::all().build().expect("Failed to build config store");
let linter = Linter::new(LintOptions::default(), lint_config).with_fix(FixKind::All);
b.iter(|| linter.run(path, Rc::clone(&semantic), Arc::clone(&module_record))); b.iter(|| linter.run(path, Rc::clone(&semantic), Arc::clone(&module_record)));
}); });
} }