diff --git a/crates/oxc_linter/src/config/config_builder.rs b/crates/oxc_linter/src/config/config_builder.rs index 6d81043e89de1..b3f8865c32323 100644 --- a/crates/oxc_linter/src/config/config_builder.rs +++ b/crates/oxc_linter/src/config/config_builder.rs @@ -346,7 +346,10 @@ impl ConfigStoreBuilder { RULES .iter() - .filter(|rule| builtin_plugins.contains(LintPlugins::from(rule.plugin_name()))) + .filter(|rule| { + LintPlugins::try_from(rule.plugin_name()) + .is_ok_and(|plugin_flag| builtin_plugins.contains(plugin_flag)) + }) .cloned() .collect() } @@ -396,7 +399,10 @@ impl ConfigStoreBuilder { let mut rules: Vec<_> = self .rules .into_iter() - .filter(|(r, _)| plugins.contains(r.plugin_name().into())) + .filter(|(r, _)| { + LintPlugins::try_from(r.plugin_name()) + .is_ok_and(|plugin_name| plugins.contains(plugin_name)) + }) .collect(); rules.sort_unstable_by_key(|(r, _)| r.id()); @@ -457,7 +463,8 @@ impl ConfigStoreBuilder { // NOTE: this logic means there's no way to disable ESLint // correctness rules. I think that's fine for now. rule.category() == RuleCategory::Correctness - && plugins.contains(LintPlugins::from(rule.plugin_name())) + && LintPlugins::try_from(rule.plugin_name()) + .is_ok_and(|plugin_flag| plugins.contains(plugin_flag)) }) .map(|rule| (rule.clone(), AllowWarnDeny::Warn)) .collect() @@ -535,7 +542,7 @@ impl ConfigStoreBuilder { match result { PluginLoadResult::Success { name, offset, rule_names } => { - if name != "eslint" && LintPlugins::from(name.as_str()) == LintPlugins::empty() { + if LintPlugins::try_from(name.as_str()).is_err() { external_plugin_store.register_plugin(plugin_path, name, offset, rule_names); Ok(()) } else { @@ -650,11 +657,12 @@ mod test { for (rule, severity) in &builder.rules { assert_eq!(rule.category(), RuleCategory::Correctness); assert_eq!(*severity, AllowWarnDeny::Warn); - let plugin = rule.plugin_name(); + let plugin_name = rule.plugin_name(); + let plugin = LintPlugins::try_from(plugin_name); let name = rule.name(); assert!( - builder.plugins().contains(plugin.into()), - "{plugin}/{name} is in the default rule set but its plugin is not enabled" + plugin.is_ok_and(|plugin| builder.plugins().contains(plugin)), + "{plugin_name}/{name} is in the default rule set but its plugin is not enabled" ); } } @@ -683,11 +691,12 @@ mod test { assert_eq!(rule.category(), RuleCategory::Correctness); assert_eq!(*severity, AllowWarnDeny::Deny); - let plugin = rule.plugin_name(); + let plugin_name = rule.plugin_name(); + let plugin = LintPlugins::try_from(plugin_name); let name = rule.name(); assert!( - builder.plugins().contains(plugin.into()), - "{plugin}/{name} is in the default rule set but its plugin is not enabled" + plugin.is_ok_and(|plugin| builder.plugins().contains(plugin)), + "{plugin_name}/{name} is in the default rule set but its plugin is not enabled" ); } } @@ -792,8 +801,8 @@ mod test { let name = rule.name(); let plugin = rule.plugin_name(); assert_ne!( - LintPlugins::from(plugin), - LintPlugins::TYPESCRIPT, + LintPlugins::try_from(plugin), + Ok(LintPlugins::TYPESCRIPT), "{plugin}/{name} is in the rules list after typescript plugin has been disabled" ); } diff --git a/crates/oxc_linter/src/config/config_store.rs b/crates/oxc_linter/src/config/config_store.rs index 8b66d78794f32..38e9e9e655f79 100644 --- a/crates/oxc_linter/src/config/config_store.rs +++ b/crates/oxc_linter/src/config/config_store.rs @@ -167,13 +167,19 @@ impl Config { let mut rules = self .base_rules .iter() - .filter(|(rule, _)| plugins.contains(LintPlugins::from(rule.plugin_name()))) + .filter(|(rule, _)| { + LintPlugins::try_from(rule.plugin_name()) + .is_ok_and(|plugin| plugins.contains(plugin)) + }) .cloned() .collect::>(); let all_rules = RULES .iter() - .filter(|rule| plugins.contains(LintPlugins::from(rule.plugin_name()))) + .filter(|rule| { + LintPlugins::try_from(rule.plugin_name()) + .is_ok_and(|plugin| plugins.contains(plugin)) + }) .cloned() .collect::>(); @@ -194,7 +200,8 @@ impl Config { if !unconfigured_plugins.is_empty() { for (rule, severity) in all_rules.iter().filter_map(|rule| { - let rule_plugin = LintPlugins::from(rule.plugin_name()); + let rule_plugin = LintPlugins::try_from(rule.plugin_name()) + .unwrap_or(LintPlugins::empty()); // Only apply categories to rules from unconfigured plugins if unconfigured_plugins.contains(rule_plugin) { self.categories diff --git a/crates/oxc_linter/src/config/plugins.rs b/crates/oxc_linter/src/config/plugins.rs index a19cc64818dbd..1d0d4f23753ec 100644 --- a/crates/oxc_linter/src/config/plugins.rs +++ b/crates/oxc_linter/src/config/plugins.rs @@ -75,31 +75,34 @@ impl LintPlugins { } } -impl From<&str> for LintPlugins { - fn from(value: &str) -> Self { +impl TryFrom<&str> for LintPlugins { + type Error = (); + + fn try_from(value: &str) -> Result { match value { - "react" | "react-hooks" | "react_hooks" => LintPlugins::REACT, - "unicorn" => LintPlugins::UNICORN, + "react" | "react-hooks" | "react_hooks" => Ok(LintPlugins::REACT), + "unicorn" => Ok(LintPlugins::UNICORN), "typescript" | "typescript-eslint" | "typescript_eslint" | "@typescript-eslint" => { - LintPlugins::TYPESCRIPT + Ok(LintPlugins::TYPESCRIPT) } // deepscan for backwards compatibility. Those rules have been moved into oxc - "oxc" | "deepscan" => LintPlugins::OXC, + "oxc" | "deepscan" => Ok(LintPlugins::OXC), // import-x has the same rules but better performance - "import" | "import-x" => LintPlugins::IMPORT, - "jsdoc" => LintPlugins::JSDOC, - "jest" => LintPlugins::JEST, - "vitest" => LintPlugins::VITEST, - "jsx-a11y" | "jsx_a11y" => LintPlugins::JSX_A11Y, - "nextjs" => LintPlugins::NEXTJS, - "react-perf" | "react_perf" => LintPlugins::REACT_PERF, - "promise" => LintPlugins::PROMISE, - "node" => LintPlugins::NODE, - "regex" => LintPlugins::REGEX, - "vue" => LintPlugins::VUE, + "import" | "import-x" => Ok(LintPlugins::IMPORT), + "jsdoc" => Ok(LintPlugins::JSDOC), + "jest" => Ok(LintPlugins::JEST), + "vitest" => Ok(LintPlugins::VITEST), + "jsx-a11y" | "jsx_a11y" => Ok(LintPlugins::JSX_A11Y), + "nextjs" => Ok(LintPlugins::NEXTJS), + "react-perf" | "react_perf" => Ok(LintPlugins::REACT_PERF), + "promise" => Ok(LintPlugins::PROMISE), + "node" => Ok(LintPlugins::NODE), + "regex" => Ok(LintPlugins::REGEX), + "vue" => Ok(LintPlugins::VUE), // "eslint" is not really a plugin, so it's 'empty'. This has the added benefit of // making it the default value. - _ => LintPlugins::empty(), + "eslint" => Ok(LintPlugins::ESLINT), + _ => Err(()), } } } @@ -134,15 +137,11 @@ impl<'de> Deserialize<'de> for LintPlugins { let mut lint_plugins = LintPlugins::empty(); for plugin in &plugin_names { - if plugin == "eslint" { - continue; - } - - let plugin_flag = LintPlugins::from(plugin.as_str()); - if plugin_flag == LintPlugins::empty() { + if let Ok(plugin_flag) = LintPlugins::try_from(plugin.as_str()) { + lint_plugins |= plugin_flag; + } else { return Err(serde::de::Error::custom(format!("Unknown plugin: '{plugin}'."))); } - lint_plugins |= plugin_flag; } Ok(lint_plugins) @@ -243,10 +242,10 @@ mod tests { #[test] fn test_plugin_from_str() { - assert_eq!(LintPlugins::from("react"), LintPlugins::REACT); - assert_eq!(LintPlugins::from("typescript-eslint"), LintPlugins::TYPESCRIPT); - assert_eq!(LintPlugins::from("deepscan"), LintPlugins::OXC); - assert_eq!(LintPlugins::from("unknown"), LintPlugins::empty()); + assert_eq!(LintPlugins::try_from("react"), Ok(LintPlugins::REACT)); + assert_eq!(LintPlugins::try_from("typescript-eslint"), Ok(LintPlugins::TYPESCRIPT)); + assert_eq!(LintPlugins::try_from("deepscan"), Ok(LintPlugins::OXC)); + assert_eq!(LintPlugins::try_from("unknown"), Err(())); } #[test] diff --git a/crates/oxc_linter/src/config/rules.rs b/crates/oxc_linter/src/config/rules.rs index 3cf38589e0688..af2434125e48b 100644 --- a/crates/oxc_linter/src/config/rules.rs +++ b/crates/oxc_linter/src/config/rules.rs @@ -86,8 +86,7 @@ impl OxlintRules { let config = rule_config.config.clone().unwrap_or_default(); let severity = rule_config.severity; - // TODO(camc314): remove the `plugin_name == "eslint"` - if plugin_name == "eslint" || !LintPlugins::from(plugin_name).is_empty() { + if LintPlugins::try_from(plugin_name).is_ok() { let rule = rules_map.get(&plugin_name).copied().or_else(|| { all_rules .iter() diff --git a/crates/oxc_linter/src/tester.rs b/crates/oxc_linter/src/tester.rs index a4c80e7112ad3..a9e36b290469a 100644 --- a/crates/oxc_linter/src/tester.rs +++ b/crates/oxc_linter/src/tester.rs @@ -518,7 +518,12 @@ impl Tester { ) .unwrap() }) - .with_builtin_plugins(self.plugins | LintPlugins::from(self.plugin_name)) + .with_builtin_plugins( + self.plugins + | LintPlugins::try_from(self.plugin_name).unwrap_or_else(|()| { + panic!("invalid plugin name: {}", self.plugin_name) + }), + ) .with_rule(rule, AllowWarnDeny::Warn) .build(&external_plugin_store) .unwrap(), diff --git a/tasks/website/src/linter/rules/doc_page.rs b/tasks/website/src/linter/rules/doc_page.rs index 43e0fdc687b77..0d32d68eeaeb6 100644 --- a/tasks/website/src/linter/rules/doc_page.rs +++ b/tasks/website/src/linter/rules/doc_page.rs @@ -172,8 +172,7 @@ fn rule_source(rule: &RuleTableRow) -> String { /// - Example: `eslint` => true /// - Example: `jest` => false fn is_default_plugin(plugin: &str) -> bool { - let plugin = LintPlugins::from(plugin); - LintPlugins::default().contains(plugin) + LintPlugins::try_from(plugin).is_ok_and(|plugin| LintPlugins::default().contains(plugin)) } /// Returns the normalized plugin name. @@ -181,7 +180,7 @@ fn is_default_plugin(plugin: &str) -> bool { /// - Example: `eslint` -> `eslint` /// - Example: `jsx_a11y` -> `jsx-a11y` fn get_normalized_plugin_name(plugin: &str) -> &str { - LintPlugins::from(plugin).into() + LintPlugins::try_from(plugin).unwrap_or(LintPlugins::empty()).into() } fn how_to_use(rule: &RuleTableRow) -> String {