Skip to content

Commit db15235

Browse files
committed
extract is safe migration
1 parent aab2385 commit db15235

File tree

2 files changed

+70
-68
lines changed

2 files changed

+70
-68
lines changed

packages/@tailwindcss-upgrade/src/template/codemods/important.ts

Lines changed: 8 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,7 @@ import type { Config } from 'tailwindcss'
22
import { parseCandidate } from '../../../../tailwindcss/src/candidate'
33
import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
44
import { printCandidate } from '../candidates'
5-
6-
const QUOTES = ['"', "'", '`']
7-
const LOGICAL_OPERATORS = ['&&', '||', '===', '==', '!=', '!==', '>', '>=', '<', '<=']
8-
const CONDITIONAL_TEMPLATE_SYNTAX = [
9-
// Vue
10-
/v-else-if=['"]$/,
11-
/v-if=['"]$/,
12-
/v-show=['"]$/,
13-
14-
// Alpine
15-
/x-if=['"]$/,
16-
/x-show=['"]$/,
17-
]
5+
import { isSafeMigration } from '../is-safe-migration'
186

197
// In v3 the important modifier `!` sits in front of the utility itself, not
208
// before any of the variants. In v4, we want it to be at the end of the utility
@@ -46,63 +34,15 @@ export function important(
4634
// with v3 in that it can read `!` in the front of the utility too, we err
4735
// on the side of caution and only migrate candidates that we are certain
4836
// are inside of a string.
49-
if (location) {
50-
let currentLineBeforeCandidate = ''
51-
for (let i = location.start - 1; i >= 0; i--) {
52-
let char = location.contents.at(i)!
53-
if (char === '\n') {
54-
break
55-
}
56-
currentLineBeforeCandidate = char + currentLineBeforeCandidate
57-
}
58-
let currentLineAfterCandidate = ''
59-
for (let i = location.end; i < location.contents.length; i++) {
60-
let char = location.contents.at(i)!
61-
if (char === '\n') {
62-
break
63-
}
64-
currentLineAfterCandidate += char
65-
}
66-
67-
// Heuristic 1: Require the candidate to be inside quotes
68-
let isQuoteBeforeCandidate = QUOTES.some((quote) =>
69-
currentLineBeforeCandidate.includes(quote),
70-
)
71-
let isQuoteAfterCandidate = QUOTES.some((quote) =>
72-
currentLineAfterCandidate.includes(quote),
73-
)
74-
if (!isQuoteBeforeCandidate || !isQuoteAfterCandidate) {
75-
continue nextCandidate
76-
}
77-
78-
// Heuristic 2: Disallow object access immediately following the candidate
79-
if (currentLineAfterCandidate[0] === '.') {
80-
continue nextCandidate
81-
}
82-
83-
// Heuristic 3: Disallow logical operators preceding or following the candidate
84-
for (let operator of LOGICAL_OPERATORS) {
85-
if (
86-
currentLineAfterCandidate.trim().startsWith(operator) ||
87-
currentLineBeforeCandidate.trim().endsWith(operator)
88-
) {
89-
continue nextCandidate
90-
}
91-
}
92-
93-
// Heuristic 4: Disallow conditional template syntax
94-
for (let rule of CONDITIONAL_TEMPLATE_SYNTAX) {
95-
if (rule.test(currentLineBeforeCandidate)) {
96-
continue nextCandidate
97-
}
98-
}
37+
if (location && !isSafeMigration(location)) {
38+
continue nextCandidate
9939
}
100-
101-
// The printCandidate function will already put the exclamation mark in
102-
// the right place, so we just need to mark this candidate as requiring a
103-
// migration.
104-
return printCandidate(designSystem, candidate)
10540
}
41+
42+
// The printCandidate function will already put the exclamation mark in the
43+
// right place, so we just need to mark this candidate as requiring a
44+
// migration.
45+
return printCandidate(designSystem, candidate)
10646
}
10747

10848
return rawCandidate
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
const QUOTES = ['"', "'", '`']
2+
const LOGICAL_OPERATORS = ['&&', '||', '===', '==', '!=', '!==', '>', '>=', '<', '<=']
3+
const CONDITIONAL_TEMPLATE_SYNTAX = [
4+
// Vue
5+
/v-else-if=['"]$/,
6+
/v-if=['"]$/,
7+
/v-show=['"]$/,
8+
9+
// Alpine
10+
/x-if=['"]$/,
11+
/x-show=['"]$/,
12+
]
13+
14+
export function isSafeMigration(location: { contents: string; start: number; end: number }) {
15+
let currentLineBeforeCandidate = ''
16+
for (let i = location.start - 1; i >= 0; i--) {
17+
let char = location.contents.at(i)!
18+
if (char === '\n') {
19+
break
20+
}
21+
currentLineBeforeCandidate = char + currentLineBeforeCandidate
22+
}
23+
let currentLineAfterCandidate = ''
24+
for (let i = location.end; i < location.contents.length; i++) {
25+
let char = location.contents.at(i)!
26+
if (char === '\n') {
27+
break
28+
}
29+
currentLineAfterCandidate += char
30+
}
31+
32+
// Heuristic 1: Require the candidate to be inside quotes
33+
let isQuoteBeforeCandidate = QUOTES.some((quote) => currentLineBeforeCandidate.includes(quote))
34+
let isQuoteAfterCandidate = QUOTES.some((quote) => currentLineAfterCandidate.includes(quote))
35+
if (!isQuoteBeforeCandidate || !isQuoteAfterCandidate) {
36+
return false
37+
}
38+
39+
// Heuristic 2: Disallow object access immediately following the candidate
40+
if (currentLineAfterCandidate[0] === '.') {
41+
return false
42+
}
43+
44+
// Heuristic 3: Disallow logical operators preceding or following the candidate
45+
for (let operator of LOGICAL_OPERATORS) {
46+
if (
47+
currentLineAfterCandidate.trim().startsWith(operator) ||
48+
currentLineBeforeCandidate.trim().endsWith(operator)
49+
) {
50+
return false
51+
}
52+
}
53+
54+
// Heuristic 4: Disallow conditional template syntax
55+
for (let rule of CONDITIONAL_TEMPLATE_SYNTAX) {
56+
if (rule.test(currentLineBeforeCandidate)) {
57+
return false
58+
}
59+
}
60+
61+
return true
62+
}

0 commit comments

Comments
 (0)