Skip to content

Commit 01a3af9

Browse files
committed
Add: generateSelector util for selector AST nodes
1 parent 5180e39 commit 01a3af9

File tree

2 files changed

+71
-0
lines changed

2 files changed

+71
-0
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { SelectorNode } from './parse-selector'
2+
3+
const prefixMap: Record<string, string> = {
4+
'class': '.',
5+
'pseudo-class': ':',
6+
'pseudo-element': '::',
7+
'attribute': '',
8+
'combinator': ''
9+
}
10+
11+
export default function generateSelector(nodes: SelectorNode[], body = ''): string {
12+
const generate = (node: SelectorNode): string => {
13+
if (node.type === 'attribute') return `[${node.value}]`
14+
if (node.type === 'combinator') return node.value
15+
16+
const prefix = node.type && prefixMap[node.type] || ''
17+
const base = node.value ? prefix + node.value : ''
18+
19+
if ('children' in node && node.children?.length) {
20+
const childrenText = node.children.map(generate).join('')
21+
return node.value ? base + `(${childrenText})` : base + childrenText
22+
}
23+
return base
24+
}
25+
26+
let pre = ''
27+
let current = ''
28+
const groups: string[] = []
29+
const flatNodes = nodes.flatMap(node => (!('value' in node) && 'children' in node && node.children?.length) ? node.children : node)
30+
31+
flatNodes
32+
.forEach(node => {
33+
if (node.value === ',') {
34+
groups.push(current)
35+
current = ''
36+
} else if (node.value === 'of' && 'children' in node) {
37+
pre += node.children!.map(generate).join('')
38+
} else {
39+
current += generate(node)
40+
}
41+
})
42+
43+
if (current) groups.push(current)
44+
if (pre && !pre.endsWith('>')) pre += ' '
45+
const result = groups.length
46+
? groups.map(text => pre + body + text).join(',')
47+
: pre + body
48+
49+
if (process.env.NODE_ENV === 'development') {
50+
/** Warning: Nodes before `:of()` cannot contain combinators or other ::pseudo-element nodes */
51+
const ofIndex = flatNodes.findIndex(node => node.type === 'pseudo-class' && node.value === 'of')
52+
if (ofIndex > 0) {
53+
const hasCombinator = flatNodes.slice(0, ofIndex).some(node => node.type === 'combinator')
54+
const hasPseudoElement = flatNodes.slice(0, ofIndex).some(node => node.type === 'pseudo-element')
55+
if (hasCombinator || hasPseudoElement) {
56+
console.warn(`Cannot use combinators or pseudo-elements before ':of()' selector: '${result}'`)
57+
}
58+
}
59+
}
60+
61+
return result
62+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { test, expect, describe } from 'vitest'
2+
import { generateSelector } from '../../src'
3+
import { cases } from './parse-selector.test'
4+
5+
describe.concurrent.each(Object.entries(cases))('%s', (_, cases) => {
6+
test.concurrent.each(cases)('%s', (_, output, nodes, config, body) => {
7+
expect(generateSelector(nodes, body)).toBe(output)
8+
})
9+
})

0 commit comments

Comments
 (0)