Skip to content

Commit c9c042f

Browse files
committed
move migrate-arbitrary-variants to core
1 parent 0ff9059 commit c9c042f

File tree

4 files changed

+160
-161
lines changed

4 files changed

+160
-161
lines changed

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

Lines changed: 0 additions & 158 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
@@ -8,7 +8,6 @@ import { spliceChangesIntoString, type StringChange } from '../../utils/splice-c
88
import { extractRawCandidates } from './candidates'
99
import { isSafeMigration } from './is-safe-migration'
1010
import { migrateArbitraryValueToBareValue } from './migrate-arbitrary-value-to-bare-value'
11-
import { migrateArbitraryVariants } from './migrate-arbitrary-variants'
1211
import { migrateAutomaticVarInjection } from './migrate-automatic-var-injection'
1312
import { migrateCamelcaseInNamedValue } from './migrate-camelcase-in-named-value'
1413
import { migrateCanonicalizeCandidate } from './migrate-canonicalize-candidate'
@@ -41,7 +40,6 @@ export const DEFAULT_MIGRATIONS: Migration[] = [
4140
migrateAutomaticVarInjection, // sync, v3 → v4
4241
migrateLegacyArbitraryValues, // sync, v3 → v4 (could also consider it a v4 optimization)
4342
migrateModernizeArbitraryValues, // sync, v3 and v4 optimizations, split up?
44-
migrateArbitraryVariants, // sync, v4
4543
migrateDropUnnecessaryDataTypes, // sync, v4 (I think this can be dropped?)
4644
migrateArbitraryValueToBareValue, // sync, v4 (optimization)
4745
migrateOptimizeModifier, // sync, v4 (optimization)

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

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,121 @@ describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s',
522522
await expectCanonicalization(input, candidate, expected)
523523
})
524524
})
525+
526+
describe('arbitrary variants', () => {
527+
let input = css`
528+
@import 'tailwindcss';
529+
@theme {
530+
--*: initial;
531+
}
532+
`
533+
534+
test.each([
535+
// Arbitrary variant to static variant
536+
['[&:focus]:flex', 'focus:flex'],
537+
538+
// Arbitrary variant to static variant with at-rules
539+
['[@media(scripting:_none)]:flex', 'noscript:flex'],
540+
541+
// Arbitrary variant to static utility at-rules and with slight differences
542+
// in whitespace. This will require some canonicalization.
543+
['[@media(scripting:none)]:flex', 'noscript:flex'],
544+
['[@media(scripting:_none)]:flex', 'noscript:flex'],
545+
['[@media_(scripting:_none)]:flex', 'noscript:flex'],
546+
547+
// With compound variants
548+
['has-[&:focus]:flex', 'has-focus:flex'],
549+
['not-[&:focus]:flex', 'not-focus:flex'],
550+
['group-[&:focus]:flex', 'group-focus:flex'],
551+
['peer-[&:focus]:flex', 'peer-focus:flex'],
552+
['in-[&:focus]:flex', 'in-focus:flex'],
553+
])(testName, async (candidate, expected) => {
554+
await expectCanonicalization(input, candidate, expected)
555+
})
556+
557+
test('unsafe migrations keep the candidate as-is', async () => {
558+
// `hover:` also includes an `@media` query in addition to the `&:hover`
559+
// state. Migration is not safe because the functionality would be different.
560+
let candidate = '[&:hover]:flex'
561+
let expected = '[&:hover]:flex'
562+
let input = css`
563+
@import 'tailwindcss';
564+
@theme {
565+
--*: initial;
566+
}
567+
`
568+
569+
await expectCanonicalization(input, candidate, expected)
570+
})
571+
572+
test('make unsafe migration safe (1)', async () => {
573+
// Overriding the `hover:` variant to only use a selector will make the
574+
// migration safe.
575+
let candidate = '[&:hover]:flex'
576+
let expected = 'hover:flex'
577+
let input = css`
578+
@import 'tailwindcss';
579+
@theme {
580+
--*: initial;
581+
}
582+
@variant hover (&:hover);
583+
`
584+
585+
await expectCanonicalization(input, candidate, expected)
586+
})
587+
588+
test('make unsafe migration safe (2)', async () => {
589+
// Overriding the `hover:` variant to only use a selector will make the
590+
// migration safe. This time with the long-hand `@variant` syntax.
591+
let candidate = '[&:hover]:flex'
592+
let expected = 'hover:flex'
593+
let input = css`
594+
@import 'tailwindcss';
595+
@theme {
596+
--*: initial;
597+
}
598+
@variant hover {
599+
&:hover {
600+
@slot;
601+
}
602+
}
603+
`
604+
605+
await expectCanonicalization(input, candidate, expected)
606+
})
607+
608+
test('custom selector-based variants', async () => {
609+
let candidate = '[&.macos]:flex'
610+
let expected = 'is-macos:flex'
611+
let input = css`
612+
@import 'tailwindcss';
613+
@theme {
614+
--*: initial;
615+
}
616+
@variant is-macos (&.macos);
617+
`
618+
619+
await expectCanonicalization(input, candidate, expected)
620+
})
621+
622+
test('custom @media-based variants', async () => {
623+
let candidate = '[@media(prefers-reduced-transparency:reduce)]:flex'
624+
let expected = 'transparency-safe:flex'
625+
let input = css`
626+
@import 'tailwindcss';
627+
@theme {
628+
--*: initial;
629+
}
630+
@variant transparency-safe {
631+
@media (prefers-reduced-transparency: reduce) {
632+
@slot;
633+
}
634+
}
635+
`
636+
637+
await expectCanonicalization(input, candidate, expected)
638+
})
639+
})
525640
})
526641

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

