import { decl } from '../../../../tailwindcss/src/ast'
import { parseCandidate, type Candidate } from '../../../../tailwindcss/src/candidate'
import type { Config } from '../../../../tailwindcss/src/compat/plugin-api'
import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
import { segment } from '../../../../tailwindcss/src/utils/segment'
import * as version from '../../utils/version'
let seenDesignSystems = new WeakSet<DesignSystem>()
export function migratePrefix(
designSystem: DesignSystem,
userConfig: Config | null,
rawCandidate: string,
): string {
if (!designSystem.theme.prefix) return rawCandidate
if (!userConfig) return rawCandidate
if (!version.isMajor(3)) return rawCandidate
if (!seenDesignSystems.has(designSystem)) {
designSystem.utilities.functional('group', (value) => [
decl('--phantom-class', 'group'),
decl('--phantom-modifier', value.modifier?.value),
])
designSystem.utilities.functional('peer', (value) => [
decl('--phantom-class', 'peer'),
decl('--phantom-modifier', value.modifier?.value),
])
seenDesignSystems.add(designSystem)
}
let v3Base = extractV3Base(designSystem, userConfig, rawCandidate)
if (!v3Base) return rawCandidate
let originalPrefix = designSystem.theme.prefix
let candidate: Candidate | null = null
try {
designSystem.theme.prefix = null
let unprefixedCandidate =
rawCandidate.slice(0, v3Base.start) + v3Base.base + rawCandidate.slice(v3Base.end)
let candidates = [...parseCandidate(unprefixedCandidate, designSystem)]
if (candidates.length > 0) {
candidate = candidates[0]
}
} finally {
designSystem.theme.prefix = originalPrefix
}
if (!candidate) return rawCandidate
return designSystem.printCandidate(candidate)
}
function extractV3Base(
designSystem: DesignSystem,
userConfig: Config,
rawCandidate: string,
): { base: string; start: number; end: number } | null {
if (!designSystem.theme.prefix) return null
if (!userConfig.prefix)
throw new Error(
'Could not find the Tailwind CSS v3 `prefix` configuration inside the JavaScript config.',
)
let rawVariants = segment(rawCandidate, ':')
let base = rawVariants.pop()!
let start = rawCandidate.length - base.length
let end = start + base.length
let important = false
let negative = false
if (base[base.length - 1] === '!') {
important = true
base = base.slice(0, -1)
}
else if (base[0] === '!') {
important = true
base = base.slice(1)
}
if (base[0] === '-') {
negative = true
base = base.slice(1)
}
if (!base.startsWith(userConfig.prefix) && base[0] !== '[') {
return null
} else {
if (base[0] !== '[') base = base.slice(userConfig.prefix.length)
if (negative) base = '-' + base
if (important) base += '!'
return {
base,
start,
end,
}
}
}
const VALID_PREFIX = /([a-z]+)/
export function migratePrefixValue(prefix: string): string {
let result = VALID_PREFIX.exec(prefix.toLocaleLowerCase())
if (!result) {
console.warn(
`The prefix "${prefix} can not be used with Tailwind CSS v4 and cannot be converted to a valid one automatically. We've updated it to "tw" for you.`,
)
return 'tw'
}
return result[0]
}