Skip to content

Commit ada0a39

Browse files
committed
fix(regex-parser): parse simple TemplateLiterals
1 parent 648e939 commit ada0a39

File tree

9 files changed

+95
-37
lines changed

9 files changed

+95
-37
lines changed

crates/oxc_linter/src/rules/eslint/no_control_regex.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,9 @@ mod tests {
317317
r"/\u{1F}/",
318318
r"/\u{1F}/g",
319319
r"new RegExp('\\u{20}', 'u')",
320+
r"new RegExp('\\u{20}', `u`)",
320321
r"new RegExp('\\u{1F}')",
322+
r"new RegExp(`\\u{1F}`)",
321323
r"new RegExp('\\u{1F}', 'g')",
322324
r"new RegExp('\\u{1F}', flags)", // unknown flags, we assume no 'u'
323325
// https://github.com/oxc-project/oxc/issues/6136
@@ -347,6 +349,8 @@ mod tests {
347349
r"/\u{1F}/u",
348350
r"/\u{1F}/ugi",
349351
r"new RegExp('\\u{1F}', 'u')",
352+
r"new RegExp(`\\u{1F}`, 'u')",
353+
r"new RegExp('\\u{1F}', `u`)",
350354
r"new RegExp('\\u{1F}', 'ugi')",
351355
// https://github.com/oxc-project/oxc/issues/6136
352356
r"/\u{0a}/u",

crates/oxc_linter/src/rules/eslint/no_useless_backreference.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,7 @@ fn test() {
365365
r"new RegExp(`${prefix}\\1(a)`)",
366366
r"let RegExp; new RegExp('\\1(a)');",
367367
r"function foo() { var RegExp; RegExp('\\1(a)', 'u'); }",
368+
r"function foo() { var RegExp; RegExp('\\1(a)', `u`); }",
368369
r"function foo(RegExp) { new RegExp('\\1(a)'); }",
369370
r"if (foo) { const RegExp = bar; RegExp('\\1(a)'); }",
370371
// we don't support globals off yet
476 Bytes
Binary file not shown.

crates/oxc_linter/src/utils/regex.rs

Lines changed: 72 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,76 @@ use oxc_span::Span;
66

77
use crate::{AstNode, context::LintContext};
88

9+
fn run_on_arguments<'a, M>(
10+
arg1: Option<&'a Argument>,
11+
arg2: Option<&'a Argument>,
12+
ctx: &'a LintContext<'_>,
13+
cb: M,
14+
) where
15+
M: FnOnce(&Pattern<'_>, Span),
16+
{
17+
// note: improvements required for strings used via identifier references
18+
// Missing or non-string arguments will be runtime errors, but are not covered by this rule.
19+
match (arg1, arg2) {
20+
(Some(Argument::StringLiteral(pattern)), Some(Argument::StringLiteral(flags))) => {
21+
let allocator = Allocator::default();
22+
if let Some(pat) = parse_regex(&allocator, pattern.span, Some(flags.span), ctx) {
23+
cb(&pat, pattern.span);
24+
}
25+
}
26+
(Some(Argument::StringLiteral(pattern)), Some(Argument::TemplateLiteral(flags))) => {
27+
let allocator = Allocator::default();
28+
if let Some(pat) = parse_regex(
29+
&allocator,
30+
pattern.span,
31+
flags.is_no_substitution_template().then(|| flags.span),
32+
ctx,
33+
) {
34+
cb(&pat, pattern.span);
35+
}
36+
}
37+
(Some(Argument::StringLiteral(pattern)), _) => {
38+
let allocator = Allocator::default();
39+
if let Some(pat) = parse_regex(&allocator, pattern.span, None, ctx) {
40+
cb(&pat, pattern.span);
41+
}
42+
}
43+
(Some(Argument::TemplateLiteral(pattern)), Some(Argument::TemplateLiteral(flags))) => {
44+
if !pattern.is_no_substitution_template() {
45+
return;
46+
}
47+
let allocator = Allocator::default();
48+
if let Some(pat) = parse_regex(
49+
&allocator,
50+
pattern.span,
51+
flags.is_no_substitution_template().then(|| flags.span),
52+
ctx,
53+
) {
54+
cb(&pat, pattern.span);
55+
}
56+
}
57+
(Some(Argument::TemplateLiteral(pattern)), Some(Argument::StringLiteral(flags))) => {
58+
if !pattern.is_no_substitution_template() {
59+
return;
60+
}
61+
let allocator = Allocator::default();
62+
if let Some(pat) = parse_regex(&allocator, pattern.span, Some(flags.span), ctx) {
63+
cb(&pat, pattern.span);
64+
}
65+
}
66+
(Some(Argument::TemplateLiteral(pattern)), _) => {
67+
if !pattern.is_no_substitution_template() {
68+
return;
69+
}
70+
let allocator = Allocator::default();
71+
if let Some(pat) = parse_regex(&allocator, pattern.span, None, ctx) {
72+
cb(&pat, pattern.span);
73+
}
74+
}
75+
_ => {}
76+
}
77+
}
78+
979
pub fn run_on_regex_node<'a, 'b, M>(node: &'a AstNode<'b>, ctx: &'a LintContext<'b>, cb: M)
1080
where
1181
M: FnOnce(&Pattern<'_>, Span),
@@ -19,48 +89,14 @@ where
1989
AstKind::NewExpression(expr)
2090
if expr.callee.is_global_reference_name("RegExp", ctx.semantic().scoping()) =>
2191
{
22-
// note: improvements required for strings used via identifier references
23-
// Missing or non-string arguments will be runtime errors, but are not covered by this rule.
24-
match (&expr.arguments.first(), &expr.arguments.get(1)) {
25-
(Some(Argument::StringLiteral(pattern)), Some(Argument::StringLiteral(flags))) => {
26-
let allocator = Allocator::default();
27-
if let Some(pat) = parse_regex(&allocator, pattern.span, Some(flags.span), ctx)
28-
{
29-
cb(&pat, pattern.span);
30-
}
31-
}
32-
(Some(Argument::StringLiteral(pattern)), _) => {
33-
let allocator = Allocator::default();
34-
if let Some(pat) = parse_regex(&allocator, pattern.span, None, ctx) {
35-
cb(&pat, pattern.span);
36-
}
37-
}
38-
_ => {}
39-
}
92+
run_on_arguments(expr.arguments.first(), expr.arguments.get(1), ctx, cb);
4093
}
4194

4295
// RegExp()
4396
AstKind::CallExpression(expr)
4497
if expr.callee.is_global_reference_name("RegExp", ctx.semantic().scoping()) =>
4598
{
46-
// note: improvements required for strings used via identifier references
47-
// Missing or non-string arguments will be runtime errors, but are not covered by this rule.
48-
match (&expr.arguments.first(), &expr.arguments.get(1)) {
49-
(Some(Argument::StringLiteral(pattern)), Some(Argument::StringLiteral(flags))) => {
50-
let allocator = Allocator::default();
51-
if let Some(pat) = parse_regex(&allocator, pattern.span, Some(flags.span), ctx)
52-
{
53-
cb(&pat, pattern.span);
54-
}
55-
}
56-
(Some(Argument::StringLiteral(pattern)), _) => {
57-
let allocator = Allocator::default();
58-
if let Some(pat) = parse_regex(&allocator, pattern.span, None, ctx) {
59-
cb(&pat, pattern.span);
60-
}
61-
}
62-
_ => {}
63-
}
99+
run_on_arguments(expr.arguments.first(), expr.arguments.get(1), ctx, cb);
64100
}
65101
_ => {}
66102
}

crates/oxc_regular_expression/src/parser/parser_impl.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ impl<'a> ConstructorParser<'a> {
9393
(false, false)
9494
};
9595

96-
let pattern_text = if matches!(self.pattern_text, r#""""# | "''") {
96+
let pattern_text = if matches!(self.pattern_text, r#""""# | "''" | "``") {
9797
r#""(?:)""#
9898
} else {
9999
self.pattern_text

crates/oxc_regular_expression/src/parser/reader/reader_impl.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::parser::reader::string_literal_parser::{
66
parse_regexp_literal,
77
};
88

9+
#[derive(Debug)]
910
pub struct Reader<'a> {
1011
source_text: &'a str,
1112
units: Vec<StringLiteralAst::CodePoint>,

crates/oxc_regular_expression/src/parser/reader/string_literal_parser/ast.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pub struct StringLiteral {
1313
pub enum StringLiteralKind {
1414
Double,
1515
Single,
16+
Template,
1617
}
1718

1819
/// Represents UTF-16 code unit(u16 as u32) or Unicode code point(char as u32).

crates/oxc_regular_expression/src/parser/reader/string_literal_parser/mod.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ without continuation""#,
120120

121121
let ast = Parser::new(r"'single'", options).parse().unwrap();
122122
assert_eq!(ast.kind, ast::StringLiteralKind::Single);
123+
124+
let ast = Parser::new(r"`template`", options).parse().unwrap();
125+
assert_eq!(ast.kind, ast::StringLiteralKind::Template);
123126
}
124127

125128
#[test]
@@ -240,4 +243,14 @@ without continuation""#,
240243
assert_eq!(a.value, b.value);
241244
}
242245
}
246+
247+
#[test]
248+
fn regexp_literal_with_template_literal_and_new_line() {
249+
let source_text = r"re = new RegExp(`^12
250+
34$`)";
251+
252+
let result = Parser::new(source_text, Options::default()).parse();
253+
254+
assert!(result.is_ok(), "Expected to parse: {source_text} but failed: {:?}", result.err());
255+
}
243256
}

crates/oxc_regular_expression/src/parser/reader/string_literal_parser/parser_impl.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ impl Parser {
8585
('"', ast::StringLiteralKind::Double)
8686
} else if self.eat('\'') {
8787
('\'', ast::StringLiteralKind::Single)
88+
} else if self.eat('`') {
89+
('`', ast::StringLiteralKind::Template)
8890
} else {
8991
return Err(diagnostics::invalid_input(Span::empty(self.options.span_offset)));
9092
};

0 commit comments

Comments
 (0)