mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Ensure strings in Pug and Slim templates are handled correctly (#17000)
This PR fixes an issue where strings in the Pug and Slim pre-processor
were handled using the `string_machine`. However, the `string_machine`
is not for strings inside of Tailwind CSS classes which means that
whitespace is invalid.
This means that parts of the code that _are_ inside strings will not be
inside strings and parts of the code that are not inside strings will be
part of a potential string. This is a bit confusing to wrap your head
around, but here is a visual representation of the problem:
```
.join(' ')
^ 3. start of new string, which means that the `)` _could_ be part of a string if a new `'` occurs later.
^ 2. whitespace is not allowed, stop string
^ 1. start of string
```
Fixes: #16998
# Test plan
1. Added new test
2. Existing tests still pass
3. Added a simple test helper to make sure that we can extract the
correct candidates _after_ pre-processing
This commit is contained in:
parent
3d0606b82d
commit
85c6e04f44
@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Fixed
|
||||
|
||||
- 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))
|
||||
|
||||
## [4.0.11] - 2025-03-06
|
||||
|
||||
|
||||
@ -25,4 +25,38 @@ pub trait PreProcessor: Sized + Default {
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn test_extract_contains(input: &str, items: Vec<&str>) {
|
||||
use crate::extractor::{Extracted, Extractor};
|
||||
|
||||
let input = input.as_bytes();
|
||||
|
||||
let processor = Self::default();
|
||||
let transformed = processor.process(input);
|
||||
|
||||
let extracted = Extractor::new(&transformed).extract();
|
||||
|
||||
// Extract all candidates and css variables.
|
||||
let candidates = extracted
|
||||
.iter()
|
||||
.filter_map(|x| match x {
|
||||
Extracted::Candidate(bytes) => std::str::from_utf8(bytes).ok(),
|
||||
Extracted::CssVariable(bytes) => std::str::from_utf8(bytes).ok(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Ensure all items are present in the candidates.
|
||||
let mut missing = vec![];
|
||||
for item in &items {
|
||||
if !candidates.contains(item) {
|
||||
missing.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
if !missing.is_empty() {
|
||||
dbg!(&candidates, &missing);
|
||||
panic!("Missing some items");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
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 Pug;
|
||||
@ -12,14 +10,29 @@ impl PreProcessor for Pug {
|
||||
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);
|
||||
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 dots with spaces
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
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;
|
||||
@ -12,14 +10,29 @@ impl PreProcessor for Slim {
|
||||
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);
|
||||
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 dots with spaces
|
||||
@ -103,8 +116,42 @@ mod tests {
|
||||
),
|
||||
// Nested brackets, with "invalid" syntax but valid due to nesting
|
||||
("content-['50[]']", "content-['50[]']"),
|
||||
// Escaped string
|
||||
("content-['a\'b\'c\'']", "content-['a\'b\'c\'']"),
|
||||
] {
|
||||
Slim::test(input, expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nested_slim_syntax() {
|
||||
let input = r#"
|
||||
.text-black[
|
||||
data-controller= ['foo', ('bar' if rand.positive?)].join(' ')
|
||||
]
|
||||
.bg-green-300
|
||||
| BLACK on GREEN - OK
|
||||
|
||||
.bg-red-300[
|
||||
data-foo= 42
|
||||
]
|
||||
| Should be BLACK on RED - FAIL
|
||||
"#;
|
||||
|
||||
let expected = r#"
|
||||
text-black
|
||||
data-controller= ['foo', ('bar' if rand.positive?)].join(' ')
|
||||
]
|
||||
bg-green-300
|
||||
| BLACK on GREEN - OK
|
||||
|
||||
bg-red-300
|
||||
data-foo= 42
|
||||
]
|
||||
| Should be BLACK on RED - FAIL
|
||||
"#;
|
||||
|
||||
Slim::test(input, expected);
|
||||
Slim::test_extract_contains(input, vec!["text-black", "bg-green-300", "bg-red-300"]);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user