parse() — tailwindcss Function Reference
Architecture documentation for the parse() function in css-parser.ts from the tailwindcss codebase.
Entity Profile
Dependency Diagram
graph TD 257c4715_dc91_0c7f_fce8_433a757d9ce6["parse()"] ef1f735b_b130_6631_a9cc_e465ace5e479["rewriteUrls()"] ef1f735b_b130_6631_a9cc_e465ace5e479 -->|calls| 257c4715_dc91_0c7f_fce8_433a757d9ce6 8ea56f72_5e23_1ddc_863d_757d2deb5a0f["expand()"] 8ea56f72_5e23_1ddc_863d_757d2deb5a0f -->|calls| 257c4715_dc91_0c7f_fce8_433a757d9ce6 214d4226_762c_0b6a_7c0b_f917da98dda0["compile()"] 214d4226_762c_0b6a_7c0b_f917da98dda0 -->|calls| 257c4715_dc91_0c7f_fce8_433a757d9ce6 70b71984_f090_260f_6475_e3bfd4e6108f["__unstable__loadDesignSystem()"] 70b71984_f090_260f_6475_e3bfd4e6108f -->|calls| 257c4715_dc91_0c7f_fce8_433a757d9ce6 b5142dec_f0ec_66d6_59a1_82d3dbd6f965["analyze()"] b5142dec_f0ec_66d6_59a1_82d3dbd6f965 -->|calls| 257c4715_dc91_0c7f_fce8_433a757d9ce6 ec867cf3_916b_0d16_65ec_c715e69fee03["optimizeAst()"] ec867cf3_916b_0d16_65ec_c715e69fee03 -->|calls| 257c4715_dc91_0c7f_fce8_433a757d9ce6 761efede_46a5_5722_0699_0e1b0947c406["substituteAtImports()"] 761efede_46a5_5722_0699_0e1b0947c406 -->|calls| 257c4715_dc91_0c7f_fce8_433a757d9ce6 ad196438_55f7_af7b_1604_1d75c1c27d8e["buildPluginApi()"] ad196438_55f7_af7b_1604_1d75c1c27d8e -->|calls| 257c4715_dc91_0c7f_fce8_433a757d9ce6 3ec7100d_c372_c98a_2ba0_9ec5e70f401d["parseVariantValue()"] 3ec7100d_c372_c98a_2ba0_9ec5e70f401d -->|calls| 257c4715_dc91_0c7f_fce8_433a757d9ce6 9508f2e7_ca66_c4bb_0665_b6dc278d127a["replaceNestedClassNameReferences()"] 9508f2e7_ca66_c4bb_0665_b6dc278d127a -->|calls| 257c4715_dc91_0c7f_fce8_433a757d9ce6 dbaac8a3_b27b_2942_9a4d_7e9063a9649d["comment()"] 257c4715_dc91_0c7f_fce8_433a757d9ce6 -->|calls| dbaac8a3_b27b_2942_9a4d_7e9063a9649d 8cb27bb8_1671_6994_5a69_8a60c69825c4["parseString()"] 257c4715_dc91_0c7f_fce8_433a757d9ce6 -->|calls| 8cb27bb8_1671_6994_5a69_8a60c69825c4 b484f819_aa07_b471_8a80_2e952408f2db["parseDeclaration()"] 257c4715_dc91_0c7f_fce8_433a757d9ce6 -->|calls| b484f819_aa07_b471_8a80_2e952408f2db 5f0c52a3_326e_e124_2536_7e86fa5c6341["parseAtRule()"] 257c4715_dc91_0c7f_fce8_433a757d9ce6 -->|calls| 5f0c52a3_326e_e124_2536_7e86fa5c6341 style 257c4715_dc91_0c7f_fce8_433a757d9ce6 fill:#6366f1,stroke:#818cf8,color:#fff
Relationship Graph
Source Code
packages/tailwindcss/src/css-parser.ts lines 64–594
export function parse(input: string, opts?: ParseOptions) {
let source: Source | null = opts?.from ? { file: opts.from, code: input } : null
// Note: it is important that any transformations of the input string
// *before* processing do NOT change the length of the string. This
// would invalidate the mechanism used to track source locations.
if (input[0] === '\uFEFF') input = ' ' + input.slice(1)
let ast: AstNode[] = []
let licenseComments: Comment[] = []
let stack: (Rule | null)[] = []
let parent = null as Rule | null
let node = null as AstNode | null
let buffer = ''
let closingBracketStack = ''
// The start of the first non-whitespace character in the buffer
let bufferStart = 0
let peekChar
for (let i = 0; i < input.length; i++) {
let currentChar = input.charCodeAt(i)
// Skip over the CR in CRLF. This allows code below to only check for a line
// break even if we're looking at a Windows newline. Peeking the input still
// has to check for CRLF but that happens less often.
if (currentChar === CARRIAGE_RETURN) {
peekChar = input.charCodeAt(i + 1)
if (peekChar === LINE_BREAK) continue
}
// Current character is a `\` therefore the next character is escaped,
// consume it together with the next character and continue.
//
// E.g.:
//
// ```css
// .hover\:foo:hover {}
// ^
// ```
//
if (currentChar === BACKSLASH) {
if (buffer === '') bufferStart = i
buffer += input.slice(i, i + 2)
i += 1
}
// Start of a comment.
//
// E.g.:
//
// ```css
// /* Example */
// ^^^^^^^^^^^^^
// .foo {
// color: red; /* Example */
// ^^^^^^^^^^^^^
// }
// .bar {
// color: /* Example */ red;
// ^^^^^^^^^^^^^
// }
// ```
else if (currentChar === SLASH && input.charCodeAt(i + 1) === ASTERISK) {
let start = i
for (let j = i + 2; j < input.length; j++) {
peekChar = input.charCodeAt(j)
// Current character is a `\` therefore the next character is escaped.
if (peekChar === BACKSLASH) {
j += 1
}
// End of the comment
else if (peekChar === ASTERISK && input.charCodeAt(j + 1) === SLASH) {
i = j + 1
break
}
}
let commentString = input.slice(start, i + 1)
// Collect all license comments so that we can hoist them to the top of
// the AST.
if (commentString.charCodeAt(2) === EXCLAMATION_MARK) {
let node = comment(commentString.slice(2, -2))
licenseComments.push(node)
if (source) {
node.src = [source, start, i + 1]
node.dst = [source, start, i + 1]
}
}
}
// Start of a string.
else if (currentChar === SINGLE_QUOTE || currentChar === DOUBLE_QUOTE) {
let end = parseString(input, i, currentChar, source)
// Adjust `buffer` to include the string.
buffer += input.slice(i, end + 1)
i = end
}
// Skip whitespace if the next character is also whitespace. This allows us
// to reduce the amount of whitespace in the AST.
else if (
(currentChar === SPACE || currentChar === LINE_BREAK || currentChar === TAB) &&
(peekChar = input.charCodeAt(i + 1)) &&
(peekChar === SPACE ||
peekChar === LINE_BREAK ||
peekChar === TAB ||
(peekChar === CARRIAGE_RETURN &&
(peekChar = input.charCodeAt(i + 2)) &&
peekChar == LINE_BREAK))
) {
continue
}
// Replace new lines with spaces.
else if (currentChar === LINE_BREAK) {
if (buffer.length === 0) continue
peekChar = buffer.charCodeAt(buffer.length - 1)
if (peekChar !== SPACE && peekChar !== LINE_BREAK && peekChar !== TAB) {
buffer += ' '
}
}
// Start of a custom property.
//
// Custom properties are very permissive and can contain almost any
// character, even `;` and `}`. Therefore we have to make sure that we are
// at the correct "end" of the custom property by making sure everything is
// balanced.
else if (currentChar === DASH && input.charCodeAt(i + 1) === DASH && buffer.length === 0) {
let closingBracketStack = ''
let start = i
let colonIdx = -1
for (let j = i + 2; j < input.length; j++) {
peekChar = input.charCodeAt(j)
// Current character is a `\` therefore the next character is escaped.
if (peekChar === BACKSLASH) {
j += 1
}
// Start of a string.
else if (peekChar === SINGLE_QUOTE || peekChar === DOUBLE_QUOTE) {
j = parseString(input, j, peekChar, source)
}
// Start of a comment.
else if (peekChar === SLASH && input.charCodeAt(j + 1) === ASTERISK) {
for (let k = j + 2; k < input.length; k++) {
peekChar = input.charCodeAt(k)
// Current character is a `\` therefore the next character is escaped.
if (peekChar === BACKSLASH) {
k += 1
}
// End of the comment
else if (peekChar === ASTERISK && input.charCodeAt(k + 1) === SLASH) {
j = k + 1
break
}
}
}
// End of the "property" of the property-value pair.
else if (colonIdx === -1 && peekChar === COLON) {
colonIdx = buffer.length + j - start
}
// End of the custom property.
else if (peekChar === SEMICOLON && closingBracketStack.length === 0) {
buffer += input.slice(start, j)
i = j
break
}
// Start of a block.
else if (peekChar === OPEN_PAREN) {
closingBracketStack += ')'
} else if (peekChar === OPEN_BRACKET) {
closingBracketStack += ']'
} else if (peekChar === OPEN_CURLY) {
closingBracketStack += '}'
}
// End of the custom property if didn't use a `;` to end the custom
// property.
//
// E.g.:
//
// ```css
// .foo {
// --custom: value
// ^
// }
// ```
else if (
(peekChar === CLOSE_CURLY || input.length - 1 === j) &&
closingBracketStack.length === 0
) {
i = j - 1
buffer += input.slice(start, j)
break
}
// End of a block.
else if (
peekChar === CLOSE_PAREN ||
peekChar === CLOSE_BRACKET ||
peekChar === CLOSE_CURLY
) {
if (
closingBracketStack.length > 0 &&
input[j] === closingBracketStack[closingBracketStack.length - 1]
) {
closingBracketStack = closingBracketStack.slice(0, -1)
}
}
}
let declaration = parseDeclaration(buffer, colonIdx)
if (!declaration) {
throw new CssSyntaxError(
`Invalid custom property, expected a value`,
source ? [source, start, i] : null,
)
}
if (source) {
declaration.src = [source, start, i]
declaration.dst = [source, start, i]
}
if (parent) {
parent.nodes.push(declaration)
} else {
ast.push(declaration)
}
buffer = ''
}
// End of a body-less at-rule.
//
// E.g.:
//
// ```css
// @charset "UTF-8";
// ^
// ```
else if (currentChar === SEMICOLON && buffer.charCodeAt(0) === AT_SIGN) {
node = parseAtRule(buffer)
if (source) {
node.src = [source, bufferStart, i]
node.dst = [source, bufferStart, i]
}
// At-rule is nested inside of a rule, attach it to the parent.
if (parent) {
parent.nodes.push(node)
}
// We are the root node which means we are done with the current node.
else {
ast.push(node)
}
// Reset the state for the next node.
buffer = ''
node = null
}
// End of a declaration.
//
// E.g.:
//
// ```css
// .foo {
// color: red;
// ^
// }
// ```
//
else if (
currentChar === SEMICOLON &&
closingBracketStack[closingBracketStack.length - 1] !== ')'
) {
let declaration = parseDeclaration(buffer)
if (!declaration) {
if (buffer.length === 0) continue
throw new CssSyntaxError(
`Invalid declaration: \`${buffer.trim()}\``,
source ? [source, bufferStart, i] : null,
)
}
if (source) {
declaration.src = [source, bufferStart, i]
declaration.dst = [source, bufferStart, i]
}
if (parent) {
parent.nodes.push(declaration)
} else {
ast.push(declaration)
}
buffer = ''
}
// Start of a block.
else if (
currentChar === OPEN_CURLY &&
closingBracketStack[closingBracketStack.length - 1] !== ')'
) {
closingBracketStack += '}'
// At this point `buffer` should resemble a selector or an at-rule.
node = rule(buffer.trim())
// Track the source location for source maps
if (source) {
node.src = [source, bufferStart, i]
node.dst = [source, bufferStart, i]
}
// Attach the rule to the parent in case it's nested.
if (parent) {
parent.nodes.push(node)
}
// Push the parent node to the stack, so that we can go back once the
// nested nodes are done.
stack.push(parent)
// Make the current node the new parent, so that nested nodes can be
// attached to it.
parent = node
// Reset the state for the next node.
buffer = ''
node = null
}
// End of a block.
else if (
currentChar === CLOSE_CURLY &&
closingBracketStack[closingBracketStack.length - 1] !== ')'
) {
if (closingBracketStack === '') {
throw new CssSyntaxError('Missing opening {', source ? [source, i, i] : null)
}
closingBracketStack = closingBracketStack.slice(0, -1)
// When we hit a `}` and `buffer` is filled in, then it means that we did
// not complete the previous node yet. This means that we hit a
// declaration without a `;` at the end.
if (buffer.length > 0) {
// This can happen for nested at-rules.
//
// E.g.:
//
// ```css
// @layer foo {
// @tailwind utilities
// ^
// }
// ```
if (buffer.charCodeAt(0) === AT_SIGN) {
node = parseAtRule(buffer)
// Track the source location for source maps
if (source) {
node.src = [source, bufferStart, i]
node.dst = [source, bufferStart, i]
}
// At-rule is nested inside of a rule, attach it to the parent.
if (parent) {
parent.nodes.push(node)
}
// We are the root node which means we are done with the current node.
else {
ast.push(node)
}
// Reset the state for the next node.
buffer = ''
node = null
}
// But it can also happen for declarations.
//
// E.g.:
//
// ```css
// .foo {
// color: red
// ^
// }
// ```
else {
// Split `buffer` into a `property` and a `value`. At this point the
// comments are already removed which means that we don't have to worry
// about `:` inside of comments.
let colonIdx = buffer.indexOf(':')
// Attach the declaration to the parent.
if (parent) {
let node = parseDeclaration(buffer, colonIdx)
if (!node) {
throw new CssSyntaxError(
`Invalid declaration: \`${buffer.trim()}\``,
source ? [source, bufferStart, i] : null,
)
}
if (source) {
node.src = [source, bufferStart, i]
node.dst = [source, bufferStart, i]
}
parent.nodes.push(node)
}
}
}
// We are done with the current node, which means we can go up one level
// in the stack.
let grandParent = stack.pop() ?? null
// We are the root node which means we are done and continue with the next
// node.
if (grandParent === null && parent) {
ast.push(parent)
}
// Go up one level in the stack.
parent = grandParent
// Reset the state for the next node.
buffer = ''
node = null
}
// `(`
else if (currentChar === OPEN_PAREN) {
closingBracketStack += ')'
buffer += '('
}
// `)`
else if (currentChar === CLOSE_PAREN) {
if (closingBracketStack[closingBracketStack.length - 1] !== ')') {
throw new CssSyntaxError('Missing opening (', source ? [source, i, i] : null)
}
closingBracketStack = closingBracketStack.slice(0, -1)
buffer += ')'
}
// Any other character is part of the current node.
else {
// Skip whitespace at the start of a new node.
if (
buffer.length === 0 &&
(currentChar === SPACE || currentChar === LINE_BREAK || currentChar === TAB)
) {
continue
}
if (buffer === '') bufferStart = i
buffer += String.fromCharCode(currentChar)
}
}
// If we have a leftover `buffer` that happens to start with an `@` then it
// means that we have an at-rule that is not terminated with a semicolon at
// the end of the input.
if (buffer.charCodeAt(0) === AT_SIGN) {
let node = parseAtRule(buffer)
// Track the source location for source maps
if (source) {
node.src = [source, bufferStart, input.length]
node.dst = [source, bufferStart, input.length]
}
ast.push(node)
}
// When we are done parsing then everything should be balanced. If we still
// have a leftover `parent`, then it means that we have an unterminated block.
if (closingBracketStack.length > 0 && parent) {
if (parent.kind === 'rule') {
throw new CssSyntaxError(
`Missing closing } at ${parent.selector}`,
parent.src ? [parent.src[0], parent.src[1], parent.src[1]] : null,
)
}
if (parent.kind === 'at-rule') {
throw new CssSyntaxError(
`Missing closing } at ${parent.name} ${parent.params}`,
parent.src ? [parent.src[0], parent.src[1], parent.src[1]] : null,
)
}
}
if (licenseComments.length > 0) {
return (licenseComments as AstNode[]).concat(ast)
}
return ast
}
Domain
Subdomains
Called By
Source
Frequently Asked Questions
What does parse() do?
parse() is a function in the tailwindcss codebase.
What does parse() call?
parse() calls 5 function(s): comment, parseAtRule, parseDeclaration, parseString, rule.
What calls parse()?
parse() is called by 10 function(s): __unstable__loadDesignSystem, analyze, buildPluginApi, compile, expand, optimizeAst, parseVariantValue, replaceNestedClassNameReferences, and 2 more.
Analyze Your Own Codebase
Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.
Try Supermodel Free