Skip to content

Commit 455236e

Browse files
committed
fix(regex-parser): parse simple TemplateLiterals
1 parent 7e78e39 commit 455236e

File tree

18 files changed

+789
-45
lines changed

18 files changed

+789
-45
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: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,19 +39,55 @@ where
3939
let arg2 = arg2.and_then(Argument::as_expression).map(Expression::get_inner_expression);
4040
// note: improvements required for strings used via identifier references
4141
// Missing or non-string arguments will be runtime errors, but are not covered by this rule.
42-
match (&arg1, &arg2) {
42+
match (arg1, arg2) {
4343
(Some(Expression::StringLiteral(pattern)), Some(Expression::StringLiteral(flags))) => {
4444
let allocator = Allocator::default();
4545
if let Some(pat) = parse_regex(&allocator, pattern.span, Some(flags.span), ctx) {
4646
cb(&pat, pattern.span);
4747
}
4848
}
49+
(Some(Expression::StringLiteral(pattern)), Some(Expression::TemplateLiteral(flags))) => {
50+
if !flags.is_no_substitution_template() {
51+
return;
52+
}
53+
let allocator = Allocator::default();
54+
if let Some(pat) = parse_regex(&allocator, pattern.span, Some(flags.span), ctx) {
55+
cb(&pat, pattern.span);
56+
}
57+
}
4958
(Some(Expression::StringLiteral(pattern)), _) => {
5059
let allocator = Allocator::default();
5160
if let Some(pat) = parse_regex(&allocator, pattern.span, None, ctx) {
5261
cb(&pat, pattern.span);
5362
}
5463
}
64+
(Some(Expression::TemplateLiteral(pattern)), Some(Expression::TemplateLiteral(flags))) => {
65+
if !pattern.is_no_substitution_template() || !flags.is_no_substitution_template() {
66+
return;
67+
}
68+
let allocator = Allocator::default();
69+
if let Some(pat) = parse_regex(&allocator, pattern.span, Some(flags.span), ctx) {
70+
cb(&pat, pattern.span);
71+
}
72+
}
73+
(Some(Expression::TemplateLiteral(pattern)), Some(Expression::StringLiteral(flags))) => {
74+
if !pattern.is_no_substitution_template() {
75+
return;
76+
}
77+
let allocator = Allocator::default();
78+
if let Some(pat) = parse_regex(&allocator, pattern.span, Some(flags.span), ctx) {
79+
cb(&pat, pattern.span);
80+
}
81+
}
82+
(Some(Expression::TemplateLiteral(pattern)), _) => {
83+
if !pattern.is_no_substitution_template() {
84+
return;
85+
}
86+
let allocator = Allocator::default();
87+
if let Some(pat) = parse_regex(&allocator, pattern.span, None, ctx) {
88+
cb(&pat, pattern.span);
89+
}
90+
}
5591
_ => {}
5692
}
5793
}

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
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
use oxc_span::Span;
2+
3+
/// Represents UTF-16 code unit(u16 as u32) or Unicode code point(char as u32).
4+
/// `Span` width may be more than 1, since there will be escape sequences.
5+
#[derive(Debug, Clone, Copy)]
6+
pub struct CodePoint {
7+
pub span: Span,
8+
// NOTE: If we need codegen, more information should be added.
9+
pub value: u32,
10+
}
File renamed without changes.

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1+
mod ast;
2+
mod characters;
3+
mod options;
14
mod reader_impl;
25
mod string_literal_parser;
6+
mod template_literal_parser;
37

8+
pub use ast::*;
9+
pub use options::Options;
410
pub use reader_impl::Reader;
511

612
#[cfg(test)]
File renamed without changes.

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

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
use oxc_diagnostics::Result;
22
use oxc_span::Atom;
33

4-
use crate::parser::reader::string_literal_parser::{
5-
Options as StringLiteralParserOptions, Parser as StringLiteralParser, ast as StringLiteralAst,
6-
parse_regexp_literal,
4+
use crate::parser::reader::{
5+
Options,
6+
ast::CodePoint,
7+
string_literal_parser::{
8+
Parser as StringLiteralParser, ast as StringLiteralAst, parse_regexp_literal,
9+
},
10+
template_literal_parser::{Parser as TemplateLiteralParser, ast as TemplateLiteralAst},
711
};
812

13+
#[derive(Debug)]
914
pub struct Reader<'a> {
1015
source_text: &'a str,
11-
units: Vec<StringLiteralAst::CodePoint>,
16+
units: Vec<CodePoint>,
1217
index: usize,
1318
offset: u32,
1419
}
@@ -17,24 +22,37 @@ impl<'a> Reader<'a> {
1722
pub fn initialize(
1823
source_text: &'a str,
1924
unicode_mode: bool,
20-
parse_string_literal: bool,
25+
parse_string_or_template_literal: bool,
2126
) -> Result<Self> {
2227
// NOTE: This must be `0`.
2328
// Since `source_text` here may be a slice of the original source text,
2429
// using `Span` for `span.source_text(source_text)` will be out of range in some cases.
2530
let span_offset = 0;
2631

27-
let units = if parse_string_literal {
28-
let StringLiteralAst::StringLiteral { body, .. } = StringLiteralParser::new(
29-
source_text,
30-
StringLiteralParserOptions {
31-
strict_mode: false,
32-
span_offset,
33-
combine_surrogate_pair: unicode_mode,
34-
},
35-
)
36-
.parse()?;
37-
body
32+
let units = if parse_string_or_template_literal {
33+
if source_text.chars().next().is_some_and(|c| c == '`') {
34+
let TemplateLiteralAst::TemplateLiteral { body, .. } = TemplateLiteralParser::new(
35+
source_text,
36+
Options {
37+
strict_mode: false,
38+
span_offset,
39+
combine_surrogate_pair: unicode_mode,
40+
},
41+
)
42+
.parse()?;
43+
body
44+
} else {
45+
let StringLiteralAst::StringLiteral { body, .. } = StringLiteralParser::new(
46+
source_text,
47+
Options {
48+
strict_mode: false,
49+
span_offset,
50+
combine_surrogate_pair: unicode_mode,
51+
},
52+
)
53+
.parse()?;
54+
body
55+
}
3856
} else {
3957
parse_regexp_literal(source_text, span_offset, unicode_mode)
4058
};
@@ -45,7 +63,7 @@ impl<'a> Reader<'a> {
4563
index: 0,
4664
// If `parse_string_literal` is `true`, the first character is the opening quote.
4765
// We need to +1 to skip it.
48-
offset: u32::from(parse_string_literal),
66+
offset: u32::from(parse_string_or_template_literal),
4967
})
5068
}
5169

0 commit comments

Comments
 (0)