From 4c9ade609b26bf7d0fea06183eef828f0905fee2 Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Tue, 28 Jan 2025 14:30:30 +0100 Subject: [PATCH 1/5] Fix svelte `class:` shorthand to work with newlines and tabs --- crates/oxide/src/lib.rs | 2 +- crates/oxide/tests/scanner.rs | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/crates/oxide/src/lib.rs b/crates/oxide/src/lib.rs index 4631cabc843c..b05a6a78d011 100644 --- a/crates/oxide/src/lib.rs +++ b/crates/oxide/src/lib.rs @@ -447,7 +447,7 @@ fn read_changed_content(c: ChangedContent) -> Option> { }; match extension { - Some("svelte") => Some(content.replace(" class:", " ")), + Some("svelte") => Some(content.replace(" class:", " ").replace("\tclass:", " ")), _ => Some(content), } } diff --git a/crates/oxide/tests/scanner.rs b/crates/oxide/tests/scanner.rs index cdec9a44dd26..7692968b5f4c 100644 --- a/crates/oxide/tests/scanner.rs +++ b/crates/oxide/tests/scanner.rs @@ -325,12 +325,22 @@ mod scanner { ("foo.html", "lg:font-bold"), // A svelte file with `class:foo="bar"` syntax ("index.svelte", "
"), + ("index2.svelte", ""), + ("index3.svelte", ""), ]) .1; assert_eq!( candidates, - vec!["condition", "div", "font-bold", "md:flex", "px-4"] + vec![ + "condition", + "div", + "font-bold", + "md:flex", + "px-4", + "px-5", + "px-6" + ] ); } From 308f9d8e6e07fa0c153886c05599170c63b11946 Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Tue, 28 Jan 2025 15:25:47 +0100 Subject: [PATCH 2/5] Fix Angular class hand shortcut --- crates/oxide/src/lib.rs | 2 ++ crates/oxide/tests/scanner.rs | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/crates/oxide/src/lib.rs b/crates/oxide/src/lib.rs index b05a6a78d011..e8d42398748f 100644 --- a/crates/oxide/src/lib.rs +++ b/crates/oxide/src/lib.rs @@ -447,6 +447,8 @@ fn read_changed_content(c: ChangedContent) -> Option> { }; match extension { + // Angular class shorthand + Some("html") => Some(content.replace("[class.", "[")), Some("svelte") => Some(content.replace(" class:", " ").replace("\tclass:", " ")), _ => Some(content), } diff --git a/crates/oxide/tests/scanner.rs b/crates/oxide/tests/scanner.rs index 7692968b5f4c..926b7aaea8b7 100644 --- a/crates/oxide/tests/scanner.rs +++ b/crates/oxide/tests/scanner.rs @@ -323,6 +323,11 @@ mod scanner { ("foo.jpg", "xl:font-bold"), // A file that is ignored ("foo.html", "lg:font-bold"), + // An Angular file using the class shorthand syntax + ( + "index.angular.html", + "
", + ), // A svelte file with `class:foo="bar"` syntax ("index.svelte", "
"), ("index2.svelte", ""), @@ -333,13 +338,15 @@ mod scanner { assert_eq!( candidates, vec![ + "bool", "condition", "div", "font-bold", "md:flex", "px-4", "px-5", - "px-6" + "px-6", + "underline" ] ); } From 54680ec11807e35bcd019529b4832171ea11efe7 Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Tue, 28 Jan 2025 16:10:18 +0100 Subject: [PATCH 3/5] Validate parens in arbitrary candidates --- crates/oxide/src/parser.rs | 73 ++++++++++++++++++++++++++++++++++---- 1 file changed, 66 insertions(+), 7 deletions(-) diff --git a/crates/oxide/src/parser.rs b/crates/oxide/src/parser.rs index d6d2df1433c3..12791ae780ac 100644 --- a/crates/oxide/src/parser.rs +++ b/crates/oxide/src/parser.rs @@ -334,6 +334,26 @@ impl<'a> Extractor<'a> { return ValidationResult::Restart; } + // Only allow parentheses for the shorthand arbitrary custom properties syntax + if let Some(index) = utility.find(b"(") { + let mut skip_parens_check = false; + let start_brace_index = utility.find(b"["); + let end_brace_index = utility.find(b"]"); + + match (start_brace_index, end_brace_index) { + (Some(start_brace_index), Some(end_brace_index)) => { + if start_brace_index < index && end_brace_index > index { + skip_parens_check = true; + } + } + _ => {} + } + + if !skip_parens_check && !utility[index + 1..].starts_with(b"--") { + return ValidationResult::Restart; + } + } + // Pluck out the part that we are interested in. let utility = &utility[offset..(utility.len() - offset_end)]; @@ -911,9 +931,6 @@ impl<'a> Extractor<'a> { fn generate_slices(&mut self, candidate: &'a [u8]) -> ParseAction<'a> { match self.without_surrounding() { Bracketing::None => ParseAction::SingleCandidate(candidate), - Bracketing::Included(sliceable) if sliceable == candidate => { - ParseAction::SingleCandidate(candidate) - } Bracketing::Included(sliceable) | Bracketing::Wrapped(sliceable) => { if candidate == sliceable { ParseAction::SingleCandidate(candidate) @@ -1117,7 +1134,7 @@ mod test { assert_eq!(candidates, vec!["something"]); let candidates = run(" [feature(slice_as_chunks)]", false); - assert_eq!(candidates, vec!["feature(slice_as_chunks)"]); + assert_eq!(candidates, vec!["feature", "slice_as_chunks"]); let candidates = run("![feature(slice_as_chunks)]", false); assert!(candidates.is_empty()); @@ -1213,9 +1230,8 @@ mod test { #[test] fn ignores_arbitrary_property_ish_things() { - // FIXME: () are only valid in an arbitrary let candidates = run(" [feature(slice_as_chunks)]", false); - assert_eq!(candidates, vec!["feature(slice_as_chunks)",]); + assert_eq!(candidates, vec!["feature", "slice_as_chunks",]); } #[test] @@ -1637,7 +1653,6 @@ mod test { #[test] fn arbitrary_properties_are_not_picked_up_after_an_escape() { - _please_trace(); let candidates = run( r#" @@ -1648,4 +1663,48 @@ mod test { assert_eq!(candidates, vec!["!code", "a"]); } + + #[test] + fn test_find_candidates_in_braces_inside_brackets() { + let candidates = run( + r#" + const classes = [wrapper("bg-red-500")] + "#, + false, + ); + + assert_eq!( + candidates, + vec!["const", "classes", "wrapper", "bg-red-500"] + ); + } + + #[test] + fn test_is_valid_candidate_string() { + assert_eq!( + Extractor::is_valid_candidate_string(b"foo"), + ValidationResult::Valid + ); + assert_eq!( + Extractor::is_valid_candidate_string(b"foo-(--color-red-500)"), + ValidationResult::Valid + ); + assert_eq!( + Extractor::is_valid_candidate_string(b"bg-[url(foo)]"), + ValidationResult::Valid + ); + assert_eq!( + Extractor::is_valid_candidate_string(b"group-foo/(--bar)"), + ValidationResult::Valid + ); + + assert_eq!( + Extractor::is_valid_candidate_string(b"foo(\"bg-red-500\")"), + ValidationResult::Restart + ); + assert_eq!( + Extractor::is_valid_candidate_string(b"foo-("), + ValidationResult::Restart + ); + } } From 491fe9be77e6eeda93a4ad82751b77d39b59717b Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Tue, 28 Jan 2025 16:17:07 +0100 Subject: [PATCH 4/5] Add change log --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2c5767ce59b..a1bf6a94e510 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Ensure CSS variable shorthand uses valid CSS variables ([#15738](https://github.com/tailwindlabs/tailwindcss/pull/15738)) - Ensure font-size utilities with `none` modifier have a line-height set e.g.: `text-sm/none` ([#15921](https://github.com/tailwindlabs/tailwindcss/pull/15921)) - Ensure font-size utilities with unknown modifier don't generate CSS ([#15921](https://github.com/tailwindlabs/tailwindcss/pull/15921)) +- Find utilities when using the Svelte class shorthand syntax across multiple lines ([#15974](https://github.com/tailwindlabs/tailwindcss/pull/15974)) +- Find utilities when using the Angular class shorthand syntax ([#15974](https://github.com/tailwindlabs/tailwindcss/pull/15974)) +- Find utilities when using functions inside arrays ([#15974](https://github.com/tailwindlabs/tailwindcss/pull/15974)) - _Upgrade_: Ensure JavaScript config files on different drives are correctly migrated ([#15927](https://github.com/tailwindlabs/tailwindcss/pull/15927)) ## [4.0.0] - 2025-01-21 From fba346e19924012492cd12970d4f5aa0beaabc4e Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Tue, 28 Jan 2025 11:14:19 -0500 Subject: [PATCH 5/5] Check for newline too --- crates/oxide/src/lib.rs | 7 ++++++- crates/oxide/tests/scanner.rs | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/oxide/src/lib.rs b/crates/oxide/src/lib.rs index e8d42398748f..557e87ef5f78 100644 --- a/crates/oxide/src/lib.rs +++ b/crates/oxide/src/lib.rs @@ -449,7 +449,12 @@ fn read_changed_content(c: ChangedContent) -> Option> { match extension { // Angular class shorthand Some("html") => Some(content.replace("[class.", "[")), - Some("svelte") => Some(content.replace(" class:", " ").replace("\tclass:", " ")), + Some("svelte") => Some( + content + .replace(" class:", " ") + .replace("\tclass:", " ") + .replace("\nclass:", " "), + ), _ => Some(content), } } diff --git a/crates/oxide/tests/scanner.rs b/crates/oxide/tests/scanner.rs index 926b7aaea8b7..4d121479948d 100644 --- a/crates/oxide/tests/scanner.rs +++ b/crates/oxide/tests/scanner.rs @@ -332,6 +332,7 @@ mod scanner { ("index.svelte", "
"), ("index2.svelte", ""), ("index3.svelte", ""), + ("index4.svelte", ""), ]) .1; @@ -346,6 +347,7 @@ mod scanner { "px-4", "px-5", "px-6", + "px-7", "underline" ] );