Skip to content

Commit 6f71aaf

Browse files
committed
Refactor to move implementation to lib/
1 parent 2e83190 commit 6f71aaf

File tree

3 files changed

+184
-177
lines changed

3 files changed

+184
-177
lines changed

index.js

Lines changed: 3 additions & 177 deletions
Original file line numberDiff line numberDiff line change
@@ -1,180 +1,6 @@
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
5-
*
6-
* @typedef {import('hast').Element} HastElement
7-
* @typedef {import('hast').Properties} HastProperties
8-
*
9-
* @typedef {'html' | 'svg'} Space
10-
* Name of namespace.
11-
*
12-
* @typedef Options
13-
* Configuration.
14-
* @property {Space} [space]
15-
* Which space first element in the selector is in.
16-
*
17-
* When an `svg` element is created in HTML, the space is automatically
18-
* switched to SVG.
19-
*
20-
* @typedef State
21-
* Info on current context.
22-
* @property {Space} space
23-
* Current space.
2+
* @typedef {import('./lib/index.js').Space} Space
3+
* @typedef {import('./lib/index.js').Options} Options
244
*/
255

26-
import {h, s} from 'hastscript'
27-
import {CssSelectorParser} from 'css-selector-parser'
28-
29-
const parser = new CssSelectorParser()
30-
31-
parser.registerNestingOperators('>', '+', '~')
32-
// Register these so we can throw nicer errors.
33-
parser.registerAttrEqualityMods('~', '|', '^', '$', '*')
34-
35-
/**
36-
* Turn a selector into a tree.
37-
*
38-
* @param {string | null | undefined} [selector='']
39-
* CSS selector.
40-
* @param {Space | Options | null | undefined} [space='html']
41-
* Space or configuration.
42-
* @returns {HastElement}
43-
* Built tree.
44-
*/
45-
export function fromSelector(selector, space) {
46-
/** @type {State} */
47-
const state = {
48-
space:
49-
(space && typeof space === 'object' && space.space) ||
50-
(typeof space === 'string' && space) ||
51-
'html'
52-
}
53-
54-
const query = parser.parse(selector || '')
55-
56-
if (query && query.type === 'selectors') {
57-
throw new Error('Cannot handle selector list')
58-
}
59-
60-
const result = query ? rule(query.rule, state) : []
61-
62-
if (
63-
query &&
64-
query.rule.rule &&
65-
(query.rule.rule.nestingOperator === '+' ||
66-
query.rule.rule.nestingOperator === '~')
67-
) {
68-
throw new Error(
69-
'Cannot handle sibling combinator `' +
70-
query.rule.rule.nestingOperator +
71-
'` at root'
72-
)
73-
}
74-
75-
return result[0] || build(state.space)('')
76-
}
77-
78-
/**
79-
* Turn a rule into one or more elements.
80-
*
81-
* @param {Rule} query
82-
* Selector.
83-
* @param {State} state
84-
* Info on current context.
85-
* @returns {Array<HastElement>}
86-
* One or more elements.
87-
*/
88-
function rule(query, state) {
89-
const space =
90-
state.space === 'html' && query.tagName === 'svg' ? 'svg' : state.space
91-
92-
checkPseudos(query.pseudos || [])
93-
94-
const node = build(space)(query.tagName === '*' ? '' : query.tagName || '', {
95-
id: query.id,
96-
className: query.classNames,
97-
...attrsToHast(query.attrs || [])
98-
})
99-
const results = [node]
100-
101-
if (query.rule) {
102-
// Sibling.
103-
if (
104-
query.rule.nestingOperator === '+' ||
105-
query.rule.nestingOperator === '~'
106-
) {
107-
results.push(...rule(query.rule, state))
108-
}
109-
// Descendant.
110-
else {
111-
node.children.push(...rule(query.rule, {space}))
112-
}
113-
}
114-
115-
return results
116-
}
117-
118-
/**
119-
* Check pseudo selectors.
120-
*
121-
* @param {Array<RulePseudo>} pseudos
122-
* Pseudo selectors.
123-
* @returns {void}
124-
* Nothing.
125-
* @throws {Error}
126-
* When a pseudo is defined.
127-
*/
128-
function checkPseudos(pseudos) {
129-
const pseudo = pseudos[0]
130-
131-
if (pseudo) {
132-
if (pseudo.name) {
133-
throw new Error('Cannot handle pseudo-selector `' + pseudo.name + '`')
134-
}
135-
136-
throw new Error('Cannot handle pseudo-element or empty pseudo-class')
137-
}
138-
}
139-
140-
/**
141-
* Turn attribute selectors into properties.
142-
*
143-
* @param {Array<RuleAttr>} attrs
144-
* Attribute selectors.
145-
* @returns {HastProperties}
146-
* Properties.
147-
*/
148-
function attrsToHast(attrs) {
149-
/** @type {HastProperties} */
150-
const props = {}
151-
let index = -1
152-
153-
while (++index < attrs.length) {
154-
const attr = attrs[index]
155-
156-
if ('operator' in attr) {
157-
if (attr.operator === '=') {
158-
props[attr.name] = attr.value
159-
} else {
160-
throw new Error(
161-
'Cannot handle attribute equality modifier `' + attr.operator + '`'
162-
)
163-
}
164-
} else {
165-
props[attr.name] = true
166-
}
167-
}
168-
169-
return props
170-
}
171-
172-
/**
173-
* @param {Space} space
174-
* Space.
175-
* @returns {typeof h}
176-
* `h`.
177-
*/
178-
function build(space) {
179-
return space === 'html' ? h : s
180-
}
6+
export {fromSelector} from './lib/index.js'

