Skip to content

Commit 4287693

Browse files
authored
Tree structure (#6)
1 parent 2ba87c3 commit 4287693

24 files changed

+2695
-238
lines changed

API.md

Lines changed: 117 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ function parse(source: string, options?: ParserOptions): CSSNode
4646
- `has_prelude` - Whether at-rule has a prelude
4747
- `has_block` - Whether rule has a `{ }` block
4848
- `has_children` - Whether node has child nodes
49+
- `block` - Block node containing declarations/nested rules (for style rules and at-rules with blocks)
50+
- `is_empty` - Whether block has no declarations or rules (only comments allowed)
4951
- `first_child` - First child node or `null`
5052
- `next_sibling` - Next sibling node or `null`
5153
- `children` - Array of all child nodes
@@ -69,7 +71,12 @@ console.log(rule.has_block) // true
6971
const selector = rule.first_child
7072
console.log(selector.text) // "body"
7173

72-
const declaration = selector.next_sibling
74+
// Access block, then declaration inside it
75+
const block = rule.block
76+
console.log(block.type) // 7 (NODE_BLOCK)
77+
console.log(block.is_empty) // false
78+
79+
const declaration = block.first_child
7380
console.log(declaration.property) // "color"
7481
console.log(declaration.value) // "red"
7582
```
@@ -81,8 +88,9 @@ Stylesheet (NODE_STYLESHEET)
8188
└─ StyleRule (NODE_STYLE_RULE)
8289
├─ SelectorList (NODE_SELECTOR_LIST) "body"
8390
│ └─ Type (NODE_SELECTOR_TYPE) "body"
84-
└─ Declaration (NODE_DECLARATION) "color: red"
85-
└─ Keyword (NODE_VALUE_KEYWORD) "red"
91+
└─ Block (NODE_BLOCK)
92+
└─ Declaration (NODE_DECLARATION) "color: red"
93+
└─ Keyword (NODE_VALUE_KEYWORD) "red"
8694
```
8795

8896
### Example 2: Parsing with Options
@@ -97,8 +105,8 @@ const ast = parse('div { margin: 10px 20px; }', {
97105
})
98106

99107
const rule = ast.first_child
100-
const selector = rule.first_child
101-
const declaration = selector.next_sibling
108+
const block = rule.block
109+
const declaration = block.first_child
102110

103111
console.log(declaration.property) // "margin"
104112
console.log(declaration.value) // "10px 20px"
@@ -120,13 +128,15 @@ console.log(mediaRule.has_block) // true
120128
console.log(mediaRule.has_children) // true
121129

122130
// Access prelude nodes when parse_atrule_preludes is true
131+
// (Prelude nodes are first children, before the block)
123132
const mediaQuery = mediaRule.first_child
124133
console.log(mediaQuery.type) // NODE_PRELUDE_MEDIA_QUERY
125134
console.log(mediaQuery.text) // "(min-width: 768px)"
126135
console.log(mediaQuery.value) // "min-width: 768px" (without parentheses)
127136

128-
// Access block content
129-
for (const child of mediaRule) {
137+
// Access block content (nested rules/declarations)
138+
const block = mediaRule.block
139+
for (const child of block) {
130140
if (child.type === 2) {
131141
// NODE_STYLE_RULE
132142
console.log('Found style rule in media query')
@@ -218,6 +228,37 @@ const firstNode = ast2.first_child
218228
console.log(firstNode.type) // NODE_STYLE_RULE (comment skipped)
219229
```
220230

231+
### Example 7: Block Nodes and Empty Rules
232+
233+
```typescript
234+
import { parse } from '@projectwallace/css-parser'
235+
236+
// Empty rule
237+
const ast1 = parse('.empty { }')
238+
const rule1 = ast1.first_child
239+
console.log(rule1.has_block) // true
240+
console.log(rule1.block.is_empty) // true
241+
242+
// Rule with only comments
243+
const ast2 = parse('.comments { /* todo */ }', { skip_comments: false })
244+
const rule2 = ast2.first_child
245+
console.log(rule2.block.is_empty) // true (only comments)
246+
247+
// Rule with declarations
248+
const ast3 = parse('.filled { color: red; }')
249+
const rule3 = ast3.first_child
250+
console.log(rule3.block.is_empty) // false
251+
252+
// Nested rules inside blocks
253+
const ast4 = parse('.parent { .child { color: blue; } }')
254+
const parent = ast4.first_child
255+
const parentBlock = parent.block
256+
const nestedRule = parentBlock.first_child
257+
258+
console.log(nestedRule.type) // NODE_STYLE_RULE
259+
console.log(nestedRule.block.is_empty) // false
260+
```
261+
221262
---
222263

223264
## `parse_selector(source)`
@@ -294,8 +335,9 @@ walk(ast, (node, depth) => {
294335
// NODE_STYLE_RULE
295336
// NODE_SELECTOR_LIST
296337
// NODE_SELECTOR_TYPE
297-
// NODE_DECLARATION
298-
// NODE_VALUE_KEYWORD
338+
// NODE_BLOCK
339+
// NODE_DECLARATION
340+
// NODE_VALUE_KEYWORD
299341
```
300342

301343
---
@@ -328,3 +370,69 @@ for (const token of tokenize('body { color: red; }')) {
328370
// TOKEN_WHITESPACE " "
329371
// TOKEN_RIGHT_BRACE "}"
330372
```
373+
374+
---
375+
376+
## Node Type Constants
377+
378+
The parser uses numeric constants for node types. Import them from the parser:
379+
380+
```typescript
381+
import {
382+
NODE_STYLESHEET,
383+
NODE_STYLE_RULE,
384+
NODE_AT_RULE,
385+
NODE_DECLARATION,
386+
NODE_SELECTOR,
387+
NODE_COMMENT,
388+
NODE_BLOCK,
389+
// ... and more
390+
} from '@projectwallace/css-parser'
391+
```
392+
393+
### Core Node Types
394+
395+
- `NODE_STYLESHEET` (1) - Root stylesheet node
396+
- `NODE_STYLE_RULE` (2) - Style rule (e.g., `body { }`)
397+
- `NODE_AT_RULE` (3) - At-rule (e.g., `@media`, `@keyframes`)
398+
- `NODE_DECLARATION` (4) - Property declaration (e.g., `color: red`)
399+
- `NODE_SELECTOR` (5) - Selector wrapper (deprecated, use NODE_SELECTOR_LIST)
400+
- `NODE_COMMENT` (6) - CSS comment
401+
- `NODE_BLOCK` (7) - Block container for declarations and nested rules
402+
403+
### Value Node Types (10-16)
404+
405+
- `NODE_VALUE_KEYWORD` (10) - Keyword value (e.g., `red`, `auto`)
406+
- `NODE_VALUE_NUMBER` (11) - Number value (e.g., `42`, `3.14`)
407+
- `NODE_VALUE_DIMENSION` (12) - Dimension value (e.g., `10px`, `2em`, `50%`)
408+
- `NODE_VALUE_STRING` (13) - String value (e.g., `"hello"`)
409+
- `NODE_VALUE_COLOR` (14) - Hex color (e.g., `#fff`, `#ff0000`)
410+
- `NODE_VALUE_FUNCTION` (15) - Function (e.g., `calc()`, `var()`)
411+
- `NODE_VALUE_OPERATOR` (16) - Operator (e.g., `+`, `,`)
412+
413+
### Selector Node Types (20-29)
414+
415+
- `NODE_SELECTOR_LIST` (20) - Selector list container
416+
- `NODE_SELECTOR_TYPE` (21) - Type selector (e.g., `div`, `span`)
417+
- `NODE_SELECTOR_CLASS` (22) - Class selector (e.g., `.classname`)
418+
- `NODE_SELECTOR_ID` (23) - ID selector (e.g., `#identifier`)
419+
- `NODE_SELECTOR_ATTRIBUTE` (24) - Attribute selector (e.g., `[attr=value]`)
420+
- `NODE_SELECTOR_PSEUDO_CLASS` (25) - Pseudo-class (e.g., `:hover`)
421+
- `NODE_SELECTOR_PSEUDO_ELEMENT` (26) - Pseudo-element (e.g., `::before`)
422+
- `NODE_SELECTOR_COMBINATOR` (27) - Combinator (e.g., `>`, `+`, `~`, or ` `)
423+
- `NODE_SELECTOR_UNIVERSAL` (28) - Universal selector (`*`)
424+
- `NODE_SELECTOR_NESTING` (29) - Nesting selector (`&`)
425+
426+
### At-Rule Prelude Node Types (30-40)
427+
428+
- `NODE_PRELUDE_MEDIA_QUERY` (30) - Media query
429+
- `NODE_PRELUDE_MEDIA_FEATURE` (31) - Media feature
430+
- `NODE_PRELUDE_MEDIA_TYPE` (32) - Media type (e.g., `screen`, `print`)
431+
- `NODE_PRELUDE_CONTAINER_QUERY` (33) - Container query
432+
- `NODE_PRELUDE_SUPPORTS_QUERY` (34) - Supports query
433+
- `NODE_PRELUDE_LAYER_NAME` (35) - Layer name
434+
- `NODE_PRELUDE_IDENTIFIER` (36) - Generic identifier
435+
- `NODE_PRELUDE_OPERATOR` (37) - Logical operator (e.g., `and`, `or`)
436+
- `NODE_PRELUDE_IMPORT_URL` (38) - Import URL
437+
- `NODE_PRELUDE_IMPORT_LAYER` (39) - Import layer
438+
- `NODE_PRELUDE_IMPORT_SUPPORTS` (40) - Import supports condition

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@
3434
"./parse-atrule-prelude": {
3535
"types": "./dist/parse-atrule-prelude.d.ts",
3636
"import": "./dist/parse-atrule-prelude.js"
37+
},
38+
"./parse-anplusb": {
39+
"types": "./dist/parse-anplusb.d.ts",
40+
"import": "./dist/parse-anplusb.js"
3741
}
3842
},
3943
"files": [

0 commit comments

Comments
 (0)