Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

unist-util-select

Package Overview
Dependencies
Maintainers
2
Versions
21
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

unist-util-select - npm Package Compare versions

Comparing version 4.0.1 to 4.0.2

lib/walk.d.ts

51

index.d.ts
/**
* 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
}
/**
* @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

55

lib/attribute.js

@@ -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": {

@@ -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
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc