mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2026-10-28 09:46:23 +08:00
Don't let quotes in Pug regex literals hide the end of an attribute list (#20572)
This is a follow-up to https://github.com/tailwindlabs/tailwindcss/pull/20547. That PR tracks strings inside Pug attribute lists, so that brackets in values like `a(title="a (b")` are ignored. But a quote that isn't a string delimiter, like the `"` in a regex such as `input(pattern=/"/.source)`, opened a string that never closed. That string hid the `)` of the attribute list. The list stayed open, so dots in class shorthands on later lines were not turned into spaces, and classes like `underline` in `span.underline.font-bold` were not extracted. ```pug input(pattern=/"/.source) span.underline.font-bold km ``` ## Solution Only template literals can span lines in JavaScript. So when a `'` or `"` string reaches the end of its line without a closing quote, it was not a string. In that case, we go back to just after the quote and scan the rest of the line again, with quotes as plain text. Each line is scanned at most twice, so this stays linear. Detecting regex literals directly needs a JavaScript tokenizer, because a `/` can also be a division. This approach also covers other unpaired quotes, like `// don't cache` in a `script.` block. The regression is not released yet, so there is no changelog entry: the existing entry of https://github.com/tailwindlabs/tailwindcss/pull/20547 covers it. ## Test plan Added cases to `test_unbalanced_brackets_in_quoted_attribute_values` in `pug.rs`. They fail without this change. ``` cargo test -p tailwindcss-oxide ```
This commit is contained in:
1 parent
2cf0e87e9a
commit
2ce6652f1c
1 file changed
+38
-5
@@ -47,6 +47,13 @@ impl PreProcessor for Pug {
|
||||
// ```
|
||||
let mut quote: Option<u8> = None;
|
||||
|
||||
// The position of the opening quote of the string we are in.
|
||||
let mut quote_start = 0;
|
||||
|
||||
// Whether quotes are text until the end of the line, because a string on this line didn't
|
||||
// end. E.g.: the `"` in a regex like `/"/`.
|
||||
let mut quotes_are_text = false;
|
||||
|
||||
while cursor.pos < len {
|
||||
if let Some(q) = quote {
|
||||
match cursor.curr() {
|
||||
@@ -54,11 +61,15 @@ impl PreProcessor for Pug {
|
||||
b'\\' => cursor.advance(),
|
||||
c if c == q => quote = None,
|
||||
|
||||
// Only template literals can span multiple lines. Ending other strings at the
|
||||
// end of the line ensures that an unpaired quote can't hide the rest of the
|
||||
// file. E.g.: `// don't cache` in a `script.` block.
|
||||
b'\n' if q != b'`' => {
|
||||
// Only template literals can span multiple lines, so this quote didn't start
|
||||
// a string. E.g.: `// don't cache` in a `script.` block, or a regex like
|
||||
// `/"/`. Scan the rest of the line again, with quotes as text, so that an
|
||||
// unpaired quote can't hide brackets like the `)` of the attribute list. The
|
||||
// end of the input also ends the line.
|
||||
c if q != b'`' && (c == b'\n' || cursor.pos + 1 == len) => {
|
||||
quote = None;
|
||||
quotes_are_text = true;
|
||||
cursor.move_to(quote_start + 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -94,8 +105,9 @@ impl PreProcessor for Pug {
|
||||
in_block_expansion_whitespace = false;
|
||||
}
|
||||
|
||||
b'"' | b'\'' | b'`' if in_attributes => {
|
||||
b'"' | b'\'' | b'`' if in_attributes && !quotes_are_text => {
|
||||
quote = Some(cursor.curr());
|
||||
quote_start = cursor.pos;
|
||||
}
|
||||
|
||||
// Whitespace ends the tag part of the line, unless it follows a `:` for block
|
||||
@@ -188,6 +200,10 @@ impl PreProcessor for Pug {
|
||||
in_block_expansion_whitespace = false;
|
||||
}
|
||||
|
||||
if cursor.curr() == b'\n' {
|
||||
quotes_are_text = false;
|
||||
}
|
||||
|
||||
cursor.advance();
|
||||
}
|
||||
|
||||
@@ -410,6 +426,23 @@ mod tests {
|
||||
"#;
|
||||
Pug::test_extract_contains(input, vec!["underline", "font-bold"]);
|
||||
|
||||
// A quote in a regex is not a string, so it does not hide the `)` of the attribute list
|
||||
let input = r#"
|
||||
input(pattern=/"/.source)
|
||||
span.underline.font-bold km
|
||||
"#;
|
||||
Pug::test_extract_contains(input, vec!["underline", "font-bold"]);
|
||||
|
||||
let input = r#"
|
||||
input(pattern=/"/.source title="x")
|
||||
span.underline.font-bold km
|
||||
"#;
|
||||
Pug::test_extract_contains(input, vec!["underline", "font-bold"]);
|
||||
|
||||
// Also on the last line, without a trailing newline
|
||||
let input = r#"div(data-pattern=/"/.source): span.underline.font-bold"#;
|
||||
Pug::test_extract_contains(input, vec!["underline", "font-bold"]);
|
||||
|
||||
// A `'` or `"` string can't span lines, so an unpaired quote does not hide the next lines
|
||||
let input = r#"
|
||||
script.
|
||||
|
||||
Reference in new issue
Block a user