From 19b7123af99492fe19c32f1a56ac91cbedf84109 Mon Sep 17 00:00:00 2001
From: Philipp Spiess
Date: Tue, 7 Jan 2025 13:44:14 +0100
Subject: [PATCH] =?UTF-8?q?Upgrade:=20Do=20not=20extract=20class=20names?=
=?UTF-8?q?=20from=20functions=20(e.g.=20`shadow`=20in=20`filter:=20'drop-?=
=?UTF-8?q?shadow(=E2=80=A6)'`)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
CHANGELOG.md | 2 +-
integrations/upgrade/js-config.test.ts | 2 ++
.../src/template/codemods/important.test.ts | 30 +++++++++----------
.../template/codemods/legacy-classes.test.ts | 30 +++++++++++++++++++
.../src/template/is-safe-migration.ts | 13 +++++---
5 files changed, 57 insertions(+), 20 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 261e67188b24..85901f90c98b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
-
diff --git a/integrations/upgrade/js-config.test.ts b/integrations/upgrade/js-config.test.ts
index f2643143a124..e9ad772f6803 100644
--- a/integrations/upgrade/js-config.test.ts
+++ b/integrations/upgrade/js-config.test.ts
@@ -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)',
}
"
`)
diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/important.test.ts b/packages/@tailwindcss-upgrade/src/template/codemods/important.test.ts
index 60d78aa53675..6667d1e3ac47 100644
--- a/packages/@tailwindcss-upgrade/src/template/codemods/important.test.ts
+++ b/packages/@tailwindcss-upgrade/src/template/codemods/important.test.ts
@@ -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(`\n`)
- shouldNotDetect(`\n`)
- shouldNotDetect(`\n`)
- shouldNotDetect(`\n`)
- shouldNotDetect(`\n`)
- shouldNotDetect(`\n`)
- shouldNotDetect(`\n`)
- shouldNotDetect(`\n`)
- shouldNotDetect(`\n`)
- shouldNotDetect(`\n`)
+ shouldNotReplace(`let notBorder = !border \n`)
+ shouldNotReplace(`{ "foo": !border.something + ""}\n`)
+ shouldNotReplace(`\n`)
+ shouldNotReplace(`\n`)
+ shouldNotReplace(`\n`)
+ shouldNotReplace(`\n`)
+ shouldNotReplace(`\n`)
+ shouldNotReplace(`\n`)
+ shouldNotReplace(`\n`)
+ shouldNotReplace(`\n`)
+ shouldNotReplace(`\n`)
+ shouldNotReplace(`\n`)
})
diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/legacy-classes.test.ts b/packages/@tailwindcss-upgrade/src/template/codemods/legacy-classes.test.ts
index 5d29c1b71b6b..f301deaa9dde 100644
--- a/packages/@tailwindcss-upgrade/src/template/codemods/legacy-classes.test.ts
+++ b/packages/@tailwindcss-upgrade/src/template/codemods/legacy-classes.test.ts
@@ -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(`\n`)
+ await shouldNotReplace(`\n`)
+ await shouldNotReplace(`\n`)
+ await shouldNotReplace(`\n`)
+ await shouldNotReplace(`\n`)
+ await shouldNotReplace(`\n`)
+ await shouldNotReplace(`\n`)
+ await shouldNotReplace(`\n`)
+ await shouldNotReplace(`\n`)
+ await shouldNotReplace(`\n`)
+ await shouldNotReplace(`\n`)
+})
diff --git a/packages/@tailwindcss-upgrade/src/template/is-safe-migration.ts b/packages/@tailwindcss-upgrade/src/template/is-safe-migration.ts
index 046ee10e6df4..e0408d3e57b8 100644
--- a/packages/@tailwindcss-upgrade/src/template/is-safe-migration.ts
+++ b/packages/@tailwindcss-upgrade/src/template/is-safe-migration.ts
@@ -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