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
1 change: 1 addition & 0 deletions packages/tailwindcss/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@
"@jridgewell/remapping": "^2.3.5",
"@tailwindcss/oxide": "workspace:^",
"@types/node": "catalog:",
"brace-expansion": "^5.0.4",
"dedent": "1.7.1",
"lightningcss": "catalog:",
"magic-string": "^0.30.21",
Expand Down
19 changes: 11 additions & 8 deletions packages/tailwindcss/src/utils/brace-expansion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ describe('expand(…)', () => {
['a/{10..0..5}/b', ['a/10/b', 'a/5/b', 'a/0/b']],
['a/{10..0..-5}/b', ['a/0/b', 'a/5/b', 'a/10/b']],

// Numeric range with padding (we do not support padding)
['a/{00..05}/b', ['a/0/b', 'a/1/b', 'a/2/b', 'a/3/b', 'a/4/b', 'a/5/b']],
['a{001..9}b', ['a1b', 'a2b', 'a3b', 'a4b', 'a5b', 'a6b', 'a7b', 'a8b', 'a9b']],
// Numeric range with zero-padding
['a/{00..05}/b', ['a/00/b', 'a/01/b', 'a/02/b', 'a/03/b', 'a/04/b', 'a/05/b']],
[
'a{001..9}b',
['a001b', 'a002b', 'a003b', 'a004b', 'a005b', 'a006b', 'a007b', 'a008b', 'a009b'],
],

// Numeric range with step
['a/{0..5..2}/b', ['a/0/b', 'a/2/b', 'a/4/b']],
Expand Down Expand Up @@ -61,15 +64,15 @@ describe('expand(…)', () => {
],
],

// Should not try to expand ranges with decimals
['{1.1..2.2}', ['1.1..2.2']],
// Decimal ranges are not expanded; braces are preserved as a literal
['{1.1..2.2}', ['{1.1..2.2}']],
])('should expand %s (%#)', (input, expected) => {
expect(expand(input).sort()).toEqual(expected.sort())
})

test('throws on unbalanced braces', () => {
expect(() => expand('a{b,c{d,e},{f,g}h}x{y,z')).toThrowErrorMatchingInlineSnapshot(
`[Error: The pattern \`x{y,z\` is not balanced.]`,
test('gracefully handles unbalanced braces', () => {
expect(expand('a{b,c{d,e},{f,g}h}x{y,z').sort()).toEqual(
['abx{y,z', 'acdx{y,z', 'acex{y,z', 'afhx{y,z', 'aghx{y,z'].sort(),
)
})

Expand Down
91 changes: 5 additions & 86 deletions packages/tailwindcss/src/utils/brace-expansion.ts
Original file line number Diff line number Diff line change
@@ -1,91 +1,10 @@
import { segment } from './segment'
import { expand as braceExpand } from 'brace-expansion'

const NUMERICAL_RANGE = /^(-?\d+)\.\.(-?\d+)(?:\.\.(-?\d+))?$/
const ZERO_STEP = /\{-?\d+\.\.-?\d+\.\.0+\}/

export function expand(pattern: string): string[] {
let index = pattern.indexOf('{')
if (index === -1) return [pattern]

let result: string[] = []
let pre = pattern.slice(0, index)
let rest = pattern.slice(index)

// Find the matching closing brace
let depth = 0
let endIndex = rest.lastIndexOf('}')
for (let i = 0; i < rest.length; i++) {
let char = rest[i]
if (char === '{') {
depth++
} else if (char === '}') {
depth--
if (depth === 0) {
endIndex = i
break
}
}
}

if (endIndex === -1) {
throw new Error(`The pattern \`${pattern}\` is not balanced.`)
}

let inside = rest.slice(1, endIndex)
let post = rest.slice(endIndex + 1)
let parts: string[]

if (isSequence(inside)) {
parts = expandSequence(inside)
} else {
parts = segment(inside, ',')
}

parts = parts.flatMap((part) => expand(part))

let expandedTail = expand(post)

for (let tail of expandedTail) {
for (let part of parts) {
result.push(pre + part + tail)
}
}
return result
}

function isSequence(str: string): boolean {
return NUMERICAL_RANGE.test(str)
}

/**
* Expands a sequence string like "01..20" (optionally with a step).
*/
function expandSequence(seq: string): string[] {
let seqMatch = seq.match(NUMERICAL_RANGE)
if (!seqMatch) {
return [seq]
}
let [, start, end, stepStr] = seqMatch
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)

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

for (let i = startNum; increasing ? i <= endNum : i >= endNum; i += step) {
result.push(i.toString())
}
if (ZERO_STEP.test(pattern)) {
throw new Error('Step cannot be zero in sequence expansion.')
}
return result
return braceExpand(pattern)
}
17 changes: 17 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading