Fix Slim attributes (#16985)
This PR fixes an issue in Slim templates where the start of attributes causes some candidates to be missing. ```slim .text-xl.text-red-600[ data-foo="bar" ] | This line should be red ``` Because of the `[` attached to the `text-red-600`, the `text-red-600` was not extracted because `[` is not a valid boundary character. To solve this, we copied the Pug pre processor and created a dedicated Slim pre processor. Next, we ensure that we replace `[` with ` ` in this scenario (by also making sure that we don't replace `[` where it's important). Additionally, we noticed that `.` was also replaced inside of arbitrary values such as URLs. This has been fixed for both Pug and Slim. Fixes: #16975 # Test plan 1. Added failing tests 2. Existing tests still pass
This commit is contained in:
parent
617b7abb81
commit
af132fbedb
5 changed files with 131 additions and 2 deletions
|
|
@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Ensure classes containing `--` are extracted correctly ([#16972](https://github.com/tailwindlabs/tailwindcss/pull/16972))
|
||||
- Ensure classes containing numbers followed by dash or underscore are extracted correctly ([#16980](https://github.com/tailwindlabs/tailwindcss/pull/16980))
|
||||
- Ensure arbitrary container queries are extracted correctly ([#16984](https://github.com/tailwindlabs/tailwindcss/pull/16984))
|
||||
- Ensure classes ending in `[` are extracted in Slim templating language ([#16985](https://github.com/tailwindlabs/tailwindcss/pull/16985))
|
||||
|
||||
## [4.0.10] - 2025-03-05
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
pub mod pre_processor;
|
||||
pub mod pug;
|
||||
pub mod ruby;
|
||||
pub mod slim;
|
||||
pub mod svelte;
|
||||
|
||||
pub use pre_processor::*;
|
||||
pub use pug::*;
|
||||
pub use ruby::*;
|
||||
pub use slim::*;
|
||||
pub use svelte::*;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use crate::cursor;
|
||||
use crate::extractor::bracket_stack::BracketStack;
|
||||
use crate::extractor::machine::Machine;
|
||||
use crate::extractor::pre_processors::pre_processor::PreProcessor;
|
||||
use crate::StringMachine;
|
||||
|
|
@ -12,6 +13,7 @@ impl PreProcessor for Pug {
|
|||
let mut result = content.to_vec();
|
||||
let mut cursor = cursor::Cursor::new(content);
|
||||
let mut string_machine = StringMachine;
|
||||
let mut bracket_stack = BracketStack::default();
|
||||
|
||||
while cursor.pos < len {
|
||||
match cursor.curr {
|
||||
|
|
@ -21,10 +23,18 @@ impl PreProcessor for Pug {
|
|||
}
|
||||
|
||||
// Replace dots with spaces
|
||||
b'.' => {
|
||||
b'.' if bracket_stack.is_empty() => {
|
||||
result[cursor.pos] = b' ';
|
||||
}
|
||||
|
||||
b'(' | b'[' | b'{' => {
|
||||
bracket_stack.push(cursor.curr);
|
||||
}
|
||||
|
||||
b')' | b']' | b'}' if !bracket_stack.is_empty() => {
|
||||
bracket_stack.pop(cursor.curr);
|
||||
}
|
||||
|
||||
// Consume everything else
|
||||
_ => {}
|
||||
};
|
||||
|
|
@ -49,6 +59,11 @@ mod tests {
|
|||
(".flex.bg-red-500", " flex bg-red-500"),
|
||||
// Keep dots in strings
|
||||
(r#"div(class="px-2.5")"#, r#"div(class="px-2.5")"#),
|
||||
// Nested brackets
|
||||
(
|
||||
"bg-[url(https://example.com/?q=[1,2])]",
|
||||
"bg-[url(https://example.com/?q=[1,2])]",
|
||||
),
|
||||
] {
|
||||
Pug::test(input, expected);
|
||||
}
|
||||
|
|
|
|||
110
crates/oxide/src/extractor/pre_processors/slim.rs
Normal file
110
crates/oxide/src/extractor/pre_processors/slim.rs
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
use crate::cursor;
|
||||
use crate::extractor::bracket_stack::BracketStack;
|
||||
use crate::extractor::machine::Machine;
|
||||
use crate::extractor::pre_processors::pre_processor::PreProcessor;
|
||||
use crate::StringMachine;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Slim;
|
||||
|
||||
impl PreProcessor for Slim {
|
||||
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 string_machine = StringMachine;
|
||||
let mut bracket_stack = BracketStack::default();
|
||||
|
||||
while cursor.pos < len {
|
||||
match cursor.curr {
|
||||
// Consume strings as-is
|
||||
b'\'' | b'"' => {
|
||||
string_machine.next(&mut cursor);
|
||||
}
|
||||
|
||||
// Replace dots with spaces
|
||||
b'.' if bracket_stack.is_empty() => {
|
||||
result[cursor.pos] = b' ';
|
||||
}
|
||||
|
||||
// Any `[` preceded by an alphanumeric value will not be part of a candidate.
|
||||
//
|
||||
// E.g.:
|
||||
//
|
||||
// ```
|
||||
// .text-xl.text-red-600[
|
||||
// ^ not part of the `text-red-600` candidate
|
||||
// data-foo="bar"
|
||||
// ]
|
||||
// | This line should be red
|
||||
// ```
|
||||
//
|
||||
// We know that `-[` is valid for an arbitrary value and that `:[` is valid as a
|
||||
// variant. However `[color:red]` is also valid, in this case `[` will be preceded
|
||||
// by nothing or a boundary character.
|
||||
// Instead of listing all boundary characters, let's list the characters we know
|
||||
// will be invalid instead.
|
||||
b'[' if bracket_stack.is_empty()
|
||||
&& matches!(cursor.prev, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9') =>
|
||||
{
|
||||
result[cursor.pos] = b' ';
|
||||
bracket_stack.push(cursor.curr);
|
||||
}
|
||||
|
||||
b'(' | b'[' | b'{' => {
|
||||
bracket_stack.push(cursor.curr);
|
||||
}
|
||||
|
||||
b')' | b']' | b'}' if !bracket_stack.is_empty() => {
|
||||
bracket_stack.pop(cursor.curr);
|
||||
}
|
||||
|
||||
// Consume everything else
|
||||
_ => {}
|
||||
};
|
||||
|
||||
cursor.advance();
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Slim;
|
||||
use crate::extractor::pre_processors::pre_processor::PreProcessor;
|
||||
|
||||
#[test]
|
||||
fn test_slim_pre_processor() {
|
||||
for (input, expected) in [
|
||||
// Convert dots to spaces
|
||||
("div.flex.bg-red-500", "div flex bg-red-500"),
|
||||
(".flex.bg-red-500", " flex bg-red-500"),
|
||||
// Keep dots in strings
|
||||
(r#"div(class="px-2.5")"#, r#"div(class="px-2.5")"#),
|
||||
// Replace top-level `(a-z0-9)[` with `$1 `. E.g.: `.flex[x]` -> `.flex x]`
|
||||
(".text-xl.text-red-600[", " text-xl text-red-600 "),
|
||||
// But keep important brackets:
|
||||
(".text-[#0088cc]", " text-[#0088cc]"),
|
||||
// Arbitrary value and arbitrary modifier
|
||||
(
|
||||
".text-[#0088cc].bg-[#0088cc]/[20%]",
|
||||
" text-[#0088cc] bg-[#0088cc]/[20%]",
|
||||
),
|
||||
// Start of arbitrary property
|
||||
("[color:red]", "[color:red]"),
|
||||
// Arbitrary container query
|
||||
("@[320px]:flex", "@[320px]:flex"),
|
||||
// Nested brackets
|
||||
(
|
||||
"bg-[url(https://example.com/?q=[1,2])]",
|
||||
"bg-[url(https://example.com/?q=[1,2])]",
|
||||
),
|
||||
// Nested brackets, with "invalid" syntax but valid due to nesting
|
||||
("content-['50[]']", "content-['50[]']"),
|
||||
] {
|
||||
Slim::test(input, expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -469,8 +469,9 @@ pub fn pre_process_input(content: &[u8], extension: &str) -> Vec<u8> {
|
|||
use crate::extractor::pre_processors::*;
|
||||
|
||||
match extension {
|
||||
"pug" => Pug.process(content),
|
||||
"rb" | "erb" => Ruby.process(content),
|
||||
"slim" | "pug" => Pug.process(content),
|
||||
"slim" => Slim.process(content),
|
||||
"svelte" => Svelte.process(content),
|
||||
_ => content.to_vec(),
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue