diff --git a/crates/oxc_linter/src/context/host.rs b/crates/oxc_linter/src/context/host.rs index e67fd54f65b79..63a2dfec2e029 100644 --- a/crates/oxc_linter/src/context/host.rs +++ b/crates/oxc_linter/src/context/host.rs @@ -319,7 +319,9 @@ impl<'a> ContextHost<'a> { OxcDiagnostic::error(message_for_disable) .with_label(span) .with_severity(rule_severity), - PossibleFixes::Single(Fix::delete(span).with_message(fix_message)), + PossibleFixes::Single( + Fix::delete(span).with_kind(FixKind::Fix).with_message(fix_message), + ), )); } RuleCommentType::Single(rules_vec) => { diff --git a/crates/oxc_linter/src/disable_directives.rs b/crates/oxc_linter/src/disable_directives.rs index 04b89d9782026..61c925f6316ef 100644 --- a/crates/oxc_linter/src/disable_directives.rs +++ b/crates/oxc_linter/src/disable_directives.rs @@ -6,7 +6,7 @@ use oxc_span::Span; use rust_lapper::{Interval, Lapper}; use rustc_hash::FxHashMap; -use crate::fixer::Fix; +use crate::{FixKind, fixer::Fix}; #[derive(Debug, Clone, Eq, PartialEq)] enum DisabledRule { @@ -114,7 +114,8 @@ impl RuleCommentRule { return Fix::delete(Span::new( self.name_span.start - comma_before_offset, self.name_span.end, - )); + )) + .with_kind(FixKind::Fix); } let after_source = &source_text[self.name_span.end as usize..comment_span.end as usize]; @@ -136,7 +137,8 @@ impl RuleCommentRule { return Fix::delete(Span::new( self.name_span.start, self.name_span.end + comma_after_offset, - )); + )) + .with_kind(FixKind::Fix); } unreachable!( diff --git a/crates/oxc_linter/src/fixer/fix.rs b/crates/oxc_linter/src/fixer/fix.rs index 70e57cb3bfd0d..8d982eb62d05f 100644 --- a/crates/oxc_linter/src/fixer/fix.rs +++ b/crates/oxc_linter/src/fixer/fix.rs @@ -277,6 +277,7 @@ impl RuleFix { }; let mut fix = self.fix.normalize_fixes(source_text); fix.message = message; + fix.kind = self.kind; fix } @@ -316,6 +317,7 @@ pub struct Fix { /// A brief suggestion message describing the fix. Will be shown in /// editors via code actions. pub message: Option>, + pub kind: FixKind, pub span: Span, } @@ -327,17 +329,17 @@ impl Default for Fix { impl Fix { pub const fn delete(span: Span) -> Self { - Self { content: Cow::Borrowed(""), message: None, span } + Self { content: Cow::Borrowed(""), message: None, span, kind: FixKind::None } } pub fn new>>(content: T, span: Span) -> Self { - Self { content: content.into(), message: None, span } + Self { content: content.into(), message: None, span, kind: FixKind::None } } /// Creates a [`Fix`] that doesn't change the source code. #[inline] pub const fn empty() -> Self { - Self { content: Cow::Borrowed(""), message: None, span: SPAN } + Self { content: Cow::Borrowed(""), message: None, span: SPAN, kind: FixKind::None } } #[must_use] @@ -345,6 +347,12 @@ impl Fix { self.message = Some(message.into()); self } + + #[must_use] + pub fn with_kind(mut self, kind: FixKind) -> Self { + self.kind = kind; + self + } } #[derive(Debug, Clone, PartialEq, Eq)] @@ -570,13 +578,17 @@ impl CompositeFix { let mut last_pos = start; let mut output = String::new(); let mut merged_fix_message = None; + let mut merged_fix_kind = FixKind::None; for fix in fixes { - let Fix { content, span, message } = fix; + let Fix { content, span, message, kind: fix_kind } = fix; if let Some(message) = message { merged_fix_message.get_or_insert(message); } + // use the most severe fix kind (dangerous > suggestion > fix > none) + merged_fix_kind = merged_fix_kind.union(fix_kind); + // negative range or overlapping ranges is invalid if span.start > span.end { return Err(MergeFixesError::NegativeRange(span)); @@ -605,6 +617,7 @@ impl CompositeFix { if let Some(message) = merged_fix_message { fix = fix.with_message(message); } + fix = fix.with_kind(merged_fix_kind); Ok(fix) } } @@ -665,6 +678,12 @@ mod test { } } + #[test] + fn assert_size() { + use std::mem::size_of; + assert_eq!(size_of::(), 64); + } + #[test] fn test_none() { assert!(FixKind::None.is_none()); diff --git a/crates/oxc_linter/src/fixer/mod.rs b/crates/oxc_linter/src/fixer/mod.rs index 7469fc2c4ffad..04f9fe53bf44a 100644 --- a/crates/oxc_linter/src/fixer/mod.rs +++ b/crates/oxc_linter/src/fixer/mod.rs @@ -425,6 +425,8 @@ mod test { use oxc_diagnostics::OxcDiagnostic; use oxc_span::{SourceType, Span}; + use crate::FixKind; + use super::{CompositeFix, Fix, FixResult, Fixer, Message, PossibleFixes}; fn insert_at_end() -> OxcDiagnostic { @@ -480,23 +482,51 @@ mod test { } const TEST_CODE: &str = "var answer = 6 * 7;"; - const INSERT_AT_END: Fix = - Fix { span: Span::new(19, 19), content: Cow::Borrowed("// end"), message: None }; - const INSERT_AT_START: Fix = - Fix { span: Span::new(0, 0), content: Cow::Borrowed("// start"), message: None }; - const INSERT_AT_MIDDLE: Fix = - Fix { span: Span::new(13, 13), content: Cow::Borrowed("5 *"), message: None }; - const REPLACE_ID: Fix = - Fix { span: Span::new(4, 10), content: Cow::Borrowed("foo"), message: None }; - const REPLACE_VAR: Fix = - Fix { span: Span::new(0, 3), content: Cow::Borrowed("let"), message: None }; - const REPLACE_NUM: Fix = - Fix { span: Span::new(13, 14), content: Cow::Borrowed("5"), message: None }; + const INSERT_AT_END: Fix = Fix { + span: Span::new(19, 19), + content: Cow::Borrowed("// end"), + message: None, + kind: FixKind::None, + }; + const INSERT_AT_START: Fix = Fix { + span: Span::new(0, 0), + content: Cow::Borrowed("// start"), + message: None, + kind: FixKind::None, + }; + const INSERT_AT_MIDDLE: Fix = Fix { + span: Span::new(13, 13), + content: Cow::Borrowed("5 *"), + message: None, + kind: FixKind::None, + }; + const REPLACE_ID: Fix = Fix { + span: Span::new(4, 10), + content: Cow::Borrowed("foo"), + message: None, + kind: FixKind::None, + }; + const REPLACE_VAR: Fix = Fix { + span: Span::new(0, 3), + content: Cow::Borrowed("let"), + message: None, + kind: FixKind::None, + }; + const REPLACE_NUM: Fix = Fix { + span: Span::new(13, 14), + content: Cow::Borrowed("5"), + message: None, + kind: FixKind::None, + }; const REMOVE_START: Fix = Fix::delete(Span::new(0, 4)); const REMOVE_MIDDLE: Fix = Fix::delete(Span::new(5, 10)); const REMOVE_END: Fix = Fix::delete(Span::new(14, 18)); - const REVERSE_RANGE: Fix = - Fix { span: Span::new(3, 0), content: Cow::Borrowed(" "), message: None }; + const REVERSE_RANGE: Fix = Fix { + span: Span::new(3, 0), + content: Cow::Borrowed(" "), + message: None, + kind: FixKind::None, + }; fn get_fix_result(messages: Vec) -> FixResult<'static> { Fixer::new(TEST_CODE, messages, Some(SourceType::default())).fix() diff --git a/crates/oxc_linter/src/lib.rs b/crates/oxc_linter/src/lib.rs index 9b3b790ac1b14..598369ae5b806 100644 --- a/crates/oxc_linter/src/lib.rs +++ b/crates/oxc_linter/src/lib.rs @@ -649,7 +649,7 @@ impl Linter { // That's possible if UTF-16 offset points to middle of a surrogate pair. let mut span = Span::new(fix.range[0], fix.range[1]); span_converter.convert_span_back(&mut span); - Fix::new(fix.text, span) + Fix::new(fix.text, span).with_kind(FixKind::Fix) }); if is_single { diff --git a/crates/oxc_linter/src/tsgolint.rs b/crates/oxc_linter/src/tsgolint.rs index a6414f48aff17..62e0a4f25c935 100644 --- a/crates/oxc_linter/src/tsgolint.rs +++ b/crates/oxc_linter/src/tsgolint.rs @@ -743,6 +743,7 @@ impl Message { content: Cow::Owned(fix.text), span: Span::new(fix.range.pos, fix.range.end), message: None, + kind: crate::fixer::FixKind::Fix, }) .collect(); @@ -768,6 +769,7 @@ impl Message { content: Cow::Owned(fix.text), span: Span::new(fix.range.pos, fix.range.end), message: Some(Cow::Owned(message)), + kind: crate::fixer::FixKind::Suggestion, } }) .collect(); @@ -1278,6 +1280,7 @@ mod test { content: "fixedhello".into(), span: Span::new(0, 10), message: None, + kind: crate::fixer::FixKind::Fix }) ); } @@ -1326,11 +1329,13 @@ mod test { content: "hello".into(), span: Span::new(0, 5), message: Some("Suggestion 1".into()), + kind: crate::fixer::FixKind::Suggestion }, crate::fixer::Fix { content: "helloworld".into(), span: Span::new(0, 10), message: Some("Suggestion 2".into()), + kind: crate::fixer::FixKind::Suggestion }, ]) ); @@ -1364,11 +1369,17 @@ mod test { assert_eq!( message.fixes, PossibleFixes::Multiple(vec![ - crate::fixer::Fix { content: "fixed".into(), span: Span::new(0, 5), message: None }, + crate::fixer::Fix { + content: "fixed".into(), + span: Span::new(0, 5), + message: None, + kind: crate::fixer::FixKind::Fix + }, crate::fixer::Fix { content: "Suggestion 1".into(), span: Span::new(0, 5), message: Some("Suggestion 1".into()), + kind: crate::fixer::FixKind::Suggestion, }, ]) );