diff --git a/crates/oxc_linter/src/context/mod.rs b/crates/oxc_linter/src/context/mod.rs index cb16cf710cce5..8fa5eba053d68 100644 --- a/crates/oxc_linter/src/context/mod.rs +++ b/crates/oxc_linter/src/context/mod.rs @@ -16,7 +16,7 @@ use crate::{ AllowWarnDeny, FrameworkFlags, ModuleRecord, OxlintEnv, OxlintGlobals, OxlintSettings, config::GlobalValue, disable_directives::DisableDirectives, - fixer::{FixKind, Message, PossibleFixes, RuleFix, RuleFixer}, + fixer::{Fix, FixKind, Message, PossibleFixes, RuleFix, RuleFixer}, }; mod host; @@ -325,7 +325,6 @@ impl<'a> LintContext<'a> { /// returns something that can turn into a [`RuleFix`]. /// /// [closure]: - #[cfg_attr(debug_assertions, expect(clippy::missing_panics_doc))] // Only panics in debug mode pub fn diagnostic_with_fix_of_kind( &self, diagnostic: OxcDiagnostic, @@ -334,29 +333,74 @@ impl<'a> LintContext<'a> { ) where C: Into>, F: FnOnce(RuleFixer<'_, 'a>) -> C, + { + let (diagnostic, fix) = self.create_fix(fix_kind, fix, diagnostic); + if let Some(fix) = fix { + self.add_diagnostic(Message::new(diagnostic, PossibleFixes::Single(fix))); + } else { + self.diagnostic(diagnostic); + } + } + + /// Report a lint rule violation and provide an automatic fix of a specific kind. + /// This method is used when the rule can provide multiple fixes for the same diagnostic. + pub fn diagnostics_with_multiple_fixes( + &self, + diagnostic: OxcDiagnostic, + fix_one: (FixKind, F1), + fix_two: (FixKind, F2), + ) where + C: Into>, + F1: FnOnce(RuleFixer<'_, 'a>) -> C, + F2: FnOnce(RuleFixer<'_, 'a>) -> C, + { + let fixes_result: Vec> = vec![ + self.create_fix(fix_one.0, fix_one.1, diagnostic.clone()).1, + self.create_fix(fix_two.0, fix_two.1, diagnostic.clone()).1, + ] + .into_iter() + .flatten() + .collect(); + + if fixes_result.is_empty() { + self.diagnostic(diagnostic); + } else { + self.add_diagnostic(Message::new(diagnostic, PossibleFixes::Multiple(fixes_result))); + } + } + + fn create_fix( + &self, + fix_kind: FixKind, + fix: F, + diagnostic: OxcDiagnostic, + ) -> (OxcDiagnostic, Option>) + where + C: Into>, + F: FnOnce(RuleFixer<'_, 'a>) -> C, { let fixer = RuleFixer::new(fix_kind, self); let rule_fix: RuleFix<'a> = fix(fixer).into(); #[cfg(debug_assertions)] - { - assert!( - self.current_rule_fix_capabilities.supports_fix(fix_kind), - "Rule `{}` does not support safe fixes. Did you forget to update fix capabilities in declare_oxc_lint?.\n\tSupported fix kinds: {:?}\n\tAttempted fix kind: {:?}", - self.current_rule_name, - FixKind::from(self.current_rule_fix_capabilities), - rule_fix.kind() - ); - } + debug_assert!( + self.current_rule_fix_capabilities.supports_fix(fix_kind), + "Rule `{}` does not support safe fixes. Did you forget to update fix capabilities in declare_oxc_lint?.\n\tSupported fix kinds: {:?}\n\tAttempted fix kind: {:?}", + self.current_rule_name, + FixKind::from(self.current_rule_fix_capabilities), + rule_fix.kind() + ); + let diagnostic = match (rule_fix.message(), &diagnostic.help) { (Some(message), None) => diagnostic.with_help(message.to_owned()), _ => diagnostic, }; + if self.parent.fix.can_apply(rule_fix.kind()) && !rule_fix.is_empty() { let fix = rule_fix.into_fix(self.source_text()); #[cfg(debug_assertions)] { if fix.span.size() > 1 { - assert!( + debug_assert!( fix.message.as_ref().is_some_and(|msg| !msg.is_empty()), "Rule `{}/{}` fix should have a message for a complex fix. Did you forget to add a message?\n Source text: {:?}\n Fixed text: {:?}\nhelp: You can add a message to a fix with `RuleFix.with_message()`", self.current_plugin_name, @@ -366,9 +410,10 @@ impl<'a> LintContext<'a> { ); } } - self.add_diagnostic(Message::new(diagnostic, PossibleFixes::Single(fix))); + + (diagnostic, Some(fix)) } else { - self.diagnostic(diagnostic); + (diagnostic, None) } }