unist-util-select
Advanced tools
Comparing version 4.0.1 to 4.0.2
/** | ||
* Check that the given `node` matches `selector`. | ||
* | ||
* This only checks the node itself, not the surrounding tree. | ||
* Thus, nesting in selectors is not supported (`paragraph strong`, | ||
* `paragraph > strong`), neither are selectors like `:first-child`, etc. | ||
* This only checks that the given node matches the selector. | ||
* | ||
* @param {string} selector | ||
* @param {Node} [node] | ||
* CSS selector, such as (`heading`, `link, linkReference`). | ||
* @param {Node | NodeLike | null | undefined} [node] | ||
* Node that might match `selector`. | ||
* @returns {boolean} | ||
* Whether `node` matches `selector`. | ||
*/ | ||
export function matches( | ||
selector: string, | ||
node?: import('unist').Node<import('unist').Data> | undefined | ||
node?: Node | NodeLike | null | undefined | ||
): boolean | ||
/** | ||
* Select the first node that matches `selector` in the given `tree`. | ||
* | ||
* Searches the tree in *preorder*. | ||
* | ||
* @param {string} selector | ||
* @param {Node} [node] | ||
* @returns {Node|null} | ||
* CSS selector, such as (`heading`, `link, linkReference`). | ||
* @param {Node | NodeLike | null | undefined} [tree] | ||
* Tree to search. | ||
* @returns {Node | null} | ||
* First node in `tree` that matches `selector` or `null` if nothing is | ||
* found. | ||
* | ||
* This could be `tree` itself. | ||
*/ | ||
export function select( | ||
selector: string, | ||
node?: import('unist').Node<import('unist').Data> | undefined | ||
tree?: Node | NodeLike | null | undefined | ||
): Node | null | ||
/** | ||
* Select all nodes that match `selector` in the given `tree`. | ||
* | ||
* Searches the tree in *preorder*. | ||
* | ||
* @param {string} selector | ||
* @param {Node} [node] | ||
* @returns {Array.<Node>} | ||
* CSS selector, such as (`heading`, `link, linkReference`). | ||
* @param {Node | NodeLike | null | undefined} [tree] | ||
* Tree to search. | ||
* @returns {Array<Node>} | ||
* Nodes in `tree` that match `selector`. | ||
* | ||
* This could include `tree` itself. | ||
*/ | ||
export function selectAll( | ||
selector: string, | ||
node?: import('unist').Node<import('unist').Data> | undefined | ||
tree?: Node | NodeLike | null | undefined | ||
): Array<Node> | ||
export type Position = import('unist').Position | ||
export type Node = import('unist').Node | ||
export type SelectState = import('./lib/types.js').SelectState | ||
export type NodeLike = Record<string, unknown> & { | ||
type: string | ||
position?: Position | undefined | ||
} |
95
index.js
/** | ||
* @typedef {import('unist').Position} Position | ||
* @typedef {import('unist').Node} Node | ||
* @typedef {import('./lib/types.js').SelectState} SelectState | ||
* @typedef {Record<string, unknown> & {type: string, position?: Position | undefined}} NodeLike | ||
*/ | ||
import {any} from './lib/any.js' | ||
import {queryToSelectors, walk} from './lib/walk.js' | ||
import {parse} from './lib/parse.js' | ||
import {parent} from './lib/util.js' | ||
/** | ||
* Check that the given `node` matches `selector`. | ||
* | ||
* This only checks the node itself, not the surrounding tree. | ||
* Thus, nesting in selectors is not supported (`paragraph strong`, | ||
* `paragraph > strong`), neither are selectors like `:first-child`, etc. | ||
* This only checks that the given node matches the selector. | ||
* | ||
* @param {string} selector | ||
* @param {Node} [node] | ||
* CSS selector, such as (`heading`, `link, linkReference`). | ||
* @param {Node | NodeLike | null | undefined} [node] | ||
* Node that might match `selector`. | ||
* @returns {boolean} | ||
* Whether `node` matches `selector`. | ||
*/ | ||
export function matches(selector, node) { | ||
return Boolean(any(parse(selector), node, {one: true, shallow: true, any})[0]) | ||
const state = createState(selector, node) | ||
state.one = true | ||
state.shallow = true | ||
walk(state, node || undefined) | ||
return state.results.length > 0 | ||
} | ||
/** | ||
* Select the first node that matches `selector` in the given `tree`. | ||
* | ||
* Searches the tree in *preorder*. | ||
* | ||
* @param {string} selector | ||
* @param {Node} [node] | ||
* @returns {Node|null} | ||
* CSS selector, such as (`heading`, `link, linkReference`). | ||
* @param {Node | NodeLike | null | undefined} [tree] | ||
* Tree to search. | ||
* @returns {Node | null} | ||
* First node in `tree` that matches `selector` or `null` if nothing is | ||
* found. | ||
* | ||
* This could be `tree` itself. | ||
*/ | ||
export function select(selector, node) { | ||
return any(parse(selector), node, {one: true, any})[0] || null | ||
export function select(selector, tree) { | ||
const state = createState(selector, tree) | ||
state.one = true | ||
walk(state, tree || undefined) | ||
// To do next major: return `undefined`. | ||
return state.results[0] || null | ||
} | ||
/** | ||
* Select all nodes that match `selector` in the given `tree`. | ||
* | ||
* Searches the tree in *preorder*. | ||
* | ||
* @param {string} selector | ||
* @param {Node} [node] | ||
* @returns {Array.<Node>} | ||
* CSS selector, such as (`heading`, `link, linkReference`). | ||
* @param {Node | NodeLike | null | undefined} [tree] | ||
* Tree to search. | ||
* @returns {Array<Node>} | ||
* Nodes in `tree` that match `selector`. | ||
* | ||
* This could include `tree` itself. | ||
*/ | ||
export function selectAll(selector, node) { | ||
return any(parse(selector), node, {any}) | ||
export function selectAll(selector, tree) { | ||
const state = createState(selector, tree) | ||
walk(state, tree || undefined) | ||
return state.results | ||
} | ||
/** | ||
* @param {string} selector | ||
* Selector to parse. | ||
* @param {Node | null | undefined} tree | ||
* Tree to search. | ||
* @returns {SelectState} | ||
*/ | ||
function createState(selector, tree) { | ||
return { | ||
// State of the query. | ||
rootQuery: queryToSelectors(parse(selector)), | ||
results: [], | ||
scopeNodes: tree | ||
? parent(tree) && | ||
// Root in nlcst. | ||
(tree.type === 'RootNode' || tree.type === 'root') | ||
? tree.children | ||
: [tree] | ||
: [], | ||
one: false, | ||
shallow: false, | ||
found: false, | ||
// State in the tree. | ||
typeIndex: undefined, | ||
nodeIndex: undefined, | ||
typeCount: undefined, | ||
nodeCount: undefined | ||
} | ||
} |
/** | ||
* @param {Rule} query | ||
* @param {Node} node | ||
* @returns {boolean} | ||
*/ | ||
@@ -5,0 +6,0 @@ export function attribute(query: Rule, node: Node): boolean |
@@ -9,4 +9,4 @@ /** | ||
/** @type {(query: RuleAttr, node: Node) => boolean} */ | ||
const handle = zwitch('operator', { | ||
// @ts-expect-error: hush. | ||
unknown: unknownOperator, | ||
@@ -16,11 +16,6 @@ // @ts-expect-error: hush. | ||
handlers: { | ||
// @ts-expect-error: hush. | ||
'=': exact, | ||
// @ts-expect-error: hush. | ||
'^=': begins, | ||
// @ts-expect-error: hush. | ||
'$=': ends, | ||
// @ts-expect-error: hush. | ||
'*=': containsString, | ||
// @ts-expect-error: hush. | ||
'~=': containsArray | ||
@@ -33,2 +28,3 @@ } | ||
* @param {Node} node | ||
* @returns {boolean} | ||
*/ | ||
@@ -46,2 +42,4 @@ export function attribute(query, node) { | ||
/** | ||
* Check whether an attribute exists. | ||
* | ||
* `[attr]` | ||
@@ -51,2 +49,3 @@ * | ||
* @param {Node} node | ||
* @returns {boolean} | ||
*/ | ||
@@ -59,2 +58,4 @@ function exists(query, node) { | ||
/** | ||
* Check whether an attribute has an exact value. | ||
* | ||
* `[attr=value]` | ||
@@ -64,2 +65,3 @@ * | ||
* @param {Node} node | ||
* @returns {boolean} | ||
*/ | ||
@@ -72,2 +74,7 @@ function exact(query, node) { | ||
/** | ||
* Check whether an attribute, as a list, contains a value. | ||
* | ||
* When the attribute value is not a list, checks that the serialized value | ||
* is the queried one. | ||
* | ||
* `[attr~=value]` | ||
@@ -77,2 +84,3 @@ * | ||
* @param {Node} node | ||
* @returns {boolean} | ||
*/ | ||
@@ -88,3 +96,3 @@ function containsArray(query, node) { | ||
// Coverage comment in place because TS turns `Array.isArray(unknown)` | ||
// into `Array.<any>` instead of `Array.<unknown>`. | ||
// into `Array<any>` instead of `Array<unknown>`. | ||
// type-coverage:ignore-next-line | ||
@@ -100,2 +108,4 @@ if (Array.isArray(value) && value.includes(query.value)) { | ||
/** | ||
* Check whether an attribute has a substring as its start. | ||
* | ||
* `[attr^=value]` | ||
@@ -105,2 +115,3 @@ * | ||
* @param {Node} node | ||
* @returns {boolean} | ||
*/ | ||
@@ -112,6 +123,6 @@ function begins(query, node) { | ||
return ( | ||
return Boolean( | ||
query.value && | ||
typeof value === 'string' && | ||
value.slice(0, query.value.length) === query.value | ||
typeof value === 'string' && | ||
value.slice(0, query.value.length) === query.value | ||
) | ||
@@ -121,2 +132,4 @@ } | ||
/** | ||
* Check whether an attribute has a substring as its end. | ||
* | ||
* `[attr$=value]` | ||
@@ -126,2 +139,3 @@ * | ||
* @param {Node} node | ||
* @returns {boolean} | ||
*/ | ||
@@ -133,6 +147,6 @@ function ends(query, node) { | ||
return ( | ||
return Boolean( | ||
query.value && | ||
typeof value === 'string' && | ||
value.slice(-query.value.length) === query.value | ||
typeof value === 'string' && | ||
value.slice(-query.value.length) === query.value | ||
) | ||
@@ -142,2 +156,4 @@ } | ||
/** | ||
* Check whether an attribute contains a substring. | ||
* | ||
* `[attr*=value]` | ||
@@ -147,2 +163,3 @@ * | ||
* @param {Node} node | ||
* @returns {boolean} | ||
*/ | ||
@@ -153,12 +170,16 @@ function containsString(query, node) { | ||
const value = node[query.name] | ||
return query.value && typeof value === 'string' && value.includes(query.value) | ||
return Boolean( | ||
query.value && typeof value === 'string' && value.includes(query.value) | ||
) | ||
} | ||
// Shouldn’t be invoked, Parser throws an error instead. | ||
/* c8 ignore next 6 */ | ||
// Shouldn’t be called, parser throws an error instead. | ||
/** | ||
* @param {{[x: string]: unknown, type: string}} query | ||
* @param {unknown} query | ||
* @returns {never} | ||
*/ | ||
/* c8 ignore next 4 */ | ||
function unknownOperator(query) { | ||
// @ts-expect-error: `operator` guaranteed. | ||
throw new Error('Unknown operator `' + query.operator + '`') | ||
} |
@@ -6,2 +6,4 @@ /** | ||
/** | ||
* Check whether a node has a type. | ||
* | ||
* @param {Rule} query | ||
@@ -8,0 +10,0 @@ * @param {Node} node |
@@ -7,2 +7,4 @@ /** | ||
/** | ||
* Check whether a node has a type. | ||
* | ||
* @param {Rule} query | ||
@@ -9,0 +11,0 @@ * @param {Node} node |
/** | ||
* @param {string} selector | ||
* @returns {Selector} | ||
* @returns {Selectors | RuleSet | null} | ||
*/ | ||
export function parse(selector: string): Selector | ||
export type Selector = import('./types.js').Selector | ||
export function parse(selector: string): Selectors | RuleSet | null | ||
export type Selectors = import('./types.js').Selectors | ||
export type RuleSet = import('./types.js').RuleSet | ||
export type Rule = import('./types.js').Rule | ||
export type RulePseudo = import('./types.js').RulePseudo | ||
export type RulePseudoNth = import('./types.js').RulePseudoNth |
/** | ||
* @typedef {import('./types.js').Selector} Selector | ||
* @typedef {import('./types.js').Selectors} Selectors | ||
* @typedef {import('./types.js').RuleSet} RuleSet | ||
* @typedef {import('./types.js').Rule} Rule | ||
* @typedef {import('./types.js').RulePseudo} RulePseudo | ||
* @typedef {import('./types.js').RulePseudoNth} RulePseudoNth | ||
*/ | ||
import {CssSelectorParser} from 'css-selector-parser' | ||
import fauxEsmNthCheck from 'nth-check' | ||
import {zwitch} from 'zwitch' | ||
/** @type {import('nth-check').default} */ | ||
// @ts-expect-error | ||
const nthCheck = fauxEsmNthCheck.default | ||
const nth = new Set([ | ||
'nth-child', | ||
'nth-last-child', | ||
'nth-of-type', | ||
'nth-last-of-type' | ||
]) | ||
const parser = new CssSelectorParser() | ||
@@ -31,8 +14,5 @@ | ||
// @ts-expect-error: hush. | ||
const compile = zwitch('type', {handlers: {selectors, ruleSet, rule}}) | ||
/** | ||
* @param {string} selector | ||
* @returns {Selector} | ||
* @returns {Selectors | RuleSet | null} | ||
*/ | ||
@@ -44,50 +24,3 @@ export function parse(selector) { | ||
// @ts-expect-error types are wrong. | ||
return compile(parser.parse(selector)) | ||
return parser.parse(selector) | ||
} | ||
/** | ||
* @param {Selectors} query | ||
*/ | ||
function selectors(query) { | ||
const selectors = query.selectors | ||
let index = -1 | ||
while (++index < selectors.length) { | ||
compile(selectors[index]) | ||
} | ||
return query | ||
} | ||
/** | ||
* @param {RuleSet} query | ||
*/ | ||
function ruleSet(query) { | ||
return rule(query.rule) | ||
} | ||
/** | ||
* @param {Rule} query | ||
*/ | ||
function rule(query) { | ||
const pseudos = query.pseudos || [] | ||
let index = -1 | ||
/** @type {RulePseudo|RulePseudoNth} */ | ||
let pseudo | ||
while (++index < pseudos.length) { | ||
pseudo = pseudos[index] | ||
if (nth.has(pseudo.name)) { | ||
// @ts-expect-error Patch a non-primitive type. | ||
pseudo.value = nthCheck(pseudo.value) | ||
// @ts-expect-error Patch a non-primitive type. | ||
pseudo.valueType = 'function' | ||
} | ||
} | ||
compile(query.rule) | ||
return query | ||
} |
/** | ||
* Check whether an node matches pseudo selectors. | ||
* | ||
* @param {Rule} query | ||
* @param {Node} node | ||
* @param {number|null} index | ||
* @param {Parent|null} parent | ||
* @param {number | undefined} index | ||
* @param {Parent | undefined} parent | ||
* @param {SelectState} state | ||
@@ -12,4 +14,4 @@ * @returns {boolean} | ||
node: Node, | ||
index: number | null, | ||
parent: Parent | null, | ||
index: number | undefined, | ||
parent: Parent | undefined, | ||
state: SelectState | ||
@@ -22,8 +24,5 @@ ): boolean | ||
export type RulePseudo = import('./types.js').RulePseudo | ||
export type RulePseudoNth = import('./types.js').RulePseudoNth | ||
export type RulePseudoSelector = import('./types.js').RulePseudoSelector | ||
export type Parent = import('./types.js').Parent | ||
export type Selector = import('./types.js').Selector | ||
export type Selectors = import('./types.js').Selectors | ||
export type SelectState = import('./types.js').SelectState | ||
export type Node = import('./types.js').Node |
/** | ||
* @typedef {import('./types.js').Rule} Rule | ||
* @typedef {import('./types.js').RulePseudo} RulePseudo | ||
* @typedef {import('./types.js').RulePseudoNth} RulePseudoNth | ||
* @typedef {import('./types.js').RulePseudoSelector} RulePseudoSelector | ||
* @typedef {import('./types.js').Parent} Parent | ||
* @typedef {import('./types.js').Selector} Selector | ||
* @typedef {import('./types.js').Selectors} Selectors | ||
* @typedef {import('./types.js').SelectState} SelectState | ||
@@ -13,48 +10,33 @@ * @typedef {import('./types.js').Node} Node | ||
import fauxEsmNthCheck from 'nth-check' | ||
import {zwitch} from 'zwitch' | ||
import {convert} from 'unist-util-is' | ||
import {parent} from './util.js' | ||
import {queryToSelectors, walk} from './walk.js' | ||
const is = convert() | ||
/** @type {import('nth-check').default} */ | ||
// @ts-expect-error | ||
const nthCheck = fauxEsmNthCheck.default || fauxEsmNthCheck | ||
/** @type {(rule: Rule | RulePseudo, node: Node, index: number | undefined, parent: Parent | undefined, state: SelectState) => boolean} */ | ||
const handle = zwitch('name', { | ||
// @ts-expect-error: hush. | ||
unknown: unknownPseudo, | ||
invalid: invalidPseudo, | ||
handlers: { | ||
// @ts-expect-error: hush. | ||
any: matches, | ||
// @ts-expect-error: hush. | ||
blank: empty, | ||
// @ts-expect-error: hush. | ||
empty, | ||
// @ts-expect-error: hush. | ||
'first-child': firstChild, | ||
// @ts-expect-error: hush. | ||
'first-of-type': firstOfType, | ||
// @ts-expect-error: hush. | ||
has: hasSelector, | ||
// @ts-expect-error: hush. | ||
has, | ||
'last-child': lastChild, | ||
// @ts-expect-error: hush. | ||
'last-of-type': lastOfType, | ||
// @ts-expect-error: hush. | ||
matches, | ||
// @ts-expect-error: hush. | ||
not, | ||
// @ts-expect-error: hush. | ||
'nth-child': nthChild, | ||
// @ts-expect-error: hush. | ||
'nth-last-child': nthLastChild, | ||
// @ts-expect-error: hush. | ||
'nth-of-type': nthOfType, | ||
// @ts-expect-error: hush. | ||
'nth-last-of-type': nthLastOfType, | ||
// @ts-expect-error: hush. | ||
'only-child': onlyChild, | ||
// @ts-expect-error: hush. | ||
'only-of-type': onlyOfType, | ||
// @ts-expect-error: hush. | ||
root, | ||
// @ts-expect-error: hush. | ||
scope | ||
@@ -65,2 +47,3 @@ } | ||
pseudo.needsIndex = [ | ||
'any', | ||
'first-child', | ||
@@ -70,2 +53,4 @@ 'first-of-type', | ||
'last-of-type', | ||
'matches', | ||
'not', | ||
'nth-child', | ||
@@ -80,6 +65,8 @@ 'nth-last-child', | ||
/** | ||
* Check whether an node matches pseudo selectors. | ||
* | ||
* @param {Rule} query | ||
* @param {Node} node | ||
* @param {number|null} index | ||
* @param {Parent|null} parent | ||
* @param {number | undefined} index | ||
* @param {Parent | undefined} parent | ||
* @param {SelectState} state | ||
@@ -100,98 +87,102 @@ * @returns {boolean} | ||
/** | ||
* @param {RulePseudoSelector} query | ||
* Check whether a node matches an `:empty` pseudo. | ||
* | ||
* @param {RulePseudo} _1 | ||
* @param {Node} node | ||
* @param {number|null} _1 | ||
* @param {Parent|null} _2 | ||
* @param {SelectState} state | ||
* @returns {boolean} | ||
*/ | ||
function matches(query, node, _1, _2, state) { | ||
const shallow = state.shallow | ||
const one = state.one | ||
state.one = true | ||
state.shallow = true | ||
const result = state.any(query.value, node, state)[0] === node | ||
state.shallow = shallow | ||
state.one = one | ||
return result | ||
function empty(_1, node) { | ||
return parent(node) ? node.children.length === 0 : !('value' in node) | ||
} | ||
/** | ||
* @param {RulePseudoSelector} query | ||
* @param {Node} node | ||
* @param {number|null} index | ||
* @param {Parent|null} parent | ||
* Check whether a node matches a `:first-child` pseudo. | ||
* | ||
* @param {RulePseudo} query | ||
* @param {Node} _1 | ||
* @param {number | undefined} _2 | ||
* @param {Parent | undefined} _3 | ||
* @param {SelectState} state | ||
* @returns {boolean} | ||
*/ | ||
function not(query, node, index, parent, state) { | ||
return !matches(query, node, index, parent, state) | ||
function firstChild(query, _1, _2, _3, state) { | ||
assertDeep(state, query) | ||
return state.nodeIndex === 0 // Specifically `0`, not falsey. | ||
} | ||
/** | ||
* @param {RulePseudo} _1 | ||
* @param {Node} node | ||
* @param {number|null} _2 | ||
* @param {Parent|null} parent | ||
* Check whether a node matches a `:first-of-type` pseudo. | ||
* | ||
* @param {RulePseudo} query | ||
* @param {Node} _1 | ||
* @param {number | undefined} _2 | ||
* @param {Parent | undefined} _3 | ||
* @param {SelectState} state | ||
* @returns {boolean} | ||
*/ | ||
function root(_1, node, _2, parent) { | ||
return is(node) && !parent | ||
function firstOfType(query, _1, _2, _3, state) { | ||
assertDeep(state, query) | ||
return state.typeIndex === 0 | ||
} | ||
/** | ||
* @param {RulePseudo} _1 | ||
* @param {RulePseudoSelector} query | ||
* @param {Node} node | ||
* @param {number|null} _2 | ||
* @param {Parent|null} _3 | ||
* @param {number | undefined} _1 | ||
* @param {Parent | undefined} _2 | ||
* @param {SelectState} state | ||
* @returns {boolean} | ||
*/ | ||
function scope(_1, node, _2, _3, state) { | ||
return ( | ||
is(node) && | ||
state.scopeNodes !== undefined && | ||
state.scopeNodes.includes(node) | ||
) | ||
} | ||
function has(query, node, _1, _2, state) { | ||
const fragment = {type: 'root', children: parent(node) ? node.children : []} | ||
/** @type {SelectState} */ | ||
const childState = { | ||
...state, | ||
// Do walk deep. | ||
shallow: false, | ||
// One result is enough. | ||
one: true, | ||
scopeNodes: [node], | ||
results: [], | ||
rootQuery: queryToSelectors(query.value) | ||
} | ||
/** | ||
* @param {RulePseudo} _1 | ||
* @param {Node} node | ||
* @returns {boolean} | ||
*/ | ||
function empty(_1, node) { | ||
return parent(node) ? node.children.length === 0 : !('value' in node) | ||
walk(childState, fragment) | ||
return childState.results.length > 0 | ||
} | ||
/** | ||
* Check whether a node matches a `:last-child` pseudo. | ||
* | ||
* @param {RulePseudo} query | ||
* @param {Node} _1 | ||
* @param {number|null} _2 | ||
* @param {Parent|null} _3 | ||
* @param {number | undefined} _2 | ||
* @param {Parent | undefined} _3 | ||
* @param {SelectState} state | ||
* @returns {boolean} | ||
*/ | ||
function firstChild(query, _1, _2, _3, state) { | ||
function lastChild(query, _1, _2, _3, state) { | ||
assertDeep(state, query) | ||
return state.nodeIndex === 0 // Specifically `0`, not falsey. | ||
return ( | ||
typeof state.nodeCount === 'number' && | ||
state.nodeIndex === state.nodeCount - 1 | ||
) | ||
} | ||
/** | ||
* Check whether a node matches a `:last-of-type` pseudo. | ||
* | ||
* @param {RulePseudo} query | ||
* @param {Node} _1 | ||
* @param {number|null} _2 | ||
* @param {Parent|null} _3 | ||
* @param {number | undefined} _2 | ||
* @param {Parent | undefined} _3 | ||
* @param {SelectState} state | ||
* @returns {boolean} | ||
*/ | ||
function lastChild(query, _1, _2, _3, state) { | ||
function lastOfType(query, _1, _2, _3, state) { | ||
assertDeep(state, query) | ||
return ( | ||
typeof state.nodeCount === 'number' && | ||
state.nodeIndex === state.nodeCount - 1 | ||
typeof state.typeCount === 'number' && | ||
state.typeIndex === state.typeCount - 1 | ||
) | ||
@@ -201,19 +192,50 @@ } | ||
/** | ||
* @param {RulePseudo} query | ||
* @param {Node} _1 | ||
* @param {number|null} _2 | ||
* @param {Parent|null} _3 | ||
* Check whether a node `:matches` further selectors. | ||
* | ||
* @param {RulePseudoSelector} query | ||
* @param {Node} node | ||
* @param {number | undefined} _1 | ||
* @param {Parent | undefined} _2 | ||
* @param {SelectState} state | ||
* @returns {boolean} | ||
*/ | ||
function onlyChild(query, _1, _2, _3, state) { | ||
assertDeep(state, query) | ||
return state.nodeCount === 1 | ||
function matches(query, node, _1, _2, state) { | ||
/** @type {SelectState} */ | ||
const childState = { | ||
...state, | ||
// Do walk deep. | ||
shallow: false, | ||
// One result is enough. | ||
one: true, | ||
scopeNodes: [node], | ||
results: [], | ||
rootQuery: queryToSelectors(query.value) | ||
} | ||
walk(childState, node) | ||
return childState.results[0] === node | ||
} | ||
/** | ||
* @param {RulePseudoNth} query | ||
* Check whether a node does `:not` match further selectors. | ||
* | ||
* @param {RulePseudoSelector} query | ||
* @param {Node} node | ||
* @param {number | undefined} index | ||
* @param {Parent | undefined} parent | ||
* @param {SelectState} state | ||
* @returns {boolean} | ||
*/ | ||
function not(query, node, index, parent, state) { | ||
return !matches(query, node, index, parent, state) | ||
} | ||
/** | ||
* Check whether a node matches an `:nth-child` pseudo. | ||
* | ||
* @param {RulePseudo} query | ||
* @param {Node} _1 | ||
* @param {number|null} _2 | ||
* @param {Parent|null} _3 | ||
* @param {number | undefined} _2 | ||
* @param {Parent | undefined} _3 | ||
* @param {SelectState} state | ||
@@ -223,11 +245,14 @@ * @returns {boolean} | ||
function nthChild(query, _1, _2, _3, state) { | ||
const fn = getCachedNthCheck(query) | ||
assertDeep(state, query) | ||
return typeof state.nodeIndex === 'number' && query.value(state.nodeIndex) | ||
return typeof state.nodeIndex === 'number' && fn(state.nodeIndex) | ||
} | ||
/** | ||
* @param {RulePseudoNth} query | ||
* Check whether a node matches an `:nth-last-child` pseudo. | ||
* | ||
* @param {RulePseudo} query | ||
* @param {Node} _1 | ||
* @param {number|null} _2 | ||
* @param {Parent|null} _3 | ||
* @param {number | undefined} _2 | ||
* @param {Parent | undefined} _3 | ||
* @param {SelectState} state | ||
@@ -237,2 +262,3 @@ * @returns {boolean} | ||
function nthLastChild(query, _1, _2, _3, state) { | ||
const fn = getCachedNthCheck(query) | ||
assertDeep(state, query) | ||
@@ -242,3 +268,3 @@ return ( | ||
typeof state.nodeIndex === 'number' && | ||
query.value(state.nodeCount - state.nodeIndex - 1) | ||
fn(state.nodeCount - state.nodeIndex - 1) | ||
) | ||
@@ -248,23 +274,13 @@ } | ||
/** | ||
* @param {RulePseudoNth} query | ||
* Check whether a node matches a `:nth-last-of-type` pseudo. | ||
* | ||
* @param {RulePseudo} query | ||
* @param {Node} _1 | ||
* @param {number|null} _2 | ||
* @param {Parent|null} _3 | ||
* @param {number | undefined} _2 | ||
* @param {Parent | undefined} _3 | ||
* @param {SelectState} state | ||
* @returns {boolean} | ||
*/ | ||
function nthOfType(query, _1, _2, _3, state) { | ||
assertDeep(state, query) | ||
return typeof state.typeIndex === 'number' && query.value(state.typeIndex) | ||
} | ||
/** | ||
* @param {RulePseudoNth} query | ||
* @param {Node} _1 | ||
* @param {number|null} _2 | ||
* @param {Parent|null} _3 | ||
* @param {SelectState} state | ||
* @returns {boolean} | ||
*/ | ||
function nthLastOfType(query, _1, _2, _3, state) { | ||
const fn = getCachedNthCheck(query) | ||
assertDeep(state, query) | ||
@@ -274,3 +290,3 @@ return ( | ||
typeof state.typeCount === 'number' && | ||
query.value(state.typeCount - 1 - state.typeIndex) | ||
fn(state.typeCount - 1 - state.typeIndex) | ||
) | ||
@@ -280,35 +296,39 @@ } | ||
/** | ||
* Check whether a node matches an `:nth-of-type` pseudo. | ||
* | ||
* @param {RulePseudo} query | ||
* @param {Node} _1 | ||
* @param {number|null} _2 | ||
* @param {Parent|null} _3 | ||
* @param {number | undefined} _2 | ||
* @param {Parent | undefined} _3 | ||
* @param {SelectState} state | ||
* @returns {boolean} | ||
*/ | ||
function firstOfType(query, _1, _2, _3, state) { | ||
function nthOfType(query, _1, _2, _3, state) { | ||
const fn = getCachedNthCheck(query) | ||
assertDeep(state, query) | ||
return state.typeIndex === 0 | ||
return typeof state.typeIndex === 'number' && fn(state.typeIndex) | ||
} | ||
/** | ||
* Check whether a node matches an `:only-child` pseudo. | ||
* | ||
* @param {RulePseudo} query | ||
* @param {Node} _1 | ||
* @param {number|null} _2 | ||
* @param {Parent|null} _3 | ||
* @param {number | undefined} _2 | ||
* @param {Parent | undefined} _3 | ||
* @param {SelectState} state | ||
* @returns {boolean} | ||
*/ | ||
function lastOfType(query, _1, _2, _3, state) { | ||
function onlyChild(query, _1, _2, _3, state) { | ||
assertDeep(state, query) | ||
return ( | ||
typeof state.typeCount === 'number' && | ||
state.typeIndex === state.typeCount - 1 | ||
) | ||
return state.nodeCount === 1 | ||
} | ||
/** | ||
* Check whether a node matches an `:only-of-type` pseudo. | ||
* | ||
* @param {RulePseudo} query | ||
* @param {Node} _1 | ||
* @param {number|null} _2 | ||
* @param {Parent|null} _3 | ||
* @param {number | undefined} _2 | ||
* @param {Parent | undefined} _3 | ||
* @param {SelectState} state | ||
@@ -322,3 +342,30 @@ * @returns {boolean} | ||
// Shouldn’t be invoked, parser gives correct data. | ||
/** | ||
* Check whether a node matches a `:root` pseudo. | ||
* | ||
* @param {RulePseudo} _1 | ||
* @param {Node} node | ||
* @param {number | undefined} _2 | ||
* @param {Parent | undefined} parent | ||
* @returns {boolean} | ||
*/ | ||
function root(_1, node, _2, parent) { | ||
return node && !parent | ||
} | ||
/** | ||
* Check whether a node matches a `:scope` pseudo. | ||
* | ||
* @param {RulePseudo} _1 | ||
* @param {Node} node | ||
* @param {number | undefined} _2 | ||
* @param {Parent | undefined} _3 | ||
* @param {SelectState} state | ||
* @returns {boolean} | ||
*/ | ||
function scope(_1, node, _2, _3, state) { | ||
return node && state.scopeNodes.includes(node) | ||
} | ||
// Shouldn’t be called, parser gives correct data. | ||
/* c8 ignore next 3 */ | ||
@@ -330,7 +377,9 @@ function invalidPseudo() { | ||
/** | ||
* @param {RulePseudo} query | ||
* @returns {boolean} | ||
* @param {unknown} query | ||
* @returns {never} | ||
*/ | ||
function unknownPseudo(query) { | ||
// @ts-expect-error: indexable. | ||
if (query.name) { | ||
// @ts-expect-error: indexable. | ||
throw new Error('Unknown pseudo-selector `' + query.name + '`') | ||
@@ -344,3 +393,3 @@ } | ||
* @param {SelectState} state | ||
* @param {RulePseudo|RulePseudoNth} query | ||
* @param {RulePseudo} query | ||
*/ | ||
@@ -354,65 +403,18 @@ function assertDeep(state, query) { | ||
/** | ||
* @param {RulePseudoSelector} query | ||
* @param {Node} node | ||
* @param {number|null} _1 | ||
* @param {Parent|null} _2 | ||
* @param {SelectState} state | ||
* @returns {boolean} | ||
* @param {RulePseudo} query | ||
* @returns {(value: number) => boolean} | ||
*/ | ||
function hasSelector(query, node, _1, _2, state) { | ||
const shallow = state.shallow | ||
const one = state.one | ||
const scopeNodes = state.scopeNodes | ||
const value = appendScope(query.value) | ||
const anything = state.any | ||
function getCachedNthCheck(query) { | ||
/** @type {(value: number) => boolean} */ | ||
// @ts-expect-error: cache. | ||
let fn = query._cachedFn | ||
state.shallow = false | ||
state.one = true | ||
state.scopeNodes = [node] | ||
const result = Boolean(anything(value, node, state)[0]) | ||
state.shallow = shallow | ||
state.one = one | ||
state.scopeNodes = scopeNodes | ||
return result | ||
} | ||
/** | ||
* @param {Selector} value | ||
*/ | ||
function appendScope(value) { | ||
/** @type {Selectors} */ | ||
const selector = | ||
value.type === 'ruleSet' ? {type: 'selectors', selectors: [value]} : value | ||
let index = -1 | ||
/** @type {Rule} */ | ||
let rule | ||
while (++index < selector.selectors.length) { | ||
rule = selector.selectors[index].rule | ||
rule.nestingOperator = null | ||
// Needed if new pseudo’s are added that accepts commas (such as | ||
// `:lang(en, nl)`) | ||
/* c8 ignore else */ | ||
if ( | ||
!rule.pseudos || | ||
rule.pseudos.length !== 1 || | ||
rule.pseudos[0].name !== 'scope' | ||
) { | ||
selector.selectors[index] = { | ||
type: 'ruleSet', | ||
rule: { | ||
type: 'rule', | ||
rule, | ||
// @ts-expect-error pseudos are fine w/ just a name! | ||
pseudos: [{name: 'scope'}] | ||
} | ||
} | ||
} | ||
if (!fn) { | ||
// @ts-expect-error: always string. | ||
fn = nthCheck(query.value) | ||
// @ts-expect-error: cache. | ||
query._cachedFn = fn | ||
} | ||
return selector | ||
return fn | ||
} |
/** | ||
* @param {Rule} query | ||
* @param {Node} node | ||
* @param {number|null} index | ||
* @param {Parent|null} parent | ||
* @param {number | undefined} index | ||
* @param {Parent | undefined} parent | ||
* @param {SelectState} state | ||
@@ -12,4 +12,4 @@ * @returns {boolean} | ||
node: Node, | ||
index: number | null, | ||
parent: Parent | null, | ||
index: number | undefined, | ||
parent: Parent | undefined, | ||
state: SelectState | ||
@@ -16,0 +16,0 @@ ): boolean |
@@ -15,4 +15,4 @@ /** | ||
* @param {Node} node | ||
* @param {number|null} index | ||
* @param {Parent|null} parent | ||
* @param {number | undefined} index | ||
* @param {Parent | undefined} parent | ||
* @param {SelectState} state | ||
@@ -19,0 +19,0 @@ * @returns {boolean} |
@@ -1,77 +0,111 @@ | ||
export type Selector = import('css-selector-parser').Selector | ||
/** | ||
* Any node. | ||
*/ | ||
export type Node = import('unist').Node | ||
/** | ||
* Node with children. | ||
*/ | ||
export type Parent = import('unist').Parent | ||
/** | ||
* Multiple selectors. | ||
*/ | ||
export type Selectors = import('css-selector-parser').Selectors | ||
/** | ||
* One rule. | ||
*/ | ||
export type Rule = import('css-selector-parser').Rule | ||
/** | ||
* Multiple rules. | ||
*/ | ||
export type RuleSet = import('css-selector-parser').RuleSet | ||
export type Rule = import('css-selector-parser').Rule | ||
/** | ||
* Pseudo rule. | ||
*/ | ||
export type RulePseudo = import('css-selector-parser').RulePseudo | ||
/** | ||
* Attribute value type. | ||
*/ | ||
export type AttrValueType = import('css-selector-parser').AttrValueType | ||
/** | ||
* Fix for types. | ||
* Fix for types from `css-selector-parser`. | ||
*/ | ||
export type Query = Selector | Rule | RulePseudo | ||
export type RuleAttr = { | ||
/** | ||
* Attribute name. | ||
*/ | ||
name: string | ||
/** | ||
* Operator, such as `'|='`, missing when for example `[x]`. | ||
*/ | ||
operator?: string | undefined | ||
valueType?: import('css-selector-parser').AttrValueType | undefined | ||
/** | ||
* More specific type for registered selector pseudos. | ||
* Attribute value type. | ||
*/ | ||
valueType?: AttrValueType | undefined | ||
/** | ||
* Attribute value. | ||
*/ | ||
value?: string | undefined | ||
} | ||
/** | ||
* More specific type for registered selector pseudos. | ||
*/ | ||
export type RulePseudoSelector = { | ||
/** | ||
* Name of pseudo, such as `'matches'`. | ||
*/ | ||
name: string | ||
/** | ||
* Set to `'selector'`, because `value` is a compiled selector. | ||
*/ | ||
valueType: 'selector' | ||
/** | ||
* Overwrite to compile nth-checks once. | ||
* Selector. | ||
*/ | ||
value: Selector | ||
value: Selectors | RuleSet | ||
} | ||
export type RulePseudoNth = { | ||
name: string | ||
valueType: 'function' | ||
value: (index: number) => boolean | ||
} | ||
export type Node = import('unist').Node | ||
export type Parent = import('unist').Parent | ||
/** | ||
* Current state. | ||
*/ | ||
export type SelectState = { | ||
any: ( | ||
query: Selectors | RuleSet | Rule, | ||
node: Node | undefined, | ||
state: SelectState | ||
) => Node[] | ||
scopeNodes?: import('unist').Node<import('unist').Data>[] | undefined | ||
iterator?: SelectIterator | null | undefined | ||
one?: boolean | undefined | ||
shallow?: boolean | undefined | ||
index?: boolean | undefined | ||
found?: boolean | undefined | ||
/** | ||
* Track siblings | ||
* Original root selectors. | ||
*/ | ||
typeIndex?: number | undefined | ||
rootQuery: Selectors | ||
/** | ||
* Track siblings | ||
* Matches. | ||
*/ | ||
nodeIndex?: number | undefined | ||
results: Array<Node> | ||
/** | ||
* Track siblings | ||
* Nodes in scope. | ||
*/ | ||
typeCount?: number | undefined | ||
scopeNodes: Array<Node> | ||
/** | ||
* Track siblings | ||
* Whether we can stop looking after we found one node. | ||
*/ | ||
nodeCount?: number | undefined | ||
one: boolean | ||
/** | ||
* Whether we only allow selectors without nesting. | ||
*/ | ||
shallow: boolean | ||
/** | ||
* Whether we found at least one match. | ||
*/ | ||
found: boolean | ||
/** | ||
* Track siblings: this current node has `n` nodes with its type before it. | ||
*/ | ||
typeIndex: number | undefined | ||
/** | ||
* Track siblings: this current node has `n` nodes before it. | ||
*/ | ||
nodeIndex: number | undefined | ||
/** | ||
* Track siblings: there are `n` siblings with this node’s type. | ||
*/ | ||
typeCount: number | undefined | ||
/** | ||
* Track siblings: there are `n` siblings. | ||
*/ | ||
nodeCount: number | undefined | ||
} | ||
export type SelectIterator = ( | ||
query: Rule, | ||
node: Node, | ||
index: number, | ||
parent: Parent | null, | ||
state: SelectState | ||
) => any | ||
export type Handler = ( | ||
query: Rule, | ||
node: Node, | ||
index: number | null, | ||
parent: Parent | null, | ||
state: SelectState | ||
) => void |
/** | ||
* @typedef {import('css-selector-parser').Selector} Selector | ||
* @typedef {import('unist').Node} Node | ||
* Any node. | ||
* @typedef {import('unist').Parent} Parent | ||
* Node with children. | ||
* | ||
* @typedef {import('css-selector-parser').Selectors} Selectors | ||
* Multiple selectors. | ||
* @typedef {import('css-selector-parser').Rule} Rule | ||
* One rule. | ||
* @typedef {import('css-selector-parser').RuleSet} RuleSet | ||
* @typedef {import('css-selector-parser').Rule} Rule | ||
* Multiple rules. | ||
* @typedef {import('css-selector-parser').RulePseudo} RulePseudo | ||
* Pseudo rule. | ||
* @typedef {import('css-selector-parser').AttrValueType} AttrValueType | ||
* @typedef {Selector|Rule|RulePseudo} Query | ||
* Attribute value type. | ||
* | ||
* Fix for types. | ||
* @typedef {Object} RuleAttr | ||
* @typedef RuleAttr | ||
* Fix for types from `css-selector-parser`. | ||
* @property {string} name | ||
* @property {string} [operator] | ||
* @property {AttrValueType} [valueType] | ||
* @property {string} [value] | ||
* Attribute name. | ||
* @property {string | undefined} [operator] | ||
* Operator, such as `'|='`, missing when for example `[x]`. | ||
* @property {AttrValueType | undefined} [valueType] | ||
* Attribute value type. | ||
* @property {string | undefined} [value] | ||
* Attribute value. | ||
* | ||
* More specific type for registered selector pseudos. | ||
* @typedef {Object} RulePseudoSelector | ||
* @typedef RulePseudoSelector | ||
* More specific type for registered selector pseudos. | ||
* @property {string} name | ||
* Name of pseudo, such as `'matches'`. | ||
* @property {'selector'} valueType | ||
* @property {Selector} value | ||
* Set to `'selector'`, because `value` is a compiled selector. | ||
* @property {Selectors | RuleSet} value | ||
* Selector. | ||
* | ||
* Overwrite to compile nth-checks once. | ||
* @typedef {Object} RulePseudoNth | ||
* @property {string} name | ||
* @property {'function'} valueType | ||
* @property {(index: number) => boolean} value | ||
* | ||
* @typedef {import('unist').Node} Node | ||
* @typedef {import('unist').Parent} Parent | ||
* | ||
* @typedef {Object} SelectState | ||
* @property {(query: Selectors|RuleSet|Rule, node: Node|undefined, state: SelectState) => Node[]} any | ||
* @property {Array.<Node>} [scopeNodes] | ||
* @property {SelectIterator|null|undefined} [iterator] | ||
* @property {boolean} [one=false] | ||
* @property {boolean} [shallow=false] | ||
* @property {boolean} [index=false] | ||
* @property {boolean} [found=false] | ||
* @property {number} [typeIndex] Track siblings | ||
* @property {number} [nodeIndex] Track siblings | ||
* @property {number} [typeCount] Track siblings | ||
* @property {number} [nodeCount] Track siblings | ||
* @typedef SelectState | ||
* Current state. | ||
* @property {Selectors} rootQuery | ||
* Original root selectors. | ||
* @property {Array<Node>} results | ||
* Matches. | ||
* @property {Array<Node>} scopeNodes | ||
* Nodes in scope. | ||
* @property {boolean} one | ||
* Whether we can stop looking after we found one node. | ||
* @property {boolean} shallow | ||
* Whether we only allow selectors without nesting. | ||
* @property {boolean} found | ||
* Whether we found at least one match. | ||
* @property {number | undefined} typeIndex | ||
* Track siblings: this current node has `n` nodes with its type before it. | ||
* @property {number | undefined} nodeIndex | ||
* Track siblings: this current node has `n` nodes before it. | ||
* @property {number | undefined} typeCount | ||
* Track siblings: there are `n` siblings with this node’s type. | ||
* @property {number | undefined} nodeCount | ||
* Track siblings: there are `n` siblings. | ||
*/ | ||
/** | ||
* @callback SelectIterator | ||
* @param {Rule} query | ||
* @param {Node} node | ||
* @param {number} index | ||
* @param {Parent|null} parent | ||
* @param {SelectState} state | ||
*/ | ||
/** | ||
* @typedef {( | ||
* ((query: Rule, node: Node, index: number|null, parent: Parent|null, state: SelectState) => void) | ||
* )} Handler | ||
*/ | ||
export {} |
/** | ||
* @typedef {import('./types.js').Selector} Selector | ||
* @typedef {import('./types.js').Selectors} Selectors | ||
* @typedef {import('./types.js').Rule} Rule | ||
* @typedef {import('./types.js').RuleSet} RuleSet | ||
* @typedef {import('./types.js').RulePseudo} RulePseudo | ||
* @typedef {import('./types.js').Query} Query | ||
* @typedef {import('./types.js').Node} Node | ||
* @typedef {import('./types.js').Parent} Parent | ||
* @typedef {import('./types.js').SelectIterator} SelectIterator | ||
* @typedef {import('./types.js').SelectState} SelectState | ||
*/ | ||
@@ -17,12 +9,2 @@ /** | ||
*/ | ||
export function root( | ||
node: Node | ||
): node is import('unist').Parent< | ||
import('unist').Node<import('unist').Data>, | ||
import('unist').Data | ||
> | ||
/** | ||
* @param {Node} node | ||
* @returns {node is Parent} | ||
*/ | ||
export function parent( | ||
@@ -34,11 +16,3 @@ node: Node | ||
> | ||
export type Selector = import('./types.js').Selector | ||
export type Selectors = import('./types.js').Selectors | ||
export type Rule = import('./types.js').Rule | ||
export type RuleSet = import('./types.js').RuleSet | ||
export type RulePseudo = import('./types.js').RulePseudo | ||
export type Query = import('./types.js').Query | ||
export type Node = import('./types.js').Node | ||
export type Parent = import('./types.js').Parent | ||
export type SelectIterator = import('./types.js').SelectIterator | ||
export type SelectState = import('./types.js').SelectState |
/** | ||
* @typedef {import('./types.js').Selector} Selector | ||
* @typedef {import('./types.js').Selectors} Selectors | ||
* @typedef {import('./types.js').Rule} Rule | ||
* @typedef {import('./types.js').RuleSet} RuleSet | ||
* @typedef {import('./types.js').RulePseudo} RulePseudo | ||
* @typedef {import('./types.js').Query} Query | ||
* @typedef {import('./types.js').Node} Node | ||
* @typedef {import('./types.js').Parent} Parent | ||
* @typedef {import('./types.js').SelectIterator} SelectIterator | ||
* @typedef {import('./types.js').SelectState} SelectState | ||
*/ | ||
@@ -18,15 +10,2 @@ | ||
*/ | ||
export function root(node) { | ||
return ( | ||
// Root in nlcst. | ||
node.type === 'RootNode' || | ||
// Rest | ||
node.type === 'root' | ||
) | ||
} | ||
/** | ||
* @param {Node} node | ||
* @returns {node is Parent} | ||
*/ | ||
export function parent(node) { | ||
@@ -33,0 +12,0 @@ // @ts-expect-error: looks like a record. |
{ | ||
"name": "unist-util-select", | ||
"version": "4.0.1", | ||
"version": "4.0.2", | ||
"description": "unist utility to select nodes with CSS-like selectors", | ||
@@ -54,24 +54,21 @@ "license": "MIT", | ||
"nth-check": "^2.0.0", | ||
"unist-util-is": "^5.0.0", | ||
"zwitch": "^2.0.0" | ||
}, | ||
"devDependencies": { | ||
"@types/tape": "^4.0.0", | ||
"@types/node": "^18.0.0", | ||
"c8": "^7.0.0", | ||
"prettier": "^2.0.0", | ||
"remark-cli": "^10.0.0", | ||
"remark-cli": "^11.0.0", | ||
"remark-preset-wooorm": "^9.0.0", | ||
"rimraf": "^3.0.0", | ||
"tape": "^5.0.0", | ||
"type-coverage": "^2.0.0", | ||
"typescript": "^4.0.0", | ||
"unist-builder": "^3.0.0", | ||
"xo": "^0.46.0" | ||
"xo": "^0.53.0" | ||
}, | ||
"scripts": { | ||
"prepack": "npm run build && npm run format", | ||
"build": "rimraf \"{lib/**,test/**,}*.d.ts\" && tsc && type-coverage", | ||
"build": "tsc --build --clean && tsc --build && type-coverage", | ||
"format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix", | ||
"test-api": "node test/index.js", | ||
"test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov node test/index.js", | ||
"test-api": "node --conditions development test/index.js", | ||
"test-coverage": "c8 --check-coverage --100 --reporter lcov npm run test-api", | ||
"test": "npm run build && npm run format && npm run test-coverage" | ||
@@ -91,3 +88,13 @@ }, | ||
"max-params": "off" | ||
} | ||
}, | ||
"overrides": [ | ||
{ | ||
"files": [ | ||
"test/**/*.js" | ||
], | ||
"rules": { | ||
"no-await-in-loop": 0 | ||
} | ||
} | ||
] | ||
}, | ||
@@ -94,0 +101,0 @@ "remarkConfig": { |
215
readme.md
@@ -11,18 +11,50 @@ # unist-util-select | ||
[**unist**][unist] utility with equivalents for `querySelector`, | ||
`querySelectorAll`, and `matches`. | ||
[unist][] utility with equivalents for `querySelector`, `querySelectorAll`, | ||
and `matches`. | ||
Note that the DOM has references to their parent nodes, meaning that | ||
`document.body.matches(':last-child')` can be evaluated. | ||
This information is not stored in unist, so selectors like that don’t work. | ||
## Contents | ||
[View the list of supported selectors »][support] | ||
* [What is this?](#what-is-this) | ||
* [When should I use this?](#when-should-i-use-this) | ||
* [Install](#install) | ||
* [Use](#use) | ||
* [API](#api) | ||
* [`matches(selector, node)`](#matchesselector-node) | ||
* [`select(selector, tree)`](#selectselector-tree) | ||
* [`selectAll(selector, tree)`](#selectallselector-tree) | ||
* [Support](#support) | ||
* [Types](#types) | ||
* [Compatibility](#compatibility) | ||
* [Related](#related) | ||
* [Contribute](#contribute) | ||
* [License](#license) | ||
## What is this? | ||
This package lets you find nodes in a tree, similar to how `querySelector`, | ||
`querySelectorAll`, and `matches` work with the DOM. | ||
One notable difference between DOM and hast is that DOM nodes have references | ||
to their parents, meaning that `document.body.matches(':last-child')` can | ||
be evaluated to check whether the body is the last child of its parent. | ||
This information is not stored in hast, so selectors like that don’t work. | ||
## When should I use this? | ||
This utility works on any unist syntax tree and you can select all node types. | ||
If you are working with [hast][], and only want to select elements, use | ||
[`hast-util-select`][hast-util-select] instead. | ||
This is a small utility that is quite useful, but is rather slow if you use it a | ||
lot. | ||
For each call, it has to walk the entire tree. | ||
In some cases, walking the tree once with [`unist-util-visit`][unist-util-visit] | ||
is smarter, such as when you want to change certain nodes. | ||
On the other hand, this is quite powerful and fast enough for many other cases. | ||
## Install | ||
This package is [ESM only](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c): | ||
Node 12+ is needed to use it and it must be `import`ed instead of `require`d. | ||
This package is [ESM only][esm]. | ||
In Node.js (version 14.14+, 16.0+), install with [npm][]: | ||
[npm][]: | ||
```sh | ||
@@ -32,5 +64,44 @@ npm install unist-util-select | ||
In Deno with [`esm.sh`][esmsh]: | ||
```js | ||
import {matches, select, selectAll} from "https://esm.sh/unist-util-select@4" | ||
``` | ||
In browsers with [`esm.sh`][esmsh]: | ||
```html | ||
<script type="module"> | ||
import {matches, select, selectAll} from "https://esm.sh/unist-util-select@4?bundle" | ||
</script> | ||
``` | ||
## Use | ||
```js | ||
import {u} from 'unist-builder' | ||
import {matches, select, selectAll} from 'unist-util-select' | ||
const tree = u('blockquote', [ | ||
u('paragraph', [u('text', 'Alpha')]), | ||
u('paragraph', [u('text', 'Bravo')]), | ||
u('code', 'Charlie'), | ||
u('paragraph', [u('text', 'Delta')]), | ||
u('paragraph', [u('text', 'Echo')]), | ||
u('paragraph', [u('text', 'Foxtrot')]), | ||
u('paragraph', [u('text', 'Golf')]) | ||
]) | ||
matches('blockquote, list', tree) // => true | ||
console.log(select('code ~ :nth-child(even)', tree)) | ||
// The paragraph with `Delta` | ||
console.log(selectAll('code ~ :nth-child(even)', tree)) | ||
// The paragraphs with `Delta` and `Foxtrot` | ||
``` | ||
## API | ||
This package exports the following identifiers: `matches`, `select`, `selectAll`. | ||
This package exports the identifiers `matches`, `select`, and `selectAll`. | ||
There is no default export. | ||
@@ -40,10 +111,22 @@ | ||
Check that the given [node][] matches `selector`. | ||
Returns `boolean`, whether the node matches or not. | ||
Check that the given `node` matches `selector`. | ||
This only checks the element itself, not the surrounding tree. | ||
This only checks the node itself, not the surrounding tree. | ||
Thus, nesting in selectors is not supported (`paragraph strong`, | ||
`paragraph > strong`), nor are selectors like `:first-child`, etc. | ||
This only checks that the given element matches the selector. | ||
`paragraph > strong`), neither are selectors like `:first-child`, etc. | ||
This only checks that the given node matches the selector. | ||
###### Parameters | ||
* `selector` (`string`) | ||
— CSS selector, such as (`heading`, `link, linkReference`). | ||
* `node` ([`Node`][node], optional) | ||
— node that might match `selector` | ||
###### Returns | ||
Whether `node` matches `selector` (`boolean`). | ||
###### Example | ||
```js | ||
@@ -59,6 +142,21 @@ import {u} from 'unist-builder' | ||
Select the first node matching `selector` in the given `tree` (could be the | ||
tree itself). | ||
Returns the found [node][], if any. | ||
Select the first node that matches `selector` in the given `tree`. | ||
Searches the tree in *[preorder][]*. | ||
###### Parameters | ||
* `selector` (`string`) | ||
— CSS selector, such as (`heading`, `link, linkReference`). | ||
* `tree` ([`Node`][node], optional) | ||
— tree to search | ||
###### Returns | ||
First node in `tree` that matches `selector` or `null` if nothing is found. | ||
This could be `tree` itself. | ||
###### Example | ||
```js | ||
@@ -90,6 +188,21 @@ import {u} from 'unist-builder' | ||
Select all nodes matching `selector` in the given `tree` (could include the | ||
tree itself). | ||
Returns all found [node][]s, if any. | ||
Select all nodes that match `selector` in the given `tree`. | ||
Searches the tree in *[preorder][]*. | ||
###### Parameters | ||
* `selector` (`string`) | ||
— CSS selector, such as (`heading`, `link, linkReference`). | ||
* `tree` ([`Node`][node], optional) | ||
— tree to search | ||
###### Returns | ||
Nodes in `tree` that match `selector`. | ||
This could include `tree` itself. | ||
###### Example | ||
```js | ||
@@ -165,29 +278,31 @@ import {u} from 'unist-builder' | ||
* \* — Not supported in `matches` | ||
* \* — not supported in `matches` | ||
## Types | ||
This package is fully typed with [TypeScript][]. | ||
It exports no additional types. | ||
## Compatibility | ||
Projects maintained by the unified collective are compatible with all maintained | ||
versions of Node.js. | ||
As of now, that is Node.js 14.14+ and 16.0+. | ||
Our projects sometimes work with older versions, but this is not guaranteed. | ||
## Related | ||
* [`unist-util-filter`](https://github.com/syntax-tree/unist-util-filter) | ||
— Create a new tree with all nodes that pass a test | ||
* [`unist-util-map`](https://github.com/syntax-tree/unist-util-map) | ||
— Create a new tree with all nodes mapped by a given function | ||
* [`unist-util-flatmap`](https://gitlab.com/staltz/unist-util-flatmap) | ||
— Create a new tree by mapping (to an array) with the given function | ||
* [`unist-util-is`](https://github.com/syntax-tree/unist-util-is) | ||
— Check if a node passes a test | ||
* [`unist-util-remove`](https://github.com/syntax-tree/unist-util-remove) | ||
— Remove nodes from trees | ||
* [`unist-util-remove-position`](https://github.com/syntax-tree/unist-util-remove-position) | ||
— Remove positional info from trees | ||
— check if a node passes a test | ||
* [`unist-util-visit`](https://github.com/syntax-tree/unist-util-visit) | ||
— Recursively walk over nodes | ||
— recursively walk over nodes | ||
* [`unist-util-visit-parents`](https://github.com/syntax-tree/unist-util-visit-parents) | ||
— Like `visit`, but with a stack of parents | ||
— like `visit`, but with a stack of parents | ||
* [`unist-builder`](https://github.com/syntax-tree/unist-builder) | ||
— Helper for creating trees | ||
— create unist trees | ||
## Contribute | ||
See [`contributing.md` in `syntax-tree/.github`][contributing] for ways to get | ||
started. | ||
See [`contributing.md`][contributing] in [`syntax-tree/.github`][health] for | ||
ways to get started. | ||
See [`support.md`][help] for ways to get help. | ||
@@ -233,10 +348,18 @@ | ||
[esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c | ||
[esmsh]: https://esm.sh | ||
[typescript]: https://www.typescriptlang.org | ||
[license]: license | ||
[contributing]: https://github.com/syntax-tree/.github/blob/HEAD/contributing.md | ||
[health]: https://github.com/syntax-tree/.github | ||
[help]: https://github.com/syntax-tree/.github/blob/HEAD/support.md | ||
[contributing]: https://github.com/syntax-tree/.github/blob/main/contributing.md | ||
[coc]: https://github.com/syntax-tree/.github/blob/HEAD/code-of-conduct.md | ||
[help]: https://github.com/syntax-tree/.github/blob/main/support.md | ||
[coc]: https://github.com/syntax-tree/.github/blob/main/code-of-conduct.md | ||
[unist]: https://github.com/syntax-tree/unist | ||
@@ -246,2 +369,8 @@ | ||
[support]: #support | ||
[preorder]: https://github.com/syntax-tree/unist#preorder | ||
[unist-util-visit]: https://github.com/syntax-tree/unist-util-visit | ||
[hast]: https://github.com/syntax-tree/hast | ||
[hast-util-select]: https://github.com/syntax-tree/hast-util-select |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
49848
4
9
369
21
1330
1
- Removedunist-util-is@^5.0.0
- Removedunist-util-is@5.2.1(transitive)