Skip to content

Commit d20f690

Browse files
committed
fix: validate pseudo-class argument arity consistently
Signed-off-by: Mridankan Mandal <xerontitan90@gmail.com>
1 parent 4458366 commit d20f690

2 files changed

Lines changed: 58 additions & 0 deletions

File tree

src/pseudo-selectors/index.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,24 @@ import { filters } from "./filters.js";
2020
import { pseudos, verifyPseudoArguments } from "./pseudos.js";
2121
import { subselects } from "./subselects.js";
2222

23+
const filtersWithArguments = new Set([
24+
"contains",
25+
"icontains",
26+
"nth-child",
27+
"nth-last-child",
28+
"nth-of-type",
29+
"nth-last-of-type",
30+
"lang",
31+
]);
32+
33+
const filtersWithoutArguments = new Set([
34+
"root",
35+
"scope",
36+
"hover",
37+
"visited",
38+
"active",
39+
]);
40+
2341
/**
2442
* Compile a pseudo selector into an executable query function.
2543
* @param next Matcher to run after this matcher succeeds.
@@ -37,6 +55,10 @@ export function compilePseudoSelector<Node, ElementNode extends Node>(
3755
): CompiledQuery<ElementNode> {
3856
const { name, data } = selector;
3957

58+
if (data === null && name in subselects) {
59+
throw new Error(`Pseudo-class :${name} requires an argument`);
60+
}
61+
4062
if (Array.isArray(data)) {
4163
if (!(name in subselects)) {
4264
throw new Error(`Unknown pseudo-class :${name}(${data})`);
@@ -67,6 +89,14 @@ export function compilePseudoSelector<Node, ElementNode extends Node>(
6789
}
6890

6991
if (name in filters) {
92+
if (data === null && filtersWithArguments.has(name)) {
93+
throw new Error(`Pseudo-class :${name} requires an argument`);
94+
}
95+
96+
if (data !== null && filtersWithoutArguments.has(name)) {
97+
throw new Error(`Pseudo-class :${name} doesn't have any arguments`);
98+
}
99+
70100
return filters[name](
71101
next,
72102
data as string,

test/pseudo-classes.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,34 @@ describe("unmatched", () => {
127127
"Unknown pseudo-class :host-context",
128128
);
129129
});
130+
131+
it("should throw when pseudo-classes are missing required arguments", () => {
132+
expect(() => CSSselect.selectAll(":lang", dom)).toThrow(
133+
"Pseudo-class :lang requires an argument",
134+
);
135+
136+
expect(() => CSSselect.selectAll(":nth-child", dom)).toThrow(
137+
"Pseudo-class :nth-child requires an argument",
138+
);
139+
140+
expect(() => CSSselect.selectAll(":has", dom)).toThrow(
141+
"Pseudo-class :has requires an argument",
142+
);
143+
144+
expect(() => CSSselect.selectAll(":not", dom)).toThrow(
145+
"Pseudo-class :not requires an argument",
146+
);
147+
});
148+
149+
it("should throw when argument-less pseudo-classes receive arguments", () => {
150+
expect(() => CSSselect.selectAll(":scope(foo)", dom)).toThrow(
151+
"Pseudo-class :scope doesn't have any arguments",
152+
);
153+
154+
expect(() => CSSselect.selectAll(":active(foo)", dom)).toThrow(
155+
"Pseudo-class :active doesn't have any arguments",
156+
);
157+
});
130158
});
131159

132160
describe(":first-child", () => {

0 commit comments

Comments
 (0)