mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Upgrade: Do not extract class names from functions (#15566)
This PR prevents the migration of utilities detected in function names,
e.g.: the use of `shadow` inside `filter: 'drop-shadow(…)'`.
## Test plan
Have content like this in a project you're migrating using the upgrade
tool:
```js
{
filter: 'drop-shadow(0 0 0.5rem #000)'
}
```
This was verified by adding unit tests to the specific codemods and
adding an integration test.
This commit is contained in:
parent
c84acf8e82
commit
d4f693f7f3
@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Fix `inset-shadow-*` suggestions in IntelliSense ([#15471](https://github.com/tailwindlabs/tailwindcss/pull/15471))
|
||||
- Only compile arbitrary values ending in `]` ([#15503](https://github.com/tailwindlabs/tailwindcss/pull/15503))
|
||||
- Improve performance and memory usage ([#15529](https://github.com/tailwindlabs/tailwindcss/pull/15529))
|
||||
- _Upgrade (experimental)_: Do not extract class names from functions (e.g. `shadow` in `filter: 'drop-shadow(…)'`) ([#15566](https://github.com/tailwindlabs/tailwindcss/pull/15566))
|
||||
|
||||
### Changed
|
||||
|
||||
@ -782,4 +783,3 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
## [4.0.0-alpha.1] - 2024-03-06
|
||||
|
||||
- First 4.0.0-alpha.1 release
|
||||
|
||||
|
||||
@ -137,6 +137,7 @@ test(
|
||||
'src/test.js': ts`
|
||||
export default {
|
||||
'shouldNotMigrate': !border.test + '',
|
||||
'filter': 'drop-shadow(0 0 0.5rem #000)',
|
||||
}
|
||||
`,
|
||||
'src/index.html': html`
|
||||
@ -288,6 +289,7 @@ test(
|
||||
--- src/test.js ---
|
||||
export default {
|
||||
'shouldNotMigrate': !border.test + '',
|
||||
'filter': 'drop-shadow(0 0 0.5rem #000)',
|
||||
}
|
||||
"
|
||||
`)
|
||||
|
||||
@ -38,31 +38,31 @@ test('does not match false positives', async () => {
|
||||
).toEqual('!border')
|
||||
})
|
||||
|
||||
test('does not match false positives', async () => {
|
||||
test('does not replace classes in invalid positions', async () => {
|
||||
let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', {
|
||||
base: __dirname,
|
||||
})
|
||||
|
||||
function shouldNotDetect(example: string, candidate = '!border') {
|
||||
function shouldNotReplace(example: string, candidate = '!border') {
|
||||
expect(
|
||||
important(designSystem, {}, candidate, {
|
||||
contents: example,
|
||||
start: example.indexOf(candidate),
|
||||
end: example.indexOf(candidate) + candidate.length,
|
||||
}),
|
||||
).toEqual('!border')
|
||||
).toEqual(candidate)
|
||||
}
|
||||
|
||||
shouldNotDetect(`let notBorder = !border \n`)
|
||||
shouldNotDetect(`{ "foo": !border.something + ""}\n`)
|
||||
shouldNotDetect(`<div v-if="something && !border"></div>\n`)
|
||||
shouldNotDetect(`<div v-else-if="something && !border"></div>\n`)
|
||||
shouldNotDetect(`<div v-show="something && !border"></div>\n`)
|
||||
shouldNotDetect(`<div v-if="!border || !border"></div>\n`)
|
||||
shouldNotDetect(`<div v-else-if="!border || !border"></div>\n`)
|
||||
shouldNotDetect(`<div v-show="!border || !border"></div>\n`)
|
||||
shouldNotDetect(`<div v-if="!border"></div>\n`)
|
||||
shouldNotDetect(`<div v-else-if="!border"></div>\n`)
|
||||
shouldNotDetect(`<div v-show="!border"></div>\n`)
|
||||
shouldNotDetect(`<div x-if="!border"></div>\n`)
|
||||
shouldNotReplace(`let notBorder = !border \n`)
|
||||
shouldNotReplace(`{ "foo": !border.something + ""}\n`)
|
||||
shouldNotReplace(`<div v-if="something && !border"></div>\n`)
|
||||
shouldNotReplace(`<div v-else-if="something && !border"></div>\n`)
|
||||
shouldNotReplace(`<div v-show="something && !border"></div>\n`)
|
||||
shouldNotReplace(`<div v-if="!border || !border"></div>\n`)
|
||||
shouldNotReplace(`<div v-else-if="!border || !border"></div>\n`)
|
||||
shouldNotReplace(`<div v-show="!border || !border"></div>\n`)
|
||||
shouldNotReplace(`<div v-if="!border"></div>\n`)
|
||||
shouldNotReplace(`<div v-else-if="!border"></div>\n`)
|
||||
shouldNotReplace(`<div v-show="!border"></div>\n`)
|
||||
shouldNotReplace(`<div x-if="!border"></div>\n`)
|
||||
})
|
||||
|
||||
@ -39,3 +39,33 @@ test.each([
|
||||
|
||||
expect(await legacyClasses(designSystem, {}, candidate)).toEqual(result)
|
||||
})
|
||||
|
||||
test('does not replace classes in invalid positions', async () => {
|
||||
let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', {
|
||||
base: __dirname,
|
||||
})
|
||||
|
||||
async function shouldNotReplace(example: string, candidate = 'shadow') {
|
||||
expect(
|
||||
await legacyClasses(designSystem, {}, candidate, {
|
||||
contents: example,
|
||||
start: example.indexOf(candidate),
|
||||
end: example.indexOf(candidate) + candidate.length,
|
||||
}),
|
||||
).toEqual(candidate)
|
||||
}
|
||||
|
||||
await shouldNotReplace(`let notShadow = shadow \n`)
|
||||
await shouldNotReplace(`{ "foo": shadow.something + ""}\n`)
|
||||
await shouldNotReplace(`<div v-if="something && shadow"></div>\n`)
|
||||
await shouldNotReplace(`<div v-else-if="something && shadow"></div>\n`)
|
||||
await shouldNotReplace(`<div v-show="something && shadow"></div>\n`)
|
||||
await shouldNotReplace(`<div v-if="shadow || shadow"></div>\n`)
|
||||
await shouldNotReplace(`<div v-else-if="shadow || shadow"></div>\n`)
|
||||
await shouldNotReplace(`<div v-show="shadow || shadow"></div>\n`)
|
||||
await shouldNotReplace(`<div v-if="shadow"></div>\n`)
|
||||
await shouldNotReplace(`<div v-else-if="shadow"></div>\n`)
|
||||
await shouldNotReplace(`<div v-show="shadow"></div>\n`)
|
||||
await shouldNotReplace(`<div x-if="shadow"></div>\n`)
|
||||
await shouldNotReplace(`<div style={{filter: 'drop-shadow(30px 10px 4px #4444dd)'}}/>\n`)
|
||||
})
|
||||
|
||||
@ -29,19 +29,24 @@ export function isSafeMigration(location: { contents: string; start: number; end
|
||||
currentLineAfterCandidate += char
|
||||
}
|
||||
|
||||
// Heuristic 1: Require the candidate to be inside quotes
|
||||
// Heuristic: 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
|
||||
// Heuristic: Disallow object access immediately following the candidate
|
||||
if (currentLineAfterCandidate[0] === '.') {
|
||||
return false
|
||||
}
|
||||
|
||||
// Heuristic 3: Disallow logical operators preceding or following the candidate
|
||||
// Heuristic: Disallow function call expressions immediately following the candidate
|
||||
if (currentLineAfterCandidate.trim().startsWith('(')) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Heuristic: Disallow logical operators preceding or following the candidate
|
||||
for (let operator of LOGICAL_OPERATORS) {
|
||||
if (
|
||||
currentLineAfterCandidate.trim().startsWith(operator) ||
|
||||
@ -51,7 +56,7 @@ export function isSafeMigration(location: { contents: string; start: number; end
|
||||
}
|
||||
}
|
||||
|
||||
// Heuristic 4: Disallow conditional template syntax
|
||||
// Heuristic: Disallow conditional template syntax
|
||||
for (let rule of CONDITIONAL_TEMPLATE_SYNTAX) {
|
||||
if (rule.test(currentLineBeforeCandidate)) {
|
||||
return false
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user