Skip to content

Commit 79cf28e

Browse files
committed
Refactor caching of nth checks
1 parent dc787fb commit 79cf28e

File tree

3 files changed

+46
-83
lines changed

3 files changed

+46
-83
lines changed

lib/parse.js

Lines changed: 8 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,33 @@
11
/**
2-
* @typedef {import('./types.js').Selector} Selector
32
* @typedef {import('./types.js').Selectors} Selectors
4-
* @typedef {import('./types.js').RuleSet} RuleSet
5-
* @typedef {import('./types.js').Rule} Rule
6-
* @typedef {import('./types.js').RulePseudo} RulePseudo
7-
* @typedef {import('./types.js').RulePseudoNth} RulePseudoNth
83
*/
94

105
import {CssSelectorParser} from 'css-selector-parser'
11-
import fauxEsmNthCheck from 'nth-check'
12-
import {zwitch} from 'zwitch'
13-
14-
/** @type {import('nth-check').default} */
15-
// @ts-expect-error
16-
const nthCheck = fauxEsmNthCheck.default || fauxEsmNthCheck
17-
18-
const nth = new Set([
19-
'nth-child',
20-
'nth-last-child',
21-
'nth-of-type',
22-
'nth-last-of-type'
23-
])
246

257
const parser = new CssSelectorParser()
268

279
parser.registerAttrEqualityMods('~', '^', '$', '*')
2810
parser.registerSelectorPseudos('any', 'matches', 'not', 'has')
2911
parser.registerNestingOperators('>', '+', '~')
3012

