Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix crash when using `@source` containing `..` ([#14831](https://github.com/tailwindlabs/tailwindcss/pull/14831))
- Ensure instances of the same variant with different values are always sorted deterministically (e.g. `data-focus:flex` and `data-active:flex`) ([#14835](https://github.com/tailwindlabs/tailwindcss/pull/14835))
- Ensure `--inset-ring=*` and `--inset-shadow-*` variables are ignored by `inset-*` utilities ([#14855](https://github.com/tailwindlabs/tailwindcss/pull/14855))
- Fix parsing `url(…)` with special characters such as `;` or `{}` ([#14879](https://github.com/tailwindlabs/tailwindcss/pull/14879))
- _Upgrade (experimental)_: Install `@tailwindcss/postcss` next to `tailwindcss` ([#14830](https://github.com/tailwindlabs/tailwindcss/pull/14830))
- _Upgrade (experimental)_: Remove whitespace around `,` separator when print arbitrary values ([#14838](https://github.com/tailwindlabs/tailwindcss/pull/14838))

Expand Down
53 changes: 53 additions & 0 deletions packages/tailwindcss/src/css-parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,59 @@ describe.each(['Unix', 'Windows'])('Line endings: %s', (lineEndings) => {
},
])
})

it('should parse url(…) without quotes and special characters such as `;`, `{}`, and `[]`', () => {
expect(
parse(css`
.foo {
/* ';' should be valid inside the 'url(…)' function */
background: url(data:image/png;base64,abc==);

/* '{', '}', '[' and ']' should be valid inside the 'url(…)' function */
/* '{' and '}' should not start a new block (nesting) */
background: url(https://example-image-search.org?q={query;limit=5}&ids=[1,2,3]);

/* '{' and '}' don't need to be balanced */
background: url(https://example-image-search.org?curlies=}});

/* '(' and ')' are not valid, unless we are in a string with quotes */
background: url('https://example-image-search.org?q={query;limit=5}&ids=[1,2,3]&format=(png|jpg)');
}
`),
).toEqual([
{
kind: 'rule',
selector: '.foo',
nodes: [
{
kind: 'declaration',
property: 'background',
value: 'url(data:image/png;base64,abc==)',
important: false,
},
{
kind: 'declaration',
property: 'background',
value: 'url(https://example-image-search.org?q={query;limit=5}&ids=[1,2,3])',
important: false,
},
{
kind: 'declaration',
property: 'background',
value: 'url(https://example-image-search.org?curlies=}})',
important: false,
},
{
kind: 'declaration',
property: 'background',
value:
"url('https://example-image-search.org?q={query;limit=5}&ids=[1,2,3]&format=(png|jpg)')",
important: false,
},
],
},
])
})
})

describe('selectors', () => {
Expand Down
31 changes: 28 additions & 3 deletions packages/tailwindcss/src/css-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,10 @@ export function parse(input: string) {
// }
// ```
//
else if (currentChar === SEMICOLON) {
else if (
currentChar === SEMICOLON &&
closingBracketStack[closingBracketStack.length - 1] !== ')'
) {
let declaration = parseDeclaration(buffer)
if (parent) {
parent.nodes.push(declaration)
Expand All @@ -343,7 +346,10 @@ export function parse(input: string) {
}

// Start of a block.
else if (currentChar === OPEN_CURLY) {
else if (
currentChar === OPEN_CURLY &&
closingBracketStack[closingBracketStack.length - 1] !== ')'
) {
closingBracketStack += '}'

// At this point `buffer` should resemble a selector or an at-rule.
Expand All @@ -368,7 +374,10 @@ export function parse(input: string) {
}

// End of a block.
else if (currentChar === CLOSE_CURLY) {
else if (
currentChar === CLOSE_CURLY &&
closingBracketStack[closingBracketStack.length - 1] !== ')'
) {
if (closingBracketStack === '') {
throw new Error('Missing opening {')
}
Expand Down Expand Up @@ -456,6 +465,22 @@ export function parse(input: string) {
node = null
}

// `(`
else if (currentChar === OPEN_PAREN) {
closingBracketStack += ')'
buffer += '('
}

// `)`
else if (currentChar === CLOSE_PAREN) {
if (closingBracketStack[closingBracketStack.length - 1] !== ')') {
throw new Error('Missing opening (')
}

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.
Expand Down