Skip to content

Commit 3be227a

Browse files
authored
feat: Support pseudo elements with data (#762)
1 parent 13ffc5b commit 3be227a

File tree

4 files changed

+84
-42
lines changed

4 files changed

+84
-42
lines changed

src/__fixtures__/tests.ts

+28-1
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,33 @@ export const tests: [
411411
{
412412
type: SelectorType.PseudoElement,
413413
name: "foo",
414+
data: null,
415+
},
416+
],
417+
],
418+
"pseudo-element",
419+
],
420+
[
421+
"::foo()",
422+
[
423+
[
424+
{
425+
type: SelectorType.PseudoElement,
426+
name: "foo",
427+
data: "",
428+
},
429+
],
430+
],
431+
"pseudo-element",
432+
],
433+
[
434+
"::foo(bar())",
435+
[
436+
[
437+
{
438+
type: SelectorType.PseudoElement,
439+
name: "foo",
440+
data: "bar()",
414441
},
415442
],
416443
],
@@ -485,7 +512,7 @@ export const tests: [
485512
{
486513
type: SelectorType.Pseudo,
487514
name: "contains",
488-
data: "(a((foo\\))))",
515+
data: "(a((foo))))",
489516
},
490517
],
491518
],

src/parse.ts

+38-29
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,38 @@ function parseSelector(
175175
}
176176
}
177177

178+
function readValueWithParenthesis(): string {
179+
selectorIndex += 1;
180+
const start = selectorIndex;
181+
let counter = 1;
182+
183+
for (
184+
;
185+
counter > 0 && selectorIndex < selector.length;
186+
selectorIndex++
187+
) {
188+
if (
189+
selector.charCodeAt(selectorIndex) ===
190+
CharCode.LeftParenthesis &&
191+
!isEscaped(selectorIndex)
192+
) {
193+
counter++;
194+
} else if (
195+
selector.charCodeAt(selectorIndex) ===
196+
CharCode.RightParenthesis &&
197+
!isEscaped(selectorIndex)
198+
) {
199+
counter--;
200+
}
201+
}
202+
203+
if (counter) {
204+
throw new Error("Parenthesis not matched");
205+
}
206+
207+
return unescapeCSS(selector.slice(start, selectorIndex - 1));
208+
}
209+
178210
function isEscaped(pos: number): boolean {
179211
let slashCount = 0;
180212

@@ -434,6 +466,11 @@ function parseSelector(
434466
tokens.push({
435467
type: SelectorType.PseudoElement,
436468
name: getName(2).toLowerCase(),
469+
data:
470+
selector.charCodeAt(selectorIndex) ===
471+
CharCode.LeftParenthesis
472+
? readValueWithParenthesis()
473+
: null,
437474
});
438475
continue;
439476
}
@@ -470,35 +507,7 @@ function parseSelector(
470507

471508
selectorIndex += 1;
472509
} else {
473-
selectorIndex += 1;
474-
const start = selectorIndex;
475-
let counter = 1;
476-
477-
for (
478-
;
479-
counter > 0 && selectorIndex < selector.length;
480-
selectorIndex++
481-
) {
482-
if (
483-
selector.charCodeAt(selectorIndex) ===
484-
CharCode.LeftParenthesis &&
485-
!isEscaped(selectorIndex)
486-
) {
487-
counter++;
488-
} else if (
489-
selector.charCodeAt(selectorIndex) ===
490-
CharCode.RightParenthesis &&
491-
!isEscaped(selectorIndex)
492-
) {
493-
counter--;
494-
}
495-
}
496-
497-
if (counter) {
498-
throw new Error("Parenthesis not matched");
499-
}
500-
501-
data = selector.slice(start, selectorIndex - 1);
510+
data = readValueWithParenthesis();
502511

503512
if (stripQuotesFromPseudos.has(name)) {
504513
const quot = data.charCodeAt(0);

src/stringify.ts

+17-12
Original file line numberDiff line numberDiff line change
@@ -69,20 +69,25 @@ function stringifyToken(
6969
return getNamespacedName(token);
7070

7171
case SelectorType.PseudoElement:
72-
return `::${escapeName(token.name, charsToEscapeInName)}`;
72+
return `::${escapeName(token.name, charsToEscapeInName)}${
73+
token.data === null
74+
? ""
75+
: `(${escapeName(token.data, charsToEscapeInPseudoValue)})`
76+
}`;
7377

7478
case SelectorType.Pseudo:
75-
if (token.data === null)
76-
return `:${escapeName(token.name, charsToEscapeInName)}`;
77-
if (typeof token.data === "string") {
78-
return `:${escapeName(
79-
token.name,
80-
charsToEscapeInName
81-
)}(${escapeName(token.data, charsToEscapeInPseudoValue)})`;
82-
}
83-
return `:${escapeName(token.name, charsToEscapeInName)}(${stringify(
84-
token.data
85-
)})`;
79+
return `:${escapeName(token.name, charsToEscapeInName)}${
80+
token.data === null
81+
? ""
82+
: `(${
83+
typeof token.data === "string"
84+
? escapeName(
85+
token.data,
86+
charsToEscapeInPseudoValue
87+
)
88+
: stringify(token.data)
89+
})`
90+
}`;
8691

8792
case SelectorType.Attribute: {
8893
if (

src/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export interface PseudoSelector {
5656
export interface PseudoElement {
5757
type: SelectorType.PseudoElement;
5858
name: string;
59+
data: string | null;
5960
}
6061

6162
export interface TagSelector {

0 commit comments

Comments
 (0)