Skip to content

Commit

Permalink
Refactor internal types
Browse files Browse the repository at this point in the history
  • Loading branch information
wooorm committed Dec 31, 2022
1 parent 5e05a61 commit dc787fb
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 127 deletions.
159 changes: 109 additions & 50 deletions lib/any.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,59 +11,84 @@ import {zwitch} from 'zwitch'
import {nest} from './nest.js'
import {pseudo} from './pseudo.js'
import {test} from './test.js'
import {root} from './util.js'

/** @type {(query: Selectors | RuleSet | Rule, node: Node, state: SelectState) => Array<Node>} */
const type = zwitch('type', {
// @ts-expect-error: hush.
unknown: unknownType,
invalid: invalidType,
// @ts-expect-error: hush.
handlers: {selectors, ruleSet, rule}
})

/**
* @param {Selectors | RuleSet | Rule} query
* Handle an optional query and node.
*
* @param {Selectors | RuleSet | Rule | undefined} query
* Thing to find.
* @param {Node | undefined} node
* Tree.
* @param {SelectState} state
* State.
* @returns {Array<Node>}
* Results.
*/
export function any(query, node, state) {
// @ts-expect-error: fine.
return query && node ? type(query, node, state) : []
}

/**
* Handle selectors.
*
* @param {Selectors} query
* Multiple selectors.
* @param {Node} node
* Tree.
* @param {SelectState} state
* State.
* @returns {Array<Node>}
* Results.
*/
function selectors(query, node, state) {
const collect = collector(state.one)
const collector = new Collector(state.one)
let index = -1

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

return collect.result
return collector.result
}

/**
* Handle a selector.
*
* @param {RuleSet} query
* One selector.
* @param {Node} node
* Tree.
* @param {SelectState} state
* State.
* @returns {Array<Node>}
* Results.
*/
function ruleSet(query, node, state) {
return rule(query.rule, node, state)
}

/**
* Handle a rule.
*
* @param {Rule} query
* One rule.
* @param {Node} tree
* Tree.
* @param {SelectState} state
* State.
* @returns {Array<Node>}
* Results.
*/
function rule(query, tree, state) {
const collect = collector(state.one)
const collector = new Collector(state.one)

if (state.shallow && query.rule) {
throw new Error('Expected selector without nesting')
Expand All @@ -75,35 +100,50 @@ function rule(query, tree, state) {
0,
undefined,
configure(query, {
any: state.any,
...state,
iterator,
scopeNodes: root(tree) ? tree.children : [tree],
one: state.one,
shallow: state.shallow,
index: false,
found: false,
typeIndex: state.typeIndex,
nodeIndex: state.nodeIndex,
typeCount: state.typeCount,
nodeCount: state.nodeCount
index: needsIndex(query)
})
)

return collect.result
return collector.result

/** @type {SelectIterator} */
function iterator(query, node, index, parent, state) {
if (test(query, node, index, parent, state)) {
if (query.rule) {
nest(query.rule, node, index, parent, configure(query.rule, state))
nest(query.rule, node, index, parent, {
...state,
iterator,
index: needsIndex(query.rule)
})
} else {
collect(node)
collector.collect(node)
state.found = true
}
}
}
}

/**
* Check if indexing is needed.
*
* @param {Rule} query
* @returns {boolean}
*/
function needsIndex(query) {
const pseudos = query.pseudos || []
let index = -1

while (++index < pseudos.length) {
if (pseudo.needsIndex.includes(pseudos[index].name)) {
return true
}
}

return false
}

/**
* @template {SelectState} S
* @param {Rule} query
Expand All @@ -125,62 +165,81 @@ function configure(query, state) {
}

// Shouldn’t be called, all data is handled.
/* c8 ignore next 6 */
/**
* @param {{[x: string]: unknown, type: string}} query
* @param {unknown} query
* @returns {never}
*/
/* c8 ignore next 4 */
function unknownType(query) {
// @ts-expect-error: `type` guaranteed.
throw new Error('Unknown type `' + query.type + '`')
}

// Shouldn’t be called, parser gives correct data.
/**
* @returns {never}
*/
/* c8 ignore next 3 */
function invalidType() {
throw new Error('Invalid type')
}

/**
* @param {boolean | undefined} one
* Collect nodes.
*/
function collector(one) {
/** @type {Array<Node>} */
const result = []
/** @type {boolean} */
let found

collect.result = result

return collect
class Collector {
/**
* @param {boolean | undefined} one
*/
constructor(one) {
/**
* Found nodes.
*
* @type {Array<Node>}
*/
this.result = []

/**
* Whether we’re looking for one result.
*
* @type {boolean}
*/
this.one = one || false

/**
* Whether we’ve found something.
*
* @type {boolean}
*/
this.found = false
}

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

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

/**
* Add one node.
*
* @param {Node} node
*/
function collectOne(node) {
if (one) {
/* Shouldn’t happen, safeguards performance problems. */
collect(node) {
if (this.one) {
// Shouldn’t happen, safeguards performance problems.
/* c8 ignore next */
if (found) throw new Error('Cannot collect multiple nodes')

found = true
if (this.found) return
this.found = true
}

if (!result.includes(node)) result.push(node)
if (!this.result.includes(node)) this.result.push(node)
}
}
36 changes: 21 additions & 15 deletions lib/attribute.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,24 @@

import {zwitch} from 'zwitch'

/** @type {(query: RuleAttr, node: Node) => boolean} */
const handle = zwitch('operator', {
// @ts-expect-error: hush.
unknown: unknownOperator,
// @ts-expect-error: hush.
invalid: exists,
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
}
})

/**
* @param {Rule} query
* @param {Node} node
* @returns {boolean}
*/
export function attribute(query, node) {
let index = -1
Expand All @@ -44,6 +40,7 @@ export function attribute(query, node) {
*
* @param {RuleAttr} query
* @param {Node} node
* @returns {boolean}
*/
function exists(query, node) {
// @ts-expect-error: Looks like a record.
Expand All @@ -55,6 +52,7 @@ function exists(query, node) {
*
* @param {RuleAttr} query
* @param {Node} node
* @returns {boolean}
*/
function exact(query, node) {
// @ts-expect-error: Looks like a record.
Expand All @@ -66,6 +64,7 @@ function exact(query, node) {
*
* @param {RuleAttr} query
* @param {Node} node
* @returns {boolean}
*/
function containsArray(query, node) {
/** @type {unknown} */
Expand All @@ -91,16 +90,17 @@ function containsArray(query, node) {
*
* @param {RuleAttr} query
* @param {Node} node
* @returns {boolean}
*/
function begins(query, node) {
/** @type {unknown} */
// @ts-expect-error: Looks like a record.
const value = node[query.name]

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
)
}

Expand All @@ -109,16 +109,17 @@ function begins(query, node) {
*
* @param {RuleAttr} query
* @param {Node} node
* @returns {boolean}
*/
function ends(query, node) {
/** @type {unknown} */
// @ts-expect-error: Looks like a record.
const value = node[query.name]

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
)
}

Expand All @@ -127,19 +128,24 @@ function ends(query, node) {
*
* @param {RuleAttr} query
* @param {Node} node
* @returns {boolean}
*/
function containsString(query, node) {
/** @type {unknown} */
// @ts-expect-error: Looks like a record.
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 called, parser throws an error instead.
/* c8 ignore next 6 */
/**
* @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 + '`')
}
Loading

0 comments on commit dc787fb

Please sign in to comment.