Skip to content

Commit 5426baf

Browse files
authored
Fix class extraction followed by ( in Pug (#17320)
This PR fixes an issue where a class shorthand in Pug followed by a `(` is not properly extracted. ```html <template lang="pug"> .text-sky-600.bg-neutral-900(title="A tooltip") This div has an HTML attribute. </template> ``` The `text-sky-600` is extracted, but the `bg-neutral-900` is not. Fixes: #17313 # Test plan 1. Added test to cover this case 2. Existing tests pass (after a few small adjustments due to _more_ extracted candidates, but definitely not _less_) 3. Verified against the original issue (top is before, bottom is this PR) <img width="1307" alt="image" src="https://github.com/user-attachments/assets/68a0529f-63ad-477d-a342-e3f91c5a1690" /> We had this exact same bug in Slim (#17278). Since Pug, Slim and Haml are the only pre processors we have right now with this dot-separated class notation I also double checked the Haml pre-processor if this is an issue or not (and it's already covered there). <img width="1263" alt="image" src="https://github.com/user-attachments/assets/c658168b-d124-46c9-9ec0-9697151a57bf" />
1 parent 91c0d56 commit 5426baf

File tree

4 files changed

+48
-3
lines changed

4 files changed

+48
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3838
- Remove redundant `line-height: initial` from Preflight ([#15212](https://github.com/tailwindlabs/tailwindcss/pull/15212))
3939
- Increase Standalone hardware compatibility on macOS x64 builds ([#17267](https://github.com/tailwindlabs/tailwindcss/pull/17267))
4040
- Ensure that the CSS file rebuilds if a new CSS variable is used from templates ([#17301](https://github.com/tailwindlabs/tailwindcss/pull/17301))
41+
- Fix class extraction followed by `(` in Pug ([#17320](https://github.com/tailwindlabs/tailwindcss/pull/17320))
4142

4243
### Changed
4344

crates/oxide/src/extractor/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -605,7 +605,7 @@ mod tests {
605605
// Quoted attribute
606606
(
607607
r#"input(type="checkbox" class="px-2.5")"#,
608-
vec!["checkbox", "class", "px-2.5"],
608+
vec!["input", "type", "checkbox", "class", "px-2.5"],
609609
),
610610
] {
611611
assert_extract_sorted_candidates(&pre_process_input(input, "pug"), expected);

crates/oxide/src/extractor/pre_processors/pug.rs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,23 @@ impl PreProcessor for Pug {
5656
}
5757
}
5858

59+
// In Pug the class name shorthand can be followed by a parenthesis. E.g.:
60+
//
61+
// ```pug
62+
// body.border-t-4.p-8(attr=value)
63+
// ^ Not part of the p-8 class
64+
// ```
65+
//
66+
// This means that we need to replace all these `(` and `)` with spaces to make
67+
// sure that we can extract the `p-8`.
68+
//
69+
// However, we also need to make sure that we keep the parens that are part of the
70+
// utility class. E.g.: `bg-(--my-color)`.
71+
b'(' if bracket_stack.is_empty() && !matches!(cursor.prev, b'-' | b'/') => {
72+
result[cursor.pos] = b' ';
73+
bracket_stack.push(cursor.curr);
74+
}
75+
5976
b'(' | b'[' | b'{' => {
6077
bracket_stack.push(cursor.curr);
6178
}
@@ -87,7 +104,7 @@ mod tests {
87104
("div.flex.bg-red-500", "div flex bg-red-500"),
88105
(".flex.bg-red-500", " flex bg-red-500"),
89106
// Keep dots in strings
90-
(r#"div(class="px-2.5")"#, r#"div(class="px-2.5")"#),
107+
(r#"div(class="px-2.5")"#, r#"div class="px-2.5")"#),
91108
// Nested brackets
92109
(
93110
"bg-[url(https://example.com/?q=[1,2])]",
@@ -134,4 +151,31 @@ mod tests {
134151
"#;
135152
Pug::test_extract_contains(input, vec!["flex", "items-center"]);
136153
}
154+
155+
// https://github.com/tailwindlabs/tailwindcss/issues/17313
156+
#[test]
157+
fn test_class_shorthand_followed_by_parens() {
158+
let input = r#"
159+
.text-sky-600.bg-neutral-900(title="A tooltip") This div has an HTML attribute.
160+
"#;
161+
Pug::test_extract_contains(input, vec!["text-sky-600", "bg-neutral-900"]);
162+
163+
// Additional test with CSS Variable shorthand syntax in the attribute itself because `(`
164+
// and `)` are not valid in the class shorthand version.
165+
//
166+
// Also included an arbitrary value including `(` and `)` to make sure that we don't
167+
// accidentally remove those either.
168+
let input = r#"
169+
.p-8(class="bg-(--my-color) bg-(--my-color)/(--my-opacity) bg-[url(https://example.com)]")
170+
"#;
171+
Pug::test_extract_contains(
172+
input,
173+
vec![
174+
"p-8",
175+
"bg-(--my-color)",
176+
"bg-(--my-color)/(--my-opacity)",
177+
"bg-[url(https://example.com)]",
178+
],
179+
);
180+
}
137181
}

crates/oxide/src/extractor/pre_processors/slim.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ impl PreProcessor for Slim {
8080
bracket_stack.push(cursor.curr);
8181
}
8282

83-
// In slim the class name shorthand can be followed by a parenthesis. E.g.:
83+
// In Slim the class name shorthand can be followed by a parenthesis. E.g.:
8484
//
8585
// ```slim
8686
// body.border-t-4.p-8(attr=value)

0 commit comments

Comments
 (0)