UNPKG

4.02 kBJavaScriptView Raw
1/**
2 * @typedef {import('./types.js').Selector} Selector
3 * @typedef {import('./types.js').Selectors} Selectors
4 * @typedef {import('./types.js').Rule} Rule
5 * @typedef {import('./types.js').RuleSet} RuleSet
6 * @typedef {import('./types.js').RulePseudo} RulePseudo
7 * @typedef {import('./types.js').Query} Query
8 * @typedef {import('./types.js').Node} Node
9 * @typedef {import('./types.js').Parent} Parent
10 * @typedef {import('./types.js').SelectIterator} SelectIterator
11 * @typedef {import('./types.js').SelectState} SelectState
12 */
13
14import {zwitch} from 'zwitch'
15import {nest} from './nest.js'
16import {pseudo} from './pseudo.js'
17import {test} from './test.js'
18import {root} from './util.js'
19
20const type = zwitch('type', {
21 // @ts-expect-error: hush.
22 unknown: unknownType,
23 invalid: invalidType,
24 // @ts-expect-error: hush.
25 handlers: {selectors, ruleSet, rule}
26})
27
28/**
29 * @param {Selectors|RuleSet|Rule} query
30 * @param {Node|undefined} node
31 * @param {SelectState} state
32 * @returns {Array.<Node>}
33 */
34export function any(query, node, state) {
35 // @ts-expect-error: fine.
36 return query && node ? type(query, node, state) : []
37}
38
39/**
40 * @param {Selectors} query
41 * @param {Node} node
42 * @param {SelectState} state
43 */
44function selectors(query, node, state) {
45 const collect = collector(state.one)
46 let index = -1
47
48 while (++index < query.selectors.length) {
49 collect(ruleSet(query.selectors[index], node, state))
50 }
51
52 return collect.result
53}
54
55/**
56 * @param {RuleSet} query
57 * @param {Node} node
58 * @param {SelectState} state
59 */
60function ruleSet(query, node, state) {
61 return rule(query.rule, node, state)
62}
63
64/**
65 * @param {Rule} query
66 * @param {Node} tree
67 * @param {SelectState} state
68 */
69function rule(query, tree, state) {
70 const collect = collector(state.one)
71
72 if (state.shallow && query.rule) {
73 throw new Error('Expected selector without nesting')
74 }
75
76 nest(
77 query,
78 tree,
79 0,
80 null,
81 configure(query, {
82 scopeNodes: root(tree) ? tree.children : [tree],
83 index: false,
84 iterator,
85 one: state.one,
86 shallow: state.shallow,
87 any: state.any
88 })
89 )
90
91 return collect.result
92
93 /** @type {SelectIterator} */
94 function iterator(query, node, index, parent, state) {
95 if (test(query, node, index, parent, state)) {
96 if (query.rule) {
97 nest(query.rule, node, index, parent, configure(query.rule, state))
98 } else {
99 collect(node)
100 state.found = true
101 }
102 }
103 }
104}
105
106/**
107 * @template {SelectState} S
108 * @param {Rule} query
109 * @param {S} state
110 * @returns {S}
111 */
112function configure(query, state) {
113 const pseudos = query.pseudos || []
114 let index = -1
115
116 while (++index < pseudos.length) {
117 if (pseudo.needsIndex.includes(pseudos[index].name)) {
118 state.index = true
119 break
120 }
121 }
122
123 return state
124}
125
126// Shouldn’t be invoked, all data is handled.
127/* c8 ignore next 6 */
128/**
129 * @param {{[x: string]: unknown, type: string}} query
130 */
131function unknownType(query) {
132 throw new Error('Unknown type `' + query.type + '`')
133}
134
135// Shouldn’t be invoked, parser gives correct data.
136/* c8 ignore next 3 */
137function invalidType() {
138 throw new Error('Invalid type')
139}
140
141/**
142 * @param {boolean|undefined} one
143 */
144function collector(one) {
145 /** @type {Array.<Node>} */
146 const result = []
147 /** @type {boolean} */
148 let found
149
150 collect.result = result
151
152 return collect
153
154 /**
155 * Append nodes to array, filtering out duplicates.
156 *
157 * @param {Node|Array.<Node>} node
158 */
159 function collect(node) {
160 let index = -1
161
162 if ('length' in node) {
163 while (++index < node.length) {
164 collectOne(node[index])
165 }
166 } else {
167 collectOne(node)
168 }
169 }
170
171 /**
172 * @param {Node} node
173 */
174 function collectOne(node) {
175 if (one) {
176 /* Shouldn’t happen, safeguards performance problems. */
177 /* c8 ignore next */
178 if (found) throw new Error('Cannot collect multiple nodes')
179
180 found = true
181 }
182
183 if (!result.includes(node)) result.push(node)
184 }
185}