Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Re-introduce automatic var injection shorthand #15020

Merged
merged 13 commits into from
Nov 18, 2024
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Reintroduce `max-w-screen-*` utilities that read from the `--breakpoint` namespace as deprecated utilities ([#15013](https://github.com/tailwindlabs/tailwindcss/pull/15013))
- Re-introduce automatic var injection shorthand ([#15020](https://github.com/tailwindlabs/tailwindcss/pull/15020))

### Fixed

Expand Down
126 changes: 95 additions & 31 deletions crates/oxide/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,24 @@ pub struct ExtractorOptions {
pub preserve_spaces_in_arbitrary: bool,
}

#[derive(Debug, PartialEq, Eq, Clone)]
enum Arbitrary {
/// Not inside any arbitrary value
None,

/// In arbitrary value mode with square brackets
///
/// E.g.: `bg-[…]`
/// ^
Brackets { start_idx: usize },

/// In arbitrary value mode with parens
///
/// E.g.: `bg-(…)`
/// ^
Parens { start_idx: usize },
}

pub struct Extractor<'a> {
opts: ExtractorOptions,

Expand All @@ -48,9 +66,9 @@ pub struct Extractor<'a> {
idx_start: usize,
idx_end: usize,
idx_last: usize,
idx_arbitrary_start: usize,

in_arbitrary: bool,
arbitrary: Arbitrary,

in_candidate: bool,
in_escape: bool,

Expand Down Expand Up @@ -105,9 +123,8 @@ impl<'a> Extractor<'a> {

idx_start: 0,
idx_end: 0,
idx_arbitrary_start: 0,

in_arbitrary: false,
arbitrary: Arbitrary::None,
in_candidate: false,
in_escape: false,

Expand Down Expand Up @@ -461,7 +478,7 @@ impl<'a> Extractor<'a> {

#[inline(always)]
fn parse_arbitrary(&mut self) -> ParseAction<'a> {
// In this we could technically use memchr 6 times (then looped) to find the indexes / bounds of arbitrary valuesq
// In this we could technically use memchr 6 times (then looped) to find the indexes / bounds of arbitrary values
if self.in_escape {
return self.parse_escaped();
}
Expand All @@ -479,9 +496,29 @@ impl<'a> Extractor<'a> {
self.bracket_stack.pop();
}

// Last bracket is different compared to what we expect, therefore we are not in a
// valid arbitrary value.
_ if !self.in_quotes() => return ParseAction::Skip,
// This is the last bracket meaning the end of arbitrary content
_ if !self.in_quotes() => {
if matches!(self.cursor.next, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9') {
return ParseAction::Consume;
}

if let Arbitrary::Parens { start_idx } = self.arbitrary {
trace!("Arbitrary::End\t");
self.arbitrary = Arbitrary::None;

if self.cursor.pos - start_idx == 1 {
// We have an empty arbitrary value, which is not allowed
return ParseAction::Skip;
}

// We have a valid arbitrary value
return ParseAction::Consume;
}

// Last parenthesis is different compared to what we expect, therefore we are
// not in a valid arbitrary value.
return ParseAction::Skip;
}

// We're probably in quotes or nested brackets, so we keep going
_ => {}
Expand All @@ -501,12 +538,14 @@ impl<'a> Extractor<'a> {
return ParseAction::Consume;
}

trace!("Arbitrary::End\t");
self.in_arbitrary = false;
if let Arbitrary::Brackets { start_idx } = self.arbitrary {
trace!("Arbitrary::End\t");
self.arbitrary = Arbitrary::None;

if self.cursor.pos - self.idx_arbitrary_start == 1 {
// We have an empty arbitrary value, which is not allowed
return ParseAction::Skip;
if self.cursor.pos - start_idx == 1 {
// We have an empty arbitrary value, which is not allowed
return ParseAction::Skip;
}
}
}

Expand All @@ -531,9 +570,13 @@ impl<'a> Extractor<'a> {
b' ' if !self.opts.preserve_spaces_in_arbitrary => {
trace!("Arbitrary::SkipAndEndEarly\t");

// Restart the parser ahead of the arbitrary value
// It may pick up more candidates
return ParseAction::RestartAt(self.idx_arbitrary_start + 1);
if let Arbitrary::Brackets { start_idx } | Arbitrary::Parens { start_idx } =
self.arbitrary
{
// Restart the parser ahead of the arbitrary value It may pick up more
// candidates
return ParseAction::RestartAt(start_idx + 1);
}
}

// Arbitrary values allow any character inside them
Expand All @@ -550,11 +593,12 @@ impl<'a> Extractor<'a> {
#[inline(always)]
fn parse_start(&mut self) -> ParseAction<'a> {
match self.cursor.curr {
// Enter arbitrary value mode
// Enter arbitrary property mode
b'[' => {
trace!("Arbitrary::Start\t");
self.in_arbitrary = true;
self.idx_arbitrary_start = self.cursor.pos;
self.arbitrary = Arbitrary::Brackets {
start_idx: self.cursor.pos,
};

ParseAction::Consume
}
Expand Down Expand Up @@ -584,22 +628,31 @@ impl<'a> Extractor<'a> {
#[inline(always)]
fn parse_continue(&mut self) -> ParseAction<'a> {
match self.cursor.curr {
// Enter arbitrary value mode
// 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'
) =>
{
trace!("Arbitrary::Start\t");
self.in_arbitrary = true;
self.idx_arbitrary_start = self.cursor.pos;
self.arbitrary = Arbitrary::Brackets {
start_idx: self.cursor.pos,
};
}

// Can't enter arbitrary value mode
// This can't be a candidate
b'[' => {
trace!("Arbitrary::Skip_Start\t");
// Enter arbitrary value mode. E.g.: `bg-(--my-color)`
// ^
b'(' if matches!(self.cursor.prev, b'-' | b'/') => {
trace!("Arbitrary::Start\t");
self.arbitrary = Arbitrary::Parens {
start_idx: self.cursor.pos,
};
}

// Can't enter arbitrary value mode. This can't be a candidate.
b'[' | b'(' => {
trace!("Arbitrary::Skip_Start\t");
return ParseAction::Skip;
}

Expand Down Expand Up @@ -684,7 +737,7 @@ impl<'a> Extractor<'a> {
#[inline(always)]
fn can_be_candidate(&mut self) -> bool {
self.in_candidate
&& !self.in_arbitrary
&& matches!(self.arbitrary, Arbitrary::None)
&& (0..=127).contains(&self.cursor.curr)
&& (self.idx_start == 0 || self.input[self.idx_start - 1] <= 127)
}
Expand All @@ -696,13 +749,13 @@ impl<'a> Extractor<'a> {
self.idx_start = self.cursor.pos;
self.idx_end = self.cursor.pos;
self.in_candidate = false;
self.in_arbitrary = false;
self.arbitrary = Arbitrary::None;
self.in_escape = false;
}

#[inline(always)]
fn parse_char(&mut self) -> ParseAction<'a> {
if self.in_arbitrary {
if !matches!(self.arbitrary, Arbitrary::None) {
self.parse_arbitrary()
} else if self.in_candidate {
self.parse_continue()
Expand Down Expand Up @@ -732,9 +785,8 @@ impl<'a> Extractor<'a> {

self.idx_start = pos;
self.idx_end = pos;
self.idx_arbitrary_start = 0;

self.in_arbitrary = false;
self.arbitrary = Arbitrary::None;
self.in_candidate = false;
self.in_escape = false;

Expand Down Expand Up @@ -977,6 +1029,18 @@ mod test {
assert_eq!(candidates, vec!["m-[2px]"]);
}

#[test]
fn it_can_parse_utilities_with_arbitrary_var_shorthand() {
let candidates = run("m-(--my-var)", false);
assert_eq!(candidates, vec!["m-(--my-var)"]);
}

#[test]
fn it_can_parse_utilities_with_arbitrary_var_shorthand_as_modifier() {
let candidates = run("bg-(--my-color)/(--my-opacity)", false);
assert_eq!(candidates, vec!["bg-(--my-color)/(--my-opacity)"]);
}

#[test]
fn it_throws_away_arbitrary_values_that_are_unbalanced() {
let candidates = run("m-[calc(100px*2]", false);
Expand Down
20 changes: 10 additions & 10 deletions integrations/upgrade/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ test(
--- ./src/index.html ---
<h1>🤠👋</h1>
<div
class="flex! sm:block! bg-linear-to-t bg-[var(--my-red)] max-w-[var(--breakpoint-md)] ml-[var(--breakpoint-md)]"
class="flex! sm:block! bg-linear-to-t bg-(--my-red) max-w-(--breakpoint-md) ml-(--breakpoint-md)"
></div>
<!-- Migrate to sm -->
<div class="blur-sm shadow-sm rounded-sm inset-shadow-sm drop-shadow-sm"></div>
Expand Down Expand Up @@ -151,9 +151,9 @@ test(
candidate`flex!`,
candidate`sm:block!`,
candidate`bg-linear-to-t`,
candidate`bg-[var(--my-red)]`,
candidate`max-w-[var(--breakpoint-md)]`,
candidate`ml-[var(--breakpoint-md)`,
candidate`bg-(--my-red)`,
candidate`max-w-(--breakpoint-md)`,
candidate`ml-(--breakpoint-md)`,
])
},
)
Expand Down Expand Up @@ -639,7 +639,7 @@ test(
'src/index.html',
// prettier-ignore
js`
<div class="bg-[var(--my-red)]"></div>
<div class="bg-(--my-red)"></div>
`,
)

Expand Down Expand Up @@ -798,7 +798,7 @@ test(
'src/index.html',
// prettier-ignore
js`
<div class="bg-[var(--my-red)]"></div>
<div class="bg-(--my-red)"></div>
`,
)

Expand Down Expand Up @@ -873,7 +873,7 @@ test(
'src/index.html',
// prettier-ignore
js`
<div class="bg-[var(--my-red)]"></div>
<div class="bg-(--my-red)"></div>
`,
)

Expand Down Expand Up @@ -1447,7 +1447,7 @@ test(
"
--- ./src/index.html ---
<div
class="flex! sm:block! bg-linear-to-t bg-[var(--my-red)]"
class="flex! sm:block! bg-linear-to-t bg-(--my-red)"
></div>

--- ./src/root.1.css ---
Expand Down Expand Up @@ -1664,7 +1664,7 @@ test(
"
--- ./src/index.html ---
<div
class="flex! sm:block! bg-linear-to-t bg-[var(--my-red)]"
class="flex! sm:block! bg-linear-to-t bg-(--my-red)"
></div>

--- ./src/index.css ---
Expand Down Expand Up @@ -1799,7 +1799,7 @@ test(
"
--- ./src/index.html ---
<div
class="flex! sm:block! bg-linear-to-t bg-[var(--my-red)]"
class="flex! sm:block! bg-linear-to-t bg-(--my-red)"
></div>

--- ./src/index.css ---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ const candidates = [
['bg-[no-repeat_url(/image_13.png)]', 'bg-[no-repeat_url(/image_13.png)]'],
[
'bg-[var(--spacing-0_5,_var(--spacing-1_5,_3rem))]',
'bg-[var(--spacing-0_5,var(--spacing-1_5,3rem))]',
'bg-(--spacing-0_5,var(--spacing-1_5,3rem))',
],
]

Expand Down
Loading