Skip to content

Commit 5085424

Browse files
authored
fix(html): handle escaped regex literals in astro frontmatter (#9728)
1 parent 467fd87 commit 5085424

4 files changed

Lines changed: 102 additions & 9 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@biomejs/biome": patch
3+
---
4+
5+
Fixed [#9696](https://github.com/biomejs/biome/issues/9696): Astro frontmatter now correctly parses regular expression literals like `/\d{4}/`.

crates/biome_html_parser/src/lexer/mod.rs

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1674,15 +1674,6 @@ impl QuotesSeen {
16741674
return;
16751675
}
16761676

1677-
// Handle escape sequences: a `\` that is not itself escaped toggles the
1678-
// escape flag for the next character.
1679-
if byte == b'\\' {
1680-
self.escaped = !self.escaped;
1681-
self.prev_byte = Some(byte);
1682-
self.prev_non_ws_byte = Some(byte);
1683-
return;
1684-
}
1685-
16861677
// If the current byte is escaped, it cannot act as a string delimiter
16871678
// or comment opener.
16881679
let was_escaped = self.escaped;
@@ -1746,6 +1737,15 @@ impl QuotesSeen {
17461737
self.prev_non_ws_byte = Some(b'/');
17471738
}
17481739

1740+
// Handle escape sequences: a `\` that is not itself escaped toggles the
1741+
// escape flag for the next character.
1742+
if byte == b'\\' {
1743+
self.escaped = !self.escaped;
1744+
self.prev_byte = Some(byte);
1745+
self.prev_non_ws_byte = Some(byte);
1746+
return;
1747+
}
1748+
17491749
// Track string delimiters.
17501750
match byte {
17511751
b'"' | b'\'' | b'`' => {
@@ -2101,4 +2101,19 @@ const f = "something" "#;
21012101
"regex literal containing dashes must not confuse the tracker"
21022102
);
21032103
}
2104+
2105+
/// A regex literal containing an escaped character followed by a quantifier
2106+
/// must close cleanly. This mirrors `/\d{4}/`, which previously regressed
2107+
/// in Astro frontmatter scanning.
2108+
#[test]
2109+
fn issue_9187_regex_with_escape_and_quantifier() {
2110+
let source = r"const test = /\d{4}/
2111+
";
2112+
let mut quotes_seen = QuotesSeen::new();
2113+
track(source, &mut quotes_seen);
2114+
assert!(
2115+
quotes_seen.is_empty(),
2116+
"regex literal containing an escape and quantifier must close cleanly"
2117+
);
2118+
}
21042119
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
const RE = /\d{4}/
3+
---
4+
5+
<div />
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
---
2+
source: crates/biome_html_parser/tests/spec_test.rs
3+
assertion_line: 145
4+
expression: snapshot
5+
---
6+
7+
## Input
8+
9+
```astro
10+
---
11+
const RE = /\d{4}/
12+
---
13+
14+
<div />
15+
16+
```
17+
18+
19+
## AST
20+
21+
```
22+
HtmlRoot {
23+
bom_token: missing (optional),
24+
frontmatter: AstroFrontmatterElement {
25+
l_fence_token: FENCE@0..3 "---" [] [],
26+
content: AstroEmbeddedContent {
27+
content_token: HTML_LITERAL@3..23 "const RE = /\\d{4}/\n" [Newline("\n")] [],
28+
},
29+
r_fence_token: FENCE@23..26 "---" [] [],
30+
},
31+
directive: missing (optional),
32+
html: HtmlElementList [
33+
HtmlSelfClosingElement {
34+
l_angle_token: L_ANGLE@26..29 "<" [Newline("\n"), Newline("\n")] [],
35+
name: HtmlTagName {
36+
value_token: HTML_LITERAL@29..33 "div" [] [Whitespace(" ")],
37+
},
38+
attributes: HtmlAttributeList [],
39+
slash_token: SLASH@33..34 "/" [] [],
40+
r_angle_token: R_ANGLE@34..35 ">" [] [],
41+
},
42+
],
43+
eof_token: EOF@35..36 "" [Newline("\n")] [],
44+
}
45+
```
46+
47+
## CST
48+
49+
```
50+
0: HTML_ROOT@0..36
51+
0: (empty)
52+
1: ASTRO_FRONTMATTER_ELEMENT@0..26
53+
0: FENCE@0..3 "---" [] []
54+
1: ASTRO_EMBEDDED_CONTENT@3..23
55+
0: HTML_LITERAL@3..23 "const RE = /\\d{4}/\n" [Newline("\n")] []
56+
2: FENCE@23..26 "---" [] []
57+
2: (empty)
58+
3: HTML_ELEMENT_LIST@26..35
59+
0: HTML_SELF_CLOSING_ELEMENT@26..35
60+
0: L_ANGLE@26..29 "<" [Newline("\n"), Newline("\n")] []
61+
1: HTML_TAG_NAME@29..33
62+
0: HTML_LITERAL@29..33 "div" [] [Whitespace(" ")]
63+
2: HTML_ATTRIBUTE_LIST@33..33
64+
3: SLASH@33..34 "/" [] []
65+
4: R_ANGLE@34..35 ">" [] []
66+
4: EOF@35..36 "" [Newline("\n")] []
67+
68+
```

0 commit comments

Comments
 (0)