Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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