import {
parseCandidate,
type Candidate,
type NamedUtilityValue,
} from '../../../../tailwindcss/src/candidate'
import type { Config } from '../../../../tailwindcss/src/compat/plugin-api'
import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
import {
isPositiveInteger,
isValidSpacingMultiplier,
} from '../../../../tailwindcss/src/utils/infer-data-type'
import { segment } from '../../../../tailwindcss/src/utils/segment'
import { walkVariants } from '../../utils/walk-variants'
import { computeUtilitySignature } from './signatures'
export function migrateArbitraryValueToBareValue(
designSystem: DesignSystem,
_userConfig: Config | null,
rawCandidate: string,
): string {
let signatures = computeUtilitySignature.get(designSystem)
for (let candidate of parseCandidate(rawCandidate, designSystem)) {
let clone = structuredClone(candidate)
let changed = false
if (clone.kind === 'functional' && clone.value?.kind === 'arbitrary') {
let expectedSignature = signatures.get(rawCandidate)
if (expectedSignature !== null) {
for (let value of tryValueReplacements(clone)) {
let newSignature = signatures.get(designSystem.printCandidate({ ...clone, value }))
if (newSignature === expectedSignature) {
changed = true
clone.value = value
break
}
}
}
}
for (let [variant] of walkVariants(clone)) {
if (
variant.kind === 'functional' &&
variant.root === 'data' &&
variant.value?.kind === 'arbitrary' &&
!variant.value.value.includes('=')
) {
changed = true
variant.value = {
kind: 'named',
value: variant.value.value,
}
}
else if (
variant.kind === 'functional' &&
variant.root === 'aria' &&
variant.value?.kind === 'arbitrary' &&
(variant.value.value.endsWith('=true') ||
variant.value.value.endsWith('="true"') ||
variant.value.value.endsWith("='true'"))
) {
let [key, _value] = segment(variant.value.value, '=')
if (
key[key.length - 1] === '~' ||
key[key.length - 1] === '|' ||
key[key.length - 1] === '^' ||
key[key.length - 1] === '$' ||
key[key.length - 1] === '*'
) {
continue
}
changed = true
variant.value = {
kind: 'named',
value: variant.value.value.slice(0, variant.value.value.indexOf('=')),
}
}
else if (
variant.kind === 'functional' &&
variant.root === 'supports' &&
variant.value?.kind === 'arbitrary' &&
/^[a-z-][a-z0-9-]*$/i.test(variant.value.value)
) {
changed = true
variant.value = {
kind: 'named',
value: variant.value.value,
}
}
}
return changed ? designSystem.printCandidate(clone) : rawCandidate
}
return rawCandidate
}
function* tryValueReplacements(
candidate: Extract<Candidate, { kind: 'functional' }>,
value: string = candidate.value?.value ?? '',
seen: Set<string> = new Set(),
): Generator<NamedUtilityValue> {
if (seen.has(value)) return
seen.add(value)
yield {
kind: 'named',
value,
fraction: null,
}
if (value.endsWith('%') && isValidSpacingMultiplier(value.slice(0, -1))) {
yield {
kind: 'named',
value: value.slice(0, -1),
fraction: null,
}
}
if (value.includes('/')) {
let [numerator, denominator] = value.split('/')
if (isPositiveInteger(numerator) && isPositiveInteger(denominator)) {
yield {
kind: 'named',
value: numerator,
fraction: `${numerator}/${denominator}`,
}
}
}
let allNumbersAndFractions = new Set<string>()
for (let match of value.matchAll(/(\d+\/\d+)|(\d+\.?\d+)/g)) {
allNumbersAndFractions.add(match[0].trim())
}
let options = Array.from(allNumbersAndFractions).sort((a, z) => {
return a.length - z.length
})
for (let option of options) {
yield* tryValueReplacements(candidate, option, seen)
}
}