11/**
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
54 *
65 * @typedef {import('hast').Element } HastElement
76 * @typedef {import('hast').Properties } HastProperties
2322 * Current space.
2423 */
2524
25+ import { ok as assert } from 'devlop'
2626import { h , s } from 'hastscript'
27- import { CssSelectorParser } from 'css-selector-parser'
27+ import { createParser } from 'css-selector-parser'
2828
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' } )
3430
3531// To do: remove `space` shortcut.
3632/**
@@ -52,34 +48,35 @@ export function fromSelector(selector, space) {
5248 'html'
5349 }
5450
55- const query = parser . parse ( selector || '' )
51+ const query = cssSelectorParse ( selector || '* ' )
5652
57- if ( query && query . type === 'selectors' ) {
53+ if ( query . rules . length > 1 ) {
5854 throw new Error ( 'Cannot handle selector list' )
5955 }
6056
61- const result = query ? rule ( query . rule , state ) : [ ]
57+ const head = query . rules [ 0 ]
58+ assert ( head , 'expected rule' )
6259
6360 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 === '~' )
6863 ) {
6964 throw new Error (
7065 'Cannot handle sibling combinator `' +
71- query . rule . rule . nestingOperator +
66+ head . nestedRule . combinator +
7267 '` at root'
7368 )
7469 }
7570
76- return result [ 0 ] || build ( state . space ) ( '' )
71+ const result = rule ( head , state )
72+
73+ return result [ 0 ]
7774}
7875
7976/**
8077 * Turn a rule into one or more elements.
8178 *
82- * @param {Rule } query
79+ * @param {AstRule } query
8380 * Selector.
8481 * @param {State } state
8582 * Info on current context.
@@ -88,82 +85,90 @@ export function fromSelector(selector, space) {
8885 */
8986function rule ( query , state ) {
9087 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+ }
92105
93- checkPseudos ( query . pseudos || [ ] )
106+ if ( query . pseudoElement ) {
107+ throw new Error (
108+ 'Cannot handle pseudo element `' + query . pseudoElement + '`'
109+ )
110+ }
94111
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 ,
97116 className : query . classNames ,
98- ...attrsToHast ( query . attrs || [ ] )
117+ ...attributesToHast ( query . attributes )
99118 } )
100119 const results = [ node ]
101120
102- if ( query . rule ) {
121+ if ( query . nestedRule ) {
103122 // Sibling.
104123 if (
105- query . rule . nestingOperator === '+' ||
106- query . rule . nestingOperator === '~'
124+ query . nestedRule . combinator === '+' ||
125+ query . nestedRule . combinator === '~'
107126 ) {
108- results . push ( ...rule ( query . rule , state ) )
127+ results . push ( ...rule ( query . nestedRule , state ) )
109128 }
110129 // Descendant.
111130 else {
112- node . children . push ( ...rule ( query . rule , { space} ) )
131+ node . children . push ( ...rule ( query . nestedRule , { space} ) )
113132 }
114133 }
115134
116135 return results
117136}
118137
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-
141138/**
142139 * Turn attribute selectors into properties.
143140 *
144- * @param {Array<RuleAttr> } attrs
141+ * @param {Array<AstAttribute> | undefined } attributes
145142 * Attribute selectors.
146143 * @returns {HastProperties }
147144 * Properties.
148145 */
149- function attrsToHast ( attrs ) {
146+ function attributesToHast ( attributes ) {
150147 /** @type {HastProperties } */
151148 const props = { }
152149 let index = - 1
153150
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+ }
160169 } else {
161- throw new Error (
162- 'Cannot handle attribute equality modifier `' + attr . operator + '`'
163- )
170+ props [ attr . name ] = true
164171 }
165- } else {
166- props [ attr . name ] = true
167172 }
168173 }
169174
0 commit comments