Skip to content

Commit 50db7b2

Browse files
committed
Refactor code-style
* Add support for `null` in API input types * Add more docs to JSDoc * Remove dependency
1 parent 9c1d57e commit 50db7b2

File tree

3 files changed

+95
-77
lines changed

3 files changed

+95
-77
lines changed

index.js

Lines changed: 92 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,115 +1,131 @@
11
/**
2-
* @typedef {import('css-selector-parser').RuleAttr} CssRuleAttr
3-
* @typedef {import('css-selector-parser').RulePseudo} CssRulePseudo
4-
* @typedef {import('css-selector-parser').Selectors} CssSelectors
5-
* @typedef {import('css-selector-parser').RuleSet} CssRuleSet
6-
* @typedef {import('css-selector-parser').Rule} CssRule
2+
* @typedef {import('css-selector-parser').RuleAttr} RuleAttr
3+
* @typedef {import('css-selector-parser').RulePseudo} RulePseudo
4+
* @typedef {import('css-selector-parser').Rule} Rule
75
*
86
* @typedef {import('hast').Element} HastElement
97
* @typedef {import('hast').Properties} HastProperties
108
*
11-
* @typedef {'html'|'svg'} Space
9+
* @typedef {'html' | 'svg'} Space
10+
* Name of namespace.
1211
*
1312
* @typedef Options
13+
* Configuration.
1414
* @property {Space} [space]
15+
* Which space first element in the selector is in.
1516
*
16-
* @typedef Context
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.
1722
* @property {Space} space
18-
* @property {boolean} root
23+
* Current space.
1924
*/
2025

2126
import {h, s} from 'hastscript'
22-
import {zwitch} from 'zwitch'
2327
import {CssSelectorParser} from 'css-selector-parser'
2428

25-
const compile = zwitch('type', {handlers: {selectors, ruleSet, rule}})
26-
2729
const parser = new CssSelectorParser()
2830

2931
parser.registerNestingOperators('>', '+', '~')
3032
// Register these so we can throw nicer errors.
3133
parser.registerAttrEqualityMods('~', '|', '^', '$', '*')
3234

3335
/**
34-
* @param {string} [selector='']
35-
* @param {Space|Options} [space='html']
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.
3642
* @returns {HastElement}
43+
* Built tree.
3744
*/
3845
export function fromSelector(selector, space) {
39-
/** @type {Context} */
40-
const config = {
46+
/** @type {State} */
47+
const state = {
4148
space:
4249
(space && typeof space === 'object' && space.space) ||
4350
(typeof space === 'string' && space) ||
44-
'html',
45-
root: true
51+
'html'
4652
}
4753

48-
return (
49-
compile(parser.parse(selector || ''), config) || build(config.space)('')
50-
)
51-
}
54+
const query = parser.parse(selector || '')
5255

53-
/**
54-
* @param {CssSelectors} _
55-
*/
56-
function selectors(_) {
57-
throw new Error('Cannot handle selector list')
58-
}
56+
if (query && query.type === 'selectors') {
57+
throw new Error('Cannot handle selector list')
58+
}
5959

60-
/**
61-
* @param {CssRuleSet} query
62-
* @param {Context} config
63-
* @returns {HastElement|Array<HastElement>}
64-
*/
65-
function ruleSet(query, config) {
66-
// @ts-expect-error Assume one or more elements is returned.
67-
return compile(query.rule, config)
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)('')
6876
}
6977

7078
/**
71-
* @param {CssRule} query
72-
* @param {Context} config
73-
* @returns {HastElement|Array<HastElement>}
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.
7487
*/
75-
function rule(query, config) {
76-
const parentSpace = config.space
77-
const name = query.tagName === '*' ? '' : query.tagName || ''
78-
const space = parentSpace === 'html' && name === 'svg' ? 'svg' : parentSpace
79-
/** @type {boolean|undefined} */
80-
let sibling
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]
81100

82101
if (query.rule) {
83-
sibling =
84-
query.rule.nestingOperator === '+' || query.rule.nestingOperator === '~'
85-
86-
if (sibling && config.root) {
87-
throw new Error(
88-
'Cannot handle sibling combinator `' +
89-
query.rule.nestingOperator +
90-
'` at root'
91-
)
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}))
92112
}
93113
}
94114

95-
const node = build(space)(
96-
name,
97-
Object.assign(
98-
{id: query.id, className: query.classNames},
99-
pseudosToHast(query.pseudos || []),
100-
attrsToHast(query.attrs || [])
101-
),
102-
!query.rule || sibling ? [] : compile(query.rule, {space})
103-
)
104-
105-
return sibling ? [node, compile(query.rule, {space: parentSpace})] : node
115+
return results
106116
}
107117

108118
/**
109-
* @param {Array<CssRulePseudo>} pseudos
110-
* @returns {HastProperties}
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.
111127
*/
112-
function pseudosToHast(pseudos) {
128+
function checkPseudos(pseudos) {
113129
const pseudo = pseudos[0]
114130

115131
if (pseudo) {
@@ -119,23 +135,23 @@ function pseudosToHast(pseudos) {
119135

120136
throw new Error('Cannot handle pseudo-element or empty pseudo-class')
121137
}
122-
123-
return {}
124138
}
125139

126140
/**
127-
* @param {Array<CssRuleAttr>} attrs
141+
* Turn attribute selectors into properties.
142+
*
143+
* @param {Array<RuleAttr>} attrs
144+
* Attribute selectors.
128145
* @returns {HastProperties}
146+
* Properties.
129147
*/
130148
function attrsToHast(attrs) {
131-
let index = -1
132149
/** @type {HastProperties} */
133150
const props = {}
134-
/** @type {CssRuleAttr} */
135-
let attr
151+
let index = -1
136152

137153
while (++index < attrs.length) {
138-
attr = attrs[index]
154+
const attr = attrs[index]
139155

140156
if ('operator' in attr) {
141157
if (attr.operator === '=') {
@@ -155,7 +171,9 @@ function attrsToHast(attrs) {
155171

156172
/**
157173
* @param {Space} space
174+
* Space.
158175
* @returns {typeof h}
176+
* `h`.
159177
*/
160178
function build(space) {
161179
return space === 'html' ? h : s

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,7 @@
3737
"dependencies": {
3838
"@types/hast": "^2.0.0",
3939
"css-selector-parser": "^1.0.0",
40-
"hastscript": "^7.0.0",
41-
"zwitch": "^2.0.0"
40+
"hastscript": "^7.0.0"
4241
},
4342
"devDependencies": {
4443
"@types/tape": "^4.0.0",

readme.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ Create one or more [*element*][element]s from a CSS selector.
111111
— treated as `options.space`
112112
* `options.space` (enum, `'svg'` or `'html'`, default: `'html'`)
113113
— which space first element in the selector is in.
114-
When an `svg` is created in HTML, the space is automatically switched to SVG
114+
When an `svg` element is created in HTML, the space is automatically
115+
switched to SVG
115116

116117
###### Returns
117118

0 commit comments

Comments
 (0)