Skip to content

Commit be4b6df

Browse files
committed
fix(linter): unicorn/prefer-string-replace-all incorrectly escapes backslashes (#15901)
fixes #15898
1 parent 0bff596 commit be4b6df

File tree

2 files changed

+38
-9
lines changed

2 files changed

+38
-9
lines changed

crates/oxc_linter/src/rules/unicorn/prefer_string_replace_all.rs

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
use oxc_allocator::Allocator;
12
use oxc_ast::{
2-
AstKind,
3+
AstBuilder, AstKind,
34
ast::{Argument, MemberExpression, RegExpFlags},
45
};
6+
use oxc_codegen::CodegenOptions;
57
use oxc_diagnostics::OxcDiagnostic;
68
use oxc_macros::declare_oxc_lint;
79
use oxc_regular_expression::ast::Term;
8-
use oxc_span::{CompactStr, GetSpan, Span};
10+
use oxc_span::{CompactStr, GetSpan, SPAN, Span};
911

1012
use crate::{AstNode, ast_util::extract_regex_flags, context::LintContext, rule::Rule};
1113

@@ -81,8 +83,19 @@ impl Rule for PreferStringReplaceAll {
8183
"replaceAll" => {
8284
if let Some(k) = get_pattern_replacement(pattern) {
8385
ctx.diagnostic_with_fix(string_literal(pattern.span(), &k), |fixer| {
84-
// foo.replaceAll(/hello world/g, bar) => foo.replaceAll("hello world", bar)
85-
fixer.replace(pattern.span(), format!("{k:?}"))
86+
// foo.replaceAll(/hello world/g, bar) => foo.replaceAll('hello world', bar)
87+
let mut codegen = fixer.codegen().with_options(CodegenOptions {
88+
single_quote: true,
89+
..Default::default()
90+
});
91+
let alloc = Allocator::default();
92+
let ast = AstBuilder::new(&alloc);
93+
codegen.print_expression(&ast.expression_string_literal(
94+
SPAN,
95+
ast.atom(&k),
96+
None,
97+
));
98+
fixer.replace(pattern.span(), codegen.into_source_text())
8699
});
87100
}
88101
}
@@ -137,7 +150,22 @@ fn get_pattern_replacement<'a>(expr: &'a Argument<'a>) -> Option<CompactStr> {
137150
return None;
138151
}
139152

140-
Some(CompactStr::new(reg_exp_literal.regex.pattern.text.as_str()))
153+
// Convert the regex pattern to a string by extracting character values
154+
// from the parsed AST instead of using the raw source text.
155+
// This ensures escape sequences are properly handled.
156+
let mut result = String::new();
157+
for term in pattern_terms {
158+
if let Term::Character(ch) = term {
159+
if let Some(c) = char::from_u32(ch.value) {
160+
result.push(c);
161+
} else {
162+
// Invalid character, fall back to source text
163+
return Some(CompactStr::new(reg_exp_literal.regex.pattern.text.as_str()));
164+
}
165+
}
166+
}
167+
168+
Some(CompactStr::new(&result))
141169
}
142170

143171
#[test]
@@ -230,7 +258,8 @@ fn test() {
230258

231259
let fix = vec![
232260
("foo.replace(/a/g, bar)", "foo.replaceAll(/a/g, bar)"),
233-
("foo.replaceAll(/a/g, bar)", "foo.replaceAll(\"a\", bar)"),
261+
("foo.replaceAll(/a/g, bar)", "foo.replaceAll('a', bar)"),
262+
(r"text.replaceAll(/\\`/g, '`')", r"text.replaceAll('\\`', '`')"),
234263
];
235264

236265
Tester::new(PreferStringReplaceAll::NAME, PreferStringReplaceAll::PLUGIN, pass, fail)

crates/oxc_linter/src/snapshots/unicorn_prefer_string_replace_all.snap

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -251,14 +251,14 @@ source: crates/oxc_linter/src/tester.rs
251251
1foo.replaceAll(/a]/g, _)
252252
· ─────
253253
╰────
254-
help: Replace `/a]/g` with `"a]"`.
254+
help: Replace `/a]/g` with `'a]'`.
255255

256256
eslint-plugin-unicorn(prefer-string-replace-all): This pattern can be replaced with `a very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long string`.
257257
╭─[prefer_string_replace_all.tsx:1:16]
258258
1foo.replaceAll(/a very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long string/g, _)
259259
· ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
260260
╰────
261-
help: Replace `/a very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long string/g` with `"a very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long string"`.
261+
help: Replace `/a very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long string/g` with `'a very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long string'`.
262262

263263
eslint-plugin-unicorn(prefer-string-replace-all): Prefer `String#replaceAll()` over `String#replace()` when using a regex with the global flag.
264264
╭─[prefer_string_replace_all.tsx:1:5]
@@ -272,4 +272,4 @@ source: crates/oxc_linter/src/tester.rs
272272
1"Hello world".replaceAll(/world/g, 'world!');
273273
· ────────
274274
╰────
275-
help: Replace `/world/g` with `"world"`.
275+
help: Replace `/world/g` with `'world'`.

0 commit comments

Comments
 (0)