Skip to content

Commit b3e5e59

Browse files
committed
refactor(stringify): Only escape what needs escaping
1 parent de367ca commit b3e5e59

File tree

1 file changed

+35
-15
lines changed

1 file changed

+35
-15
lines changed

src/stringify.ts

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
11
import { Selector, SelectorType, AttributeAction } from "./types";
22

3-
const charsToEscape = new Set(
3+
const attribValChars = ["\\", '"'];
4+
const pseudoValChars = [...attribValChars, "(", ")"];
5+
6+
const charsToEscapeInAttributeValue = new Set(
7+
attribValChars.map((c) => c.charCodeAt(0))
8+
);
9+
const charsToEscapeInPseudoValue = new Set(
10+
pseudoValChars.map((c) => c.charCodeAt(0))
11+
);
12+
const charsToEscapeInName = new Set(
413
[
14+
...pseudoValChars,
515
"~",
616
"^",
717
"$",
@@ -14,10 +24,6 @@ const charsToEscape = new Set(
1424
"]",
1525
" ",
1626
".",
17-
"\\",
18-
"(",
19-
")",
20-
'"',
2127
].map((c) => c.charCodeAt(0))
2228
);
2329

@@ -54,14 +60,20 @@ function stringifyToken(token: Selector): string {
5460
return getNamespacedName(token);
5561

5662
case SelectorType.PseudoElement:
57-
return `::${escapeName(token.name)}`;
63+
return `::${escapeName(token.name, charsToEscapeInName)}`;
5864

5965
case SelectorType.Pseudo:
60-
if (token.data === null) return `:${escapeName(token.name)}`;
66+
if (token.data === null)
67+
return `:${escapeName(token.name, charsToEscapeInName)}`;
6168
if (typeof token.data === "string") {
62-
return `:${escapeName(token.name)}(${escapeName(token.data)})`;
69+
return `:${escapeName(
70+
token.name,
71+
charsToEscapeInName
72+
)}(${escapeName(token.data, charsToEscapeInPseudoValue)})`;
6373
}
64-
return `:${escapeName(token.name)}(${stringify(token.data)})`;
74+
return `:${escapeName(token.name, charsToEscapeInName)}(${stringify(
75+
token.data
76+
)})`;
6577

6678
case SelectorType.Attribute: {
6779
if (
@@ -70,15 +82,15 @@ function stringifyToken(token: Selector): string {
7082
token.ignoreCase === "quirks" &&
7183
!token.namespace
7284
) {
73-
return `#${escapeName(token.value)}`;
85+
return `#${escapeName(token.value, charsToEscapeInName)}`;
7486
}
7587
if (
7688
token.name === "class" &&
7789
token.action === AttributeAction.Element &&
7890
token.ignoreCase === "quirks" &&
7991
!token.namespace
8092
) {
81-
return `.${escapeName(token.value)}`;
93+
return `.${escapeName(token.value, charsToEscapeInName)}`;
8294
}
8395

8496
const name = getNamespacedName(token);
@@ -88,7 +100,8 @@ function stringifyToken(token: Selector): string {
88100
}
89101

90102
return `[${name}${getActionValue(token.action)}="${escapeName(
91-
token.value
103+
token.value,
104+
charsToEscapeInAttributeValue
92105
)}"${
93106
token.ignoreCase === null ? "" : token.ignoreCase ? " i" : " s"
94107
}]`;
@@ -121,16 +134,23 @@ function getNamespacedName(token: {
121134
name: string;
122135
namespace: string | null;
123136
}): string {
124-
return `${getNamespace(token.namespace)}${escapeName(token.name)}`;
137+
return `${getNamespace(token.namespace)}${escapeName(
138+
token.name,
139+
charsToEscapeInName
140+
)}`;
125141
}
126142

127143
function getNamespace(namespace: string | null): string {
128144
return namespace !== null
129-
? `${namespace === "*" ? "*" : escapeName(namespace)}|`
145+
? `${
146+
namespace === "*"
147+
? "*"
148+
: escapeName(namespace, charsToEscapeInName)
149+
}|`
130150
: "";
131151
}
132152

133-
function escapeName(str: string): string {
153+
function escapeName(str: string, charsToEscape: Set<number>): string {
134154
let lastIdx = 0;
135155
let ret = "";
136156

0 commit comments

Comments
 (0)