10
10
*/
11
11
12
12
import { stringify as commas } from 'comma-separated-tokens'
13
- import { ok as assert , unreachable } from 'devlop'
14
- import { hasProperty } from 'hast-util-has-property'
13
+ import { ok as assert } from 'devlop'
15
14
import { find } from 'property-information'
16
- import { stringify as spaces } from 'space-separated-tokens'
17
- import { zwitch } from 'zwitch'
18
-
19
- /** @type {(query: AstAttribute, element: Element, info: Info) => boolean } */
20
- const handle = zwitch ( 'operator' , {
21
- unknown : unknownOperator ,
22
- // @ts -expect-error: `exists` is fine.
23
- invalid : exists ,
24
- handlers : {
25
- '=' : exact ,
26
- '$=' : ends ,
27
- '*=' : contains ,
28
- '^=' : begins ,
29
- '|=' : exactOrPrefix ,
30
- '~=' : spaceSeparatedList
31
- }
32
- } )
15
+ import * as spaces from 'space-separated-tokens'
33
16
34
17
/**
35
18
* @param {AstRule } query
@@ -41,14 +24,12 @@ const handle = zwitch('operator', {
41
24
* @returns {boolean }
42
25
* Whether `element` matches `query`.
43
26
*/
44
- export function attribute ( query , element , schema ) {
27
+ export function attributes ( query , element , schema ) {
45
28
let index = - 1
46
29
47
30
if ( query . attributes ) {
48
31
while ( ++ index < query . attributes . length ) {
49
- const attribute = query . attributes [ index ]
50
-
51
- if ( ! handle ( attribute , element , find ( schema , attribute . name ) ) ) {
32
+ if ( ! attribute ( query . attributes [ index ] , element , schema ) ) {
52
33
return false
53
34
}
54
35
}
@@ -58,225 +39,105 @@ export function attribute(query, element, schema) {
58
39
}
59
40
60
41
/**
61
- * Check whether an attribute has a substring as its start.
62
- *
63
- * `[attr^=value]`
64
- *
65
42
* @param {AstAttribute } query
66
43
* Query.
67
44
* @param {Element } element
68
45
* Element.
69
- * @param {Info } info
70
- * Property info .
46
+ * @param {Schema } schema
47
+ * Schema of element .
71
48
* @returns {boolean }
72
49
* Whether `element` matches `query`.
73
50
*/
74
- function begins ( query , element , info ) {
75
- assert ( query . value , 'expected `value`' )
76
- assert ( query . value . type === 'String' , 'expected plain string' )
77
-
78
- return Boolean (
79
- hasProperty ( element , info . property ) &&
80
- normalizeValue ( element . properties [ info . property ] , info ) . slice (
81
- 0 ,
82
- query . value . value . length
83
- ) === query . value . value
84
- )
85
- }
51
+ function attribute ( query , element , schema ) {
52
+ const info = find ( schema , query . name )
53
+ const propertyValue = element . properties [ info . property ]
54
+ let value = normalizeValue ( propertyValue , info )
55
+
56
+ // Exists.
57
+ if ( ! query . value ) {
58
+ return value !== undefined
59
+ }
86
60
87
- /**
88
- * Check whether an attribute contains a substring.
89
- *
90
- * `[attr*=value]`
91
- *
92
- * @param {AstAttribute } query
93
- * Query.
94
- * @param {Element } element
95
- * Element.
96
- * @param {Info } info
97
- * Property info.
98
- * @returns {boolean }
99
- * Whether `element` matches `query`.
100
- */
101
- function contains ( query , element , info ) {
102
- assert ( query . value , 'expected `value`' )
103
61
assert ( query . value . type === 'String' , 'expected plain string' )
62
+ let key = query . value . value
104
63
105
- return Boolean (
106
- hasProperty ( element , info . property ) &&
107
- normalizeValue ( element . properties [ info . property ] , info ) . includes (
108
- query . value . value
109
- )
110
- )
111
- }
64
+ // Case-sensitivity.
65
+ if ( query . caseSensitivityModifier === 'i' ) {
66
+ key = key . toLowerCase ( )
112
67
113
- /**
114
- * Check whether an attribute has a substring as its end.
115
- *
116
- * `[attr$=value]`
117
- *
118
- * @param {AstAttribute } query
119
- * Query.
120
- * @param {Element } element
121
- * Element.
122
- * @param {Info } info
123
- * Property info.
124
- * @returns {boolean }
125
- * Whether `element` matches `query`.
126
- */
127
- function ends ( query , element , info ) {
128
- assert ( query . value , 'expected `value`' )
129
- assert ( query . value . type === 'String' , 'expected plain string' )
68
+ if ( value ) {
69
+ value = value . toLowerCase ( )
70
+ }
71
+ }
130
72
131
- return Boolean (
132
- hasProperty ( element , info . property ) &&
133
- normalizeValue ( element . properties [ info . property ] , info ) . slice (
134
- - query . value . value . length
135
- ) === query . value . value
136
- )
137
- }
73
+ if ( value !== undefined ) {
74
+ switch ( query . operator ) {
75
+ // Exact.
76
+ case '=' : {
77
+ return key === value
78
+ }
138
79
139
- /**
140
- * Check whether an attribute has an exact value.
141
- *
142
- * `[attr=value]`
143
- *
144
- * @param {AstAttribute } query
145
- * Query.
146
- * @param {Element } element
147
- * Element.
148
- * @param {Info } info
149
- * Property info.
150
- * @returns {boolean }
151
- * Whether `element` matches `query`.
152
- */
153
- function exact ( query , element , info ) {
154
- assert ( query . value , 'expected `value`' )
155
- assert ( query . value . type === 'String' , 'expected plain string' )
80
+ // Ends.
81
+ case '$=' : {
82
+ return key === value . slice ( - key . length )
83
+ }
156
84
157
- return Boolean (
158
- hasProperty ( element , info . property ) &&
159
- normalizeValue ( element . properties [ info . property ] , info ) ===
160
- query . value . value
161
- )
162
- }
85
+ // Contains.
86
+ case '*=' : {
87
+ return value . includes ( key )
88
+ }
163
89
164
- /**
165
- * Check whether an attribute has a substring as either the exact value or a
166
- * prefix.
167
- *
168
- * `[attr|=value]`
169
- *
170
- * @param {AstAttribute } query
171
- * Query.
172
- * @param {Element } element
173
- * Element.
174
- * @param {Info } info
175
- * Property info.
176
- * @returns {boolean }
177
- * Whether `element` matches `query`.
178
- */
179
- function exactOrPrefix ( query , element , info ) {
180
- assert ( query . value , 'expected `value`' )
181
- assert ( query . value . type === 'String' , 'expected plain string' )
90
+ // Begins.
91
+ case '^=' : {
92
+ return key === value . slice ( 0 , key . length )
93
+ }
182
94
183
- const value = normalizeValue ( element . properties [ info . property ] , info )
95
+ // Exact or prefix.
96
+ case '|=' : {
97
+ return (
98
+ key === value ||
99
+ ( key === value . slice ( 0 , key . length ) &&
100
+ value . charAt ( key . length ) === '-' )
101
+ )
102
+ }
184
103
185
- return Boolean (
186
- hasProperty ( element , info . property ) &&
187
- ( value === query . value . value ||
188
- ( value . slice ( 0 , query . value . value . length ) === query . value . value &&
189
- value . charAt ( query . value . value . length ) === '-' ) )
190
- )
191
- }
104
+ // Space-separated list.
105
+ case '~=' : {
106
+ return (
107
+ // For all other values (including comma-separated lists), return whether this
108
+ // is an exact match.
109
+ key === value ||
110
+ // If this is a space-separated list, and the query is contained in it, return
111
+ // true.
112
+ spaces . parse ( value ) . includes ( key )
113
+ )
114
+ }
115
+ // Other values are not yet supported by CSS.
116
+ // No default
117
+ }
118
+ }
192
119
193
- /**
194
- * Check whether an attribute exists.
195
- *
196
- * `[attr]`
197
- *
198
- * @param {AstAttribute } _
199
- * Query.
200
- * @param {Element } element
201
- * Element.
202
- * @param {Info } info
203
- * Property info.
204
- * @returns {boolean }
205
- * Whether `element` matches `query`.
206
- */
207
- function exists ( _ , element , info ) {
208
- return hasProperty ( element , info . property )
120
+ return false
209
121
}
210
122
211
123
/**
212
- * Stringify a hast value back to its HTML form.
213
124
*
214
125
* @param {Properties[keyof Properties] } value
215
- * hast property value.
216
126
* @param {Info } info
217
- * Property info.
218
- * @returns {string }
219
- * Normalized value.
127
+ * @returns {string | undefined }
220
128
*/
221
129
function normalizeValue ( value , info ) {
222
- if ( typeof value === 'boolean' ) {
223
- return info . attribute
224
- }
225
-
226
- if ( Array . isArray ( value ) ) {
227
- return ( info . commaSeparated ? commas : spaces ) ( value )
130
+ if ( value === null || value === undefined ) {
131
+ // Empty.
132
+ } else if ( typeof value === 'boolean' ) {
133
+ if ( value ) {
134
+ return info . attribute
135
+ }
136
+ } else if ( Array . isArray ( value ) ) {
137
+ if ( value . length > 0 ) {
138
+ return ( info . commaSeparated ? commas : spaces . stringify ) ( value )
139
+ }
140
+ } else {
141
+ return String ( value )
228
142
}
229
-
230
- return String ( value )
231
- }
232
-
233
- /**
234
- * Check whether an attribute, interpreted as a space-separated list, contains
235
- * a value.
236
- *
237
- * `[attr~=value]`
238
- *
239
- * @param {AstAttribute } query
240
- * Query.
241
- * @param {Element } element
242
- * Element.
243
- * @param {Info } info
244
- * Property info.
245
- * @returns {boolean }
246
- * Whether `element` matches `query`.
247
- */
248
- function spaceSeparatedList ( query , element , info ) {
249
- assert ( query . value , 'expected `value`' )
250
- assert ( query . value . type === 'String' , 'expected plain string' )
251
-
252
- const value = element . properties [ info . property ]
253
-
254
- return (
255
- // If this is a space-separated list, and the query is contained in it, return
256
- // true.
257
- ( ! info . commaSeparated &&
258
- value &&
259
- typeof value === 'object' &&
260
- value . includes ( query . value . value ) ) ||
261
- // For all other values (including comma-separated lists), return whether this
262
- // is an exact match.
263
- ( hasProperty ( element , info . property ) &&
264
- normalizeValue ( value , info ) === query . value . value )
265
- )
266
- }
267
-
268
- // Shouldn’t be called, Parser throws an error instead.
269
- /**
270
- * @param {unknown } query_
271
- * Query.
272
- * @returns {never }
273
- * Nothing.
274
- * @throws {Error }
275
- * Error.
276
- */
277
- /* c8 ignore next 5 */
278
- function unknownOperator ( query_ ) {
279
- // Runtime guarantees `operator` exists.
280
- const query = /** @type {AstAttribute } */ ( query_ )
281
- unreachable ( 'Unknown operator `' + query . operator + '`' )
282
143
}
0 commit comments