import { type AstNode } from '../../../../tailwindcss/src/ast'
import { type Variant } from '../../../../tailwindcss/src/candidate'
import type { Config } from '../../../../tailwindcss/src/compat/plugin-api'
import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
import { walk } from '../../../../tailwindcss/src/walk'
import * as version from '../../utils/version'
export function migrateVariantOrder(
designSystem: DesignSystem,
_userConfig: Config | null,
rawCandidate: string,
): string {
if (!version.isMajor(3)) {
return rawCandidate
}
for (let candidate of designSystem.parseCandidate(rawCandidate)) {
if (candidate.variants.length <= 1) {
continue
}
let atRuleVariants = []
let regularVariants = []
let pseudoElementVariants = []
let originalOrder = candidate.variants
for (let variant of candidate.variants) {
if (isAtRuleVariant(designSystem, variant)) {
atRuleVariants.push(variant)
} else if (isEndOfSelectorPseudoElement(designSystem, variant)) {
pseudoElementVariants.push(variant)
} else {
regularVariants.push(variant)
}
}
let regularVariantsNeedReordering = regularVariants.some((v) =>
isCombinatorVariant(designSystem, v),
)
let newOrder = [
...pseudoElementVariants,
...(regularVariantsNeedReordering ? regularVariants.reverse() : regularVariants),
...atRuleVariants,
]
if (orderMatches(originalOrder, newOrder)) {
continue
}
return designSystem.printCandidate({ ...candidate, variants: newOrder })
}
return rawCandidate
}
function isAtRuleVariant(designSystem: DesignSystem, variant: Variant) {
if (variant.kind === 'static' && variant.root === 'dark') {
return true
}
let stack = getAppliedNodeStack(designSystem, variant)
return stack.every((node) => node.kind === 'at-rule')
}
function isCombinatorVariant(designSystem: DesignSystem, variant: Variant) {
let stack = getAppliedNodeStack(designSystem, variant)
return stack.some(
(node) =>
node.kind === 'rule' &&
(node.selector.includes(' ') ||
node.selector.includes('>') ||
node.selector.includes('+') ||
node.selector.includes('~')),
)
}
function isEndOfSelectorPseudoElement(designSystem: DesignSystem, variant: Variant) {
let stack = getAppliedNodeStack(designSystem, variant)
return stack.some(
(node) =>
node.kind === 'rule' &&
(node.selector.includes('::after') ||
node.selector.includes('::backdrop') ||
node.selector.includes('::before') ||
node.selector.includes('::first-letter') ||
node.selector.includes('::first-line') ||
node.selector.includes('::marker') ||
node.selector.includes('::placeholder') ||
node.selector.includes('::selection')),
)
}
function getAppliedNodeStack(designSystem: DesignSystem, variant: Variant): AstNode[] {
let stack: AstNode[] = []
let ast = designSystem
.compileAstNodes({
kind: 'arbitrary',
property: 'color',
value: 'red',
modifier: null,
variants: [variant],
important: false,
raw: 'candidate',
})
.map((c) => c.node)
walk(ast, (node) => {
if (node.kind === 'rule' && node.selector === '.candidate') {
return
}
if (node.kind === 'declaration' && node.property === 'color' && node.value === 'red') {
return
}
stack.push(node)
})
return stack
}
function orderMatches<T>(a: T[], b: T[]): boolean {
if (a.length !== b.length) {
return false
}
return a.every((v, i) => b[i] === v)
}