UNPKG

3.2 kBJavaScriptView Raw
1/**
2 * @typedef {import('./types.js').Rule} Rule
3 * @typedef {import('./types.js').RuleAttr} RuleAttr
4 * @typedef {import('./types.js').Node} Node
5 */
6
7import {zwitch} from 'zwitch'
8
9const handle = zwitch('operator', {
10 // @ts-expect-error: hush.
11 unknown: unknownOperator,
12 // @ts-expect-error: hush.
13 invalid: exists,
14 handlers: {
15 // @ts-expect-error: hush.
16 '=': exact,
17 // @ts-expect-error: hush.
18 '^=': begins,
19 // @ts-expect-error: hush.
20 '$=': ends,
21 // @ts-expect-error: hush.
22 '*=': containsString,
23 // @ts-expect-error: hush.
24 '~=': containsArray
25 }
26})
27
28/**
29 * @param {Rule} query
30 * @param {Node} node
31 */
32export function attribute(query, node) {
33 let index = -1
34
35 while (++index < query.attrs.length) {
36 if (!handle(query.attrs[index], node)) return false
37 }
38
39 return true
40}
41
42/**
43 * `[attr]`
44 *
45 * @param {RuleAttr} query
46 * @param {Node} node
47 */
48function exists(query, node) {
49 // @ts-expect-error: Looks like a record.
50 return node[query.name] !== null && node[query.name] !== undefined
51}
52
53/**
54 * `[attr=value]`
55 *
56 * @param {RuleAttr} query
57 * @param {Node} node
58 */
59function exact(query, node) {
60 // @ts-expect-error: Looks like a record.
61 return exists(query, node) && String(node[query.name]) === query.value
62}
63
64/**
65 * `[attr~=value]`
66 *
67 * @param {RuleAttr} query
68 * @param {Node} node
69 */
70function containsArray(query, node) {
71 /** @type {unknown} */
72 // @ts-expect-error: Looks like a record.
73 const value = node[query.name]
74
75 if (value === null || value === undefined) return false
76
77 // If this is an array, and the query is contained in it, return true.
78 // Coverage comment in place because TS turns `Array.isArray(unknown)`
79 // into `Array.<any>` instead of `Array.<unknown>`.
80 // type-coverage:ignore-next-line
81 if (Array.isArray(value) && value.includes(query.value)) {
82 return true
83 }
84
85 // For all other values, return whether this is an exact match.
86 return String(value) === query.value
87}
88
89/**
90 * `[attr^=value]`
91 *
92 * @param {RuleAttr} query
93 * @param {Node} node
94 */
95function begins(query, node) {
96 /** @type {unknown} */
97 // @ts-expect-error: Looks like a record.
98 const value = node[query.name]
99
100 return (
101 query.value &&
102 typeof value === 'string' &&
103 value.slice(0, query.value.length) === query.value
104 )
105}
106
107/**
108 * `[attr$=value]`
109 *
110 * @param {RuleAttr} query
111 * @param {Node} node
112 */
113function ends(query, node) {
114 /** @type {unknown} */
115 // @ts-expect-error: Looks like a record.
116 const value = node[query.name]
117
118 return (
119 query.value &&
120 typeof value === 'string' &&
121 value.slice(-query.value.length) === query.value
122 )
123}
124
125/**
126 * `[attr*=value]`
127 *
128 * @param {RuleAttr} query
129 * @param {Node} node
130 */
131function containsString(query, node) {
132 /** @type {unknown} */
133 // @ts-expect-error: Looks like a record.
134 const value = node[query.name]
135 return query.value && typeof value === 'string' && value.includes(query.value)
136}
137
138// Shouldn’t be invoked, Parser throws an error instead.
139/* c8 ignore next 6 */
140/**
141 * @param {{[x: string]: unknown, type: string}} query
142 */
143function unknownOperator(query) {
144 throw new Error('Unknown operator `' + query.operator + '`')
145}