From f92261f44fb99f39e04ad5502abfc2cd32fca62d Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 10 Oct 2024 22:14:16 +0200 Subject: [PATCH 1/6] use extension We were passing through the extension, but weren't using it. --- packages/@tailwindcss-upgrade/src/template/candidates.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/@tailwindcss-upgrade/src/template/candidates.ts b/packages/@tailwindcss-upgrade/src/template/candidates.ts index bc422d188781..be08477ee205 100644 --- a/packages/@tailwindcss-upgrade/src/template/candidates.ts +++ b/packages/@tailwindcss-upgrade/src/template/candidates.ts @@ -5,9 +5,10 @@ import type { DesignSystem } from '../../../tailwindcss/src/design-system' export async function extractRawCandidates( content: string, + extension: string = 'html', ): Promise<{ rawCandidate: string; start: number; end: number }[]> { let scanner = new Scanner({}) - let result = scanner.getCandidatesWithPositions({ content, extension: 'html' }) + let result = scanner.getCandidatesWithPositions({ content, extension }) let candidates: { rawCandidate: string; start: number; end: number }[] = [] for (let { candidate: rawCandidate, position: start } of result) { From 8a0972612f07cc6982186560dc4a5a7fccf2d2b0 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 10 Oct 2024 22:37:18 +0200 Subject: [PATCH 2/6] add migration for simple legacy classes --- .../codemods/simple-legacy-classes.test.ts | 22 +++++++++++ .../codemods/simple-legacy-classes.ts | 39 +++++++++++++++++++ .../src/template/migrate.ts | 2 + 3 files changed, 63 insertions(+) create mode 100644 packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.test.ts create mode 100644 packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.ts diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.test.ts b/packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.test.ts new file mode 100644 index 000000000000..d0a89da58b32 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.test.ts @@ -0,0 +1,22 @@ +import { __unstable__loadDesignSystem } from '@tailwindcss/node' +import { expect, test } from 'vitest' +import { simpleLegacyClasses } from './simple-legacy-classes' + +test.each([ + ['overflow-clip', 'text-clip'], + ['overflow-ellipsis', 'text-ellipsis'], + ['flex-grow-0', 'grow-0'], + ['flex-shrink-0', 'shrink-0'], + ['decoration-clone', 'box-decoration-clone'], + ['decoration-slice', 'box-decoration-slice'], + + ['max-lg:hover:decoration-slice', 'max-lg:hover:box-decoration-slice'], + ['max-lg:hover:decoration-slice!', 'max-lg:hover:box-decoration-slice!'], + ['max-lg:hover:!decoration-slice', 'max-lg:hover:box-decoration-slice!'], +])('%s => %s', async (candidate, result) => { + let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', { + base: __dirname, + }) + + expect(simpleLegacyClasses(designSystem, {}, candidate)).toEqual(result) +}) diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.ts b/packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.ts new file mode 100644 index 000000000000..1131ee57aa3d --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.ts @@ -0,0 +1,39 @@ +import type { Config } from 'tailwindcss' +import type { DesignSystem } from '../../../../tailwindcss/src/design-system' +import { segment } from '../../../../tailwindcss/src/utils/segment' + +// Classes that used to exist in Tailwind CSS v3, but do not exist in Tailwind +// CSS v4 anymore. +const LEGACY_CLASS_MAP = { + 'overflow-clip': 'text-clip', + 'overflow-ellipsis': 'text-ellipsis', + 'flex-grow-0': 'grow-0', + 'flex-shrink-0': 'shrink-0', + 'decoration-clone': 'box-decoration-clone', + 'decoration-slice': 'box-decoration-slice', +} + +export function simpleLegacyClasses( + _designSystem: DesignSystem, + _userConfig: Config, + rawCandidate: string, +): string { + let variants = segment(rawCandidate, ':') + let utility = variants.pop()! + + let important = false + if (utility[0] === '!') { + important = true + utility = utility.slice(1) + } else if (utility[utility?.length - 1] === '!') { + important = true + utility = utility.slice(0, -1) + } + + if (utility in LEGACY_CLASS_MAP) { + let replacement = LEGACY_CLASS_MAP[utility as keyof typeof LEGACY_CLASS_MAP] + return `${variants.concat(replacement).join(':')}${important ? '!' : ''}` + } + + return rawCandidate +} diff --git a/packages/@tailwindcss-upgrade/src/template/migrate.ts b/packages/@tailwindcss-upgrade/src/template/migrate.ts index 5fd710d4fdaa..df9d22b15af8 100644 --- a/packages/@tailwindcss-upgrade/src/template/migrate.ts +++ b/packages/@tailwindcss-upgrade/src/template/migrate.ts @@ -7,6 +7,7 @@ import { automaticVarInjection } from './codemods/automatic-var-injection' import { bgGradient } from './codemods/bg-gradient' import { important } from './codemods/important' import { prefix } from './codemods/prefix' +import { simpleLegacyClasses } from './codemods/simple-legacy-classes' import { variantOrder } from './codemods/variant-order' export type Migration = ( @@ -20,6 +21,7 @@ export const DEFAULT_MIGRATIONS: Migration[] = [ important, automaticVarInjection, bgGradient, + simpleLegacyClasses, variantOrder, ] From 696b6b582791c94f98f18e0920ef8087449db77f Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 10 Oct 2024 22:48:06 +0200 Subject: [PATCH 3/6] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b5434b0467c..88314a4ffc0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Support `keyframes` in JS config file themes ([#14594](https://github.com/tailwindlabs/tailwindcss/pull/14594)) - _Upgrade (experimental)_: Migrate v3 PostCSS setups to v4 in some cases ([#14612](https://github.com/tailwindlabs/tailwindcss/pull/14612)) - _Upgrade (experimental)_: The upgrade tool now automatically discovers your JavaScript config ([#14597](https://github.com/tailwindlabs/tailwindcss/pull/14597)) +- _Upgrade (experimental)_: Migrate legacy classes to the v4 alternative ([#14643](https://github.com/tailwindlabs/tailwindcss/pull/14643)) ### Fixed From 8587c66d5037bc01daf40d62457030fdb8c308fa Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 11 Oct 2024 01:01:58 +0200 Subject: [PATCH 4/6] only look directly in the object with the `in`, we go up the prototype chain. Which means that this happens: ```js let utility = 'constructor' utility in LEGACY_CLASS_MAP // true LEGACY_CLASS_MAP[utility].toString() // function Object() { [native code] } ``` --- .../src/template/codemods/simple-legacy-classes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.ts b/packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.ts index 1131ee57aa3d..e8ca0efadeff 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.ts @@ -30,7 +30,7 @@ export function simpleLegacyClasses( utility = utility.slice(0, -1) } - if (utility in LEGACY_CLASS_MAP) { + if (Object.hasOwn(LEGACY_CLASS_MAP, utility)) { let replacement = LEGACY_CLASS_MAP[utility as keyof typeof LEGACY_CLASS_MAP] return `${variants.concat(replacement).join(':')}${important ? '!' : ''}` } From 065aabc87c825218ec44d5c230e8dafdf3440992 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 11 Oct 2024 01:13:00 +0200 Subject: [PATCH 5/6] rely on real parser We will register empty utilities for the old legacy classes, this way we can rely on the design system and the real parser to get an Candidate AST out of the rawCandidate. --- .../codemods/simple-legacy-classes.ts | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.ts b/packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.ts index e8ca0efadeff..a2744a15245e 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.ts @@ -1,6 +1,6 @@ import type { Config } from 'tailwindcss' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' -import { segment } from '../../../../tailwindcss/src/utils/segment' +import { printCandidate } from '../candidates' // Classes that used to exist in Tailwind CSS v3, but do not exist in Tailwind // CSS v4 anymore. @@ -13,26 +13,28 @@ const LEGACY_CLASS_MAP = { 'decoration-slice': 'box-decoration-slice', } +const SEEDED = new WeakMap() + export function simpleLegacyClasses( - _designSystem: DesignSystem, + designSystem: DesignSystem, _userConfig: Config, rawCandidate: string, ): string { - let variants = segment(rawCandidate, ':') - let utility = variants.pop()! - - let important = false - if (utility[0] === '!') { - important = true - utility = utility.slice(1) - } else if (utility[utility?.length - 1] === '!') { - important = true - utility = utility.slice(0, -1) + // Prepare design system with the legacy classes + if (!SEEDED.has(designSystem)) { + for (let old in LEGACY_CLASS_MAP) { + designSystem.utilities.static(old, () => []) + } + SEEDED.set(designSystem, true) } - if (Object.hasOwn(LEGACY_CLASS_MAP, utility)) { - let replacement = LEGACY_CLASS_MAP[utility as keyof typeof LEGACY_CLASS_MAP] - return `${variants.concat(replacement).join(':')}${important ? '!' : ''}` + for (let candidate of designSystem.parseCandidate(rawCandidate)) { + if (candidate.kind === 'static' && Object.hasOwn(LEGACY_CLASS_MAP, candidate.root)) { + return printCandidate(designSystem, { + ...candidate, + root: LEGACY_CLASS_MAP[candidate.root as keyof typeof LEGACY_CLASS_MAP], + }) + } } return rawCandidate From bf7e85023e6f77b49713ceb0c83f4012c910f18c Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 11 Oct 2024 01:19:23 +0200 Subject: [PATCH 6/6] use a `WeakSet` --- .../src/template/codemods/simple-legacy-classes.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.ts b/packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.ts index a2744a15245e..94a4121f8f76 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.ts @@ -13,7 +13,7 @@ const LEGACY_CLASS_MAP = { 'decoration-slice': 'box-decoration-slice', } -const SEEDED = new WeakMap() +const SEEDED = new WeakSet() export function simpleLegacyClasses( designSystem: DesignSystem, @@ -25,7 +25,7 @@ export function simpleLegacyClasses( for (let old in LEGACY_CLASS_MAP) { designSystem.utilities.static(old, () => []) } - SEEDED.set(designSystem, true) + SEEDED.add(designSystem) } for (let candidate of designSystem.parseCandidate(rawCandidate)) {