Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Suggest container query variants ([#15857](https://github.com/tailwindlabs/tailwindcss/pull/15857))
- Disable bare value suggestions when not using the `--spacing` variable ([#15857](https://github.com/tailwindlabs/tailwindcss/pull/15857))
- Ensure suggested classes are properly sorted ([#15857](https://github.com/tailwindlabs/tailwindcss/pull/15857))
- Find utilities when using the Svelte class shorthand syntax across multiple lines ([#15974](https://github.com/tailwindlabs/tailwindcss/pull/15974))
- Find utilities when using the Angular class shorthand syntax ([#15974](https://github.com/tailwindlabs/tailwindcss/pull/15974))
- Find utilities when using functions inside arrays ([#15974](https://github.com/tailwindlabs/tailwindcss/pull/15974))
- _Upgrade_: Ensure JavaScript config files on different drives are correctly migrated ([#15927](https://github.com/tailwindlabs/tailwindcss/pull/15927))

## [4.0.0] - 2025-01-21
Expand Down
4 changes: 3 additions & 1 deletion crates/oxide/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,9 @@ fn read_changed_content(c: ChangedContent) -> Option<Vec<u8>> {
};

match extension {
Some("svelte") => Some(content.replace(" class:", " ")),
// Angular class shorthand
Some("html") => Some(content.replace("[class.", "[")),
Some("svelte") => Some(content.replace(" class:", " ").replace("\tclass:", " ")),
_ => Some(content),
}
}
Expand Down
73 changes: 66 additions & 7 deletions crates/oxide/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,26 @@ impl<'a> Extractor<'a> {
return ValidationResult::Restart;
}

// Only allow parentheses for the shorthand arbitrary custom properties syntax
if let Some(index) = utility.find(b"(") {
let mut skip_parens_check = false;
let start_brace_index = utility.find(b"[");
let end_brace_index = utility.find(b"]");

match (start_brace_index, end_brace_index) {
(Some(start_brace_index), Some(end_brace_index)) => {
if start_brace_index < index && end_brace_index > index {
skip_parens_check = true;
}
}
_ => {}
}

if !skip_parens_check && !utility[index + 1..].starts_with(b"--") {
return ValidationResult::Restart;
}
}

// Pluck out the part that we are interested in.
let utility = &utility[offset..(utility.len() - offset_end)];

Expand Down Expand Up @@ -911,9 +931,6 @@ impl<'a> Extractor<'a> {
fn generate_slices(&mut self, candidate: &'a [u8]) -> ParseAction<'a> {
match self.without_surrounding() {
Bracketing::None => ParseAction::SingleCandidate(candidate),
Bracketing::Included(sliceable) if sliceable == candidate => {
ParseAction::SingleCandidate(candidate)
}
Bracketing::Included(sliceable) | Bracketing::Wrapped(sliceable) => {
if candidate == sliceable {
ParseAction::SingleCandidate(candidate)
Expand Down Expand Up @@ -1117,7 +1134,7 @@ mod test {
assert_eq!(candidates, vec!["something"]);

let candidates = run(" [feature(slice_as_chunks)]", false);
assert_eq!(candidates, vec!["feature(slice_as_chunks)"]);
assert_eq!(candidates, vec!["feature", "slice_as_chunks"]);

let candidates = run("![feature(slice_as_chunks)]", false);
assert!(candidates.is_empty());
Expand Down Expand Up @@ -1213,9 +1230,8 @@ mod test {

#[test]
fn ignores_arbitrary_property_ish_things() {
// FIXME: () are only valid in an arbitrary
let candidates = run(" [feature(slice_as_chunks)]", false);
assert_eq!(candidates, vec!["feature(slice_as_chunks)",]);
assert_eq!(candidates, vec!["feature", "slice_as_chunks",]);
}

#[test]
Expand Down Expand Up @@ -1637,7 +1653,6 @@ mod test {

#[test]
fn arbitrary_properties_are_not_picked_up_after_an_escape() {
_please_trace();
let candidates = run(
r#"
<!-- [!code word:group-has-\\[a\\]\\:block] -->
Expand All @@ -1648,4 +1663,48 @@ mod test {

assert_eq!(candidates, vec!["!code", "a"]);
}

#[test]
fn test_find_candidates_in_braces_inside_brackets() {
let candidates = run(
r#"
const classes = [wrapper("bg-red-500")]
"#,
false,
);

assert_eq!(
candidates,
vec!["const", "classes", "wrapper", "bg-red-500"]
);
}

#[test]
fn test_is_valid_candidate_string() {
assert_eq!(
Extractor::is_valid_candidate_string(b"foo"),
ValidationResult::Valid
);
assert_eq!(
Extractor::is_valid_candidate_string(b"foo-(--color-red-500)"),
ValidationResult::Valid
);
assert_eq!(
Extractor::is_valid_candidate_string(b"bg-[url(foo)]"),
ValidationResult::Valid
);
assert_eq!(
Extractor::is_valid_candidate_string(b"group-foo/(--bar)"),
ValidationResult::Valid
);

assert_eq!(
Extractor::is_valid_candidate_string(b"foo(\"bg-red-500\")"),
ValidationResult::Restart
);
assert_eq!(
Extractor::is_valid_candidate_string(b"foo-("),
ValidationResult::Restart
);
}
}
19 changes: 18 additions & 1 deletion crates/oxide/tests/scanner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,14 +323,31 @@ mod scanner {
("foo.jpg", "xl:font-bold"),
// A file that is ignored
("foo.html", "lg:font-bold"),
// An Angular file using the class shorthand syntax
(
"index.angular.html",
"<div [class.underline]=\"bool\"></div>",
),
// A svelte file with `class:foo="bar"` syntax
("index.svelte", "<div class:px-4='condition'></div>"),
("index2.svelte", "<div\n\tclass:px-5='condition'></div>"),
("index3.svelte", "<div\n class:px-6='condition'></div>"),
])
.1;

assert_eq!(
candidates,
vec!["condition", "div", "font-bold", "md:flex", "px-4"]
vec![
"bool",
"condition",
"div",
"font-bold",
"md:flex",
"px-4",
"px-5",
"px-6",
"underline"
]
);
}

Expand Down