-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Ensure it's safe to perform suffix-less migrations (#14979)
This PR makes sure that migrations from suffix-less candidates (e.g.: `rounded`, `blur`, `shadow`) are safe to be migrated. In some code snippets that's not always the case. Given the following code snippet: ```tsx type Star = [ x: number, y: number, dim?: boolean, blur?: boolean, rounded?: boolean, shadow?: boolean, ] function Star({ point: [cx, cy, dim, blur, rounded, shadow] }: { point: Star }) { return <svg class="rounded shadow blur" filter={blur ? 'url(…)' : undefined} /> } ``` Without this change, it would result in: ```tsx type Star = [ x: number, y: number, dim?: boolean, blur-sm?: boolean, rounded-sm?: boolean, shadow-sm?: boolean, ] function Star({ point: [cx, cy, dim, blur-sm, rounded-sm, shadow-sm] }: { point: Star }) { return <svg class="rounded-sm shadow-sm blur-sm" filter={blur-sm ? 'url(…)' : undefined} /> } ``` But with this change, it results in: ```tsx type Star = [ x: number, y: number, dim?: boolean, blur?: boolean, rounded?: boolean, shadow?: boolean, ] function Star({ point: [cx, cy, dim, blur, rounded, shadow] }: { point: Star }) { return <svg class="rounded-sm shadow-sm blur-sm" filter={blur ? 'url(…)' : undefined} /> } ``` Notice how the classes inside the `class` attribute _are_ converted, but the ones in the types or as part of the JavaScript code (e.g.: `filter={blur ? 'url(…)' : undefined}`) are not. --------- Co-authored-by: Philipp Spiess <[email protected]>
- Loading branch information
1 parent
ac11740
commit c63f01b
Showing
5 changed files
with
173 additions
and
71 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
62 changes: 62 additions & 0 deletions
62
packages/@tailwindcss-upgrade/src/template/is-safe-migration.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
const QUOTES = ['"', "'", '`'] | ||
const LOGICAL_OPERATORS = ['&&', '||', '?', '===', '==', '!=', '!==', '>', '>=', '<', '<='] | ||
const CONDITIONAL_TEMPLATE_SYNTAX = [ | ||
// Vue | ||
/v-else-if=['"]$/, | ||
/v-if=['"]$/, | ||
/v-show=['"]$/, | ||
|
||
// Alpine | ||
/x-if=['"]$/, | ||
/x-show=['"]$/, | ||
] | ||
|
||
export function isSafeMigration(location: { contents: string; start: number; end: number }) { | ||
let currentLineBeforeCandidate = '' | ||
for (let i = location.start - 1; i >= 0; i--) { | ||
let char = location.contents.at(i)! | ||
if (char === '\n') { | ||
break | ||
} | ||
currentLineBeforeCandidate = char + currentLineBeforeCandidate | ||
} | ||
let currentLineAfterCandidate = '' | ||
for (let i = location.end; i < location.contents.length; i++) { | ||
let char = location.contents.at(i)! | ||
if (char === '\n') { | ||
break | ||
} | ||
currentLineAfterCandidate += char | ||
} | ||
|
||
// Heuristic 1: Require the candidate to be inside quotes | ||
let isQuoteBeforeCandidate = QUOTES.some((quote) => currentLineBeforeCandidate.includes(quote)) | ||
let isQuoteAfterCandidate = QUOTES.some((quote) => currentLineAfterCandidate.includes(quote)) | ||
if (!isQuoteBeforeCandidate || !isQuoteAfterCandidate) { | ||
return false | ||
} | ||
|
||
// Heuristic 2: Disallow object access immediately following the candidate | ||
if (currentLineAfterCandidate[0] === '.') { | ||
return false | ||
} | ||
|
||
// Heuristic 3: Disallow logical operators preceding or following the candidate | ||
for (let operator of LOGICAL_OPERATORS) { | ||
if ( | ||
currentLineAfterCandidate.trim().startsWith(operator) || | ||
currentLineBeforeCandidate.trim().endsWith(operator) | ||
) { | ||
return false | ||
} | ||
} | ||
|
||
// Heuristic 4: Disallow conditional template syntax | ||
for (let rule of CONDITIONAL_TEMPLATE_SYNTAX) { | ||
if (rule.test(currentLineBeforeCandidate)) { | ||
return false | ||
} | ||
} | ||
|
||
return true | ||
} |