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

Fix Oxide scanner bugs #15974

Merged
merged 8 commits into from
Jan 28, 2025
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:", " ")),
thecrypticace marked this conversation as resolved.
Show resolved Hide resolved
_ => 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