Home / Function/ parseCandidate() — tailwindcss Function Reference

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

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