diff --git a/CHANGELOG.md b/CHANGELOG.md index e1fbf3177..63265693d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Remove redundant `line-height: initial` from Preflight ([#15212](https://github.com/tailwindlabs/tailwindcss/pull/15212)) - Increase Standalone hardware compatibility on macOS x64 builds ([#17267](https://github.com/tailwindlabs/tailwindcss/pull/17267)) - Ensure that the CSS file rebuilds if a new CSS variable is used from templates ([#17301](https://github.com/tailwindlabs/tailwindcss/pull/17301)) +- Fix class extraction followed by `(` in Pug ([#17320](https://github.com/tailwindlabs/tailwindcss/pull/17320)) ### Changed diff --git a/crates/oxide/src/extractor/mod.rs b/crates/oxide/src/extractor/mod.rs index bf2fdd35e..ce6d0aeae 100644 --- a/crates/oxide/src/extractor/mod.rs +++ b/crates/oxide/src/extractor/mod.rs @@ -605,7 +605,7 @@ mod tests { // Quoted attribute ( r#"input(type="checkbox" class="px-2.5")"#, - vec!["checkbox", "class", "px-2.5"], + vec!["input", "type", "checkbox", "class", "px-2.5"], ), ] { assert_extract_sorted_candidates(&pre_process_input(input, "pug"), expected); diff --git a/crates/oxide/src/extractor/pre_processors/pug.rs b/crates/oxide/src/extractor/pre_processors/pug.rs index ffee00c1d..7b44fcf5b 100644 --- a/crates/oxide/src/extractor/pre_processors/pug.rs +++ b/crates/oxide/src/extractor/pre_processors/pug.rs @@ -56,6 +56,23 @@ impl PreProcessor for Pug { } } + // In Pug the class name shorthand can be followed by a parenthesis. E.g.: + // + // ```pug + // body.border-t-4.p-8(attr=value) + // ^ Not part of the p-8 class + // ``` + // + // This means that we need to replace all these `(` and `)` with spaces to make + // sure that we can extract the `p-8`. + // + // However, we also need to make sure that we keep the parens that are part of the + // utility class. E.g.: `bg-(--my-color)`. + b'(' if bracket_stack.is_empty() && !matches!(cursor.prev, b'-' | b'/') => { + result[cursor.pos] = b' '; + bracket_stack.push(cursor.curr); + } + b'(' | b'[' | b'{' => { bracket_stack.push(cursor.curr); } @@ -87,7 +104,7 @@ mod tests { ("div.flex.bg-red-500", "div flex bg-red-500"), (".flex.bg-red-500", " flex bg-red-500"), // Keep dots in strings - (r#"div(class="px-2.5")"#, r#"div(class="px-2.5")"#), + (r#"div(class="px-2.5")"#, r#"div class="px-2.5")"#), // Nested brackets ( "bg-[url(https://example.com/?q=[1,2])]", @@ -134,4 +151,31 @@ mod tests { "#; Pug::test_extract_contains(input, vec!["flex", "items-center"]); } + + // https://github.com/tailwindlabs/tailwindcss/issues/17313 + #[test] + fn test_class_shorthand_followed_by_parens() { + let input = r#" + .text-sky-600.bg-neutral-900(title="A tooltip") This div has an HTML attribute. + "#; + Pug::test_extract_contains(input, vec!["text-sky-600", "bg-neutral-900"]); + + // Additional test with CSS Variable shorthand syntax in the attribute itself because `(` + // and `)` are not valid in the class shorthand version. + // + // Also included an arbitrary value including `(` and `)` to make sure that we don't + // accidentally remove those either. + let input = r#" + .p-8(class="bg-(--my-color) bg-(--my-color)/(--my-opacity) bg-[url(https://example.com)]") + "#; + Pug::test_extract_contains( + input, + vec![ + "p-8", + "bg-(--my-color)", + "bg-(--my-color)/(--my-opacity)", + "bg-[url(https://example.com)]", + ], + ); + } } diff --git a/crates/oxide/src/extractor/pre_processors/slim.rs b/crates/oxide/src/extractor/pre_processors/slim.rs index 78e1cb0f5..9f8855c2c 100644 --- a/crates/oxide/src/extractor/pre_processors/slim.rs +++ b/crates/oxide/src/extractor/pre_processors/slim.rs @@ -80,7 +80,7 @@ impl PreProcessor for Slim { bracket_stack.push(cursor.curr); } - // In slim the class name shorthand can be followed by a parenthesis. E.g.: + // In Slim the class name shorthand can be followed by a parenthesis. E.g.: // // ```slim // body.border-t-4.p-8(attr=value)