31-
/** @type {(query: Selectors | RuleSet | Rule | undefined) => void} */
32-
const compile = zwitch('type', {handlers: {selectors, ruleSet, rule}})
33-
3413
/**
3514
* @param {string} selector
36-
* @returns {Selector}
15+
* @returns {Selectors}
3716
*/
3817
export function parse(selector) {
3918
if (typeof selector !== 'string') {
4019
throw new TypeError('Expected `string` as selector, not `' + selector + '`')
4120
}
4221

43-
const parsed = parser.parse(selector)
44-
compile(parsed)
45-
return parsed
46-
}
47-
48-
/**
49-
* @param {Selectors} query
50-
* @returns {void}
51-
*/
52-
function selectors(query) {
53-
let index = -1
54-
55-
while (++index < query.selectors.length) {
56-
compile(query.selectors[index])
22+
const query = parser.parse(selector)
23+
// Empty selectors object doesn’t match anything.
24+
if (!query) {
25+
return {type: 'selectors', selectors: []}
5726
}
58-
}
59-
60-
/**
61-
* @param {RuleSet} query
62-
* @returns {void}
63-
*/
64-
function ruleSet(query) {
65-
rule(query.rule)
66-
}
67-
68-
/**
69-
* @param {Rule} query
70-
* @returns {void}
71-
*/
72-
function rule(query) {
73-
const pseudos = query.pseudos || []
74-
let index = -1
75-
76-
while (++index < pseudos.length) {
77-
const pseudo = pseudos[index]
7827

79-
if (nth.has(pseudo.name)) {
80-
// @ts-expect-error Patch a non-primitive type.
81-
pseudo.value = nthCheck(pseudo.value)
82-
// @ts-expect-error Patch a non-primitive type.
83-
pseudo.valueType = 'function'
84-
}
28+
if (query.type === 'selectors') {
29+
return query
8530
}
8631

87-
compile(query.rule)
32+
return {type: 'selectors', selectors: [query]}
8833
}

lib/pseudo.js

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
/**
22
* @typedef {import('./types.js').Rule} Rule
33
* @typedef {import('./types.js').RulePseudo} RulePseudo
4-
* @typedef {import('./types.js').RulePseudoNth} RulePseudoNth
54
* @typedef {import('./types.js').RulePseudoSelector} RulePseudoSelector
65
* @typedef {import('./types.js').Parent} Parent
76
* @typedef {import('./types.js').Selector} Selector
@@ -10,10 +9,15 @@
109
* @typedef {import('./types.js').Node} Node
1110
*/
1211

13-
import {zwitch} from 'zwitch'
12+
import fauxEsmNthCheck from 'nth-check'
1413
import {convert} from 'unist-util-is'
14+
import {zwitch} from 'zwitch'
1515
import {parent} from './util.js'
1616

17+
/** @type {import('nth-check').default} */
18+
// @ts-expect-error
19+
const nthCheck = fauxEsmNthCheck.default || fauxEsmNthCheck
20+
1721
const is = convert()
1822

1923
/** @type {(rule: Rule | RulePseudo, element: Node, index: number | undefined, parent: Parent | undefined, state: SelectState) => boolean} */
@@ -191,62 +195,66 @@ function onlyChild(query, _1, _2, _3, state) {
191195
}
192196

193197
/**
194-
* @param {RulePseudoNth} query
198+
* @param {RulePseudo} query
195199
* @param {Node} _1
196200
* @param {number | undefined} _2
197201
* @param {Parent | undefined} _3
198202
* @param {SelectState} state
199203
* @returns {boolean}
200204
*/
201205
function nthChild(query, _1, _2, _3, state) {
206+
const fn = getCachedNthCheck(query)
202207
assertDeep(state, query)
203-
return typeof state.nodeIndex === 'number' && query.value(state.nodeIndex)
208+
return typeof state.nodeIndex === 'number' && fn(state.nodeIndex)
204209
}
205210

206211
/**
207-
* @param {RulePseudoNth} query
212+
* @param {RulePseudo} query
208213
* @param {Node} _1
209214
* @param {number | undefined} _2
210215
* @param {Parent | undefined} _3
211216
* @param {SelectState} state
212217
* @returns {boolean}
213218
*/
214219
function nthLastChild(query, _1, _2, _3, state) {
220+
const fn = getCachedNthCheck(query)
215221
assertDeep(state, query)
216222
return (
217223
typeof state.nodeCount === 'number' &&
218224
typeof state.nodeIndex === 'number' &&
219-
query.value(state.nodeCount - state.nodeIndex - 1)
225+
fn(state.nodeCount - state.nodeIndex - 1)
220226
)
221227
}
222228

223229
/**
224-
* @param {RulePseudoNth} query
230+
* @param {RulePseudo} query
225231
* @param {Node} _1
226232
* @param {number | undefined} _2
227233
* @param {Parent | undefined} _3
228234
* @param {SelectState} state
229235
* @returns {boolean}
230236
*/
231237
function nthOfType(query, _1, _2, _3, state) {
238+
const fn = getCachedNthCheck(query)
232239
assertDeep(state, query)
233-
return typeof state.typeIndex === 'number' && query.value(state.typeIndex)
240+
return typeof state.typeIndex === 'number' && fn(state.typeIndex)
234241
}
235242

236243
/**
237-
* @param {RulePseudoNth} query
244+
* @param {RulePseudo} query
238245
* @param {Node} _1
239246
* @param {number | undefined} _2
240247
* @param {Parent | undefined} _3
241248
* @param {SelectState} state
242249
* @returns {boolean}
243250
*/
244251
function nthLastOfType(query, _1, _2, _3, state) {
252+
const fn = getCachedNthCheck(query)
245253
assertDeep(state, query)
246254
return (
247255
typeof state.typeIndex === 'number' &&
248256
typeof state.typeCount === 'number' &&
249-
query.value(state.typeCount - 1 - state.typeIndex)
257+
fn(state.typeCount - 1 - state.typeIndex)
250258
)
251259
}
252260

@@ -314,7 +322,7 @@ function unknownPseudo(query) {
314322

315323
/**
316324
* @param {SelectState} state
317-
* @param {RulePseudo | RulePseudoNth} query
325+
* @param {RulePseudo} query
318326
*/
319327
function assertDeep(state, query) {
320328
if (state.shallow) {
@@ -387,3 +395,22 @@ function appendScope(value) {
387395

388396
return selector
389397
}
398+
399+
/**
400+
* @param {RulePseudo} query
401+
* @returns {(value: number) => boolean}
402+
*/
403+
function getCachedNthCheck(query) {
404+
/** @type {(value: number) => boolean} */
405+
// @ts-expect-error: cache.
406+
let fn = query._cachedFn
407+
408+
if (!fn) {
409+
// @ts-expect-error: always string.
410+
fn = nthCheck(query.value)
411+
// @ts-expect-error: cache.
412+
query._cachedFn = fn
413+
}
414+
415+
return fn
416+
}

lib/types.js

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,6 @@
3737
* @property {Selectors | RuleSet} value
3838
* Selector.
3939
*
40-
* @typedef RulePseudoNth
41-
* Overwrite to compile nth-checks once.
42-
* @property {string} name
43-
* Name of pseudo, such as `'nth-child'`.
44-
* @property {'function'} valueType
45-
* Set to `'function'`, because `value` is a compiled check.
46-
* @property {(index: number) => boolean} value
47-
* Compiled function from `nth-check`.
48-
*
4940
* @typedef SelectState
5041
* Current state.
5142
* @property {(query: Selectors | RuleSet | Rule, node: Node | undefined, state: SelectState) => Array<Node>} any

0 commit comments

Comments
 (0)