Use local svgo.cmd wrapper binary

Switch SVG optimizer resolution from bin/svgo-cli.exe to bin/svgo.cmd.

Update unit tests to validate the new local binary path behavior.

Co-Authored-By: Abacus.AI CLI <agent@abacus.ai>
This commit is contained in:
2026-06-08 14:50:19 +02:00
parent 75059f829a
commit 6c5a5256c7
1054 changed files with 152359 additions and 7 deletions
+7
View File
@@ -0,0 +1,7 @@
import type { CompiledQuery, InternalOptions } from "./types.js";
import type { AttributeSelector, AttributeAction } from "css-what";
/**
* Attribute selectors
*/
export declare const attributeRules: Record<AttributeAction, <Node, ElementNode extends Node>(next: CompiledQuery<ElementNode>, data: AttributeSelector, options: InternalOptions<Node, ElementNode>) => CompiledQuery<ElementNode>>;
//# sourceMappingURL=attributes.d.ts.map
+222
View File
@@ -0,0 +1,222 @@
import boolbase from "boolbase";
/**
* All reserved characters in a regex, used for escaping.
*
* Taken from XRegExp, (c) 2007-2020 Steven Levithan under the MIT license
* https://github.com/slevithan/xregexp/blob/95eeebeb8fac8754d54eafe2b4743661ac1cf028/src/xregexp.js#L794
*/
const reChars = /[-[\]{}()*+?.,\\^$|#\s]/g;
function escapeRegex(value) {
return value.replace(reChars, "\\$&");
}
/**
* Attributes that are case-insensitive in HTML.
*
* @private
* @see https://html.spec.whatwg.org/multipage/semantics-other.html#case-sensitivity-of-selectors
*/
const caseInsensitiveAttributes = new Set([
"accept",
"accept-charset",
"align",
"alink",
"axis",
"bgcolor",
"charset",
"checked",
"clear",
"codetype",
"color",
"compact",
"declare",
"defer",
"dir",
"direction",
"disabled",
"enctype",
"face",
"frame",
"hreflang",
"http-equiv",
"lang",
"language",
"link",
"media",
"method",
"multiple",
"nohref",
"noresize",
"noshade",
"nowrap",
"readonly",
"rel",
"rev",
"rules",
"scope",
"scrolling",
"selected",
"shape",
"target",
"text",
"type",
"valign",
"valuetype",
"vlink",
]);
function shouldIgnoreCase(selector, options) {
return typeof selector.ignoreCase === "boolean"
? selector.ignoreCase
: selector.ignoreCase === "quirks"
? !!options.quirksMode
: !options.xmlMode && caseInsensitiveAttributes.has(selector.name);
}
/**
* Attribute selectors
*/
export const attributeRules = {
equals(next, data, options) {
const { adapter } = options;
const { name } = data;
let { value } = data;
if (shouldIgnoreCase(data, options)) {
value = value.toLowerCase();
return (elem) => {
const attr = adapter.getAttributeValue(elem, name);
return (attr != null &&
attr.length === value.length &&
attr.toLowerCase() === value &&
next(elem));
};
}
return (elem) => adapter.getAttributeValue(elem, name) === value && next(elem);
},
hyphen(next, data, options) {
const { adapter } = options;
const { name } = data;
let { value } = data;
const len = value.length;
if (shouldIgnoreCase(data, options)) {
value = value.toLowerCase();
return function hyphenIC(elem) {
const attr = adapter.getAttributeValue(elem, name);
return (attr != null &&
(attr.length === len || attr.charAt(len) === "-") &&
attr.substr(0, len).toLowerCase() === value &&
next(elem));
};
}
return function hyphen(elem) {
const attr = adapter.getAttributeValue(elem, name);
return (attr != null &&
(attr.length === len || attr.charAt(len) === "-") &&
attr.substr(0, len) === value &&
next(elem));
};
},
element(next, data, options) {
const { adapter } = options;
const { name, value } = data;
if (/\s/.test(value)) {
return boolbase.falseFunc;
}
const regex = new RegExp(`(?:^|\\s)${escapeRegex(value)}(?:$|\\s)`, shouldIgnoreCase(data, options) ? "i" : "");
return function element(elem) {
const attr = adapter.getAttributeValue(elem, name);
return (attr != null &&
attr.length >= value.length &&
regex.test(attr) &&
next(elem));
};
},
exists(next, { name }, { adapter }) {
return (elem) => adapter.hasAttrib(elem, name) && next(elem);
},
start(next, data, options) {
const { adapter } = options;
const { name } = data;
let { value } = data;
const len = value.length;
if (len === 0) {
return boolbase.falseFunc;
}
if (shouldIgnoreCase(data, options)) {
value = value.toLowerCase();
return (elem) => {
const attr = adapter.getAttributeValue(elem, name);
return (attr != null &&
attr.length >= len &&
attr.substr(0, len).toLowerCase() === value &&
next(elem));
};
}
return (elem) => {
var _a;
return !!((_a = adapter.getAttributeValue(elem, name)) === null || _a === void 0 ? void 0 : _a.startsWith(value)) &&
next(elem);
};
},
end(next, data, options) {
const { adapter } = options;
const { name } = data;
let { value } = data;
const len = -value.length;
if (len === 0) {
return boolbase.falseFunc;
}
if (shouldIgnoreCase(data, options)) {
value = value.toLowerCase();
return (elem) => {
var _a;
return ((_a = adapter
.getAttributeValue(elem, name)) === null || _a === void 0 ? void 0 : _a.substr(len).toLowerCase()) === value && next(elem);
};
}
return (elem) => {
var _a;
return !!((_a = adapter.getAttributeValue(elem, name)) === null || _a === void 0 ? void 0 : _a.endsWith(value)) &&
next(elem);
};
},
any(next, data, options) {
const { adapter } = options;
const { name, value } = data;
if (value === "") {
return boolbase.falseFunc;
}
if (shouldIgnoreCase(data, options)) {
const regex = new RegExp(escapeRegex(value), "i");
return function anyIC(elem) {
const attr = adapter.getAttributeValue(elem, name);
return (attr != null &&
attr.length >= value.length &&
regex.test(attr) &&
next(elem));
};
}
return (elem) => {
var _a;
return !!((_a = adapter.getAttributeValue(elem, name)) === null || _a === void 0 ? void 0 : _a.includes(value)) &&
next(elem);
};
},
not(next, data, options) {
const { adapter } = options;
const { name } = data;
let { value } = data;
if (value === "") {
return (elem) => !!adapter.getAttributeValue(elem, name) && next(elem);
}
else if (shouldIgnoreCase(data, options)) {
value = value.toLowerCase();
return (elem) => {
const attr = adapter.getAttributeValue(elem, name);
return ((attr == null ||
attr.length !== value.length ||
attr.toLowerCase() !== value) &&
next(elem));
};
}
return (elem) => adapter.getAttributeValue(elem, name) !== value && next(elem);
},
};
//# sourceMappingURL=attributes.js.map
+13
View File
@@ -0,0 +1,13 @@
import { Selector } from "css-what";
import type { CompiledQuery, InternalOptions, InternalSelector } from "./types.js";
/**
* Compiles a selector to an executable function.
*
* @param selector Selector to compile.
* @param options Compilation options.
* @param context Optional context for the selector.
*/
export declare function compile<Node, ElementNode extends Node>(selector: string | Selector[][], options: InternalOptions<Node, ElementNode>, context?: Node[] | Node): CompiledQuery<Node>;
export declare function compileUnsafe<Node, ElementNode extends Node>(selector: string | Selector[][], options: InternalOptions<Node, ElementNode>, context?: Node[] | Node): CompiledQuery<ElementNode>;
export declare function compileToken<Node, ElementNode extends Node>(token: InternalSelector[][], options: InternalOptions<Node, ElementNode>, context?: Node[] | Node): CompiledQuery<ElementNode>;
//# sourceMappingURL=compile.d.ts.map
+115
View File
@@ -0,0 +1,115 @@
import { parse, SelectorType } from "css-what";
import boolbase from "boolbase";
import sortRules, { isTraversal } from "./sort.js";
import { compileGeneralSelector } from "./general.js";
import { ensureIsTag, PLACEHOLDER_ELEMENT, } from "./pseudo-selectors/subselects.js";
/**
* Compiles a selector to an executable function.
*
* @param selector Selector to compile.
* @param options Compilation options.
* @param context Optional context for the selector.
*/
export function compile(selector, options, context) {
const next = compileUnsafe(selector, options, context);
return ensureIsTag(next, options.adapter);
}
export function compileUnsafe(selector, options, context) {
const token = typeof selector === "string" ? parse(selector) : selector;
return compileToken(token, options, context);
}
function includesScopePseudo(t) {
return (t.type === SelectorType.Pseudo &&
(t.name === "scope" ||
(Array.isArray(t.data) &&
t.data.some((data) => data.some(includesScopePseudo)))));
}
const DESCENDANT_TOKEN = { type: SelectorType.Descendant };
const FLEXIBLE_DESCENDANT_TOKEN = {
type: "_flexibleDescendant",
};
const SCOPE_TOKEN = {
type: SelectorType.Pseudo,
name: "scope",
data: null,
};
/*
* CSS 4 Spec (Draft): 3.4.1. Absolutizing a Relative Selector
* http://www.w3.org/TR/selectors4/#absolutizing
*/
function absolutize(token, { adapter }, context) {
// TODO Use better check if the context is a document
const hasContext = !!(context === null || context === void 0 ? void 0 : context.every((e) => {
const parent = adapter.isTag(e) && adapter.getParent(e);
return e === PLACEHOLDER_ELEMENT || (parent && adapter.isTag(parent));
}));
for (const t of token) {
if (t.length > 0 &&
isTraversal(t[0]) &&
t[0].type !== SelectorType.Descendant) {
// Don't continue in else branch
}
else if (hasContext && !t.some(includesScopePseudo)) {
t.unshift(DESCENDANT_TOKEN);
}
else {
continue;
}
t.unshift(SCOPE_TOKEN);
}
}
export function compileToken(token, options, context) {
var _a;
token.forEach(sortRules);
context = (_a = options.context) !== null && _a !== void 0 ? _a : context;
const isArrayContext = Array.isArray(context);
const finalContext = context && (Array.isArray(context) ? context : [context]);
// Check if the selector is relative
if (options.relativeSelector !== false) {
absolutize(token, options, finalContext);
}
else if (token.some((t) => t.length > 0 && isTraversal(t[0]))) {
throw new Error("Relative selectors are not allowed when the `relativeSelector` option is disabled");
}
let shouldTestNextSiblings = false;
const query = token
.map((rules) => {
if (rules.length >= 2) {
const [first, second] = rules;
if (first.type !== SelectorType.Pseudo ||
first.name !== "scope") {
// Ignore
}
else if (isArrayContext &&
second.type === SelectorType.Descendant) {
rules[1] = FLEXIBLE_DESCENDANT_TOKEN;
}
else if (second.type === SelectorType.Adjacent ||
second.type === SelectorType.Sibling) {
shouldTestNextSiblings = true;
}
}
return compileRules(rules, options, finalContext);
})
.reduce(reduceRules, boolbase.falseFunc);
query.shouldTestNextSiblings = shouldTestNextSiblings;
return query;
}
function compileRules(rules, options, context) {
var _a;
return rules.reduce((previous, rule) => previous === boolbase.falseFunc
? boolbase.falseFunc
: compileGeneralSelector(previous, rule, options, context, compileToken), (_a = options.rootFunc) !== null && _a !== void 0 ? _a : boolbase.trueFunc);
}
function reduceRules(a, b) {
if (b === boolbase.falseFunc || a === boolbase.trueFunc) {
return a;
}
if (a === boolbase.falseFunc || b === boolbase.trueFunc) {
return b;
}
return function combine(elem) {
return a(elem) || b(elem);
};
}
//# sourceMappingURL=compile.js.map
+3
View File
@@ -0,0 +1,3 @@
import type { CompiledQuery, InternalOptions, InternalSelector, CompileToken } from "./types.js";
export declare function compileGeneralSelector<Node, ElementNode extends Node>(next: CompiledQuery<ElementNode>, selector: InternalSelector, options: InternalOptions<Node, ElementNode>, context: Node[] | undefined, compileToken: CompileToken<Node, ElementNode>): CompiledQuery<ElementNode>;
//# sourceMappingURL=general.d.ts.map
+144
View File
@@ -0,0 +1,144 @@
import { attributeRules } from "./attributes.js";
import { compilePseudoSelector } from "./pseudo-selectors/index.js";
import { SelectorType } from "css-what";
function getElementParent(node, adapter) {
const parent = adapter.getParent(node);
if (parent && adapter.isTag(parent)) {
return parent;
}
return null;
}
/*
* All available rules
*/
export function compileGeneralSelector(next, selector, options, context, compileToken) {
const { adapter, equals } = options;
switch (selector.type) {
case SelectorType.PseudoElement: {
throw new Error("Pseudo-elements are not supported by css-select");
}
case SelectorType.ColumnCombinator: {
throw new Error("Column combinators are not yet supported by css-select");
}
case SelectorType.Attribute: {
if (selector.namespace != null) {
throw new Error("Namespaced attributes are not yet supported by css-select");
}
if (!options.xmlMode || options.lowerCaseAttributeNames) {
selector.name = selector.name.toLowerCase();
}
return attributeRules[selector.action](next, selector, options);
}
case SelectorType.Pseudo: {
return compilePseudoSelector(next, selector, options, context, compileToken);
}
// Tags
case SelectorType.Tag: {
if (selector.namespace != null) {
throw new Error("Namespaced tag names are not yet supported by css-select");
}
let { name } = selector;
if (!options.xmlMode || options.lowerCaseTags) {
name = name.toLowerCase();
}
return function tag(elem) {
return adapter.getName(elem) === name && next(elem);
};
}
// Traversal
case SelectorType.Descendant: {
if (options.cacheResults === false ||
typeof WeakSet === "undefined") {
return function descendant(elem) {
let current = elem;
while ((current = getElementParent(current, adapter))) {
if (next(current)) {
return true;
}
}
return false;
};
}
// @ts-expect-error `ElementNode` is not extending object
const isFalseCache = new WeakSet();
return function cachedDescendant(elem) {
let current = elem;
while ((current = getElementParent(current, adapter))) {
if (!isFalseCache.has(current)) {
if (adapter.isTag(current) && next(current)) {
return true;
}
isFalseCache.add(current);
}
}
return false;
};
}
case "_flexibleDescendant": {
// Include element itself, only used while querying an array
return function flexibleDescendant(elem) {
let current = elem;
do {
if (next(current))
return true;
} while ((current = getElementParent(current, adapter)));
return false;
};
}
case SelectorType.Parent: {
return function parent(elem) {
return adapter
.getChildren(elem)
.some((elem) => adapter.isTag(elem) && next(elem));
};
}
case SelectorType.Child: {
return function child(elem) {
const parent = adapter.getParent(elem);
return parent != null && adapter.isTag(parent) && next(parent);
};
}
case SelectorType.Sibling: {
return function sibling(elem) {
const siblings = adapter.getSiblings(elem);
for (let i = 0; i < siblings.length; i++) {
const currentSibling = siblings[i];
if (equals(elem, currentSibling))
break;
if (adapter.isTag(currentSibling) && next(currentSibling)) {
return true;
}
}
return false;
};
}
case SelectorType.Adjacent: {
if (adapter.prevElementSibling) {
return function adjacent(elem) {
const previous = adapter.prevElementSibling(elem);
return previous != null && next(previous);
};
}
return function adjacent(elem) {
const siblings = adapter.getSiblings(elem);
let lastElement;
for (let i = 0; i < siblings.length; i++) {
const currentSibling = siblings[i];
if (equals(elem, currentSibling))
break;
if (adapter.isTag(currentSibling)) {
lastElement = currentSibling;
}
}
return !!lastElement && next(lastElement);
};
}
case SelectorType.Universal: {
if (selector.namespace != null && selector.namespace !== "*") {
throw new Error("Namespaced universal selectors are not yet supported by css-select");
}
return next;
}
}
}
//# sourceMappingURL=general.js.map
+12
View File
@@ -0,0 +1,12 @@
import type { CompiledQuery, InternalOptions } from "../types.js";
/**
* Some selectors such as `:contains` and (non-relative) `:has` will only be
* able to match elements if their parents match the selector (as they contain
* a subset of the elements that the parent contains).
*
* This function wraps the given `matches` function in a function that caches
* the results of the parent elements, so that the `matches` function only
* needs to be called once for each subtree.
*/
export declare function cacheParentResults<Node, ElementNode extends Node>(next: CompiledQuery<ElementNode>, { adapter, cacheResults }: InternalOptions<Node, ElementNode>, matches: (elem: ElementNode) => boolean): CompiledQuery<ElementNode>;
//# sourceMappingURL=cache.d.ts.map
+41
View File
@@ -0,0 +1,41 @@
import { getElementParent } from "./querying.js";
/**
* Some selectors such as `:contains` and (non-relative) `:has` will only be
* able to match elements if their parents match the selector (as they contain
* a subset of the elements that the parent contains).
*
* This function wraps the given `matches` function in a function that caches
* the results of the parent elements, so that the `matches` function only
* needs to be called once for each subtree.
*/
export function cacheParentResults(next, { adapter, cacheResults }, matches) {
if (cacheResults === false || typeof WeakMap === "undefined") {
return (elem) => next(elem) && matches(elem);
}
// Use a cache to avoid re-checking children of an element.
// @ts-expect-error `Node` is not extending object
const resultCache = new WeakMap();
function addResultToCache(elem) {
const result = matches(elem);
resultCache.set(elem, result);
return result;
}
return function cachedMatcher(elem) {
if (!next(elem))
return false;
if (resultCache.has(elem)) {
return resultCache.get(elem);
}
// Check all of the element's parents.
let node = elem;
do {
const parent = getElementParent(node, adapter);
if (parent === null) {
return addResultToCache(elem);
}
node = parent;
} while (!resultCache.has(node));
return resultCache.get(node) && addResultToCache(elem);
};
}
//# sourceMappingURL=cache.js.map
+24
View File
@@ -0,0 +1,24 @@
import type { InternalOptions, Predicate, Adapter } from "../types.js";
/**
* Find all elements matching the query. If not in XML mode, the query will ignore
* the contents of `<template>` elements.
*
* @param query - Function that returns true if the element matches the query.
* @param elems - Nodes to query. If a node is an element, its children will be queried.
* @param options - Options for querying the document.
* @returns All matching elements.
*/
export declare function findAll<Node, ElementNode extends Node>(query: Predicate<ElementNode>, elems: Node[], options: InternalOptions<Node, ElementNode>): ElementNode[];
/**
* Find the first element matching the query. If not in XML mode, the query will ignore
* the contents of `<template>` elements.
*
* @param query - Function that returns true if the element matches the query.
* @param elems - Nodes to query. If a node is an element, its children will be queried.
* @param options - Options for querying the document.
* @returns The first matching element, or null if there was no match.
*/
export declare function findOne<Node, ElementNode extends Node>(query: Predicate<ElementNode>, elems: Node[], options: InternalOptions<Node, ElementNode>): ElementNode | null;
export declare function getNextSiblings<Node, ElementNode extends Node>(elem: Node, adapter: Adapter<Node, ElementNode>): ElementNode[];
export declare function getElementParent<Node, ElementNode extends Node>(node: ElementNode, adapter: Adapter<Node, ElementNode>): ElementNode | null;
//# sourceMappingURL=querying.d.ts.map
+105
View File
@@ -0,0 +1,105 @@
/**
* Find all elements matching the query. If not in XML mode, the query will ignore
* the contents of `<template>` elements.
*
* @param query - Function that returns true if the element matches the query.
* @param elems - Nodes to query. If a node is an element, its children will be queried.
* @param options - Options for querying the document.
* @returns All matching elements.
*/
export function findAll(query, elems, options) {
const { adapter, xmlMode = false } = options;
const result = [];
/** Stack of the arrays we are looking at. */
const nodeStack = [elems];
/** Stack of the indices within the arrays. */
const indexStack = [0];
for (;;) {
// First, check if the current array has any more elements to look at.
if (indexStack[0] >= nodeStack[0].length) {
// If we have no more arrays to look at, we are done.
if (nodeStack.length === 1) {
return result;
}
nodeStack.shift();
indexStack.shift();
// Loop back to the start to continue with the next array.
continue;
}
const elem = nodeStack[0][indexStack[0]++];
if (!adapter.isTag(elem))
continue;
if (query(elem))
result.push(elem);
if (xmlMode || adapter.getName(elem) !== "template") {
/*
* Add the children to the stack. We are depth-first, so this is
* the next array we look at.
*/
const children = adapter.getChildren(elem);
if (children.length > 0) {
nodeStack.unshift(children);
indexStack.unshift(0);
}
}
}
}
/**
* Find the first element matching the query. If not in XML mode, the query will ignore
* the contents of `<template>` elements.
*
* @param query - Function that returns true if the element matches the query.
* @param elems - Nodes to query. If a node is an element, its children will be queried.
* @param options - Options for querying the document.
* @returns The first matching element, or null if there was no match.
*/
export function findOne(query, elems, options) {
const { adapter, xmlMode = false } = options;
/** Stack of the arrays we are looking at. */
const nodeStack = [elems];
/** Stack of the indices within the arrays. */
const indexStack = [0];
for (;;) {
// First, check if the current array has any more elements to look at.
if (indexStack[0] >= nodeStack[0].length) {
// If we have no more arrays to look at, we are done.
if (nodeStack.length === 1) {
return null;
}
nodeStack.shift();
indexStack.shift();
// Loop back to the start to continue with the next array.
continue;
}
const elem = nodeStack[0][indexStack[0]++];
if (!adapter.isTag(elem))
continue;
if (query(elem))
return elem;
if (xmlMode || adapter.getName(elem) !== "template") {
/*
* Add the children to the stack. We are depth-first, so this is
* the next array we look at.
*/
const children = adapter.getChildren(elem);
if (children.length > 0) {
nodeStack.unshift(children);
indexStack.unshift(0);
}
}
}
}
export function getNextSiblings(elem, adapter) {
const siblings = adapter.getSiblings(elem);
if (siblings.length <= 1)
return [];
const elemIndex = siblings.indexOf(elem);
if (elemIndex < 0 || elemIndex === siblings.length - 1)
return [];
return siblings.slice(elemIndex + 1).filter(adapter.isTag);
}
export function getElementParent(node, adapter) {
const parent = adapter.getParent(node);
return parent != null && adapter.isTag(parent) ? parent : null;
}
//# sourceMappingURL=querying.js.map
+20
View File
@@ -0,0 +1,20 @@
import type { InternalSelector } from "../types.js";
import { type Traversal } from "css-what";
export declare function isTraversal(token: InternalSelector): token is Traversal;
/**
* Sort the parts of the passed selector, as there is potential for
* optimization (some types of selectors are faster than others).
*
* @param arr Selector to sort
*/
export declare function sortRules(arr: InternalSelector[]): void;
/**
* Determine the quality of the passed token. The higher the number, the
* faster the token is to execute.
*
* @param token Token to get the quality of.
* @returns The token's quality.
*/
export declare function getQuality(token: InternalSelector): number;
export declare function includesScopePseudo(t: InternalSelector): boolean;
//# sourceMappingURL=selectors.d.ts.map
+103
View File
@@ -0,0 +1,103 @@
import { AttributeAction, SelectorType, isTraversal as isTraversalBase, } from "css-what";
export function isTraversal(token) {
return token.type === "_flexibleDescendant" || isTraversalBase(token);
}
/**
* Sort the parts of the passed selector, as there is potential for
* optimization (some types of selectors are faster than others).
*
* @param arr Selector to sort
*/
export function sortRules(arr) {
const ratings = arr.map(getQuality);
for (let i = 1; i < arr.length; i++) {
const procNew = ratings[i];
if (procNew < 0)
continue;
// Use insertion sort to move the token to the correct position.
for (let j = i; j > 0 && procNew < ratings[j - 1]; j--) {
const token = arr[j];
arr[j] = arr[j - 1];
arr[j - 1] = token;
ratings[j] = ratings[j - 1];
ratings[j - 1] = procNew;
}
}
}
function getAttributeQuality(token) {
switch (token.action) {
case AttributeAction.Exists: {
return 10;
}
case AttributeAction.Equals: {
// Prefer ID selectors (eg. #ID)
return token.name === "id" ? 9 : 8;
}
case AttributeAction.Not: {
return 7;
}
case AttributeAction.Start: {
return 6;
}
case AttributeAction.End: {
return 6;
}
case AttributeAction.Any: {
return 5;
}
case AttributeAction.Hyphen: {
return 4;
}
case AttributeAction.Element: {
return 3;
}
}
}
/**
* Determine the quality of the passed token. The higher the number, the
* faster the token is to execute.
*
* @param token Token to get the quality of.
* @returns The token's quality.
*/
export function getQuality(token) {
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
switch (token.type) {
case SelectorType.Universal: {
return 50;
}
case SelectorType.Tag: {
return 30;
}
case SelectorType.Attribute: {
return Math.floor(getAttributeQuality(token) /
// `ignoreCase` adds some overhead, half the result if applicable.
(token.ignoreCase ? 2 : 1));
}
case SelectorType.Pseudo: {
return !token.data
? 3
: token.name === "has" ||
token.name === "contains" ||
token.name === "icontains"
? // Expensive in any case — run as late as possible.
0
: Array.isArray(token.data)
? // Eg. `:is`, `:not`
Math.max(
// If we have traversals, try to avoid executing this selector
0, Math.min(...token.data.map((d) => Math.min(...d.map(getQuality)))))
: 2;
}
default: {
return -1;
}
}
}
export function includesScopePseudo(t) {
return (t.type === SelectorType.Pseudo &&
(t.name === "scope" ||
(Array.isArray(t.data) &&
t.data.some((data) => data.some(includesScopePseudo)))));
}
//# sourceMappingURL=selectors.js.map
+50
View File
@@ -0,0 +1,50 @@
import type { CompiledQuery, Options, Query, Adapter } from "./types.js";
export type { Options };
/**
* Compiles the query, returns a function.
*/
export declare const compile: <Node, ElementNode extends Node>(selector: string | import("css-what").Selector[][], options?: Options<Node, ElementNode> | undefined, context?: Node | Node[] | undefined) => CompiledQuery<Node>;
export declare const _compileUnsafe: <Node, ElementNode extends Node>(selector: string | import("css-what").Selector[][], options?: Options<Node, ElementNode> | undefined, context?: Node | Node[] | undefined) => CompiledQuery<ElementNode>;
export declare const _compileToken: <Node, ElementNode extends Node>(selector: import("./types.js").InternalSelector[][], options?: Options<Node, ElementNode> | undefined, context?: Node | Node[] | undefined) => CompiledQuery<ElementNode>;
export declare function prepareContext<Node, ElementNode extends Node>(elems: Node | Node[], adapter: Adapter<Node, ElementNode>, shouldTestNextSiblings?: boolean): Node[];
/**
* @template Node The generic Node type for the DOM adapter being used.
* @template ElementNode The Node type for elements for the DOM adapter being used.
* @param elems Elements to query. If it is an element, its children will be queried..
* @param query can be either a CSS selector string or a compiled query function.
* @param [options] options for querying the document.
* @see compile for supported selector queries.
* @returns All matching elements.
*
*/
export declare const selectAll: <Node, ElementNode extends Node>(query: Query<ElementNode>, elements: Node | Node[], options?: Options<Node, ElementNode> | undefined) => ElementNode[];
/**
* @template Node The generic Node type for the DOM adapter being used.
* @template ElementNode The Node type for elements for the DOM adapter being used.
* @param elems Elements to query. If it is an element, its children will be queried..
* @param query can be either a CSS selector string or a compiled query function.
* @param [options] options for querying the document.
* @see compile for supported selector queries.
* @returns the first match, or null if there was no match.
*/
export declare const selectOne: <Node, ElementNode extends Node>(query: Query<ElementNode>, elements: Node | Node[], options?: Options<Node, ElementNode> | undefined) => ElementNode | null;
/**
* Tests whether or not an element is matched by query.
*
* @template Node The generic Node type for the DOM adapter being used.
* @template ElementNode The Node type for elements for the DOM adapter being used.
* @param elem The element to test if it matches the query.
* @param query can be either a CSS selector string or a compiled query function.
* @param [options] options for querying the document.
* @see compile for supported selector queries.
* @returns
*/
export declare function is<Node, ElementNode extends Node>(elem: ElementNode, query: Query<ElementNode>, options?: Options<Node, ElementNode>): boolean;
/**
* Alias for selectAll(query, elems, options).
* @see [compile] for supported selector queries.
*/
export default selectAll;
/** @deprecated Use the `pseudos` option instead. */
export { filters, pseudos, aliases } from "./pseudo-selectors/index.js";
//# sourceMappingURL=index.d.ts.map
+115
View File
@@ -0,0 +1,115 @@
import * as DomUtils from "domutils";
import boolbase from "boolbase";
import { compile as compileRaw, compileUnsafe, compileToken, } from "./compile.js";
import { getNextSiblings } from "./pseudo-selectors/subselects.js";
const defaultEquals = (a, b) => a === b;
const defaultOptions = {
adapter: DomUtils,
equals: defaultEquals,
};
function convertOptionFormats(options) {
var _a, _b, _c, _d;
/*
* We force one format of options to the other one.
*/
// @ts-expect-error Default options may have incompatible `Node` / `ElementNode`.
const opts = options !== null && options !== void 0 ? options : defaultOptions;
// @ts-expect-error Same as above.
(_a = opts.adapter) !== null && _a !== void 0 ? _a : (opts.adapter = DomUtils);
// @ts-expect-error `equals` does not exist on `Options`
(_b = opts.equals) !== null && _b !== void 0 ? _b : (opts.equals = (_d = (_c = opts.adapter) === null || _c === void 0 ? void 0 : _c.equals) !== null && _d !== void 0 ? _d : defaultEquals);
return opts;
}
function wrapCompile(func) {
return function addAdapter(selector, options, context) {
const opts = convertOptionFormats(options);
return func(selector, opts, context);
};
}
/**
* Compiles the query, returns a function.
*/
export const compile = wrapCompile(compileRaw);
export const _compileUnsafe = wrapCompile(compileUnsafe);
export const _compileToken = wrapCompile(compileToken);
function getSelectorFunc(searchFunc) {
return function select(query, elements, options) {
const opts = convertOptionFormats(options);
if (typeof query !== "function") {
query = compileUnsafe(query, opts, elements);
}
const filteredElements = prepareContext(elements, opts.adapter, query.shouldTestNextSiblings);
return searchFunc(query, filteredElements, opts);
};
}
export function prepareContext(elems, adapter, shouldTestNextSiblings = false) {
/*
* Add siblings if the query requires them.
* See https://github.com/fb55/css-select/pull/43#issuecomment-225414692
*/
if (shouldTestNextSiblings) {
elems = appendNextSiblings(elems, adapter);
}
return Array.isArray(elems)
? adapter.removeSubsets(elems)
: adapter.getChildren(elems);
}
function appendNextSiblings(elem, adapter) {
// Order matters because jQuery seems to check the children before the siblings
const elems = Array.isArray(elem) ? elem.slice(0) : [elem];
const elemsLength = elems.length;
for (let i = 0; i < elemsLength; i++) {
const nextSiblings = getNextSiblings(elems[i], adapter);
elems.push(...nextSiblings);
}
return elems;
}
/**
* @template Node The generic Node type for the DOM adapter being used.
* @template ElementNode The Node type for elements for the DOM adapter being used.
* @param elems Elements to query. If it is an element, its children will be queried..
* @param query can be either a CSS selector string or a compiled query function.
* @param [options] options for querying the document.
* @see compile for supported selector queries.
* @returns All matching elements.
*
*/
export const selectAll = getSelectorFunc((query, elems, options) => query === boolbase.falseFunc || !elems || elems.length === 0
? []
: options.adapter.findAll(query, elems));
/**
* @template Node The generic Node type for the DOM adapter being used.
* @template ElementNode The Node type for elements for the DOM adapter being used.
* @param elems Elements to query. If it is an element, its children will be queried..
* @param query can be either a CSS selector string or a compiled query function.
* @param [options] options for querying the document.
* @see compile for supported selector queries.
* @returns the first match, or null if there was no match.
*/
export const selectOne = getSelectorFunc((query, elems, options) => query === boolbase.falseFunc || !elems || elems.length === 0
? null
: options.adapter.findOne(query, elems));
/**
* Tests whether or not an element is matched by query.
*
* @template Node The generic Node type for the DOM adapter being used.
* @template ElementNode The Node type for elements for the DOM adapter being used.
* @param elem The element to test if it matches the query.
* @param query can be either a CSS selector string or a compiled query function.
* @param [options] options for querying the document.
* @see compile for supported selector queries.
* @returns
*/
export function is(elem, query, options) {
const opts = convertOptionFormats(options);
return (typeof query === "function" ? query : compileRaw(query, opts))(elem);
}
/**
* Alias for selectAll(query, elems, options).
* @see [compile] for supported selector queries.
*/
export default selectAll;
// Export filters, pseudos and aliases to allow users to supply their own.
/** @deprecated Use the `pseudos` option instead. */
export { filters, pseudos, aliases } from "./pseudo-selectors/index.js";
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"type":"module"}
@@ -0,0 +1,5 @@
/**
* Aliases are pseudos that are expressed as selectors.
*/
export declare const aliases: Record<string, string>;
//# sourceMappingURL=aliases.d.ts.map
@@ -0,0 +1,35 @@
/**
* Aliases are pseudos that are expressed as selectors.
*/
export const aliases = {
// Links
"any-link": ":is(a, area, link)[href]",
link: ":any-link:not(:visited)",
// Forms
// https://html.spec.whatwg.org/multipage/scripting.html#disabled-elements
disabled: `:is(
:is(button, input, select, textarea, optgroup, option)[disabled],
optgroup[disabled] > option,
fieldset[disabled]:not(fieldset[disabled] legend:first-of-type *)
)`,
enabled: ":not(:disabled)",
checked: ":is(:is(input[type=radio], input[type=checkbox])[checked], option:selected)",
required: ":is(input, select, textarea)[required]",
optional: ":is(input, select, textarea):not([required])",
// JQuery extensions
// https://html.spec.whatwg.org/multipage/form-elements.html#concept-option-selectedness
selected: "option:is([selected], select:not([multiple]):not(:has(> option[selected])) > :first-of-type)",
checkbox: "[type=checkbox]",
file: "[type=file]",
password: "[type=password]",
radio: "[type=radio]",
reset: "[type=reset]",
image: "[type=image]",
submit: "[type=submit]",
parent: ":not(:empty)",
header: ":is(h1, h2, h3, h4, h5, h6)",
button: ":is(button, input[type=button])",
input: ":is(input, textarea, select, button)",
text: "input:is(:not([type!='']), [type=text])",
};
//# sourceMappingURL=aliases.js.map
@@ -0,0 +1,4 @@
import type { CompiledQuery, InternalOptions } from "../types.js";
export declare type Filter = <Node, ElementNode extends Node>(next: CompiledQuery<ElementNode>, text: string, options: InternalOptions<Node, ElementNode>, context?: Node[]) => CompiledQuery<ElementNode>;
export declare const filters: Record<string, Filter>;
//# sourceMappingURL=filters.d.ts.map
@@ -0,0 +1,143 @@
import getNCheck from "nth-check";
import boolbase from "boolbase";
function getChildFunc(next, adapter) {
return (elem) => {
const parent = adapter.getParent(elem);
return parent != null && adapter.isTag(parent) && next(elem);
};
}
export const filters = {
contains(next, text, { adapter }) {
return function contains(elem) {
return next(elem) && adapter.getText(elem).includes(text);
};
},
icontains(next, text, { adapter }) {
const itext = text.toLowerCase();
return function icontains(elem) {
return (next(elem) &&
adapter.getText(elem).toLowerCase().includes(itext));
};
},
// Location specific methods
"nth-child"(next, rule, { adapter, equals }) {
const func = getNCheck(rule);
if (func === boolbase.falseFunc)
return boolbase.falseFunc;
if (func === boolbase.trueFunc)
return getChildFunc(next, adapter);
return function nthChild(elem) {
const siblings = adapter.getSiblings(elem);
let pos = 0;
for (let i = 0; i < siblings.length; i++) {
if (equals(elem, siblings[i]))
break;
if (adapter.isTag(siblings[i])) {
pos++;
}
}
return func(pos) && next(elem);
};
},
"nth-last-child"(next, rule, { adapter, equals }) {
const func = getNCheck(rule);
if (func === boolbase.falseFunc)
return boolbase.falseFunc;
if (func === boolbase.trueFunc)
return getChildFunc(next, adapter);
return function nthLastChild(elem) {
const siblings = adapter.getSiblings(elem);
let pos = 0;
for (let i = siblings.length - 1; i >= 0; i--) {
if (equals(elem, siblings[i]))
break;
if (adapter.isTag(siblings[i])) {
pos++;
}
}
return func(pos) && next(elem);
};
},
"nth-of-type"(next, rule, { adapter, equals }) {
const func = getNCheck(rule);
if (func === boolbase.falseFunc)
return boolbase.falseFunc;
if (func === boolbase.trueFunc)
return getChildFunc(next, adapter);
return function nthOfType(elem) {
const siblings = adapter.getSiblings(elem);
let pos = 0;
for (let i = 0; i < siblings.length; i++) {
const currentSibling = siblings[i];
if (equals(elem, currentSibling))
break;
if (adapter.isTag(currentSibling) &&
adapter.getName(currentSibling) === adapter.getName(elem)) {
pos++;
}
}
return func(pos) && next(elem);
};
},
"nth-last-of-type"(next, rule, { adapter, equals }) {
const func = getNCheck(rule);
if (func === boolbase.falseFunc)
return boolbase.falseFunc;
if (func === boolbase.trueFunc)
return getChildFunc(next, adapter);
return function nthLastOfType(elem) {
const siblings = adapter.getSiblings(elem);
let pos = 0;
for (let i = siblings.length - 1; i >= 0; i--) {
const currentSibling = siblings[i];
if (equals(elem, currentSibling))
break;
if (adapter.isTag(currentSibling) &&
adapter.getName(currentSibling) === adapter.getName(elem)) {
pos++;
}
}
return func(pos) && next(elem);
};
},
// TODO determine the actual root element
root(next, _rule, { adapter }) {
return (elem) => {
const parent = adapter.getParent(elem);
return (parent == null || !adapter.isTag(parent)) && next(elem);
};
},
scope(next, rule, options, context) {
const { equals } = options;
if (!context || context.length === 0) {
// Equivalent to :root
return filters["root"](next, rule, options);
}
if (context.length === 1) {
// NOTE: can't be unpacked, as :has uses this for side-effects
return (elem) => equals(context[0], elem) && next(elem);
}
return (elem) => context.includes(elem) && next(elem);
},
hover: dynamicStatePseudo("isHovered"),
visited: dynamicStatePseudo("isVisited"),
active: dynamicStatePseudo("isActive"),
};
/**
* Dynamic state pseudos. These depend on optional Adapter methods.
*
* @param name The name of the adapter method to call.
* @returns Pseudo for the `filters` object.
*/
function dynamicStatePseudo(name) {
return function dynamicPseudo(next, _rule, { adapter }) {
const func = adapter[name];
if (typeof func !== "function") {
return boolbase.falseFunc;
}
return function active(elem) {
return func(elem) && next(elem);
};
};
}
//# sourceMappingURL=filters.js.map
@@ -0,0 +1,8 @@
import type { CompiledQuery, InternalOptions, CompileToken } from "../types.js";
import { PseudoSelector } from "css-what";
import { filters } from "./filters.js";
import { pseudos } from "./pseudos.js";
import { aliases } from "./aliases.js";
export { filters, pseudos, aliases };
export declare function compilePseudoSelector<Node, ElementNode extends Node>(next: CompiledQuery<ElementNode>, selector: PseudoSelector, options: InternalOptions<Node, ElementNode>, context: Node[] | undefined, compileToken: CompileToken<Node, ElementNode>): CompiledQuery<ElementNode>;
//# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,40 @@
import { parse } from "css-what";
import { filters } from "./filters.js";
import { pseudos, verifyPseudoArgs } from "./pseudos.js";
import { aliases } from "./aliases.js";
import { subselects } from "./subselects.js";
export { filters, pseudos, aliases };
export function compilePseudoSelector(next, selector, options, context, compileToken) {
var _a;
const { name, data } = selector;
if (Array.isArray(data)) {
if (!(name in subselects)) {
throw new Error(`Unknown pseudo-class :${name}(${data})`);
}
return subselects[name](next, data, options, context, compileToken);
}
const userPseudo = (_a = options.pseudos) === null || _a === void 0 ? void 0 : _a[name];
const stringPseudo = typeof userPseudo === "string" ? userPseudo : aliases[name];
if (typeof stringPseudo === "string") {
if (data != null) {
throw new Error(`Pseudo ${name} doesn't have any arguments`);
}
// The alias has to be parsed here, to make sure options are respected.
const alias = parse(stringPseudo);
return subselects["is"](next, alias, options, context, compileToken);
}
if (typeof userPseudo === "function") {
verifyPseudoArgs(userPseudo, name, data, 1);
return (elem) => userPseudo(elem, data) && next(elem);
}
if (name in filters) {
return filters[name](next, data, options, context);
}
if (name in pseudos) {
const pseudo = pseudos[name];
verifyPseudoArgs(pseudo, name, data, 2);
return (elem) => pseudo(elem, options, data) && next(elem);
}
throw new Error(`Unknown pseudo-class :${name}`);
}
//# sourceMappingURL=index.js.map
@@ -0,0 +1,6 @@
import type { PseudoSelector } from "css-what";
import type { InternalOptions } from "../types.js";
export declare type Pseudo = <Node, ElementNode extends Node>(elem: ElementNode, options: InternalOptions<Node, ElementNode>, subselect?: string | null) => boolean;
export declare const pseudos: Record<string, Pseudo>;
export declare function verifyPseudoArgs<T extends Array<unknown>>(func: (...args: T) => boolean, name: string, subselect: PseudoSelector["data"], argIndex: number): void;
//# sourceMappingURL=pseudos.d.ts.map
@@ -0,0 +1,79 @@
// While filters are precompiled, pseudos get called when they are needed
export const pseudos = {
empty(elem, { adapter }) {
return !adapter.getChildren(elem).some((elem) =>
// FIXME: `getText` call is potentially expensive.
adapter.isTag(elem) || adapter.getText(elem) !== "");
},
"first-child"(elem, { adapter, equals }) {
if (adapter.prevElementSibling) {
return adapter.prevElementSibling(elem) == null;
}
const firstChild = adapter
.getSiblings(elem)
.find((elem) => adapter.isTag(elem));
return firstChild != null && equals(elem, firstChild);
},
"last-child"(elem, { adapter, equals }) {
const siblings = adapter.getSiblings(elem);
for (let i = siblings.length - 1; i >= 0; i--) {
if (equals(elem, siblings[i]))
return true;
if (adapter.isTag(siblings[i]))
break;
}
return false;
},
"first-of-type"(elem, { adapter, equals }) {
const siblings = adapter.getSiblings(elem);
const elemName = adapter.getName(elem);
for (let i = 0; i < siblings.length; i++) {
const currentSibling = siblings[i];
if (equals(elem, currentSibling))
return true;
if (adapter.isTag(currentSibling) &&
adapter.getName(currentSibling) === elemName) {
break;
}
}
return false;
},
"last-of-type"(elem, { adapter, equals }) {
const siblings = adapter.getSiblings(elem);
const elemName = adapter.getName(elem);
for (let i = siblings.length - 1; i >= 0; i--) {
const currentSibling = siblings[i];
if (equals(elem, currentSibling))
return true;
if (adapter.isTag(currentSibling) &&
adapter.getName(currentSibling) === elemName) {
break;
}
}
return false;
},
"only-of-type"(elem, { adapter, equals }) {
const elemName = adapter.getName(elem);
return adapter
.getSiblings(elem)
.every((sibling) => equals(elem, sibling) ||
!adapter.isTag(sibling) ||
adapter.getName(sibling) !== elemName);
},
"only-child"(elem, { adapter, equals }) {
return adapter
.getSiblings(elem)
.every((sibling) => equals(elem, sibling) || !adapter.isTag(sibling));
},
};
export function verifyPseudoArgs(func, name, subselect, argIndex) {
if (subselect === null) {
if (func.length > argIndex) {
throw new Error(`Pseudo-class :${name} requires an argument`);
}
}
else if (func.length === argIndex) {
throw new Error(`Pseudo-class :${name} doesn't have any arguments`);
}
}
//# sourceMappingURL=pseudos.js.map
@@ -0,0 +1,9 @@
import type { Selector } from "css-what";
import type { CompiledQuery, InternalOptions, CompileToken, Adapter } from "../types.js";
/** Used as a placeholder for :has. Will be replaced with the actual element. */
export declare const PLACEHOLDER_ELEMENT: {};
export declare function ensureIsTag<Node, ElementNode extends Node>(next: CompiledQuery<ElementNode>, adapter: Adapter<Node, ElementNode>): CompiledQuery<Node>;
export declare type Subselect = <Node, ElementNode extends Node>(next: CompiledQuery<ElementNode>, subselect: Selector[][], options: InternalOptions<Node, ElementNode>, context: Node[] | undefined, compileToken: CompileToken<Node, ElementNode>) => CompiledQuery<ElementNode>;
export declare function getNextSiblings<Node, ElementNode extends Node>(elem: Node, adapter: Adapter<Node, ElementNode>): ElementNode[];
export declare const subselects: Record<string, Subselect>;
//# sourceMappingURL=subselects.d.ts.map
@@ -0,0 +1,94 @@
import boolbase from "boolbase";
import { isTraversal } from "../sort.js";
/** Used as a placeholder for :has. Will be replaced with the actual element. */
export const PLACEHOLDER_ELEMENT = {};
export function ensureIsTag(next, adapter) {
if (next === boolbase.falseFunc)
return boolbase.falseFunc;
return (elem) => adapter.isTag(elem) && next(elem);
}
export function getNextSiblings(elem, adapter) {
const siblings = adapter.getSiblings(elem);
if (siblings.length <= 1)
return [];
const elemIndex = siblings.indexOf(elem);
if (elemIndex < 0 || elemIndex === siblings.length - 1)
return [];
return siblings.slice(elemIndex + 1).filter(adapter.isTag);
}
function copyOptions(options) {
// Not copied: context, rootFunc
return {
xmlMode: !!options.xmlMode,
lowerCaseAttributeNames: !!options.lowerCaseAttributeNames,
lowerCaseTags: !!options.lowerCaseTags,
quirksMode: !!options.quirksMode,
cacheResults: !!options.cacheResults,
pseudos: options.pseudos,
adapter: options.adapter,
equals: options.equals,
};
}
const is = (next, token, options, context, compileToken) => {
const func = compileToken(token, copyOptions(options), context);
return func === boolbase.trueFunc
? next
: func === boolbase.falseFunc
? boolbase.falseFunc
: (elem) => func(elem) && next(elem);
};
/*
* :not, :has, :is, :matches and :where have to compile selectors
* doing this in src/pseudos.ts would lead to circular dependencies,
* so we add them here
*/
export const subselects = {
is,
/**
* `:matches` and `:where` are aliases for `:is`.
*/
matches: is,
where: is,
not(next, token, options, context, compileToken) {
const func = compileToken(token, copyOptions(options), context);
return func === boolbase.falseFunc
? next
: func === boolbase.trueFunc
? boolbase.falseFunc
: (elem) => !func(elem) && next(elem);
},
has(next, subselect, options, _context, compileToken) {
const { adapter } = options;
const opts = copyOptions(options);
opts.relativeSelector = true;
const context = subselect.some((s) => s.some(isTraversal))
? // Used as a placeholder. Will be replaced with the actual element.
[PLACEHOLDER_ELEMENT]
: undefined;
const compiled = compileToken(subselect, opts, context);
if (compiled === boolbase.falseFunc)
return boolbase.falseFunc;
const hasElement = ensureIsTag(compiled, adapter);
// If `compiled` is `trueFunc`, we can skip this.
if (context && compiled !== boolbase.trueFunc) {
/*
* `shouldTestNextSiblings` will only be true if the query starts with
* a traversal (sibling or adjacent). That means we will always have a context.
*/
const { shouldTestNextSiblings = false } = compiled;
return (elem) => {
if (!next(elem))
return false;
context[0] = elem;
const childs = adapter.getChildren(elem);
const nextElements = shouldTestNextSiblings
? [...childs, ...getNextSiblings(elem, adapter)]
: childs;
return adapter.existsOne(hasElement, nextElements);
};
}
return (elem) => next(elem) &&
adapter.existsOne(hasElement, adapter.getChildren(elem));
},
};
//# sourceMappingURL=subselects.js.map
+12
View File
@@ -0,0 +1,12 @@
import type { InternalSelector } from "./types.js";
import { type Traversal } from "css-what";
export declare function isTraversal(token: InternalSelector): token is Traversal;
/**
* Sort the parts of the passed selector,
* as there is potential for optimization
* (some types of selectors are faster than others)
*
* @param arr Selector to sort
*/
export default function sortByProcedure(arr: InternalSelector[]): void;
//# sourceMappingURL=sort.d.ts.map
+79
View File
@@ -0,0 +1,79 @@
import { AttributeAction, SelectorType } from "css-what";
const procedure = new Map([
[SelectorType.Universal, 50],
[SelectorType.Tag, 30],
[SelectorType.Attribute, 1],
[SelectorType.Pseudo, 0],
]);
export function isTraversal(token) {
return !procedure.has(token.type);
}
const attributes = new Map([
[AttributeAction.Exists, 10],
[AttributeAction.Equals, 8],
[AttributeAction.Not, 7],
[AttributeAction.Start, 6],
[AttributeAction.End, 6],
[AttributeAction.Any, 5],
]);
/**
* Sort the parts of the passed selector,
* as there is potential for optimization
* (some types of selectors are faster than others)
*
* @param arr Selector to sort
*/
export default function sortByProcedure(arr) {
const procs = arr.map(getProcedure);
for (let i = 1; i < arr.length; i++) {
const procNew = procs[i];
if (procNew < 0)
continue;
for (let j = i - 1; j >= 0 && procNew < procs[j]; j--) {
const token = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = token;
procs[j + 1] = procs[j];
procs[j] = procNew;
}
}
}
function getProcedure(token) {
var _a, _b;
let proc = (_a = procedure.get(token.type)) !== null && _a !== void 0 ? _a : -1;
if (token.type === SelectorType.Attribute) {
proc = (_b = attributes.get(token.action)) !== null && _b !== void 0 ? _b : 4;
if (token.action === AttributeAction.Equals && token.name === "id") {
// Prefer ID selectors (eg. #ID)
proc = 9;
}
if (token.ignoreCase) {
/*
* IgnoreCase adds some overhead, prefer "normal" token
* this is a binary operation, to ensure it's still an int
*/
proc >>= 1;
}
}
else if (token.type === SelectorType.Pseudo) {
if (!token.data) {
proc = 3;
}
else if (token.name === "has" || token.name === "contains") {
proc = 0; // Expensive in any case
}
else if (Array.isArray(token.data)) {
// Eg. :matches, :not
proc = Math.min(...token.data.map((d) => Math.min(...d.map(getProcedure))));
// If we have traversals, try to avoid executing this selector
if (proc < 0) {
proc = 0;
}
}
else {
proc = 2;
}
}
return proc;
}
//# sourceMappingURL=sort.js.map
+167
View File
@@ -0,0 +1,167 @@
import type { Selector } from "css-what";
export declare type InternalSelector = Selector | {
type: "_flexibleDescendant";
};
export declare type Predicate<Value> = (v: Value) => boolean;
export interface Adapter<Node, ElementNode extends Node> {
/**
* Is the node a tag?
*/
isTag: (node: Node) => node is ElementNode;
/**
* Does at least one of passed element nodes pass the test predicate?
*/
existsOne: (test: Predicate<ElementNode>, elems: Node[]) => boolean;
/**
* Get the attribute value.
*/
getAttributeValue: (elem: ElementNode, name: string) => string | undefined;
/**
* Get the node's children
*/
getChildren: (node: Node) => Node[];
/**
* Get the name of the tag
*/
getName: (elem: ElementNode) => string;
/**
* Get the parent of the node
*/
getParent: (node: ElementNode) => Node | null;
/**
* Get the siblings of the node. Note that unlike jQuery's `siblings` method,
* this is expected to include the current node as well
*/
getSiblings: (node: Node) => Node[];
/**
* Returns the previous element sibling of a node.
*/
prevElementSibling?: (node: Node) => ElementNode | null;
/**
* Get the text content of the node, and its children if it has any.
*/
getText: (node: Node) => string;
/**
* Does the element have the named attribute?
*/
hasAttrib: (elem: ElementNode, name: string) => boolean;
/**
* Takes an array of nodes, and removes any duplicates, as well as any
* nodes whose ancestors are also in the array.
*/
removeSubsets: (nodes: Node[]) => Node[];
/**
* Finds all of the element nodes in the array that match the test predicate,
* as well as any of their children that match it.
*/
findAll: (test: Predicate<ElementNode>, nodes: Node[]) => ElementNode[];
/**
* Finds the first node in the array that matches the test predicate, or one
* of its children.
*/
findOne: (test: Predicate<ElementNode>, elems: Node[]) => ElementNode | null;
/**
* The adapter can also optionally include an equals method, if your DOM
* structure needs a custom equality test to compare two objects which refer
* to the same underlying node. If not provided, `css-select` will fall back to
* `a === b`.
*/
equals?: (a: Node, b: Node) => boolean;
/**
* Is the element in hovered state?
*/
isHovered?: (elem: ElementNode) => boolean;
/**
* Is the element in visited state?
*/
isVisited?: (elem: ElementNode) => boolean;
/**
* Is the element in active state?
*/
isActive?: (elem: ElementNode) => boolean;
}
export interface Options<Node, ElementNode extends Node> {
/**
* When enabled, tag names will be case-sensitive.
*
* @default false
*/
xmlMode?: boolean;
/**
* Lower-case attribute names.
*
* @default !xmlMode
*/
lowerCaseAttributeNames?: boolean;
/**
* Lower-case tag names.
*
* @default !xmlMode
*/
lowerCaseTags?: boolean;
/**
* Is the document in quirks mode?
*
* This will lead to .className and #id being case-insensitive.
*
* @default false
*/
quirksMode?: boolean;
/**
* Pseudo-classes that override the default ones.
*
* Maps from names to either strings of functions.
* - A string value is a selector that the element must match to be selected.
* - A function is called with the element as its first argument, and optional
* parameters second. If it returns true, the element is selected.
*/
pseudos?: Record<string, string | ((elem: ElementNode, value?: string | null) => boolean)> | undefined;
/**
* The last function in the stack, will be called with the last element
* that's looked at.
*/
rootFunc?: (element: ElementNode) => boolean;
/**
* The adapter to use when interacting with the backing DOM structure. By
* default it uses the `domutils` module.
*/
adapter?: Adapter<Node, ElementNode>;
/**
* The context of the current query. Used to limit the scope of searches.
* Can be matched directly using the `:scope` pseudo-class.
*/
context?: Node | Node[];
/**
* Indicates whether to consider the selector as a relative selector.
*
* Relative selectors that don't include a `:scope` pseudo-class behave
* as if they have a `:scope ` prefix (a `:scope` pseudo-class, followed by
* a descendant selector).
*
* If relative selectors are disabled, selectors starting with a traversal
* will lead to an error.
*
* @default true
* @see {@link https://www.w3.org/TR/selectors-4/#relative}
*/
relativeSelector?: boolean;
/**
* Allow css-select to cache results for some selectors, sometimes greatly
* improving querying performance. Disable this if your document can
* change in between queries with the same compiled selector.
*
* @default true
*/
cacheResults?: boolean;
}
export interface InternalOptions<Node, ElementNode extends Node> extends Options<Node, ElementNode> {
adapter: Adapter<Node, ElementNode>;
equals: (a: Node, b: Node) => boolean;
}
export interface CompiledQuery<ElementNode> {
(node: ElementNode): boolean;
shouldTestNextSiblings?: boolean;
}
export declare type Query<ElementNode> = string | CompiledQuery<ElementNode> | Selector[][];
export declare type CompileToken<Node, ElementNode extends Node> = (token: InternalSelector[][], options: InternalOptions<Node, ElementNode>, context?: Node[] | Node) => CompiledQuery<ElementNode>;
//# sourceMappingURL=types.d.ts.map
+2
View File
@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=types.js.map