Skip to content

Commit

Permalink
Add JSDoc based types
Browse files Browse the repository at this point in the history
  • Loading branch information
wooorm committed Apr 20, 2021
1 parent 4b4e243 commit 4c1b02e
Show file tree
Hide file tree
Showing 22 changed files with 654 additions and 209 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.DS_Store
*.d.ts
*.log
coverage/
node_modules/
Expand Down
19 changes: 19 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,33 @@
/**
* @typedef {import('unist').Node} Node
*/

import {any} from './lib/any.js'
import {parse} from './lib/parse.js'

/**
* @param {string} selector
* @param {Node} [node]
* @returns {boolean}
*/
export function matches(selector, node) {
return Boolean(any(parse(selector), node, {one: true, shallow: true, any})[0])
}

/**
* @param {string} selector
* @param {Node} [node]
* @returns {Node|null}
*/
export function select(selector, node) {
return any(parse(selector), node, {one: true, any})[0] || null
}

/**
* @param {string} selector
* @param {Node} [node]
* @returns {Array.<Node>}
*/
export function selectAll(selector, node) {
return any(parse(selector), node, {any})
}
68 changes: 63 additions & 5 deletions lib/any.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,43 @@
/**
* @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
*/

import {zwitch} from 'zwitch'
import {nest} from './nest.js'
import {pseudo} from './pseudo.js'
import {test} from './test.js'
import {nest} from './nest.js'
import {root} from './util.js'

var type = zwitch('type', {
unknown: unknownType,
invalid: invalidType,
handlers: {selectors, ruleSet, rule}
})

/**
* @param {Selectors|RuleSet|Rule} query
* @param {Node} node
* @param {SelectState} state
*/
export function any(query, node, state) {
// @ts-ignore zwitch types are off.
return query && node ? type(query, node, state) : []
}

/**
* @param {Selectors} query
* @param {Node} node
* @param {SelectState} state
*/
function selectors(query, node, state) {
var collect = collector(state.one)
var index = -1
Expand All @@ -24,10 +49,20 @@ function selectors(query, node, state) {
return collect.result
}

/**
* @param {RuleSet} query
* @param {Node} node
* @param {SelectState} state
*/
function ruleSet(query, node, state) {
return rule(query.rule, node, state)
}

/**
* @param {Rule} query
* @param {Node} tree
* @param {SelectState} state
*/
function rule(query, tree, state) {
var collect = collector(state.one)

Expand All @@ -41,7 +76,8 @@ function rule(query, tree, state) {
0,
null,
configure(query, {
scopeNodes: tree.type === 'root' ? tree.children : [tree],
scopeNodes: root(tree) ? tree.children : [tree],
index: false,
iterator,
one: state.one,
shallow: state.shallow,
Expand All @@ -51,9 +87,10 @@ function rule(query, tree, state) {

return collect.result

/** @type {SelectIterator} */
function iterator(query, node, index, parent, state) {
if (test(query, node, index, parent, state)) {
if (query.rule) {
if ('rule' in query) {
nest(query.rule, node, index, parent, configure(query.rule, state))
} else {
collect(node)
Expand All @@ -63,6 +100,12 @@ function rule(query, tree, state) {
}
}

/**
* @template {SelectState} S
* @param {Rule} query
* @param {S} state
* @returns {S}
*/
function configure(query, state) {
var pseudos = query.pseudos || []
var index = -1
Expand All @@ -78,7 +121,10 @@ function configure(query, state) {
}

// Shouldn’t be invoked, all data is handled.
/* c8 ignore next 3 */
/* c8 ignore next 6 */
/**
* @param {{[x: string]: unknown, type: string}} query
*/
function unknownType(query) {
throw new Error('Unknown type `' + query.type + '`')
}
Expand All @@ -89,15 +135,24 @@ function invalidType() {
throw new Error('Invalid type')
}

/**
* @param {boolean} one
*/
function collector(one) {
/** @type {Array.<Node>} */
var result = []
/** @type {boolean} */
var found

collect.result = result

return collect

/* Append nodes to array, filtering out duplicates. */
/**
* Append nodes to array, filtering out duplicates.
*
* @param {Node|Array.<Node>} node
*/
function collect(node) {
var index = -1

Expand All @@ -110,6 +165,9 @@ function collector(one) {
}
}

/**
* @param {Node} node
*/
function collectOne(node) {
if (one) {
/* Shouldn’t happen, safeguards performance problems. */
Expand Down
71 changes: 56 additions & 15 deletions lib/attribute.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
/**
* @typedef {import('./types.js').Rule} Rule
* @typedef {import('./types.js').RuleAttr} RuleAttr
* @typedef {import('./types.js').Node} Node
*/

import {zwitch} from 'zwitch'

var handle = zwitch('operator', {
Expand All @@ -12,47 +18,69 @@ var handle = zwitch('operator', {
}
})

/**
* @param {Rule} query
* @param {Node} node
*/
export function attribute(query, node) {
var attrs = query.attrs
var index = -1

while (++index < attrs.length) {
if (!handle(attrs[index], node)) return false
while (++index < query.attrs.length) {
if (!handle(query.attrs[index], node)) return false
}

return true
}

// [attr]
/**
* `[attr]`
*
* @param {RuleAttr} query
* @param {Node} node
*/
function exists(query, node) {
return node[query.name] !== null && node[query.name] !== undefined
}

// [attr=value]
/**
* `[attr=value]`
*
* @param {RuleAttr} query
* @param {Node} node
*/
function exact(query, node) {
return exists(query, node) && String(node[query.name]) === query.value
}

// [attr~=value]
/**
* `[attr~=value]`
*
* @param {RuleAttr} query
* @param {Node} node
*/
function containsArray(query, node) {
var value = node[query.name]

if (value === null || value === undefined) return false

// If this is an array, and the query is contained in it, return true.
if (
typeof value === 'object' &&
'length' in value &&
value.includes(query.value)
) {
// 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(query.value)) {
return true
}

// For all other values, return whether this is an exact match.
return String(value) === query.value
}

// [attr^=value]
/**
* `[attr^=value]`
*
* @param {RuleAttr} query
* @param {Node} node
*/
function begins(query, node) {
var value = node[query.name]

Expand All @@ -62,7 +90,12 @@ function begins(query, node) {
)
}

// [attr$=value]
/**
* `[attr$=value]`
*
* @param {RuleAttr} query
* @param {Node} node
*/
function ends(query, node) {
var value = node[query.name]

Expand All @@ -72,14 +105,22 @@ function ends(query, node) {
)
}

// [attr*=value]
/**
* `[attr*=value]`
*
* @param {RuleAttr} query
* @param {Node} node
*/
function containsString(query, node) {
var value = node[query.name]
return typeof value === 'string' && value.includes(query.value)
}

// Shouldn’t be invoked, Parser throws an error instead.
/* c8 ignore next 3 */
/* c8 ignore next 6 */
/**
* @param {{[x: string]: unknown, type: string}} query
*/
function unknownOperator(query) {
throw new Error('Unknown operator `' + query.operator + '`')
}
9 changes: 9 additions & 0 deletions lib/name.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
/**
* @typedef {import('./types.js').Rule} Rule
* @typedef {import('./types.js').Node} Node
*/

/**
* @param {Rule} query
* @param {Node} node
*/
export function name(query, node) {
return query.tagName === '*' || query.tagName === node.type
}
Loading

0 comments on commit 4c1b02e

Please sign in to comment.