import { walk, WalkAction } from '../../../../tailwindcss/src/ast'
import { type Candidate, type Variant } from '../../../../tailwindcss/src/candidate'
import type { Config } from '../../../../tailwindcss/src/compat/plugin-api'
import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
import * as ValueParser from '../../../../tailwindcss/src/value-parser'
export function migrateAutomaticVarInjection(
designSystem: DesignSystem,
_userConfig: Config | null,
rawCandidate: string,
): string {
for (let readonlyCandidate of designSystem.parseCandidate(rawCandidate)) {
let candidate = structuredClone(readonlyCandidate) as Candidate
let didChange = false
if (
'modifier' in candidate &&
candidate.modifier?.kind === 'arbitrary' &&
!isAutomaticVarInjectionException(designSystem, candidate, candidate.modifier.value)
) {
let { value, didChange: modifierDidChange } = injectVar(candidate.modifier.value)
candidate.modifier.value = value
didChange ||= modifierDidChange
}
for (let variant of candidate.variants) {
let didChangeVariant = injectVarIntoVariant(designSystem, variant)
if (didChangeVariant) {
didChange = true
}
}
if (
candidate.kind === 'arbitrary' &&
!isAutomaticVarInjectionException(designSystem, candidate, candidate.value)
) {
let { value, didChange: valueDidChange } = injectVar(candidate.value)
candidate.value = value
didChange ||= valueDidChange
}
if (
candidate.kind === 'functional' &&
candidate.value &&
candidate.value.kind === 'arbitrary' &&
!isAutomaticVarInjectionException(designSystem, candidate, candidate.value.value)
) {
let { value, didChange: valueDidChange } = injectVar(candidate.value.value)
candidate.value.value = value
didChange ||= valueDidChange
}
if (didChange) {
return designSystem.printCandidate(candidate)
}
}
return rawCandidate
}
function injectVar(value: string): { value: string; didChange: boolean } {
let didChange = false
if (value.startsWith('--')) {
if (
!value.includes('(') ||
ValueParser.parse(value)[0]?.kind !== 'function'
) {
value = `var(${value})`
didChange = true
}
} else if (value.startsWith(' --')) {
value = value.slice(1)
didChange = true
}
return { value, didChange }
}
function injectVarIntoVariant(designSystem: DesignSystem, variant: Variant): boolean {
let didChange = false
if (
variant.kind === 'functional' &&
variant.value &&
variant.value.kind === 'arbitrary' &&
!isAutomaticVarInjectionException(
designSystem,
createEmptyCandidate(variant),
variant.value.value,
)
) {
let { value, didChange: valueDidChange } = injectVar(variant.value.value)
variant.value.value = value
didChange ||= valueDidChange
}
if (variant.kind === 'compound') {
let compoundDidChange = injectVarIntoVariant(designSystem, variant.variant)
if (compoundDidChange) {
didChange = true
}
}
return didChange
}
function createEmptyCandidate(variant: Variant) {
return {
kind: 'arbitrary' as const,
property: 'color',
value: 'red',
modifier: null,
variants: [variant],
important: false,
raw: 'candidate',
} satisfies Candidate
}
const AUTO_VAR_INJECTION_EXCEPTIONS = new Set([
'scroll-timeline-name',
'timeline-scope',
'view-timeline-name',
'font-palette',
'anchor-name',
'anchor-scope',
'position-anchor',
'position-try-options',
'scroll-timeline',
'animation-timeline',
'view-timeline',
'position-try',
])
function isAutomaticVarInjectionException(
designSystem: DesignSystem,
candidate: Candidate,
value: string,
): boolean {
let ast = designSystem.compileAstNodes(candidate).map((n) => n.node)
let isException = false
walk(ast, (node) => {
if (
node.kind === 'declaration' &&
AUTO_VAR_INJECTION_EXCEPTIONS.has(node.property) &&
node.value == value
) {
isException = true
return WalkAction.Stop
}
})
return isException
}