UNPKG

css-select

Version:

a CSS selector compiler/engine

137 lines (129 loc) 4.15 kB
import { AttributeAction, type AttributeSelector, isTraversal as isTraversalBase, SelectorType, type Traversal, } from "css-what"; import type { InternalSelector } from "../types.js"; /** * Check whether a selector token performs traversal. * @param token Selector token(s) to compile. */ export function isTraversal(token: InternalSelector): token is Traversal { 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 array Selector to sort */ export function sortRules(array: InternalSelector[]): void { const ratings = array.map(getQuality); for (let index = 1; index < array.length; index++) { const procNew = ratings[index]; if (procNew < 0) { continue; } // Use insertion sort to move the token to the correct position. for ( let currentIndex = index; currentIndex > 0 && procNew < ratings[currentIndex - 1]; currentIndex-- ) { const token = array[currentIndex]; array[currentIndex] = array[currentIndex - 1]; array[currentIndex - 1] = token; ratings[currentIndex] = ratings[currentIndex - 1]; ratings[currentIndex - 1] = procNew; } } } function getAttributeQuality(token: AttributeSelector): number { 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: InternalSelector): number { 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 ? 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 : 3; } default: { return -1; } } } /** * Check whether a token or nested token includes `:scope`. * @param t Selector token under inspection. */ export function includesScopePseudo(t: InternalSelector): boolean { return ( t.type === SelectorType.Pseudo && (t.name === "scope" || (Array.isArray(t.data) && t.data.some((data) => data.some(includesScopePseudo)))) ); }