diff --git a/crates/oxc_linter/src/lib.rs b/crates/oxc_linter/src/lib.rs index 0dba9828ea99e..91214504e2600 100644 --- a/crates/oxc_linter/src/lib.rs +++ b/crates/oxc_linter/src/lib.rs @@ -161,7 +161,7 @@ impl Linter { .with_frameworks(self.options.framework_hints); // set file-specific jest/vitest flags - if self.options.plugins.jest || self.options.plugins.vitest { + if self.options.plugins.has_test() { let mut test_flags = FrameworkFlags::empty(); if frameworks::has_vitest_imports(ctx.module_record()) { @@ -191,7 +191,7 @@ impl Linter { } fn map_jest(&self, plugin_name: &'static str, rule_name: &str) -> &'static str { - if self.options.plugins.vitest + if self.options.plugins.has_vitest() && plugin_name == "jest" && utils::is_jest_rule_adapted_to_vitest(rule_name) { diff --git a/crates/oxc_linter/src/options/mod.rs b/crates/oxc_linter/src/options/mod.rs index d3e77fb193632..8f54003983bd5 100644 --- a/crates/oxc_linter/src/options/mod.rs +++ b/crates/oxc_linter/src/options/mod.rs @@ -6,6 +6,7 @@ use std::{convert::From, path::PathBuf}; pub use allow_warn_deny::AllowWarnDeny; use oxc_diagnostics::Error; pub use plugins::LintPluginOptions; +use plugins::LintPlugins; use rustc_hash::FxHashSet; use crate::{ @@ -26,7 +27,7 @@ use crate::{ pub(crate) struct LintOptions { pub fix: FixKind, pub framework_hints: FrameworkFlags, - pub plugins: LintPluginOptions, + pub plugins: LintPlugins, } impl From for LintOptions { @@ -34,7 +35,7 @@ impl From for LintOptions { Self { fix: options.fix, framework_hints: options.framework_hints, - plugins: options.plugins, + plugins: options.plugins.into(), } } } diff --git a/crates/oxc_linter/src/options/plugins.rs b/crates/oxc_linter/src/options/plugins.rs index 97d464676ade4..785bea0d2f797 100644 --- a/crates/oxc_linter/src/options/plugins.rs +++ b/crates/oxc_linter/src/options/plugins.rs @@ -1,3 +1,84 @@ +use bitflags::bitflags; + +bitflags! { + // NOTE: may be increased to a u32 if needed + #[derive(Debug, Clone, Copy, PartialEq, Hash)] + pub(crate) struct LintPlugins: u16 { + /// `eslint-plugin-react`, plus `eslint-plugin-react-hooks` + const REACT = 1 << 0; + /// `eslint-plugin-unicorn` + const UNICORN = 1 << 1; + /// `@typescript-eslint/eslint-plugin` + const TYPESCRIPT = 1 << 2; + /// Custom rules for Oxc, plus some ported from Deepscan + const OXC = 1 << 3; + /// `eslint-plugin-import` + const IMPORT = 1 << 4; + /// `eslint-plugin-jsdoc` + const JSDOC = 1 << 5; + /// `eslint-plugin-jest` + const JEST = 1 << 6; + /// `eslint-plugin-vitest` + const VITEST = 1 << 7; + /// `eslint-plugin-jsx-a11y` + const JSX_A11Y = 1 << 8; + /// `eslint-plugin-next` + const NEXTJS = 1 << 9; + /// `eslint-plugin-react-perf` + const REACT_PERF = 1 << 10; + /// `eslint-plugin-promise` + const PROMISE = 1 << 11; + /// `eslint-plugin-node` + const NODE = 1 << 12; + } +} +impl Default for LintPlugins { + #[inline] + fn default() -> Self { + LintPlugins::REACT | LintPlugins::UNICORN | LintPlugins::TYPESCRIPT | LintPlugins::OXC + } +} + +impl From for LintPlugins { + fn from(options: LintPluginOptions) -> Self { + let mut plugins = LintPlugins::empty(); + plugins.set(LintPlugins::REACT, options.react); + plugins.set(LintPlugins::UNICORN, options.unicorn); + plugins.set(LintPlugins::TYPESCRIPT, options.typescript); + plugins.set(LintPlugins::OXC, options.oxc); + plugins.set(LintPlugins::IMPORT, options.import); + plugins.set(LintPlugins::JSDOC, options.jsdoc); + plugins.set(LintPlugins::JEST, options.jest); + plugins.set(LintPlugins::VITEST, options.vitest); + plugins.set(LintPlugins::JSX_A11Y, options.jsx_a11y); + plugins.set(LintPlugins::NEXTJS, options.nextjs); + plugins.set(LintPlugins::REACT_PERF, options.react_perf); + plugins.set(LintPlugins::PROMISE, options.promise); + plugins.set(LintPlugins::NODE, options.node); + plugins + } +} + +impl LintPlugins { + /// Returns `true` if the Vitest plugin is enabled. + #[inline] + pub fn has_vitest(self) -> bool { + self.contains(LintPlugins::VITEST) + } + + /// Returns `true` if Jest or Vitest plugins are enabled. + #[inline] + pub fn has_test(self) -> bool { + self.intersects(LintPlugins::JEST.union(LintPlugins::VITEST)) + } + + /// Returns `true` if the import plugin is enabled. + #[inline] + pub fn has_import(self) -> bool { + self.contains(LintPlugins::IMPORT) + } +} + #[derive(Debug)] #[non_exhaustive] pub struct LintPluginOptions { @@ -138,6 +219,13 @@ mod test { } } + #[test] + fn test_default_conversion() { + let plugins = LintPlugins::default(); + let options = LintPluginOptions::default(); + assert_eq!(LintPlugins::from(options), plugins); + } + #[test] fn test_collect_empty() { let empty: &[&str] = &[]; diff --git a/crates/oxc_linter/src/service.rs b/crates/oxc_linter/src/service.rs index 152f4841e7eb8..bc6d86afa740a 100644 --- a/crates/oxc_linter/src/service.rs +++ b/crates/oxc_linter/src/service.rs @@ -165,7 +165,7 @@ pub struct Runtime { impl Runtime { fn new(linter: Linter, options: LintServiceOptions) -> Self { - let resolver = linter.options().plugins.import.then(|| { + let resolver = linter.options().plugins.has_import().then(|| { Self::get_resolver(options.tsconfig.or_else(|| Some(options.cwd.join("tsconfig.json")))) }); Self { @@ -310,7 +310,7 @@ impl Runtime { .build_module_record(path, program); let module_record = semantic_builder.module_record(); - if self.linter.options().plugins.import { + if self.linter.options().plugins.has_import() { self.module_map.insert( path.to_path_buf().into_boxed_path(), ModuleState::Resolved(Arc::clone(&module_record)), @@ -392,7 +392,7 @@ impl Runtime { } fn init_cache_state(&self, path: &Path) -> bool { - if !self.linter.options().plugins.import { + if !self.linter.options().plugins.has_import() { return false; } @@ -447,7 +447,7 @@ impl Runtime { } fn ignore_path(&self, path: &Path) { - if self.linter.options().plugins.import { + if self.linter.options().plugins.has_import() { self.module_map.insert(path.to_path_buf().into_boxed_path(), ModuleState::Ignored); self.update_cache_state(path); }