1
1
/**
2
- * @typedef {import('css-selector-parser').RuleAttr } RuleAttr
3
- * @typedef {import('css-selector-parser').RulePseudo } RulePseudo
4
- * @typedef {import('css-selector-parser').Rule } Rule
2
+ * @typedef {import('css-selector-parser').AstAttribute } AstAttribute
3
+ * @typedef {import('css-selector-parser').AstRule } AstRule
5
4
*
6
5
* @typedef {import('hast').Element } HastElement
7
6
* @typedef {import('hast').Properties } HastProperties
23
22
* Current space.
24
23
*/
25
24
25
+ import { ok as assert } from 'devlop'
26
26
import { h , s } from 'hastscript'
27
- import { CssSelectorParser } from 'css-selector-parser'
27
+ import { createParser } from 'css-selector-parser'
28
28
29
- const parser = new CssSelectorParser ( )
30
-
31
- parser . registerNestingOperators ( '>' , '+' , '~' )
32
- // Register these so we can throw nicer errors.
33
- parser . registerAttrEqualityMods ( '~' , '|' , '^' , '$' , '*' )
29
+ const cssSelectorParse = createParser ( { syntax : 'selectors-4' } )
34
30
35
31
// To do: remove `space` shortcut.
36
32
/**
@@ -52,34 +48,35 @@ export function fromSelector(selector, space) {
52
48
'html'
53
49
}
54
50
55
- const query = parser . parse ( selector || '' )
51
+ const query = cssSelectorParse ( selector || '* ' )
56
52
57
- if ( query && query . type === 'selectors' ) {
53
+ if ( query . rules . length > 1 ) {
58
54
throw new Error ( 'Cannot handle selector list' )
59
55
}
60
56
61
- const result = query ? rule ( query . rule , state ) : [ ]
57
+ const head = query . rules [ 0 ]
58
+ assert ( head , 'expected rule' )
62
59
63
60
if (
64
- query &&
65
- query . rule . rule &&
66
- ( query . rule . rule . nestingOperator === '+' ||
67
- query . rule . rule . nestingOperator === '~' )
61
+ head . nestedRule &&
62
+ ( head . nestedRule . combinator === '+' || head . nestedRule . combinator === '~' )
68
63
) {
69
64
throw new Error (
70
65
'Cannot handle sibling combinator `' +
71
- query . rule . rule . nestingOperator +
66
+ head . nestedRule . combinator +
72
67
'` at root'
73
68
)
74
69
}
75
70
76
- return result [ 0 ] || build ( state . space ) ( '' )
71
+ const result = rule ( head , state )
72
+
73
+ return result [ 0 ]
77
74
}
78
75
79
76
/**
80
77
* Turn a rule into one or more elements.
81
78
*
82
- * @param {Rule } query
79
+ * @param {AstRule } query
83
80
* Selector.
84
81
* @param {State } state
85
82
* Info on current context.
@@ -88,82 +85,90 @@ export function fromSelector(selector, space) {
88
85
*/
89
86
function rule ( query , state ) {
90
87
const space =
91
- state . space === 'html' && query . tagName === 'svg' ? 'svg' : state . space
88
+ state . space === 'html' &&
89
+ query . tag &&
90
+ query . tag . type === 'TagName' &&
91
+ query . tag . name === 'svg'
92
+ ? 'svg'
93
+ : state . space
94
+
95
+ const pseudoClass = query . pseudoClasses ? query . pseudoClasses [ 0 ] : undefined
96
+
97
+ if ( pseudoClass ) {
98
+ if ( pseudoClass . name ) {
99
+ throw new Error ( 'Cannot handle pseudo class `' + pseudoClass . name + '`' )
100
+ /* c8 ignore next 4 -- types say this can occur, but I don’t understand how */
101
+ }
102
+
103
+ throw new Error ( 'Cannot handle empty pseudo class' )
104
+ }
92
105
93
- checkPseudos ( query . pseudos || [ ] )
106
+ if ( query . pseudoElement ) {
107
+ throw new Error (
108
+ 'Cannot handle pseudo element `' + query . pseudoElement + '`'
109
+ )
110
+ }
94
111
95
- const node = build ( space ) ( query . tagName === '*' ? '' : query . tagName || '' , {
96
- id : query . id ,
112
+ const name = query . tag && query . tag . type === 'TagName' ? query . tag . name : ''
113
+
114
+ const node = build ( space ) ( name , {
115
+ id : query . ids ? query . ids [ query . ids . length - 1 ] : undefined ,
97
116
className : query . classNames ,
98
- ...attrsToHast ( query . attrs || [ ] )
117
+ ...attributesToHast ( query . attributes )
99
118
} )
100
119
const results = [ node ]
101
120
102
- if ( query . rule ) {
121
+ if ( query . nestedRule ) {
103
122
// Sibling.
104
123
if (
105
- query . rule . nestingOperator === '+' ||
106
- query . rule . nestingOperator === '~'
124
+ query . nestedRule . combinator === '+' ||
125
+ query . nestedRule . combinator === '~'
107
126
) {
108
- results . push ( ...rule ( query . rule , state ) )
127
+ results . push ( ...rule ( query . nestedRule , state ) )
109
128
}
110
129
// Descendant.
111
130
else {
112
- node . children . push ( ...rule ( query . rule , { space} ) )
131
+ node . children . push ( ...rule ( query . nestedRule , { space} ) )
113
132
}
114
133
}
115
134
116
135
return results
117
136
}
118
137
119
- /**
120
- * Check pseudo selectors.
121
- *
122
- * @param {Array<RulePseudo> } pseudos
123
- * Pseudo selectors.
124
- * @returns {void }
125
- * Nothing.
126
- * @throws {Error }
127
- * When a pseudo is defined.
128
- */
129
- function checkPseudos ( pseudos ) {
130
- const pseudo = pseudos [ 0 ]
131
-
132
- if ( pseudo ) {
133
- if ( pseudo . name ) {
134
- throw new Error ( 'Cannot handle pseudo-selector `' + pseudo . name + '`' )
135
- }
136
-
137
- throw new Error ( 'Cannot handle pseudo-element or empty pseudo-class' )
138
- }
139
- }
140
-
141
138
/**
142
139
* Turn attribute selectors into properties.
143
140
*
144
- * @param {Array<RuleAttr> } attrs
141
+ * @param {Array<AstAttribute> | undefined } attributes
145
142
* Attribute selectors.
146
143
* @returns {HastProperties }
147
144
* Properties.
148
145
*/
149
- function attrsToHast ( attrs ) {
146
+ function attributesToHast ( attributes ) {
150
147
/** @type {HastProperties } */
151
148
const props = { }
152
149
let index = - 1
153
150
154
- while ( ++ index < attrs . length ) {
155
- const attr = attrs [ index ]
156
-
157
- if ( 'operator' in attr ) {
158
- if ( attr . operator === '=' ) {
159
- props [ attr . name ] = attr . value
151
+ if ( attributes ) {
152
+ while ( ++ index < attributes . length ) {
153
+ const attr = attributes [ index ]
154
+
155
+ if ( 'operator' in attr ) {
156
+ if ( attr . operator === '=' ) {
157
+ const value = attr . value
158
+
159
+ // eslint-disable-next-line max-depth
160
+ if ( value ) {
161
+ assert ( value . type === 'String' , 'substitution are not enabled' )
162
+ props [ attr . name ] = value . value
163
+ }
164
+ } else {
165
+ throw new Error (
166
+ 'Cannot handle attribute equality modifier `' + attr . operator + '`'
167
+ )
168
+ }
160
169
} else {
161
- throw new Error (
162
- 'Cannot handle attribute equality modifier `' + attr . operator + '`'
163
- )
170
+ props [ attr . name ] = true
164
171
}
165
- } else {
166
- props [ attr . name ] = true
167
172
}
168
173
}
169
174
0 commit comments