Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions packages/tailwindcss/src/canonicalize-candidates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1099,10 +1099,14 @@ function allVariablesAreUsed(
walk(ValueParser.parse(value), (node) => {
if (node.kind === 'function' && node.value === 'var') {
let variable = node.nodes[0].value
let r = new RegExp(`var\\(${variable}[,)]\\s*`, 'g')
// Check if the variable is used in the replacement using string methods
// instead of RegExp to avoid the overhead of creating a RegExp per variable
let hasVarWithComma = replacementAsCss.includes(`var(${variable},`)
let hasVarWithParen = replacementAsCss.includes(`var(${variable})`)

if (
// We need to check if the variable is used in the replacement
!r.test(replacementAsCss) ||
!(hasVarWithComma || hasVarWithParen) ||
// The value cannot be set to a different value in the
// replacement because that would make it an unsafe migration
replacementAsCss.includes(`${variable}:`)
Expand Down
31 changes: 16 additions & 15 deletions packages/tailwindcss/src/utils/brace-expansion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,24 +68,25 @@ function expandSequence(seq: string): string[] {
let step = stepStr ? parseInt(stepStr, 10) : undefined
let result: string[] = []

if (/^-?\d+$/.test(start) && /^-?\d+$/.test(end)) {
let startNum = parseInt(start, 10)
let endNum = parseInt(end, 10)
// NUMERICAL_RANGE already ensures start and end are numbers (-?\d+)
// so no need to test again
let startNum = parseInt(start, 10)
let endNum = parseInt(end, 10)

if (step === undefined) {
step = startNum <= endNum ? 1 : -1
}
if (step === 0) {
throw new Error('Step cannot be zero in sequence expansion.')
}
if (step === undefined) {
step = startNum <= endNum ? 1 : -1
}
if (step === 0) {
throw new Error('Step cannot be zero in sequence expansion.')
}

let increasing = startNum < endNum
if (increasing && step < 0) step = -step
if (!increasing && step > 0) step = -step
let increasing = startNum < endNum
if (increasing && step < 0) step = -step
if (!increasing && step > 0) step = -step

for (let i = startNum; increasing ? i <= endNum : i >= endNum; i += step) {
result.push(i.toString())
}
for (let i = startNum; increasing ? i <= endNum : i >= endNum; i += step) {
result.push(i.toString())
}

return result
}
5 changes: 5 additions & 0 deletions packages/tailwindcss/src/utils/decode-arbitrary-value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ export function decodeArbitraryValue(input: string): string {
* converted to `_` instead.
*/
function convertUnderscoresToWhitespace(input: string, skipUnderscoreToSpace = false) {
// Fast path: if there are no underscores at all, return input as-is
if (input.indexOf('_') === -1) {
return input
}

let output = ''
for (let i = 0; i < input.length; i++) {
let char = input[i]
Expand Down
73 changes: 35 additions & 38 deletions packages/tailwindcss/src/utils/infer-data-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,11 @@ function isUrl(value: string): boolean {

/* -------------------------------------------------------------------------- */

const LINE_WIDTH_KEYWORDS = new Set(['thin', 'medium', 'thick'])

function isLineWidth(value: string): boolean {
return segment(value, ' ').every(
(value) =>
isLength(value) ||
isNumber(value) ||
value === 'thin' ||
value === 'medium' ||
value === 'thick',
(value) => isLength(value) || isNumber(value) || LINE_WIDTH_KEYWORDS.has(value),
)
}

Expand Down Expand Up @@ -111,22 +108,24 @@ function isImage(value: string) {

/* -------------------------------------------------------------------------- */

const GENERIC_NAMES = new Set([
'serif',
'sans-serif',
'monospace',
'cursive',
'fantasy',
'system-ui',
'ui-serif',
'ui-sans-serif',
'ui-monospace',
'ui-rounded',
'math',
'emoji',
'fangsong',
])

function isGenericName(value: string): boolean {
return (
value === 'serif' ||
value === 'sans-serif' ||
value === 'monospace' ||
value === 'cursive' ||
value === 'fantasy' ||
value === 'system-ui' ||
value === 'ui-serif' ||
value === 'ui-sans-serif' ||
value === 'ui-monospace' ||
value === 'ui-rounded' ||
value === 'math' ||
value === 'emoji' ||
value === 'fangsong'
)
return GENERIC_NAMES.has(value)
}

function isFamilyName(value: string): boolean {
Expand All @@ -145,17 +144,19 @@ function isFamilyName(value: string): boolean {
return count > 0
}

const ABSOLUTE_SIZES = new Set([
'xx-small',
'x-small',
'small',
'medium',
'large',
'x-large',
'xx-large',
'xxx-large',
])

function isAbsoluteSize(value: string): boolean {
return (
value === 'xx-small' ||
value === 'x-small' ||
value === 'small' ||
value === 'medium' ||
value === 'large' ||
value === 'x-large' ||
value === 'xx-large' ||
value === 'xxx-large'
)
return ABSOLUTE_SIZES.has(value)
}

function isRelativeSize(value: string): boolean {
Expand Down Expand Up @@ -239,17 +240,13 @@ export function isLength(value: string): boolean {

/* -------------------------------------------------------------------------- */

const BACKGROUND_POSITION_KEYWORDS = new Set(['center', 'top', 'right', 'bottom', 'left'])

function isBackgroundPosition(value: string): boolean {
let count = 0

for (let part of segment(value, ' ')) {
if (
part === 'center' ||
part === 'top' ||
part === 'right' ||
part === 'bottom' ||
part === 'left'
) {
if (BACKGROUND_POSITION_KEYWORDS.has(part)) {
count += 1
continue
}
Expand Down
25 changes: 22 additions & 3 deletions packages/tailwindcss/src/utils/is-color.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,26 @@ const NAMED_COLORS = new Set([
const IS_COLOR_FN = /^(rgba?|hsla?|hwb|color|(ok)?(lab|lch)|light-dark|color-mix)\(/i

export function isColor(value: string): boolean {
return (
value.charCodeAt(0) === HASH || IS_COLOR_FN.test(value) || NAMED_COLORS.has(value.toLowerCase())
)
// Fast path: check for hash first (hex colors)
if (value.charCodeAt(0) === HASH) return true

// Fast path: check for color functions
if (IS_COLOR_FN.test(value)) return true

// Check named colors - try lowercase first, then convert if needed
// Most values in practice are already lowercase
if (NAMED_COLORS.has(value)) return true

// Only convert to lowercase if the first check failed and value contains uppercase
// This avoids the toLowerCase() call in the common case
let hasUppercase = false
for (let i = 0; i < value.length; i++) {
let code = value.charCodeAt(i)
if (code >= 65 && code <= 90) { // A-Z
hasUppercase = true
break
}
}

return hasUppercase && NAMED_COLORS.has(value.toLowerCase())
}
36 changes: 34 additions & 2 deletions packages/tailwindcss/src/utils/math-operators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,44 @@ const MATH_FUNCTIONS = [
]

export function hasMathFn(input: string) {
return input.indexOf('(') !== -1 && MATH_FUNCTIONS.some((fn) => input.includes(`${fn}(`))
// Fast path: no opening paren means no function calls
if (input.indexOf('(') === -1) return false

// Check for math functions without creating template strings
for (let fn of MATH_FUNCTIONS) {
// Build the pattern to match: "fn("
// Look for the function name followed immediately by opening paren
let idx = 0
while ((idx = input.indexOf(fn, idx)) !== -1) {
// Check if the character after the function name is '('
// charCodeAt returns NaN for out-of-bounds, which won't equal OPEN_PAREN
if (input.charCodeAt(idx + fn.length) === OPEN_PAREN) {
return true
}
idx++
}
}

return false
}

export function addWhitespaceAroundMathOperators(input: string) {
// Bail early if there are no math functions in the input
if (!MATH_FUNCTIONS.some((fn) => input.includes(fn))) {
// Check for opening paren first as a fast path
if (input.indexOf('(') === -1) {
return input
}

// Check if any math function exists in the string
let hasMathFunction = false
for (let fn of MATH_FUNCTIONS) {
if (input.includes(fn)) {
hasMathFunction = true
break
}
}

if (!hasMathFunction) {
return input
}

Expand Down
2 changes: 1 addition & 1 deletion packages/tailwindcss/src/utils/to-key-path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export function toKeyPath(path: string) {
}

// Add the part after the last bracket as a key
if (currentIndex <= part.length - 1) {
if (currentIndex < part.length) {
keypath.push(part.slice(currentIndex))
}
}
Expand Down