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
  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
  }
}

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 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