|
| 1 | +use oxc_allocator::Allocator; |
1 | 2 | use oxc_ast::{ |
2 | | - AstKind, |
| 3 | + AstBuilder, AstKind, |
3 | 4 | ast::{Argument, MemberExpression, RegExpFlags}, |
4 | 5 | }; |
| 6 | +use oxc_codegen::CodegenOptions; |
5 | 7 | use oxc_diagnostics::OxcDiagnostic; |
6 | 8 | use oxc_macros::declare_oxc_lint; |
7 | 9 | use oxc_regular_expression::ast::Term; |
8 | | -use oxc_span::{CompactStr, GetSpan, Span}; |
| 10 | +use oxc_span::{CompactStr, GetSpan, SPAN, Span}; |
9 | 11 |
|
10 | 12 | use crate::{AstNode, ast_util::extract_regex_flags, context::LintContext, rule::Rule}; |
11 | 13 |
|
@@ -81,8 +83,19 @@ impl Rule for PreferStringReplaceAll { |
81 | 83 | "replaceAll" => { |
82 | 84 | if let Some(k) = get_pattern_replacement(pattern) { |
83 | 85 | 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()) |
86 | 99 | }); |
87 | 100 | } |
88 | 101 | } |
@@ -137,7 +150,22 @@ fn get_pattern_replacement<'a>(expr: &'a Argument<'a>) -> Option<CompactStr> { |
137 | 150 | return None; |
138 | 151 | } |
139 | 152 |
|
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)) |
141 | 169 | } |
142 | 170 |
|
143 | 171 | #[test] |
@@ -230,7 +258,8 @@ fn test() { |
230 | 258 |
|
231 | 259 | let fix = vec![ |
232 | 260 | ("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('\\`', '`')"), |
234 | 263 | ]; |
235 | 264 |
|
236 | 265 | Tester::new(PreferStringReplaceAll::NAME, PreferStringReplaceAll::PLUGIN, pass, fail) |
|
0 commit comments