mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Ensure candidate extraction works as expected in Clojure/ClojureScript (#17087)
This PR adds a Clojure/ClojureScript pre processor to make sure that candidate extraction works as expected. | Before | After | | --- | --- | | <img width="908" alt="image" src="https://github.com/user-attachments/assets/98aba8b6-0c44-47c6-b87c-ecf955e5e007" /> | <img width="908" alt="image" src="https://github.com/user-attachments/assets/7a5ec3eb-1630-4b60-80bd-c07bc2381d3b" /> | You can see that the classes preceded by `:` are not properly extracted in the before case, but they are in the after case. We do extract a few more cases now like `:class` and `:className` itself, but at least we also retrieve all the `flex-*` classes. We could also always ignore `:class` and `:className` literals: <img width="908" alt="image" src="https://github.com/user-attachments/assets/f5a67cae-25d6-4811-b777-f72fdb5ef450" />
This commit is contained in:
parent
74ccde4672
commit
221855b195
@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
- Do not extract candidates with JS string interpolation `${` ([#17142](https://github.com/tailwindlabs/tailwindcss/pull/17142))
|
||||
- Fix extraction of variants containing `.` character ([#17153](https://github.com/tailwindlabs/tailwindcss/pull/17153))
|
||||
- Fix extracting candidates in Clojure/ClojureScript ([#17087](https://github.com/tailwindlabs/tailwindcss/pull/17087))
|
||||
|
||||
## [4.0.13] - 2025-03-11
|
||||
|
||||
|
||||
@ -355,12 +355,6 @@ mod tests {
|
||||
r#"[:is(italic):is(underline)]:flex"#,
|
||||
vec!["[:is(italic):is(underline)]:flex"],
|
||||
),
|
||||
// Clojure syntax. See: https://github.com/tailwindlabs/tailwindcss/issues/16189#issuecomment-2642438176
|
||||
(r#"[:div {:class ["p-2"]}"#, vec!["p-2"]),
|
||||
(
|
||||
r#"[:div {:class ["p-2" "text-green"]}"#,
|
||||
vec!["p-2", "text-green"],
|
||||
),
|
||||
(r#"[:div {:class ["p-2""#, vec!["p-2"]),
|
||||
(r#" "text-green"]}"#, vec!["text-green"]),
|
||||
(r#"[:div.p-2]"#, vec!["p-2"]),
|
||||
@ -668,8 +662,13 @@ mod tests {
|
||||
(r#"[:div {:class ["p-2""#, vec!["p-2"]),
|
||||
(r#" "text-green"]}"#, vec!["text-green"]),
|
||||
(r#"[:div.p-2]"#, vec!["p-2"]),
|
||||
(r#"[:div {:class ["p-2"]}"#, vec!["p-2"]),
|
||||
(
|
||||
r#"[:div {:class ["p-2" "text-green"]}"#,
|
||||
vec!["p-2", "text-green"],
|
||||
),
|
||||
] {
|
||||
assert_extract_sorted_candidates(input, expected);
|
||||
assert_extract_candidates_contains(&pre_process_input(input, "cljs"), expected);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
159
crates/oxide/src/extractor/pre_processors/clojure.rs
Normal file
159
crates/oxide/src/extractor/pre_processors/clojure.rs
Normal file
@ -0,0 +1,159 @@
|
||||
use crate::cursor;
|
||||
use crate::extractor::pre_processors::pre_processor::PreProcessor;
|
||||
use bstr::ByteSlice;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Clojure;
|
||||
|
||||
impl PreProcessor for Clojure {
|
||||
fn process(&self, content: &[u8]) -> Vec<u8> {
|
||||
let content = content
|
||||
.replace(":class", " ")
|
||||
.replace(":className", " ");
|
||||
let len = content.len();
|
||||
let mut result = content.to_vec();
|
||||
let mut cursor = cursor::Cursor::new(&content);
|
||||
|
||||
while cursor.pos < len {
|
||||
match cursor.curr {
|
||||
// Consume strings as-is
|
||||
b'"' => {
|
||||
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'"' => break,
|
||||
|
||||
// Everything else is valid
|
||||
_ => cursor.advance(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Consume comments as-is until the end of the line.
|
||||
// Comments start with `;;`
|
||||
b';' if matches!(cursor.next, b';') => {
|
||||
while cursor.pos < len && cursor.curr != b'\n' {
|
||||
cursor.advance();
|
||||
}
|
||||
}
|
||||
|
||||
b':' | b'.' => {
|
||||
result[cursor.pos] = b' ';
|
||||
}
|
||||
|
||||
// Consume everything else
|
||||
_ => {}
|
||||
};
|
||||
|
||||
cursor.advance();
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Clojure;
|
||||
use crate::extractor::pre_processors::pre_processor::PreProcessor;
|
||||
|
||||
#[test]
|
||||
fn test_clojure_pre_processor() {
|
||||
for (input, expected) in [
|
||||
(":div.flex-1.flex-2", " div flex-1 flex-2"),
|
||||
(
|
||||
":.flex-3.flex-4 ;defaults to div",
|
||||
" flex-3 flex-4 ;defaults to div",
|
||||
),
|
||||
("{:class :flex-5.flex-6", "{ flex-5 flex-6"),
|
||||
(r#"{:class "flex-7 flex-8"}"#, r#"{ "flex-7 flex-8"}"#),
|
||||
(
|
||||
r#"{:class ["flex-9" :flex-10]}"#,
|
||||
r#"{ ["flex-9" flex-10]}"#,
|
||||
),
|
||||
(
|
||||
r#"(dom/div {:class "flex-11 flex-12"})"#,
|
||||
r#"(dom/div { "flex-11 flex-12"})"#,
|
||||
),
|
||||
("(dom/div :.flex-13.flex-14", "(dom/div flex-13 flex-14"),
|
||||
] {
|
||||
Clojure::test(input, expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_candidates() {
|
||||
// https://github.com/luckasRanarison/tailwind-tools.nvim/issues/68#issuecomment-2660951258
|
||||
let input = r#"
|
||||
:div.c1.c2
|
||||
:.c3.c4 ;defaults to div
|
||||
{:class :c5.c6
|
||||
{:class "c7 c8"}
|
||||
{:class ["c9" :c10]}
|
||||
(dom/div {:class "c11 c12"})
|
||||
(dom/div :.c13.c14
|
||||
{:className :c15.c16
|
||||
{:className "c17 c18"}
|
||||
{:className ["c19" :c20]}
|
||||
(dom/div {:className "c21 c22"})
|
||||
"#;
|
||||
|
||||
Clojure::test_extract_contains(
|
||||
input,
|
||||
vec![
|
||||
"c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "c10", "c11", "c12", "c13",
|
||||
"c14", "c15", "c16", "c17", "c18", "c19", "c20", "c21", "c22",
|
||||
],
|
||||
);
|
||||
|
||||
// Similar structure but using real classes
|
||||
let input = r#"
|
||||
:div.flex-1.flex-2
|
||||
:.flex-3.flex-4 ;defaults to div
|
||||
{:class :flex-5.flex-6
|
||||
{:class "flex-7 flex-8"}
|
||||
{:class ["flex-9" :flex-10]}
|
||||
(dom/div {:class "flex-11 flex-12"})
|
||||
(dom/div :.flex-13.flex-14
|
||||
{:className :flex-15.flex-16
|
||||
{:className "flex-17 flex-18"}
|
||||
{:className ["flex-19" :flex-20]}
|
||||
(dom/div {:className "flex-21 flex-22"})
|
||||
"#;
|
||||
|
||||
Clojure::test_extract_contains(
|
||||
input,
|
||||
vec![
|
||||
"flex-1", "flex-2", "flex-3", "flex-4", "flex-5", "flex-6", "flex-7", "flex-8",
|
||||
"flex-9", "flex-10", "flex-11", "flex-12", "flex-13", "flex-14", "flex-15",
|
||||
"flex-16", "flex-17", "flex-18", "flex-19", "flex-20", "flex-21", "flex-22",
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_special_characters_are_valid_in_strings() {
|
||||
// In this case the `:` and `.` should not be replaced by ` ` because they are inside a
|
||||
// string.
|
||||
let input = r#"
|
||||
(dom/div {:class "hover:flex px-1.5"})
|
||||
"#;
|
||||
|
||||
Clojure::test_extract_contains(input, vec!["hover:flex", "px-1.5"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ignore_comments_with_invalid_strings() {
|
||||
let input = r#"
|
||||
;; This is an unclosed string: "
|
||||
(dom/div {:class "hover:flex px-1.5"})
|
||||
"#;
|
||||
|
||||
Clojure::test_extract_contains(input, vec!["hover:flex", "px-1.5"]);
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
pub mod clojure;
|
||||
pub mod haml;
|
||||
pub mod json;
|
||||
pub mod pre_processor;
|
||||
@ -7,6 +8,7 @@ pub mod ruby;
|
||||
pub mod slim;
|
||||
pub mod svelte;
|
||||
|
||||
pub use clojure::*;
|
||||
pub use haml::*;
|
||||
pub use json::*;
|
||||
pub use pre_processor::*;
|
||||
|
||||
@ -468,6 +468,7 @@ pub fn pre_process_input(content: &[u8], extension: &str) -> Vec<u8> {
|
||||
use crate::extractor::pre_processors::*;
|
||||
|
||||
match extension {
|
||||
"clj" | "cljs" | "cljc" => Clojure.process(content),
|
||||
"cshtml" | "razor" => Razor.process(content),
|
||||
"haml" => Haml.process(content),
|
||||
"json" => Json.process(content),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user