From 6440cde67502bdc72020a1b8992b5445b7b490f3 Mon Sep 17 00:00:00 2001 From: Sysix <3897725+Sysix@users.noreply.github.com> Date: Mon, 13 Oct 2025 14:45:50 +0000 Subject: [PATCH] refactor(linter): remove lifetime of `Message` (#14481) closes https://github.com/oxc-project/backlog/issues/175 Some fixes are references to the `source_text`, removed that reference to hold its own fix source text. So the language server / tester does not need to clone the `Message` into the own allocator. Tried to use `.to_owned()` where possible, some needed `.to_string()`, or `into_source_text()` (for codegen). --- .../src/linter/error_with_position.rs | 4 +- .../src/linter/isolated_lint_handler.rs | 2 +- crates/oxc_linter/src/context/host.rs | 12 +- crates/oxc_linter/src/context/mod.rs | 22 +-- crates/oxc_linter/src/disable_directives.rs | 2 +- crates/oxc_linter/src/fixer/fix.rs | 146 +++++++----------- crates/oxc_linter/src/fixer/mod.rs | 80 ++++------ crates/oxc_linter/src/lib.rs | 4 +- crates/oxc_linter/src/rules/eslint/curly.rs | 2 +- crates/oxc_linter/src/rules/eslint/eqeqeq.rs | 6 +- .../src/rules/eslint/for_direction.rs | 2 +- .../oxc_linter/src/rules/eslint/func_names.rs | 6 +- .../oxc_linter/src/rules/eslint/no_console.rs | 2 +- .../src/rules/eslint/no_new_wrappers.rs | 2 +- .../no_unused_vars/fixers/fix_imports.rs | 2 +- .../no_unused_vars/fixers/fix_symbol.rs | 6 +- .../eslint/no_unused_vars/fixers/fix_vars.rs | 2 +- .../rules/jest/prefer_lowercase_title/mod.rs | 2 +- .../src/rules/jsx_a11y/anchor_has_content.rs | 2 +- .../oxc_linter/src/rules/oxc/no_map_spread.rs | 2 +- .../src/rules/react/exhaustive_deps.rs | 4 +- .../rules/react/jsx_curly_brace_presence.rs | 12 +- .../rules/react/jsx_no_useless_fragment.rs | 9 +- .../rules/react/jsx_props_no_spread_multi.rs | 2 +- .../src/rules/typescript/ban_ts_comment.rs | 3 +- .../typescript/consistent_type_imports.rs | 43 +++--- .../typescript/no_wrapper_object_types.rs | 2 +- .../unicorn/no_await_expression_member.rs | 2 +- .../src/rules/unicorn/no_console_spaces.rs | 2 +- .../oxc_linter/src/rules/unicorn/no_null.rs | 10 +- .../no_single_promise_in_promise_methods.rs | 2 +- .../src/rules/unicorn/no_unnecessary_await.rs | 4 +- .../no_useless_promise_resolve_reject.rs | 2 +- .../rules/unicorn/no_useless_spread/mod.rs | 10 +- .../oxc_linter/src/rules/unicorn/prefer_at.rs | 6 +- .../src/rules/unicorn/prefer_class_fields.rs | 6 +- .../rules/unicorn/prefer_classlist_toggle.rs | 6 +- .../src/rules/unicorn/prefer_regexp_test.rs | 14 +- .../src/rules/unicorn/prefer_spread.rs | 2 +- .../unicorn/prefer_string_starts_ends_with.rs | 4 +- .../rules/unicorn/prefer_structured_clone.rs | 8 +- .../unicorn/require_module_specifiers.rs | 7 +- .../src/rules/unicorn/switch_case_braces.rs | 2 +- crates/oxc_linter/src/service/mod.rs | 11 +- crates/oxc_linter/src/service/runtime.rs | 97 +----------- crates/oxc_linter/src/tsgolint.rs | 6 +- 46 files changed, 223 insertions(+), 361 deletions(-) diff --git a/crates/oxc_language_server/src/linter/error_with_position.rs b/crates/oxc_language_server/src/linter/error_with_position.rs index 26f022ef7af80..231a4a86fbf85 100644 --- a/crates/oxc_language_server/src/linter/error_with_position.rs +++ b/crates/oxc_language_server/src/linter/error_with_position.rs @@ -35,7 +35,7 @@ pub enum PossibleFixContent { // we assume that the fix offset will not exceed 2GB in either direction #[expect(clippy::cast_possible_truncation)] pub fn message_to_lsp_diagnostic( - message: &Message<'_>, + message: &Message, uri: &Uri, source_text: &str, rope: &Rope, @@ -134,7 +134,7 @@ pub fn message_to_lsp_diagnostic( DiagnosticReport { diagnostic, fixed_content } } -fn fix_to_fixed_content(fix: &Fix<'_>, rope: &Rope, source_text: &str) -> FixedContent { +fn fix_to_fixed_content(fix: &Fix, rope: &Rope, source_text: &str) -> FixedContent { let start_position = offset_to_position(rope, fix.span.start, source_text); let end_position = offset_to_position(rope, fix.span.end, source_text); diff --git a/crates/oxc_language_server/src/linter/isolated_lint_handler.rs b/crates/oxc_language_server/src/linter/isolated_lint_handler.rs index 932323ca448e6..0387b353d2a33 100644 --- a/crates/oxc_language_server/src/linter/isolated_lint_handler.rs +++ b/crates/oxc_language_server/src/linter/isolated_lint_handler.rs @@ -145,7 +145,7 @@ impl IsolatedLintHandler { &self, directives: &DisableDirectives, severity: AllowWarnDeny, - ) -> Vec> { + ) -> Vec { let diagnostics = create_unused_directives_diagnostics(directives, severity); diagnostics .into_iter() diff --git a/crates/oxc_linter/src/context/host.rs b/crates/oxc_linter/src/context/host.rs index 76433f3b6c972..f9770f5c79df5 100644 --- a/crates/oxc_linter/src/context/host.rs +++ b/crates/oxc_linter/src/context/host.rs @@ -132,7 +132,7 @@ pub struct ContextHost<'a> { /// Diagnostics reported by the linter. /// /// Contains diagnostics for all rules across a single file. - diagnostics: RefCell>>, + diagnostics: RefCell>, /// Whether or not to apply code fixes during linting. Defaults to /// [`FixKind::None`] (no fixing). /// @@ -241,7 +241,7 @@ impl<'a> ContextHost<'a> { /// Add a diagnostic message to the end of the list of diagnostics. Can be used /// by any rule to report issues. #[inline] - pub(crate) fn push_diagnostic(&self, mut diagnostic: Message<'a>) { + pub(crate) fn push_diagnostic(&self, mut diagnostic: Message) { if self.current_sub_host().source_text_offset != 0 { diagnostic.move_offset(self.current_sub_host().source_text_offset); } @@ -249,7 +249,7 @@ impl<'a> ContextHost<'a> { } // Append a list of diagnostics. Only used in report_unused_directives. - fn append_diagnostics(&self, mut diagnostics: Vec>) { + fn append_diagnostics(&self, mut diagnostics: Vec) { if self.current_sub_host().source_text_offset != 0 { let offset = self.current_sub_host().source_text_offset; for diagnostic in &mut diagnostics { @@ -345,7 +345,7 @@ impl<'a> ContextHost<'a> { } /// Take ownership of all diagnostics collected during linting. - pub fn take_diagnostics(&self) -> Vec> { + pub fn take_diagnostics(&self) -> Vec { // NOTE: diagnostics are only ever borrowed here and in push_diagnostic, append_diagnostics. // The latter drops the reference as soon as the function returns, so // this should never panic. @@ -368,7 +368,7 @@ impl<'a> ContextHost<'a> { } #[cfg(debug_assertions)] - pub fn get_diagnostics(&self, cb: impl FnOnce(&mut Vec>)) { + pub fn get_diagnostics(&self, cb: impl FnOnce(&mut Vec)) { cb(self.diagnostics.borrow_mut().as_mut()); } @@ -450,7 +450,7 @@ impl<'a> ContextHost<'a> { } } -impl<'a> From> for Vec> { +impl<'a> From> for Vec { fn from(ctx_host: ContextHost<'a>) -> Self { ctx_host.diagnostics.into_inner() } diff --git a/crates/oxc_linter/src/context/mod.rs b/crates/oxc_linter/src/context/mod.rs index c4235396b80a4..a1a6a97f7f3da 100644 --- a/crates/oxc_linter/src/context/mod.rs +++ b/crates/oxc_linter/src/context/mod.rs @@ -222,7 +222,7 @@ impl<'a> LintContext<'a> { /// Add a diagnostic message to the list of diagnostics. Outputs a diagnostic with the current rule /// name, severity, and a link to the rule's documentation URL. - fn add_diagnostic(&self, mut message: Message<'a>) { + fn add_diagnostic(&self, mut message: Message) { if self.parent.disable_directives().contains(self.current_rule_name, message.span()) { return; } @@ -272,7 +272,7 @@ impl<'a> LintContext<'a> { #[inline] pub fn diagnostic_with_fix(&self, diagnostic: OxcDiagnostic, fix: F) where - C: Into>, + C: Into, F: FnOnce(RuleFixer<'_, 'a>) -> C, { self.diagnostic_with_fix_of_kind(diagnostic, FixKind::SafeFix, fix); @@ -293,7 +293,7 @@ impl<'a> LintContext<'a> { #[inline] pub fn diagnostic_with_suggestion(&self, diagnostic: OxcDiagnostic, fix: F) where - C: Into>, + C: Into, F: FnOnce(RuleFixer<'_, 'a>) -> C, { self.diagnostic_with_fix_of_kind(diagnostic, FixKind::Suggestion, fix); @@ -314,7 +314,7 @@ impl<'a> LintContext<'a> { #[inline] pub fn diagnostic_with_dangerous_suggestion(&self, diagnostic: OxcDiagnostic, fix: F) where - C: Into>, + C: Into, F: FnOnce(RuleFixer<'_, 'a>) -> C, { self.diagnostic_with_fix_of_kind(diagnostic, FixKind::DangerousSuggestion, fix); @@ -342,7 +342,7 @@ impl<'a> LintContext<'a> { #[inline] pub fn diagnostic_with_dangerous_fix(&self, diagnostic: OxcDiagnostic, fix: F) where - C: Into>, + C: Into, F: FnOnce(RuleFixer<'_, 'a>) -> C, { self.diagnostic_with_fix_of_kind(diagnostic, FixKind::DangerousFix, fix); @@ -360,7 +360,7 @@ impl<'a> LintContext<'a> { fix_kind: FixKind, fix: F, ) where - C: Into>, + C: Into, F: FnOnce(RuleFixer<'_, 'a>) -> C, { let (diagnostic, fix) = self.create_fix(fix_kind, fix, diagnostic); @@ -386,11 +386,11 @@ impl<'a> LintContext<'a> { fix_one: (FixKind, F1), fix_two: (FixKind, F2), ) where - C: Into>, + C: Into, F1: FnOnce(RuleFixer<'_, 'a>) -> C, F2: FnOnce(RuleFixer<'_, 'a>) -> C, { - let fixes_result: Vec> = vec![ + 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, ] @@ -417,13 +417,13 @@ impl<'a> LintContext<'a> { fix_kind: FixKind, fix: F, diagnostic: OxcDiagnostic, - ) -> (OxcDiagnostic, Option>) + ) -> (OxcDiagnostic, Option) where - C: Into>, + C: Into, F: FnOnce(RuleFixer<'_, 'a>) -> C, { let fixer = RuleFixer::new(fix_kind, self); - let rule_fix: RuleFix<'a> = fix(fixer).into(); + let rule_fix: RuleFix = fix(fixer).into(); #[cfg(debug_assertions)] debug_assert!( self.current_rule_fix_capabilities.supports_fix(fix_kind), diff --git a/crates/oxc_linter/src/disable_directives.rs b/crates/oxc_linter/src/disable_directives.rs index 546cfc7d5f343..b47334fae18c8 100644 --- a/crates/oxc_linter/src/disable_directives.rs +++ b/crates/oxc_linter/src/disable_directives.rs @@ -40,7 +40,7 @@ pub struct RuleCommentRule { impl RuleCommentRule { #[expect(clippy::cast_possible_truncation)] // for `as u32` - pub fn create_fix<'a>(&self, source_text: &'a str, comment_span: Span) -> Fix<'a> { + pub fn create_fix(&self, source_text: &str, comment_span: Span) -> Fix { let before_source = &source_text[comment_span.start as usize..self.name_span.start as usize]; diff --git a/crates/oxc_linter/src/fixer/fix.rs b/crates/oxc_linter/src/fixer/fix.rs index a69afdfb337c3..b874e68d60000 100644 --- a/crates/oxc_linter/src/fixer/fix.rs +++ b/crates/oxc_linter/src/fixer/fix.rs @@ -6,7 +6,6 @@ use std::{ use bitflags::bitflags; -use oxc_allocator::{Allocator, CloneIn}; use oxc_span::{GetSpan, SPAN, Span}; bitflags! { @@ -123,20 +122,20 @@ impl FixKind { // TODO: rename #[derive(Debug, Default)] #[must_use = "Fixes must be used. If you don't need a fix, use `LintContext::diagnostic`, or create an empty fix using `RuleFixer::noop`."] -pub struct RuleFix<'a> { +pub struct RuleFix { kind: FixKind, /// A suggestion message. Will be shown in editors via code actions. - message: Option>, + message: Option>, /// The actual that will be applied to the source code. /// /// See: [`Fix`] - fix: CompositeFix<'a>, + fix: CompositeFix, } macro_rules! impl_from { ($($ty:ty),*) => { $( - impl<'a> From<$ty> for RuleFix<'a> { + impl From<$ty> for RuleFix { fn from(fix: $ty) -> Self { Self { kind: FixKind::SafeFix, message: None, fix: fix.into() } } @@ -146,11 +145,11 @@ macro_rules! impl_from { } // I'd like to use // impl<'a, F: Into>> From for RuleFix<'a> b -// but this breaks when implementing `From> for CompositeFix<'a>`. -impl_from!(CompositeFix<'a>, Fix<'a>, Option>, Vec>); +// but this breaks when implementing `From for CompositeFix`. +impl_from!(CompositeFix, Fix, Option, Vec); -impl<'a> FromIterator> for RuleFix<'a> { - fn from_iter>>(iter: T) -> Self { +impl FromIterator for RuleFix { + fn from_iter>(iter: T) -> Self { Self { kind: FixKind::SafeFix, message: None, @@ -159,34 +158,38 @@ impl<'a> FromIterator> for RuleFix<'a> { } } -impl<'a> From> for CompositeFix<'a> { +impl From for CompositeFix { #[inline] - fn from(val: RuleFix<'a>) -> Self { + fn from(val: RuleFix) -> Self { val.fix } } -impl<'a> RuleFix<'a> { +impl RuleFix { #[inline] - pub(super) fn new(kind: FixKind, message: Option>, fix: CompositeFix<'a>) -> Self { + pub(super) fn new( + kind: FixKind, + message: Option>, + fix: CompositeFix, + ) -> Self { Self { kind, message, fix } } /// Create a new safe fix. #[inline] - pub fn fix(fix: CompositeFix<'a>) -> Self { + pub fn fix(fix: CompositeFix) -> Self { Self { kind: FixKind::Fix, message: None, fix } } /// Create a new suggestion #[inline] - pub const fn suggestion(fix: CompositeFix<'a>, message: Cow<'a, str>) -> Self { + pub const fn suggestion(fix: CompositeFix, message: Cow<'static, str>) -> Self { Self { kind: FixKind::Suggestion, message: Some(message), fix } } /// Create a dangerous fix. #[inline] - pub fn dangerous(fix: CompositeFix<'a>) -> Self { + pub fn dangerous(fix: CompositeFix) -> Self { Self { kind: FixKind::DangerousFix, message: None, fix } } @@ -225,7 +228,7 @@ impl<'a> RuleFix<'a> { } #[inline] - pub fn with_message>>(mut self, message: S) -> Self { + pub fn with_message>>(mut self, message: S) -> Self { self.message = Some(message.into()); self } @@ -241,7 +244,7 @@ impl<'a> RuleFix<'a> { } #[inline] - pub fn into_fix(self, source_text: &str) -> Fix<'a> { + pub fn into_fix(self, source_text: &str) -> Fix { // If there is only one fix, use the message from that fix. let message = match &self.fix { CompositeFix::Single(fix) if fix.message.as_ref().is_some_and(|m| !m.is_empty()) => { @@ -255,25 +258,25 @@ impl<'a> RuleFix<'a> { } #[inline] - pub fn extend>>(mut self, fix: F) -> Self { + pub fn extend>(mut self, fix: F) -> Self { self.fix = self.fix.concat(fix.into()); self } #[inline] - pub fn push>>(&mut self, fix: F) { + pub fn push>(&mut self, fix: F) { self.fix.push(fix.into()); } } -impl GetSpan for RuleFix<'_> { +impl GetSpan for RuleFix { fn span(&self) -> Span { self.fix.span() } } -impl<'a> Deref for RuleFix<'a> { - type Target = CompositeFix<'a>; +impl Deref for RuleFix { + type Target = CompositeFix; fn deref(&self) -> &Self::Target { &self.fix @@ -285,44 +288,26 @@ impl<'a> Deref for RuleFix<'a> { /// Used internally by this module. Lint rules should use `RuleFix`. #[derive(Debug, Clone, PartialEq, Eq)] #[non_exhaustive] -pub struct Fix<'a> { - pub content: Cow<'a, str>, +pub struct Fix { + pub content: Cow<'static, str>, /// A brief suggestion message describing the fix. Will be shown in /// editors via code actions. - pub message: Option>, + pub message: Option>, pub span: Span, } -impl<'new> CloneIn<'new> for Fix<'_> { - type Cloned = Fix<'new>; - - fn clone_in(&self, allocator: &'new Allocator) -> Self::Cloned { - Fix { - content: match &self.content { - Cow::Borrowed(s) => Cow::Borrowed(allocator.alloc_str(s)), - Cow::Owned(s) => Cow::Owned(s.clone()), - }, - span: self.span, - message: self.message.as_ref().map(|s| match s { - Cow::Borrowed(s) => Cow::Borrowed(allocator.alloc_str(s)), - Cow::Owned(s) => Cow::Owned(s.clone()), - }), - } - } -} - -impl Default for Fix<'_> { +impl Default for Fix { fn default() -> Self { Self::empty() } } -impl<'a> Fix<'a> { +impl Fix { pub const fn delete(span: Span) -> Self { Self { content: Cow::Borrowed(""), message: None, span } } - pub fn new>>(content: T, span: Span) -> Self { + pub fn new>>(content: T, span: Span) -> Self { Self { content: content.into(), message: None, span } } @@ -333,35 +318,20 @@ impl<'a> Fix<'a> { } #[must_use] - pub fn with_message(mut self, message: impl Into>) -> Self { + pub fn with_message(mut self, message: impl Into>) -> Self { self.message = Some(message.into()); self } } #[derive(Debug, Clone, PartialEq, Eq)] -pub enum PossibleFixes<'a> { +pub enum PossibleFixes { None, - Single(Fix<'a>), - Multiple(Vec>), -} - -impl<'new> CloneIn<'new> for PossibleFixes<'_> { - type Cloned = PossibleFixes<'new>; - - fn clone_in(&self, allocator: &'new Allocator) -> Self::Cloned { - match self { - Self::None => PossibleFixes::None, - Self::Single(fix) => PossibleFixes::Single(fix.clone_in(allocator)), - Self::Multiple(fixes) => { - //ToDo: what about the vec? - PossibleFixes::Multiple(fixes.iter().map(|fix| fix.clone_in(allocator)).collect()) - } - } - } + Single(Fix), + Multiple(Vec), } -impl PossibleFixes<'_> { +impl PossibleFixes { /// Gets the number of [`Fix`]es contained in this [`PossibleFixes`]. pub fn len(&self) -> usize { match self { @@ -392,23 +362,23 @@ impl PossibleFixes<'_> { // resulting struct size was larger (40 bytes vs 32). So, we're sticking with // this (at least for now). #[derive(Debug, Default)] -pub enum CompositeFix<'a> { +pub enum CompositeFix { /// No fixes #[default] None, - Single(Fix<'a>), + Single(Fix), /// Several fixes that will be merged into one, in order. - Multiple(Vec>), + Multiple(Vec), } -impl<'a> From> for CompositeFix<'a> { - fn from(fix: Fix<'a>) -> Self { +impl From for CompositeFix { + fn from(fix: Fix) -> Self { CompositeFix::Single(fix) } } -impl<'a> From>> for CompositeFix<'a> { - fn from(fix: Option>) -> Self { +impl From> for CompositeFix { + fn from(fix: Option) -> Self { match fix { Some(fix) => CompositeFix::Single(fix), None => CompositeFix::None, @@ -416,8 +386,8 @@ impl<'a> From>> for CompositeFix<'a> { } } -impl<'a> From>> for CompositeFix<'a> { - fn from(mut fixes: Vec>) -> Self { +impl From> for CompositeFix { + fn from(mut fixes: Vec) -> Self { match fixes.len() { 0 => CompositeFix::None, // fixes[0] doesn't correctly move the vec's entry @@ -427,13 +397,13 @@ impl<'a> From>> for CompositeFix<'a> { } } -impl<'a> From>> for CompositeFix<'a> { +impl From> for CompositeFix { fn from(fixes: Vec) -> Self { fixes.into_iter().reduce(Self::concat).unwrap_or_default() } } -impl GetSpan for CompositeFix<'_> { +impl GetSpan for CompositeFix { fn span(&self) -> Span { match self { CompositeFix::Single(fix) => fix.span, @@ -445,8 +415,8 @@ impl GetSpan for CompositeFix<'_> { } } -impl<'a> CompositeFix<'a> { - pub fn push(&mut self, fix: CompositeFix<'a>) { +impl CompositeFix { + pub fn push(&mut self, fix: CompositeFix) { match self { Self::None => *self = fix, Self::Single(fix1) => match fix { @@ -471,7 +441,7 @@ impl<'a> CompositeFix<'a> { #[cold] #[must_use] - pub fn concat(self, fix: CompositeFix<'a>) -> Self { + pub fn concat(self, fix: CompositeFix) -> Self { match (self, fix) { (Self::None, f) | (f, Self::None) => f, (Self::Single(fix1), Self::Single(fix2)) => Self::Multiple(vec![fix1, fix2]), @@ -522,7 +492,7 @@ impl<'a> CompositeFix<'a> { /// Gets one fix from the fixes. If we retrieve multiple fixes, this merges those into one. /// - pub fn normalize_fixes(self, source_text: &str) -> Fix<'a> { + pub fn normalize_fixes(self, source_text: &str) -> Fix { match self { CompositeFix::Single(fix) => fix, CompositeFix::Multiple(fixes) => Self::merge_fixes(fixes, source_text), @@ -542,7 +512,7 @@ impl<'a> CompositeFix<'a> { /// /// # Panics /// In debug mode, panics if merging fails. - pub fn merge_fixes(fixes: Vec>, source_text: &str) -> Fix<'a> { + pub fn merge_fixes(fixes: Vec, source_text: &str) -> Fix { Self::merge_fixes_fallible(fixes, source_text).unwrap_or_else(|err| { debug_assert!(false, "{err}"); Fix::empty() @@ -558,9 +528,9 @@ impl<'a> CompositeFix<'a> { /// * Negative ranges (`span.start` > `span.end`). /// * Ranges are out of bounds of `source_text`. pub fn merge_fixes_fallible( - fixes: Vec>, + fixes: Vec, source_text: &str, - ) -> Result, MergeFixesError> { + ) -> Result { let mut fixes = fixes; if fixes.is_empty() { // Do nothing @@ -639,7 +609,7 @@ impl Display for MergeFixesError { mod test { use super::*; - impl Clone for CompositeFix<'_> { + impl Clone for CompositeFix { fn clone(&self) -> Self { match self { Self::None => Self::None, @@ -649,7 +619,7 @@ mod test { } } - impl PartialEq for CompositeFix<'_> { + impl PartialEq for CompositeFix { fn eq(&self, other: &Self) -> bool { match self { Self::None => matches!(other, CompositeFix::None), @@ -738,7 +708,7 @@ mod test { let mut f = single(); f.push(vec![f2.clone(), f3.clone()].into()); - assert_eq!(f, CompositeFix::Multiple(vec![f1.clone(), f2.clone(), f3.clone()])); + assert_eq!(f, CompositeFix::Multiple(vec![f1, f2, f3])); } #[test] diff --git a/crates/oxc_linter/src/fixer/mod.rs b/crates/oxc_linter/src/fixer/mod.rs index 20cf07c80123a..d0c567770817f 100644 --- a/crates/oxc_linter/src/fixer/mod.rs +++ b/crates/oxc_linter/src/fixer/mod.rs @@ -8,7 +8,6 @@ use crate::LintContext; mod fix; pub use fix::{CompositeFix, Fix, FixKind, PossibleFixes, RuleFix}; -use oxc_allocator::{Allocator, CloneIn}; /// Produces [`RuleFix`] instances. Inspired by ESLint's [`RuleFixer`]. /// @@ -55,12 +54,12 @@ impl<'c, 'a: 'c> RuleFixer<'c, 'a> { // NOTE(@DonIsaac): Internal methods shouldn't use `T: Into` generics to optimize binary // size. Only use such generics in public APIs. - fn new_fix(&self, fix: CompositeFix<'a>, message: Option>) -> RuleFix<'a> { + fn new_fix(&self, fix: CompositeFix, message: Option>) -> RuleFix { RuleFix::new(self.kind, message, fix) } /// Create a new [`RuleFix`] with pre-allocated memory for multiple fixes. - pub fn new_fix_with_capacity(&self, capacity: usize) -> RuleFix<'a> { + pub fn new_fix_with_capacity(&self, capacity: usize) -> RuleFix { RuleFix::new(self.kind, None, CompositeFix::Multiple(Vec::with_capacity(capacity))) } @@ -79,12 +78,12 @@ impl<'c, 'a: 'c> RuleFixer<'c, 'a> { /// Create a [`RuleFix`] that deletes the text covered by the given [`Span`] /// or AST node. #[inline] - pub fn delete(&self, spanned: &S) -> RuleFix<'a> { + pub fn delete(&self, spanned: &S) -> RuleFix { self.delete_range(spanned.span()) } /// Delete text covered by a [`Span`] - pub fn delete_range(&self, span: Span) -> RuleFix<'a> { + pub fn delete_range(&self, span: Span) -> RuleFix { self.new_fix( CompositeFix::Single(Fix::delete(span)), self.auto_message.then_some(Cow::Borrowed("Delete this code.")), @@ -92,11 +91,11 @@ impl<'c, 'a: 'c> RuleFixer<'c, 'a> { } /// Replace a `target` AST node with the source code of a `replacement` node.. - pub fn replace_with(&self, target: &T, replacement: &S) -> RuleFix<'a> { + pub fn replace_with(&self, target: &T, replacement: &S) -> RuleFix { // use an inner function to avoid megamorphic bloat - fn inner<'a>(fixer: &RuleFixer<'_, 'a>, target: Span, replacement: Span) -> RuleFix<'a> { + fn inner(fixer: &RuleFixer<'_, '_>, target: Span, replacement: Span) -> RuleFix { let replacement_text = fixer.ctx.source_range(replacement); - let fix = Fix::new(replacement_text, target); + let fix = Fix::new(Cow::Owned(replacement_text.to_string()), target); let message = fixer.auto_message.then(|| { let target_text = fixer.possibly_truncate_range(target); let borrowed_replacement = Cow::Borrowed(replacement_text); @@ -110,13 +109,13 @@ impl<'c, 'a: 'c> RuleFixer<'c, 'a> { } /// Replace a `target` AST node with a `replacement` string. - pub fn replace>>(&self, target: Span, replacement: S) -> RuleFix<'a> { + pub fn replace>>(&self, target: Span, replacement: S) -> RuleFix { // use an inner function to avoid megamorphic bloat - fn inner<'a>( - fixer: &RuleFixer<'_, 'a>, + fn inner( + fixer: &RuleFixer<'_, '_>, target: Span, - replacement: Cow<'a, str>, - ) -> RuleFix<'a> { + replacement: Cow<'static, str>, + ) -> RuleFix { let fix = Fix::new(replacement, target); let target_text = fixer.possibly_truncate_range(target); let content = fixer.possibly_truncate_snippet(&fix.content); @@ -131,46 +130,46 @@ impl<'c, 'a: 'c> RuleFixer<'c, 'a> { /// Creates a fix command that inserts text before the given node. #[inline] - pub fn insert_text_before>>( + pub fn insert_text_before>>( &self, target: &T, text: S, - ) -> RuleFix<'a> { + ) -> RuleFix { self.insert_text_at(target.span().start, text.into()) } /// Creates a fix command that inserts text before the specified range in the source text. #[inline] - pub fn insert_text_before_range>>( + pub fn insert_text_before_range>>( &self, span: Span, text: S, - ) -> RuleFix<'a> { + ) -> RuleFix { self.insert_text_at(span.start, text.into()) } /// Creates a fix command that inserts text after the given node. #[inline] - pub fn insert_text_after>>( + pub fn insert_text_after>>( &self, target: &T, text: S, - ) -> RuleFix<'a> { + ) -> RuleFix { self.insert_text_at(target.span().end, text.into()) } /// Creates a fix command that inserts text after the specified range in the source text. #[inline] - pub fn insert_text_after_range>>( + pub fn insert_text_after_range>>( &self, span: Span, text: S, - ) -> RuleFix<'a> { + ) -> RuleFix { self.insert_text_at(span.end, text.into()) } /// Creates a fix command that inserts text at the specified index in the source text. - fn insert_text_at(&self, index: u32, text: Cow<'a, str>) -> RuleFix<'a> { + fn insert_text_at(&self, index: u32, text: Cow<'static, str>) -> RuleFix { let fix = Fix::new(text, Span::empty(index)); let content = self.possibly_truncate_snippet(&fix.content); let message = self.auto_message.then(|| Cow::Owned(format!("Insert `{content}`"))); @@ -184,7 +183,7 @@ impl<'c, 'a: 'c> RuleFixer<'c, 'a> { .with_options(CodegenOptions { single_quote: true, ..CodegenOptions::default() }) } - pub fn noop(&self) -> RuleFix<'a> { + pub fn noop(&self) -> RuleFix { self.new_fix(CompositeFix::None, None) } @@ -215,37 +214,22 @@ impl<'c, 'a: 'c> RuleFixer<'c, 'a> { pub struct FixResult<'a> { pub fixed: bool, pub fixed_code: Cow<'a, str>, - pub messages: Vec>, + pub messages: Vec, } #[derive(Debug, Clone, Eq, PartialEq)] -pub struct Message<'a> { +pub struct Message { pub error: OxcDiagnostic, - pub fixes: PossibleFixes<'a>, + pub fixes: PossibleFixes, span: Span, fixed: bool, #[cfg(feature = "language_server")] pub section_offset: u32, } -impl<'new> CloneIn<'new> for Message<'_> { - type Cloned = Message<'new>; - - fn clone_in(&self, allocator: &'new Allocator) -> Self::Cloned { - Message { - error: self.error.clone(), - fixes: self.fixes.clone_in(allocator), - span: self.span, - fixed: self.fixed, - #[cfg(feature = "language_server")] - section_offset: self.section_offset, - } - } -} - -impl<'a> Message<'a> { +impl Message { #[expect(clippy::cast_possible_truncation)] // for `as u32` - pub fn new(error: OxcDiagnostic, fixes: PossibleFixes<'a>) -> Self { + pub fn new(error: OxcDiagnostic, fixes: PossibleFixes) -> Self { let span = error .labels .as_ref() @@ -298,14 +282,14 @@ impl<'a> Message<'a> { } } -impl From> for OxcDiagnostic { +impl From for OxcDiagnostic { #[inline] fn from(message: Message) -> Self { message.error } } -impl GetSpan for Message<'_> { +impl GetSpan for Message { #[inline] fn span(&self) -> Span { self.span @@ -316,7 +300,7 @@ impl GetSpan for Message<'_> { /// Note that our parser has handled the BOM, so we don't need to port the BOM test cases from `ESLint`. pub struct Fixer<'a> { source_text: &'a str, - messages: Vec>, + messages: Vec, // To test different fixes, we need to override the default behavior. // The behavior is oriented by `oxlint` where only one PossibleFixes is applied. @@ -324,7 +308,7 @@ pub struct Fixer<'a> { } impl<'a> Fixer<'a> { - pub fn new(source_text: &'a str, messages: Vec>) -> Self { + pub fn new(source_text: &'a str, messages: Vec) -> Self { Self { source_text, messages, fix_index: 0 } } @@ -473,7 +457,7 @@ mod test { const REVERSE_RANGE: Fix = Fix { span: Span::new(3, 0), content: Cow::Borrowed(" "), message: None }; - fn get_fix_result(messages: Vec) -> FixResult { + fn get_fix_result(messages: Vec) -> FixResult<'static> { Fixer::new(TEST_CODE, messages).fix() } diff --git a/crates/oxc_linter/src/lib.rs b/crates/oxc_linter/src/lib.rs index fec1d195f3c7e..3bce2a8e6d50e 100644 --- a/crates/oxc_linter/src/lib.rs +++ b/crates/oxc_linter/src/lib.rs @@ -146,7 +146,7 @@ impl Linter { path: &Path, context_sub_hosts: Vec>, allocator: &'a Allocator, - ) -> Vec> { + ) -> Vec { self.run_with_disable_directives(path, context_sub_hosts, allocator).0 } @@ -159,7 +159,7 @@ impl Linter { path: &Path, context_sub_hosts: Vec>, allocator: &'a Allocator, - ) -> (Vec>, Option) { + ) -> (Vec, Option) { let ResolvedLinterState { rules, config, external_rules } = self.config.resolve(path); let mut ctx_host = Rc::new(ContextHost::new(path, context_sub_hosts, self.options, config)); diff --git a/crates/oxc_linter/src/rules/eslint/curly.rs b/crates/oxc_linter/src/rules/eslint/curly.rs index 9091c053c7a59..7e847279bcd3b 100644 --- a/crates/oxc_linter/src/rules/eslint/curly.rs +++ b/crates/oxc_linter/src/rules/eslint/curly.rs @@ -424,7 +424,7 @@ fn apply_rule_fix<'a>( body: &Statement<'a>, should_have_braces: bool, ctx: &LintContext<'a>, -) -> RuleFix<'a> { +) -> RuleFix { let source = ctx.source_range(body.span()); let fixed = if should_have_braces { diff --git a/crates/oxc_linter/src/rules/eslint/eqeqeq.rs b/crates/oxc_linter/src/rules/eslint/eqeqeq.rs index 19ece8b49be57..ab4e92e7115e2 100644 --- a/crates/oxc_linter/src/rules/eslint/eqeqeq.rs +++ b/crates/oxc_linter/src/rules/eslint/eqeqeq.rs @@ -296,9 +296,9 @@ fn get_operator_span(binary_expr: &BinaryExpression, operator: &str, ctx: &LintC fn apply_rule_fix<'a>( fixer: &RuleFixer<'_, 'a>, - binary_expr: &BinaryExpression, - preferred_operator_with_padding: &'a str, -) -> RuleFix<'a> { + binary_expr: &'a BinaryExpression, + preferred_operator_with_padding: &'static str, +) -> RuleFix { let span = Span::new(binary_expr.left.span().end, binary_expr.right.span().start); fixer.replace(span, preferred_operator_with_padding) diff --git a/crates/oxc_linter/src/rules/eslint/for_direction.rs b/crates/oxc_linter/src/rules/eslint/for_direction.rs index f1fca5053cbed..69debd1e161e6 100644 --- a/crates/oxc_linter/src/rules/eslint/for_direction.rs +++ b/crates/oxc_linter/src/rules/eslint/for_direction.rs @@ -202,7 +202,7 @@ fn get_fixer_replace_span(update: &Expression) -> Span { } } -fn apply_rule_fix<'a>(fixer: &RuleFixer<'_, 'a>, update: &Expression) -> RuleFix<'a> { +fn apply_rule_fix(fixer: &RuleFixer<'_, '_>, update: &Expression) -> RuleFix { let span = get_fixer_replace_span(update); let replacement = get_fixer_replace_operator(update); diff --git a/crates/oxc_linter/src/rules/eslint/func_names.rs b/crates/oxc_linter/src/rules/eslint/func_names.rs index 2ef9747c12d40..c31d889ea115c 100644 --- a/crates/oxc_linter/src/rules/eslint/func_names.rs +++ b/crates/oxc_linter/src/rules/eslint/func_names.rs @@ -431,12 +431,12 @@ fn can_safely_apply_fix(func: &Function, name: &str, ctx: &LintContext) -> bool }) } -fn apply_rule_fix<'a>( - fixer: &RuleFixer<'_, 'a>, +fn apply_rule_fix( + fixer: &RuleFixer<'_, '_>, is_safe_fix: bool, replace_span: Span, function_name: Option, -) -> RuleFix<'a> { +) -> RuleFix { if is_safe_fix && let Some(name) = function_name { return fixer.insert_text_after(&replace_span, format!(" {name}")); } diff --git a/crates/oxc_linter/src/rules/eslint/no_console.rs b/crates/oxc_linter/src/rules/eslint/no_console.rs index 963409f007184..30a202a1b9aa8 100644 --- a/crates/oxc_linter/src/rules/eslint/no_console.rs +++ b/crates/oxc_linter/src/rules/eslint/no_console.rs @@ -156,7 +156,7 @@ fn remove_console<'c, 'a: 'c>( fixer: RuleFixer<'c, 'a>, ctx: &'c LintContext<'a>, node: &AstNode<'a>, -) -> RuleFix<'a> { +) -> RuleFix { let mut node_to_delete = node; for parent in ctx.nodes().ancestors(node.id()) { match parent.kind() { diff --git a/crates/oxc_linter/src/rules/eslint/no_new_wrappers.rs b/crates/oxc_linter/src/rules/eslint/no_new_wrappers.rs index 1b556595ace61..c5c5ddd3ab7ac 100644 --- a/crates/oxc_linter/src/rules/eslint/no_new_wrappers.rs +++ b/crates/oxc_linter/src/rules/eslint/no_new_wrappers.rs @@ -104,7 +104,7 @@ fn remove_new_operator<'a>( fixer: RuleFixer<'_, 'a>, expr: &NewExpression<'a>, name: &'a str, -) -> RuleFix<'a> { +) -> RuleFix { debug_assert!(expr.callee.is_identifier_reference()); let remove_new_fix = fixer.delete_range(Span::new(expr.span.start, expr.callee.span().start)); diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_vars/fixers/fix_imports.rs b/crates/oxc_linter/src/rules/eslint/no_unused_vars/fixers/fix_imports.rs index b365fb9fdc53b..786e3f1d6525a 100644 --- a/crates/oxc_linter/src/rules/eslint/no_unused_vars/fixers/fix_imports.rs +++ b/crates/oxc_linter/src/rules/eslint/no_unused_vars/fixers/fix_imports.rs @@ -11,7 +11,7 @@ impl NoUnusedVars { fixer: RuleFixer<'_, 'a>, symbol: &Symbol<'_, 'a>, import: &ImportDeclaration<'a>, - ) -> RuleFix<'a> { + ) -> RuleFix { let specifiers = import .specifiers .as_ref() diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_vars/fixers/fix_symbol.rs b/crates/oxc_linter/src/rules/eslint/no_unused_vars/fixers/fix_symbol.rs index fac5b8213140e..9268b78d2fcce 100644 --- a/crates/oxc_linter/src/rules/eslint/no_unused_vars/fixers/fix_symbol.rs +++ b/crates/oxc_linter/src/rules/eslint/no_unused_vars/fixers/fix_symbol.rs @@ -12,7 +12,7 @@ impl<'s, 'a> Symbol<'s, 'a> { fixer: RuleFixer<'_, 'a>, list: &[T], own: &T, - ) -> RuleFix<'a> + ) -> RuleFix where T: GetSpan, Symbol<'s, 'a>: PartialEq, @@ -52,8 +52,8 @@ impl<'s, 'a> Symbol<'s, 'a> { fixer.delete(&delete_range) } - pub(super) fn rename(&self, new_name: &CompactStr) -> RuleFix<'a> { - let mut fixes: Vec> = vec![]; + pub(super) fn rename(&self, new_name: &CompactStr) -> RuleFix { + let mut fixes: Vec = vec![]; let decl_span = self.span(); fixes.push(Fix::new(new_name.clone(), decl_span)); diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_vars/fixers/fix_vars.rs b/crates/oxc_linter/src/rules/eslint/no_unused_vars/fixers/fix_vars.rs index 0ec730053806a..c0f54aaf18b36 100644 --- a/crates/oxc_linter/src/rules/eslint/no_unused_vars/fixers/fix_vars.rs +++ b/crates/oxc_linter/src/rules/eslint/no_unused_vars/fixers/fix_vars.rs @@ -30,7 +30,7 @@ impl NoUnusedVars { symbol: &Symbol<'_, 'a>, decl: &VariableDeclarator<'a>, decl_id: NodeId, - ) -> RuleFix<'a> { + ) -> RuleFix { if decl.init.as_ref().is_some_and(|init| is_skipped_init(symbol, init)) { return fixer.noop(); } diff --git a/crates/oxc_linter/src/rules/jest/prefer_lowercase_title/mod.rs b/crates/oxc_linter/src/rules/jest/prefer_lowercase_title/mod.rs index 7948fcc184639..11b54e3e500f0 100644 --- a/crates/oxc_linter/src/rules/jest/prefer_lowercase_title/mod.rs +++ b/crates/oxc_linter/src/rules/jest/prefer_lowercase_title/mod.rs @@ -298,7 +298,7 @@ impl PreferLowercaseTitle { let replacement_len = replacement.len() as u32; ctx.diagnostic_with_fix(prefer_lowercase_title_diagnostic(literal, span), |fixer| { - fixer.replace(Span::sized(span.start + 1, replacement_len), replacement) + fixer.replace(Span::sized(span.start + 1, replacement_len), replacement.into_owned()) }); } diff --git a/crates/oxc_linter/src/rules/jsx_a11y/anchor_has_content.rs b/crates/oxc_linter/src/rules/jsx_a11y/anchor_has_content.rs index 6551dc17f09e2..dd6049e872192 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/anchor_has_content.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/anchor_has_content.rs @@ -98,7 +98,7 @@ impl Rule for AnchorHasContent { } } -fn remove_hidden_attributes<'a>(element: &JSXElement<'a>) -> RuleFix<'a> { +fn remove_hidden_attributes(element: &JSXElement<'_>) -> RuleFix { element .opening_element .attributes diff --git a/crates/oxc_linter/src/rules/oxc/no_map_spread.rs b/crates/oxc_linter/src/rules/oxc/no_map_spread.rs index 463794711b43c..e70e3cac3caff 100644 --- a/crates/oxc_linter/src/rules/oxc/no_map_spread.rs +++ b/crates/oxc_linter/src/rules/oxc/no_map_spread.rs @@ -469,7 +469,7 @@ fn get_map_callback<'a, 'b>(call_expr: &'b CallExpression<'a>) -> Option<&'b Exp fn fix_spread_to_object_assign<'a>( fixer: RuleFixer<'_, 'a>, obj: &ObjectExpression<'a>, -) -> RuleFix<'a> { +) -> RuleFix { use oxc_allocator::{Allocator, CloneIn}; use oxc_ast::AstBuilder; use oxc_codegen::CodegenOptions; diff --git a/crates/oxc_linter/src/rules/react/exhaustive_deps.rs b/crates/oxc_linter/src/rules/react/exhaustive_deps.rs index 3038726a3a03c..b1b28c52a0ef5 100644 --- a/crates/oxc_linter/src/rules/react/exhaustive_deps.rs +++ b/crates/oxc_linter/src/rules/react/exhaustive_deps.rs @@ -1524,7 +1524,7 @@ mod fix { fixer: RuleFixer<'c, 'a>, names: &[Name<'a>], deps: &ArrayExpression<'a>, - ) -> RuleFix<'a> { + ) -> RuleFix { let mut codegen = fixer.codegen(); let alloc = Allocator::default(); @@ -1548,7 +1548,7 @@ mod fix { fixer: RuleFixer<'c, 'a>, dependency: &Dependency, deps: &ArrayExpression<'a>, - ) -> RuleFix<'a> { + ) -> RuleFix { let mut codegen = fixer.codegen(); let alloc = Allocator::default(); diff --git a/crates/oxc_linter/src/rules/react/jsx_curly_brace_presence.rs b/crates/oxc_linter/src/rules/react/jsx_curly_brace_presence.rs index 53ec3fa1fff26..939f8f60da49f 100644 --- a/crates/oxc_linter/src/rules/react/jsx_curly_brace_presence.rs +++ b/crates/oxc_linter/src/rules/react/jsx_curly_brace_presence.rs @@ -575,13 +575,13 @@ fn report_unnecessary_curly<'a>( let mut fix = fixer.codegen(); fix.print_str(template_lit.single_quasi().unwrap().as_str()); - fixer.replace(container.span, fix) + fixer.replace(container.span, fix.into_source_text()) } JSXExpression::StringLiteral(string_literal) => { let mut fix = fixer.codegen(); fix.print_str(string_literal.value.as_str()); - fixer.replace(container.span, fix) + fixer.replace(container.span, fix.into_source_text()) } _ => { let mut fix = fixer.new_fix_with_capacity(2); @@ -608,7 +608,7 @@ fn report_unnecessary_curly_for_attribute_value<'a>( JSXExpression::TemplateLiteral(template_lit) => template_lit.single_quasi().unwrap(), JSXExpression::StringLiteral(string_lit) => string_lit.value, JSXExpression::JSXElement(el) => { - return fixer.replace(container.span, ctx.source_range(el.span)); + return fixer.replace(container.span, ctx.source_range(el.span).to_owned()); } _ => unreachable!(), }; @@ -625,7 +625,7 @@ fn report_unnecessary_curly_for_attribute_value<'a>( None, )); - fixer.replace(container.span, fix) + fixer.replace(container.span, fix.into_source_text()) }); } @@ -658,7 +658,7 @@ fn report_missing_curly_for_string_attribute_value( let mut fix = fixer.new_fix_with_capacity(3); fix.push(fixer.insert_text_before(&span, "{")); fix.push(fixer.insert_text_after(&span, "}")); - fix.push(fixer.replace(span, replace)); + fix.push(fixer.replace(span, replace.into_source_text())); fix.with_message("add the missing curly braces") }); } @@ -695,7 +695,7 @@ fn report_missing_curly_for_text_node(ctx: &LintContext, span: Span, string_valu text, None, )); - fix.push(fixer.replace(span_from_first_char, replace)); + fix.push(fixer.replace(span_from_first_char, replace.into_source_text())); fix.push(fixer.insert_text_before(&span_from_first_char, "{")); fix.push(fixer.insert_text_after(&span_from_first_char, "}")); } diff --git a/crates/oxc_linter/src/rules/react/jsx_no_useless_fragment.rs b/crates/oxc_linter/src/rules/react/jsx_no_useless_fragment.rs index ceed65b823f2e..f570f52f69711 100644 --- a/crates/oxc_linter/src/rules/react/jsx_no_useless_fragment.rs +++ b/crates/oxc_linter/src/rules/react/jsx_no_useless_fragment.rs @@ -159,7 +159,7 @@ fn fix_fragment_element<'a>( elem: &JSXElement, ctx: &LintContext<'a>, fixer: RuleFixer<'_, 'a>, -) -> RuleFix<'a> { +) -> RuleFix { let replacement = if let Some(closing_elem) = &elem.closing_element { trim_like_react( Span::new(elem.opening_element.span.end, closing_elem.span.start) @@ -169,20 +169,21 @@ fn fix_fragment_element<'a>( "" }; - fixer.replace(elem.span(), trim_like_react(replacement)) + fixer.replace(elem.span(), trim_like_react(replacement).to_owned()) } fn fix_jsx_fragment<'a>( elem: &JSXFragment, ctx: &LintContext<'a>, fixer: RuleFixer<'_, 'a>, -) -> RuleFix<'a> { +) -> RuleFix { fixer.replace( elem.span(), trim_like_react( Span::new(elem.opening_fragment.span.end, elem.closing_fragment.span.start) .source_text(ctx.source_text()), - ), + ) + .to_owned(), ) } diff --git a/crates/oxc_linter/src/rules/react/jsx_props_no_spread_multi.rs b/crates/oxc_linter/src/rules/react/jsx_props_no_spread_multi.rs index 5660344d14a78..97a47f881fd7d 100644 --- a/crates/oxc_linter/src/rules/react/jsx_props_no_spread_multi.rs +++ b/crates/oxc_linter/src/rules/react/jsx_props_no_spread_multi.rs @@ -109,7 +109,7 @@ impl Rule for JsxPropsNoSpreadMulti { .rev() .skip(1) .map(|span| Fix::delete(*span)) - .collect::>() + .collect::() .with_message(REMOVE_DUPLICATE_SPREAD) }, ); diff --git a/crates/oxc_linter/src/rules/typescript/ban_ts_comment.rs b/crates/oxc_linter/src/rules/typescript/ban_ts_comment.rs index 98a61a60869d2..980640e75a690 100644 --- a/crates/oxc_linter/src/rules/typescript/ban_ts_comment.rs +++ b/crates/oxc_linter/src/rules/typescript/ban_ts_comment.rs @@ -187,7 +187,8 @@ impl Rule for BanTsComment { |fixer| { fixer.replace( comm.content_span(), - raw.cow_replace("@ts-ignore", "@ts-expect-error"), + raw.cow_replace("@ts-ignore", "@ts-expect-error") + .into_owned(), ) }, ); diff --git a/crates/oxc_linter/src/rules/typescript/consistent_type_imports.rs b/crates/oxc_linter/src/rules/typescript/consistent_type_imports.rs index dae89cd5a6fa1..78a5d3bc6399d 100644 --- a/crates/oxc_linter/src/rules/typescript/consistent_type_imports.rs +++ b/crates/oxc_linter/src/rules/typescript/consistent_type_imports.rs @@ -432,7 +432,7 @@ fn fixer_error, T>(message: S) -> FixerResult { // import { Foo, Bar } from 'foo' => import type { Foo, Bar } from 'foo' #[expect(clippy::cast_possible_truncation)] -fn fix_to_type_import_declaration<'a>(options: &FixOptions<'a, '_>) -> FixerResult> { +fn fix_to_type_import_declaration(options: &FixOptions<'_, '_>) -> FixerResult { let FixOptions { fixer, import_decl, type_names, fix_style, ctx } = options; let fixer = fixer.for_multifix(); @@ -620,10 +620,10 @@ fn fix_to_type_import_declaration<'a>(options: &FixOptions<'a, '_>) -> FixerResu .with_message("Mark all type-only imports with the type specifier")) } -fn fix_insert_named_specifiers_in_named_specifier_list<'a>( - options: &FixOptions<'a, '_>, +fn fix_insert_named_specifiers_in_named_specifier_list( + options: &FixOptions<'_, '_>, insert_text: &str, -) -> FixerResult> { +) -> FixerResult { let FixOptions { fixer, import_decl, ctx, .. } = options; let import_text = ctx.source_range(import_decl.span); let close_brace = try_find_char(import_text, '}')?; @@ -641,9 +641,9 @@ fn fix_insert_named_specifiers_in_named_specifier_list<'a>( // Returns information for fixing named specifiers, type or value #[derive(Default, Debug)] -struct FixNamedSpecifiers<'a> { +struct FixNamedSpecifiers { type_named_specifiers_text: String, - remove_type_name_specifiers: RuleFix<'a>, + remove_type_name_specifiers: RuleFix, } // get the type-only named import declaration with same source @@ -677,7 +677,7 @@ fn get_fixes_named_specifiers<'a>( options: &FixOptions<'a, '_>, subset_named_specifiers: &[&ImportSpecifier<'a>], all_named_specifiers: &[&ImportSpecifier<'a>], -) -> FixerResult> { +) -> FixerResult { let FixOptions { fixer, import_decl, ctx, .. } = options; let fixer = fixer.for_multifix(); @@ -834,9 +834,7 @@ fn try_find_char(text: &str, c: char) -> Result> { } } -fn fix_inline_type_import_declaration<'a>( - options: &FixOptions<'a, '_>, -) -> FixerResult> { +fn fix_inline_type_import_declaration(options: &FixOptions<'_, '_>) -> FixerResult { let FixOptions { fixer, import_decl, type_names, ctx, .. } = options; let fixer = fixer.for_multifix(); @@ -859,10 +857,10 @@ fn fix_inline_type_import_declaration<'a>( Ok(rule_fixes.with_message("Add type specifier to imported types")) } -fn fix_insert_type_specifier_for_import_declaration<'a>( - options: &FixOptions<'a, '_>, +fn fix_insert_type_specifier_for_import_declaration( + options: &FixOptions<'_, '_>, is_default_import: bool, -) -> FixerResult> { +) -> FixerResult { let FixOptions { fixer, import_decl, ctx, .. } = options; let fixer = fixer.for_multifix(); let import_specifiers_span = Span::new(import_decl.span.start, import_decl.source.span.start); @@ -963,7 +961,7 @@ fn fix_remove_type_specifier_from_import_declaration<'a>( fixer: RuleFixer<'_, 'a>, import_decl_span: Span, ctx: &LintContext<'a>, -) -> RuleFix<'a> { +) -> RuleFix { let import_source = ctx.source_range(import_decl_span); let new_import_source = import_source // ` type Foo from 'foo'` @@ -979,21 +977,24 @@ fn fix_remove_type_specifier_from_import_declaration<'a>( fixer.replace(import_decl_span, new_import_source) } else { // when encountering an unexpected import declaration, do nothing. - fixer.replace(import_decl_span, import_source) + fixer.replace(import_decl_span, import_source.to_string()) } } // import { type Foo } from 'foo' // ^^^^ remove -fn fix_remove_type_specifier_from_import_specifier<'a>( - fixer: RuleFixer<'_, 'a>, +fn fix_remove_type_specifier_from_import_specifier( + fixer: RuleFixer<'_, '_>, specifier_span: Span, - ctx: &LintContext<'a>, -) -> RuleFix<'a> { - let specifier_source = ctx.source_range(specifier_span); + ctx: &LintContext<'_>, +) -> RuleFix { + let specifier_source = ctx.source_range(specifier_span).to_string(); let new_specifier_source = specifier_source.strip_prefix("type "); - fixer.replace(specifier_span, new_specifier_source.unwrap_or(specifier_source)) + fixer.replace( + specifier_span, + new_specifier_source.map(std::string::ToString::to_string).unwrap_or(specifier_source), + ) } #[test] diff --git a/crates/oxc_linter/src/rules/typescript/no_wrapper_object_types.rs b/crates/oxc_linter/src/rules/typescript/no_wrapper_object_types.rs index 001a0c577e489..5e77074f0f16d 100644 --- a/crates/oxc_linter/src/rules/typescript/no_wrapper_object_types.rs +++ b/crates/oxc_linter/src/rules/typescript/no_wrapper_object_types.rs @@ -91,7 +91,7 @@ impl Rule for NoWrapperObjectTypes { if can_fix { ctx.diagnostic_with_fix(no_wrapper_object_types(ident_span), |fixer| { - fixer.replace(ident_span, ident_name.cow_to_ascii_lowercase()) + fixer.replace(ident_span, ident_name.cow_to_ascii_lowercase().to_string()) }); } else { ctx.diagnostic(no_wrapper_object_types(ident_span)); diff --git a/crates/oxc_linter/src/rules/unicorn/no_await_expression_member.rs b/crates/oxc_linter/src/rules/unicorn/no_await_expression_member.rs index 2bead2a996d6e..1452d46c24c33 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_await_expression_member.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_await_expression_member.rs @@ -118,7 +118,7 @@ impl Rule for NoAwaitExpressionMember { } // (await b())[0] => await b() // (await b()).a => await b() - rule_fixes.push(fixer.replace(member_expr.span(), inner_text)); + rule_fixes.push(fixer.replace(member_expr.span(), inner_text.to_owned())); rule_fixes.with_message("Assign the result of the await expression to a variable, then access the member from that variable.") }, ); diff --git a/crates/oxc_linter/src/rules/unicorn/no_console_spaces.rs b/crates/oxc_linter/src/rules/unicorn/no_console_spaces.rs index f143fa64a8f14..125afbb06973c 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_console_spaces.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_console_spaces.rs @@ -140,7 +140,7 @@ fn report_diagnostic<'a>( } else { Cow::Borrowed(literal_raw.trim()) }; - fixer.replace(span, content) + fixer.replace(span, content.to_string()) }); } diff --git a/crates/oxc_linter/src/rules/unicorn/no_null.rs b/crates/oxc_linter/src/rules/unicorn/no_null.rs index bb2098f0eb4dd..cdd4b0f24d1f1 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_null.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_null.rs @@ -237,15 +237,15 @@ impl Rule for NoNull { } } -fn fix_null<'a>(fixer: RuleFixer<'_, 'a>, null: &NullLiteral) -> RuleFix<'a> { +fn fix_null(fixer: RuleFixer<'_, '_>, null: &NullLiteral) -> RuleFix { fixer.replace(null.span, "undefined") } -fn try_fix_case<'a>( - fixer: RuleFixer<'_, 'a>, +fn try_fix_case( + fixer: RuleFixer<'_, '_>, null: &NullLiteral, - switch: &SwitchStatement<'a>, -) -> RuleFix<'a> { + switch: &SwitchStatement<'_>, +) -> RuleFix { let also_has_undefined = switch .cases .iter() diff --git a/crates/oxc_linter/src/rules/unicorn/no_single_promise_in_promise_methods.rs b/crates/oxc_linter/src/rules/unicorn/no_single_promise_in_promise_methods.rs index 8a59482c63d0f..72a8d0d63e05d 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_single_promise_in_promise_methods.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_single_promise_in_promise_methods.rs @@ -111,7 +111,7 @@ impl Rule for NoSinglePromiseInPromiseMethods { let call_span = call_expr.span; if is_directly_in_await { - fixer.replace(call_span, elem_text) + fixer.replace(call_span, elem_text.to_owned()) } else { fixer.replace(call_span, format!("Promise.resolve({elem_text})")) } diff --git a/crates/oxc_linter/src/rules/unicorn/no_unnecessary_await.rs b/crates/oxc_linter/src/rules/unicorn/no_unnecessary_await.rs index c86ce7dcbc419..e579fe6b911d4 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_unnecessary_await.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_unnecessary_await.rs @@ -1,7 +1,7 @@ use oxc_ast::{AstKind, ast::Expression}; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; -use oxc_span::{GetSpan, Span}; +use oxc_span::Span; use crate::{AstNode, context::LintContext, rule::Rule}; @@ -71,7 +71,7 @@ impl Rule for NoUnnecessaryAwait { } else { ctx.diagnostic_with_fix( no_unnecessary_await_diagnostic(Span::sized(expr.span.start, 5)), - |fixer| fixer.replace(expr.span, fixer.source_range(expr.argument.span())), + |fixer| fixer.replace_with(expr, &expr.argument), ); } } diff --git a/crates/oxc_linter/src/rules/unicorn/no_useless_promise_resolve_reject.rs b/crates/oxc_linter/src/rules/unicorn/no_useless_promise_resolve_reject.rs index fd1b9b29a55f6..11f8a45cb46ab 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_useless_promise_resolve_reject.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_useless_promise_resolve_reject.rs @@ -284,7 +284,7 @@ fn generate_fix<'a>( fixer: RuleFixer<'_, 'a>, ctx: &LintContext<'a>, node: &AstNode<'a>, -) -> RuleFix<'a> { +) -> RuleFix { if call_expr.arguments.len() > 1 { return fixer.noop(); } diff --git a/crates/oxc_linter/src/rules/unicorn/no_useless_spread/mod.rs b/crates/oxc_linter/src/rules/unicorn/no_useless_spread/mod.rs index dedbfee614de7..8828714454e61 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_useless_spread/mod.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_useless_spread/mod.rs @@ -224,7 +224,7 @@ fn check_useless_spread_in_list<'a>(node: &AstNode<'a>, ctx: &LintContext<'a>) - "" }; - fixer.replace(spread_elem.span(), replacer) + fixer.replace(spread_elem.span(), replacer.to_owned()) }); true } @@ -291,7 +291,7 @@ fn diagnose_array_in_array_spread<'a>( } } codegen.print_ascii_byte(b']'); - fixer.replace(outer_array.span, codegen) + fixer.replace(outer_array.span, codegen.into_source_text()) }); } } @@ -452,8 +452,8 @@ fn fix_by_removing_array_spread<'a, S: GetSpan>( fixer: RuleFixer<'_, 'a>, iterable: &S, spread: &SpreadElement<'a>, -) -> RuleFix<'a> { - fixer.replace(iterable.span(), fixer.source_range(spread.argument.span())) +) -> RuleFix { + fixer.replace_with(iterable, &spread.argument) } /// Creates a fix that replaces `{...spread}` with `spread`, when `spread` is an @@ -465,7 +465,7 @@ fn fix_by_removing_array_spread<'a, S: GetSpan>( fn fix_by_removing_object_spread<'a>( fixer: RuleFixer<'_, 'a>, spread: &SpreadElement<'a>, -) -> RuleFix<'a> { +) -> RuleFix { // get contents inside object brackets // e.g. `...{ a, b, }` -> ` a, b, ` let replacement_span = &spread.argument.span().shrink(1); diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_at.rs b/crates/oxc_linter/src/rules/unicorn/prefer_at.rs index 9f07f7cf3524d..017196fc18536 100644 --- a/crates/oxc_linter/src/rules/unicorn/prefer_at.rs +++ b/crates/oxc_linter/src/rules/unicorn/prefer_at.rs @@ -545,12 +545,12 @@ fn extract_length_minus_pattern<'a>(expr: &'a Expression<'a>) -> Option<(&'a Exp } // Unified fix creation -fn create_at_fix<'a>( - fixer: &RuleFixer<'_, 'a>, +fn create_at_fix( + fixer: &RuleFixer<'_, '_>, object_span: Span, full_span: Span, index: i64, -) -> RuleFix<'a> { +) -> RuleFix { let new_code = format!("{}.at({})", fixer.source_range(object_span), index); fixer.replace(full_span, new_code) } diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_class_fields.rs b/crates/oxc_linter/src/rules/unicorn/prefer_class_fields.rs index a44f4331c70a4..cf5412e7b8604 100644 --- a/crates/oxc_linter/src/rules/unicorn/prefer_class_fields.rs +++ b/crates/oxc_linter/src/rules/unicorn/prefer_class_fields.rs @@ -143,7 +143,7 @@ impl Rule for PreferClassFields { if let Some(value) = &prop.value { let mut codegen = fixer.codegen(); codegen.print_expression(&assign.right); - fix.push(fixer.replace(value.span(), codegen)); + fix.push(fixer.replace(value.span(), codegen.into_source_text())); } fix.with_message("Replace `this` assignment with class field declaration") @@ -162,7 +162,7 @@ impl Rule for PreferClassFields { let mut codegen = fixer.codegen(); codegen.print_str(" = "); codegen.print_expression(&assign.right); - fix.push(fixer.insert_text_after(&prop.key, codegen)); + fix.push(fixer.insert_text_after(&prop.key, codegen.into_source_text())); } else { let indent = ctx.source_range(constructor.span).lines().next().map_or("\t", |line| { @@ -176,7 +176,7 @@ impl Rule for PreferClassFields { codegen.print_str(" = "); codegen.print_expression(&assign.right); codegen.print_str(";\n"); - fix.push(fixer.insert_text_before(&**constructor, codegen)); + fix.push(fixer.insert_text_before(&**constructor, codegen.into_source_text())); } fix.with_message("Replace `this` assignment with class field declaration") diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_classlist_toggle.rs b/crates/oxc_linter/src/rules/unicorn/prefer_classlist_toggle.rs index b52a8f8e3e9e4..3e4b7e8520abf 100644 --- a/crates/oxc_linter/src/rules/unicorn/prefer_classlist_toggle.rs +++ b/crates/oxc_linter/src/rules/unicorn/prefer_classlist_toggle.rs @@ -345,7 +345,7 @@ fn fix_if_statement<'a>( add_call: &CallExpression<'a>, is_add_first: bool, ctx: &LintContext<'a>, -) -> RuleFix<'a> { +) -> RuleFix { let Some(member_expr) = add_call.callee.get_member_expr() else { return fixer.noop(); }; @@ -372,7 +372,7 @@ fn fix_conditional_expression<'a>( add_call: &CallExpression<'a>, is_add_first: bool, ctx: &LintContext<'a>, -) -> RuleFix<'a> { +) -> RuleFix { let Some(member_expr) = add_call.callee.get_member_expr() else { return fixer.noop(); }; @@ -398,7 +398,7 @@ fn fix_computed_member_call<'a>( test: &Expression<'a>, is_add_first: bool, ctx: &LintContext<'a>, -) -> RuleFix<'a> { +) -> RuleFix { let Some(member_expr) = call_expr.callee.get_member_expr() else { return fixer.noop(); }; diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_regexp_test.rs b/crates/oxc_linter/src/rules/unicorn/prefer_regexp_test.rs index 3660143f9de56..f1ab6bf0857ee 100644 --- a/crates/oxc_linter/src/rules/unicorn/prefer_regexp_test.rs +++ b/crates/oxc_linter/src/rules/unicorn/prefer_regexp_test.rs @@ -4,7 +4,7 @@ use oxc_ast::{ }; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; -use oxc_span::{GetSpan, Span}; +use oxc_span::Span; use crate::{AstNode, ast_util::outermost_paren_parent, context::LintContext, rule::Rule}; @@ -157,16 +157,8 @@ impl Rule for PreferRegexpTest { let mut fix = fixer.new_fix_with_capacity(3); fix.push(fixer.replace(span, "test")); - - fix.push(fixer.replace( - call_expr.arguments[0].span(), - fixer.source_range(member_expr.object().span()), - )); - - fix.push(fixer.replace( - member_expr.object().span(), - fixer.source_range(call_expr.arguments[0].span()), - )); + fix.push(fixer.replace_with(&call_expr.arguments[0], member_expr.object())); + fix.push(fixer.replace_with(member_expr.object(), &call_expr.arguments[0])); fix.with_message("Replace with `RegExp.test()`") }); diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_spread.rs b/crates/oxc_linter/src/rules/unicorn/prefer_spread.rs index ebd97189c3bd4..b5b2e9149bab9 100644 --- a/crates/oxc_linter/src/rules/unicorn/prefer_spread.rs +++ b/crates/oxc_linter/src/rules/unicorn/prefer_spread.rs @@ -251,7 +251,7 @@ fn report_with_spread_fixer( codegen.print_str("[..."); codegen.print_expression(expr_to_spread); codegen.print_str("]"); - fixer.replace(span, codegen) + fixer.replace(span, codegen.into_source_text()) }); } diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_string_starts_ends_with.rs b/crates/oxc_linter/src/rules/unicorn/prefer_string_starts_ends_with.rs index 2e19a286a8344..deeb4e4688da9 100644 --- a/crates/oxc_linter/src/rules/unicorn/prefer_string_starts_ends_with.rs +++ b/crates/oxc_linter/src/rules/unicorn/prefer_string_starts_ends_with.rs @@ -103,7 +103,7 @@ fn do_fix<'a>( fixer: RuleFixer<'_, 'a>, err_kind: ErrorKind, call_expr: &CallExpression<'a>, -) -> RuleFix<'a> { +) -> RuleFix { let Some(target_span) = can_replace(call_expr) else { return fixer.noop() }; let (argument, method) = match err_kind { ErrorKind::StartsWith(arg) => { @@ -120,7 +120,7 @@ fn do_fix<'a>( content.print_str(&format!(r"{}.{}(", fixer.source_range(target_span), method)); content.print_expression(&ast.expression_string_literal(SPAN, ast.atom(&argument), None)); content.print_str(r")"); - fixer.replace(call_expr.span, content) + fixer.replace(call_expr.span, content.into_source_text()) } fn can_replace(call_expr: &CallExpression) -> Option { diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_structured_clone.rs b/crates/oxc_linter/src/rules/unicorn/prefer_structured_clone.rs index 6be2debfb3758..f6c30eb43de21 100644 --- a/crates/oxc_linter/src/rules/unicorn/prefer_structured_clone.rs +++ b/crates/oxc_linter/src/rules/unicorn/prefer_structured_clone.rs @@ -155,16 +155,16 @@ impl Rule for PreferStructuredClone { } } -fn replace_with_structured_clone<'a>( - fixer: RuleFixer<'_, 'a>, +fn replace_with_structured_clone( + fixer: RuleFixer<'_, '_>, call_expr: &CallExpression<'_>, first_argument: &Expression<'_>, -) -> RuleFix<'a> { +) -> RuleFix { let mut codegen = fixer.codegen(); codegen.print_str("structuredClone("); codegen.print_expression(first_argument); codegen.print_str(")"); - fixer.replace(call_expr.span, codegen) + fixer.replace(call_expr.span, codegen.into_source_text()) } #[test] diff --git a/crates/oxc_linter/src/rules/unicorn/require_module_specifiers.rs b/crates/oxc_linter/src/rules/unicorn/require_module_specifiers.rs index 042f726299ef6..b48cc250fc718 100644 --- a/crates/oxc_linter/src/rules/unicorn/require_module_specifiers.rs +++ b/crates/oxc_linter/src/rules/unicorn/require_module_specifiers.rs @@ -125,7 +125,7 @@ fn find_empty_braces_in_export( find_empty_braces_in_text(export_text, export_decl.span) } -fn fix_import<'a>(fixer: RuleFixer<'_, 'a>, import_decl: &ImportDeclaration<'a>) -> RuleFix<'a> { +fn fix_import<'a>(fixer: RuleFixer<'_, 'a>, import_decl: &ImportDeclaration<'a>) -> RuleFix { let import_text = fixer.source_range(import_decl.span); let Some(comma_pos) = import_text.find(',') else { @@ -141,10 +141,7 @@ fn fix_import<'a>(fixer: RuleFixer<'_, 'a>, import_decl: &ImportDeclaration<'a>) fixer.replace(import_decl.span, format!("{default_part} {from_part}")) } -fn fix_export<'a>( - fixer: RuleFixer<'_, 'a>, - export_decl: &ExportNamedDeclaration<'a>, -) -> RuleFix<'a> { +fn fix_export<'a>(fixer: RuleFixer<'_, 'a>, export_decl: &ExportNamedDeclaration<'a>) -> RuleFix { if export_decl.source.is_some() { return fixer.noop(); } diff --git a/crates/oxc_linter/src/rules/unicorn/switch_case_braces.rs b/crates/oxc_linter/src/rules/unicorn/switch_case_braces.rs index 0b9f8e18ab9dc..4854026936cc3 100644 --- a/crates/oxc_linter/src/rules/unicorn/switch_case_braces.rs +++ b/crates/oxc_linter/src/rules/unicorn/switch_case_braces.rs @@ -130,7 +130,7 @@ impl Rule for SwitchCaseBraces { |fixer| { fixer.replace( block_stmt.span, - fixer.source_range(block_stmt.span.shrink(1)), + fixer.source_range(block_stmt.span.shrink(1)).to_owned(), ) }, ); diff --git a/crates/oxc_linter/src/service/mod.rs b/crates/oxc_linter/src/service/mod.rs index 2d919e1f44c71..1ace2a56f02b6 100644 --- a/crates/oxc_linter/src/service/mod.rs +++ b/crates/oxc_linter/src/service/mod.rs @@ -96,21 +96,18 @@ impl LintService { } #[cfg(feature = "language_server")] - pub fn run_source<'a>( - &mut self, - allocator: &'a mut oxc_allocator::Allocator, - ) -> Vec> { + pub fn run_source(&mut self, allocator: &mut oxc_allocator::Allocator) -> Vec { self.runtime.run_source(allocator) } /// For tests #[cfg(test)] - pub(crate) fn run_test_source<'a>( + pub(crate) fn run_test_source( &mut self, - allocator: &'a mut oxc_allocator::Allocator, + allocator: &mut oxc_allocator::Allocator, check_syntax_errors: bool, tx_error: &DiagnosticSender, - ) -> Vec> { + ) -> Vec { self.runtime.run_test_source(allocator, check_syntax_errors, tx_error) } } diff --git a/crates/oxc_linter/src/service/runtime.rs b/crates/oxc_linter/src/service/runtime.rs index 37d531e20815c..127c57fb4c94a 100644 --- a/crates/oxc_linter/src/service/runtime.rs +++ b/crates/oxc_linter/src/service/runtime.rs @@ -188,74 +188,6 @@ impl RuntimeFileSystem for OsFileSystem { } } -/// [`MessageCloner`] is a wrapper around an `&Allocator` which allows it to be safely shared across threads, -/// in order to clone [`crate::fixer::Message`]s into it. -/// -/// `Allocator` is not thread safe (it is not `Sync`), so cannot be shared across threads. -/// It would be undefined behavior to allocate into an `Allocator` from multiple threads simultaneously. -/// -/// `MessageCloner` ensures only one thread at a time can utilize the `Allocator`, by taking an -/// exclusive `&mut Allocator` to start with, and synchronising access to the `Allocator` with a `Mutex`. -/// -/// This type is wrapped in a module so that other code cannot access the inner `UnsafeAllocatorRef` -/// directly, and must go via the [`MessageCloner::clone_message`] method. -#[cfg(any(feature = "language_server", test))] -mod message_cloner { - use std::sync::Mutex; - - use oxc_allocator::{Allocator, CloneIn}; - - use crate::Message; - - /// Unsafe wrapper around an `&Allocator` which makes it `Send`. - struct UnsafeAllocatorRef<'a>(&'a Allocator); - - // SAFETY: It is sound to implement `Send` for `UnsafeAllocatorRef` because: - // * The only way to construct an `UnsafeAllocatorRef` is via `MessageCloner::new`, which takes - // an exclusive `&mut Allocator`, ensuring no other references to the same `Allocator` exist. - // * The lifetime `'a` ensures that the reference to the `Allocator` cannot outlive the original - // mutable borrow, preventing aliasing or concurrent mutation. - // * All access to the `Allocator` via `UnsafeAllocatorRef` is synchronized by a `Mutex` inside - // `MessageCloner`, so only one thread can access the allocator at a time. - // * The module encapsulation prevents direct access to `UnsafeAllocatorRef`, so it cannot be - // misused outside of the intended, synchronized context. - // - // Therefore, although `Allocator` is not `Sync`, it is safe to send `UnsafeAllocatorRef` between - // threads as long as it is only accessed via the `Mutex` in `MessageCloner`. - unsafe impl Send for UnsafeAllocatorRef<'_> {} - - /// Wrapper around an [`Allocator`] which allows safely using it on multiple threads to - /// clone [`Message`]s into. - pub struct MessageCloner<'a>(Mutex>); - - impl<'a> MessageCloner<'a> { - /// Wrap an [`Allocator`] in a [`MessageCloner`]. - /// - /// This method takes a `&mut Allocator`, to ensure that no other references to the `Allocator` - /// can exist, which guarantees no other threads can allocate with the `Allocator` while this - /// `MessageCloner` exists. - #[inline] - #[expect(clippy::needless_pass_by_ref_mut)] - pub fn new(allocator: &'a mut Allocator) -> Self { - Self(Mutex::new(UnsafeAllocatorRef(allocator))) - } - - /// Clone a [`Message`] into the [`Allocator`] held by this [`MessageCloner`]. - /// - /// # Panics - /// Panics if the underlying `Mutex` is poisoned. - pub fn clone_message(&self, message: &Message) -> Message<'a> { - // Obtain an exclusive lock on the `Mutex` during `clone_in` operation, - // to ensure no other thread can be simultaneously using the `Allocator` - let guard = self.0.lock().unwrap(); - let allocator = guard.0; - message.clone_in(allocator) - } - } -} -#[cfg(any(feature = "language_server", test))] -use message_cloner::MessageCloner; - impl Runtime { pub(super) fn new(linter: Linter, options: LintServiceOptions) -> Self { // If global thread pool wasn't already initialized, do it now. @@ -705,16 +637,10 @@ impl Runtime { // the struct not using `oxc_diagnostic::Error, because we are just collecting information // and returning it to the client to let him display it. #[cfg(feature = "language_server")] - pub(super) fn run_source<'a>( - &mut self, - allocator: &'a mut oxc_allocator::Allocator, - ) -> Vec> { + pub(super) fn run_source(&mut self, _allocator: &mut oxc_allocator::Allocator) -> Vec { use std::sync::Mutex; - // Wrap allocator in `MessageCloner` so can clone `Message`s into it - let message_cloner = MessageCloner::new(allocator); - - let messages = Mutex::new(Vec::>::new()); + let messages = Mutex::new(Vec::::new()); rayon::scope(|scope| { self.resolve_modules(scope, true, None, |me, mut module_to_lint| { module_to_lint.content.with_dependent_mut( @@ -768,8 +694,6 @@ impl Runtime { messages.lock().unwrap().extend( section_messages - .iter() - .map(|message| message_cloner.clone_message(message)), ); }, ); @@ -780,18 +704,15 @@ impl Runtime { } #[cfg(test)] - pub(super) fn run_test_source<'a>( + pub(super) fn run_test_source( &mut self, - allocator: &'a mut Allocator, + _allocator: &mut Allocator, check_syntax_errors: bool, tx_error: &DiagnosticSender, - ) -> Vec> { + ) -> Vec { use std::sync::Mutex; - // Wrap allocator in `MessageCloner` so can clone `Message`s into it - let message_cloner = MessageCloner::new(allocator); - - let messages = Mutex::new(Vec::>::new()); + let messages = Mutex::new(Vec::::new()); rayon::scope(|scope| { self.resolve_modules(scope, check_syntax_errors, Some(tx_error), |me, mut module| { module.content.with_dependent_mut( @@ -833,10 +754,8 @@ impl Runtime { Path::new(&module.path), context_sub_hosts, allocator_guard - ).iter_mut() - .map(|message| { - message_cloner.clone_message(message) - }), + ) + , ); }, ); diff --git a/crates/oxc_linter/src/tsgolint.rs b/crates/oxc_linter/src/tsgolint.rs index 7fbbd722cb020..9123314f0f62b 100644 --- a/crates/oxc_linter/src/tsgolint.rs +++ b/crates/oxc_linter/src/tsgolint.rs @@ -292,7 +292,7 @@ impl TsGoLintState { &self, path: &Arc, source_text: String, - ) -> Result>, String> { + ) -> Result, String> { let mut resolved_configs: FxHashMap = FxHashMap::default(); let json_input = self.json_input(std::slice::from_ref(path), &mut resolved_configs); @@ -332,7 +332,7 @@ impl TsGoLintState { // Stream diagnostics as they are emitted, rather than waiting for all output let stdout = child.stdout.take().expect("Failed to open tsgolint stdout"); - let stdout_handler = std::thread::spawn(move || -> Result>, String> { + let stdout_handler = std::thread::spawn(move || -> Result, String> { let msg_iter = TsGoLintMessageStream::new(stdout); let mut result = vec![]; @@ -554,7 +554,7 @@ impl From for OxcDiagnostic { } #[cfg(feature = "language_server")] -impl Message<'_> { +impl Message { /// Converts a `TsGoLintDiagnostic` into a `Message` with possible fixes. fn from_tsgo_lint_diagnostic(mut val: TsGoLintDiagnostic, source_text: &str) -> Self { use std::{borrow::Cow, mem};