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 29, 2021
1 parent 465234f commit 6b0fdb8
Show file tree
Hide file tree
Showing 19 changed files with 864 additions and 163 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
24 changes: 24 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,40 @@
/**
* @typedef {import('./lib/types.js').Element} Element
* @typedef {import('./lib/types.js').HastNode} HastNode
* @typedef {import('./lib/types.js').Space} Space
*/

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

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

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

/**
* @param {string} selector
* @param {HastNode} [node]
* @param {Space} [space]
* @returns {Array.<Element>}
*/
export function selectAll(selector, node, space) {
return any(parse(selector), node, {space})
}
119 changes: 88 additions & 31 deletions lib/any.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
/**
* @typedef {import('hast').Element} Element
* @typedef {import('./types.js').Selectors} Selectors
* @typedef {import('./types.js').Rule} Rule
* @typedef {import('./types.js').RuleSet} RuleSet
* @typedef {import('./types.js').HastNode} HastNode
* @typedef {import('./types.js').SelectIterator} SelectIterator
* @typedef {import('./types.js').SelectState} SelectState
*/

import {html, svg} from 'property-information'
import {zwitch} from 'zwitch'
import {enterState} from './enter-state.js'
Expand All @@ -11,27 +21,52 @@ var type = zwitch('type', {
handlers: {selectors, ruleSet, rule}
})

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

/**
* @param {Selectors} query
* @param {HastNode} node
* @param {SelectState} state
* @returns {Array.<Element>}
*/
function selectors(query, node, state) {
var collect = collector(state.one)
var collector = new Collector(state.one)
var index = -1

while (++index < query.selectors.length) {
collect(ruleSet(query.selectors[index], node, state))
collector.collectAll(ruleSet(query.selectors[index], node, state))
}

return collect.result
return collector.result
}

/**
* @param {RuleSet} query
* @param {HastNode} node
* @param {SelectState} state
* @returns {Array.<Element>}
*/
function ruleSet(query, node, state) {
return rule(query.rule, node, state)
}

/**
* @param {Rule} query
* @param {HastNode} tree
* @param {SelectState} state
* @returns {Array.<Element>}
*/
function rule(query, tree, state) {
var collect = collector(state.one)
var collector = new Collector(state.one)

if (state.shallow && query.rule) {
throw new Error('Expected selector without nesting')
Expand All @@ -47,30 +82,39 @@ function rule(query, tree, state) {
language: null,
direction: 'ltr',
editableOrEditingHost: false,
// @ts-ignore assume elements.
scopeElements: tree.type === 'root' ? tree.children : [tree],
iterator,
one: state.one,
shallow: state.shallow
})
)

return collect.result
return collector.result

/** @type {SelectIterator} */
function iterator(query, node, index, parent, state) {
var exit = enterState(state, node)

if (test(query, node, index, parent, state)) {
if (query.rule) {
nest(query.rule, node, index, parent, configure(query.rule, state))
} else {
collect(node)
// @ts-ignore `test` also asserts `node is Element`
collector.collect(node)
state.found = true
}
}

exit()
}

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

// Shouldn’t be called, 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 @@ -98,35 +145,45 @@ function invalidType() {
throw new Error('Invalid type')
}

function collector(one) {
var result = []
var found

collect.result = result

return collect
class Collector {
/**
* @param {boolean} one
*/
constructor(one) {
/** @type {Array.<Element>} */
this.result = []
/** @type {boolean} */
this.one = one
/** @type {boolean} */
this.found = false
}

// Append elements to array, filtering out duplicates.
function collect(source) {
/**
* Append nodes to array, filtering out duplicates.
*
* @param {Array.<Element>} elements
*/
collectAll(elements) {
var index = -1

if ('length' in source) {
while (++index < source.length) {
collectOne(source[index])
}
} else {
collectOne(source)
while (++index < elements.length) {
this.collect(elements[index])
}
}

function collectOne(element) {
if (one) {
// Shouldn’t happen, safeguards performance problems.
/* c8 ignore next */
if (found) throw new Error('Cannot collect multiple nodes')
found = true
}

if (!result.includes(element)) result.push(element)
/**
* Append one node.
*
* @param {Element} element
*/
collect(element) {
if (this.one) {
// Shouldn’t happen, safeguards performance problems.
/* c8 ignore next */
if (this.found) throw new Error('Cannot collect multiple nodes')
this.found = true
}

if (!this.result.includes(element)) this.result.push(element)
}
}
Loading

0 comments on commit 6b0fdb8

Please sign in to comment.