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 5.0.0 to 5.1.0

27

index.d.ts

@@ -16,6 +16,3 @@ /**

*/
export function matches(
selector: string,
node?: Node | NodeLike | null | undefined
): boolean
export function matches(selector: string, node?: Node | NodeLike | null | undefined): boolean;
/**

@@ -36,6 +33,3 @@ * Select the first node that matches `selector` in the given `tree`.

*/
export function select(
selector: string,
tree?: Node | NodeLike | null | undefined
): Node | undefined
export function select(selector: string, tree?: Node | NodeLike | null | undefined): Node | undefined;
/**

@@ -55,12 +49,9 @@ * Select all nodes that match `selector` in the given `tree`.

*/
export function selectAll(
selector: string,
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 function selectAll(selector: string, 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
}
type: string;
position?: Position | undefined;
};
/**
* @param {AstRule} query
* @param {AstAttribute} query
* Query.
* @param {Node} node
* Node.
* @returns {boolean}
* Whether `node` matches `query`.
*/
export function attribute(query: AstRule, node: Node): boolean
export type AstAttribute = import('css-selector-parser').AstAttribute
export type AstRule = import('css-selector-parser').AstRule
export type Node = import('./types.js').Node
export function attribute(query: AstAttribute, node: Node): boolean;
export type AstAttribute = import('css-selector-parser').AstAttribute;
export type AstRule = import('css-selector-parser').AstRule;
export type Node = import('./types.js').Node;