lib/index.js

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
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
5+
*
6+
* @typedef {import('hast').Element} HastElement
7+
* @typedef {import('hast').Properties} HastProperties
8+
*
9+
* @typedef {'html' | 'svg'} Space
10+
* Name of namespace.
11+
*
12+
* @typedef Options
13+
* Configuration.
14+
* @property {Space} [space]
15+
* Which space first element in the selector is in.
16+
*
17+
* When an `svg` element is created in HTML, the space is automatically
18+
* switched to SVG.
19+
*
20+
* @typedef State
21+
* Info on current context.
22+
* @property {Space} space
23+
* Current space.
24+
*/
25+
26+
import {h, s} from 'hastscript'
27+
import {CssSelectorParser} from 'css-selector-parser'
28+
29+
const parser = new CssSelectorParser()
30+
31+
parser.registerNestingOperators('>', '+', '~')
32+
// Register these so we can throw nicer errors.
33+
parser.registerAttrEqualityMods('~', '|', '^', '$', '*')
34+
35+
/**
36+
* Turn a selector into a tree.
37+
*
38+
* @param {string | null | undefined} [selector='']
39+
* CSS selector.
40+
* @param {Space | Options | null | undefined} [space='html']
41+
* Space or configuration.
42+
* @returns {HastElement}
43+
* Built tree.
44+
*/
45+
export function fromSelector(selector, space) {
46+
/** @type {State} */
47+
const state = {
48+
space:
49+
(space && typeof space === 'object' && space.space) ||
50+
(typeof space === 'string' && space) ||
51+
'html'
52+
}
53+
54+
const query = parser.parse(selector || '')
55+
56+
if (query && query.type === 'selectors') {
57+
throw new Error('Cannot handle selector list')
58+
}
59+
60+
const result = query ? rule(query.rule, state) : []
61+
62+
if (
63+
query &&
64+
query.rule.rule &&
65+
(query.rule.rule.nestingOperator === '+' ||
66+
query.rule.rule.nestingOperator === '~')
67+
) {
68+
throw new Error(
69+
'Cannot handle sibling combinator `' +
70+
query.rule.rule.nestingOperator +
71+
'` at root'
72+
)
73+
}
74+
75+
return result[0] || build(state.space)('')
76+
}
77+
78+
/**
79+
* Turn a rule into one or more elements.
80+
*
81+
* @param {Rule} query
82+
* Selector.
83+
* @param {State} state
84+
* Info on current context.
85+
* @returns {Array<HastElement>}
86+
* One or more elements.
87+
*/
88+
function rule(query, state) {
89+
const space =
90+
state.space === 'html' && query.tagName === 'svg' ? 'svg' : state.space
91+
92+
checkPseudos(query.pseudos || [])
93+
94+
const node = build(space)(query.tagName === '*' ? '' : query.tagName || '', {
95+
id: query.id,
96+
className: query.classNames,
97+
...attrsToHast(query.attrs || [])
98+
})
99+
const results = [node]
100+
101+
if (query.rule) {
102+
// Sibling.
103+
if (
104+
query.rule.nestingOperator === '+' ||
105+
query.rule.nestingOperator === '~'
106+
) {
107+
results.push(...rule(query.rule, state))
108+
}
109+
// Descendant.
110+
else {
111+
node.children.push(...rule(query.rule, {space}))
112+
}
113+
}
114+
115+
return results
116+
}
117+
118+
/**
119+
* Check pseudo selectors.
120+
*
121+
* @param {Array<RulePseudo>} pseudos
122+
* Pseudo selectors.
123+
* @returns {void}
124+
* Nothing.
125+
* @throws {Error}
126+
* When a pseudo is defined.
127+
*/
128+
function checkPseudos(pseudos) {
129+
const pseudo = pseudos[0]
130+
131+
if (pseudo) {
132+
if (pseudo.name) {
133+
throw new Error('Cannot handle pseudo-selector `' + pseudo.name + '`')
134+
}
135+
136+
throw new Error('Cannot handle pseudo-element or empty pseudo-class')
137+
}
138+
}
139+
140+
/**
141+
* Turn attribute selectors into properties.
142+
*
143+
* @param {Array<RuleAttr>} attrs
144+
* Attribute selectors.
145+
* @returns {HastProperties}
146+
* Properties.
147+
*/
148+
function attrsToHast(attrs) {
149+
/** @type {HastProperties} */
150+
const props = {}
151+
let index = -1
152+
153+
while (++index < attrs.length) {
154+
const attr = attrs[index]
155+
156+
if ('operator' in attr) {
157+
if (attr.operator === '=') {
158+
props[attr.name] = attr.value
159+
} else {
160+
throw new Error(
161+
'Cannot handle attribute equality modifier `' + attr.operator + '`'
162+
)
163+
}
164+
} else {
165+
props[attr.name] = true
166+
}
167+
}
168+
169+
return props
170+
}
171+
172+
/**
173+
* @param {Space} space
174+
* Space.
175+
* @returns {typeof h}
176+
* `h`.
177+
*/
178+
function build(space) {
179+
return space === 'html' ? h : s
180+
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"main": "index.js",
3232
"types": "index.d.ts",
3333
"files": [
34+
"lib/",
3435
"index.d.ts",
3536
"index.js"
3637
],

0 commit comments

Comments
 (0)