Skip to content

Commit 0dbde1e

Browse files
committed
breaking: always wrap selectors in selector_list node
1 parent db6fe79 commit 0dbde1e

File tree

10 files changed

+123
-148
lines changed

10 files changed

+123
-148
lines changed

API.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ console.log(declaration.value) // "red"
7979
```
8080
Stylesheet (NODE_STYLESHEET)
8181
└─ StyleRule (NODE_STYLE_RULE)
82-
├─ Selector (NODE_SELECTOR) "body"
82+
├─ SelectorList (NODE_SELECTOR_LIST) "body"
83+
│ └─ Type (NODE_SELECTOR_TYPE) "body"
8384
└─ Declaration (NODE_DECLARATION) "color: red"
8485
└─ Keyword (NODE_VALUE_KEYWORD) "red"
8586
```
@@ -234,8 +235,9 @@ import { parse_selector } from '@projectwallace/css-parser'
234235
235236
const selector = parse_selector('div.class > p#id::before')
236237
237-
console.log(selector.type) // NODE_SELECTOR
238-
for (const part of selector) {
238+
console.log(selector.type) // NODE_SELECTOR_LIST
239+
// Iterate over selector components
240+
for (const part of selector.first_child) {
239241
console.log(part.type, part.text)
240242
}
241243
// NODE_SELECTOR_TYPE "div"
@@ -290,7 +292,8 @@ walk(ast, (node, depth) => {
290292
})
291293
// NODE_STYLESHEET
292294
// NODE_STYLE_RULE
293-
// NODE_SELECTOR
295+
// NODE_SELECTOR_LIST
296+
// NODE_SELECTOR_TYPE
294297
// NODE_DECLARATION
295298
// NODE_VALUE_KEYWORD
296299
```

src/column-tracking.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, test, expect } from 'vitest'
22
import { parse } from './parse'
3-
import { NODE_STYLE_RULE, NODE_DECLARATION, NODE_AT_RULE, NODE_SELECTOR } from './parser'
3+
import { NODE_STYLE_RULE, NODE_DECLARATION, NODE_AT_RULE, NODE_SELECTOR_LIST } from './parser'
44

55
describe('Column Tracking', () => {
66
test('should track column for single-line CSS', () => {
@@ -21,7 +21,7 @@ describe('Column Tracking', () => {
2121
// Selector (body)
2222
const selector = rule!.first_child
2323
expect(selector).not.toBeNull()
24-
expect(selector!.type).toBe(NODE_SELECTOR)
24+
expect(selector!.type).toBe(NODE_SELECTOR_LIST)
2525
expect(selector!.line).toBe(1)
2626
expect(selector!.column).toBe(1)
2727

src/css-node.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, test, expect } from 'vitest'
22
import { Parser } from './parser'
3-
import { NODE_DECLARATION, NODE_SELECTOR, NODE_STYLE_RULE, NODE_AT_RULE } from './arena'
3+
import { NODE_DECLARATION, NODE_SELECTOR_LIST, NODE_STYLE_RULE, NODE_AT_RULE } from './arena'
44

55
describe('CSSNode', () => {
66
describe('iteration', () => {
@@ -16,7 +16,7 @@ describe('CSSNode', () => {
1616
types.push(child.type)
1717
}
1818

19-
expect(types).toEqual([NODE_SELECTOR, NODE_DECLARATION, NODE_DECLARATION, NODE_DECLARATION])
19+
expect(types).toEqual([NODE_SELECTOR_LIST, NODE_DECLARATION, NODE_DECLARATION, NODE_DECLARATION])
2020
})
2121

2222
test('should work with spread operator', () => {

src/parse-selector.test.ts

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,156 +1,156 @@
11
import { describe, test, expect } from 'vitest'
22
import { parse_selector } from './parse-selector'
3-
import { NODE_SELECTOR } from './arena'
3+
import { NODE_SELECTOR_LIST } from './arena'
44

55
describe('parse_selector()', () => {
66
test('should parse simple type selector', () => {
77
const result = parse_selector('div')
88

9-
expect(result.type).toBe(NODE_SELECTOR)
9+
expect(result.type).toBe(NODE_SELECTOR_LIST)
1010
expect(result.text).toBe('div')
1111
expect(result.has_children).toBe(true)
1212
})
1313

1414
test('should parse class selector', () => {
1515
const result = parse_selector('.classname')
1616

17-
expect(result.type).toBe(NODE_SELECTOR)
17+
expect(result.type).toBe(NODE_SELECTOR_LIST)
1818
expect(result.text).toBe('.classname')
1919
expect(result.has_children).toBe(true)
2020
})
2121

2222
test('should parse ID selector', () => {
2323
const result = parse_selector('#identifier')
2424

25-
expect(result.type).toBe(NODE_SELECTOR)
25+
expect(result.type).toBe(NODE_SELECTOR_LIST)
2626
expect(result.text).toBe('#identifier')
2727
expect(result.has_children).toBe(true)
2828
})
2929

3030
test('should parse compound selector', () => {
3131
const result = parse_selector('div.class#id')
3232

33-
expect(result.type).toBe(NODE_SELECTOR)
33+
expect(result.type).toBe(NODE_SELECTOR_LIST)
3434
expect(result.text).toBe('div.class#id')
3535
expect(result.has_children).toBe(true)
3636
})
3737

3838
test('should parse complex selector with combinator', () => {
3939
const result = parse_selector('div.class > p#id')
4040

41-
expect(result.type).toBe(NODE_SELECTOR)
41+
expect(result.type).toBe(NODE_SELECTOR_LIST)
4242
expect(result.text).toBe('div.class > p#id')
4343
expect(result.has_children).toBe(true)
4444
})
4545

4646
test('should parse selector list', () => {
4747
const result = parse_selector('h1, h2, h3')
4848

49-
expect(result.type).toBe(NODE_SELECTOR)
49+
expect(result.type).toBe(NODE_SELECTOR_LIST)
5050
expect(result.text).toBe('h1, h2, h3')
5151
expect(result.has_children).toBe(true)
5252
})
5353

5454
test('should parse pseudo-class selector', () => {
5555
const result = parse_selector('a:hover')
5656

57-
expect(result.type).toBe(NODE_SELECTOR)
57+
expect(result.type).toBe(NODE_SELECTOR_LIST)
5858
expect(result.text).toBe('a:hover')
5959
expect(result.has_children).toBe(true)
6060
})
6161

6262
test('should parse pseudo-class with function', () => {
6363
const result = parse_selector(':nth-child(2n+1)')
6464

65-
expect(result.type).toBe(NODE_SELECTOR)
65+
expect(result.type).toBe(NODE_SELECTOR_LIST)
6666
expect(result.text).toBe(':nth-child(2n+1)')
6767
expect(result.has_children).toBe(true)
6868
})
6969

7070
test('should parse attribute selector', () => {
7171
const result = parse_selector('[href^="https"]')
7272

73-
expect(result.type).toBe(NODE_SELECTOR)
73+
expect(result.type).toBe(NODE_SELECTOR_LIST)
7474
expect(result.text).toBe('[href^="https"]')
7575
expect(result.has_children).toBe(true)
7676
})
7777

7878
test('should parse universal selector', () => {
7979
const result = parse_selector('*')
8080

81-
expect(result.type).toBe(NODE_SELECTOR)
81+
expect(result.type).toBe(NODE_SELECTOR_LIST)
8282
expect(result.text).toBe('*')
8383
expect(result.has_children).toBe(true)
8484
})
8585

8686
test('should parse nesting selector', () => {
8787
const result = parse_selector('& .child')
8888

89-
expect(result.type).toBe(NODE_SELECTOR)
89+
expect(result.type).toBe(NODE_SELECTOR_LIST)
9090
expect(result.text).toBe('& .child')
9191
expect(result.has_children).toBe(true)
9292
})
9393

9494
test('should parse descendant combinator', () => {
9595
const result = parse_selector('div span')
9696

97-
expect(result.type).toBe(NODE_SELECTOR)
97+
expect(result.type).toBe(NODE_SELECTOR_LIST)
9898
expect(result.text).toBe('div span')
9999
expect(result.has_children).toBe(true)
100100
})
101101

102102
test('should parse child combinator', () => {
103103
const result = parse_selector('ul > li')
104104

105-
expect(result.type).toBe(NODE_SELECTOR)
105+
expect(result.type).toBe(NODE_SELECTOR_LIST)
106106
expect(result.text).toBe('ul > li')
107107
expect(result.has_children).toBe(true)
108108
})
109109

110110
test('should parse adjacent sibling combinator', () => {
111111
const result = parse_selector('h1 + p')
112112

113-
expect(result.type).toBe(NODE_SELECTOR)
113+
expect(result.type).toBe(NODE_SELECTOR_LIST)
114114
expect(result.text).toBe('h1 + p')
115115
expect(result.has_children).toBe(true)
116116
})
117117

118118
test('should parse general sibling combinator', () => {
119119
const result = parse_selector('h1 ~ p')
120120

121-
expect(result.type).toBe(NODE_SELECTOR)
121+
expect(result.type).toBe(NODE_SELECTOR_LIST)
122122
expect(result.text).toBe('h1 ~ p')
123123
expect(result.has_children).toBe(true)
124124
})
125125

126126
test('should parse modern pseudo-classes', () => {
127127
const result = parse_selector(':is(h1, h2, h3)')
128128

129-
expect(result.type).toBe(NODE_SELECTOR)
129+
expect(result.type).toBe(NODE_SELECTOR_LIST)
130130
expect(result.text).toBe(':is(h1, h2, h3)')
131131
expect(result.has_children).toBe(true)
132132
})
133133

134134
test('should parse :where() pseudo-class', () => {
135135
const result = parse_selector(':where(.a, .b)')
136136

137-
expect(result.type).toBe(NODE_SELECTOR)
137+
expect(result.type).toBe(NODE_SELECTOR_LIST)
138138
expect(result.text).toBe(':where(.a, .b)')
139139
expect(result.has_children).toBe(true)
140140
})
141141

142142
test('should parse :has() pseudo-class', () => {
143143
const result = parse_selector('div:has(> img)')
144144

145-
expect(result.type).toBe(NODE_SELECTOR)
145+
expect(result.type).toBe(NODE_SELECTOR_LIST)
146146
expect(result.text).toBe('div:has(> img)')
147147
expect(result.has_children).toBe(true)
148148
})
149149

150150
test('should parse empty selector', () => {
151151
const result = parse_selector('')
152152

153-
expect(result.type).toBe(NODE_SELECTOR)
153+
expect(result.type).toBe(NODE_SELECTOR_LIST)
154154
expect(result.text).toBe('')
155155
})
156156

src/parse-selector.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { CSSDataArena, NODE_SELECTOR } from './arena'
1+
import { CSSDataArena, NODE_SELECTOR_LIST } from './arena'
22
import { SelectorParser } from './selector-parser'
33
import { CSSNode } from './css-node'
44

@@ -18,9 +18,9 @@ export function parse_selector(source: string): CSSNode {
1818
const selector_index = selector_parser.parse_selector(0, source.length)
1919

2020
if (selector_index === null) {
21-
// Return empty selector node if parsing failed
21+
// Return empty selector list node if parsing failed
2222
const empty = arena.create_node()
23-
arena.set_type(empty, NODE_SELECTOR)
23+
arena.set_type(empty, NODE_SELECTOR_LIST)
2424
arena.set_start_offset(empty, 0)
2525
arena.set_length(empty, 0)
2626
arena.set_start_line(empty, 1)

src/parser-options.test.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, it, expect } from 'vitest'
22
import { Parser } from './parser'
3-
import { NODE_SELECTOR, NODE_DECLARATION, NODE_VALUE_KEYWORD } from './arena'
3+
import { NODE_SELECTOR_LIST, NODE_DECLARATION, NODE_VALUE_KEYWORD } from './arena'
44

55
describe('Parser Options', () => {
66
const css = 'body { color: red; }'
@@ -12,10 +12,10 @@ describe('Parser Options', () => {
1212
const rule = root.first_child
1313

1414
// Check selector is parsed with detailed structure
15-
// All selectors wrapped in NODE_SELECTOR
15+
// All selectors wrapped in NODE_SELECTOR_LIST
1616
const selector = rule?.first_child
1717
expect(selector).not.toBeNull()
18-
expect(selector?.type).toBe(NODE_SELECTOR)
18+
expect(selector?.type).toBe(NODE_SELECTOR_LIST)
1919

2020
// Check value is parsed with detailed structure
2121
const declaration = selector?.next_sibling
@@ -31,9 +31,9 @@ describe('Parser Options', () => {
3131
const rule = root.first_child
3232

3333
// Check selector is parsed
34-
// Simple selector (just "body") returns NODE_SELECTOR directly
34+
// Simple selector (just "body") returns NODE_SELECTOR_LIST directly
3535
const selector = rule?.first_child
36-
expect(selector?.type).toBe(NODE_SELECTOR)
36+
expect(selector?.type).toBe(NODE_SELECTOR_LIST)
3737

3838
// Check value is parsed
3939
const declaration = selector?.next_sibling
@@ -49,10 +49,10 @@ describe('Parser Options', () => {
4949
const rule = root.first_child
5050

5151
// Selector should still be parsed
52-
// Simple selector (just "body") returns NODE_SELECTOR directly
52+
// Simple selector (just "body") returns NODE_SELECTOR_LIST directly
5353
const selector = rule?.first_child
5454
expect(selector).not.toBeNull()
55-
expect(selector?.type).toBe(NODE_SELECTOR)
55+
expect(selector?.type).toBe(NODE_SELECTOR_LIST)
5656

5757
// Declaration should exist but have no value children
5858
const declaration = selector?.next_sibling
@@ -94,10 +94,10 @@ describe('Parser Options', () => {
9494
const root = parser.parse()
9595
const rule = root.first_child
9696

97-
// Selector should exist but be simple (just NODE_SELECTOR, no detailed structure)
97+
// Selector should exist but be simple (just NODE_SELECTOR_LIST, no detailed structure)
9898
const selector = rule?.first_child
9999
expect(selector).not.toBeNull()
100-
expect(selector?.type).toBe(NODE_SELECTOR)
100+
expect(selector?.type).toBe(NODE_SELECTOR_LIST)
101101
expect(selector?.text).toBe('body')
102102
expect(selector?.has_children).toBe(false) // No detailed selector nodes
103103

@@ -113,7 +113,7 @@ describe('Parser Options', () => {
113113
const rule = root.first_child
114114
const selector = rule?.first_child
115115

116-
expect(selector?.type).toBe(NODE_SELECTOR)
116+
expect(selector?.type).toBe(NODE_SELECTOR_LIST)
117117
expect(selector?.text).toBe('div.container#app')
118118
expect(selector?.has_children).toBe(false)
119119
})
@@ -124,7 +124,7 @@ describe('Parser Options', () => {
124124
const rule = root.first_child
125125
const selector = rule?.first_child
126126

127-
expect(selector?.type).toBe(NODE_SELECTOR)
127+
expect(selector?.type).toBe(NODE_SELECTOR_LIST)
128128
expect(selector?.text).toBe('div, p, span')
129129
expect(selector?.has_children).toBe(false)
130130
})
@@ -138,7 +138,7 @@ describe('Parser Options', () => {
138138

139139
// Selector should be simple
140140
const selector = rule?.first_child
141-
expect(selector?.type).toBe(NODE_SELECTOR)
141+
expect(selector?.type).toBe(NODE_SELECTOR_LIST)
142142
expect(selector?.text).toBe('body')
143143
expect(selector?.has_children).toBe(false)
144144

@@ -162,7 +162,7 @@ describe('Parser Options', () => {
162162
const rule = root.first_child
163163

164164
const selector = rule?.first_child
165-
expect(selector?.type).toBe(NODE_SELECTOR)
165+
expect(selector?.type).toBe(NODE_SELECTOR_LIST)
166166
expect(selector?.has_children).toBe(false)
167167

168168
const decl1 = selector?.next_sibling
@@ -234,7 +234,7 @@ describe('Parser Options', () => {
234234
const declaration = selector?.next_sibling
235235

236236
// Should use defaults (both enabled)
237-
expect(selector?.type).toBe(NODE_SELECTOR)
237+
expect(selector?.type).toBe(NODE_SELECTOR_LIST)
238238
expect(declaration?.has_children).toBe(true)
239239
})
240240

@@ -246,7 +246,7 @@ describe('Parser Options', () => {
246246
const declaration = selector?.next_sibling
247247

248248
// Selector should still be parsed (default true)
249-
expect(selector?.type).toBe(NODE_SELECTOR)
249+
expect(selector?.type).toBe(NODE_SELECTOR_LIST)
250250
// Values should not be parsed (explicitly false)
251251
expect(declaration?.has_children).toBe(false)
252252
})

src/parser.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
NODE_STYLESHEET,
66
NODE_STYLE_RULE,
77
NODE_SELECTOR,
8+
NODE_SELECTOR_LIST,
89
NODE_DECLARATION,
910
NODE_AT_RULE,
1011
FLAG_IMPORTANT,
@@ -237,9 +238,9 @@ export class Parser {
237238
}
238239
}
239240

240-
// Otherwise create a simple selector node with just text offsets
241+
// Otherwise create a simple selector list node with just text offsets
241242
let selector = this.arena.create_node()
242-
this.arena.set_type(selector, NODE_SELECTOR)
243+
this.arena.set_type(selector, NODE_SELECTOR_LIST)
243244
this.arena.set_start_line(selector, selector_line)
244245
this.arena.set_start_column(selector, selector_column)
245246
this.arena.set_start_offset(selector, selector_start)

0 commit comments

Comments
 (0)