mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Add Haml pre processor (#17051)
This PR ensures we extract candidates from Haml files. Fixes: #17050
This commit is contained in:
parent
2f28e5fbcb
commit
7005ad7e00
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
123
crates/oxide/src/extractor/pre_processors/haml.rs
Normal file
123
crates/oxide/src/extractor/pre_processors/haml.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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::*;
|
||||
|
||||
@ -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),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user