From f98f7771d82b46d2eddc936a5de4289054d40df8 Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Mon, 10 Jun 2024 05:20:14 -0400 Subject: [PATCH] refactor(linter): add rule fixer (#3589) Adds a new `RuleFixer` struct that gets passed to `ctx.diagnostic_with_fix`. --- crates/oxc_ast/src/span.rs | 8 +- crates/oxc_codegen/src/lib.rs | 19 +- crates/oxc_linter/src/context.rs | 17 +- crates/oxc_linter/src/fixer.rs | 51 ++++- crates/oxc_linter/src/rules/eslint/eqeqeq.rs | 7 +- .../src/rules/eslint/no_debugger.rs | 6 +- .../src/rules/eslint/no_div_regex.rs | 7 +- .../src/rules/eslint/no_unsafe_negation.rs | 10 +- .../src/rules/eslint/no_unused_labels.rs | 4 +- .../src/rules/eslint/no_useless_escape.rs | 6 +- .../src/rules/eslint/unicode_bom.rs | 12 +- .../oxc_linter/src/rules/eslint/use_isnan.rs | 10 +- .../src/rules/eslint/valid_typeof.rs | 4 +- .../src/rules/jest/no_alias_methods.rs | 11 +- .../src/rules/jest/no_deprecated_functions.rs | 4 +- .../src/rules/jest/no_focused_tests.rs | 21 +- .../src/rules/jest/no_jasmine_globals.rs | 8 +- .../src/rules/jest/no_test_prefixes.rs | 5 +- .../src/rules/jest/no_untyped_mock_factory.rs | 11 +- .../rules/jest/prefer_comparison_matcher.rs | 26 +-- .../src/rules/jest/prefer_expect_resolves.rs | 24 +-- .../src/rules/jest/prefer_lowercase_title.rs | 78 +++----- .../jest/prefer_mock_promise_shorthand.rs | 23 ++- .../src/rules/jest/prefer_spy_on.rs | 12 +- .../src/rules/jest/prefer_strict_equal.rs | 19 +- .../oxc_linter/src/rules/jest/prefer_to_be.rs | 28 +-- .../src/rules/jest/prefer_to_have_length.rs | 16 +- .../oxc_linter/src/rules/jest/prefer_todo.rs | 44 ++--- .../oxc_linter/src/rules/oxc/no_const_enum.rs | 9 +- .../src/rules/typescript/array_type.rs | 39 ++-- .../src/rules/typescript/ban_ts_comment.rs | 10 +- .../rules/typescript/ban_tslint_comment.rs | 4 +- .../consistent_indexed_object_style.rs | 58 +++--- .../typescript/consistent_type_definitions.rs | 20 +- .../src/rules/typescript/no_explicit_any.rs | 5 +- .../src/rules/typescript/prefer_as_const.rs | 8 +- .../rules/typescript/prefer_function_type.rs | 55 ++---- .../typescript/prefer_ts_expect_error.rs | 13 +- .../src/rules/unicorn/empty_brace_spaces.rs | 8 +- .../src/rules/unicorn/escape_case.rs | 14 +- .../rules/unicorn/explicit_length_check.rs | 42 ++-- .../src/rules/unicorn/no_console_spaces.rs | 30 +-- .../src/rules/unicorn/no_hex_escape.rs | 14 +- .../src/rules/unicorn/no_instanceof_array.rs | 8 +- .../src/rules/unicorn/no_nested_ternary.rs | 13 +- .../oxc_linter/src/rules/unicorn/no_null.rs | 31 +-- .../no_single_promise_in_promise_methods.rs | 10 +- .../src/rules/unicorn/no_unnecessary_await.rs | 8 +- .../unicorn/no_useless_fallback_in_spread.rs | 10 +- .../src/rules/unicorn/no_useless_spread.rs | 35 ++-- .../src/rules/unicorn/no_zero_fractions.rs | 4 +- .../src/rules/unicorn/number_literal_case.rs | 4 +- .../rules/unicorn/numeric_separators_style.rs | 6 +- .../unicorn/prefer_dom_node_text_content.rs | 9 +- .../rules/unicorn/prefer_prototype_methods.rs | 8 +- .../rules/unicorn/prefer_query_selector.rs | 13 +- .../src/rules/unicorn/prefer_spread.rs | 8 +- ...require_number_to_fixed_digits_argument.rs | 12 +- .../src/rules/unicorn/switch_case_braces.rs | 10 +- .../src/snapshots/no_console_spaces.snap | 180 +++++++++--------- .../oxc_linter/src/snapshots/prefer_todo.snap | 12 +- crates/oxc_span/src/span.rs | 5 + 62 files changed, 587 insertions(+), 619 deletions(-) diff --git a/crates/oxc_ast/src/span.rs b/crates/oxc_ast/src/span.rs index 98371ce85598a..bb610750ffe3b 100644 --- a/crates/oxc_ast/src/span.rs +++ b/crates/oxc_ast/src/span.rs @@ -94,12 +94,6 @@ impl<'a> GetSpan for Expression<'a> { } } -impl<'a> GetSpan for Directive<'a> { - fn span(&self) -> Span { - self.span - } -} - impl<'a> GetSpan for BindingPatternKind<'a> { fn span(&self) -> Span { match self { @@ -122,7 +116,7 @@ impl<'a> GetSpan for BindingPattern<'a> { } } -impl<'a> GetSpan for BindingProperty<'a> { +impl GetSpan for BindingProperty<'_> { fn span(&self) -> Span { self.span } diff --git a/crates/oxc_codegen/src/lib.rs b/crates/oxc_codegen/src/lib.rs index 01996a77fe176..c0ae1f2918f63 100644 --- a/crates/oxc_codegen/src/lib.rs +++ b/crates/oxc_codegen/src/lib.rs @@ -15,7 +15,7 @@ mod gen_ts; mod operator; mod sourcemap_builder; -use std::ops::Range; +use std::{borrow::Cow, ops::Range}; #[allow(clippy::wildcard_imports)] use oxc_ast::ast::*; @@ -524,3 +524,20 @@ fn choose_quote(s: &str) -> char { '\'' } } + +impl From for String { + fn from(val: CodegenReturn) -> Self { + val.source_text + } +} + +impl<'a, const MINIFY: bool> From> for String { + fn from(mut val: Codegen<'a, MINIFY>) -> Self { + val.into_source_text() + } +} +impl<'a, const MINIFY: bool> From> for Cow<'a, str> { + fn from(mut val: Codegen<'a, MINIFY>) -> Self { + Cow::Owned(val.into_source_text()) + } +} diff --git a/crates/oxc_linter/src/context.rs b/crates/oxc_linter/src/context.rs index babf3237a9d96..9b174349d8f44 100644 --- a/crates/oxc_linter/src/context.rs +++ b/crates/oxc_linter/src/context.rs @@ -1,13 +1,12 @@ use std::{cell::RefCell, path::Path, rc::Rc, sync::Arc}; -use oxc_codegen::{Codegen, CodegenOptions}; use oxc_diagnostics::{OxcDiagnostic, Severity}; use oxc_semantic::{AstNodes, JSDocFinder, ScopeTree, Semantic, SymbolTable}; use oxc_span::{SourceType, Span}; use crate::{ disable_directives::{DisableDirectives, DisableDirectivesBuilder}, - fixer::{Fix, Message}, + fixer::{Fix, Message, RuleFixer}, javascript_globals::GLOBALS, AllowWarnDeny, OxlintConfig, OxlintEnv, OxlintGlobals, OxlintSettings, }; @@ -147,9 +146,14 @@ impl<'a> LintContext<'a> { } /// Report a lint rule violation and provide an automatic fix. - pub fn diagnostic_with_fix Fix<'a>>(&self, diagnostic: OxcDiagnostic, fix: F) { + pub fn diagnostic_with_fix) -> Fix<'a>>( + &self, + diagnostic: OxcDiagnostic, + fix: F, + ) { if self.fix { - self.add_diagnostic(Message::new(diagnostic, Some(fix()))); + let fixer = RuleFixer::new(self); + self.add_diagnostic(Message::new(diagnostic, Some(fix(fixer)))); } else { self.diagnostic(diagnostic); } @@ -167,11 +171,6 @@ impl<'a> LintContext<'a> { self.semantic().symbols() } - #[allow(clippy::unused_self)] - pub fn codegen(&self) -> Codegen { - Codegen::::new("", "", CodegenOptions::default(), None) - } - /* JSDoc */ pub fn jsdoc(&self) -> &JSDocFinder<'a> { self.semantic().jsdoc() diff --git a/crates/oxc_linter/src/fixer.rs b/crates/oxc_linter/src/fixer.rs index becf8f8524d5b..6a18fcef1ac5d 100644 --- a/crates/oxc_linter/src/fixer.rs +++ b/crates/oxc_linter/src/fixer.rs @@ -1,7 +1,10 @@ use std::borrow::Cow; +use oxc_codegen::{Codegen, CodegenOptions}; use oxc_diagnostics::OxcDiagnostic; -use oxc_span::Span; +use oxc_span::{GetSpan, Span}; + +use crate::LintContext; #[derive(Debug, Clone, Default)] pub struct Fix<'a> { @@ -19,6 +22,52 @@ impl<'a> Fix<'a> { } } +/// Inspired by ESLint's [`RuleFixer`]. +/// +/// [`RuleFixer`]: https://github.com/eslint/eslint/blob/main/lib/linter/rule-fixer.js +#[derive(Clone, Copy)] +pub struct RuleFixer<'c, 'a: 'c> { + ctx: &'c LintContext<'a>, +} + +impl<'c, 'a: 'c> RuleFixer<'c, 'a> { + pub fn new(ctx: &'c LintContext<'a>) -> Self { + Self { ctx } + } + + pub fn source_range(self, span: Span) -> &'a str { + self.ctx.source_range(span) + } + + /// Create a [`Fix`] that deletes the text covered by the given [`Span`] or + /// AST node. + pub fn delete(self, spanned: &S) -> Fix<'a> { + self.delete_range(spanned.span()) + } + + #[allow(clippy::unused_self)] + pub fn delete_range(self, span: Span) -> Fix<'a> { + Fix::delete(span) + } + + /// Replace a `target` AST node with the source code of a `replacement` node.. + pub fn replace_with(self, target: &T, replacement: &S) -> Fix<'a> { + let replacement_text = self.ctx.source_range(replacement.span()); + Fix::new(replacement_text, target.span()) + } + + /// Replace a `target` AST node with a `replacement` string. + #[allow(clippy::unused_self)] + pub fn replace>>(self, target: Span, replacement: S) -> Fix<'a> { + Fix::new(replacement, target) + } + + #[allow(clippy::unused_self)] + pub fn codegen(self) -> Codegen<'a, false> { + Codegen::::new("", "", CodegenOptions::default(), None) + } +} + pub struct FixResult<'a> { pub fixed: bool, pub fixed_code: Cow<'a, str>, diff --git a/crates/oxc_linter/src/rules/eslint/eqeqeq.rs b/crates/oxc_linter/src/rules/eslint/eqeqeq.rs index 01898d38df913..78f45e4f23f10 100644 --- a/crates/oxc_linter/src/rules/eslint/eqeqeq.rs +++ b/crates/oxc_linter/src/rules/eslint/eqeqeq.rs @@ -8,7 +8,7 @@ use oxc_macros::declare_oxc_lint; use oxc_span::{GetSpan, Span}; use oxc_syntax::operator::{BinaryOperator, UnaryOperator}; -use crate::{context::LintContext, fixer::Fix, rule::Rule, AstNode}; +use crate::{context::LintContext, rule::Rule, AstNode}; fn eqeqeq_diagnostic(x0: &str, x1: &str, span2: Span) -> OxcDiagnostic { OxcDiagnostic::warn(format!("eslint(eqeqeq): Expected {x1} and instead saw {x0}")) @@ -120,10 +120,11 @@ impl Rule for Eqeqeq { if is_type_of_binary_bool || are_literals_and_same_type_bool { ctx.diagnostic_with_fix( eqeqeq_diagnostic(operator, preferred_operator, operator_span), - || { + |fixer| { let start = binary_expr.left.span().end; let end = binary_expr.right.span().start; - Fix::new(preferred_operator_with_padding, Span::new(start, end)) + let span = Span::new(start, end); + fixer.replace(span, preferred_operator_with_padding) }, ); } else { diff --git a/crates/oxc_linter/src/rules/eslint/no_debugger.rs b/crates/oxc_linter/src/rules/eslint/no_debugger.rs index 9c9e3bfc1c8f8..6621358d74082 100644 --- a/crates/oxc_linter/src/rules/eslint/no_debugger.rs +++ b/crates/oxc_linter/src/rules/eslint/no_debugger.rs @@ -4,7 +4,7 @@ use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::Span; -use crate::{context::LintContext, fixer::Fix, rule::Rule, AstNode}; +use crate::{context::LintContext, rule::Rule, AstNode}; fn no_debugger_diagnostic(span0: Span) -> OxcDiagnostic { OxcDiagnostic::warn("eslint(no-debugger): `debugger` statement is not allowed") @@ -35,7 +35,9 @@ declare_oxc_lint!( impl Rule for NoDebugger { fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { if let AstKind::DebuggerStatement(stmt) = node.kind() { - ctx.diagnostic_with_fix(no_debugger_diagnostic(stmt.span), || Fix::delete(stmt.span)); + ctx.diagnostic_with_fix(no_debugger_diagnostic(stmt.span), |fixer| { + fixer.delete(&stmt.span) + }); } } } diff --git a/crates/oxc_linter/src/rules/eslint/no_div_regex.rs b/crates/oxc_linter/src/rules/eslint/no_div_regex.rs index 13a0cac039fe1..da105cb34975b 100644 --- a/crates/oxc_linter/src/rules/eslint/no_div_regex.rs +++ b/crates/oxc_linter/src/rules/eslint/no_div_regex.rs @@ -3,7 +3,7 @@ use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::Span; -use crate::{context::LintContext, fixer::Fix, rule::Rule, AstNode}; +use crate::{context::LintContext, rule::Rule, AstNode}; fn no_div_regex_diagnostic(span0: Span) -> OxcDiagnostic { OxcDiagnostic::warn( @@ -38,8 +38,9 @@ impl Rule for NoDivRegex { fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { if let AstKind::RegExpLiteral(lit) = node.kind() { if lit.regex.pattern.starts_with('=') { - ctx.diagnostic_with_fix(no_div_regex_diagnostic(lit.span), || { - Fix::new("[=]", Span::new(lit.span.start + 1, lit.span.start + 2)) + ctx.diagnostic_with_fix(no_div_regex_diagnostic(lit.span), |fixer| { + let span = Span::sized(lit.span.start + 1, 1); + fixer.replace(span, "[=]") }); } } diff --git a/crates/oxc_linter/src/rules/eslint/no_unsafe_negation.rs b/crates/oxc_linter/src/rules/eslint/no_unsafe_negation.rs index 28f6553c97fd8..47a2484413425 100644 --- a/crates/oxc_linter/src/rules/eslint/no_unsafe_negation.rs +++ b/crates/oxc_linter/src/rules/eslint/no_unsafe_negation.rs @@ -8,7 +8,7 @@ use oxc_macros::declare_oxc_lint; use oxc_span::{GetSpan, Span}; use oxc_syntax::operator::{BinaryOperator, UnaryOperator}; -use crate::{context::LintContext, fixer::Fix, rule::Rule, AstNode}; +use crate::{context::LintContext, fixer::RuleFixer, rule::Rule, AstNode}; fn no_unsafe_negation_diagnostic(x0: &str, span1: Span) -> OxcDiagnostic { OxcDiagnostic::warn(format!("Unexpected logical not in the left hand side of '{x0}' operator")) @@ -75,15 +75,15 @@ impl NoUnsafeNegation { /// Precondition: /// expr.left is `UnaryExpression` whose operator is '!' - fn report_with_fix(expr: &BinaryExpression, ctx: &LintContext<'_>) { + fn report_with_fix<'a>(expr: &BinaryExpression, ctx: &LintContext<'a>) { use oxc_codegen::{Context, Gen}; // Diagnostic points at the unexpected negation let diagnostic = no_unsafe_negation_diagnostic(expr.operator.as_str(), expr.left.span()); - let fix_producer = || { + let fix_producer = |fixer: RuleFixer<'_, 'a>| { // modify `!a instance of B` to `!(a instanceof B)` let modified_code = { - let mut codegen = ctx.codegen(); + let mut codegen = fixer.codegen(); codegen.print(b'!'); let Expression::UnaryExpression(left) = &expr.left else { unreachable!() }; codegen.print(b'('); @@ -93,7 +93,7 @@ impl NoUnsafeNegation { codegen.print(b')'); codegen.into_source_text() }; - Fix::new(modified_code, expr.span) + fixer.replace(expr.span, modified_code) }; ctx.diagnostic_with_fix(diagnostic, fix_producer); diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_labels.rs b/crates/oxc_linter/src/rules/eslint/no_unused_labels.rs index 15d4a7925a65c..ee6909d5dbc07 100644 --- a/crates/oxc_linter/src/rules/eslint/no_unused_labels.rs +++ b/crates/oxc_linter/src/rules/eslint/no_unused_labels.rs @@ -4,7 +4,7 @@ use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::Span; -use crate::{context::LintContext, fixer::Fix, rule::Rule}; +use crate::{context::LintContext, rule::Rule}; fn no_unused_labels_diagnostic(x0: &str, span1: Span) -> OxcDiagnostic { OxcDiagnostic::warn("eslint(no-unused-labels): Disallow unused labels") @@ -51,7 +51,7 @@ impl Rule for NoUnusedLabels { // e.g. A: /* Comment */ function foo(){} ctx.diagnostic_with_fix( no_unused_labels_diagnostic(stmt.label.name.as_str(), stmt.label.span), - || Fix::delete(stmt.label.span), + |fixer| fixer.delete_range(stmt.label.span), ); } } diff --git a/crates/oxc_linter/src/rules/eslint/no_useless_escape.rs b/crates/oxc_linter/src/rules/eslint/no_useless_escape.rs index f91f20b1ddb23..6eef3918d7c50 100644 --- a/crates/oxc_linter/src/rules/eslint/no_useless_escape.rs +++ b/crates/oxc_linter/src/rules/eslint/no_useless_escape.rs @@ -6,7 +6,7 @@ use oxc_macros::declare_oxc_lint; use oxc_semantic::AstNodeId; use oxc_span::Span; -use crate::{context::LintContext, rule::Rule, AstNode, Fix}; +use crate::{context::LintContext, rule::Rule, AstNode}; fn no_useless_escape_diagnostic(x0: char, span1: Span) -> OxcDiagnostic { OxcDiagnostic::warn(format!("eslint(no-useless-escape): Unnecessary escape character {x0:?}")) @@ -84,8 +84,8 @@ fn check(ctx: &LintContext<'_>, node_id: AstNodeId, start: u32, offsets: &[usize if !is_within_jsx_attribute_item(node_id, ctx) { let span = Span::new(offset - 1, offset + len); - ctx.diagnostic_with_fix(no_useless_escape_diagnostic(c, span), || { - Fix::new(c.to_string(), span) + ctx.diagnostic_with_fix(no_useless_escape_diagnostic(c, span), |fixer| { + fixer.replace(span, c.to_string()) }); } } diff --git a/crates/oxc_linter/src/rules/eslint/unicode_bom.rs b/crates/oxc_linter/src/rules/eslint/unicode_bom.rs index 9d74a62049627..dc04a4d92c2df 100644 --- a/crates/oxc_linter/src/rules/eslint/unicode_bom.rs +++ b/crates/oxc_linter/src/rules/eslint/unicode_bom.rs @@ -1,8 +1,8 @@ use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; -use oxc_span::Span; +use oxc_span::{Span, SPAN}; -use crate::{context::LintContext, rule::Rule, Fix}; +use crate::{context::LintContext, rule::Rule}; fn unexpected_unicode_bom_diagnostic(span0: Span) -> OxcDiagnostic { OxcDiagnostic::warn("eslint(unicode-bom): Unexpected Unicode BOM (Byte Order Mark)") @@ -59,14 +59,14 @@ impl Rule for UnicodeBom { let has_bomb = source.starts_with(''); if has_bomb && matches!(self.bom_option, BomOptionType::Never) { - ctx.diagnostic_with_fix(unexpected_unicode_bom_diagnostic(Span::new(0, 0)), || { - return Fix::delete(Span::new(0, 3)); + ctx.diagnostic_with_fix(unexpected_unicode_bom_diagnostic(SPAN), |fixer| { + return fixer.delete_range(Span::new(0, 3)); }); } if !has_bomb && matches!(self.bom_option, BomOptionType::Always) { - ctx.diagnostic_with_fix(expected_unicode_bom_diagnostic(Span::new(0, 0)), || { - return Fix::new("", Span::new(0, 0)); + ctx.diagnostic_with_fix(expected_unicode_bom_diagnostic(SPAN), |fixer| { + return fixer.replace(SPAN, ""); }); } } diff --git a/crates/oxc_linter/src/rules/eslint/use_isnan.rs b/crates/oxc_linter/src/rules/eslint/use_isnan.rs index a006d5ab157cd..574ab88e6bfa4 100644 --- a/crates/oxc_linter/src/rules/eslint/use_isnan.rs +++ b/crates/oxc_linter/src/rules/eslint/use_isnan.rs @@ -7,7 +7,7 @@ use oxc_macros::declare_oxc_lint; use oxc_span::{GetSpan, Span}; use oxc_syntax::operator::BinaryOperator; -use crate::{context::LintContext, fixer::Fix, rule::Rule, AstNode}; +use crate::{context::LintContext, rule::Rule, AstNode}; fn comparison_with_na_n(span0: Span) -> OxcDiagnostic { OxcDiagnostic::warn("eslint(use-isnan): Requires calls to isNaN() when checking for NaN") @@ -90,13 +90,13 @@ impl Rule for UseIsnan { } AstKind::BinaryExpression(expr) if expr.operator.is_equality() => { if is_nan_identifier(&expr.left) { - ctx.diagnostic_with_fix(comparison_with_na_n(expr.left.span()), || { - Fix::new(make_equality_fix(true, expr, ctx), expr.span) + ctx.diagnostic_with_fix(comparison_with_na_n(expr.left.span()), |fixer| { + fixer.replace(expr.span, make_equality_fix(true, expr, ctx)) }); } if is_nan_identifier(&expr.right) { - ctx.diagnostic_with_fix(comparison_with_na_n(expr.right.span()), || { - Fix::new(make_equality_fix(false, expr, ctx), expr.span) + ctx.diagnostic_with_fix(comparison_with_na_n(expr.right.span()), |fixer| { + fixer.replace(expr.span, make_equality_fix(false, expr, ctx)) }); } } diff --git a/crates/oxc_linter/src/rules/eslint/valid_typeof.rs b/crates/oxc_linter/src/rules/eslint/valid_typeof.rs index 987e76b054124..4f95eb1841d06 100644 --- a/crates/oxc_linter/src/rules/eslint/valid_typeof.rs +++ b/crates/oxc_linter/src/rules/eslint/valid_typeof.rs @@ -5,7 +5,7 @@ use oxc_span::{GetSpan, Span}; use oxc_syntax::operator::UnaryOperator; use phf::{phf_set, Set}; -use crate::{context::LintContext, fixer::Fix, rule::Rule, AstNode}; +use crate::{context::LintContext, rule::Rule, AstNode}; fn not_string(x0: Option<&'static str>, span1: Span) -> OxcDiagnostic { let mut d = OxcDiagnostic::warn( @@ -110,7 +110,7 @@ impl Rule for ValidTypeof { sibling.span(), ) }, - || Fix::new("\"undefined\"", sibling.span()), + |fixer| fixer.replace(sibling.span(), "\"undefined\""), ); return; } diff --git a/crates/oxc_linter/src/rules/jest/no_alias_methods.rs b/crates/oxc_linter/src/rules/jest/no_alias_methods.rs index 1d5482f110604..9f19e2984dd88 100644 --- a/crates/oxc_linter/src/rules/jest/no_alias_methods.rs +++ b/crates/oxc_linter/src/rules/jest/no_alias_methods.rs @@ -6,7 +6,6 @@ use oxc_span::Span; use crate::{ context::LintContext, - fixer::Fix, rule::Rule, utils::{collect_possible_jest_call_node, parse_expect_jest_fn_call, PossibleJestNode}, }; @@ -71,18 +70,18 @@ fn run<'a>(possible_jest_node: &PossibleJestNode<'a, '_>, ctx: &LintContext<'a>) if let Some(method_name) = BadAliasMethodName::from_str(alias.as_ref()) { let (name, canonical_name) = method_name.name_with_canonical(); - let mut start = matcher.span.start; - let mut end = matcher.span.end; + let mut span = matcher.span; // expect(a).not['toThrowError']() // matcher is the node of `toThrowError`, we only what to replace the content in the quotes. if matcher.element.is_string_literal() { - start += 1; - end -= 1; + span.start += 1; + span.end -= 1; } ctx.diagnostic_with_fix( no_alias_methods_diagnostic(name, canonical_name, matcher.span), - || Fix::new(canonical_name, Span::new(start, end)), + // || Fix::new(canonical_name, Span::new(start, end)), + |fixer| fixer.replace(span, canonical_name), ); } } diff --git a/crates/oxc_linter/src/rules/jest/no_deprecated_functions.rs b/crates/oxc_linter/src/rules/jest/no_deprecated_functions.rs index 294967b9d9d0a..a496087856836 100644 --- a/crates/oxc_linter/src/rules/jest/no_deprecated_functions.rs +++ b/crates/oxc_linter/src/rules/jest/no_deprecated_functions.rs @@ -6,7 +6,7 @@ use oxc_span::{GetSpan, Span}; use phf::{phf_map, Map}; use std::borrow::Cow; -use crate::{context::LintContext, fixer::Fix, rule::Rule}; +use crate::{context::LintContext, rule::Rule}; fn deprecated_function(x0: &str, x1: &str, span2: Span) -> OxcDiagnostic { OxcDiagnostic::warn( @@ -129,7 +129,7 @@ impl Rule for NoDeprecatedFunctions { if jest_version_num >= *base_version { ctx.diagnostic_with_fix( deprecated_function(&node_name, replacement, mem_expr.span()), - || Fix::new(*replacement, mem_expr.span()), + |fixer| fixer.replace(mem_expr.span(), *replacement), ); } } diff --git a/crates/oxc_linter/src/rules/jest/no_focused_tests.rs b/crates/oxc_linter/src/rules/jest/no_focused_tests.rs index 52ba5e1020d15..01022b6b7ad74 100644 --- a/crates/oxc_linter/src/rules/jest/no_focused_tests.rs +++ b/crates/oxc_linter/src/rules/jest/no_focused_tests.rs @@ -6,7 +6,6 @@ use oxc_span::Span; use crate::{ context::LintContext, - fixer::Fix, rule::Rule, utils::{ collect_possible_jest_call_node, parse_general_jest_fn_call, JestFnKind, JestGeneralFnKind, @@ -77,9 +76,8 @@ fn run<'a>(possible_jest_node: &PossibleJestNode<'a, '_>, ctx: &LintContext<'a>) } if name.starts_with('f') { - ctx.diagnostic_with_fix(no_focused_tests_diagnostic(call_expr.span), || { - let start = call_expr.span.start; - Fix::delete(Span::new(start, start + 1)) + ctx.diagnostic_with_fix(no_focused_tests_diagnostic(call_expr.span), |fixer| { + fixer.delete_range(Span::sized(call_expr.span.start, 1)) }); return; @@ -87,15 +85,12 @@ fn run<'a>(possible_jest_node: &PossibleJestNode<'a, '_>, ctx: &LintContext<'a>) let only_node = members.iter().find(|member| member.is_name_equal("only")); if let Some(only_node) = only_node { - ctx.diagnostic_with_fix(no_focused_tests_diagnostic(call_expr.span), || { - let span = only_node.span; - let start = span.start - 1; - let end = if matches!(only_node.element, MemberExpressionElement::IdentName(_)) { - span.end - } else { - span.end + 1 - }; - Fix::delete(Span::new(start, end)) + ctx.diagnostic_with_fix(no_focused_tests_diagnostic(call_expr.span), |fixer| { + let mut span = only_node.span.expand_left(1); + if !matches!(only_node.element, MemberExpressionElement::IdentName(_)) { + span = span.expand_right(1); + } + fixer.delete_range(span) }); } } diff --git a/crates/oxc_linter/src/rules/jest/no_jasmine_globals.rs b/crates/oxc_linter/src/rules/jest/no_jasmine_globals.rs index ba6b9d6178f85..66a7c5be2709e 100644 --- a/crates/oxc_linter/src/rules/jest/no_jasmine_globals.rs +++ b/crates/oxc_linter/src/rules/jest/no_jasmine_globals.rs @@ -9,7 +9,7 @@ use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::{GetSpan, Span}; -use crate::{context::LintContext, rule::Rule, Fix}; +use crate::{context::LintContext, rule::Rule}; fn no_jasmine_globals_diagnostic(x0: &str, x1: &str, span2: Span) -> OxcDiagnostic { OxcDiagnostic::warn(format!("eslint-plugin-jest(no-jasmine-globals): {x0:?}")) @@ -87,9 +87,9 @@ fn diagnostic_assign_expr<'a>(expr: &'a AssignmentExpression<'a>, ctx: &LintCont if let Expression::NumericLiteral(number_literal) = &expr.right { ctx.diagnostic_with_fix( no_jasmine_globals_diagnostic(COMMON_ERROR_TEXT, COMMON_HELP_TEXT, span), - || { + |fixer| { let content = format!("jest.setTimeout({})", number_literal.value); - Fix::new(content, expr.span) + fixer.replace(expr.span, content) }, ); return; @@ -119,7 +119,7 @@ fn diagnostic_call_expr<'a>(expr: &'a CallExpression<'a>, ctx: &LintContext) { if jasmine_property.available_in_jest_expect() { ctx.diagnostic_with_fix( no_jasmine_globals_diagnostic(error, help, span), - || Fix::new("expect", member_expr.object().span()), + |fixer| fixer.replace(member_expr.object().span(), "expect"), ); } else { ctx.diagnostic(no_jasmine_globals_diagnostic(error, help, span)); diff --git a/crates/oxc_linter/src/rules/jest/no_test_prefixes.rs b/crates/oxc_linter/src/rules/jest/no_test_prefixes.rs index 811c5ffd04eae..5bf2844895f78 100644 --- a/crates/oxc_linter/src/rules/jest/no_test_prefixes.rs +++ b/crates/oxc_linter/src/rules/jest/no_test_prefixes.rs @@ -6,7 +6,6 @@ use oxc_span::{GetSpan, Span}; use crate::{ context::LintContext, - fixer::Fix, rule::Rule, utils::{ collect_possible_jest_call_node, parse_general_jest_fn_call, JestGeneralFnKind, @@ -87,8 +86,8 @@ fn run<'a>(possible_jest_node: &PossibleJestNode<'a, '_>, ctx: &LintContext<'a>) let preferred_node_name = get_preferred_node_names(&jest_fn_call); - ctx.diagnostic_with_fix(no_test_prefixes_diagnostic(&preferred_node_name, span), || { - Fix::new(preferred_node_name, span) + ctx.diagnostic_with_fix(no_test_prefixes_diagnostic(&preferred_node_name, span), |fixer| { + fixer.replace(span, preferred_node_name) }); } diff --git a/crates/oxc_linter/src/rules/jest/no_untyped_mock_factory.rs b/crates/oxc_linter/src/rules/jest/no_untyped_mock_factory.rs index 5f6f69d816b31..e0fe71ca5f0df 100644 --- a/crates/oxc_linter/src/rules/jest/no_untyped_mock_factory.rs +++ b/crates/oxc_linter/src/rules/jest/no_untyped_mock_factory.rs @@ -1,6 +1,5 @@ use crate::{ context::LintContext, - fixer::Fix, rule::Rule, utils::{collect_possible_jest_call_node, PossibleJestNode}, }; @@ -140,16 +139,14 @@ impl NoUntypedMockFactory { string_literal.value.as_str(), property_span, ), - || { - let mut content = ctx.codegen(); + |fixer| { + let mut content = fixer.codegen(); content.print_str(b"("); + let span = Span::sized(string_literal.span.start - 1, 1); - Fix::new( - content.into_source_text(), - Span::new(string_literal.span.start - 1, string_literal.span.start), - ) + fixer.replace(span, content) }, ); } else if let Expression::Identifier(ident) = expr { diff --git a/crates/oxc_linter/src/rules/jest/prefer_comparison_matcher.rs b/crates/oxc_linter/src/rules/jest/prefer_comparison_matcher.rs index 791b8f155cc98..937a8b7199873 100644 --- a/crates/oxc_linter/src/rules/jest/prefer_comparison_matcher.rs +++ b/crates/oxc_linter/src/rules/jest/prefer_comparison_matcher.rs @@ -1,6 +1,6 @@ use crate::{ context::LintContext, - fixer::Fix, + fixer::RuleFixer, rule::Rule, utils::{ collect_possible_jest_call_node, is_equality_matcher, parse_expect_jest_fn_call, @@ -113,16 +113,16 @@ impl PreferComparisonMatcher { return; }; - ctx.diagnostic_with_fix(use_to_be_comparison(prefer_matcher_name, matcher.span), || { + ctx.diagnostic_with_fix(use_to_be_comparison(prefer_matcher_name, matcher.span), |fixer| { // This is to handle the case can be transform into the following case: // expect(value > 1,).toEqual(true,) => expect(value,).toBeGreaterThan(1,) // ^ ^ // Therefore the range starting after ',' and before '.' is called as call_span_end, // and the same as `arg_span_end`. - let call_span_end = Span::new(binary_expr.span.end, parent_call_expr.span.end) - .source_text(ctx.source_text()); - let arg_span_end = Span::new(matcher_arg_value.span.end, call_expr.span.end) - .source_text(ctx.source_text()); + let call_span_end = + fixer.source_range(Span::new(binary_expr.span.end, parent_call_expr.span.end)); + let arg_span_end = + fixer.source_range(Span::new(matcher_arg_value.span.end, call_expr.span.end)); let content = Self::building_code( binary_expr, call_span_end, @@ -130,9 +130,9 @@ impl PreferComparisonMatcher { parse_expect_jest_fn.local.as_bytes(), &parse_expect_jest_fn.modifiers(), prefer_matcher_name, - ctx, + fixer, ); - Fix::new(content, call_expr.span) + fixer.replace(call_expr.span, content) }); } @@ -171,16 +171,16 @@ impl PreferComparisonMatcher { } } - fn building_code( - binary_expr: &BinaryExpression, + fn building_code<'a>( + binary_expr: &BinaryExpression<'a>, call_span_end: &str, arg_span_end: &str, local_name: &[u8], - modifiers: &[&KnownMemberExpressionProperty], + modifiers: &[&KnownMemberExpressionProperty<'a>], prefer_matcher_name: &str, - ctx: &LintContext, + fixer: RuleFixer<'_, 'a>, ) -> String { - let mut content = ctx.codegen(); + let mut content = fixer.codegen(); content.print_str(local_name); content.print(b'('); content.print_expression(&binary_expr.left); diff --git a/crates/oxc_linter/src/rules/jest/prefer_expect_resolves.rs b/crates/oxc_linter/src/rules/jest/prefer_expect_resolves.rs index 9c3c6df1651a9..f32fd9fc4c608 100644 --- a/crates/oxc_linter/src/rules/jest/prefer_expect_resolves.rs +++ b/crates/oxc_linter/src/rules/jest/prefer_expect_resolves.rs @@ -9,7 +9,7 @@ use oxc_span::Span; use crate::{ context::LintContext, - fixer::Fix, + fixer::{Fix, RuleFixer}, rule::Rule, utils::{ collect_possible_jest_call_node, parse_expect_jest_fn_call, ParsedExpectFnCall, @@ -107,22 +107,22 @@ impl PreferExpectResolves { return; }; - ctx.diagnostic_with_fix(expect_resolves(await_expr.span), || { - let content = Self::build_code(&jest_expect_fn_call, call_expr, ident.span, ctx); - Fix::new(content, call_expr.span) + ctx.diagnostic_with_fix(expect_resolves(await_expr.span), |fixer| { + Self::fix(fixer, &jest_expect_fn_call, call_expr, ident.span) }); } - fn build_code<'a>( - jest_expect_fn_call: &ParsedExpectFnCall, + fn fix<'c, 'a: 'c>( + fixer: RuleFixer<'c, 'a>, + jest_expect_fn_call: &ParsedExpectFnCall<'a>, call_expr: &CallExpression<'a>, ident_span: Span, - ctx: &LintContext<'a>, - ) -> String { - let mut formatter = ctx.codegen(); + ) -> Fix<'a> { + let mut formatter = fixer.codegen(); let first = call_expr.arguments.first().unwrap(); let Argument::AwaitExpression(await_expr) = first else { - return formatter.into_source_text(); + // return formatter.into_source_text(); + return fixer.replace(call_expr.span, formatter); }; let offset = match &await_expr.argument { @@ -140,9 +140,9 @@ impl PreferExpectResolves { formatter.print_hard_space(); formatter.print_str(jest_expect_fn_call.local.as_bytes()); formatter.print(b'('); - formatter.print_str(arg_span.source_text(ctx.source_text()).as_bytes()); + formatter.print_str(fixer.source_range(arg_span)); formatter.print_str(b".resolves"); - formatter.into_source_text() + fixer.replace(call_expr.span, formatter) } } diff --git a/crates/oxc_linter/src/rules/jest/prefer_lowercase_title.rs b/crates/oxc_linter/src/rules/jest/prefer_lowercase_title.rs index e37cf41e6637e..1f7da11fd4068 100644 --- a/crates/oxc_linter/src/rules/jest/prefer_lowercase_title.rs +++ b/crates/oxc_linter/src/rules/jest/prefer_lowercase_title.rs @@ -6,7 +6,6 @@ use oxc_span::Span; use crate::{ context::LintContext, - fixer::Fix, rule::Rule, utils::{ collect_possible_jest_call_node, parse_jest_fn_call, JestFnKind, JestGeneralFnKind, @@ -171,9 +170,7 @@ impl Rule for PreferLowercaseTitle { impl PreferLowercaseTitle { fn run<'a>(&self, possible_jest_node: &PossibleJestNode<'a, '_>, ctx: &LintContext<'a>) { - let scopes = ctx.scopes(); let node = possible_jest_node.node; - let ignores = Self::populate_ignores(&self.ignore); let AstKind::CallExpression(call_expr) = node.kind() else { return; }; @@ -183,6 +180,9 @@ impl PreferLowercaseTitle { return; }; + let scopes = ctx.scopes(); + let ignores = Self::populate_ignores(&self.ignore); + if ignores.contains(&jest_fn_call.name.as_ref()) { return; } @@ -200,61 +200,12 @@ impl PreferLowercaseTitle { }; if let Argument::StringLiteral(string_expr) = arg { - if string_expr.value.is_empty() - || self.allowed_prefixes.iter().any(|name| string_expr.value.starts_with(name)) - { - return; - } - - let Some(first_char) = string_expr.value.chars().next() else { - return; - }; - - if first_char == first_char.to_ascii_lowercase() { - return; - } - - ctx.diagnostic_with_fix( - unexpected_lowercase(string_expr.value.as_str(), string_expr.span), - || { - let mut content = ctx.codegen(); - content.print_str(first_char.to_ascii_lowercase().to_string().as_bytes()); - Fix::new( - content.into_source_text(), - Span::new(string_expr.span.start + 1, string_expr.span.start + 2), - ) - }, - ); + self.lint_string(ctx, string_expr.value.as_str(), string_expr.span); } else if let Argument::TemplateLiteral(template_expr) = arg { let Some(template_string) = template_expr.quasi() else { return; }; - - if template_string.is_empty() - || self.allowed_prefixes.iter().any(|name| template_string.starts_with(name)) - { - return; - } - - let Some(first_char) = template_string.chars().next() else { - return; - }; - - if first_char == first_char.to_ascii_lowercase() { - return; - } - - ctx.diagnostic_with_fix( - unexpected_lowercase(template_string.as_str(), template_expr.span), - || { - let mut content = ctx.codegen(); - content.print_str(first_char.to_ascii_lowercase().to_string().as_bytes()); - Fix::new( - content.into_source_text(), - Span::new(template_expr.span.start + 1, template_expr.span.start + 2), - ) - }, - ); + self.lint_string(ctx, template_string, template_expr.span); } } @@ -279,6 +230,25 @@ impl PreferLowercaseTitle { ignores } + fn lint_string<'a>(&self, ctx: &LintContext<'a>, literal: &'a str, span: Span) { + if literal.is_empty() || self.allowed_prefixes.iter().any(|name| literal.starts_with(name)) + { + return; + } + + let Some(first_char) = literal.chars().next() else { + return; + }; + + let lower = first_char.to_ascii_lowercase(); + if first_char == lower { + return; + } + + ctx.diagnostic_with_fix(unexpected_lowercase(literal, span), |fixer| { + fixer.replace(Span::sized(span.start + 1, 1), lower.to_string()) + }); + } } #[test] diff --git a/crates/oxc_linter/src/rules/jest/prefer_mock_promise_shorthand.rs b/crates/oxc_linter/src/rules/jest/prefer_mock_promise_shorthand.rs index d7b9128044fd1..80d6a650eb261 100644 --- a/crates/oxc_linter/src/rules/jest/prefer_mock_promise_shorthand.rs +++ b/crates/oxc_linter/src/rules/jest/prefer_mock_promise_shorthand.rs @@ -7,7 +7,7 @@ use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::{Atom, Span}; -use crate::{context::LintContext, fixer::Fix, rule::Rule, utils::get_node_name}; +use crate::{context::LintContext, fixer::RuleFixer, rule::Rule, utils::get_node_name}; fn use_mock_shorthand(x0: &str, span1: Span) -> OxcDiagnostic { OxcDiagnostic::warn("eslint-plugin-jest(prefer-mock-promise-shorthand): Prefer mock resolved/rejected shorthands for promises").with_help(format!("Prefer {x0:?}")).with_labels([span1.into()]) @@ -127,7 +127,7 @@ impl PreferMockPromiseShorthand { property_span: Span, arg_span: Option, arg_expr: &'a Expression<'a>, - ctx: &LintContext, + ctx: &LintContext<'a>, ) { let Expression::CallExpression(call_expr) = arg_expr else { return; @@ -142,7 +142,7 @@ impl PreferMockPromiseShorthand { if is_once { "mockResolvedValueOnce" } else { "mockResolvedValue" }; let mock_promise_reject = if is_once { "mockRejectedValueOnce" } else { "mockRejectedValue" }; - let prefer_name = + let prefer_name: &'static str = if arg_name.ends_with("reject") { mock_promise_reject } else { mock_promise_resolve }; let fix_span = arg_span.unwrap_or(call_expr.span); @@ -150,9 +150,10 @@ impl PreferMockPromiseShorthand { if call_expr.arguments.len() <= 1 { ctx.diagnostic_with_fix( use_mock_shorthand(Atom::from(prefer_name).as_str(), property_span), - || { - let content = Self::fix(prefer_name, call_expr, ctx); - Fix::new(content, Span::new(property_span.start, fix_span.end)) + |fixer| { + let content = Self::fix(fixer, prefer_name, call_expr); + let span = Span::new(property_span.start, fix_span.end); + fixer.replace(span, content) }, ); } else { @@ -160,9 +161,13 @@ impl PreferMockPromiseShorthand { } } - fn fix(prefer_name: &str, call_expr: &CallExpression, ctx: &LintContext) -> String { - let mut content = ctx.codegen(); - content.print_str(prefer_name.as_bytes()); + fn fix<'a>( + fixer: RuleFixer<'_, 'a>, + prefer_name: &'a str, + call_expr: &CallExpression<'a>, + ) -> String { + let mut content = fixer.codegen(); + content.print_str(prefer_name); content.print(b'('); if call_expr.arguments.is_empty() { content.print_str(b"undefined"); diff --git a/crates/oxc_linter/src/rules/jest/prefer_spy_on.rs b/crates/oxc_linter/src/rules/jest/prefer_spy_on.rs index 8f4d7daa1c613..fedfdf21002fc 100644 --- a/crates/oxc_linter/src/rules/jest/prefer_spy_on.rs +++ b/crates/oxc_linter/src/rules/jest/prefer_spy_on.rs @@ -13,7 +13,7 @@ use oxc_span::Span; use crate::{ context::LintContext, - fixer::Fix, + fixer::RuleFixer, rule::Rule, utils::{get_node_name, parse_general_jest_fn_call, PossibleJestNode}, }; @@ -111,7 +111,7 @@ impl PreferSpyOn { ctx.diagnostic_with_fix( use_jest_spy_on(Span::new(call_expr.span.start, first_fn_member.span.end)), - || { + |fixer| { let (end, has_mock_implementation) = if jest_fn_call.members.len() > 1 { let second = &jest_fn_call.members[1]; let has_mock_implementation = jest_fn_call @@ -127,8 +127,8 @@ impl PreferSpyOn { ) }; let content = - Self::build_code(call_expr, left_assign, has_mock_implementation, ctx); - Fix::new(content, Span::new(assign_expr.span.start, end)) + Self::build_code(call_expr, left_assign, has_mock_implementation, fixer); + fixer.replace(Span::new(assign_expr.span.start, end), content) }, ); } @@ -137,9 +137,9 @@ impl PreferSpyOn { call_expr: &'a CallExpression<'a>, left_assign: &MemberExpression, has_mock_implementation: bool, - ctx: &LintContext, + fixer: RuleFixer<'_, 'a>, ) -> String { - let mut formatter = ctx.codegen(); + let mut formatter = fixer.codegen(); formatter.print_str(b"jest.spyOn("); match left_assign { diff --git a/crates/oxc_linter/src/rules/jest/prefer_strict_equal.rs b/crates/oxc_linter/src/rules/jest/prefer_strict_equal.rs index f7ddb92c71d49..0a5a85eaa1bde 100644 --- a/crates/oxc_linter/src/rules/jest/prefer_strict_equal.rs +++ b/crates/oxc_linter/src/rules/jest/prefer_strict_equal.rs @@ -1,6 +1,5 @@ use crate::{ context::LintContext, - fixer::Fix, rule::Rule, utils::{collect_possible_jest_call_node, parse_expect_jest_fn_call, PossibleJestNode}, }; @@ -65,16 +64,14 @@ impl PreferStrictEqual { }; if matcher_name.eq("toEqual") { - ctx.diagnostic_with_fix(use_to_strict_equal(matcher.span), || { - let mut formatter = ctx.codegen(); - formatter.print_str( - matcher - .span - .source_text(ctx.source_text()) - .replace(matcher_name.to_string().as_str(), "toStrictEqual") - .as_bytes(), - ); - Fix::new(formatter.into_source_text(), matcher.span) + ctx.diagnostic_with_fix(use_to_strict_equal(matcher.span), |fixer| { + let replacement = match fixer.source_range(matcher.span).chars().next().unwrap() { + '\'' => "'toStrictEqual'", + '"' => "\"toStrictEqual\"", + '`' => "`toStrictEqual`", + _ => "toStrictEqual", + }; + fixer.replace(matcher.span, replacement) }); } } diff --git a/crates/oxc_linter/src/rules/jest/prefer_to_be.rs b/crates/oxc_linter/src/rules/jest/prefer_to_be.rs index 878725a8decfd..369ad88e58106 100644 --- a/crates/oxc_linter/src/rules/jest/prefer_to_be.rs +++ b/crates/oxc_linter/src/rules/jest/prefer_to_be.rs @@ -9,7 +9,6 @@ use oxc_span::Span; use crate::{ context::LintContext, - fixer::Fix, rule::Rule, utils::{ collect_possible_jest_call_node, is_equality_matcher, parse_expect_jest_fn_call, @@ -230,39 +229,40 @@ impl PreferToBe { let maybe_not_modifier = modifiers.iter().find(|modifier| modifier.is_name_equal("not")); if kind == &PreferToBeKind::Undefined { - ctx.diagnostic_with_fix(use_to_be_undefined(span), || { + ctx.diagnostic_with_fix(use_to_be_undefined(span), |fixer| { let new_matcher = if is_cmp_mem_expr { "[\"toBeUndefined\"]()" } else { "toBeUndefined()" }; - if let Some(not_modifier) = maybe_not_modifier { - Fix::new(new_matcher.to_string(), Span::new(not_modifier.span.start, end)) + let span = if let Some(not_modifier) = maybe_not_modifier { + Span::new(not_modifier.span.start, end) } else { - Fix::new(new_matcher.to_string(), Span::new(span.start, end)) - } + Span::new(span.start, end) + }; + fixer.replace(span, new_matcher) }); } else if kind == &PreferToBeKind::Defined { - ctx.diagnostic_with_fix(use_to_be_defined(span), || { + ctx.diagnostic_with_fix(use_to_be_defined(span), |fixer| { let (new_matcher, start) = if is_cmp_mem_expr { ("[\"toBeDefined\"]()", modifiers.first().unwrap().span.end) } else { ("toBeDefined()", maybe_not_modifier.unwrap().span.start) }; - Fix::new(new_matcher.to_string(), Span::new(start, end)) + fixer.replace(Span::new(start, end), new_matcher) }); } else if kind == &PreferToBeKind::Null { - ctx.diagnostic_with_fix(use_to_be_null(span), || { + ctx.diagnostic_with_fix(use_to_be_null(span), |fixer| { let new_matcher = if is_cmp_mem_expr { "\"toBeNull\"]()" } else { "toBeNull()" }; - Fix::new(new_matcher.to_string(), Span::new(span.start, end)) + fixer.replace(Span::new(span.start, end), new_matcher) }); } else if kind == &PreferToBeKind::NaN { - ctx.diagnostic_with_fix(use_to_be_na_n(span), || { + ctx.diagnostic_with_fix(use_to_be_na_n(span), |fixer| { let new_matcher = if is_cmp_mem_expr { "\"toBeNaN\"]()" } else { "toBeNaN()" }; - Fix::new(new_matcher.to_string(), Span::new(span.start, end)) + fixer.replace(Span::new(span.start, end), new_matcher) }); } else { - ctx.diagnostic_with_fix(use_to_be(span), || { + ctx.diagnostic_with_fix(use_to_be(span), |fixer| { let new_matcher = if is_cmp_mem_expr { "\"toBe\"" } else { "toBe" }; - Fix::new(new_matcher.to_string(), span) + fixer.replace(span, new_matcher) }); } } diff --git a/crates/oxc_linter/src/rules/jest/prefer_to_have_length.rs b/crates/oxc_linter/src/rules/jest/prefer_to_have_length.rs index eba6ec8a1b949..e819a4f98b0e7 100644 --- a/crates/oxc_linter/src/rules/jest/prefer_to_have_length.rs +++ b/crates/oxc_linter/src/rules/jest/prefer_to_have_length.rs @@ -9,7 +9,7 @@ use oxc_span::{GetSpan, Span}; use crate::{ context::LintContext, - fixer::Fix, + fixer::RuleFixer, rule::Rule, utils::{ collect_possible_jest_call_node, is_equality_matcher, parse_expect_jest_fn_call, @@ -146,24 +146,24 @@ impl PreferToHaveLength { return; } - ctx.diagnostic_with_fix(use_to_have_length(matcher.span), || { - let code = Self::build_code(static_mem_expr, kind, property_name, ctx); + ctx.diagnostic_with_fix(use_to_have_length(matcher.span), |fixer| { + let code = Self::build_code(fixer, static_mem_expr, kind, property_name); let end = if call_expr.arguments.len() > 0 { call_expr.arguments.first().unwrap().span().start } else { matcher.span.end }; - Fix::new(code, Span::new(call_expr.span.start, end - 1)) + fixer.replace(Span::new(call_expr.span.start, end - 1), code) }); } - fn build_code( - mem_expr: &MemberExpression, + fn build_code<'a>( + fixer: RuleFixer<'_, 'a>, + mem_expr: &MemberExpression<'a>, kind: Option<&str>, property_name: Option<&str>, - ctx: &LintContext<'_>, ) -> String { - let mut formatter = ctx.codegen(); + let mut formatter = fixer.codegen(); let Expression::Identifier(prop_ident) = mem_expr.object() else { return formatter.into_source_text(); }; diff --git a/crates/oxc_linter/src/rules/jest/prefer_todo.rs b/crates/oxc_linter/src/rules/jest/prefer_todo.rs index d92216c281edb..26ac6606cb4a6 100644 --- a/crates/oxc_linter/src/rules/jest/prefer_todo.rs +++ b/crates/oxc_linter/src/rules/jest/prefer_todo.rs @@ -5,11 +5,11 @@ use oxc_ast::{ use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; -use oxc_span::Span; +use oxc_span::{GetSpan, Span}; use crate::{ context::LintContext, - fixer::Fix, + fixer::{Fix, RuleFixer}, rule::Rule, utils::{ collect_possible_jest_call_node, is_type_of_jest_fn_call, JestFnKind, JestGeneralFnKind, @@ -79,16 +79,26 @@ fn run<'a>(possible_jest_node: &PossibleJestNode<'a, '_>, ctx: &LintContext<'a>) } if counts == 1 && !filter_todo_case(call_expr) { - let (content, span) = get_fix_content(call_expr); - ctx.diagnostic_with_fix(un_implemented_test_diagnostic(span), || { - Fix::new(content, span) + let span = call_expr + .callee + .as_member_expression() + .map_or(call_expr.callee.span(), GetSpan::span); + ctx.diagnostic_with_fix(un_implemented_test_diagnostic(span), |fixer| { + if let Expression::Identifier(ident) = &call_expr.callee { + return fixer.replace(Span::empty(ident.span.end), ".todo"); + } + if let Some(mem_expr) = call_expr.callee.as_member_expression() { + if let Some((span, _)) = mem_expr.static_property_info() { + return fixer.replace(span, "todo"); + } + } + fixer.delete_range(call_expr.span) }); } if counts > 1 && is_empty_function(call_expr) { - ctx.diagnostic_with_fix(empty_test(call_expr.span), || { - let (content, span) = build_code(call_expr, ctx); - Fix::new(content, span) + ctx.diagnostic_with_fix(empty_test(call_expr.span), |fixer| { + build_code(fixer, call_expr) }); } } @@ -128,20 +138,8 @@ fn is_empty_function(expr: &CallExpression) -> bool { } } -fn get_fix_content<'a>(expr: &'a CallExpression<'a>) -> (&'a str, Span) { - if let Expression::Identifier(ident) = &expr.callee { - return (".todo", Span::new(ident.span.end, ident.span.end)); - } - if let Some(mem_expr) = expr.callee.as_member_expression() { - if let Some((span, _)) = mem_expr.static_property_info() { - return ("todo", span); - } - } - ("", expr.span) -} - -fn build_code(expr: &CallExpression, ctx: &LintContext) -> (String, Span) { - let mut formatter = ctx.codegen(); +fn build_code<'a>(fixer: RuleFixer<'_, 'a>, expr: &CallExpression<'a>) -> Fix<'a> { + let mut formatter = fixer.codegen(); match &expr.callee { Expression::Identifier(ident) => { @@ -180,7 +178,7 @@ fn build_code(expr: &CallExpression, ctx: &LintContext) -> (String, Span) { formatter.print(b')'); } - (formatter.into_source_text(), expr.span) + fixer.replace(expr.span, formatter) } #[test] diff --git a/crates/oxc_linter/src/rules/oxc/no_const_enum.rs b/crates/oxc_linter/src/rules/oxc/no_const_enum.rs index f3156b0fd4865..4e1151842c595 100644 --- a/crates/oxc_linter/src/rules/oxc/no_const_enum.rs +++ b/crates/oxc_linter/src/rules/oxc/no_const_enum.rs @@ -3,7 +3,7 @@ use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::Span; -use crate::{context::LintContext, fixer::Fix, rule::Rule, AstNode}; +use crate::{context::LintContext, rule::Rule, AstNode}; fn no_const_enum_diagnostic(span0: Span) -> OxcDiagnostic { OxcDiagnostic::warn("oxc(no-const-enum): Unexpected const enum") @@ -47,21 +47,20 @@ impl Rule for NoConstEnum { return; }; - ctx.diagnostic_with_fix(no_const_enum_diagnostic(const_enum.span), || { + ctx.diagnostic_with_fix(no_const_enum_diagnostic(const_enum.span), |fixer| { // const enum Color { Red, Green, Blue } // ^ let start = const_enum.span.start; // const enum Color { Red, Green, Blue } // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - let text = Span::new(start, enum_decl.span.end).source_text(ctx.source_text()); + let text = fixer.source_range(Span::new(start, enum_decl.span.end)); // const enum Color { Red, Green, Blue } // ^^^^^^ let offset = u32::try_from(text.find("enum").unwrap_or(1)).unwrap_or(1); // 1 is the default offset - let end = start + offset; - Fix::delete(Span::new(start, end)) + fixer.delete_range(Span::sized(start, offset)) }); } } diff --git a/crates/oxc_linter/src/rules/typescript/array_type.rs b/crates/oxc_linter/src/rules/typescript/array_type.rs index c6b8ec6cac78a..d8edbf3d33b0c 100644 --- a/crates/oxc_linter/src/rules/typescript/array_type.rs +++ b/crates/oxc_linter/src/rules/typescript/array_type.rs @@ -7,7 +7,7 @@ use oxc_macros::declare_oxc_lint; use oxc_semantic::AstNode; use oxc_span::Span; -use crate::{context::LintContext, fixer::Fix, rule::Rule}; +use crate::{context::LintContext, rule::Rule}; #[derive(Debug, Default, Clone)] pub struct ArrayType(Box); @@ -202,15 +202,12 @@ fn check_and_report_error_generic( return; }; - ctx.diagnostic_with_fix(diagnostic, || { + ctx.diagnostic_with_fix(diagnostic, |fixer| { let type_text = &source_text[element_type_span.start as usize..element_type_span.end as usize]; let array_type_identifier = if is_readonly { "ReadonlyArray" } else { "Array" }; - Fix::new( - array_type_identifier.to_string() + "<" + type_text + ">", - Span::new(type_reference_span.start, type_reference_span.end), - ) + fixer.replace(type_reference_span, format!("{array_type_identifier}<{type_text}>")) }); } @@ -234,7 +231,7 @@ fn check_and_report_error_array( if matches!(config, ArrayOption::Generic) { return; } - let readonly_prefix: &str = if is_readonly_array_type { "readonly " } else { "" }; + let readonly_prefix: &'static str = if is_readonly_array_type { "readonly " } else { "" }; let class_name = if is_readonly_array_type { "ReadonlyArray" } else { "Array" }; let type_params = &ts_type_reference.type_parameters; @@ -248,8 +245,8 @@ fn check_and_report_error_array( ts_type_reference.span, ), }; - ctx.diagnostic_with_fix(diagnostic, || { - Fix::new(readonly_prefix.to_string() + "any[]", ts_type_reference.span) + ctx.diagnostic_with_fix(diagnostic, |fixer| { + fixer.replace(ts_type_reference.span, readonly_prefix.to_string() + "any[]") }); return; } @@ -276,17 +273,6 @@ fn check_and_report_error_array( return; }; - let type_text = - &ctx.source_text()[element_type_span.start as usize..element_type_span.end as usize]; - - let mut start = String::from(if parent_parens { "(" } else { "" }); - start.push_str(readonly_prefix); - start.push_str(if type_parens { "(" } else { "" }); - - let mut end = String::from(if type_parens { ")" } else { "" }); - end.push_str("[]"); - end.push_str(if parent_parens { ")" } else { "" }); - let message_type = get_message_type(first_type_param, ctx.source_text()); let diagnostic = match config { ArrayOption::Array => { @@ -299,8 +285,17 @@ fn check_and_report_error_array( ts_type_reference.span, ), }; - ctx.diagnostic_with_fix(diagnostic, || { - Fix::new(start + type_text + end.as_str(), ts_type_reference.span) + ctx.diagnostic_with_fix(diagnostic, |fixer| { + let mut start = String::from(if parent_parens { "(" } else { "" }); + start.push_str(readonly_prefix); + start.push_str(if type_parens { "(" } else { "" }); + + let mut end = String::from(if type_parens { ")" } else { "" }); + end.push_str("[]"); + end.push_str(if parent_parens { ")" } else { "" }); + + let type_text = fixer.source_range(element_type_span); + fixer.replace(ts_type_reference.span, start + type_text + end.as_str()) }); } 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 25fc4b6f281c3..84f9037cfc87f 100644 --- a/crates/oxc_linter/src/rules/typescript/ban_ts_comment.rs +++ b/crates/oxc_linter/src/rules/typescript/ban_ts_comment.rs @@ -4,7 +4,7 @@ use oxc_macros::declare_oxc_lint; use oxc_span::Span; use regex::Regex; -use crate::{context::LintContext, fixer::Fix, rule::Rule}; +use crate::{context::LintContext, rule::Rule}; fn comment(x0: &str, span1: Span) -> OxcDiagnostic { OxcDiagnostic::warn(format!("typescript-eslint(ban-ts-comment): Do not use @ts-{x0} because it alters compilation errors.")).with_labels([span1.into()]) @@ -142,7 +142,7 @@ impl Rule for BanTsComment { fn run_once(&self, ctx: &LintContext) { let comments = ctx.semantic().trivias().comments(); for (kind, span) in comments { - let raw = span.source_text(ctx.semantic().source_text()); + let raw = ctx.source_range(span); if let Some(captures) = find_ts_comment_directive(raw, kind.is_single_line()) { // safe to unwrap, if capture success, it can always capture one of the four directives let (directive, description) = (captures.0, captures.1); @@ -164,10 +164,10 @@ impl Rule for BanTsComment { if directive == "ignore" { ctx.diagnostic_with_fix( ignore_instead_of_expect_error(span), - || { - Fix::new( - raw.replace("@ts-ignore", "@ts-expect-error"), + |fixer| { + fixer.replace( span, + raw.replace("@ts-ignore", "@ts-expect-error"), ) }, ); diff --git a/crates/oxc_linter/src/rules/typescript/ban_tslint_comment.rs b/crates/oxc_linter/src/rules/typescript/ban_tslint_comment.rs index 10e7d9a3ed087..ab3dcaf2dd6d5 100644 --- a/crates/oxc_linter/src/rules/typescript/ban_tslint_comment.rs +++ b/crates/oxc_linter/src/rules/typescript/ban_tslint_comment.rs @@ -5,7 +5,7 @@ use oxc_macros::declare_oxc_lint; use oxc_span::Span; use regex::Regex; -use crate::{context::LintContext, fixer::Fix, rule::Rule}; +use crate::{context::LintContext, rule::Rule}; fn ban_tslint_comment_diagnostic(x0: &str, span1: Span) -> OxcDiagnostic { OxcDiagnostic::warn(format!( @@ -48,7 +48,7 @@ impl Rule for BanTslintComment { ctx.diagnostic_with_fix( ban_tslint_comment_diagnostic(raw.trim(), comment_span), - || Fix::delete(comment_span), + |fixer| fixer.delete_range(comment_span), ); } } diff --git a/crates/oxc_linter/src/rules/typescript/consistent_indexed_object_style.rs b/crates/oxc_linter/src/rules/typescript/consistent_indexed_object_style.rs index ac275ef51f77d..97664f52161fa 100644 --- a/crates/oxc_linter/src/rules/typescript/consistent_indexed_object_style.rs +++ b/crates/oxc_linter/src/rules/typescript/consistent_indexed_object_style.rs @@ -1,12 +1,12 @@ use oxc_ast::{ - ast::{TSSignature, TSType, TSTypeName, TSTypeReference}, + ast::{TSSignature, TSType, TSTypeName}, AstKind, }; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::Span; -use crate::{context::LintContext, fixer::Fix, rule::Rule, AstNode}; +use crate::{context::LintContext, rule::Rule, AstNode}; fn consistent_indexed_object_style_diagnostic(a: &str, b: &str, span: Span) -> OxcDiagnostic { OxcDiagnostic::warn(format!( @@ -219,49 +219,35 @@ impl Rule for ConsistentIndexedObjectStyle { return; } - let fixer = fix_for_index_signature(ctx, tref); - match fixer { - Some(fix) => { - ctx.diagnostic_with_fix( - consistent_indexed_object_style_diagnostic( - "index signature", - "record", - tref.span, - ), - || fix, - ); - } - None => { - ctx.diagnostic(consistent_indexed_object_style_diagnostic( + if let Some(TSType::TSStringKeyword(first)) = + &tref.type_parameters.as_ref().and_then(|params| params.params.first()) + { + ctx.diagnostic_with_fix( + consistent_indexed_object_style_diagnostic( "index signature", "record", tref.span, - )); - } + ), + |fixer| { + let key = fixer.source_range(first.span); + let params_span = Span::new(first.span.end + 2, tref.span.end - 1); + let params = fixer.source_range(params_span); + let content = format!("{{ [key: {key}]: {params} }}"); + fixer.replace(tref.span, content) + }, + ); + } else { + ctx.diagnostic(consistent_indexed_object_style_diagnostic( + "index signature", + "record", + tref.span, + )); } } } } } -fn fix_for_index_signature<'a>( - ctx: &LintContext<'a>, - tref: &TSTypeReference<'a>, -) -> Option> { - let params = &tref.type_parameters.as_ref()?; - - let end = tref.span.end; - let start = tref.span.start; - - let TSType::TSStringKeyword(first) = ¶ms.params[0] else { - return None; - }; - - let key = &ctx.source_text()[first.span.start as usize..first.span.end as usize]; - let params = &ctx.source_text()[(first.span.end + 2) as usize..(end - 1) as usize]; - Some(Fix::new(format!("{{ [key: {key}]: {params} }}"), Span::new(start, end))) -} - #[test] fn test() { use crate::tester::Tester; diff --git a/crates/oxc_linter/src/rules/typescript/consistent_type_definitions.rs b/crates/oxc_linter/src/rules/typescript/consistent_type_definitions.rs index 926fa4eedbbe2..225a499faffb6 100644 --- a/crates/oxc_linter/src/rules/typescript/consistent_type_definitions.rs +++ b/crates/oxc_linter/src/rules/typescript/consistent_type_definitions.rs @@ -7,7 +7,7 @@ use oxc_diagnostics::{LabeledSpan, OxcDiagnostic}; use oxc_macros::declare_oxc_lint; use oxc_span::Span; -use crate::{context::LintContext, fixer::Fix, rule::Rule, AstNode}; +use crate::{context::LintContext, rule::Rule, AstNode}; fn consistent_type_definitions_diagnostic(x0: &str, x1: &str, span2: Span) -> OxcDiagnostic { OxcDiagnostic::warn("typescript-eslint(consistent-type-definitions):") @@ -100,10 +100,10 @@ impl Rule for ConsistentTypeDefinitions { "type", Span::new(start, start + 4), ), - || { - Fix::new( - format!("interface {name} {body}"), + |fixer| { + fixer.replace( Span::new(start, decl.span.end), + format!("interface {name} {body}"), ) }, ); @@ -157,10 +157,10 @@ impl Rule for ConsistentTypeDefinitions { "interface", Span::new(decl.span.start, decl.span.start + 9), ), - || { - Fix::new( + |fixer| { + fixer.replace( + exp.span, format!("type {name} = {body}{extends}\nexport default {name}"), - Span::new(exp.span.start, exp.span.end), ) }, ); @@ -215,10 +215,10 @@ impl Rule for ConsistentTypeDefinitions { "interface", Span::new(start, start + 9), ), - || { - Fix::new( - format!("type {name} = {body}{extends}"), + |fixer| { + fixer.replace( Span::new(start, decl.span.end), + format!("type {name} = {body}{extends}"), ) }, ); diff --git a/crates/oxc_linter/src/rules/typescript/no_explicit_any.rs b/crates/oxc_linter/src/rules/typescript/no_explicit_any.rs index a7390dae061d8..a9c57742686c0 100644 --- a/crates/oxc_linter/src/rules/typescript/no_explicit_any.rs +++ b/crates/oxc_linter/src/rules/typescript/no_explicit_any.rs @@ -1,4 +1,3 @@ -use crate::Fix; use oxc_ast::AstKind; use oxc_diagnostics::OxcDiagnostic; @@ -97,8 +96,8 @@ impl Rule for NoExplicitAny { } if self.fix_to_unknown { - ctx.diagnostic_with_fix(no_explicit_any_diagnostic(any.span), || { - Fix::new("unknown", any.span) + ctx.diagnostic_with_fix(no_explicit_any_diagnostic(any.span), |fixer| { + fixer.replace(any.span, "unknown") }); } else { ctx.diagnostic(no_explicit_any_diagnostic(any.span)); diff --git a/crates/oxc_linter/src/rules/typescript/prefer_as_const.rs b/crates/oxc_linter/src/rules/typescript/prefer_as_const.rs index 23509060daa10..6d5c59a8c2860 100644 --- a/crates/oxc_linter/src/rules/typescript/prefer_as_const.rs +++ b/crates/oxc_linter/src/rules/typescript/prefer_as_const.rs @@ -5,7 +5,7 @@ use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::Span; -use crate::{context::LintContext, fixer::Fix, rule::Rule, AstNode}; +use crate::{context::LintContext, rule::Rule, AstNode}; fn prefer_as_const_diagnostic(span0: Span) -> OxcDiagnostic { OxcDiagnostic::warn("typescript-eslint(prefer-as-const): Expected a `const` assertion instead of a literal type annotation.") @@ -117,10 +117,8 @@ fn check_and_report( }; if let Some(span) = error_span { if can_fix { - ctx.diagnostic_with_fix(prefer_as_const_diagnostic(span), || { - let start = span.start; - let end = span.end; - Fix::new("const", Span::new(start, end)) + ctx.diagnostic_with_fix(prefer_as_const_diagnostic(span), |fixer| { + fixer.replace(span, "const") }); } else { ctx.diagnostic(prefer_as_const_diagnostic(span)); diff --git a/crates/oxc_linter/src/rules/typescript/prefer_function_type.rs b/crates/oxc_linter/src/rules/typescript/prefer_function_type.rs index cea6e070fb5d9..6ab4a6e80d2c8 100644 --- a/crates/oxc_linter/src/rules/typescript/prefer_function_type.rs +++ b/crates/oxc_linter/src/rules/typescript/prefer_function_type.rs @@ -124,24 +124,22 @@ fn check_member(member: &TSSignature, node: &AstNode<'_>, ctx: &LintContext<'_>) match node.kind() { AstKind::TSInterfaceDeclaration(interface_decl) => { if let Some(type_parameters) = &interface_decl.type_parameters { - let node_start = interface_decl.span.start; - let node_end = interface_decl.span.end; - let type_name = &source_code[interface_decl.id.span.start as usize - ..type_parameters.span.end as usize]; - ctx.diagnostic_with_fix( prefer_function_type_diagnostic(&suggestion, decl.span), - || { - Fix::new( + |fixer| { + let mut span = interface_decl.id.span; + span.end = type_parameters.span.end; + let type_name = fixer.source_range(span); + fixer.replace( + interface_decl.span, format!("type {type_name} = {suggestion};"), - Span::new(node_start, node_end), ) }, ); } else { ctx.diagnostic_with_fix( prefer_function_type_diagnostic(&suggestion, decl.span), - || { + |_| { let mut is_parent_exported = false; let mut node_start = interface_decl.span.start; let mut node_end = interface_decl.span.end; @@ -228,11 +226,8 @@ fn check_member(member: &TSSignature, node: &AstNode<'_>, ctx: &LintContext<'_>) if let TSType::TSTypeLiteral(literal) = ts_type { ctx.diagnostic_with_fix( prefer_function_type_diagnostic(&suggestion, decl.span), - || { - Fix::new( - format!("({suggestion})"), - Span::new(literal.span.start, literal.span.end), - ) + |fixer| { + fixer.replace(literal.span, format!("({suggestion})")) }, ); } @@ -241,12 +236,7 @@ fn check_member(member: &TSSignature, node: &AstNode<'_>, ctx: &LintContext<'_>) TSType::TSTypeLiteral(literal) => ctx.diagnostic_with_fix( prefer_function_type_diagnostic(&suggestion, decl.span), - || { - Fix::new( - suggestion.to_string(), - Span::new(literal.span.start, literal.span.end), - ) - }, + |fixer| fixer.replace(literal.span, suggestion), ), _ => { @@ -266,13 +256,10 @@ fn check_member(member: &TSSignature, node: &AstNode<'_>, ctx: &LintContext<'_>) &suggestion, decl.span, ), - || { - Fix::new( + |fixer| { + fixer.replace( + literal.span, format!("({suggestion})"), - Span::new( - literal.span.start, - literal.span.end, - ), ) }, ); @@ -291,13 +278,10 @@ fn check_member(member: &TSSignature, node: &AstNode<'_>, ctx: &LintContext<'_>) &suggestion, decl.span, ), - || { - Fix::new( + |fixer| { + fixer.replace( + literal.span, format!("({suggestion})"), - Span::new( - literal.span.start, - literal.span.end, - ), ) }, ); @@ -308,12 +292,7 @@ fn check_member(member: &TSSignature, node: &AstNode<'_>, ctx: &LintContext<'_>) TSType::TSTypeLiteral(literal) => ctx.diagnostic_with_fix( prefer_function_type_diagnostic(&suggestion, decl.span), - || { - Fix::new( - suggestion.to_string(), - Span::new(literal.span.start, literal.span.end), - ) - }, + |fixer| fixer.replace(literal.span, suggestion), ), _ => {} diff --git a/crates/oxc_linter/src/rules/typescript/prefer_ts_expect_error.rs b/crates/oxc_linter/src/rules/typescript/prefer_ts_expect_error.rs index fecc40d24961c..095260adbbb8a 100644 --- a/crates/oxc_linter/src/rules/typescript/prefer_ts_expect_error.rs +++ b/crates/oxc_linter/src/rules/typescript/prefer_ts_expect_error.rs @@ -4,7 +4,6 @@ use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::Span; -use crate::fixer::Fix; use crate::{context::LintContext, rule::Rule}; fn prefer_ts_expect_error_diagnostic(span0: Span) -> OxcDiagnostic { @@ -56,18 +55,18 @@ impl Rule for PreferTsExpectError { if kind.is_single_line() { let comment_span = Span::new(span.start - 2, span.end); - ctx.diagnostic_with_fix(prefer_ts_expect_error_diagnostic(comment_span), || { - Fix::new( - format!("//{}", raw.replace("@ts-ignore", "@ts-expect-error")), + ctx.diagnostic_with_fix(prefer_ts_expect_error_diagnostic(comment_span), |fixer| { + fixer.replace( comment_span, + format!("//{}", raw.replace("@ts-ignore", "@ts-expect-error")), ) }); } else { let comment_span = Span::new(span.start - 2, span.end + 2); - ctx.diagnostic_with_fix(prefer_ts_expect_error_diagnostic(comment_span), || { - Fix::new( - format!("/*{}*/", raw.replace("@ts-ignore", "@ts-expect-error")), + ctx.diagnostic_with_fix(prefer_ts_expect_error_diagnostic(comment_span), |fixer| { + fixer.replace( comment_span, + format!("/*{}*/", raw.replace("@ts-ignore", "@ts-expect-error")), ) }); } diff --git a/crates/oxc_linter/src/rules/unicorn/empty_brace_spaces.rs b/crates/oxc_linter/src/rules/unicorn/empty_brace_spaces.rs index fb844a29b2104..9dab4f1699ac7 100644 --- a/crates/oxc_linter/src/rules/unicorn/empty_brace_spaces.rs +++ b/crates/oxc_linter/src/rules/unicorn/empty_brace_spaces.rs @@ -4,7 +4,7 @@ use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::Span; -use crate::{context::LintContext, rule::Rule, AstNode, Fix}; +use crate::{context::LintContext, rule::Rule, AstNode}; fn empty_brace_spaces_diagnostic(span0: Span) -> OxcDiagnostic { OxcDiagnostic::warn("eslint-plugin-unicorn(empty-brace-spaces): No spaces inside empty pair of braces allowed") @@ -47,7 +47,7 @@ impl Rule for EmptyBraceSpaces { { ctx.diagnostic_with_fix( empty_brace_spaces_diagnostic(static_block.span), - || Fix::new("static {}", static_block.span), + |fixer| fixer.replace(static_block.span, "static {}"), ); } } @@ -88,7 +88,9 @@ fn remove_empty_braces_spaces(ctx: &LintContext, is_empty_body: bool, span: Span if is_empty_body && end - start > 2 && !ctx.semantic().trivias().has_comments_between(span) { // length of "{}" - ctx.diagnostic_with_fix(empty_brace_spaces_diagnostic(span), || Fix::new("{}", span)); + ctx.diagnostic_with_fix(empty_brace_spaces_diagnostic(span), |fixer| { + fixer.replace(span, "{}") + }); } } diff --git a/crates/oxc_linter/src/rules/unicorn/escape_case.rs b/crates/oxc_linter/src/rules/unicorn/escape_case.rs index 6a0636309c733..007f62142fe3a 100644 --- a/crates/oxc_linter/src/rules/unicorn/escape_case.rs +++ b/crates/oxc_linter/src/rules/unicorn/escape_case.rs @@ -9,7 +9,7 @@ use oxc_ast::{ use oxc_macros::declare_oxc_lint; use oxc_span::Span; -use crate::{context::LintContext, rule::Rule, AstNode, Fix}; +use crate::{context::LintContext, rule::Rule, AstNode}; fn escape_case_diagnostic(span0: Span) -> OxcDiagnostic { OxcDiagnostic::warn("eslint-plugin-unicorn(escape-case): Use uppercase characters for the value of the escape sequence.").with_labels([span0.into()]) @@ -125,8 +125,8 @@ impl Rule for EscapeCase { AstKind::StringLiteral(StringLiteral { span, .. }) => { let text = span.source_text(ctx.source_text()); if let Some(fixed) = check_case(text, false) { - ctx.diagnostic_with_fix(escape_case_diagnostic(*span), || { - Fix::new(fixed, *span) + ctx.diagnostic_with_fix(escape_case_diagnostic(*span), |fixer| { + fixer.replace(*span, fixed) }); } } @@ -135,8 +135,8 @@ impl Rule for EscapeCase { if let Some(fixed) = check_case(quasi.span.source_text(ctx.source_text()), false) { - ctx.diagnostic_with_fix(escape_case_diagnostic(quasi.span), || { - Fix::new(fixed, quasi.span) + ctx.diagnostic_with_fix(escape_case_diagnostic(quasi.span), |fixer| { + fixer.replace(quasi.span, fixed) }); } }); @@ -144,8 +144,8 @@ impl Rule for EscapeCase { AstKind::RegExpLiteral(regex) => { let text = regex.span.source_text(ctx.source_text()); if let Some(fixed) = check_case(text, true) { - ctx.diagnostic_with_fix(escape_case_diagnostic(regex.span), || { - Fix::new(fixed, regex.span) + ctx.diagnostic_with_fix(escape_case_diagnostic(regex.span), |fixer| { + fixer.replace(regex.span, fixed) }); } } diff --git a/crates/oxc_linter/src/rules/unicorn/explicit_length_check.rs b/crates/oxc_linter/src/rules/unicorn/explicit_length_check.rs index baf80e972cb47..fe82a68e62f3b 100644 --- a/crates/oxc_linter/src/rules/unicorn/explicit_length_check.rs +++ b/crates/oxc_linter/src/rules/unicorn/explicit_length_check.rs @@ -6,14 +6,14 @@ use oxc_ast::{ }; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; -use oxc_span::Span; +use oxc_span::{GetSpan, Span}; use oxc_syntax::operator::{BinaryOperator, LogicalOperator}; use crate::{ context::LintContext, rule::Rule, utils::{get_boolean_ancestor, is_boolean_node}, - AstNode, Fix, + AstNode, }; fn none_zero(span0: Span, x1: &str, x2: &str, x3: Option) -> OxcDiagnostic { @@ -161,13 +161,6 @@ impl ExplicitLengthCheck { auto_fix: bool, ) { let kind = node.kind(); - let span = match kind { - AstKind::BinaryExpression(expr) => expr.span, - AstKind::UnaryExpression(expr) => expr.span, - AstKind::CallExpression(expr) => expr.span, - AstKind::MemberExpression(MemberExpression::StaticMemberExpression(expr)) => expr.span, - _ => unreachable!(), - }; let check_code = if is_zero_length_check { if matches!(kind, AstKind::BinaryExpression(BinaryExpression{operator:BinaryOperator::StrictEquality,right,..}) if right.is_number_0()) { @@ -192,6 +185,8 @@ impl ExplicitLengthCheck { } } }; + + let span = kind.span(); let mut need_pad_start = false; let mut need_pad_end = false; let parent = ctx.nodes().parent_kind(node.id()); @@ -216,31 +211,18 @@ impl ExplicitLengthCheck { if need_pad_end { " " } else { "" }, ); let property = static_member_expr.property.name.clone(); + let help = if auto_fix { + None + } else { + Some(format!("Replace `.{property}` with `.{property} {check_code}`.")) + }; let diagnostic = if is_zero_length_check { - zero( - span, - property.as_str(), - check_code, - if auto_fix { - None - } else { - Some(format!("Replace `.{property}` with `.{property} {check_code}`.")) - }, - ) + zero(span, property.as_str(), check_code, help) } else { - none_zero( - span, - property.as_str(), - check_code, - if auto_fix { - None - } else { - Some(format!("Replace `.{property}` with `.{property} {check_code}`.")) - }, - ) + none_zero(span, property.as_str(), check_code, help) }; if auto_fix { - ctx.diagnostic_with_fix(diagnostic, || Fix::new(fixed, span)); + ctx.diagnostic_with_fix(diagnostic, |fixer| fixer.replace(span, fixed)); } else { ctx.diagnostic(diagnostic); } 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 04f7ede2cf63e..dfe1670c41338 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_console_spaces.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_console_spaces.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use oxc_ast::{ast::Expression, AstKind}; use oxc_diagnostics::OxcDiagnostic; @@ -8,7 +10,7 @@ use crate::{ ast_util::{call_expr_method_callee_info, is_method_call}, context::LintContext, rule::Rule, - AstNode, Fix, + AstNode, }; fn no_console_spaces_diagnostic(x0: &str, x1: &str, span2: Span) -> OxcDiagnostic { @@ -116,25 +118,23 @@ fn check_literal_leading(i: usize, literal: &str) -> bool { fn check_literal_trailing(i: usize, literal: &str, call_expr_arg_len: usize) -> bool { i != call_expr_arg_len - 1 && literal.ends_with(' ') } -fn report_diagnostic( +fn report_diagnostic<'a>( direction: &'static str, - ident: &str, + ident: &'a str, span: Span, - literal_raw: &str, + literal_raw: &'a str, is_template_lit: bool, - ctx: &LintContext, + ctx: &LintContext<'a>, ) { - let (start, end) = - if is_template_lit { (span.start, span.end) } else { (span.start + 1, span.end - 1) }; - - let fix = if is_template_lit { - format!("`{}`", literal_raw.trim()) - } else { - literal_raw.trim().to_string() - }; + let span = if is_template_lit { span } else { Span::new(span.start + 1, span.end - 1) }; - ctx.diagnostic_with_fix(no_console_spaces_diagnostic(direction, ident, span), || { - Fix::new(fix, Span::new(start, end)) + ctx.diagnostic_with_fix(no_console_spaces_diagnostic(direction, ident, span), |fixer| { + let content = if is_template_lit { + Cow::Owned(format!("`{}`", literal_raw.trim())) + } else { + Cow::Borrowed(literal_raw.trim()) + }; + fixer.replace(span, content) }); } diff --git a/crates/oxc_linter/src/rules/unicorn/no_hex_escape.rs b/crates/oxc_linter/src/rules/unicorn/no_hex_escape.rs index 3397be8fab38d..1d445f66f1c98 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_hex_escape.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_hex_escape.rs @@ -7,7 +7,7 @@ use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::Span; -use crate::{context::LintContext, rule::Rule, AstNode, Fix}; +use crate::{context::LintContext, rule::Rule, AstNode}; fn no_hex_escape_diagnostic(span0: Span) -> OxcDiagnostic { OxcDiagnostic::warn( @@ -73,16 +73,16 @@ impl Rule for NoHexEscape { AstKind::StringLiteral(StringLiteral { span, .. }) => { let text = span.source_text(ctx.source_text()); if let Some(fixed) = check_escape(&text[1..text.len() - 1]) { - ctx.diagnostic_with_fix(no_hex_escape_diagnostic(*span), || { - Fix::new(format!("'{fixed}'"), *span) + ctx.diagnostic_with_fix(no_hex_escape_diagnostic(*span), |fixer| { + fixer.replace(*span, format!("'{fixed}'")) }); } } AstKind::TemplateLiteral(TemplateLiteral { quasis, .. }) => { quasis.iter().for_each(|quasi| { if let Some(fixed) = check_escape(quasi.span.source_text(ctx.source_text())) { - ctx.diagnostic_with_fix(no_hex_escape_diagnostic(quasi.span), || { - Fix::new(fixed, quasi.span) + ctx.diagnostic_with_fix(no_hex_escape_diagnostic(quasi.span), |fixer| { + fixer.replace(quasi.span, fixed) }); } }); @@ -90,8 +90,8 @@ impl Rule for NoHexEscape { AstKind::RegExpLiteral(regex) => { let text = regex.span.source_text(ctx.source_text()); if let Some(fixed) = check_escape(&text[1..text.len() - 1]) { - ctx.diagnostic_with_fix(no_hex_escape_diagnostic(regex.span), || { - Fix::new(format!("/{fixed}/"), regex.span) + ctx.diagnostic_with_fix(no_hex_escape_diagnostic(regex.span), |fixer| { + fixer.replace(regex.span, format!("/{fixed}/")) }); } } diff --git a/crates/oxc_linter/src/rules/unicorn/no_instanceof_array.rs b/crates/oxc_linter/src/rules/unicorn/no_instanceof_array.rs index 7f2ace62cae63..4d7081a66bbbd 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_instanceof_array.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_instanceof_array.rs @@ -6,7 +6,7 @@ use oxc_macros::declare_oxc_lint; use oxc_span::{GetSpan, Span}; use oxc_syntax::operator::BinaryOperator; -use crate::{context::LintContext, fixer::Fix, rule::Rule, AstNode}; +use crate::{context::LintContext, rule::Rule, AstNode}; fn no_instanceof_array_diagnostic(span0: Span) -> OxcDiagnostic { OxcDiagnostic::warn("eslint-plugin-unicorn(no-instanceof-array): Use `Array.isArray()` instead of `instanceof Array`.") @@ -44,15 +44,15 @@ impl Rule for NoInstanceofArray { match &expr.right.without_parenthesized() { Expression::Identifier(identifier) if identifier.name == "Array" => { - ctx.diagnostic_with_fix(no_instanceof_array_diagnostic(expr.span), || { + ctx.diagnostic_with_fix(no_instanceof_array_diagnostic(expr.span), |fixer| { let modified_code = { let mut codegen = String::new(); codegen.push_str("Array.isArray("); - codegen.push_str(expr.left.span().source_text(ctx.source_text())); + codegen.push_str(fixer.source_range(expr.left.span())); codegen.push(')'); codegen }; - Fix::new(modified_code, expr.span) + fixer.replace(expr.span, modified_code) }); } _ => {} diff --git a/crates/oxc_linter/src/rules/unicorn/no_nested_ternary.rs b/crates/oxc_linter/src/rules/unicorn/no_nested_ternary.rs index 5591ace761467..83b505127bc96 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_nested_ternary.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_nested_ternary.rs @@ -3,7 +3,7 @@ use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::Span; -use crate::{context::LintContext, rule::Rule, AstNode, Fix}; +use crate::{context::LintContext, rule::Rule, AstNode}; fn unparenthesized_nested_ternary(span0: Span) -> OxcDiagnostic { OxcDiagnostic::warn("eslint-plugin-unicorn(no-nested-ternary): Unexpected nested ternary expression without parentheses.") @@ -86,17 +86,14 @@ impl Rule for NoNestedTernary { if let AstKind::ParenthesizedExpression(_) = parent_node.kind() { return; } - ctx.diagnostic_with_fix(unparenthesized_nested_ternary(cond_expr.span), || { - Fix::new( - format!("({})", cond_expr.span.source_text(ctx.source_text())), - cond_expr.span, - ) + ctx.diagnostic_with_fix(unparenthesized_nested_ternary(cond_expr.span), |fixer| { + let content = format!("({})", fixer.source_range(cond_expr.span)); + fixer.replace(cond_expr.span, content) }); } - 2 => { + _ => { ctx.diagnostic(deeply_nested_ternary(cond_expr.span)); } - _ => unreachable!(), } } } diff --git a/crates/oxc_linter/src/rules/unicorn/no_null.rs b/crates/oxc_linter/src/rules/unicorn/no_null.rs index 44094f876ba6a..66ec7a9ff5023 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_null.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_null.rs @@ -10,7 +10,9 @@ use oxc_macros::declare_oxc_lint; use oxc_span::{GetSpan, Span}; use oxc_syntax::operator::BinaryOperator; -use crate::{ast_util::is_method_call, context::LintContext, rule::Rule, AstNode, Fix}; +use crate::{ + ast_util::is_method_call, context::LintContext, fixer::RuleFixer, rule::Rule, AstNode, Fix, +}; fn replace_null_diagnostic(span0: Span) -> OxcDiagnostic { OxcDiagnostic::warn("eslint-plugin-unicorn(no-null): Disallow the use of the `null` literal") @@ -81,16 +83,16 @@ fn diagnose_binary_expression( // `if (foo != null) {}` if matches!(binary_expr.operator, BinaryOperator::Equality | BinaryOperator::Inequality) { - ctx.diagnostic_with_fix(replace_null_diagnostic(null_literal.span), || { - Fix::new("undefined", null_literal.span) + ctx.diagnostic_with_fix(replace_null_diagnostic(null_literal.span), |fixer| { + fix_null(fixer, null_literal) }); return; } // checkStrictEquality=true && `if (foo !== null) {}` - ctx.diagnostic_with_fix(replace_null_diagnostic(null_literal.span), || { - Fix::new("undefined", null_literal.span) + ctx.diagnostic_with_fix(replace_null_diagnostic(null_literal.span), |fixer| { + fix_null(fixer, null_literal) }); } @@ -104,16 +106,16 @@ fn diagnose_variable_declarator( if matches!(&variable_declarator.init, Some(Expression::NullLiteral(expr)) if expr.span == null_literal.span) && matches!(parent_kind, Some(AstKind::VariableDeclaration(var_declaration)) if !var_declaration.kind.is_const() ) { - ctx.diagnostic_with_fix(remove_null_diagnostic(null_literal.span), || { - Fix::delete(Span::new(variable_declarator.id.span().end, null_literal.span.end)) + ctx.diagnostic_with_fix(remove_null_diagnostic(null_literal.span), |fixer| { + fixer.delete_range(Span::new(variable_declarator.id.span().end, null_literal.span.end)) }); return; } // `const foo = null` - ctx.diagnostic_with_fix(replace_null_diagnostic(null_literal.span), || { - Fix::new("undefined", null_literal.span) + ctx.diagnostic_with_fix(replace_null_diagnostic(null_literal.span), |fixer| { + fix_null(fixer, null_literal) }); } @@ -201,20 +203,23 @@ impl Rule for NoNull { // `function foo() { return null; }`, if matches!(parent_node.kind(), AstKind::ReturnStatement(_)) { - ctx.diagnostic_with_fix(remove_null_diagnostic(null_literal.span), || { - Fix::delete(null_literal.span) + ctx.diagnostic_with_fix(remove_null_diagnostic(null_literal.span), |fixer| { + fixer.delete_range(null_literal.span) }); return; } } - ctx.diagnostic_with_fix(replace_null_diagnostic(null_literal.span), || { - Fix::new("undefined", null_literal.span) + ctx.diagnostic_with_fix(replace_null_diagnostic(null_literal.span), |fixer| { + fix_null(fixer, null_literal) }); } } +fn fix_null<'a>(fixer: RuleFixer<'_, 'a>, null: &NullLiteral) -> Fix<'a> { + fixer.replace(null.span, "undefined") +} #[test] fn test() { use crate::tester::Tester; 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 8b1b304f22bf6..858791aa16dfa 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 @@ -7,7 +7,7 @@ use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::{GetSpan, Span}; -use crate::{ast_util::is_method_call, context::LintContext, fixer::Fix, rule::Rule, AstNode}; +use crate::{ast_util::is_method_call, context::LintContext, rule::Rule, AstNode}; fn no_single_promise_in_promise_methods_diagnostic(span0: Span, x1: &str) -> OxcDiagnostic { OxcDiagnostic::warn(format!("eslint-plugin-unicorn(no-single-promise-in-promise-methods): Wrapping single-element array with `Promise.{x1}()` is unnecessary.")) @@ -85,8 +85,8 @@ impl Rule for NoSinglePromiseInPromiseMethods { .expect("callee is a static property"); let diagnostic = no_single_promise_in_promise_methods_diagnostic(info.0, info.1); - ctx.diagnostic_with_fix(diagnostic, || { - let elem_text = first.span().source_text(ctx.source_text()); + ctx.diagnostic_with_fix(diagnostic, |fixer| { + let elem_text = fixer.source_range(first.span()); let is_directly_in_await = ctx .semantic() @@ -101,9 +101,9 @@ impl Rule for NoSinglePromiseInPromiseMethods { let call_span = call_expr.span; if is_directly_in_await { - Fix::new(elem_text, call_span) + fixer.replace(call_span, elem_text) } else { - Fix::new(format!("Promise.resolve({elem_text})"), call_span) + 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 0369346a0fa2c..9eb2181fa6d49 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_unnecessary_await.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_unnecessary_await.rs @@ -4,7 +4,7 @@ use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::{GetSpan, Span}; -use crate::{context::LintContext, rule::Rule, AstNode, Fix}; +use crate::{context::LintContext, rule::Rule, AstNode}; fn no_unnecessary_await_diagnostic(span0: Span) -> OxcDiagnostic { OxcDiagnostic::warn( @@ -66,11 +66,7 @@ impl Rule for NoUnnecessaryAwait { expr.span.start, expr.span.start + 5, )), - || { - let mut codegen = String::new(); - codegen.push_str(expr.argument.span().source_text(ctx.source_text())); - Fix::new(codegen, expr.span) - }, + |fixer| fixer.replace(expr.span, fixer.source_range(expr.argument.span())), ); }; } diff --git a/crates/oxc_linter/src/rules/unicorn/no_useless_fallback_in_spread.rs b/crates/oxc_linter/src/rules/unicorn/no_useless_fallback_in_spread.rs index 32ca2b1e00cce..2850e925d6d17 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_useless_fallback_in_spread.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_useless_fallback_in_spread.rs @@ -5,9 +5,7 @@ use oxc_macros::declare_oxc_lint; use oxc_span::{GetSpan, Span}; use oxc_syntax::operator::LogicalOperator; -use crate::{ - ast_util::outermost_paren_parent, context::LintContext, fixer::Fix, rule::Rule, AstNode, -}; +use crate::{ast_util::outermost_paren_parent, context::LintContext, rule::Rule, AstNode}; fn no_useless_fallback_in_spread_diagnostic(span: Span) -> OxcDiagnostic { OxcDiagnostic::warn("eslint-plugin-unicorn(no-useless-fallback-in-spread): Disallow useless fallback when spreading in object literals") @@ -80,9 +78,9 @@ impl Rule for NoUselessFallbackInSpread { let diagnostic = no_useless_fallback_in_spread_diagnostic(spread_element.span); if can_fix(&logical_expression.left) { - ctx.diagnostic_with_fix(diagnostic, || { - let left_text = ctx.source_range(logical_expression.left.span()); - Fix::new(format!("...{left_text}"), spread_element.span) + ctx.diagnostic_with_fix(diagnostic, |fixer| { + let left_text = fixer.source_range(logical_expression.left.span()); + fixer.replace(spread_element.span, format!("...{left_text}")) }); } else { ctx.diagnostic(diagnostic); diff --git a/crates/oxc_linter/src/rules/unicorn/no_useless_spread.rs b/crates/oxc_linter/src/rules/unicorn/no_useless_spread.rs index 86bd331409466..3384a9a5ffd64 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_useless_spread.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_useless_spread.rs @@ -15,7 +15,7 @@ use crate::{ get_new_expr_ident_name, is_method_call, is_new_expression, outermost_paren_parent, }, context::LintContext, - fixer::Fix, + fixer::{Fix, RuleFixer}, rule::Rule, AstNode, }; @@ -167,8 +167,8 @@ fn check_useless_spread_in_list<'a>(node: &AstNode<'a>, ctx: &LintContext<'a>) { AstKind::ArrayExpressionElement(_) => { let diagnostic = spread_in_list(span, "array"); if let Some(outer_array) = ctx.nodes().parent_kind(parent_parent.id()) { - ctx.diagnostic_with_fix(diagnostic, || { - fix_replace(ctx, &outer_array, array_expr) + ctx.diagnostic_with_fix(diagnostic, |fixer| { + fix_replace(fixer, &outer_array, array_expr) }); } else { ctx.diagnostic(diagnostic); @@ -176,8 +176,8 @@ fn check_useless_spread_in_list<'a>(node: &AstNode<'a>, ctx: &LintContext<'a>) { } // foo(...[ ]) AstKind::Argument(_) => { - ctx.diagnostic_with_fix(spread_in_arguments(span), || { - fix_by_removing_spread(ctx, array_expr, spread_elem) + ctx.diagnostic_with_fix(spread_in_arguments(span), |fixer| { + fix_by_removing_spread(fixer, array_expr, spread_elem) }); } _ => {} @@ -260,7 +260,7 @@ fn check_useless_iterable_to_array<'a>( } ctx.diagnostic_with_fix( iterable_to_array(span, get_new_expr_ident_name(new_expr).unwrap_or("unknown")), - || fix_by_removing_spread(ctx, &new_expr.arguments[0], spread_elem), + |fixer| fix_by_removing_spread(fixer, &new_expr.arguments[0], spread_elem), ); } AstKind::CallExpression(call_expr) => { @@ -305,7 +305,7 @@ fn check_useless_iterable_to_array<'a>( span, &get_method_name(call_expr).unwrap_or_else(|| "unknown".into()), ), - || fix_by_removing_spread(ctx, array_expr, spread_elem), + |fixer| fix_by_removing_spread(fixer, array_expr, spread_elem), ); } _ => {} @@ -363,8 +363,8 @@ fn check_useless_array_clone<'a>(array_expr: &ArrayExpression<'a>, ctx: &LintCon }, ); - ctx.diagnostic_with_fix(clone_array(span, &method), || { - fix_by_removing_spread(ctx, array_expr, spread_elem) + ctx.diagnostic_with_fix(clone_array(span, &method), |fixer| { + fix_by_removing_spread(fixer, array_expr, spread_elem) }); } @@ -382,29 +382,30 @@ fn check_useless_array_clone<'a>(array_expr: &ArrayExpression<'a>, ctx: &LintCon let method_name = call_expr.callee.get_member_expr().unwrap().static_property_name().unwrap(); - ctx.diagnostic_with_fix(clone_array(span, &format!("Promise.{method_name}")), || { - fix_by_removing_spread(ctx, array_expr, spread_elem) - }); + ctx.diagnostic_with_fix( + clone_array(span, &format!("Promise.{method_name}")), + |fixer| fix_by_removing_spread(fixer, array_expr, spread_elem), + ); } } } fn fix_replace<'a, T: GetSpan, U: GetSpan>( - ctx: &LintContext<'a>, + fixer: RuleFixer<'_, 'a>, target: &T, replacement: &U, ) -> Fix<'a> { - let replacement = ctx.source_range(replacement.span()); - Fix::new(replacement, target.span()) + let replacement = fixer.source_range(replacement.span()); + fixer.replace(target.span(), replacement) } /// Creates a fix that replaces `[...spread]` with `spread` fn fix_by_removing_spread<'a, S: GetSpan>( - ctx: &LintContext<'a>, + fixer: RuleFixer<'_, 'a>, iterable: &S, spread: &SpreadElement<'a>, ) -> Fix<'a> { - Fix::new(ctx.source_range(spread.argument.span()), iterable.span()) + fixer.replace(iterable.span(), fixer.source_range(spread.argument.span())) } /// Checks if `node` is `[...(expr)]` diff --git a/crates/oxc_linter/src/rules/unicorn/no_zero_fractions.rs b/crates/oxc_linter/src/rules/unicorn/no_zero_fractions.rs index ce4836c2be64b..27f78d0301fc3 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_zero_fractions.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_zero_fractions.rs @@ -3,7 +3,7 @@ use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::Span; -use crate::{context::LintContext, rule::Rule, AstNode, Fix}; +use crate::{context::LintContext, rule::Rule, AstNode}; fn zero_fraction(span0: Span, x1: &str) -> OxcDiagnostic { OxcDiagnostic::warn( @@ -69,7 +69,7 @@ impl Rule for NoZeroFractions { } else { zero_fraction(number_literal.span, &fmt) }, - || Fix::new(fmt, number_literal.span), + |fixer| fixer.replace(number_literal.span, fmt), ); } } diff --git a/crates/oxc_linter/src/rules/unicorn/number_literal_case.rs b/crates/oxc_linter/src/rules/unicorn/number_literal_case.rs index 87c35a6517c0d..863d8e0cff792 100644 --- a/crates/oxc_linter/src/rules/unicorn/number_literal_case.rs +++ b/crates/oxc_linter/src/rules/unicorn/number_literal_case.rs @@ -4,7 +4,7 @@ use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::Span; -use crate::{context::LintContext, rule::Rule, AstNode, Fix}; +use crate::{context::LintContext, rule::Rule, AstNode}; fn uppercase_prefix(span0: Span, x1: &str) -> OxcDiagnostic { OxcDiagnostic::warn("eslint-plugin-unicorn(number-literal-case): Unexpected number literal prefix in uppercase.") @@ -83,7 +83,7 @@ impl Rule for NumberLiteralCase { }; if let Some((diagnostic, fixed_literal)) = check_number_literal(raw_literal, raw_span) { - ctx.diagnostic_with_fix(diagnostic, || Fix::new(fixed_literal, raw_span)); + ctx.diagnostic_with_fix(diagnostic, |fixer| fixer.replace(raw_span, fixed_literal)); } } } diff --git a/crates/oxc_linter/src/rules/unicorn/numeric_separators_style.rs b/crates/oxc_linter/src/rules/unicorn/numeric_separators_style.rs index 39210c8f7a220..6bd2f54970b52 100644 --- a/crates/oxc_linter/src/rules/unicorn/numeric_separators_style.rs +++ b/crates/oxc_linter/src/rules/unicorn/numeric_separators_style.rs @@ -9,7 +9,7 @@ use oxc_macros::declare_oxc_lint; use oxc_span::Span; use regex::Regex; -use crate::{context::LintContext, fixer::Fix, rule::Rule, AstNode}; +use crate::{context::LintContext, rule::Rule, AstNode}; fn numeric_separators_style_diagnostic(span0: Span) -> OxcDiagnostic { OxcDiagnostic::warn( @@ -98,7 +98,7 @@ impl Rule for NumericSeparatorsStyle { if formatted != number.raw { ctx.diagnostic_with_fix( numeric_separators_style_diagnostic(number.span), - || Fix::new(formatted, number.span), + |fixer| fixer.replace(number.span, formatted), ); } } @@ -114,7 +114,7 @@ impl Rule for NumericSeparatorsStyle { if formatted.len() != number.span.size() as usize { ctx.diagnostic_with_fix( numeric_separators_style_diagnostic(number.span), - || Fix::new(formatted, number.span), + |fixer| fixer.replace(number.span, formatted), ); } } diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_dom_node_text_content.rs b/crates/oxc_linter/src/rules/unicorn/prefer_dom_node_text_content.rs index 513ed6cdd3b65..67cb1cda10371 100644 --- a/crates/oxc_linter/src/rules/unicorn/prefer_dom_node_text_content.rs +++ b/crates/oxc_linter/src/rules/unicorn/prefer_dom_node_text_content.rs @@ -4,7 +4,7 @@ use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::Span; -use crate::{context::LintContext, rule::Rule, AstNode, Fix}; +use crate::{context::LintContext, rule::Rule, AstNode}; fn prefer_dom_node_text_content_diagnostic(span0: Span) -> OxcDiagnostic { OxcDiagnostic::warn("eslint-plugin-unicorn(prefer-dom-node-text-content): Prefer `.textContent` over `.innerText`.") @@ -44,9 +44,10 @@ impl Rule for PreferDomNodeTextContent { if let AstKind::MemberExpression(member_expr) = node.kind() { if let Some((span, name)) = member_expr.static_property_info() { if name == "innerText" && !member_expr.is_computed() { - ctx.diagnostic_with_fix(prefer_dom_node_text_content_diagnostic(span), || { - Fix::new("textContent", span) - }); + ctx.diagnostic_with_fix( + prefer_dom_node_text_content_diagnostic(span), + |fixer| fixer.replace(span, "textContent"), + ); } } } diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_prototype_methods.rs b/crates/oxc_linter/src/rules/unicorn/prefer_prototype_methods.rs index 7c40c4535e50a..72a769a58ebac 100644 --- a/crates/oxc_linter/src/rules/unicorn/prefer_prototype_methods.rs +++ b/crates/oxc_linter/src/rules/unicorn/prefer_prototype_methods.rs @@ -8,7 +8,7 @@ use crate::{ context::LintContext, rule::Rule, utils::{is_empty_array_expression, is_empty_object_expression}, - AstNode, Fix, + AstNode, }; fn known_method(span0: Span, x1: &str, x2: &str) -> OxcDiagnostic { @@ -109,17 +109,17 @@ impl Rule for PreferPrototypeMethods { || unknown_method(method_expr.span(), constructor_name), |method_name| known_method(method_expr.span(), constructor_name, method_name), ), - || { + |fixer| { let span = object_expr.span(); let need_padding = span.start >= 1 && ctx.source_text().as_bytes()[span.start as usize - 1].is_ascii_alphabetic(); - Fix::new( + fixer.replace( + span, format!( "{}{}.prototype", if need_padding { " " } else { "" }, constructor_name ), - span, ) }, ); diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_query_selector.rs b/crates/oxc_linter/src/rules/unicorn/prefer_query_selector.rs index 2851669581224..3946dcb29a453 100644 --- a/crates/oxc_linter/src/rules/unicorn/prefer_query_selector.rs +++ b/crates/oxc_linter/src/rules/unicorn/prefer_query_selector.rs @@ -88,8 +88,8 @@ impl Rule for PreferQuerySelector { ); if argument_expr.is_null() { - return ctx.diagnostic_with_fix(diagnostic, || { - return Fix::new(*preferred_selector, property_span); + return ctx.diagnostic_with_fix(diagnostic, |fixer| { + fixer.replace(property_span, *preferred_selector) }); } @@ -106,15 +106,18 @@ impl Rule for PreferQuerySelector { }; if let Some(literal_value) = literal_value { - return ctx.diagnostic_with_fix(diagnostic, || { + return ctx.diagnostic_with_fix(diagnostic, |fixer| { if literal_value.is_empty() { return Fix::new(*preferred_selector, property_span); } - let source_text = argument_expr.span().source_text(ctx.source_text()); + // let source_text = + // argument_expr.span().source_text(ctx.source_text()); + let source_text = fixer.source_range(argument_expr.span()); let quotes_symbol = source_text.chars().next().unwrap(); let sharp = if cur_property_name.eq(&"getElementById") { "#" } else { "" }; - return Fix::new(format!("{preferred_selector}({quotes_symbol}{sharp}{literal_value}{quotes_symbol}"), property_span.merge(&argument_expr.span())); + let span = property_span.merge(&argument_expr.span()); + return fixer.replace(span, format!("{preferred_selector}({quotes_symbol}{sharp}{literal_value}{quotes_symbol}")); }); } diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_spread.rs b/crates/oxc_linter/src/rules/unicorn/prefer_spread.rs index a10a71a41824d..f9051adddfc62 100644 --- a/crates/oxc_linter/src/rules/unicorn/prefer_spread.rs +++ b/crates/oxc_linter/src/rules/unicorn/prefer_spread.rs @@ -8,7 +8,7 @@ use oxc_macros::declare_oxc_lint; use oxc_span::{GetSpan, Span}; use phf::phf_set; -use crate::{context::LintContext, rule::Rule, AstNode, Fix}; +use crate::{context::LintContext, rule::Rule, AstNode}; fn prefer_spread_diagnostic(span0: Span, x1: &str) -> OxcDiagnostic { OxcDiagnostic::warn(format!( @@ -161,11 +161,11 @@ impl Rule for PreferSpread { ctx.diagnostic_with_fix( prefer_spread_diagnostic(call_expr.span, "string.split()"), - || { + |fixer| { let callee_obj = member_expr.object().without_parenthesized(); - Fix::new( - format!("[...{}]", callee_obj.span().source_text(ctx.source_text())), + fixer.replace( call_expr.span, + format!("[...{}]", callee_obj.span().source_text(ctx.source_text())), ) }, ); diff --git a/crates/oxc_linter/src/rules/unicorn/require_number_to_fixed_digits_argument.rs b/crates/oxc_linter/src/rules/unicorn/require_number_to_fixed_digits_argument.rs index 7acc90693d02b..feef5e3a842d7 100644 --- a/crates/oxc_linter/src/rules/unicorn/require_number_to_fixed_digits_argument.rs +++ b/crates/oxc_linter/src/rules/unicorn/require_number_to_fixed_digits_argument.rs @@ -4,7 +4,7 @@ use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::{GetSpan, Span}; -use crate::{context::LintContext, fixer::Fix, rule::Rule, AstNode}; +use crate::{context::LintContext, rule::Rule, AstNode}; fn require_number_to_fixed_digits_argument_diagnostic(span0: Span) -> OxcDiagnostic { OxcDiagnostic::warn("eslint-plugin-unicorn(require-number-to-fixed-digits-argument): Number method .toFixed() should have an argument") @@ -61,15 +61,15 @@ impl Rule for RequireNumberToFixedDigitsArgument { ctx.diagnostic_with_fix( require_number_to_fixed_digits_argument_diagnostic(parenthesis_span), - || { + |fixer| { let modified_code = { - let mut formatter = ctx.codegen(); + let mut formatter = fixer.codegen(); let mut parenthesis_span_without_right_one = parenthesis_span; parenthesis_span_without_right_one.end -= 1; - let span_source_code = parenthesis_span_without_right_one - .source_text(ctx.source_text()); + let span_source_code = + fixer.source_range(parenthesis_span_without_right_one); formatter.print_str(span_source_code.as_bytes()); formatter.print_str(b"0)"); @@ -77,7 +77,7 @@ impl Rule for RequireNumberToFixedDigitsArgument { formatter.into_source_text() }; - Fix::new(modified_code, parenthesis_span) + fixer.replace(parenthesis_span, modified_code) }, ); } 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 a976c986f0dba..567f4d0c32368 100644 --- a/crates/oxc_linter/src/rules/unicorn/switch_case_braces.rs +++ b/crates/oxc_linter/src/rules/unicorn/switch_case_braces.rs @@ -4,7 +4,7 @@ use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::{GetSpan, Span}; -use crate::{context::LintContext, fixer::Fix, rule::Rule, AstNode}; +use crate::{context::LintContext, rule::Rule, AstNode}; fn switch_case_braces_diagnostic(span0: Span) -> OxcDiagnostic { OxcDiagnostic::warn("eslint-plugin-unicorn(switch-case-braces): Empty switch case shouldn't have braces and not-empty case should have braces around it.") @@ -54,7 +54,7 @@ impl Rule for SwitchCaseBraces { if case_block.body.is_empty() { ctx.diagnostic_with_fix( switch_case_braces_diagnostic(case_block.span), - || Fix::new("", case_block.span), + |fixer| fixer.delete_range(case_block.span), ); } } @@ -72,10 +72,10 @@ impl Rule for SwitchCaseBraces { ctx.diagnostic_with_fix( switch_case_braces_diagnostic(case_body_span), - || { + |fixer| { use oxc_codegen::{Context, Gen}; let modified_code = { - let mut formatter = ctx.codegen(); + let mut formatter = fixer.codegen(); if let Some(case_test) = &case.test { formatter.print_str(b"case "); @@ -95,7 +95,7 @@ impl Rule for SwitchCaseBraces { formatter.into_source_text() }; - Fix::new(modified_code, case.span) + fixer.replace(case.span, modified_code) }, ); diff --git a/crates/oxc_linter/src/snapshots/no_console_spaces.snap b/crates/oxc_linter/src/snapshots/no_console_spaces.snap index 9c9a202b8c161..26c5fb85cbd59 100644 --- a/crates/oxc_linter/src/snapshots/no_console_spaces.snap +++ b/crates/oxc_linter/src/snapshots/no_console_spaces.snap @@ -3,30 +3,30 @@ source: crates/oxc_linter/src/tester.rs expression: no_console_spaces --- ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use trailing spaces with `console.log` parameters - ╭─[no_console_spaces.tsx:1:13] + ╭─[no_console_spaces.tsx:1:14] 1 │ console.log("abc ", "def"); - · ────── + · ──── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use leading spaces with `console.log` parameters - ╭─[no_console_spaces.tsx:1:20] + ╭─[no_console_spaces.tsx:1:21] 1 │ console.log("abc", " def"); - · ────── + · ──── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use trailing spaces with `console.log` parameters - ╭─[no_console_spaces.tsx:1:13] + ╭─[no_console_spaces.tsx:1:14] 1 │ console.log(" abc ", "def"); - · ─────── + · ───── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use trailing spaces with `console.debug` parameters - ╭─[no_console_spaces.tsx:1:15] + ╭─[no_console_spaces.tsx:1:16] 1 │ console.debug("abc ", "def"); - · ────── + · ──── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. @@ -38,58 +38,58 @@ expression: no_console_spaces help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use trailing spaces with `console.info` parameters - ╭─[no_console_spaces.tsx:1:14] + ╭─[no_console_spaces.tsx:1:15] 1 │ console.info("abc ", "def"); - · ────── + · ──── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use trailing spaces with `console.warn` parameters - ╭─[no_console_spaces.tsx:1:14] + ╭─[no_console_spaces.tsx:1:15] 1 │ console.warn("abc ", "def"); - · ────── + · ──── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use trailing spaces with `console.error` parameters - ╭─[no_console_spaces.tsx:1:15] + ╭─[no_console_spaces.tsx:1:16] 1 │ console.error("abc ", "def"); - · ────── + · ──── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use leading spaces with `console.log` parameters - ╭─[no_console_spaces.tsx:1:20] + ╭─[no_console_spaces.tsx:1:21] 1 │ console.log("abc", " def ", "ghi"); - · ─────── + · ───── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use trailing spaces with `console.log` parameters - ╭─[no_console_spaces.tsx:1:20] + ╭─[no_console_spaces.tsx:1:21] 1 │ console.log("abc", " def ", "ghi"); - · ─────── + · ───── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use trailing spaces with `console.log` parameters - ╭─[no_console_spaces.tsx:1:13] + ╭─[no_console_spaces.tsx:1:14] 1 │ console.log("abc ", "def ", "ghi"); - · ────── + · ──── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use trailing spaces with `console.log` parameters - ╭─[no_console_spaces.tsx:1:21] + ╭─[no_console_spaces.tsx:1:22] 1 │ console.log("abc ", "def ", "ghi"); - · ────── + · ──── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use trailing spaces with `console.log` parameters - ╭─[no_console_spaces.tsx:1:13] + ╭─[no_console_spaces.tsx:1:14] 1 │ console.log('abc ', "def"); - · ────── + · ──── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. @@ -101,9 +101,9 @@ expression: no_console_spaces help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use trailing spaces with `console.error` parameters - ╭─[no_console_spaces.tsx:1:15] + ╭─[no_console_spaces.tsx:1:16] 1 │ console.error('abc ', "def"); - · ────── + · ──── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. @@ -122,225 +122,225 @@ expression: no_console_spaces help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use leading spaces with `console.log` parameters - ╭─[no_console_spaces.tsx:1:20] + ╭─[no_console_spaces.tsx:1:21] 1 │ console.log("abc", " def ", "ghi"); - · ─────── + · ───── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use trailing spaces with `console.log` parameters - ╭─[no_console_spaces.tsx:1:20] + ╭─[no_console_spaces.tsx:1:21] 1 │ console.log("abc", " def ", "ghi"); - · ─────── + · ───── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use leading spaces with `console.log` parameters - ╭─[no_console_spaces.tsx:1:18] + ╭─[no_console_spaces.tsx:1:19] 1 │ console.log("_", " leading", "_") - · ────────── + · ──────── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use trailing spaces with `console.log` parameters - ╭─[no_console_spaces.tsx:1:18] + ╭─[no_console_spaces.tsx:1:19] 1 │ console.log("_", "trailing ", "_") - · ─────────── + · ───────── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use leading spaces with `console.log` parameters - ╭─[no_console_spaces.tsx:1:18] + ╭─[no_console_spaces.tsx:1:19] 1 │ console.log("_", " leading and trailing ", "_") - · ──────────────────────── + · ────────────────────── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use trailing spaces with `console.log` parameters - ╭─[no_console_spaces.tsx:1:18] + ╭─[no_console_spaces.tsx:1:19] 1 │ console.log("_", " leading and trailing ", "_") - · ──────────────────────── + · ────────────────────── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use leading spaces with `console.error` parameters - ╭─[no_console_spaces.tsx:1:22] + ╭─[no_console_spaces.tsx:1:23] 1 │ console.error("abc", " def ", "ghi"); - · ─────── + · ───── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use trailing spaces with `console.error` parameters - ╭─[no_console_spaces.tsx:1:22] + ╭─[no_console_spaces.tsx:1:23] 1 │ console.error("abc", " def ", "ghi"); - · ─────── + · ───── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use leading spaces with `console.error` parameters - ╭─[no_console_spaces.tsx:1:20] + ╭─[no_console_spaces.tsx:1:21] 1 │ console.error("_", " leading", "_") - · ────────── + · ──────── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use trailing spaces with `console.error` parameters - ╭─[no_console_spaces.tsx:1:20] + ╭─[no_console_spaces.tsx:1:21] 1 │ console.error("_", "trailing ", "_") - · ─────────── + · ───────── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use leading spaces with `console.error` parameters - ╭─[no_console_spaces.tsx:1:20] + ╭─[no_console_spaces.tsx:1:21] 1 │ console.error("_", " leading and trailing ", "_") - · ──────────────────────── + · ────────────────────── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use trailing spaces with `console.error` parameters - ╭─[no_console_spaces.tsx:1:20] + ╭─[no_console_spaces.tsx:1:21] 1 │ console.error("_", " leading and trailing ", "_") - · ──────────────────────── + · ────────────────────── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use leading spaces with `console.log` parameters - ╭─[no_console_spaces.tsx:1:18] + ╭─[no_console_spaces.tsx:1:19] 1 │ console.log("_", " log ", "_") - · ─────── + · ───── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use trailing spaces with `console.log` parameters - ╭─[no_console_spaces.tsx:1:18] + ╭─[no_console_spaces.tsx:1:19] 1 │ console.log("_", " log ", "_") - · ─────── + · ───── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use leading spaces with `console.debug` parameters - ╭─[no_console_spaces.tsx:1:20] + ╭─[no_console_spaces.tsx:1:21] 1 │ console.debug("_", " debug ", "_") - · ───────── + · ─────── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use trailing spaces with `console.debug` parameters - ╭─[no_console_spaces.tsx:1:20] + ╭─[no_console_spaces.tsx:1:21] 1 │ console.debug("_", " debug ", "_") - · ───────── + · ─────── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use leading spaces with `console.info` parameters - ╭─[no_console_spaces.tsx:1:19] + ╭─[no_console_spaces.tsx:1:20] 1 │ console.info("_", " info ", "_") - · ──────── + · ────── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use trailing spaces with `console.info` parameters - ╭─[no_console_spaces.tsx:1:19] + ╭─[no_console_spaces.tsx:1:20] 1 │ console.info("_", " info ", "_") - · ──────── + · ────── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use leading spaces with `console.warn` parameters - ╭─[no_console_spaces.tsx:1:19] + ╭─[no_console_spaces.tsx:1:20] 1 │ console.warn("_", " warn ", "_") - · ──────── + · ────── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use trailing spaces with `console.warn` parameters - ╭─[no_console_spaces.tsx:1:19] + ╭─[no_console_spaces.tsx:1:20] 1 │ console.warn("_", " warn ", "_") - · ──────── + · ────── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use leading spaces with `console.error` parameters - ╭─[no_console_spaces.tsx:1:20] + ╭─[no_console_spaces.tsx:1:21] 1 │ console.error("_", " error ", "_") - · ───────── + · ─────── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use trailing spaces with `console.error` parameters - ╭─[no_console_spaces.tsx:1:20] + ╭─[no_console_spaces.tsx:1:21] 1 │ console.error("_", " error ", "_") - · ───────── + · ─────── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use trailing spaces with `console.log` parameters - ╭─[no_console_spaces.tsx:1:16] + ╭─[no_console_spaces.tsx:1:17] 1 │ console["log"](" a ", " b "); - · ───── + · ─── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use leading spaces with `console.log` parameters - ╭─[no_console_spaces.tsx:1:23] + ╭─[no_console_spaces.tsx:1:24] 1 │ console["log"](" a ", " b "); - · ───── + · ─── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use trailing spaces with `console.debug` parameters - ╭─[no_console_spaces.tsx:1:18] + ╭─[no_console_spaces.tsx:1:19] 1 │ console["debug"](" a ", " b "); - · ───── + · ─── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use leading spaces with `console.debug` parameters - ╭─[no_console_spaces.tsx:1:25] + ╭─[no_console_spaces.tsx:1:26] 1 │ console["debug"](" a ", " b "); - · ───── + · ─── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use trailing spaces with `console.info` parameters - ╭─[no_console_spaces.tsx:1:17] + ╭─[no_console_spaces.tsx:1:18] 1 │ console["info"](" a ", " b "); - · ───── + · ─── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use leading spaces with `console.info` parameters - ╭─[no_console_spaces.tsx:1:24] + ╭─[no_console_spaces.tsx:1:25] 1 │ console["info"](" a ", " b "); - · ───── + · ─── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use trailing spaces with `console.warn` parameters - ╭─[no_console_spaces.tsx:1:17] + ╭─[no_console_spaces.tsx:1:18] 1 │ console["warn"](" a ", " b "); - · ───── + · ─── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use leading spaces with `console.warn` parameters - ╭─[no_console_spaces.tsx:1:24] + ╭─[no_console_spaces.tsx:1:25] 1 │ console["warn"](" a ", " b "); - · ───── + · ─── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use trailing spaces with `console.error` parameters - ╭─[no_console_spaces.tsx:1:18] + ╭─[no_console_spaces.tsx:1:19] 1 │ console["error"](" a ", " b "); - · ───── + · ─── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. ⚠ eslint-plugin-unicorn(no-console-spaces): Do not use leading spaces with `console.error` parameters - ╭─[no_console_spaces.tsx:1:25] + ╭─[no_console_spaces.tsx:1:26] 1 │ console["error"](" a ", " b "); - · ───── + · ─── ╰──── help: The `console.log()` method and similar methods join the parameters with a space so adding a leading/trailing space to a parameter, results in two spaces being added. diff --git a/crates/oxc_linter/src/snapshots/prefer_todo.snap b/crates/oxc_linter/src/snapshots/prefer_todo.snap index 3ae833f958853..50e990c478f88 100644 --- a/crates/oxc_linter/src/snapshots/prefer_todo.snap +++ b/crates/oxc_linter/src/snapshots/prefer_todo.snap @@ -3,21 +3,21 @@ source: crates/oxc_linter/src/tester.rs expression: prefer_todo --- ⚠ eslint-plugin-jest(prefer-todo): Suggest using `test.todo`. - ╭─[prefer_todo.tsx:1:5] + ╭─[prefer_todo.tsx:1:1] 1 │ test('i need to write this test'); - · ▲ + · ──── ╰──── ⚠ eslint-plugin-jest(prefer-todo): Suggest using `test.todo`. - ╭─[prefer_todo.tsx:1:5] + ╭─[prefer_todo.tsx:1:1] 1 │ test('i need to write this test',); - · ▲ + · ──── ╰──── ⚠ eslint-plugin-jest(prefer-todo): Suggest using `test.todo`. - ╭─[prefer_todo.tsx:1:5] + ╭─[prefer_todo.tsx:1:1] 1 │ test(`i need to write this test`); - · ▲ + · ──── ╰──── ⚠ eslint-plugin-jest(prefer-todo): Suggest using `test.todo`. diff --git a/crates/oxc_span/src/span.rs b/crates/oxc_span/src/span.rs index 7dd536a1ae6df..9ac0302efe3e0 100644 --- a/crates/oxc_span/src/span.rs +++ b/crates/oxc_span/src/span.rs @@ -307,6 +307,11 @@ impl From for LabeledSpan { pub trait GetSpan { fn span(&self) -> Span; } +impl GetSpan for Span { + fn span(&self) -> Span { + *self + } +} #[cfg(test)] mod test {