Fix scanning classes delimited by tab characters (#15169)

This PR fixes an issue where multi-line candidates in Svelte files
couldn't be found as reported in #15148

After digging in, the real culprit seems to be that the reproduction
used tab `\t` characters instead of spaces and we only delimited
explicitly on spaces.

Initially I couldn't reproduce this in an integration test until we
(@thecrypticace and I) realised that `\t` was being used.

## Test plan:

This PR adds an integration test that fails before the fix happens. The
fix itself is easy in the sense that we just use all ascii whitespace
characters instead of just spaces.

Fixes: #15148
This commit is contained in:
Robin Malfait 2024-11-26 19:22:18 +01:00 committed by GitHub
parent 8ae8f65f86
commit 4bdc724a22
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 93 additions and 14 deletions

View File

@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Ensure `.group` and `.peer` are prefixed when using the `prefix(…)` option ([#15174](https://github.com/tailwindlabs/tailwindcss/pull/15174))
- Ensure 3D transforms render correctly in Safari ([#15179](https://github.com/tailwindlabs/tailwindcss/pull/15179))
- Ensure `--spacing-*` variables take precedence over `--container-*` variables ([#15180](https://github.com/tailwindlabs/tailwindcss/pull/15180))
- Fix scanning classes delimited by tab characters ([#15169](https://github.com/tailwindlabs/tailwindcss/pull/15169))
## [4.0.0-beta.2] - 2024-11-22

View File

@ -253,9 +253,7 @@ impl<'a> Extractor<'a> {
// Reject candidates that are single camelCase words, e.g.: `useEffect`
if candidate.iter().all(|c| c.is_ascii_alphanumeric())
&& candidate
.iter()
.any(|c| c.is_ascii_uppercase())
&& candidate.iter().any(|c| c.is_ascii_uppercase())
{
return ValidationResult::Invalid;
}
@ -570,7 +568,7 @@ impl<'a> Extractor<'a> {
}
},
b' ' if !self.opts.preserve_spaces_in_arbitrary => {
c if c.is_ascii_whitespace() && !self.opts.preserve_spaces_in_arbitrary => {
trace!("Arbitrary::SkipAndEndEarly\t");
if let Arbitrary::Brackets { start_idx } | Arbitrary::Parens { start_idx } =
@ -633,10 +631,8 @@ impl<'a> Extractor<'a> {
match self.cursor.curr {
// Enter arbitrary value mode. E.g.: `bg-[rgba(0, 0, 0)]`
// ^
b'[' if matches!(
self.cursor.prev,
b'@' | b'-' | b' ' | b':' | b'/' | b'!' | b'\0'
) =>
b'[' if matches!(self.cursor.prev, b'@' | b'-' | b':' | b'/' | b'!' | b'\0')
|| self.cursor.prev.is_ascii_whitespace() =>
{
trace!("Arbitrary::Start\t");
self.arbitrary = Arbitrary::Brackets {
@ -668,7 +664,8 @@ impl<'a> Extractor<'a> {
(true, _) => ParseAction::Consume,
// Looks like the end of a candidate == okay
(_, b' ' | b'\'' | b'"' | b'`') => ParseAction::Consume,
(_, b'\'' | b'"' | b'`') => ParseAction::Consume,
(_, c) if c.is_ascii_whitespace() => ParseAction::Consume,
// Otherwise, not a valid character in a candidate
_ => ParseAction::Skip,
@ -1542,17 +1539,98 @@ mod test {
#[test]
fn simple_utility_names_with_numbers_work() {
let candidates = run(r#"<div class="h2 hz"></div>"#, false);
assert_eq!(candidates, vec!["div", "class", "h2", "hz",]);
}
#[test]
fn classes_in_an_array_without_whitespace() {
let candidates = run(
r#"<div class="h2 hz"></div>"#,
"let classes = ['bg-black','hover:px-0.5','text-[13px]','[--my-var:1_/_2]','[.foo_&]:px-[0]','[.foo_&]:[color:red]']",
false,
);
assert_eq!(
candidates,
vec![
"div",
"class",
"h2",
"hz",
"let",
"classes",
"bg-black",
"hover:px-0.5",
"text-[13px]",
"[--my-var:1_/_2]",
"--my-var:1_/_2",
"[.foo_&]:px-[0]",
"[.foo_&]:[color:red]",
]
);
}
#[test]
fn classes_in_an_array_with_spaces() {
let candidates = run(
"let classes = ['bg-black', 'hover:px-0.5', 'text-[13px]', '[--my-var:1_/_2]', '[.foo_&]:px-[0]', '[.foo_&]:[color:red]']",
false,
);
assert_eq!(
candidates,
vec![
"let",
"classes",
"bg-black",
"hover:px-0.5",
"text-[13px]",
"[--my-var:1_/_2]",
"--my-var:1_/_2",
"[.foo_&]:px-[0]",
"[.foo_&]:[color:red]",
]
);
}
#[test]
fn classes_in_an_array_with_tabs() {
let candidates = run(
"let classes = ['bg-black',\t'hover:px-0.5',\t'text-[13px]',\t'[--my-var:1_/_2]',\t'[.foo_&]:px-[0]',\t'[.foo_&]:[color:red]']",
false,
);
assert_eq!(
candidates,
vec![
"let",
"classes",
"bg-black",
"hover:px-0.5",
"text-[13px]",
"[--my-var:1_/_2]",
"--my-var:1_/_2",
"[.foo_&]:px-[0]",
"[.foo_&]:[color:red]",
]
);
}
#[test]
fn classes_in_an_array_with_newlines() {
let candidates = run(
"let classes = [\n'bg-black',\n'hover:px-0.5',\n'text-[13px]',\n'[--my-var:1_/_2]',\n'[.foo_&]:px-[0]',\n'[.foo_&]:[color:red]'\n]",
false,
);
assert_eq!(
candidates,
vec![
"let",
"classes",
"bg-black",
"hover:px-0.5",
"text-[13px]",
"[--my-var:1_/_2]",
"--my-var:1_/_2",
"[.foo_&]:px-[0]",
"[.foo_&]:[color:red]",
]
);
}