mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Ensure strings are consumed as-is when using internal segment() (#13608)
* ensure we handle strings as-in When encountering strings when using `segment` we didn't really treat them as actual strings. This means that if you used any parens, brackets, or curlies then we wanted them to be properly balanced. This should not be the case, whenever we encounter a string, we want to consume it as-is and don't want to worry about bracket balancing. We will now consume it until the end of the string (and make sure that escaped closing quotes are not seen as real closing quotes). * update changelog * drop unnecessary test Already had this test * ensure we utilities and variants defined * add example test that parses with unbalanced brackets inside quotes * improve changelog entry * hoist comment
This commit is contained in:
parent
719c0d4883
commit
cb17447ff1
@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Fixed
|
||||
|
||||
- Make sure `contain-*` utility variables resolve to a valid value ([#13521](https://github.com/tailwindlabs/tailwindcss/pull/13521))
|
||||
- Support unbalanced parentheses and braces in quotes in arbitrary values and variants ([#13608](https://github.com/tailwindlabs/tailwindcss/pull/13608))
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
@ -1031,5 +1031,40 @@ it('should parse arbitrary properties that are important and using stacked arbit
|
||||
})
|
||||
|
||||
it('should not parse compound group with a non-compoundable variant', () => {
|
||||
expect(run('group-*:flex')).toMatchInlineSnapshot(`null`)
|
||||
let utilities = new Utilities()
|
||||
utilities.static('flex', () => [])
|
||||
|
||||
let variants = new Variants()
|
||||
variants.compound('group', () => {})
|
||||
|
||||
expect(run('group-*:flex', { utilities, variants })).toMatchInlineSnapshot(`null`)
|
||||
})
|
||||
|
||||
it('should parse a variant containing an arbitrary string with unbalanced parens, brackets, curlies and other quotes', () => {
|
||||
let utilities = new Utilities()
|
||||
utilities.static('flex', () => [])
|
||||
|
||||
let variants = new Variants()
|
||||
variants.functional('string', () => {})
|
||||
|
||||
expect(run(`string-['}[("\\'']:flex`, { utilities, variants })).toMatchInlineSnapshot(`
|
||||
{
|
||||
"important": false,
|
||||
"kind": "static",
|
||||
"negative": false,
|
||||
"root": "flex",
|
||||
"variants": [
|
||||
{
|
||||
"compounds": true,
|
||||
"kind": "functional",
|
||||
"modifier": null,
|
||||
"root": "string",
|
||||
"value": {
|
||||
"kind": "arbitrary",
|
||||
"value": "'}[("\\''",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
@ -21,6 +21,30 @@ it('should not split inside of curlies', () => {
|
||||
expect(segment('a:{b:c}:d', ':')).toEqual(['a', '{b:c}', 'd'])
|
||||
})
|
||||
|
||||
it('should not split inside of double quotes', () => {
|
||||
expect(segment('a:"b:c":d', ':')).toEqual(['a', '"b:c"', 'd'])
|
||||
})
|
||||
|
||||
it('should not split inside of single quotes', () => {
|
||||
expect(segment("a:'b:c':d", ':')).toEqual(['a', "'b:c'", 'd'])
|
||||
})
|
||||
|
||||
it('should not crash when double quotes are unbalanced', () => {
|
||||
expect(segment('a:"b:c:d', ':')).toEqual(['a', '"b:c:d'])
|
||||
})
|
||||
|
||||
it('should not crash when single quotes are unbalanced', () => {
|
||||
expect(segment("a:'b:c:d", ':')).toEqual(['a', "'b:c:d"])
|
||||
})
|
||||
|
||||
it('should skip escaped double quotes', () => {
|
||||
expect(segment(String.raw`a:"b:c\":d":e`, ':')).toEqual(['a', String.raw`"b:c\":d"`, 'e'])
|
||||
})
|
||||
|
||||
it('should skip escaped single quotes', () => {
|
||||
expect(segment(String.raw`a:'b:c\':d':e`, ':')).toEqual(['a', String.raw`'b:c\':d'`, 'e'])
|
||||
})
|
||||
|
||||
it('should split by the escape sequence which is escape as well', () => {
|
||||
expect(segment('a\\b\\c\\d', '\\')).toEqual(['a', 'b', 'c', 'd'])
|
||||
expect(segment('a\\(b\\c)\\d', '\\')).toEqual(['a', '(b\\c)', 'd'])
|
||||
|
||||
@ -5,6 +5,8 @@ const OPEN_PAREN = 0x28
|
||||
const CLOSE_PAREN = 0x29
|
||||
const OPEN_BRACKET = 0x5b
|
||||
const CLOSE_BRACKET = 0x5d
|
||||
const DOUBLE_QUOTE = 0x22
|
||||
const SINGLE_QUOTE = 0x27
|
||||
|
||||
// This is a shared buffer that is used to keep track of the current nesting level
|
||||
// of parens, brackets, and braces. It is used to determine if a character is at
|
||||
@ -30,10 +32,11 @@ export function segment(input: string, separator: string) {
|
||||
let stackPos = 0
|
||||
let parts: string[] = []
|
||||
let lastPos = 0
|
||||
let len = input.length
|
||||
|
||||
let separatorCode = separator.charCodeAt(0)
|
||||
|
||||
for (let idx = 0; idx < input.length; idx++) {
|
||||
for (let idx = 0; idx < len; idx++) {
|
||||
let char = input.charCodeAt(idx)
|
||||
|
||||
if (stackPos === 0 && char === separatorCode) {
|
||||
@ -47,6 +50,25 @@ export function segment(input: string, separator: string) {
|
||||
// The next character is escaped, so we skip it.
|
||||
idx += 1
|
||||
break
|
||||
// Strings should be handled as-is until the end of the string. No need to
|
||||
// worry about balancing parens, brackets, or curlies inside a string.
|
||||
case SINGLE_QUOTE:
|
||||
case DOUBLE_QUOTE:
|
||||
// Ensure we don't go out of bounds.
|
||||
while (++idx < len) {
|
||||
let nextChar = input.charCodeAt(idx)
|
||||
|
||||
// The next character is escaped, so we skip it.
|
||||
if (nextChar === BACKSLASH) {
|
||||
idx += 1
|
||||
continue
|
||||
}
|
||||
|
||||
if (nextChar === char) {
|
||||
break
|
||||
}
|
||||
}
|
||||
break
|
||||
case OPEN_PAREN:
|
||||
closingBracketStack[stackPos] = CLOSE_PAREN
|
||||
stackPos++
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user