diff --git a/packages/@tailwindcss-upgrade/src/mock-design-system.ts b/packages/@tailwindcss-upgrade/src/mock-design-system.ts new file mode 100644 index 000000000000..299cbd659935 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/mock-design-system.ts @@ -0,0 +1,84 @@ +import { + parseCandidate, + parseVariant, + type Candidate, + type Variant, +} from '../../tailwindcss/src/candidate' +import type { DesignSystem } from '../../tailwindcss/src/design-system' +import { convertUnderscoresToWhitespace } from '../../tailwindcss/src/utils/decode-arbitrary-value' +import * as ValueParser from '../../tailwindcss/src/value-parser' + +export function mockDesignSystem(designSystem: DesignSystem): DesignSystem { + // Custom `parseCandidate` implementation that does two things: + // 1. Has custom `decodeArbitraryValue` that does not add whitespace around + // operators. + // 2. Does not cache the parsing of candidates + designSystem.parseCandidate = (candidate) => { + return Array.from(parseCandidate(candidate, designSystem, { decodeArbitraryValue })).map( + (candidate) => { + // We inject `&:is(…)` into arbitrary variants `[p]` becomes `[&:is(p)]`. + // + // In this case, for the migrations we don't care about this outer + // `&:is(…)` so we replace it with the contents of the `&:is(…)` instead. + // + // We could have a false positive here _if_ the user added the `&:is()` + // themselves. But if it's not there then we would inject it so the + // behavior should be the exact same. + for (let variant of variants(candidate)) { + if (variant.kind === 'arbitrary') { + let ast = ValueParser.parse(variant.selector) + + // &:is(…) + if ( + ast.length === 3 && + // & + ast[0].kind === 'word' && + ast[0].value === '&' && + // : + ast[1].kind === 'separator' && + ast[1].value === ':' && + // is(…) + ast[2].kind === 'function' && + ast[2].value === 'is' + ) { + variant.selector = ValueParser.toCss(ast[2].nodes) + } + } + } + + return candidate + }, + ) + } + + designSystem.parseVariant = (variant) => { + return parseVariant(variant, designSystem, { decodeArbitraryValue }) + } + + return designSystem +} + +function decodeArbitraryValue(input: string) { + // We do not want to normalize anything inside of a url() because if we + // replace `_` with ` `, then it will very likely break the url. + if (input.startsWith('url(')) { + return input + } + + input = convertUnderscoresToWhitespace(input) + + return input +} + +function* variants(candidate: Candidate) { + function* inner(variant: Variant): Iterable { + yield variant + if (variant.kind === 'compound') { + yield* inner(variant.variant) + } + } + + for (let variant of candidate.variants) { + yield* inner(variant) + } +} diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/arbitrary-value-to-bare-value.test.ts b/packages/@tailwindcss-upgrade/src/template/codemods/arbitrary-value-to-bare-value.test.ts index 411f0eb9ee3a..310c550e6505 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/arbitrary-value-to-bare-value.test.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/arbitrary-value-to-bare-value.test.ts @@ -1,5 +1,6 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' import { expect, test } from 'vitest' +import { mockDesignSystem } from '../../mock-design-system' import { arbitraryValueToBareValue } from './arbitrary-value-to-bare-value' test.each([ @@ -45,9 +46,11 @@ test.each([ 'data-selected:aria-selected:aspect-12/34', ], ])('%s => %s', async (candidate, result) => { - let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', { - base: __dirname, - }) + let designSystem = mockDesignSystem( + await __unstable__loadDesignSystem('@import "tailwindcss";', { + base: __dirname, + }), + ) expect(arbitraryValueToBareValue(designSystem, {}, candidate)).toEqual(result) }) diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/arbitrary-value-to-bare-value.ts b/packages/@tailwindcss-upgrade/src/template/codemods/arbitrary-value-to-bare-value.ts index 44aa2675db83..a69a0760cf4c 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/arbitrary-value-to-bare-value.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/arbitrary-value-to-bare-value.ts @@ -1,5 +1,5 @@ import type { Config } from 'tailwindcss' -import { parseCandidate, type Candidate, type Variant } from '../../../../tailwindcss/src/candidate' +import { type Candidate, type Variant } from '../../../../tailwindcss/src/candidate' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' import { isPositiveInteger } from '../../../../tailwindcss/src/utils/infer-data-type' import { segment } from '../../../../tailwindcss/src/utils/segment' @@ -10,24 +10,26 @@ export function arbitraryValueToBareValue( _userConfig: Config, rawCandidate: string, ): string { - for (let candidate of parseCandidate(rawCandidate, designSystem)) { - let clone = structuredClone(candidate) + for (let candidate of designSystem.parseCandidate(rawCandidate) as Candidate[]) { let changed = false // Convert font-stretch-* utilities if ( - clone.kind === 'functional' && - clone.value?.kind === 'arbitrary' && - clone.value.dataType === null && - clone.root === 'font-stretch' + candidate.kind === 'functional' && + candidate.value?.kind === 'arbitrary' && + candidate.value.dataType === null && + candidate.root === 'font-stretch' ) { - if (clone.value.value.endsWith('%') && isPositiveInteger(clone.value.value.slice(0, -1))) { - let percentage = parseInt(clone.value.value) + if ( + candidate.value.value.endsWith('%') && + isPositiveInteger(candidate.value.value.slice(0, -1)) + ) { + let percentage = parseInt(candidate.value.value) if (percentage >= 50 && percentage <= 200) { changed = true - clone.value = { + candidate.value = { kind: 'named', - value: clone.value.value, + value: candidate.value.value, fraction: null, } } @@ -37,23 +39,23 @@ export function arbitraryValueToBareValue( // Convert arbitrary values with positive integers to bare values // Convert arbitrary values with fractions to bare values else if ( - clone.kind === 'functional' && - clone.value?.kind === 'arbitrary' && - clone.value.dataType === null + candidate.kind === 'functional' && + candidate.value?.kind === 'arbitrary' && + candidate.value.dataType === null ) { - let parts = segment(clone.value.value, '/') + let parts = segment(candidate.value.value, '/') if (parts.every((part) => isPositiveInteger(part))) { changed = true - let currentValue = clone.value - let currentModifier = clone.modifier + let currentValue = candidate.value + let currentModifier = candidate.modifier // E.g.: `col-start-[12]` // ^^ if (parts.length === 1) { - clone.value = { + candidate.value = { kind: 'named', - value: clone.value.value, + value: candidate.value.value, fraction: null, } } @@ -61,27 +63,27 @@ export function arbitraryValueToBareValue( // E.g.: `aspect-[12/34]` // ^^ ^^ else { - clone.value = { + candidate.value = { kind: 'named', value: parts[0], - fraction: clone.value.value, + fraction: candidate.value.value, } - clone.modifier = { + candidate.modifier = { kind: 'named', value: parts[1], } } // Double check that the new value compiles correctly - if (designSystem.compileAstNodes(clone).length === 0) { - clone.value = currentValue - clone.modifier = currentModifier + if (designSystem.compileAstNodes(candidate).length === 0) { + candidate.value = currentValue + candidate.modifier = currentModifier changed = false } } } - for (let variant of variants(clone)) { + for (let variant of variants(candidate)) { // Convert `data-[selected]` to `data-selected` if ( variant.kind === 'functional' && @@ -143,7 +145,7 @@ export function arbitraryValueToBareValue( } } - return changed ? printCandidate(designSystem, clone) : rawCandidate + return changed ? printCandidate(designSystem, candidate) : rawCandidate } return rawCandidate diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/automatic-var-injection.test.ts b/packages/@tailwindcss-upgrade/src/template/codemods/automatic-var-injection.test.ts index b3ddb06d338a..745dccc572b6 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/automatic-var-injection.test.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/automatic-var-injection.test.ts @@ -1,5 +1,6 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' import { expect, test } from 'vitest' +import { mockDesignSystem } from '../../mock-design-system' import { automaticVarInjection } from './automatic-var-injection' test.each([ @@ -50,9 +51,11 @@ test.each([ ['[view-timeline:--myTimeline]', '[view-timeline:--myTimeline]'], ['[position-try:--myAnchor]', '[position-try:--myAnchor]'], ])('%s => %s', async (candidate, result) => { - let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', { - base: __dirname, - }) + let designSystem = mockDesignSystem( + await __unstable__loadDesignSystem('@import "tailwindcss";', { + base: __dirname, + }), + ) let migrated = automaticVarInjection(designSystem, {}, candidate) expect(migrated).toEqual(result) diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/automatic-var-injection.ts b/packages/@tailwindcss-upgrade/src/template/codemods/automatic-var-injection.ts index 858a80fd96d3..f17989aaca3f 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/automatic-var-injection.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/automatic-var-injection.ts @@ -9,11 +9,7 @@ export function automaticVarInjection( _userConfig: Config, rawCandidate: string, ): string { - for (let readonlyCandidate of designSystem.parseCandidate(rawCandidate)) { - // The below logic makes extended use of mutation. Since candidates in the - // DesignSystem are cached, we can't mutate them directly. - let candidate = structuredClone(readonlyCandidate) as Candidate - + for (let candidate of designSystem.parseCandidate(rawCandidate) as Candidate[]) { let didChange = false // Add `var(…)` in modifier position, e.g.: diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/bg-gradient.test.ts b/packages/@tailwindcss-upgrade/src/template/codemods/bg-gradient.test.ts index 3c42903c3c93..b739fde42cf1 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/bg-gradient.test.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/bg-gradient.test.ts @@ -1,5 +1,6 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' import { expect, test } from 'vitest' +import { mockDesignSystem } from '../../mock-design-system' import { bgGradient } from './bg-gradient' test.each([ @@ -14,9 +15,11 @@ test.each([ ['max-lg:hover:bg-gradient-to-t', 'max-lg:hover:bg-linear-to-t'], ])('%s => %s', async (candidate, result) => { - let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', { - base: __dirname, - }) + let designSystem = mockDesignSystem( + await __unstable__loadDesignSystem('@import "tailwindcss";', { + base: __dirname, + }), + ) expect(bgGradient(designSystem, {}, candidate)).toEqual(result) }) diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/bg-gradient.ts b/packages/@tailwindcss-upgrade/src/template/codemods/bg-gradient.ts index d85350ccdafc..7f3bba9c2106 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/bg-gradient.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/bg-gradient.ts @@ -1,4 +1,5 @@ import type { Config } from 'tailwindcss' +import type { Candidate } from '../../../../tailwindcss/src/candidate' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' import { printCandidate } from '../candidates' @@ -9,7 +10,7 @@ export function bgGradient( _userConfig: Config, rawCandidate: string, ): string { - for (let candidate of designSystem.parseCandidate(rawCandidate)) { + for (let candidate of designSystem.parseCandidate(rawCandidate) as Candidate[]) { if (candidate.kind === 'static' && candidate.root.startsWith('bg-gradient-to-')) { let direction = candidate.root.slice(15) @@ -17,10 +18,9 @@ export function bgGradient( continue } - return printCandidate(designSystem, { - ...candidate, - root: `bg-linear-to-${direction}`, - }) + candidate.root = `bg-linear-to-${direction}` + + return printCandidate(designSystem, candidate) } } return rawCandidate diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/important.test.ts b/packages/@tailwindcss-upgrade/src/template/codemods/important.test.ts index dfbce064c9ad..1c7950233e3b 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/important.test.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/important.test.ts @@ -1,19 +1,26 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' import { expect, test } from 'vitest' +import { mockDesignSystem } from '../../mock-design-system' import { important } from './important' test.each([ ['!flex', 'flex!'], - ['min-[calc(1000px+12em)]:!flex', 'min-[calc(1000px_+_12em)]:flex!'], + ['min-[calc(1000px+12em)]:!flex', 'min-[calc(1000px+12em)]:flex!'], ['md:!block', 'md:block!'], + // Maintains the original arbitrary value contents + ['has-[[data-foo]]:!flex', 'has-[[data-foo]]:flex!'], + ['!px-[calc(var(--spacing-1)-1px)]', 'px-[calc(var(--spacing-1)-1px)]!'], + // Does not change non-important candidates ['bg-blue-500', 'bg-blue-500'], ['min-[calc(1000px+12em)]:flex', 'min-[calc(1000px+12em)]:flex'], ])('%s => %s', async (candidate, result) => { - let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', { - base: __dirname, - }) + let designSystem = mockDesignSystem( + await __unstable__loadDesignSystem('@import "tailwindcss";', { + base: __dirname, + }), + ) expect(important(designSystem, {}, candidate)).toEqual(result) }) diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/important.ts b/packages/@tailwindcss-upgrade/src/template/codemods/important.ts index 69dbdba853f3..025b603ddbe6 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/important.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/important.ts @@ -1,5 +1,4 @@ import type { Config } from 'tailwindcss' -import { parseCandidate } from '../../../../tailwindcss/src/candidate' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' import { printCandidate } from '../candidates' @@ -20,7 +19,7 @@ export function important( _userConfig: Config, rawCandidate: string, ): string { - for (let candidate of parseCandidate(rawCandidate, designSystem)) { + for (let candidate of designSystem.parseCandidate(rawCandidate)) { if (candidate.important && candidate.raw[candidate.raw.length - 1] !== '!') { // The printCandidate function will already put the exclamation mark in // the right place, so we just need to mark this candidate as requiring a diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/prefix.test.ts b/packages/@tailwindcss-upgrade/src/template/codemods/prefix.test.ts index b50dbf64bbea..536f2ee1decb 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/prefix.test.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/prefix.test.ts @@ -1,5 +1,6 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' import { describe, expect, test } from 'vitest' +import { mockDesignSystem } from '../../mock-design-system' import { prefix } from './prefix' describe('for projects with configured prefix', () => { @@ -21,27 +22,33 @@ describe('for projects with configured prefix', () => { // Adds prefix to arbitrary candidates ['[color:red]', 'tw:[color:red]'], ])('%s => %s', async (candidate, result) => { - let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss" prefix(tw);', { - base: __dirname, - }) + let designSystem = mockDesignSystem( + await __unstable__loadDesignSystem('@import "tailwindcss" prefix(tw);', { + base: __dirname, + }), + ) expect(prefix(designSystem, { prefix: 'tw-' }, candidate)).toEqual(result) }) }) test('can handle complex prefix separators', async () => { - let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss" prefix(tw);', { - base: __dirname, - }) + let designSystem = mockDesignSystem( + await __unstable__loadDesignSystem('@import "tailwindcss" prefix(tw);', { + base: __dirname, + }), + ) expect(prefix(designSystem, { prefix: 'tw__' }, 'tw__flex')).toEqual('tw:flex') }) describe('for projects without configured prefix', () => { test('ignores candidates with prefixes', async () => { - let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', { - base: __dirname, - }) + let designSystem = mockDesignSystem( + await __unstable__loadDesignSystem('@import "tailwindcss";', { + base: __dirname, + }), + ) expect(prefix(designSystem, {}, 'tw-flex')).toEqual('tw-flex') }) diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/prefix.ts b/packages/@tailwindcss-upgrade/src/template/codemods/prefix.ts index 8f5979326b01..8988ac081df8 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/prefix.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/prefix.ts @@ -1,5 +1,5 @@ import type { Config } from 'tailwindcss' -import { parseCandidate, type Candidate } from '../../../../tailwindcss/src/candidate' +import { type Candidate } from '../../../../tailwindcss/src/candidate' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' import { segment } from '../../../../tailwindcss/src/utils/segment' import { printCandidate } from '../candidates' @@ -24,10 +24,7 @@ export function prefix( let unprefixedCandidate = rawCandidate.slice(0, v3Base.start) + v3Base.base + rawCandidate.slice(v3Base.end) - // Note: This is not a valid candidate in the original DesignSystem, so we - // can not use the `DesignSystem#parseCandidate` API here or otherwise this - // invalid candidate will be cached. - let candidates = [...parseCandidate(unprefixedCandidate, designSystem)] + let candidates = [...designSystem.parseCandidate(unprefixedCandidate)] if (candidates.length > 0) { candidate = candidates[0] } 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 index d0a89da58b32..f25eac2f7a49 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.test.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.test.ts @@ -1,5 +1,6 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' import { expect, test } from 'vitest' +import { mockDesignSystem } from '../../mock-design-system' import { simpleLegacyClasses } from './simple-legacy-classes' test.each([ @@ -14,9 +15,11 @@ test.each([ ['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, - }) + let designSystem = mockDesignSystem( + 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 index 94a4121f8f76..610e028f0e08 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.ts @@ -1,4 +1,5 @@ import type { Config } from 'tailwindcss' +import type { Candidate } from '../../../../tailwindcss/src/candidate' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' import { printCandidate } from '../candidates' @@ -28,12 +29,10 @@ export function simpleLegacyClasses( SEEDED.add(designSystem) } - for (let candidate of designSystem.parseCandidate(rawCandidate)) { + for (let candidate of designSystem.parseCandidate(rawCandidate) as Candidate[]) { 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], - }) + candidate.root = LEGACY_CLASS_MAP[candidate.root as keyof typeof LEGACY_CLASS_MAP] + return printCandidate(designSystem, candidate) } } diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.test.ts b/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.test.ts index d8b0a238fbd0..989cc5550c88 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.test.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.test.ts @@ -1,5 +1,6 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' import { expect, test } from 'vitest' +import { mockDesignSystem } from '../../mock-design-system' import { themeToVar } from './theme-to-var' test.each([ @@ -26,7 +27,7 @@ test.each([ ], // Use `theme(…)` (deeply nested) inside of a `calc(…)` function - ['text-[calc(theme(fontSize.xs)*2)]', 'text-[calc(var(--font-size-xs)_*_2)]'], + ['text-[calc(theme(fontSize.xs)*2)]', 'text-[calc(var(--font-size-xs)*2)]'], // Multiple `theme(… / …)` calls should result in modern syntax of `theme(…)` // - Can't convert to `var(…)` because that would lose the modifier. @@ -95,10 +96,17 @@ test.each([ '[--foo:theme(colors.red.500/50/50)_theme(colors.blue.200)]/50', '[--foo:theme(colors.red.500/50/50)_var(--color-blue-200)]/50', ], + + // Maintains the whitespace of the original arbitrary value contents + ['[@media(calc(theme(spacing.4)-1px))]:flex', '[@media(calc(theme(--spacing-4)-1px))]:flex'], + ['px-[calc(theme(spacing.4)-1px)]', 'px-[calc(var(--spacing-4)-1px)]'], + ['px-[calc(theme(spacing.4)-_1px)]', 'px-[calc(var(--spacing-4)-_1px)]'], ])('%s => %s', async (candidate, result) => { - let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', { - base: __dirname, - }) + let designSystem = mockDesignSystem( + await __unstable__loadDesignSystem('@import "tailwindcss";', { + base: __dirname, + }), + ) expect(themeToVar(designSystem, {}, candidate)).toEqual(result) }) diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.ts b/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.ts index 4934108e85d2..fda31dc8c902 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.ts @@ -1,6 +1,5 @@ import type { Config } from 'tailwindcss' import { - parseCandidate, type Candidate, type CandidateModifier, type Variant, @@ -25,40 +24,39 @@ export function themeToVar( ): string { let convert = createConverter(designSystem) - for (let candidate of parseCandidate(rawCandidate, designSystem)) { - let clone = structuredClone(candidate) + for (let candidate of designSystem.parseCandidate(rawCandidate) as Candidate[]) { let changed = false - if (clone.kind === 'arbitrary') { + if (candidate.kind === 'arbitrary') { let [newValue, modifier] = convert( - clone.value, - clone.modifier === null ? Convert.MigrateModifier : Convert.All, + candidate.value, + candidate.modifier === null ? Convert.MigrateModifier : Convert.All, ) - if (newValue !== clone.value) { + if (newValue !== candidate.value) { changed = true - clone.value = newValue + candidate.value = newValue if (modifier !== null) { - clone.modifier = modifier + candidate.modifier = modifier } } - } else if (clone.kind === 'functional' && clone.value?.kind === 'arbitrary') { + } else if (candidate.kind === 'functional' && candidate.value?.kind === 'arbitrary') { let [newValue, modifier] = convert( - clone.value.value, - clone.modifier === null ? Convert.MigrateModifier : Convert.All, + candidate.value.value, + candidate.modifier === null ? Convert.MigrateModifier : Convert.All, ) - if (newValue !== clone.value.value) { + if (newValue !== candidate.value.value) { changed = true - clone.value.value = newValue + candidate.value.value = newValue if (modifier !== null) { - clone.modifier = modifier + candidate.modifier = modifier } } } // Handle variants - for (let variant of variants(clone)) { + for (let variant of variants(candidate)) { if (variant.kind === 'arbitrary') { let [newValue] = convert(variant.selector, Convert.MigrateThemeOnly) if (newValue !== variant.selector) { @@ -74,7 +72,7 @@ export function themeToVar( } } - return changed ? printCandidate(designSystem, clone) : rawCandidate + return changed ? printCandidate(designSystem, candidate) : rawCandidate } return rawCandidate diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/variant-order.test.ts b/packages/@tailwindcss-upgrade/src/template/codemods/variant-order.test.ts index e42595645b45..d7c54ec4fdb4 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/variant-order.test.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/variant-order.test.ts @@ -1,6 +1,7 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' import dedent from 'dedent' import { expect, test } from 'vitest' +import { mockDesignSystem } from '../../mock-design-system' import { variantOrder } from './variant-order' let css = dedent @@ -49,38 +50,42 @@ test.each([ ], ['hover:[@supports(display:grid)]:flex', '[@supports(display:grid)]:hover:flex'], ])('%s => %s', async (candidate, result) => { - let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', { - base: __dirname, - }) + let designSystem = mockDesignSystem( + await __unstable__loadDesignSystem('@import "tailwindcss";', { + base: __dirname, + }), + ) expect(variantOrder(designSystem, {}, candidate)).toEqual(result) }) test('it works with custom variants', async () => { - let designSystem = await __unstable__loadDesignSystem( - css` - @import 'tailwindcss'; - @variant atrule { - @media (print) { - @slot; + let designSystem = mockDesignSystem( + await __unstable__loadDesignSystem( + css` + @import 'tailwindcss'; + @variant atrule { + @media (print) { + @slot; + } } - } - @variant combinator { - > * { - @slot; + @variant combinator { + > * { + @slot; + } } - } - @variant pseudo { - &::before { - @slot; + @variant pseudo { + &::before { + @slot; + } } - } - `, - { - base: __dirname, - }, + `, + { + base: __dirname, + }, + ), ) expect(variantOrder(designSystem, {}, 'combinator:pseudo:atrule:underline')).toEqual( diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/variant-order.ts b/packages/@tailwindcss-upgrade/src/template/codemods/variant-order.ts index 5234208a5a8a..d5721ec3f91d 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/variant-order.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/variant-order.ts @@ -1,6 +1,6 @@ import type { Config } from 'tailwindcss' import { walk, type AstNode } from '../../../../tailwindcss/src/ast' -import { type Variant } from '../../../../tailwindcss/src/candidate' +import { type Candidate, type Variant } from '../../../../tailwindcss/src/candidate' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' import { printCandidate } from '../candidates' @@ -9,7 +9,7 @@ export function variantOrder( _userConfig: Config, rawCandidate: string, ): string { - for (let candidate of designSystem.parseCandidate(rawCandidate)) { + for (let candidate of designSystem.parseCandidate(rawCandidate) as Candidate[]) { if (candidate.variants.length <= 1) { continue } @@ -46,7 +46,9 @@ export function variantOrder( continue } - return printCandidate(designSystem, { ...candidate, variants: newOrder }) + candidate.variants = newOrder + + return printCandidate(designSystem, candidate) } return rawCandidate } diff --git a/packages/@tailwindcss-upgrade/src/template/prepare-config.ts b/packages/@tailwindcss-upgrade/src/template/prepare-config.ts index 174962ceb65b..637626fcbc7a 100644 --- a/packages/@tailwindcss-upgrade/src/template/prepare-config.ts +++ b/packages/@tailwindcss-upgrade/src/template/prepare-config.ts @@ -7,6 +7,7 @@ import { fileURLToPath } from 'url' import { loadModule } from '../../../@tailwindcss-node/src/compile' import { resolveConfig } from '../../../tailwindcss/src/compat/config/resolve-config' import type { DesignSystem } from '../../../tailwindcss/src/design-system' +import { mockDesignSystem } from '../mock-design-system' import { error } from '../utils/renderer' import { migratePrefix } from './codemods/prefix' @@ -58,7 +59,7 @@ export async function prepareConfig( ]) return { - designSystem, + designSystem: mockDesignSystem(designSystem), globs: compiler.globs, userConfig, newPrefix, diff --git a/packages/tailwindcss/src/design-system.ts b/packages/tailwindcss/src/design-system.ts index 894dba6eaae2..f1b5ff88a4a5 100644 --- a/packages/tailwindcss/src/design-system.ts +++ b/packages/tailwindcss/src/design-system.ts @@ -60,7 +60,7 @@ export function buildDesignSystem(theme: Theme): DesignSystem { let wasInvalid = false let { astNodes } = compileCandidates([className], this, { - onInvalidCandidate(candidate) { + onInvalidCandidate(_candidate) { wasInvalid = true }, }) diff --git a/packages/tailwindcss/src/utils/decode-arbitrary-value.ts b/packages/tailwindcss/src/utils/decode-arbitrary-value.ts index f644e663995a..2fcc13fb865a 100644 --- a/packages/tailwindcss/src/utils/decode-arbitrary-value.ts +++ b/packages/tailwindcss/src/utils/decode-arbitrary-value.ts @@ -17,7 +17,7 @@ export function decodeArbitraryValue(input: string): string { * Convert `_` to ` `, except for escaped underscores `\_` they should be * converted to `_` instead. */ -function convertUnderscoresToWhitespace(input: string) { +export function convertUnderscoresToWhitespace(input: string) { let output = '' for (let i = 0; i < input.length; i++) { let char = input[i]