@@ -3,6 +3,18 @@ import { parseCandidate } from '../../../../tailwindcss/src/candidate'
3
3
import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
4
4
import { printCandidate } from '../candidates'
5
5
6
+ const QUOTES = [ '"' , "'" , '`' ]
7
+ const LOGICAL_OPERATORS = [ '&&' , '||' , '===' , '==' , '!=' , '!==' , '>' , '>=' , '<' , '<=' ]
8
+ const CONDITIONAL_TEMPLATE_SYNTAX = [
9
+ // Vue
10
+ / v - i f = [ ' " ] $ / ,
11
+ / v - s h o w = [ ' " ] $ / ,
12
+
13
+ // Alpine
14
+ / x - s h o w = [ ' " ] $ / ,
15
+ / x - i f = [ ' " ] $ / ,
16
+ ]
17
+
6
18
// In v3 the important modifier `!` sits in front of the utility itself, not
7
19
// before any of the variants. In v4, we want it to be at the end of the utility
8
20
// so that it's always in the same location regardless of whether you used
@@ -25,7 +37,7 @@ export function important(
25
37
end : number
26
38
} ,
27
39
) : string {
28
- for ( let candidate of parseCandidate ( rawCandidate , designSystem ) ) {
40
+ nextCandidate: for ( let candidate of parseCandidate ( rawCandidate , designSystem ) ) {
29
41
if ( candidate . important && candidate . raw [ candidate . raw . length - 1 ] !== '!' ) {
30
42
// The important migration is one of the most broad migrations with a high
31
43
// potential of matching false positives since `!` is a valid character in
@@ -34,32 +46,54 @@ export function important(
34
46
// on the side of caution and only migrate candidates that we are certain
35
47
// are inside of a string.
36
48
if ( location ) {
37
- let isQuoteBeforeCandidate = false
49
+ let currentLineBeforeCandidate = ''
38
50
for ( let i = location . start - 1 ; i >= 0 ; i -- ) {
39
51
let char = location . contents . at ( i ) !
40
52
if ( char === '\n' ) {
41
53
break
42
54
}
43
- if ( isQuote ( char ) ) {
44
- isQuoteBeforeCandidate = true
45
- break
46
- }
55
+ currentLineBeforeCandidate = char + currentLineBeforeCandidate
47
56
}
48
-
49
- let isQuoteAfterCandidate = false
57
+ let currentLineAfterCandidate = ''
50
58
for ( let i = location . end ; i < location . contents . length ; i ++ ) {
51
59
let char = location . contents . at ( i ) !
52
60
if ( char === '\n' ) {
53
61
break
54
62
}
55
- if ( isQuote ( char ) ) {
56
- isQuoteAfterCandidate = true
57
- break
58
- }
63
+ currentLineAfterCandidate += char
59
64
}
60
65
66
+ // Heuristics 1: Require the candidate to be inside quotes
67
+ let isQuoteBeforeCandidate = QUOTES . some ( ( quote ) =>
68
+ currentLineBeforeCandidate . includes ( quote ) ,
69
+ )
70
+ let isQuoteAfterCandidate = QUOTES . some ( ( quote ) =>
71
+ currentLineAfterCandidate . includes ( quote ) ,
72
+ )
61
73
if ( ! isQuoteBeforeCandidate || ! isQuoteAfterCandidate ) {
62
- continue
74
+ continue nextCandidate
75
+ }
76
+
77
+ // Heuristics 2: Disallow object access immediately following the candidate
78
+ if ( currentLineAfterCandidate [ 0 ] === '.' ) {
79
+ continue nextCandidate
80
+ }
81
+
82
+ // Heuristics 3: Disallow logical operators proceeding or following the candidate
83
+ for ( let operator of LOGICAL_OPERATORS ) {
84
+ if (
85
+ currentLineAfterCandidate . trim ( ) . startsWith ( operator ) ||
86
+ currentLineBeforeCandidate . trim ( ) . endsWith ( operator )
87
+ ) {
88
+ continue nextCandidate
89
+ }
90
+ }
91
+
92
+ // Heuristics 4: Disallow conditional template syntax
93
+ for ( let rule of CONDITIONAL_TEMPLATE_SYNTAX ) {
94
+ if ( rule . test ( currentLineBeforeCandidate ) ) {
95
+ continue nextCandidate
96
+ }
63
97
}
64
98
}
65
99
@@ -72,14 +106,3 @@ export function important(
72
106
73
107
return rawCandidate
74
108
}
75
-
76
- function isQuote ( char : string ) {
77
- switch ( char ) {
78
- case '"' :
79
- case "'" :
80
- case '`' :
81
- return true
82
- default :
83
- return false
84
- }
85
- }
0 commit comments