diff --git a/crates/oxc_language_server/src/main.rs b/crates/oxc_language_server/src/main.rs index f55dd1342c66a..daedab95e5fd8 100644 --- a/crates/oxc_language_server/src/main.rs +++ b/crates/oxc_language_server/src/main.rs @@ -7,7 +7,7 @@ use futures::future::join_all; use globset::Glob; use ignore::gitignore::Gitignore; use log::{debug, error, info}; -use oxc_linter::{FixKind, Linter, OxlintOptions}; +use oxc_linter::{FixKind, LinterBuilder, Oxlintrc}; use serde::{Deserialize, Serialize}; use tokio::sync::{Mutex, OnceCell, RwLock, SetError}; use tower_lsp::{ @@ -345,12 +345,13 @@ impl Backend { if let Some(config_path) = config_path { let mut linter = self.server_linter.write().await; *linter = ServerLinter::new_with_linter( - Linter::from_options( - OxlintOptions::default() - .with_fix(FixKind::SafeFix) - .with_config_path(Some(config_path)), + LinterBuilder::from_oxlintrc( + true, + Oxlintrc::from_file(&config_path) + .expect("should have initialized linter with new options"), ) - .expect("should have initialized linter with new options"), + .with_fix(FixKind::SafeFix) + .build(), ); } } diff --git a/crates/oxc_linter/src/lib.rs b/crates/oxc_linter/src/lib.rs index fec6cb9de306a..b2ad5345d530e 100644 --- a/crates/oxc_linter/src/lib.rs +++ b/crates/oxc_linter/src/lib.rs @@ -26,7 +26,6 @@ use std::{io::Write, path::Path, rc::Rc, sync::Arc}; use config::LintConfig; use context::ContextHost; use options::LintOptions; -use oxc_diagnostics::Error; use oxc_semantic::{AstNode, Semantic}; pub use crate::{ @@ -35,9 +34,7 @@ pub use crate::{ context::LintContext, fixer::FixKind, frameworks::FrameworkFlags, - options::{ - AllowWarnDeny, InvalidFilterKind, LintFilter, LintFilterKind, LintPlugins, OxlintOptions, - }, + options::{AllowWarnDeny, InvalidFilterKind, LintFilter, LintFilterKind, LintPlugins}, rule::{RuleCategory, RuleFixMeta, RuleMeta, RuleWithSeverity}, service::{LintService, LintServiceOptions}, }; @@ -66,7 +63,7 @@ pub struct Linter { impl Default for Linter { fn default() -> Self { - Self::from_options(OxlintOptions::default()).unwrap() + LinterBuilder::default().build() } } @@ -79,14 +76,6 @@ impl Linter { Self { rules, options, config: Arc::new(config) } } - /// # Errors - /// - /// Returns `Err` if there are any errors parsing the configuration file. - pub fn from_options(options: OxlintOptions) -> Result { - let (rules, config) = options.derive_rules_and_config()?; - Ok(Self { rules, options: options.into(), config: Arc::new(config) }) - } - #[cfg(test)] #[must_use] pub fn with_rules(mut self, rules: Vec) -> Self { diff --git a/crates/oxc_linter/src/options/mod.rs b/crates/oxc_linter/src/options/mod.rs index ceb566d5cf108..4ce020dc5f984 100644 --- a/crates/oxc_linter/src/options/mod.rs +++ b/crates/oxc_linter/src/options/mod.rs @@ -2,28 +2,13 @@ mod allow_warn_deny; mod filter; mod plugins; -use std::{convert::From, path::PathBuf}; - -use oxc_diagnostics::Error; -use rustc_hash::FxHashSet; - pub use allow_warn_deny::AllowWarnDeny; pub use filter::{InvalidFilterKind, LintFilter, LintFilterKind}; -pub use plugins::{LintPluginOptions, LintPlugins}; +pub use plugins::LintPlugins; -use crate::{ - config::{LintConfig, Oxlintrc}, - fixer::FixKind, - rules::RULES, - utils::is_jest_rule_adapted_to_vitest, - FrameworkFlags, RuleCategory, RuleEnum, RuleWithSeverity, -}; +use crate::{fixer::FixKind, FrameworkFlags}; -/// Subset of options used directly by the [`Linter`]. Derived from -/// [`OxlintOptions`], which is the public-facing API. Do not expose this -/// outside of this crate. -/// -/// [`Linter`]: crate::Linter +/// Subset of options used directly by the linter. #[derive(Debug, Default, Clone, Copy)] #[cfg_attr(test, derive(PartialEq))] pub(crate) struct LintOptions { @@ -31,271 +16,3 @@ pub(crate) struct LintOptions { pub framework_hints: FrameworkFlags, pub plugins: LintPlugins, } - -impl From for LintOptions { - fn from(options: OxlintOptions) -> Self { - Self { - fix: options.fix, - framework_hints: options.framework_hints, - plugins: options.plugins.into(), - } - } -} - -#[derive(Debug)] -pub struct OxlintOptions { - /// Allow / Deny rules in order. [("allow" / "deny", rule name)] - /// Defaults to [("deny", "correctness")] - pub filter: Vec, - pub config_path: Option, - /// Enable automatic code fixes. Set to [`None`] to disable. - /// - /// The kind represents the riskiest fix that the linter can apply. - pub fix: FixKind, - - pub plugins: LintPluginOptions, - - pub framework_hints: FrameworkFlags, -} - -impl Default for OxlintOptions { - fn default() -> Self { - Self { - filter: vec![LintFilter::warn(RuleCategory::Correctness)], - config_path: None, - fix: FixKind::None, - plugins: LintPluginOptions::default(), - framework_hints: FrameworkFlags::default(), - } - } -} - -impl OxlintOptions { - #[must_use] - pub fn with_filter(mut self, filter: Vec) -> Self { - if !filter.is_empty() { - self.filter = filter; - } - self - } - - #[must_use] - pub fn with_config_path(mut self, filter: Option) -> Self { - self.config_path = filter; - self - } - - /// Set the kind of auto fixes to apply. - /// - /// # Example - /// - /// ``` - /// use oxc_linter::{LintOptions, FixKind}; - /// - /// // turn off all auto fixes. This is default behavior. - /// LintOptions::default().with_fix(FixKind::None); - /// ``` - #[must_use] - pub fn with_fix(mut self, kind: FixKind) -> Self { - self.fix = kind; - self - } - - #[must_use] - pub fn with_react_plugin(mut self, yes: bool) -> Self { - self.plugins.react = yes; - self - } - - #[must_use] - pub fn with_unicorn_plugin(mut self, yes: bool) -> Self { - self.plugins.unicorn = yes; - self - } - - #[must_use] - pub fn with_typescript_plugin(mut self, yes: bool) -> Self { - self.plugins.typescript = yes; - self - } - - #[must_use] - pub fn with_oxc_plugin(mut self, yes: bool) -> Self { - self.plugins.oxc = yes; - self - } - - #[must_use] - pub fn with_import_plugin(mut self, yes: bool) -> Self { - self.plugins.import = yes; - self - } - - #[must_use] - pub fn with_jsdoc_plugin(mut self, yes: bool) -> Self { - self.plugins.jsdoc = yes; - self - } - - #[must_use] - pub fn with_jest_plugin(mut self, yes: bool) -> Self { - self.plugins.jest = yes; - self - } - - #[must_use] - pub fn with_vitest_plugin(mut self, yes: bool) -> Self { - self.plugins.vitest = yes; - self - } - - #[must_use] - pub fn with_jsx_a11y_plugin(mut self, yes: bool) -> Self { - self.plugins.jsx_a11y = yes; - self - } - - #[must_use] - pub fn with_nextjs_plugin(mut self, yes: bool) -> Self { - self.plugins.nextjs = yes; - self - } - - #[must_use] - pub fn with_react_perf_plugin(mut self, yes: bool) -> Self { - self.plugins.react_perf = yes; - self - } - - #[must_use] - pub fn with_promise_plugin(mut self, yes: bool) -> Self { - self.plugins.promise = yes; - self - } - - #[must_use] - pub fn with_node_plugin(mut self, yes: bool) -> Self { - self.plugins.node = yes; - self - } - - #[must_use] - pub fn with_security_plugin(mut self, yes: bool) -> Self { - self.plugins.security = yes; - self - } -} - -impl OxlintOptions { - /// # Errors - /// - /// * Returns `Err` if there are any errors parsing the configuration file. - pub(crate) fn derive_rules_and_config( - &self, - ) -> Result<(Vec, LintConfig), Error> { - let config = self.config_path.as_ref().map(|path| Oxlintrc::from_file(path)).transpose()?; - - let mut rules: FxHashSet = FxHashSet::default(); - let all_rules = self.get_filtered_rules(); - - for (severity, filter) in self.filter.iter().map(Into::into) { - match severity { - AllowWarnDeny::Deny | AllowWarnDeny::Warn => match filter { - LintFilterKind::Category(category) => { - rules.extend( - all_rules - .iter() - .filter(|rule| rule.category() == *category) - .map(|rule| RuleWithSeverity::new(rule.clone(), severity)), - ); - } - LintFilterKind::Rule(_, name) => { - rules.extend( - all_rules - .iter() - .filter(|rule| rule.name() == name) - .map(|rule| RuleWithSeverity::new(rule.clone(), severity)), - ); - } - LintFilterKind::Generic(name_or_category) => { - if name_or_category == "all" { - rules.extend( - all_rules - .iter() - .filter(|rule| rule.category() != RuleCategory::Nursery) - .map(|rule| RuleWithSeverity::new(rule.clone(), severity)), - ); - } else { - rules.extend( - all_rules - .iter() - .filter(|rule| rule.name() == name_or_category) - .map(|rule| RuleWithSeverity::new(rule.clone(), severity)), - ); - } - } - }, - AllowWarnDeny::Allow => match filter { - LintFilterKind::Category(category) => { - rules.retain(|rule| rule.category() != *category); - } - LintFilterKind::Rule(_, name) => { - rules.retain(|rule| rule.name() != name); - } - LintFilterKind::Generic(name_or_category) => { - if name_or_category == "all" { - rules.clear(); - } else { - rules.retain(|rule| rule.name() != name_or_category); - } - } - }, - } - } - - if let Some(config) = &config { - config.rules.override_rules(&mut rules, &all_rules); - } - - let mut rules = rules.into_iter().collect::>(); - - // for stable diagnostics output ordering - rules.sort_unstable_by_key(|rule| rule.id()); - - Ok((rules, config.map(Into::into).unwrap_or_default())) - } - - /// Get final filtered rules by reading `self.xxx_plugin` - fn get_filtered_rules(&self) -> Vec { - RULES - .iter() - .filter(|rule| match rule.plugin_name() { - "react" => self.plugins.react, - "unicorn" => self.plugins.unicorn, - "typescript" => self.plugins.typescript, - "import" => self.plugins.import, - "jsdoc" => self.plugins.jsdoc, - "jest" => { - if self.plugins.jest { - return true; - } - if self.plugins.vitest && is_jest_rule_adapted_to_vitest(rule.name()) { - return true; - } - false - } - "vitest" => self.plugins.vitest, - "jsx_a11y" => self.plugins.jsx_a11y, - "nextjs" => self.plugins.nextjs, - "react_perf" => self.plugins.react_perf, - "oxc" => self.plugins.oxc, - "eslint" | "tree_shaking" => true, - "promise" => self.plugins.promise, - "node" => self.plugins.node, - "security" => self.plugins.security, - name => panic!("Unhandled plugin: {name}"), - }) - .cloned() - .collect::>() - } -}