Skip to content

Upgrade deprecated order-none to order-0 #18126

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 23, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Upgrade: Do not migrate declarations that look like candidates in `<style>` blocks ([#18057](https://github.com/tailwindlabs/tailwindcss/pull/18057), [18068](https://github.com/tailwindlabs/tailwindcss/pull/18068))
- Upgrade: Improve `pnpm` workspaces support ([#18065](https://github.com/tailwindlabs/tailwindcss/pull/18065))
- Upgrade: Migrate deprecated `order-none` to `order-0` ([#18126](https://github.com/tailwindlabs/tailwindcss/pull/18126))
- Support Leptos `class:` attributes when extracting classes ([#18093](https://github.com/tailwindlabs/tailwindcss/pull/18093))

## [4.1.7] - 2025-05-15
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,11 @@ export function parseCandidate(designSystem: DesignSystem, input: string) {
: input,
)
}

export function printUnprefixedCandidate(designSystem: DesignSystem, candidate: Candidate) {
let candidateString = designSystem.printCandidate(candidate)

return designSystem.theme.prefix && candidateString.startsWith(`${designSystem.theme.prefix}:`)
? candidateString.slice(designSystem.theme.prefix.length + 1)
: candidateString
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import type { Config } from '../../../../tailwindcss/src/compat/plugin-api'
import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
import type { Writable } from '../../utils/types'
import { baseCandidate, parseCandidate, printUnprefixedCandidate } from './candidates'
import { computeUtilitySignature } from './signatures'

const DEPRECATION_MAP = new Map([['order-none', 'order-0']])

export async function migrateDeprecatedUtilities(
designSystem: DesignSystem,
_userConfig: Config | null,
rawCandidate: string,
): Promise<string> {
let signatures = computeUtilitySignature.get(designSystem)

for (let readonlyCandidate of designSystem.parseCandidate(rawCandidate)) {
// The below logic makes use of mutation. Since candidates in the
// DesignSystem are cached, we can't mutate them directly.
let candidate = structuredClone(readonlyCandidate) as Writable<typeof readonlyCandidate>

// Create a basic stripped candidate without variants or important flag. We
// will re-add those later but they are irrelevant for what we are trying to
// do here (and will increase cache hits because we only have to deal with
// the base utility, nothing more).
let targetCandidate = baseCandidate(candidate)
let targetCandidateString = printUnprefixedCandidate(designSystem, targetCandidate)

let replacementString = DEPRECATION_MAP.get(targetCandidateString) ?? null
if (replacementString === null) return rawCandidate

let legacySignature = signatures.get(targetCandidateString)
if (typeof legacySignature !== 'string') return rawCandidate

let replacementSignature = signatures.get(replacementString)
if (typeof replacementSignature !== 'string') return rawCandidate

// Not the same signature, not safe to migrate
if (legacySignature !== replacementSignature) return rawCandidate

let [replacement] = parseCandidate(designSystem, replacementString)

// Re-add the variants and important flag from the original candidate
return designSystem.printCandidate(
Object.assign(structuredClone(replacement), {
variants: candidate.variants,
important: candidate.important,
}),
)
}

return rawCandidate
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s',

// Promote inferred data type to more specific utility if it exists
['bg-[123px]', 'bg-position-[123px]'],

// Do not migrate bare values or arbitrary values to named values that are
// deprecated
['order-[0]', 'order-0'],
['order-0', 'order-0'],

// Migrate deprecated named values to bare values
['order-none', 'order-0'],
])(testName, async (candidate, result) => {
if (strategy === 'with-variant') {
candidate = `focus:${candidate}`
Expand All @@ -63,4 +71,38 @@ describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s',
let migrated = await migrate(designSystem, {}, candidate)
expect(migrated).toEqual(result)
})

test.each([
['order-[0]', 'order-0'],
['order-0', 'order-0'],

// Do not migrate away `order-none` if it's customly defined and thus not
// safe to migrate to `order-0`
['order-none', 'order-none'],
])(`${testName} with custom implementations`, async (candidate, result) => {
if (strategy === 'with-variant') {
candidate = `focus:${candidate}`
result = `focus:${result}`
} else if (strategy === 'important') {
candidate = `${candidate}!`
result = `${result}!`
} else if (strategy === 'prefix') {
// Not only do we need to prefix the candidate, we also have to make
// sure that we prefix all CSS variables.
candidate = `tw:${candidate.replaceAll('var(--', 'var(--tw-')}`
result = `tw:${result.replaceAll('var(--', 'var(--tw-')}`
}

let localInput = css`
${input}

@utility order-none {
order: none; /* imagine this exists */
}
`

let designSystem = await designSystems.get(__dirname).get(localInput)
let migrated = await migrate(designSystem, {}, candidate)
expect(migrated).toEqual(result)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { migrateBareValueUtilities } from './migrate-bare-utilities'
import { migrateBgGradient } from './migrate-bg-gradient'
import { migrateCamelcaseInNamedValue } from './migrate-camelcase-in-named-value'
import { migrateCanonicalizeCandidate } from './migrate-canonicalize-candidate'
import { migrateDeprecatedUtilities } from './migrate-deprecated-utilities'
import { migrateDropUnnecessaryDataTypes } from './migrate-drop-unnecessary-data-types'
import { migrateEmptyArbitraryValues } from './migrate-handle-empty-arbitrary-values'
import { migrateLegacyArbitraryValues } from './migrate-legacy-arbitrary-values'
Expand Down Expand Up @@ -48,6 +49,7 @@ export const DEFAULT_MIGRATIONS: Migration[] = [
migrateLegacyArbitraryValues,
migrateArbitraryUtilities,
migrateBareValueUtilities,
migrateDeprecatedUtilities,
migrateModernizeArbitraryValues,
migrateArbitraryVariants,
migrateDropUnnecessaryDataTypes,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8079,7 +8079,6 @@ exports[`getClassList 1`] = `
"order-12",
"order-first",
"order-last",
"order-none",
"ordinal",
"origin-bottom",
"origin-bottom-left",
Expand Down
2 changes: 2 additions & 0 deletions packages/tailwindcss/src/compat/legacy-utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,6 @@ export function registerLegacyUtilities(designSystem: DesignSystem) {
return [decl('flex-grow', candidate.value.value)]
}
})

designSystem.utilities.static('order-none', () => [decl('order', '0')])
}
1 change: 0 additions & 1 deletion packages/tailwindcss/src/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -649,7 +649,6 @@ export function createUtilities(theme: Theme) {
*/
staticUtility('order-first', [['order', '-9999']])
staticUtility('order-last', [['order', '9999']])
staticUtility('order-none', [['order', '0']])
functionalUtility('order', {
supportsNegative: true,
handleBareValue: ({ value }) => {
Expand Down