Skip to content

Commit 954b6a3

Browse files
committed
move migrate-bare-utilities to core
1 parent f41164c commit 954b6a3

File tree

5 files changed

+142
-200
lines changed

5 files changed

+142
-200
lines changed

packages/@tailwindcss-upgrade/src/codemods/template/migrate-bare-utilities.test.ts

Lines changed: 0 additions & 72 deletions
This file was deleted.

packages/@tailwindcss-upgrade/src/codemods/template/migrate-bare-utilities.ts

Lines changed: 0 additions & 125 deletions
This file was deleted.

packages/@tailwindcss-upgrade/src/codemods/template/migrate.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { isSafeMigration } from './is-safe-migration'
1010
import { migrateArbitraryValueToBareValue } from './migrate-arbitrary-value-to-bare-value'
1111
import { migrateArbitraryVariants } from './migrate-arbitrary-variants'
1212
import { migrateAutomaticVarInjection } from './migrate-automatic-var-injection'
13-
import { migrateBareValueUtilities } from './migrate-bare-utilities'
1413
import { migrateCamelcaseInNamedValue } from './migrate-camelcase-in-named-value'
1514
import { migrateCanonicalizeCandidate } from './migrate-canonicalize-candidate'
1615
import { migrateDeprecatedUtilities } from './migrate-deprecated-utilities'
@@ -42,7 +41,6 @@ export const DEFAULT_MIGRATIONS: Migration[] = [
4241
migrateVariantOrder, // sync, v3 → v4, Has to happen before migrations that modify variants
4342
migrateAutomaticVarInjection, // sync, v3 → v4
4443
migrateLegacyArbitraryValues, // sync, v3 → v4 (could also consider it a v4 optimization)
45-
migrateBareValueUtilities, // sync, v4
4644
migrateDeprecatedUtilities, // sync, v4 (deprecation map, order-none → order-0)
4745
migrateModernizeArbitraryValues, // sync, v3 and v4 optimizations, split up?
4846
migrateArbitraryVariants, // sync, v4

packages/tailwindcss/src/canonicalize-candidates.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,32 @@ describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s',
468468
await expectCanonicalization(input, candidate, expected)
469469
})
470470
})
471+
472+
describe('bare values', () => {
473+
let input = css`
474+
@import 'tailwindcss';
475+
@theme {
476+
--*: initial;
477+
--spacing: 0.25rem;
478+
--aspect-video: 16 / 9;
479+
--tab-size-github: 8;
480+
}
481+
482+
@utility tab-* {
483+
tab-size: --value(--tab-size, integer);
484+
}
485+
`
486+
487+
test.each([
488+
// Built-in utility with bare value fraction
489+
['aspect-16/9', 'aspect-video'],
490+
491+
// Custom utility with bare value integer
492+
['tab-8', 'tab-github'],
493+
])(testName, async (candidate, expected) => {
494+
await expectCanonicalization(input, candidate, expected)
495+
})
496+
})
471497
})
472498

473499
describe('theme to var', () => {

packages/tailwindcss/src/canonicalize-candidates.ts

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,13 @@ const canonicalizeCandidateCache = new DefaultMap((ds: DesignSystem) => {
3131
})
3232
})
3333

34-
const CANONICALIZATIONS = [bgGradientToLinear, themeToVar, arbitraryUtilities, print]
34+
const CANONICALIZATIONS = [
35+
bgGradientToLinear,
36+
themeToVar,
37+
arbitraryUtilities,
38+
bareValueUtilities,
39+
print,
40+
]
3541

