@@ -5,6 +5,14 @@ use bstr::ByteSlice;
5
5
#[ derive( Debug , Default ) ]
6
6
pub struct Clojure ;
7
7
8
+ #[ inline]
9
+ fn is_keyword_character ( byte : u8 ) -> bool {
10
+ matches ! (
11
+ byte,
12
+ b'+' | b'-' | b'/' | b'*' | b'_' | b'#' | b'.' | b':' | b'?'
13
+ ) | byte. is_ascii_alphanumeric ( )
14
+ }
15
+
8
16
impl PreProcessor for Clojure {
9
17
fn process ( & self , content : & [ u8 ] ) -> Vec < u8 > {
10
18
let content = content
@@ -18,6 +26,7 @@ impl PreProcessor for Clojure {
18
26
match cursor. curr {
19
27
// Consume strings as-is
20
28
b'"' => {
29
+ result[ cursor. pos ] = b' ' ;
21
30
cursor. advance ( ) ;
22
31
23
32
while cursor. pos < len {
@@ -26,52 +35,82 @@ impl PreProcessor for Clojure {
26
35
b'\\' => cursor. advance_twice ( ) ,
27
36
28
37
// End of the string
29
- b'"' => break ,
38
+ b'"' => {
39
+ result[ cursor. pos ] = b' ' ;
40
+ break ;
41
+ }
30
42
31
43
// Everything else is valid
32
44
_ => cursor. advance ( ) ,
33
45
} ;
34
46
}
35
47
}
36
48
37
- // Consume comments as-is until the end of the line.
49
+ // Discard line comments until the end of the line.
38
50
// Comments start with `;;`
39
51
b';' if matches ! ( cursor. next, b';' ) => {
40
52
while cursor. pos < len && cursor. curr != b'\n' {
53
+ result[ cursor. pos ] = b' ' ;
41
54
cursor. advance ( ) ;
42
55
}
43
56
}
44
57
45
- // A `.` surrounded by digits is a decimal number, so we don't want to replace it.
46
- //
47
- // E.g.:
48
- // ```
49
- // gap-1.5
50
- // ^
51
- // ``
52
- b'.' if cursor. prev . is_ascii_digit ( ) && cursor. next . is_ascii_digit ( ) => {
58
+ // Consume keyword until a terminating character is reached.
59
+ b':' => {
60
+ result[ cursor. pos ] = b' ' ;
61
+ cursor. advance ( ) ;
53
62
54
- // Keep the `.` as-is
55
- }
63
+ while cursor. pos < len {
64
+ match cursor. curr {
65
+ // A `.` surrounded by digits is a decimal number, so we don't want to replace it.
66
+ //
67
+ // E.g.:
68
+ // ```
69
+ // gap-1.5
70
+ // ^
71
+ // ```
72
+ b'.' if cursor. prev . is_ascii_digit ( )
73
+ && cursor. next . is_ascii_digit ( ) =>
74
+ {
75
+ // Keep the `.` as-is
76
+ }
77
+ // A `.` not surrounded by digits denotes the start of a new class name in a
78
+ // dot-delimited keyword.
79
+ //
80
+ // E.g.:
81
+ // ```
82
+ // flex.gap-1.5
83
+ // ^
84
+ // ```
85
+ b'.' => {
86
+ result[ cursor. pos ] = b' ' ;
87
+ }
88
+ // End of keyword.
89
+ _ if !is_keyword_character ( cursor. curr ) => {
90
+ result[ cursor. pos ] = b' ' ;
91
+ break ;
92
+ }
56
93
57
- // A `:` surrounded by letters denotes a variant. Keep as is.
58
- //
59
- // E.g.:
60
- // ```
61
- // lg:pr-6"
62
- // ^
63
- // ``
64
- b':' if cursor. prev . is_ascii_alphanumeric ( ) && cursor. next . is_ascii_alphanumeric ( ) => {
94
+ // Consume everything else.
95
+ _ => { }
96
+ } ;
65
97
66
- // Keep the `:` as-is
98
+ cursor. advance ( ) ;
99
+ }
67
100
}
68
101
69
- b':' | b'.' => {
102
+ // Aggressively discard everything else, reducing false positives and preventing
103
+ // characters surrounding keywords from producing false negatives.
104
+ // E.g.:
105
+ // ```
106
+ // (when condition :bg-white)
107
+ // ^
108
+ // ```
109
+ // A ')' is never a valid part of a keyword, but will nonetheless prevent 'bg-white'
110
+ // from being extracted if not discarded.
111
+ _ => {
70
112
result[ cursor. pos ] = b' ' ;
71
113
}
72
-
73
- // Consume everything else
74
- _ => { }
75
114
} ;
76
115
77
116
cursor. advance ( ) ;
@@ -92,19 +131,23 @@ mod tests {
92
131
( ":div.flex-1.flex-2" , " div flex-1 flex-2" ) ,
93
132
(
94
133
":.flex-3.flex-4 ;defaults to div" ,
95
- " flex-3 flex-4 ;defaults to div " ,
134
+ " flex-3 flex-4 " ,
96
135
) ,
97
- ( "{:class :flex-5.flex-6" , "{ flex-5 flex-6" ) ,
98
- ( r#"{:class "flex-7 flex-8"}"# , r#"{ " flex-7 flex-8"} "# ) ,
136
+ ( "{:class :flex-5.flex-6" , " flex-5 flex-6" ) ,
137
+ ( r#"{:class "flex-7 flex-8"}"# , r#" flex-7 flex-8 "# ) ,
99
138
(
100
139
r#"{:class ["flex-9" :flex-10]}"# ,
101
- r#"{ [" flex-9" flex-10]} "# ,
140
+ r#" flex-9 flex-10 "# ,
102
141
) ,
103
142
(
104
143
r#"(dom/div {:class "flex-11 flex-12"})"# ,
105
- r#"(dom/div { "flex-11 flex-12"})"# ,
144
+ r#" flex-11 flex-12 "# ,
145
+ ) ,
146
+ ( "(dom/div :.flex-13.flex-14" , " flex-13 flex-14" ) ,
147
+ (
148
+ r#"[:div#hello.bg-white.pr-1.5 {:class ["grid grid-cols-[auto,1fr] grid-rows-2"]}]"# ,
149
+ r#" div#hello bg-white pr-1.5 grid grid-cols-[auto,1fr] grid-rows-2 "# ,
106
150
) ,
107
- ( "(dom/div :.flex-13.flex-14" , "(dom/div flex-13 flex-14" ) ,
108
151
] {
109
152
Clojure :: test ( input, expected) ;
110
153
}
@@ -198,8 +241,35 @@ mod tests {
198
241
($ :div {:class [:flex :first:lg:pr-6 :first:2xl:pl-6 :group-hover/2:2xs:pt-6]} …)
199
242
200
243
:.hover:bg-white
244
+
245
+ [:div#hello.bg-white.pr-1.5]
246
+ "# ;
247
+
248
+ Clojure :: test_extract_contains (
249
+ input,
250
+ vec ! [
251
+ "flex" ,
252
+ "first:lg:pr-6" ,
253
+ "first:2xl:pl-6" ,
254
+ "group-hover/2:2xs:pt-6" ,
255
+ "hover:bg-white" ,
256
+ "bg-white" ,
257
+ "pr-1.5" ,
258
+ ] ,
259
+ ) ;
260
+ }
261
+
262
+ // https://github.com/tailwindlabs/tailwindcss/issues/18344
263
+ #[ test]
264
+ fn test_noninterference_of_parens_on_keywords ( ) {
265
+ let input = r#"
266
+ (get props :y-padding :py-5)
267
+ ($ :div {:class [:flex.pr-1.5 (if condition :bg-white :bg-black)]})
201
268
"# ;
202
269
203
- Clojure :: test_extract_contains ( input, vec ! [ "flex" , "first:lg:pr-6" , "first:2xl:pl-6" , "group-hover/2:2xs:pt-6" , "hover:bg-white" ] ) ;
270
+ Clojure :: test_extract_contains (
271
+ input,
272
+ vec ! [ "py-5" , "flex" , "pr-1.5" , "bg-white" , "bg-black" ] ,
273
+ ) ;
204
274
}
205
275
}
0 commit comments