unist-util-select
Advanced tools
Comparing version 5.0.0 to 5.1.0
@@ -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", |
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
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
45436
1085
+ Addedcss-selector-parser@3.0.5(transitive)
- Removedcss-selector-parser@2.3.2(transitive)
Updatedcss-selector-parser@^3.0.0