diff --git a/integrations/upgrade/js-config.test.ts b/integrations/upgrade/js-config.test.ts index de10250c29a1..039f3dc8dd57 100644 --- a/integrations/upgrade/js-config.test.ts +++ b/integrations/upgrade/js-config.test.ts @@ -167,7 +167,7 @@ test( " --- src/index.html ---
diff --git a/packages/tailwindcss/src/canonicalize-candidates.test.ts b/packages/tailwindcss/src/canonicalize-candidates.test.ts index 9eac5cb92d40..60c23c22106c 100644 --- a/packages/tailwindcss/src/canonicalize-candidates.test.ts +++ b/packages/tailwindcss/src/canonicalize-candidates.test.ts @@ -253,6 +253,14 @@ describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s', '[--foo:theme(colors.red.500/50/50)_theme(colors.blue.200)]/50', '[--foo:theme(colors.red.500/50/50)_var(--color-blue-200)]/50', ], + + // If a utility sets `property` and `--tw-{property}` with the same value, + // we can ignore the `--tw-{property}`. This is just here for composition. + // This means that we should be able to upgrade the one _without_ to the one + // _with_ the variable + ['[font-weight:400]', 'font-normal'], + ['[line-height:0]', 'leading-0'], + ['[border-style:solid]', 'border-solid'], ])(testName, async (candidate, expected) => { await expectCanonicalization( css` diff --git a/packages/tailwindcss/src/signatures.ts b/packages/tailwindcss/src/signatures.ts index 3e6a6605d381..7ad608f22cb4 100644 --- a/packages/tailwindcss/src/signatures.ts +++ b/packages/tailwindcss/src/signatures.ts @@ -11,8 +11,6 @@ import { isValidSpacingMultiplier } from './utils/infer-data-type' import * as ValueParser from './value-parser' import { walk, WalkAction } from './walk' -const FLOATING_POINT_PERCENTAGE = /\d*\.\d+(?:[eE][+-]?\d+)?%/g - export enum SignatureFeatures { None = 0, ExpandProperties = 1 << 0, @@ -101,29 +99,33 @@ function canonicalizeAst(ast: AstNode[], options: SignatureOptions) { let { rem, designSystem } = options walk(ast, { - enter(node) { + enter(node, ctx) { // Optimize declarations if (node.kind === 'declaration') { if (node.value === undefined || node.property === '--tw-sort') { return WalkAction.Replace([]) } + // Ignore `--tw-{property}` if `{property}` exists with the same value + if (node.property.startsWith('--tw-')) { + if ( + (ctx.parent?.nodes ?? []).some( + (sibling) => + sibling.kind === 'declaration' && + node.value === sibling.value && + node.important === sibling.important && + !sibling.property.startsWith('--tw-'), + ) + ) { + return WalkAction.Replace([]) + } + } + if (options.features & SignatureFeatures.ExpandProperties) { let replacement = expandDeclaration(node, options.features) if (replacement) return WalkAction.Replace(replacement) } - // Normalize percentages by removing unnecessary dots and zeros. - // - // E.g.: `50.0%` → `50%` - if (node.value.includes('%')) { - FLOATING_POINT_PERCENTAGE.lastIndex = 0 - node.value = node.value.replaceAll( - FLOATING_POINT_PERCENTAGE, - (match) => `${Number(match.slice(0, -1))}%`, - ) - } - // Resolve theme values to their inlined value. if (node.value.includes('var(')) { node.value = resolveVariablesInValue(node.value, designSystem)