packages/tailwindcss/src/canonicalize-candidates.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
import { printModifier, type Candidate, type CandidateModifier, type Variant } from './candidate'
22
import { keyPathToCssProperty } from './compat/apply-config-to-theme'
33
import type { DesignSystem } from './design-system'
4-
import { computeUtilitySignature, preComputedUtilities } from './signatures'
4+
import {
5+
computeUtilitySignature,
6+
computeVariantSignature,
7+
preComputedUtilities,
8+
preComputedVariants,
9+
} from './signatures'
510
import type { Writable } from './types'
611
import { DefaultMap } from './utils/default-map'
712
import { dimensions } from './utils/dimensions'
813
import { isValidSpacingMultiplier } from './utils/infer-data-type'
14+
import { replaceObject } from './utils/replace-object'
915
import { segment } from './utils/segment'
1016
import { toKeyPath } from './utils/to-key-path'
1117
import * as ValueParser from './value-parser'
@@ -37,6 +43,7 @@ const CANONICALIZATIONS = [
3743
arbitraryUtilities,
3844
bareValueUtilities,
3945
deprecatedUtilities,
46+
arbitraryVariants,
4047
print,
4148
]
4249

@@ -865,3 +872,40 @@ function deprecatedUtilities(designSystem: DesignSystem, rawCandidate: string):
865872

866873
return rawCandidate
867874
}
875+
876+
// ----
877+
878+
function arbitraryVariants(designSystem: DesignSystem, rawCandidate: string): string {
879+
let signatures = computeVariantSignature.get(designSystem)
880+
let variants = preComputedVariants.get(designSystem)
881+
882+
for (let readonlyCandidate of designSystem.parseCandidate(rawCandidate)) {
883+
// We are only interested in the variants
884+
if (readonlyCandidate.variants.length <= 0) return rawCandidate
885+
886+
// The below logic makes use of mutation. Since candidates in the
887+
// DesignSystem are cached, we can't mutate them directly.
888+
let candidate = structuredClone(readonlyCandidate) as Writable<typeof readonlyCandidate>
889+
890+
for (let [variant] of walkVariants(candidate)) {
891+
if (variant.kind === 'compound') continue
892+
893+
let targetString = designSystem.printVariant(variant)
894+
let targetSignature = signatures.get(targetString)
895+
if (typeof targetSignature !== 'string') continue
896+
897+
let foundVariants = variants.get(targetSignature)
898+
if (foundVariants.length !== 1) continue
899+
900+
let foundVariant = foundVariants[0]
901+
let parsedVariant = designSystem.parseVariant(foundVariant)
902+
if (parsedVariant === null) continue
903+
904+
replaceObject(variant, parsedVariant)
905+
}
906+
907+
return designSystem.printCandidate(candidate)
908+
}
909+
910+
return rawCandidate
911+
}

0 commit comments

Comments
 (0)