import SelectorParser from 'postcss-selector-parser'
import { parseCandidate, type Variant } from '../../../../tailwindcss/src/candidate'
import type { Config } from '../../../../tailwindcss/src/compat/plugin-api'
import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
import { isPositiveInteger } from '../../../../tailwindcss/src/utils/infer-data-type'
import * as ValueParser from '../../../../tailwindcss/src/value-parser'
import { replaceObject } from '../../utils/replace-object'
import { walkVariants } from '../../utils/walk-variants'
import { computeVariantSignature } from './signatures'
export function migrateModernizeArbitraryValues(
designSystem: DesignSystem,
_userConfig: Config | null,
rawCandidate: string,
): string {
let signatures = computeVariantSignature.get(designSystem)
for (let candidate of parseCandidate(rawCandidate, designSystem)) {
let clone = structuredClone(candidate)
let changed = false
for (let [variant, parent] of walkVariants(clone)) {
if (
variant.kind === 'compound' &&
(variant.root === 'has' || variant.root === 'not' || variant.root === 'in')
) {
if (variant.modifier !== null) {
if ('modifier' in variant.variant) {
variant.variant.modifier = variant.modifier
variant.modifier = null
}
}
}
if (
variant.kind === 'compound' &&
variant.root === 'group' &&
variant.variant.kind === 'arbitrary' &&
variant.variant.selector === '&'
) {
if (variant.modifier === null) {
changed = true
replaceObject(
variant,
designSystem.parseVariant(
designSystem.theme.prefix
? `in-[.${designSystem.theme.prefix}\\:group]`
: 'in-[.group]',
),
)
}
else if (variant.modifier.kind === 'named') {
changed = true
replaceObject(
variant,
designSystem.parseVariant(
designSystem.theme.prefix
? `in-[.${designSystem.theme.prefix}\\:group\\/${variant.modifier.value}]`
: `in-[.group\\/${variant.modifier.value}]`,
),
)
}
continue
}
if (variant.kind === 'arbitrary') {
if (variant.relative) continue
let ast = SelectorParser().astSync(variant.selector)
if (ast.nodes.length !== 1) continue
if (
parent === null &&
ast.nodes[0].length === 3 &&
ast.nodes[0].nodes[0].type === 'nesting' &&
ast.nodes[0].nodes[0].value === '&' &&
ast.nodes[0].nodes[1].type === 'combinator' &&
ast.nodes[0].nodes[1].value === '>' &&
ast.nodes[0].nodes[2].type === 'universal'
) {
changed = true
replaceObject(variant, designSystem.parseVariant('*'))
continue
}
if (
parent === null &&
ast.nodes[0].length === 3 &&
ast.nodes[0].nodes[0].type === 'nesting' &&
ast.nodes[0].nodes[0].value === '&' &&
ast.nodes[0].nodes[1].type === 'combinator' &&
ast.nodes[0].nodes[1].value === ' ' &&
ast.nodes[0].nodes[2].type === 'universal'
) {
changed = true
replaceObject(variant, designSystem.parseVariant('**'))
continue
}
if (
parent === null &&
ast.nodes[0].nodes.length === 3 &&
ast.nodes[0].nodes[1].type === 'combinator' &&
ast.nodes[0].nodes[1].value === ' ' &&
ast.nodes[0].nodes[2].type === 'nesting'
) {
ast.nodes[0].nodes.pop()
ast.nodes[0].nodes.pop()
changed = true
replaceObject(variant, designSystem.parseVariant(`in-[${ast.toString()}]`))
continue
}
if (
parent === null &&
ast.nodes[0].nodes[0].type === 'tag' &&
(ast.nodes[0].nodes[0].value.startsWith('@media') ||
ast.nodes[0].nodes[0].value.startsWith('@supports'))
) {
let targetSignature = signatures.get(designSystem.printVariant(variant))
let parsed = ValueParser.parse(ast.nodes[0].toString().trim())
let containsNot = false
ValueParser.walk(parsed, (node, { replaceWith }) => {
if (node.kind === 'word' && node.value === 'not') {
containsNot = true
replaceWith([])
}
})
parsed = ValueParser.parse(ValueParser.toCss(parsed))
ValueParser.walk(parsed, (node) => {
if (node.kind === 'separator' && node.value !== ' ' && node.value.trim() === '') {
node.value = ' '
}
})
if (containsNot) {
let hoistedNot = designSystem.parseVariant(`not-[${ValueParser.toCss(parsed)}]`)
if (hoistedNot === null) continue
let hoistedNotSignature = signatures.get(designSystem.printVariant(hoistedNot))
if (targetSignature === hoistedNotSignature) {
changed = true
replaceObject(variant, hoistedNot)
continue
}
}
}
let prefixedVariant: Variant | null = null
if (
parent === null &&
ast.nodes[0].length === 3 &&
ast.nodes[0].nodes[0].type === 'nesting' &&
ast.nodes[0].nodes[0].value === '&' &&
ast.nodes[0].nodes[1].type === 'combinator' &&
ast.nodes[0].nodes[1].value === '>' &&
ast.nodes[0].nodes[2].type === 'attribute'
) {
ast.nodes[0].nodes = [ast.nodes[0].nodes[2]]
prefixedVariant = designSystem.parseVariant('*')
}
if (
parent === null &&
ast.nodes[0].length === 3 &&
ast.nodes[0].nodes[0].type === 'nesting' &&
ast.nodes[0].nodes[0].value === '&' &&
ast.nodes[0].nodes[1].type === 'combinator' &&
ast.nodes[0].nodes[1].value === ' ' &&
ast.nodes[0].nodes[2].type === 'attribute'
) {
ast.nodes[0].nodes = [ast.nodes[0].nodes[2]]
prefixedVariant = designSystem.parseVariant('**')
}
let selectorNodes = ast.nodes[0].filter((node) => node.type !== 'nesting')
if (selectorNodes.length !== 1) continue
let target = selectorNodes[0]
if (target.type === 'pseudo' && target.value === ':is') {
if (target.nodes.length !== 1) continue
if (target.nodes[0].nodes.length !== 1) continue
target = target.nodes[0].nodes[0]
}
if (target.type === 'pseudo') {
let targetNode = target
let compoundNot = false
if (target.value === ':not') {
compoundNot = true
if (target.nodes.length !== 1) continue
if (target.nodes[0].type !== 'selector') continue
if (target.nodes[0].nodes.length !== 1) continue
if (target.nodes[0].nodes[0].type !== 'pseudo') continue
targetNode = target.nodes[0].nodes[0]
}
let newVariant = ((value) => {
if (
value === ':nth-child' &&
targetNode.nodes.length === 1 &&
targetNode.nodes[0].nodes.length === 1 &&
targetNode.nodes[0].nodes[0].type === 'tag' &&
targetNode.nodes[0].nodes[0].value === 'odd'
) {
if (compoundNot) {
compoundNot = false
return 'even'
}
return 'odd'
}
if (
value === ':nth-child' &&
targetNode.nodes.length === 1 &&
targetNode.nodes[0].nodes.length === 1 &&
targetNode.nodes[0].nodes[0].type === 'tag' &&
targetNode.nodes[0].nodes[0].value === 'even'
) {
if (compoundNot) {
compoundNot = false
return 'odd'
}
return 'even'
}
for (let [selector, variantName] of [
[':nth-child', 'nth'],
[':nth-last-child', 'nth-last'],
[':nth-of-type', 'nth-of-type'],
[':nth-last-of-type', 'nth-of-last-type'],
]) {
if (value === selector && targetNode.nodes.length === 1) {
if (
targetNode.nodes[0].nodes.length === 1 &&
targetNode.nodes[0].nodes[0].type === 'tag' &&
isPositiveInteger(targetNode.nodes[0].nodes[0].value)
) {
return `${variantName}-${targetNode.nodes[0].nodes[0].value}`
}
return `${variantName}-[${targetNode.nodes[0].toString()}]`
}
}
if (compoundNot) {
let targetSignature = signatures.get(designSystem.printVariant(variant))
let replacementSignature = signatures.get(`not-[${value}]`)
if (targetSignature === replacementSignature) {
return `[&${value}]`
}
}
return null
})(targetNode.value)
if (newVariant === null) continue
if (compoundNot) newVariant = `not-${newVariant}`
let parsed = designSystem.parseVariant(newVariant)
if (parsed === null) continue
changed = true
replaceObject(variant, structuredClone(parsed))
}
else if (target.type === 'attribute') {
let attributeKey = target.attribute
let attributeValue = target.value
? target.quoted
? `${target.quoteMark}${target.value}${target.quoteMark}`
: target.value
: null
if (target.insensitive && attributeValue) {
attributeValue += ' i'
}
let operator = target.operator ?? '='
if (attributeKey.startsWith('data-')) {
changed = true
attributeKey = attributeKey.slice(5)
replaceObject(variant, {
kind: 'functional',
root: 'data',
modifier: null,
value:
attributeValue === null
? { kind: 'named', value: attributeKey }
: { kind: 'arbitrary', value: `${attributeKey}${operator}${attributeValue}` },
} satisfies Variant)
}
else if (attributeKey.startsWith('aria-')) {
changed = true
attributeKey = attributeKey.slice(5)
replaceObject(variant, {
kind: 'functional',
root: 'aria',
modifier: null,
value:
attributeValue === null
? { kind: 'arbitrary', value: attributeKey }
: operator === '=' && target.value === 'true' && !target.insensitive
? { kind: 'named', value: attributeKey }
: { kind: 'arbitrary', value: `${attributeKey}${operator}${attributeValue}` },
} satisfies Variant)
}
}
if (prefixedVariant) {
let idx = clone.variants.indexOf(variant)
if (idx === -1) continue
clone.variants.splice(idx, 1, variant, prefixedVariant)
}
}
}
return changed ? designSystem.printCandidate(clone) : rawCandidate
}
return rawCandidate
}