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