@@ -7,184 +7,69 @@ /**

import {unreachable} from 'devlop'
import {zwitch} from 'zwitch'
import {ok as assert} from 'devlop'
import {indexable} from './util.js'
/** @type {(query: AstAttribute, node: Node) => boolean} */
const handle = zwitch('operator', {
unknown: unknownOperator,
// @ts-expect-error: hush.
invalid: exists,
handlers: {
'=': exact,
'^=': begins,
'$=': ends,
'*=': containsString,
'~=': containsArray
}
})
/**
* @param {AstRule} query
* @param {Node} node
* @returns {boolean}
*/
export function attribute(query, node) {
let index = -1
if (query.attributes) {
while (++index < query.attributes.length) {
if (!handle(query.attributes[index], node)) return false
}
}
return true
}
/**
* Check whether an attribute exists.
*
* `[attr]`
*
* @param {AstAttribute} query
* Query.
* @param {Node} node
* Node.
* @returns {boolean}
* Whether `node` matches `query`.
*/
function exists(query, node) {
indexable(node)
return node[query.name] !== null && node[query.name] !== undefined
}
/**
* Check whether an attribute has an exact value.
*
* `[attr=value]`
*
* @param {AstAttribute} query
* @param {Node} node
* @returns {boolean}
*/
function exact(query, node) {
const queryValue = attributeValue(query)
export function attribute(query, node) {
indexable(node)
return exists(query, node) && String(node[query.name]) === queryValue
}
/**
* 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]`
*
* @param {AstAttribute} query
* @param {Node} node
* @returns {boolean}
*/
function containsArray(query, node) {
indexable(node)
const value = node[query.name]
if (value === null || value === undefined) return false
const queryValue = attributeValue(query)
// If this is an array, and the query is contained in it, return `true`.
// Coverage comment in place because TS turns `Array.isArray(unknown)`
// into `Array<any>` instead of `Array<unknown>`.
// type-coverage:ignore-next-line
if (Array.isArray(value) && value.includes(queryValue)) {
return true
// Exists.
if (!query.value) {
return value !== null && value !== undefined
}
// For all other values, return whether this is an exact match.
return String(value) === queryValue
}
assert(query.value.type === 'String', 'expected plain string')
let key = query.value.value
let normal = value === null || value === undefined ? undefined : String(value)
/**
* Check whether an attribute has a substring as its start.
*
* `[attr^=value]`
*
* @param {AstAttribute} query
* @param {Node} node
* @returns {boolean}
*/
function begins(query, node) {
indexable(node)
const value = node[query.name]
const queryValue = attributeValue(query)
// Case-sensitivity.
if (query.caseSensitivityModifier === 'i') {
key = key.toLowerCase()
return Boolean(
query.value &&
typeof value === 'string' &&
value.slice(0, queryValue.length) === queryValue
)
}
if (normal) {
normal = normal.toLowerCase()
}
}
/**
* Check whether an attribute has a substring as its end.
*
* `[attr$=value]`
*
* @param {AstAttribute} query
* @param {Node} node
* @returns {boolean}
*/
function ends(query, node) {
indexable(node)
const value = node[query.name]
const queryValue = attributeValue(query)
if (value !== undefined) {
switch (query.operator) {
// Exact.
case '=': {
return typeof normal === 'string' && key === normal
}
return Boolean(
query.value &&
typeof value === 'string' &&
value.slice(-queryValue.length) === queryValue
)
}
// Ends.
case '$=': {
return typeof value === 'string' && value.slice(-key.length) === key
}
/**
* Check whether an attribute contains a substring.
*
* `[attr*=value]`
*
* @param {AstAttribute} query
* @param {Node} node
* @returns {boolean}
*/
function containsString(query, node) {
indexable(node)
const value = node[query.name]
const queryValue = attributeValue(query)
// Contains.
case '*=': {
return typeof value === 'string' && value.includes(key)
}
return Boolean(
typeof value === 'string' && queryValue && value.includes(queryValue)
)
}
// Begins.
case '^=': {
return typeof value === 'string' && key === value.slice(0, key.length)
}
// Shouldn’t be called, parser throws an error instead.
/**
* @param {unknown} query
* @returns {never}
*/
/* c8 ignore next 4 */
function unknownOperator(query) {
// @ts-expect-error: `operator` guaranteed.
throw new Error('Unknown operator `' + query.operator + '`')
}
/**
* @param {AstAttribute} query
* @returns {string}
*/
function attributeValue(query) {
const queryValue = query.value
/* c8 ignore next 4 -- never happens with our config */
if (!queryValue) unreachable('Attribute values should be defined')
if (queryValue.type === 'Substitution') {
unreachable('Substitutions are not enabled')
// Space-separated list.
case '~=': {
// type-coverage:ignore-next-line -- some bug with TS.
return (Array.isArray(value) && value.includes(key)) || normal === key
}
// Other values are not yet supported by CSS.
// No default
}
}
return queryValue.value
return false
}

@@ -5,3 +5,3 @@ /**

*/
export function parse(selector: string): AstSelector
export type AstSelector = import('css-selector-parser').AstSelector
export function parse(selector: string): AstSelector;
export type AstSelector = import('css-selector-parser').AstSelector;

@@ -1,25 +0,6 @@

/**
* Check whether an node matches pseudo selectors.
*
* @param {AstRule} query
* @param {Node} node
* @param {number | undefined} index
* @param {Parent | undefined} parent
* @param {SelectState} state
* @returns {boolean}
*/
export function pseudo(
query: AstRule,
node: Node,
index: number | undefined,
parent: Parent | undefined,
state: SelectState
): boolean
export namespace pseudo {
let needsIndex: string[]
}
export type AstRule = import('css-selector-parser').AstRule
export type AstPseudoClass = import('css-selector-parser').AstPseudoClass
export type Node = import('unist').Node
export type Parent = import('unist').Parent
export type SelectState = import('./types.js').SelectState
/** @type {(rule: AstPseudoClass, node: Node, index: number | undefined, parent: Parent | undefined, state: SelectState) => boolean} */
export const pseudo: (rule: AstPseudoClass, node: Node, index: number | undefined, parent: Parent | undefined, state: SelectState) => boolean;
export type AstPseudoClass = import('css-selector-parser').AstPseudoClass;
export type Node = import('unist').Node;
export type Parent = import('unist').Parent;
export type SelectState = import('./types.js').SelectState;
/**
* @typedef {import('css-selector-parser').AstRule} AstRule
* @typedef {import('css-selector-parser').AstPseudoClass} AstPseudoClass

@@ -9,3 +8,3 @@ * @typedef {import('unist').Node} Node

import {unreachable} from 'devlop'
import {ok as assert, unreachable} from 'devlop'
import fauxEsmNthCheck from 'nth-check'

@@ -21,3 +20,3 @@ import {zwitch} from 'zwitch'

/** @type {(rule: AstPseudoClass, node: Node, index: number | undefined, parent: Parent | undefined, state: SelectState) => boolean} */
const handle = zwitch('name', {
export const pseudo = zwitch('name', {
// @ts-expect-error: always known.

@@ -47,42 +46,3 @@ unknown: unknownPseudo,

pseudo.needsIndex = [
'any',
'first-child',
'first-of-type',
'last-child',
'last-of-type',
'is',
'not',
'nth-child',
'nth-last-child',
'nth-of-type',
'nth-last-of-type',
'only-child',
'only-of-type'
]
/**
* Check whether an node matches pseudo selectors.
*
* @param {AstRule} query
* @param {Node} node
* @param {number | undefined} index
* @param {Parent | undefined} parent
* @param {SelectState} state
* @returns {boolean}
*/
export function pseudo(query, node, index, parent, state) {
let offset = -1
if (query.pseudoClasses) {
while (++offset < query.pseudoClasses.length) {
if (!handle(query.pseudoClasses[offset], node, index, parent, state))
return false
}
}
return true
}
/**
* Check whether a node matches an `:empty` pseudo.

@@ -415,6 +375,8 @@ *

const value = query.argument
assert(value, 'expected `argument`')
/* c8 ignore next 3 -- never happens with our config */
if (!value || value.type !== 'Formula') {
unreachable('`:nth` has a formula')
if (value.type !== 'Formula') {
throw new Error(
'Expected `nth` formula, such as `even` or `2n+1` (`of` is not yet supported)'
)
}

@@ -421,0 +383,0 @@

@@ -9,12 +9,6 @@ /**

*/
export function test(
query: AstRule,
node: Node,
index: number | undefined,
parent: Parent | undefined,
state: SelectState
): boolean
export type AstRule = import('css-selector-parser').AstRule
export type Node = import('unist').Node
export type Parent = import('unist').Parent
export type SelectState = import('./types.js').SelectState
export function test(query: AstRule, node: Node, index: number | undefined, parent: Parent | undefined, state: SelectState): boolean;
export type AstRule = import('css-selector-parser').AstRule;
export type Node = import('unist').Node;
export type Parent = import('unist').Parent;
export type SelectState = import('./types.js').SelectState;

@@ -20,16 +20,22 @@ /**

export function test(query, node, index, parent, state) {
if (query.ids) throw new Error('Invalid selector: id')
if (query.classNames) throw new Error('Invalid selector: class')
if (query.pseudoElement) {
throw new Error('Invalid selector: `::' + query.pseudoElement + '`')
for (const item of query.items) {
// eslint-disable-next-line unicorn/prefer-switch
if (item.type === 'Attribute') {
if (!attribute(item, node)) return false
} else if (item.type === 'Id') {
throw new Error('Invalid selector: id')
} else if (item.type === 'ClassName') {
throw new Error('Invalid selector: class')
} else if (item.type === 'PseudoClass') {
if (!pseudo(item, node, index, parent, state)) return false
} else if (item.type === 'PseudoElement') {
throw new Error('Invalid selector: `::' + item.name + '`')
} else if (item.type === 'TagName') {
if (item.name !== node.type) return false
} else {
// Otherwise `item.type` is `WildcardTag`, which matches.
}
}
return Boolean(
node &&
(!query.tag ||
query.tag.type === 'WildcardTag' ||
query.tag.name === node.type) &&
(!query.attributes || attribute(query, node)) &&
(!query.pseudoClasses || pseudo(query, node, index, parent, state))
)
return true
}

@@ -1,3 +0,3 @@

export type AstSelector = import('css-selector-parser').AstSelector
export type Node = import('unist').Node
export type AstSelector = import('css-selector-parser').AstSelector;
export type Node = import('unist').Node;
/**

@@ -7,42 +7,42 @@ * Current state.

export type SelectState = {
/**
* Original root selectors.
*/
rootQuery: AstSelector
/**
* Matches.
*/
results: Array<Node>
/**
* Nodes in scope.
*/
scopeNodes: Array<Node>
/**
* Whether we can stop looking after we found one node.
*/
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
}
/**
* Original root selectors.
*/
rootQuery: AstSelector;
/**
* Matches.
*/
results: Array<Node>;
/**
* Nodes in scope.
*/
scopeNodes: Array<Node>;
/**
* Whether we can stop looking after we found one node.
*/
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;
};

@@ -12,5 +12,3 @@ /**

*/
export function indexable(
value: unknown
): asserts value is Record<string, unknown>
export function indexable(value: unknown): asserts value is Record<string, unknown>;
/**

@@ -20,4 +18,4 @@ * @param {Node} node

*/
export function parent(node: Node): node is import('unist').Parent
export type Node = import('unist').Node
export type Parent = import('unist').Parent
export function parent(node: Node): node is import("unist").Parent;
export type Node = import('unist').Node;
export type Parent = import('unist').Parent;

@@ -7,7 +7,7 @@ /**

*/
export function walk(state: SelectState, tree: Node | undefined): void
export type AstRule = import('css-selector-parser').AstRule
export type Node = import('unist').Node
export type Parent = import('unist').Parent
export type SelectState = import('./types.js').SelectState
export function walk(state: SelectState, tree: Node | undefined): void;
export type AstRule = import('css-selector-parser').AstRule;
export type Node = import('unist').Node;
export type Parent = import('unist').Parent;
export type SelectState = import('./types.js').SelectState;
/**

@@ -17,19 +17,19 @@ * Rule sets by nesting.

export type Nest = {
/**
* `a b`
*/
descendant: Array<AstRule> | undefined
/**
* `a > b`
*/
directChild: Array<AstRule> | undefined
/**
* `a + b`
*/
adjacentSibling: Array<AstRule> | undefined
/**
* `a ~ b`
*/
generalSibling: Array<AstRule> | undefined
}
/**
* `a b`
*/
descendant: Array<AstRule> | undefined;
/**
* `a > b`
*/
directChild: Array<AstRule> | undefined;
/**
* `a + b`
*/
adjacentSibling: Array<AstRule> | undefined;
/**
* `a ~ b`
*/
generalSibling: Array<AstRule> | undefined;
};
/**

@@ -39,10 +39,10 @@ * Info on nodes in a parent.

export type Counts = {
/**
* Number of nodes.
*/
count: number
/**
* Number of nodes by type.
*/
types: Map<string, number>
}
/**
* Number of nodes.
*/
count: number;
/**
* Number of nodes by type.
*/
types: Map<string, number>;
};
{
"name": "unist-util-select",
"version": "5.0.0",
"version": "5.1.0",
"description": "unist utility to select nodes with CSS-like selectors",

@@ -51,3 +51,3 @@ "license": "MIT",

"@types/unist": "^3.0.0",
"css-selector-parser": "^2.0.0",
"css-selector-parser": "^3.0.0",
"devlop": "^1.1.0",

@@ -60,3 +60,3 @@ "nth-check": "^2.0.0",

"c8": "^8.0.0",
"prettier": "^2.0.0",
"prettier": "^3.0.0",
"remark-cli": "^11.0.0",

@@ -66,4 +66,4 @@ "remark-preset-wooorm": "^9.0.0",

"typescript": "^5.0.0",
"unist-builder": "^3.0.0",
"xo": "^0.54.0"
"unist-builder": "^4.0.0",
"xo": "^0.56.0"
},

@@ -73,3 +73,3 @@ "scripts": {

"build": "tsc --build --clean && tsc --build && type-coverage",
"format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
"format": "remark . -qfo && prettier . -w --log-level warn && xo --fix",
"test-api": "node --conditions development test/index.js",

@@ -76,0 +76,0 @@ "test-coverage": "c8 --100 --reporter lcov npm run test-api",

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