3642
function print(designSystem: DesignSystem, rawCandidate: string): string {
3743
for (let candidate of designSystem.parseCandidate(rawCandidate)) {
@@ -696,3 +702,112 @@ function allVariablesAreUsed(
696702

697703
return isSafeMigration
698704
}
705+
706+
// ----
707+
708+
function bareValueUtilities(designSystem: DesignSystem, rawCandidate: string): string {
709+
let utilities = preComputedUtilities.get(designSystem)
710+
let signatures = computeUtilitySignature.get(designSystem)
711+
712+
for (let readonlyCandidate of designSystem.parseCandidate(rawCandidate)) {
713+
// We are only interested in bare value utilities
714+
if (readonlyCandidate.kind !== 'functional' || readonlyCandidate.value?.kind !== 'named') {
715+
continue
716+
}
717+
718+
// The below logic makes use of mutation. Since candidates in the
719+
// DesignSystem are cached, we can't mutate them directly.
720+
let candidate = structuredClone(readonlyCandidate) as Writable<typeof readonlyCandidate>
721+
722+
// Create a basic stripped candidate without variants or important flag. We
723+
// will re-add those later but they are irrelevant for what we are trying to
724+
// do here (and will increase cache hits because we only have to deal with
725+
// the base utility, nothing more).
726+
let targetCandidate = baseCandidate(candidate)
727+
728+
let targetCandidateString = designSystem.printCandidate(targetCandidate)
729+
if (baseReplacementsCache.get(designSystem).has(targetCandidateString)) {
730+
let target = structuredClone(
731+
baseReplacementsCache.get(designSystem).get(targetCandidateString)!,
732+
)
733+
// Re-add the variants and important flag from the original candidate
734+
target.variants = candidate.variants
735+
target.important = candidate.important
736+
737+
return designSystem.printCandidate(target)
738+
}
739+
740+
// Compute the signature for the target candidate
741+
let targetSignature = signatures.get(targetCandidateString)
742+
if (typeof targetSignature !== 'string') continue
743+
744+
// Try a few options to find a suitable replacement utility
745+
for (let replacementCandidate of tryReplacements(targetSignature, targetCandidate)) {
746+
let replacementString = designSystem.printCandidate(replacementCandidate)
747+
let replacementSignature = signatures.get(replacementString)
748+
if (replacementSignature !== targetSignature) {
749+
continue
750+
}
751+
752+
replacementCandidate = structuredClone(replacementCandidate)
753+
754+
// Cache the result so we can re-use this work later
755+
baseReplacementsCache.get(designSystem).set(targetCandidateString, replacementCandidate)
756+
757+
// Re-add the variants and important flag from the original candidate
758+
replacementCandidate.variants = candidate.variants
759+
replacementCandidate.important = candidate.important
760+
761+
// Update the candidate with the new value
762+
Object.assign(candidate, replacementCandidate)
763+
764+
// We will re-print the candidate to get the migrated candidate out
765+
return designSystem.printCandidate(candidate)
766+
}
767+
}
768+
769+
return rawCandidate
770+
771+
function* tryReplacements(
772+
targetSignature: string,
773+
candidate: Extract<Candidate, { kind: 'functional' }>,
774+
): Generator<Candidate> {
775+
// Find a corresponding utility for the same signature
776+
let replacements = utilities.get(targetSignature)
777+
778+
// Multiple utilities can map to the same signature. Not sure how to migrate
779+
// this one so let's just skip it for now.
780+
//
781+
// TODO: Do we just migrate to the first one?
782+
if (replacements.length > 1) return
783+
784+
// If we didn't find any replacement utilities, let's try to strip the
785+
// modifier and find a replacement then. If we do, we can try to re-add the
786+
// modifier later and verify if we have a valid migration.
787+
//
788+
// This is necessary because `text-red-500/50` will not be pre-computed,
789+
// only `text-red-500` will.
790+
if (replacements.length === 0 && candidate.modifier) {
791+
let candidateWithoutModifier = { ...candidate, modifier: null }
792+
let targetSignatureWithoutModifier = signatures.get(
793+
designSystem.printCandidate(candidateWithoutModifier),
794+
)
795+
if (typeof targetSignatureWithoutModifier === 'string') {
796+
for (let replacementCandidate of tryReplacements(
797+
targetSignatureWithoutModifier,
798+
candidateWithoutModifier,
799+
)) {
800+
yield Object.assign({}, replacementCandidate, { modifier: candidate.modifier })
801+
}
802+
}
803+
}
804+
805+
// If only a single utility maps to the signature, we can use that as the
806+
// replacement.
807+
if (replacements.length === 1) {
808+
for (let replacementCandidate of parseCandidate(designSystem, replacements[0])) {
809+
yield replacementCandidate
810+
}
811+
}
812+
}
813+
}

0 commit comments

Comments
 (0)