parseCandidate() — tailwindcss Function Reference
Architecture documentation for the parseCandidate() function in candidate.ts from the tailwindcss codebase.
Entity Profile
Dependency Diagram
graph TD 0bec5ca9_74c8_dcc7_ec12_6404fb6493bd["parseCandidate()"] 5f08487b_feab_b37d_a44f_5e13d0aaf1a6["parseCandidate()"] 5f08487b_feab_b37d_a44f_5e13d0aaf1a6 -->|calls| 0bec5ca9_74c8_dcc7_ec12_6404fb6493bd aca11807_c543_c2c2_e4e5_c0f3adcb5924["isSafeMigration()"] aca11807_c543_c2c2_e4e5_c0f3adcb5924 -->|calls| 0bec5ca9_74c8_dcc7_ec12_6404fb6493bd 0d34c838_b3bc_b388_6b0d_17fe24312e5f["migrateAutomaticVarInjection()"] 0d34c838_b3bc_b388_6b0d_17fe24312e5f -->|calls| 0bec5ca9_74c8_dcc7_ec12_6404fb6493bd 4d31f3a8_5ec2_6651_828a_48dc621934f5["migrateCamelcaseInNamedValue()"] 4d31f3a8_5ec2_6651_828a_48dc621934f5 -->|calls| 0bec5ca9_74c8_dcc7_ec12_6404fb6493bd c54c0b46_6508_253f_e6ff_d96bcc4246d5["migrateLegacyArbitraryValues()"] c54c0b46_6508_253f_e6ff_d96bcc4246d5 -->|calls| 0bec5ca9_74c8_dcc7_ec12_6404fb6493bd 042344d6_c400_b6f0_00d2_ff8fded428bc["migrateModernizeArbitraryValues()"] 042344d6_c400_b6f0_00d2_ff8fded428bc -->|calls| 0bec5ca9_74c8_dcc7_ec12_6404fb6493bd f18be462_61dc_36f7_156d_7e1323554ba7["migratePrefix()"] f18be462_61dc_36f7_156d_7e1323554ba7 -->|calls| 0bec5ca9_74c8_dcc7_ec12_6404fb6493bd 52b790cf_9f93_aadf_60b7_6333be12e6cb["migrateVariantOrder()"] 52b790cf_9f93_aadf_60b7_6333be12e6cb -->|calls| 0bec5ca9_74c8_dcc7_ec12_6404fb6493bd c98bf5b0_e31e_f92d_3810_96d1e1308d34["parseCandidate()"] c98bf5b0_e31e_f92d_3810_96d1e1308d34 -->|calls| 0bec5ca9_74c8_dcc7_ec12_6404fb6493bd 19c89ebd_512f_276a_754b_c043c41d7bd4["compileCandidates()"] 19c89ebd_512f_276a_754b_c043c41d7bd4 -->|calls| 0bec5ca9_74c8_dcc7_ec12_6404fb6493bd c58d3214_88d6_f4fc_257f_8e84def5b24f["buildDesignSystem()"] c58d3214_88d6_f4fc_257f_8e84def5b24f -->|calls| 0bec5ca9_74c8_dcc7_ec12_6404fb6493bd 707ef5d3_1654_7094_06d2_99e8004cab43["migrateArbitraryVariants()"] 707ef5d3_1654_7094_06d2_99e8004cab43 -->|calls| 0bec5ca9_74c8_dcc7_ec12_6404fb6493bd 03b8d706_a876_a776_0056_186ced5d6067["segment()"] 0bec5ca9_74c8_dcc7_ec12_6404fb6493bd -->|calls| 03b8d706_a876_a776_0056_186ced5d6067 ceb1ff41_a641_27dd_6289_7f779710b411["parseVariant()"] 0bec5ca9_74c8_dcc7_ec12_6404fb6493bd -->|calls| ceb1ff41_a641_27dd_6289_7f779710b411 style 0bec5ca9_74c8_dcc7_ec12_6404fb6493bd 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 12 function(s): buildDesignSystem, compileCandidates, isSafeMigration, migrateArbitraryVariants, migrateAutomaticVarInjection, migrateCamelcaseInNamedValue, migrateLegacyArbitraryValues, migrateModernizeArbitraryValues, and 4 more.
Analyze Your Own Codebase
Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.
Try Supermodel Free