parseCandidate() — tailwindcss Function Reference
Architecture documentation for the parseCandidate() function in candidate.ts from the tailwindcss codebase.
Entity Profile
Dependency Diagram
graph TD 53cf41fe_5903_d247_3bb3_38414ba7d631["parseCandidate()"] 51eaaf36_2d89_536b_61ce_4a6fd0d89f26["parseCandidate()"] 51eaaf36_2d89_536b_61ce_4a6fd0d89f26 -->|calls| 53cf41fe_5903_d247_3bb3_38414ba7d631 15780a15_d07b_4940_2fae_2b625b686371["isSafeMigration()"] 15780a15_d07b_4940_2fae_2b625b686371 -->|calls| 53cf41fe_5903_d247_3bb3_38414ba7d631 39857840_c6e7_0594_122a_445c1ca9d108["migrateAutomaticVarInjection()"] 39857840_c6e7_0594_122a_445c1ca9d108 -->|calls| 53cf41fe_5903_d247_3bb3_38414ba7d631 dea7ced9_f1f5_2ff1_f4bc_f35a2d35ce09["migrateCamelcaseInNamedValue()"] dea7ced9_f1f5_2ff1_f4bc_f35a2d35ce09 -->|calls| 53cf41fe_5903_d247_3bb3_38414ba7d631 e186eef0_e5f2_5d47_3825_19ab57be2de9["migrateLegacyArbitraryValues()"] e186eef0_e5f2_5d47_3825_19ab57be2de9 -->|calls| 53cf41fe_5903_d247_3bb3_38414ba7d631 486b36da_67cd_fb5a_cf7f_c5aca1c480be["migrateModernizeArbitraryValues()"] 486b36da_67cd_fb5a_cf7f_c5aca1c480be -->|calls| 53cf41fe_5903_d247_3bb3_38414ba7d631 faeb1e90_cf89_2804_6ef1_386b3a9ffaba["migratePrefix()"] faeb1e90_cf89_2804_6ef1_386b3a9ffaba -->|calls| 53cf41fe_5903_d247_3bb3_38414ba7d631 0b5eaab6_9345_179e_f96e_cde6155e1ac1["migrateVariantOrder()"] 0b5eaab6_9345_179e_f96e_cde6155e1ac1 -->|calls| 53cf41fe_5903_d247_3bb3_38414ba7d631 74157cfe_4a6c_d75a_a5ac_16fa6909752f["parseCandidate()"] 74157cfe_4a6c_d75a_a5ac_16fa6909752f -->|calls| 53cf41fe_5903_d247_3bb3_38414ba7d631 cbdeac33_581c_3396_0f10_877935a68176["compileCandidates()"] cbdeac33_581c_3396_0f10_877935a68176 -->|calls| 53cf41fe_5903_d247_3bb3_38414ba7d631 cebe77e1_f0f2_aeee_417e_2192f5790344["buildDesignSystem()"] cebe77e1_f0f2_aeee_417e_2192f5790344 -->|calls| 53cf41fe_5903_d247_3bb3_38414ba7d631 2a20ea29_c850_cd61_5600_aeebbe3dda66["segment()"] 53cf41fe_5903_d247_3bb3_38414ba7d631 -->|calls| 2a20ea29_c850_cd61_5600_aeebbe3dda66 ca76ae68_c9c0_d977_a6d8_8ba86685bf25["parseVariant()"] 53cf41fe_5903_d247_3bb3_38414ba7d631 -->|calls| ca76ae68_c9c0_d977_a6d8_8ba86685bf25 db928a82_c6a9_f0a7_efee_5436571565b0["parseModifier()"] 53cf41fe_5903_d247_3bb3_38414ba7d631 -->|calls| db928a82_c6a9_f0a7_efee_5436571565b0 style 53cf41fe_5903_d247_3bb3_38414ba7d631 fill:#6366f1,stroke:#818cf8,color:#fff
Relationship Graph
Source Code
packages/tailwindcss/src/candidate.ts lines 316–612
export function* parseCandidate(input: string, designSystem: DesignSystem): Iterable<Candidate> {
// hover:focus:underline
// ^^^^^ ^^^^^^ -> Variants
// ^^^^^^^^^ -> Base
let rawVariants = segment(input, ':')
// A prefix is a special variant used to prefix all utilities. When present,
// all utilities must start with that variant which we will then remove from
// the variant list so no other part of the codebase has to know about it.
if (designSystem.theme.prefix) {
if (rawVariants.length === 1) return null
if (rawVariants[0] !== designSystem.theme.prefix) return null
rawVariants.shift()
}
// Safety: At this point it is safe to use TypeScript's non-null assertion
// operator because even if the `input` was an empty string, splitting an
// empty string by `:` will always result in an array with at least one
// element.
let base = rawVariants.pop()!
let parsedCandidateVariants: Variant[] = []
for (let i = rawVariants.length - 1; i >= 0; --i) {
let parsedVariant = designSystem.parseVariant(rawVariants[i])
if (parsedVariant === null) return
parsedCandidateVariants.push(parsedVariant)
}
let important = false
// Candidates that end with an exclamation mark are the important version with
// higher specificity of the non-important candidate, e.g. `mx-4!`.
if (base[base.length - 1] === '!') {
important = true
base = base.slice(0, -1)
}
// Legacy syntax with leading `!`, e.g. `!mx-4`.
else if (base[0] === '!') {
important = true
base = base.slice(1)
}
// Check for an exact match of a static utility first as long as it does not
// look like an arbitrary value.
if (designSystem.utilities.has(base, 'static') && !base.includes('[')) {
yield {
kind: 'static',
root: base,
variants: parsedCandidateVariants,
important,
raw: input,
}
}
// Figure out the new base and the modifier segment if present.
//
// E.g.:
//
// ```
// bg-red-500/50
// ^^^^^^^^^^ -> Base without modifier
// ^^ -> Modifier segment
// ```
let [baseWithoutModifier, modifierSegment = null, additionalModifier] = segment(base, '/')
// If there's more than one modifier, the utility is invalid.
//
// E.g.:
//
// - `bg-red-500/50/50`
if (additionalModifier) return
let parsedModifier = modifierSegment === null ? null : parseModifier(modifierSegment)
// Empty arbitrary values are invalid. E.g.: `[color:red]/[]` or `[color:red]/()`.
// ^^ ^^
// `bg-[#0088cc]/[]` or `bg-[#0088cc]/()`.
// ^^ ^^
if (modifierSegment !== null && parsedModifier === null) return
// Arbitrary properties
if (baseWithoutModifier[0] === '[') {
// Arbitrary properties should end with a `]`.
if (baseWithoutModifier[baseWithoutModifier.length - 1] !== ']') return
// The property part of the arbitrary property can only start with a-z
// lowercase or a dash `-` in case of vendor prefixes such as `-webkit-`
// or `-moz-`.
//
// Otherwise, it is an invalid candidate, and skip continue parsing.
let charCode = baseWithoutModifier.charCodeAt(1)
if (charCode !== DASH && !(charCode >= LOWER_A && charCode <= LOWER_Z)) {
return
}
baseWithoutModifier = baseWithoutModifier.slice(1, -1)
// Arbitrary properties consist of a property and a value separated by a
// `:`. If the `:` cannot be found, then it is an invalid candidate, and we
// can skip continue parsing.
//
// Since the property and the value should be separated by a `:`, we can
// also verify that the colon is not the first or last character in the
// candidate, because that would make it invalid as well.
let idx = baseWithoutModifier.indexOf(':')
if (idx === -1 || idx === 0 || idx === baseWithoutModifier.length - 1) return
let property = baseWithoutModifier.slice(0, idx)
let value = decodeArbitraryValue(baseWithoutModifier.slice(idx + 1))
// Values can't contain `;` or `}` characters at the top-level.
if (!isValidArbitrary(value)) return
yield {
kind: 'arbitrary',
property,
value,
modifier: parsedModifier,
variants: parsedCandidateVariants,
important,
raw: input,
}
return
}
// The different "versions"" of a candidate that are utilities
// e.g. `['bg', 'red-500']` and `['bg-red', '500']`
let roots: Iterable<Root>
// If the base of the utility ends with a `]`, then we know it's an arbitrary
// value. This also means that everything before the `[…]` part should be the
// root of the utility.
//
// E.g.:
//
// ```
// bg-[#0088cc]
// ^^ -> Root
// ^^^^^^^^^ -> Arbitrary value
//
// border-l-[#0088cc]
// ^^^^^^^^ -> Root
// ^^^^^^^^^ -> Arbitrary value
// ```
if (baseWithoutModifier[baseWithoutModifier.length - 1] === ']') {
let idx = baseWithoutModifier.indexOf('-[')
if (idx === -1) return
let root = baseWithoutModifier.slice(0, idx)
// The root of the utility should exist as-is in the utilities map. If not,
// it's an invalid utility and we can skip continue parsing.
if (!designSystem.utilities.has(root, 'functional')) return
let value = baseWithoutModifier.slice(idx + 1)
roots = [[root, value]]
}
// If the base of the utility ends with a `)`, then we know it's an arbitrary
// value that encapsulates a CSS variable. This also means that everything
// before the `(…)` part should be the root of the utility.
//
// E.g.:
//
// ```
// bg-(--my-var)
// ^^ -> Root
// ^^^^^^^^^^ -> Arbitrary value
// ```
else if (baseWithoutModifier[baseWithoutModifier.length - 1] === ')') {
let idx = baseWithoutModifier.indexOf('-(')
if (idx === -1) return
let root = baseWithoutModifier.slice(0, idx)
// The root of the utility should exist as-is in the utilities map. If not,
// it's an invalid utility and we can skip continue parsing.
if (!designSystem.utilities.has(root, 'functional')) return
let value = baseWithoutModifier.slice(idx + 2, -1)
let parts = segment(value, ':')
let dataType = null
if (parts.length === 2) {
dataType = parts[0]
value = parts[1]
}
// An arbitrary value with `(…)` should always start with `--` since it
// represents a CSS variable.
if (value[0] !== '-' || value[1] !== '-') return
// Values can't contain `;` or `}` characters at the top-level.
if (!isValidArbitrary(value)) return
roots = [[root, dataType === null ? `[var(${value})]` : `[${dataType}:var(${value})]`]]
}
// Not an arbitrary value
else {
roots = findRoots(baseWithoutModifier, (root: string) => {
return designSystem.utilities.has(root, 'functional')
})
}
for (let [root, value] of roots) {
let candidate: Candidate = {
kind: 'functional',
root,
modifier: parsedModifier,
value: null,
variants: parsedCandidateVariants,
important,
raw: input,
}
if (value === null) {
yield candidate
continue
}
{
let startArbitraryIdx = value.indexOf('[')
let valueIsArbitrary = startArbitraryIdx !== -1
if (valueIsArbitrary) {
// Arbitrary values must end with a `]`.
if (value[value.length - 1] !== ']') return
let arbitraryValue = decodeArbitraryValue(value.slice(startArbitraryIdx + 1, -1))
// Values can't contain `;` or `}` characters at the top-level.
if (!isValidArbitrary(arbitraryValue)) continue
// Extract an explicit typehint if present, e.g. `bg-[color:var(--my-var)])`
let typehint: string | null = null
for (let i = 0; i < arbitraryValue.length; i++) {
let code = arbitraryValue.charCodeAt(i)
// If we hit a ":", we're at the end of a typehint.
if (code === COLON) {
typehint = arbitraryValue.slice(0, i)
arbitraryValue = arbitraryValue.slice(i + 1)
break
}
// Keep iterating as long as we've only seen valid typehint characters.
if (code === DASH || (code >= LOWER_A && code <= LOWER_Z)) {
continue
}
// If we see any other character, there's no typehint so break early.
break
}
// Empty arbitrary values are invalid. E.g.: `p-[]`
// ^^
if (arbitraryValue.length === 0 || arbitraryValue.trim().length === 0) {
continue
}
if (typehint === '') continue
candidate.value = {
kind: 'arbitrary',
dataType: typehint || null,
value: arbitraryValue,
}
} else {
// Some utilities support fractions as values, e.g. `w-1/2`. Since it's
// ambiguous whether the slash signals a modifier or not, we store the
// fraction separately in case the utility matcher is interested in it.
let fraction =
modifierSegment === null || candidate.modifier?.kind === 'arbitrary'
? null
: `${value}/${modifierSegment}`
if (!IS_VALID_NAMED_VALUE.test(value)) continue
candidate.value = {
kind: 'named',
value,
fraction,
}
}
}
yield candidate
}
}
Domain
Subdomains
Called By
Source
Frequently Asked Questions
What does parseCandidate() do?
parseCandidate() is a function in the tailwindcss codebase.
What does parseCandidate() call?
parseCandidate() calls 6 function(s): decodeArbitraryValue, findRoots, isValidArbitrary, parseModifier, parseVariant, segment.
What calls parseCandidate()?
parseCandidate() is called by 11 function(s): buildDesignSystem, compileCandidates, isSafeMigration, migrateAutomaticVarInjection, migrateCamelcaseInNamedValue, migrateLegacyArbitraryValues, migrateModernizeArbitraryValues, migratePrefix, and 3 more.
Analyze Your Own Codebase
Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.
Try Supermodel Free