11/**
2- * @typedef {import('./types.js ').Rule } Rule
3- * @typedef {import('./types.js ').RuleAttr } RuleAttr
2+ * @typedef {import('css-selector-parser ').AstAttribute } AstAttribute
3+ * @typedef {import('css-selector-parser ').AstRule } AstRule
44 * @typedef {import('./types.js').Node } Node
55 */
66
7+ import { unreachable } from 'devlop'
78import { zwitch } from 'zwitch'
89
9- /** @type {(query: RuleAttr , node: Node) => boolean } */
10+ /** @type {(query: AstAttribute , node: Node) => boolean } */
1011const handle = zwitch ( 'operator' , {
1112 unknown : unknownOperator ,
1213 // @ts -expect-error: hush.
@@ -21,15 +22,17 @@ const handle = zwitch('operator', {
2122} )
2223
2324/**
24- * @param {Rule } query
25+ * @param {AstRule } query
2526 * @param {Node } node
2627 * @returns {boolean }
2728 */
2829export function attribute ( query , node ) {
2930 let index = - 1
3031
31- while ( ++ index < query . attrs . length ) {
32- if ( ! handle ( query . attrs [ index ] , node ) ) return false
32+ if ( query . attributes ) {
33+ while ( ++ index < query . attributes . length ) {
34+ if ( ! handle ( query . attributes [ index ] , node ) ) return false
35+ }
3336 }
3437
3538 return true
@@ -40,7 +43,7 @@ export function attribute(query, node) {
4043 *
4144 * `[attr]`
4245 *
43- * @param {RuleAttr } query
46+ * @param {AstAttribute } query
4447 * @param {Node } node
4548 * @returns {boolean }
4649 */
@@ -54,13 +57,15 @@ function exists(query, node) {
5457 *
5558 * `[attr=value]`
5659 *
57- * @param {RuleAttr } query
60+ * @param {AstAttribute } query
5861 * @param {Node } node
5962 * @returns {boolean }
6063 */
6164function exact ( query , node ) {
65+ const queryValue = attributeValue ( query )
66+
6267 // @ts -expect-error: Looks like a record.
63- return exists ( query , node ) && String ( node [ query . name ] ) === query . value
68+ return exists ( query , node ) && String ( node [ query . name ] ) === queryValue
6469}
6570
6671/**
@@ -71,7 +76,7 @@ function exact(query, node) {
7176 *
7277 * `[attr~=value]`
7378 *
74- * @param {RuleAttr } query
79+ * @param {AstAttribute } query
7580 * @param {Node } node
7681 * @returns {boolean }
7782 */
@@ -82,36 +87,39 @@ function containsArray(query, node) {
8287
8388 if ( value === null || value === undefined ) return false
8489
85- // If this is an array, and the query is contained in it, return true.
90+ const queryValue = attributeValue ( query )
91+
92+ // If this is an array, and the query is contained in it, return `true`.
8693 // Coverage comment in place because TS turns `Array.isArray(unknown)`
8794 // into `Array<any>` instead of `Array<unknown>`.
8895 // type-coverage:ignore-next-line
89- if ( Array . isArray ( value ) && value . includes ( query . value ) ) {
96+ if ( Array . isArray ( value ) && value . includes ( queryValue ) ) {
9097 return true
9198 }
9299
93100 // For all other values, return whether this is an exact match.
94- return String ( value ) === query . value
101+ return String ( value ) === queryValue
95102}
96103
97104/**
98105 * Check whether an attribute has a substring as its start.
99106 *
100107 * `[attr^=value]`
101108 *
102- * @param {RuleAttr } query
109+ * @param {AstAttribute } query
103110 * @param {Node } node
104111 * @returns {boolean }
105112 */
106113function begins ( query , node ) {
107114 /** @type {unknown } */
108115 // @ts -expect-error: Looks like a record.
109116 const value = node [ query . name ]
117+ const queryValue = attributeValue ( query )
110118
111119 return Boolean (
112120 query . value &&
113121 typeof value === 'string' &&
114- value . slice ( 0 , query . value . length ) === query . value
122+ value . slice ( 0 , queryValue . length ) === queryValue
115123 )
116124}
117125
@@ -120,19 +128,20 @@ function begins(query, node) {
120128 *
121129 * `[attr$=value]`
122130 *
123- * @param {RuleAttr } query
131+ * @param {AstAttribute } query
124132 * @param {Node } node
125133 * @returns {boolean }
126134 */
127135function ends ( query , node ) {
128136 /** @type {unknown } */
129137 // @ts -expect-error: Looks like a record.
130138 const value = node [ query . name ]
139+ const queryValue = attributeValue ( query )
131140
132141 return Boolean (
133142 query . value &&
134143 typeof value === 'string' &&
135- value . slice ( - query . value . length ) === query . value
144+ value . slice ( - queryValue . length ) === queryValue
136145 )
137146}
138147
@@ -141,16 +150,18 @@ function ends(query, node) {
141150 *
142151 * `[attr*=value]`
143152 *
144- * @param {RuleAttr } query
153+ * @param {AstAttribute } query
145154 * @param {Node } node
146155 * @returns {boolean }
147156 */
148157function containsString ( query , node ) {
149158 /** @type {unknown } */
150159 // @ts -expect-error: Looks like a record.
151160 const value = node [ query . name ]
161+ const queryValue = attributeValue ( query )
162+
152163 return Boolean (
153- query . value && typeof value === 'string' && value . includes ( query . value )
164+ typeof value === 'string' && queryValue && value . includes ( queryValue )
154165 )
155166}
156167
@@ -164,3 +175,19 @@ function unknownOperator(query) {
164175 // @ts -expect-error: `operator` guaranteed.
165176 throw new Error ( 'Unknown operator `' + query . operator + '`' )
166177}
178+
179+ /**
180+ * @param {AstAttribute } query
181+ * @returns {string }
182+ */
183+ function attributeValue ( query ) {
184+ const queryValue = query . value
185+
186+ /* c8 ignore next 4 -- never happens with our config */
187+ if ( ! queryValue ) unreachable ( 'Attribute values should be defined' )
188+ if ( queryValue . type === 'Substitution' ) {
189+ unreachable ( 'Substitutions are not enabled' )
190+ }
191+
192+ return queryValue . value
193+ }
0 commit comments