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

const allFilterNames = new Set(Object.keys(filters));

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

for (const filterName of filtersWithArguments) {
if (!allFilterNames.has(filterName)) {
throw new Error(`Unknown filter in filtersWithArguments: ${filterName}`);
}
}

const filtersWithoutArguments = new Set(
[...allFilterNames].filter((filterName) => !filtersWithArguments.has(filterName)),
);

/**
* Compile a pseudo selector into an executable query function.
* @param next Matcher to run after this matcher succeeds.
Expand All @@ -37,6 +59,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 +93,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
72 changes: 72 additions & 0 deletions test/pseudo-classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,78 @@ describe("unmatched", () => {
"Unknown pseudo-class :host-context",
);
});

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

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

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(":nth-last-child", dom)).toThrow(
"Pseudo-class :nth-last-child requires an argument",
);

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

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

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

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

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

expect(() => CSSselect.selectAll(":where", dom)).toThrow(
"Pseudo-class :where 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",
);

expect(() => CSSselect.selectAll(":root(foo)", dom)).toThrow(
"Pseudo-class :root doesn't have any arguments",
);

expect(() => CSSselect.selectAll(":hover(foo)", dom)).toThrow(
"Pseudo-class :hover doesn't have any arguments",
);

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

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