Skip to content

Commit 287b923

Browse files
committed
fix: match HTML5 unquoted attributes terminators
1 parent f5aa89b commit 287b923

2 files changed

Lines changed: 14 additions & 7 deletions

File tree

crates/oxc_parser/src/astro/jsx.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -688,11 +688,13 @@ impl<'a, C: ParserConfig> ParserImpl<'a, C> {
688688
while matches!(bytes.get(value_start), Some(b' ' | b'\t' | b'\r' | b'\n')) {
689689
value_start += 1;
690690
}
691-
// A structural terminator (`/`, `>`, `}`, EOF) means the value is missing
692-
// (`attr=` before `/>`); fall through to the JS path to report it there.
691+
// A structural terminator (`>`, `}`, EOF) means the value is missing
692+
// (`attr=` before `>`); fall through to the JS path to report it there.
693+
// `/` is a valid first value char (HTML5): `href=/about` is value `/about`,
694+
// and even `attr=/>` is value `/`, not a self-close.
693695
let is_unquoted = bytes
694696
.get(value_start)
695-
.is_some_and(|b| !matches!(b, b'"' | b'\'' | b'{' | b'`' | b'<' | b'/' | b'>' | b'}'));
697+
.is_some_and(|b| !matches!(b, b'"' | b'\'' | b'{' | b'`' | b'<' | b'>' | b'}'));
696698

697699
if is_unquoted {
698700
self.prev_token_end = self.cur_token().end(); // consume `=`

crates/oxc_parser/src/lexer/astro.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,17 @@ mod astro_jsx {
4343
b'=' | b'>' | b'/' | b'{' | b'}' | b' ' | b'\t' | b'\n' | b'\r' | b'<'
4444
));
4545

46-
/// Per HTML spec, unquoted attribute values cannot contain whitespace, `"`,
47-
/// `'`, `=`, `<`, `>`, or backtick. We additionally stop at `/`, `{`, and
48-
/// `}` so JSX self-closing tags and Astro expressions remain parseable.
46+
/// An unquoted attribute value ends only at HTML5's terminators — whitespace
47+
/// or `>` — plus the Astro-structural characters `{`/`}` (an expression
48+
/// starts/ends) and `<` (a following tag). Everything else is an ordinary
49+
/// value character, matching the HTML tokenizer, which flags `"`, `'`, `=`,
50+
/// and backtick as a parse error but still appends them. So `href=a=b` and
51+
/// `href=https://example.com/x` parse whole; the printer escapes any quotes
52+
/// or backticks when emitting the value. The `/` self-close form
53+
/// (`<img src=x />`) still works because the space ends the value first.
4954
static ASTRO_UNQUOTED_ATTR_VALUE_END_TABLE: SafeByteMatchTable = safe_byte_match_table!(|b| matches!(
5055
b,
51-
b'=' | b'>' | b'/' | b'{' | b'}' | b' ' | b'\t' | b'\n' | b'\r' | b'<' | b'"' | b'\'' | b'`'
56+
b'>' | b'{' | b'}' | b' ' | b'\t' | b'\n' | b'\r' | b'<'
5257
));
5358

5459
/// Astro/HTML text content can include `>` as literal text (unlike JSX).

0 commit comments

Comments
 (0)