Skip to content

Commit c6a3a4d

Browse files
committed
handle & and * in selector parser as standalone selector
We were also converting the current charcode back to a string, but we already had that information in `input[i]`
1 parent 2f0929a commit c6a3a4d

File tree

2 files changed

+57
-4
lines changed

2 files changed

+57
-4
lines changed

packages/tailwindcss/src/compat/selector-parser.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,34 @@ describe('parse', () => {
131131
},
132132
])
133133
})
134+
135+
it('parses nesting selector before attribute selector', () => {
136+
expect(parse('&[data-foo]')).toEqual([
137+
{ kind: 'selector', value: '&' },
138+
{ kind: 'selector', value: '[data-foo]' },
139+
])
140+
})
141+
142+
it('parses nesting selector after an attribute selector', () => {
143+
expect(parse('[data-foo]&')).toEqual([
144+
{ kind: 'selector', value: '[data-foo]' },
145+
{ kind: 'selector', value: '&' },
146+
])
147+
})
148+
149+
it('parses universal selector before attribute selector', () => {
150+
expect(parse('*[data-foo]')).toEqual([
151+
{ kind: 'selector', value: '*' },
152+
{ kind: 'selector', value: '[data-foo]' },
153+
])
154+
})
155+
156+
it('parses universal selector after an attribute selector', () => {
157+
expect(parse('[data-foo]*')).toEqual([
158+
{ kind: 'selector', value: '[data-foo]' },
159+
{ kind: 'selector', value: '*' },
160+
])
161+
})
134162
})
135163

136164
describe('toCss', () => {

packages/tailwindcss/src/compat/selector-parser.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,8 @@ const SINGLE_QUOTE = 0x27
181181
const SPACE = 0x20
182182
const TAB = 0x09
183183
const TILDE = 0x7e
184+
const AMPERSAND = 0x26
185+
const ASTERISK = 0x2a
184186

185187
export function parse(input: string) {
186188
input = input.replaceAll('\r\n', '\n')
@@ -369,7 +371,7 @@ export function parse(input: string) {
369371
ast.push(node)
370372
}
371373
}
372-
buffer = String.fromCharCode(currentChar)
374+
buffer = input[i]
373375
break
374376
}
375377

@@ -443,17 +445,40 @@ export function parse(input: string) {
443445
break
444446
}
445447

448+
// Nesting `&` is always a new selector.
449+
// Universal `*` is always a new selector.
450+
case AMPERSAND:
451+
case ASTERISK: {
452+
// 1. Handle everything before the combinator as a selector
453+
if (buffer.length > 0) {
454+
let node = selector(buffer)
455+
if (parent) {
456+
parent.nodes.push(node)
457+
} else {
458+
ast.push(node)
459+
}
460+
buffer = ''
461+
}
462+
463+
// 2. Handle the `&` or `*` as a selector on its own
464+
if (parent) {
465+
parent.nodes.push(selector(input[i]))
466+
} else {
467+
ast.push(selector(input[i]))
468+
}
469+
break
470+
}
471+
446472
// Escaped characters.
447473
case BACKSLASH: {
448-
let nextChar = input.charCodeAt(i + 1)
449-
buffer += String.fromCharCode(currentChar) + String.fromCharCode(nextChar)
474+
buffer += input[i] + input[i + 1]
450475
i += 1
451476
break
452477
}
453478

454479
// Everything else will be collected in the buffer
455480
default: {
456-
buffer += String.fromCharCode(currentChar)
481+
buffer += input[i]
457482
}
458483
}
459484
}

0 commit comments

Comments
 (0)