Add more canonicalization rules for deprecated utilities (#19849)
This PR adds more canonicalization rules for deprecated utilities. | Before | After | | --- | --- | | `overflow-ellipsis` | `text-ellipsis` | | `start-full` | `inset-s-full` | | `-start-full` | `-inset-s-full` | | `start-auto` | `inset-s-auto` | | `start-px` | `inset-s-px` | | `-start-px` | `-inset-s-px` | | `start-8` | `inset-s-8` | | `-start-8` | `-inset-s-8` | | `start-123` | `inset-s-123` | | `-start-123` | `-inset-s-123` | | `end-full` | `inset-e-full` | | `-end-full` | `-inset-e-full` | | `end-auto` | `inset-e-auto` | | `end-px` | `inset-e-px` | | `-end-px` | `-inset-e-px` | | `end-8` | `inset-e-8` | | `-end-8` | `-inset-e-8` | | `end-123` | `inset-e-123` | | `-end-123` | `-inset-e-123` | In a few cases we already had canonicalization rules, for example `start-8` where `8` is one of the default suggested spacing scale values. But this now adds support for positive and negative values that exceed the default suggested spacing scale as well as some keywords. ## Test plan 1. Existing tests pass 2. Added new tests to ensure these canonicalizations work
This commit is contained in:
parent
e4856c9720
commit
88a2d22c2f
3 changed files with 87 additions and 64 deletions
|
|
@ -27,6 +27,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Upgrade: Use `config.content` when migrating from Tailwind CSS v3 to Tailwind CSS v4 ([#19846](https://github.com/tailwindlabs/tailwindcss/pull/19846))
|
||||
- Upgrade: Never migrate files that are ignored by git ([#19846](https://github.com/tailwindlabs/tailwindcss/pull/19846))
|
||||
- Add `.env` and `.env.*` to default ignored content files ([#19846](https://github.com/tailwindlabs/tailwindcss/pull/19846))
|
||||
- Canonicalization: migrate `overflow-ellipsis` into `text-ellipsis` ([#19849](https://github.com/tailwindlabs/tailwindcss/pull/19849))
|
||||
- Canonicalization: migrate `start-full` → `inset-s-full`, `start-auto` → `inset-s-auto`, `start-px` → `inset-s-px`, and `start-<number>` → `inset-s-<number>` as well as negative versions ([#19849](https://github.com/tailwindlabs/tailwindcss/pull/19849))
|
||||
- Canonicalization: migrate `end-full` → `inset-e-full`, `end-auto` → `inset-e-auto`, `end-px` → `inset-e-px`, and `end-<number>` → `inset-e-<number>` as well as negative versions ([#19849](https://github.com/tailwindlabs/tailwindcss/pull/19849))
|
||||
|
||||
## [4.2.2] - 2026-03-18
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { randomUUID } from 'node:crypto'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { describe, expect, test } from 'vitest'
|
||||
|
|
@ -611,68 +612,67 @@ describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s',
|
|||
})
|
||||
|
||||
describe('deprecated utilities', () => {
|
||||
test('`order-none` → `order-0`', { timeout }, async () => {
|
||||
let candidate = 'order-none'
|
||||
let expected = 'order-0'
|
||||
let deprecated = [
|
||||
['order-none', 'order-0'],
|
||||
['break-words', 'wrap-break-word'],
|
||||
['overflow-ellipsis', 'text-ellipsis'],
|
||||
|
||||
let input = css`
|
||||
@import 'tailwindcss';
|
||||
`
|
||||
['start-full', 'inset-s-full'],
|
||||
['-start-full', '-inset-s-full'],
|
||||
['start-auto', 'inset-s-auto'],
|
||||
['start-px', 'inset-s-px'],
|
||||
['-start-px', '-inset-s-px'],
|
||||
['start-8', 'inset-s-8'], // Within default spacing scale
|
||||
['-start-8', '-inset-s-8'], // Within default spacing scale
|
||||
['start-123', 'inset-s-123'], // Outside of default spacing scale
|
||||
['-start-123', '-inset-s-123'], // Outside of default spacing scale
|
||||
|
||||
await expectCanonicalization(input, candidate, expected)
|
||||
})
|
||||
['end-full', 'inset-e-full'],
|
||||
['-end-full', '-inset-e-full'],
|
||||
['end-auto', 'inset-e-auto'],
|
||||
['end-px', 'inset-e-px'],
|
||||
['-end-px', '-inset-e-px'],
|
||||
['end-8', 'inset-e-8'], // Within default spacing scale
|
||||
['-end-8', '-inset-e-8'], // Within default spacing scale
|
||||
['end-123', 'inset-e-123'], // Outside of default spacing scale
|
||||
['-end-123', '-inset-e-123'], // Outside of default spacing scale
|
||||
]
|
||||
|
||||
test('`order-none` → `order-none` with custom implementation', { timeout }, async () => {
|
||||
let candidate = 'order-none'
|
||||
let expected = 'order-none'
|
||||
// Creating a shared CSS file such that we can re-use the same design system
|
||||
// for all of these.
|
||||
let customImplementations = deprecated
|
||||
.map(
|
||||
([candidate]) => css`
|
||||
@utility ${candidate} {
|
||||
--custom-${randomUUID()}: implementation;
|
||||
}
|
||||
`,
|
||||
)
|
||||
.join('\n')
|
||||
|
||||
let input = css`
|
||||
@import 'tailwindcss';
|
||||
for (let [candidate, expected] of deprecated) {
|
||||
test(`\`${candidate}\` → \`${expected}\` (%#)`, { timeout }, async () => {
|
||||
let input = css`
|
||||
@import 'tailwindcss';
|
||||
`
|
||||
|
||||
@utility order-none {
|
||||
order: none; /* imagine this exists */
|
||||
}
|
||||
`
|
||||
await expectCanonicalization(input, candidate, expected)
|
||||
})
|
||||
|
||||
await expectCanonicalization(input, candidate, expected)
|
||||
})
|
||||
test(
|
||||
`\`${candidate}\` → \`${candidate}\` because of custom implementation (%#)`,
|
||||
{ timeout },
|
||||
async () => {
|
||||
let input = css`
|
||||
@import 'tailwindcss';
|
||||
|
||||
test('`break-words` → `wrap-break-word`', { timeout }, async () => {
|
||||
let candidate = 'break-words'
|
||||
let expected = 'wrap-break-word'
|
||||
${customImplementations}
|
||||
`
|
||||
|
||||
let input = css`
|
||||
@import 'tailwindcss';
|
||||
`
|
||||
|
||||
await expectCanonicalization(input, candidate, expected)
|
||||
})
|
||||
|
||||
test('`[overflow-wrap:break-word]` → `wrap-break-word`', { timeout }, async () => {
|
||||
let candidate = '[overflow-wrap:break-word]'
|
||||
let expected = 'wrap-break-word'
|
||||
|
||||
let input = css`
|
||||
@import 'tailwindcss';
|
||||
`
|
||||
|
||||
await expectCanonicalization(input, candidate, expected)
|
||||
})
|
||||
|
||||
test('`break-words` → `break-words` with custom implementation', { timeout }, async () => {
|
||||
let candidate = 'break-words'
|
||||
let expected = 'break-words'
|
||||
|
||||
let input = css`
|
||||
@import 'tailwindcss';
|
||||
|
||||
@utility break-words {
|
||||
break: words; /* imagine this exists */
|
||||
}
|
||||
`
|
||||
|
||||
await expectCanonicalization(input, candidate, expected)
|
||||
})
|
||||
await expectCanonicalization(input, candidate, candidate)
|
||||
},
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
describe('arbitrary variants', () => {
|
||||
|
|
|
|||
|
|
@ -198,7 +198,6 @@ export function canonicalizeCandidates(
|
|||
}
|
||||
|
||||
function collapseCandidates(options: InternalCanonicalizeOptions, candidates: string[]): string[] {
|
||||
if (candidates.length <= 1) return candidates
|
||||
let designSystem = options.designSystem
|
||||
|
||||
// To keep things simple, we group candidates such that we only collapse
|
||||
|
|
@ -1428,8 +1427,28 @@ function bareValueUtilities(candidate: Candidate, options: InternalCanonicalizeO
|
|||
const DEPRECATION_MAP = new Map([
|
||||
['order-none', 'order-0'],
|
||||
['break-words', 'wrap-break-word'],
|
||||
['overflow-ellipsis', 'text-ellipsis'],
|
||||
])
|
||||
|
||||
const DEPRECATION_TRANSFORMATION_MAP = new Map([
|
||||
[/^(-)?start-(.*?)$/, '$1inset-s-$2'],
|
||||
[/^(-)?end-(.*?)$/, '$1inset-e-$2'],
|
||||
])
|
||||
|
||||
function* tryDeprecatedUtilities(candidate: string) {
|
||||
// Try static replacements
|
||||
let replacement = DEPRECATION_MAP.get(candidate)
|
||||
if (replacement) yield replacement
|
||||
|
||||
// Try dynamic replacements
|
||||
for (let [searchValue, replaceValue] of DEPRECATION_TRANSFORMATION_MAP) {
|
||||
let replacement = candidate.replace(searchValue, replaceValue)
|
||||
if (replacement === candidate) continue
|
||||
|
||||
yield replacement
|
||||
}
|
||||
}
|
||||
|
||||
function deprecatedUtilities(
|
||||
candidate: Candidate,
|
||||
options: InternalCanonicalizeOptions,
|
||||
|
|
@ -1439,20 +1458,21 @@ function deprecatedUtilities(
|
|||
|
||||
let targetCandidateString = printUnprefixedCandidate(designSystem, candidate)
|
||||
|
||||
let replacementString = DEPRECATION_MAP.get(targetCandidateString) ?? null
|
||||
if (replacementString === null) return candidate
|
||||
|
||||
let legacySignature = signatures.get(targetCandidateString)
|
||||
if (typeof legacySignature !== 'string') return candidate
|
||||
|
||||
let replacementSignature = signatures.get(replacementString)
|
||||
if (typeof replacementSignature !== 'string') return candidate
|
||||
for (let replacementString of tryDeprecatedUtilities(targetCandidateString)) {
|
||||
let replacementSignature = signatures.get(replacementString)
|
||||
if (typeof replacementSignature !== 'string') continue
|
||||
|
||||
// Not the same signature, not safe to migrate
|
||||
if (legacySignature !== replacementSignature) return candidate
|
||||
// Not the same signature, not safe to migrate
|
||||
if (legacySignature !== replacementSignature) continue
|
||||
|
||||
let [replacement] = parseCandidate(designSystem, replacementString)
|
||||
return replacement
|
||||
let [replacement] = parseCandidate(designSystem, replacementString)
|
||||
return replacement
|
||||
}
|
||||
|
||||
return candidate
|
||||
}
|
||||
|
||||
// ----
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue