Skip to content

Commit c0f83af

Browse files
better splitting of selectors (#1440)
* better splitting of selectors - overlapping with #1401 * Add test from example at PostHog/posthog#21427 * ignore brackets inside selector strings * Add another test as noticed that it's possible to escape strings * Ensure we are ignoring commas within strings Co-authored-by: Eoghan Murray <eoghan@getthere.ie>
1 parent bee92c8 commit c0f83af

File tree

3 files changed

+60
-1
lines changed

3 files changed

+60
-1
lines changed

.changeset/modern-doors-watch.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'rrweb-snapshot': patch
3+
---
4+
5+
better nested css selector splitting when commas or brackets happen to be in quoted text

packages/rrweb-snapshot/src/css.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,7 @@ export function parse(css: string, options: ParserOptions = {}): Stylesheet {
437437
if (!m) {
438438
return;
439439
}
440+
440441
/* @fix Remove all comments from selectors
441442
* http://ostermiller.org/findcomment.html */
442443
const cleanedInput = m[0]
@@ -463,16 +464,25 @@ export function parse(css: string, options: ParserOptions = {}): Stylesheet {
463464
let currentSegment = '';
464465
let depthParentheses = 0; // Track depth of parentheses
465466
let depthBrackets = 0; // Track depth of square brackets
467+
let currentStringChar = null;
466468

467469
for (const char of input) {
468-
if (char === '(') {
470+
const hasStringEscape = currentSegment.endsWith('\\');
471+
472+
if (currentStringChar) {
473+
if (currentStringChar === char && !hasStringEscape) {
474+
currentStringChar = null;
475+
}
476+
} else if (char === '(') {
469477
depthParentheses++;
470478
} else if (char === ')') {
471479
depthParentheses--;
472480
} else if (char === '[') {
473481
depthBrackets++;
474482
} else if (char === ']') {
475483
depthBrackets--;
484+
} else if ('\'"'.includes(char)) {
485+
currentStringChar = char;
476486
}
477487

478488
// Split point is a comma that is not inside parentheses or square brackets

packages/rrweb-snapshot/test/css.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,50 @@ describe('css parser', () => {
148148
expect(out3).toEqual('[data-aa\\:other] { color: red; }');
149149
});
150150

151+
it('parses nested commas in selectors correctly', () => {
152+
const result = parse(
153+
`
154+
body > ul :is(li:not(:first-of-type) a:hover, li:not(:first-of-type).active a) {
155+
background: red;
156+
}
157+
`,
158+
);
159+
expect((result.stylesheet!.rules[0] as Rule)!.selectors!.length).toEqual(1);
160+
161+
const trickresult = parse(
162+
`
163+
li[attr="weirdly("] a:hover, li[attr="weirdly)"] a {
164+
background-color: red;
165+
}
166+
`,
167+
);
168+
expect(
169+
(trickresult.stylesheet!.rules[0] as Rule)!.selectors!.length,
170+
).toEqual(2);
171+
172+
const weirderresult = parse(
173+
`
174+
li[attr="weirder\\"("] a:hover, li[attr="weirder\\")"] a {
175+
background-color: red;
176+
}
177+
`,
178+
);
179+
expect(
180+
(weirderresult.stylesheet!.rules[0] as Rule)!.selectors!.length,
181+
).toEqual(2);
182+
183+
const commainstrresult = parse(
184+
`
185+
li[attr="has,comma"] a:hover {
186+
background-color: red;
187+
}
188+
`,
189+
);
190+
expect(
191+
(commainstrresult.stylesheet!.rules[0] as Rule)!.selectors!.length,
192+
).toEqual(1);
193+
});
194+
151195
it('parses imports with quotes correctly', () => {
152196
const out1 = escapeImportStatement({
153197
cssText: `@import url("/foo.css;900;800"");`,

0 commit comments

Comments
 (0)