diff --git a/CHANGELOG.md b/CHANGELOG.md index e44a8358a064..07b1eeb9038c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Ensure validation of `source(…)` happens relative to the file it is in ([#19274](https://github.com/tailwindlabs/tailwindcss/pull/19274)) - Include filename and line numbers in CSS parse errors ([#19282](https://github.com/tailwindlabs/tailwindcss/pull/19282)) +- Skip comments in Ruby files when checking for class names ([#19243](https://github.com/tailwindlabs/tailwindcss/pull/19243)) +- Skip over arbitrary property utilities with a top-level `!` in the value ([#19243](https://github.com/tailwindlabs/tailwindcss/pull/19243)) ### Added diff --git a/crates/oxide/src/extractor/arbitrary_property_machine.rs b/crates/oxide/src/extractor/arbitrary_property_machine.rs index 8ed3f1763091..8fd13e39405f 100644 --- a/crates/oxide/src/extractor/arbitrary_property_machine.rs +++ b/crates/oxide/src/extractor/arbitrary_property_machine.rs @@ -231,6 +231,12 @@ impl Machine for ArbitraryPropertyMachine { return self.restart() } + // An `!` at the top-level is invalid. We don't allow things to end with + // `!important` either as we have dedicated syntax for this. + Class::Exclamation if self.bracket_stack.is_empty() => { + return self.restart(); + } + // Everything else is valid _ => cursor.advance(), }; @@ -293,6 +299,9 @@ enum Class { #[bytes(b'/')] Slash, + #[bytes(b'!')] + Exclamation, + #[bytes(b' ', b'\t', b'\n', b'\r', b'\x0C')] Whitespace, @@ -369,6 +378,9 @@ mod tests { "[background:url(https://example.com?q={[{[([{[[2]]}])]}]})]", vec!["[background:url(https://example.com?q={[{[([{[[2]]}])]}]})]"], ), + // A property containing `!` at the top-level is invalid + ("[color:red!]", vec![]), + ("[color:red!important]", vec![]), ] { for wrapper in [ // No wrapper diff --git a/crates/oxide/src/extractor/pre_processors/ruby.rs b/crates/oxide/src/extractor/pre_processors/ruby.rs index 6bb1d49238a3..c94bd945f56e 100644 --- a/crates/oxide/src/extractor/pre_processors/ruby.rs +++ b/crates/oxide/src/extractor/pre_processors/ruby.rs @@ -77,8 +77,74 @@ impl PreProcessor for Ruby { // Ruby extraction while cursor.pos < len { - // Looking for `%w` or `%W` - if cursor.curr != b'%' && !matches!(cursor.next, b'w' | b'W') { + match cursor.curr { + b'"' => { + cursor.advance(); + + while cursor.pos < len { + match cursor.curr { + // Escaped character, skip ahead to the next character + b'\\' => cursor.advance_twice(), + + // End of the string + b'"' => break, + + // Everything else is valid + _ => cursor.advance(), + }; + } + + cursor.advance(); + continue; + } + + b'\'' => { + cursor.advance(); + + while cursor.pos < len { + match cursor.curr { + // Escaped character, skip ahead to the next character + b'\\' => cursor.advance_twice(), + + // End of the string + b'\'' => break, + + // Everything else is valid + _ => cursor.advance(), + }; + } + + cursor.advance(); + continue; + } + + // Replace comments in Ruby files + b'#' => { + result[cursor.pos] = b' '; + cursor.advance(); + + while cursor.pos < len { + match cursor.curr { + // End of the comment + b'\n' => break, + + // Everything else is part of the comment and replaced + _ => { + result[cursor.pos] = b' '; + cursor.advance(); + } + }; + } + + cursor.advance(); + continue; + } + + _ => {} + } + + // Looking for `%w`, `%W`, or `%p` + if cursor.curr != b'%' || !matches!(cursor.next, b'w' | b'W' | b'p') { cursor.advance(); continue; } @@ -90,6 +156,8 @@ impl PreProcessor for Ruby { b'[' => b']', b'(' => b')', b'{' => b'}', + b'#' => b'#', + b' ' => b'\n', _ => { cursor.advance(); continue; @@ -131,7 +199,10 @@ impl PreProcessor for Ruby { // End of the pattern, replace the boundary character with a space _ if cursor.curr == boundary => { - result[cursor.pos] = b' '; + if boundary != b'\n' { + result[cursor.pos] = b' '; + } + break; } @@ -173,12 +244,51 @@ mod tests { "%w(flex data-[state=pending]:bg-(--my-color) flex-col)", "%w flex data-[state=pending]:bg-(--my-color) flex-col ", ), + + // %w …\n + ("%w flex px-2.5\n", "%w flex px-2.5\n"), + // Use backslash to embed spaces in the strings. (r#"%w[foo\ bar baz\ bat]"#, r#"%w foo bar baz bat "#), (r#"%W[foo\ bar baz\ bat]"#, r#"%W foo bar baz bat "#), + // The nested delimiters evaluated to a flat array of strings // (not nested array). (r#"%w[foo[bar baz]qux]"#, r#"%w foo[bar baz]qux "#), + + ( + "# test\n# test\n# {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!]\n%w[flex px-2.5]", + " \n \n \n%w flex px-2.5 " + ), + + (r#""foo # bar""#, r#""foo # bar""#), + (r#"'foo # bar'"#, r#"'foo # bar'"#), + ( + r#"def call = tag.span "Foo", class: %w[rounded-full h-0.75 w-0.75]"#, + r#"def call = tag.span "Foo", class: %w rounded-full h-0.75 w-0.75 "# + ), + + (r#"%w[foo ' bar]"#, r#"%w foo ' bar "#), + (r#"%w[foo " bar]"#, r#"%w foo " bar "#), + (r#"%W[foo ' bar]"#, r#"%W foo ' bar "#), + (r#"%W[foo " bar]"#, r#"%W foo " bar "#), + + (r#"%p foo ' bar "#, r#"%p foo ' bar "#), + (r#"%p foo " bar "#, r#"%p foo " bar "#), + + ( + "%p has a ' quote\n# this should be removed\n%p has a ' quote", + "%p has a ' quote\n \n%p has a ' quote" + ), + ( + "%p has a \" quote\n# this should be removed\n%p has a \" quote", + "%p has a \" quote\n \n%p has a \" quote" + ), + + ( + "%w#this text is kept# # this text is not", + "%w this text is kept ", + ), ] { Ruby::test(input, expected); } @@ -211,6 +321,16 @@ mod tests { "%w(flex data-[state=pending]:bg-(--my-color) flex-col)", vec!["flex", "data-[state=pending]:bg-(--my-color)", "flex-col"], ), + + ( + "# test\n# test\n# {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!]\n%w[flex px-2.5]", + vec!["flex", "px-2.5"], + ), + + (r#""foo # bar""#, vec!["foo", "bar"]), + (r#"'foo # bar'"#, vec!["foo", "bar"]), + + (r#"%w[foo ' bar]"#, vec!["foo", "bar"]), ] { Ruby::test_extract_contains(input, expected); } diff --git a/crates/oxide/src/extractor/pre_processors/test-fixtures/haml/dst-17051.haml b/crates/oxide/src/extractor/pre_processors/test-fixtures/haml/dst-17051.haml index b233f3b8b2a4..4a3543379420 100644 --- a/crates/oxide/src/extractor/pre_processors/test-fixtures/haml/dst-17051.haml +++ b/crates/oxide/src/extractor/pre_processors/test-fixtures/haml/dst-17051.haml @@ -7,7 +7,6 @@ .relative ^^^^^^^^ - # Blurred background star - ^^^^^^^^^^ ^^^^ .absolute.left-0.z-0{ class: "-top-[400px] -right-[400px]" } ^^^^^^^^ ^^^^^^ ^^^ ^^^^^ ^^^^^^^^^^^^ ^^^^^^^^^^^^^^ .flex.justify-end.blur-3xl @@ -196,7 +195,6 @@ ^^^^^^^^ ^^^^ ^^^ ^^^^ ^^ :escaped - # app/components/character_component.html.haml - ^^^^ = part(:component) do ^^ = part(:head)