Add Haml pre processor (#17051)

This PR ensures we extract candidates from Haml files.

Fixes: #17050
This commit is contained in:
Robin Malfait 2025-03-07 22:32:15 +01:00 committed by GitHub
parent 2f28e5fbcb
commit 7005ad7e00
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 181 additions and 1 deletions

View File

@ -17,6 +17,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- _Experimental_: Add `user-valid` and `user-invalid` variants ([#12370](https://github.com/tailwindlabs/tailwindcss/pull/12370))
- _Experimental_: Add `wrap-anywhere`, `wrap-break-word`, and `wrap-normal` utilities ([#12128](https://github.com/tailwindlabs/tailwindcss/pull/12128))
### Fixed
- Fix `haml` pre-processing ([#17051](https://github.com/tailwindlabs/tailwindcss/pull/17051))
## [4.0.12] - 2025-03-07
### Fixed
@ -26,7 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Ensure utilities are sorted based on their actual property order ([#16995](https://github.com/tailwindlabs/tailwindcss/pull/16995))
- Ensure strings in Pug and Slim templates are handled correctly ([#17000](https://github.com/tailwindlabs/tailwindcss/pull/17000))
- Ensure classes between `}` and `{` are properly extracted ([#17001](https://github.com/tailwindlabs/tailwindcss/pull/17001))
- Add `razor`/`cshtml` pre processing ([#17027](https://github.com/tailwindlabs/tailwindcss/pull/17027))
- Fix `razor`/`cshtml` pre-processing ([#17027](https://github.com/tailwindlabs/tailwindcss/pull/17027))
- Ensure extracting candidates from JS embedded in a PHP string works as expected ([#17031](https://github.com/tailwindlabs/tailwindcss/pull/17031))
## [4.0.11] - 2025-03-06

View File

@ -873,6 +873,55 @@ mod tests {
);
}
// https://github.com/tailwindlabs/tailwindcss/issues/17050
#[test]
fn test_haml_syntax() {
for (input, expected) in [
// Element with classes
(
"%body.flex.flex-col.items-center.justify-center",
vec!["flex", "flex-col", "items-center", "justify-center"],
),
// Plain element
(
".text-slate-500.xl:text-gray-500",
vec!["text-slate-500", "xl:text-gray-500"],
),
// Element with hash attributes
(
".text-black.xl:text-red-500{ data: { tailwind: 'css' } }",
vec!["text-black", "xl:text-red-500"],
),
// Element with a boolean attribute
(
".text-green-500.xl:text-blue-500(data-sidebar)",
vec!["text-green-500", "xl:text-blue-500"],
),
// Element with interpreted content
(
".text-yellow-500.xl:text-purple-500= 'Element with interpreted content'",
vec!["text-yellow-500", "xl:text-purple-500"],
),
// Element with a hash at the end and an extra class.
(
".text-orange-500.xl:text-pink-500{ class: 'bg-slate-100' }",
vec!["text-orange-500", "xl:text-pink-500", "bg-slate-100"],
),
// Object reference
(
".text-teal-500.xl:text-indigo-500[@user, :greeting]",
vec!["text-teal-500", "xl:text-indigo-500"],
),
// Element with an ID
(
".text-lime-500.xl:text-emerald-500#root",
vec!["text-lime-500", "xl:text-emerald-500"],
),
] {
assert_extract_candidates_contains(&pre_process_input(input, "haml"), expected);
}
}
// https://github.com/tailwindlabs/tailwindcss/issues/16982
#[test]
fn test_arbitrary_container_queries_syntax() {
@ -888,6 +937,7 @@ mod tests {
);
}
// https://github.com/tailwindlabs/tailwindcss/issues/17023
#[test]
fn test_js_embedded_in_php_syntax() {
// Escaped single quotes

View File

@ -0,0 +1,123 @@
use crate::cursor;
use crate::extractor::bracket_stack::BracketStack;
use crate::extractor::pre_processors::pre_processor::PreProcessor;
#[derive(Debug, Default)]
pub struct Haml;
impl PreProcessor for Haml {
fn process(&self, content: &[u8]) -> Vec<u8> {
let len = content.len();
let mut result = content.to_vec();
let mut cursor = cursor::Cursor::new(content);
let mut bracket_stack = BracketStack::default();
while cursor.pos < len {
match cursor.curr {
// Consume strings as-is
b'\'' | b'"' => {
let len = cursor.input.len();
let end_char = cursor.curr;
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'\'' | b'"' if cursor.curr == end_char => break,
// Everything else is valid
_ => cursor.advance(),
};
}
}
// Replace following characters with spaces if they are not inside of brackets
b'.' | b'#' | b'=' if bracket_stack.is_empty() => {
result[cursor.pos] = b' ';
}
b'(' | b'[' | b'{' => {
// Replace first bracket with a space
if bracket_stack.is_empty() {
result[cursor.pos] = b' ';
}
bracket_stack.push(cursor.curr);
}
b')' | b']' | b'}' => {
bracket_stack.pop(cursor.curr);
// Replace closing bracket with a space
if bracket_stack.is_empty() {
result[cursor.pos] = b' ';
}
}
// Consume everything else
_ => {}
};
cursor.advance();
}
result
}
}
#[cfg(test)]
mod tests {
use super::Haml;
use crate::extractor::pre_processors::pre_processor::PreProcessor;
#[test]
fn test_haml_pre_processor() {
for (input, expected) in [
// Element with classes
(
"%body.flex.flex-col.items-center.justify-center",
"%body flex flex-col items-center justify-center",
),
// Plain element
(
".text-slate-500.xl:text-gray-500",
" text-slate-500 xl:text-gray-500",
),
// Element with hash attributes
(
".text-black.xl:text-red-500{ data: { tailwind: 'css' } }",
" text-black xl:text-red-500 data: { tailwind: 'css' } ",
),
// Element with a boolean attribute
(
".text-green-500.xl:text-blue-500(data-sidebar)",
" text-green-500 xl:text-blue-500 data-sidebar ",
),
// Element with interpreted content
(
".text-yellow-500.xl:text-purple-500= 'Element with interpreted content'",
" text-yellow-500 xl:text-purple-500 'Element with interpreted content'",
),
// Element with a hash at the end and an extra class.
(
".text-orange-500.xl:text-pink-500{ class: 'bg-slate-100' }",
" text-orange-500 xl:text-pink-500 class: 'bg-slate-100' ",
),
// Object reference
(
".text-teal-500.xl:text-indigo-500[@user, :greeting]",
" text-teal-500 xl:text-indigo-500 @user, :greeting ",
),
// Element with an ID
(
".text-lime-500.xl:text-emerald-500#root",
" text-lime-500 xl:text-emerald-500 root",
),
] {
Haml::test(input, expected);
}
}
}

View File

@ -1,3 +1,4 @@
pub mod haml;
pub mod pre_processor;
pub mod pug;
pub mod razor;
@ -5,6 +6,7 @@ pub mod ruby;
pub mod slim;
pub mod svelte;
pub use haml::*;
pub use pre_processor::*;
pub use pug::*;
pub use razor::*;

View File

@ -469,6 +469,7 @@ pub fn pre_process_input(content: &[u8], extension: &str) -> Vec<u8> {
match extension {
"cshtml" | "razor" => Razor.process(content),
"haml" => Haml.process(content),
"pug" => Pug.process(content),
"rb" | "erb" => Ruby.process(content),
"slim" => Slim.process(content),