Skip to content
Open
30 changes: 30 additions & 0 deletions src/pseudo-selectors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,24 @@ import { filters } from "./filters.js";
import { pseudos, verifyPseudoArguments } from "./pseudos.js";
import { subselects } from "./subselects.js";

const filtersWithArguments = new Set([
"contains",
"icontains",
"nth-child",
"nth-last-child",
"nth-of-type",
"nth-last-of-type",
"lang",
]);

const filtersWithoutArguments = new Set([
"root",
"scope",
"hover",
"visited",
"active",
]);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

/**
* Compile a pseudo selector into an executable query function.
* @param next Matcher to run after this matcher succeeds.
Expand All @@ -37,6 +55,10 @@ export function compilePseudoSelector<Node, ElementNode extends Node>(
): CompiledQuery<ElementNode> {
const { name, data } = selector;

if (data === null && name in subselects) {
throw new Error(`Pseudo-class :${name} requires an argument`);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

if (Array.isArray(data)) {
if (!(name in subselects)) {
throw new Error(`Unknown pseudo-class :${name}(${data})`);
Expand Down Expand Up @@ -67,6 +89,14 @@ export function compilePseudoSelector<Node, ElementNode extends Node>(
}

if (name in filters) {
if (data === null && filtersWithArguments.has(name)) {
throw new Error(`Pseudo-class :${name} requires an argument`);
}

if (data !== null && filtersWithoutArguments.has(name)) {
throw new Error(`Pseudo-class :${name} doesn't have any arguments`);
}

return filters[name](
next,
data as string,
Expand Down
28 changes: 28 additions & 0 deletions test/pseudo-classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,34 @@ describe("unmatched", () => {
"Unknown pseudo-class :host-context",
);
});

it("should throw when pseudo-classes are missing required arguments", () => {
expect(() => CSSselect.selectAll(":lang", dom)).toThrow(
"Pseudo-class :lang requires an argument",
);

expect(() => CSSselect.selectAll(":nth-child", dom)).toThrow(
"Pseudo-class :nth-child requires an argument",
);

expect(() => CSSselect.selectAll(":has", dom)).toThrow(
"Pseudo-class :has requires an argument",
);

expect(() => CSSselect.selectAll(":not", dom)).toThrow(
"Pseudo-class :not requires an argument",
);
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.

it("should throw when argument-less pseudo-classes receive arguments", () => {
expect(() => CSSselect.selectAll(":scope(foo)", dom)).toThrow(
"Pseudo-class :scope doesn't have any arguments",
);

expect(() => CSSselect.selectAll(":active(foo)", dom)).toThrow(
"Pseudo-class :active doesn't have any arguments",
);
});
Comment thread
RedZapdos123 marked this conversation as resolved.
});

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