Handle template toolkit %]…[% syntax (#20269)
This PR handles template toolkit syntax as a pre-processor step such that `%]` and `[%` are seen as valid boundary characters. This is handled for the `.tt`, `.tt2` and `.tx` file extensions. It's not handled if this syntax is used in `.html` files because then everybody pays a pre processor cost even if you don't need this syntax in most cases. This now ensures that a `template.tx` like this: ```html <div class="[% IF $is_open %]bg-white/40[% ELSE %]bg-white/10[% END %]"></div> <!-- ^^^^^^^^^^^ ^^^^^^^^^^^ --> ``` Extracts the classes in between those conditions correctly. This also fixes a small issue related to Maud, a template engine for Rust where conditionals like `p.text-black[condition]` caused the `text-black` class not to be extracted. This is fixed as part of this PR because it was commented on the linked issue. Fixes: #20233 ## Test plan 1. Added a new extractor 2. Added regression tests 3. All existing tests pass
This commit is contained in:
parent
0fee7b55f9
commit
d5ca0aeac9
6 changed files with 91 additions and 0 deletions
|
|
@ -20,6 +20,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Ensure `@tailwindcss/vite` doesn't crash during HMR when scanned files or directories are deleted ([#20259](https://github.com/tailwindlabs/tailwindcss/pull/20259))
|
||||
- Ensure `text-[--spacing(…)]` generates `font-size` instead of `color` ([#20260](https://github.com/tailwindlabs/tailwindcss/pull/20260))
|
||||
- Prevent `@source` patterns from scanning unrelated sibling files and folders ([#20263](https://github.com/tailwindlabs/tailwindcss/pull/20263))
|
||||
- Extract class candidates adjacent to Template Toolkit delimiters like `%]…[%` in `.tt`, `.tt2`, and `.tx` files ([#20269](https://github.com/tailwindlabs/tailwindcss/pull/20269))
|
||||
- Extract class candidates from conditional Maud syntax like `p.text-black[condition]` ([#20269](https://github.com/tailwindlabs/tailwindcss/pull/20269))
|
||||
|
||||
## [4.3.1] - 2026-06-12
|
||||
|
||||
|
|
|
|||
|
|
@ -908,6 +908,18 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
// https://github.com/tailwindlabs/tailwindcss/issues/20233
|
||||
#[test]
|
||||
fn test_template_toolkit_syntax() {
|
||||
assert_extract_candidates_contains(
|
||||
&pre_process_input(
|
||||
r#"<div class="[% IF $is_open %]bg-white/40[% ELSE %]bg-white/10[% END %]"></div>"#,
|
||||
"tx",
|
||||
),
|
||||
vec!["bg-white/40", "bg-white/10"],
|
||||
);
|
||||
}
|
||||
|
||||
// https://github.com/tailwindlabs/tailwindcss/issues/17050
|
||||
#[test]
|
||||
fn test_haml_syntax() {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ pub mod ruby;
|
|||
pub mod rust;
|
||||
pub mod slim;
|
||||
pub mod svelte;
|
||||
pub mod template_toolkit;
|
||||
pub mod twig;
|
||||
pub mod vue;
|
||||
|
||||
|
|
@ -25,5 +26,6 @@ pub use ruby::*;
|
|||
pub use rust::*;
|
||||
pub use slim::*;
|
||||
pub use svelte::*;
|
||||
pub use template_toolkit::*;
|
||||
pub use twig::*;
|
||||
pub use vue::*;
|
||||
|
|
|
|||
|
|
@ -104,6 +104,12 @@ impl Rust {
|
|||
|
||||
b'[' => {
|
||||
bracket_stack.push(cursor.curr());
|
||||
|
||||
// Handle `p.flex[condition]`. If there is a `-` before it, it will likely be an
|
||||
// arbitrary value e.g. `text-[red]`
|
||||
if !matches!(cursor.prev(), b'-') {
|
||||
result[cursor.pos] = b' ';
|
||||
}
|
||||
}
|
||||
|
||||
b']' if !bracket_stack.is_empty() => {
|
||||
|
|
@ -213,4 +219,20 @@ mod tests {
|
|||
let input = r#"html! { \x.px-4.text-black { } }"#;
|
||||
Rust::test(input, r#"html! { \x px-4 text-black { } }"#);
|
||||
}
|
||||
|
||||
// https://github.com/tailwindlabs/tailwindcss/issues/20233
|
||||
#[test]
|
||||
fn test_maud_template_extraction_with_conditional_classes() {
|
||||
let input = r#"
|
||||
use maud::{html, Markup};
|
||||
|
||||
pub fn main() -> Markup {
|
||||
html! {
|
||||
p.text-black[cuteness > 50] { "Squee!" }
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
Rust::test_extract_contains(input, vec!["text-black"]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
use crate::cursor;
|
||||
use crate::extractor::pre_processors::pre_processor::PreProcessor;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TemplateToolkit;
|
||||
|
||||
impl PreProcessor for TemplateToolkit {
|
||||
fn process(&self, content: &[u8]) -> Vec<u8> {
|
||||
let len = content.len();
|
||||
let mut result = content.to_vec();
|
||||
let mut cursor = cursor::Cursor::new(content);
|
||||
|
||||
while cursor.pos < len {
|
||||
match (cursor.curr(), cursor.next()) {
|
||||
(b'[', b'%') => result[cursor.pos] = b' ',
|
||||
(b'%', b']') => result[cursor.pos + 1] = b' ',
|
||||
_ => {}
|
||||
}
|
||||
|
||||
cursor.advance();
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::TemplateToolkit;
|
||||
use crate::extractor::pre_processors::pre_processor::PreProcessor;
|
||||
|
||||
#[test]
|
||||
fn test_template_toolkit_pre_processor() {
|
||||
for (input, expected) in [
|
||||
(
|
||||
"[% IF $is_open %]bg-white/40[% ELSE %]bg-white/10[% END %]",
|
||||
" % IF $is_open % bg-white/40 % ELSE % bg-white/10 % END % ",
|
||||
),
|
||||
("[% WRAPPER %]flex[% END %]", " % WRAPPER % flex % END % "),
|
||||
] {
|
||||
TemplateToolkit::test(input, expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extraction_between_template_tags_works() {
|
||||
TemplateToolkit::test_extract_contains(
|
||||
r#"<div class="[% IF $is_open %]bg-white/40[% ELSE %]bg-white/10[% END %]"></div>"#,
|
||||
vec!["bg-white/40", "bg-white/10"],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -480,6 +480,7 @@ pub fn pre_process_input(content: Vec<u8>, extension: &str) -> Vec<u8> {
|
|||
"slim" | "slang" => Slim.process(&content),
|
||||
"svelte" => Svelte.process(&content),
|
||||
"rs" => Rust.process(&content),
|
||||
"tt" | "tt2" | "tx" => TemplateToolkit.process(&content),
|
||||
"twig" => Twig.process(&content),
|
||||
"vue" => Vue.process(&content),
|
||||
_ => content,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue