Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions src/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,17 @@ describe('CSSNode', () => {
expect(hasPrelude).toBe(true)
})

test('should return true for style rules with selectors', () => {
const source = 'body { color: red; }'
const root = parse(source)
const rule = root.first_child!

expect(rule.type).toBe(STYLE_RULE)
expect(rule.has_prelude).toBe(true)
expect(rule.prelude).not.toBeNull()
expect(rule.prelude?.type_name).toBe('SelectorList')
})

test('should work for other node types that use value field', () => {
const source = 'body { color: red; }'
const root = parse(source, { parse_values: false })
Expand All @@ -193,12 +204,9 @@ describe('CSSNode', () => {
const declaration = block.first_child!

// Rules and selectors don't use value field
expect(rule.has_prelude).toBe(false)
expect(selector.has_prelude).toBe(false)
expect(rule.has_prelude).toBe(true) // has selector

// Declarations use value field for their value (same arena fields as prelude)
// So has_prelude returns true for declarations with values
expect(declaration.has_prelude).toBe(true)
expect(declaration.has_prelude).toBe(false)
expect(declaration.value).toBe('red')
})
})
Expand Down
36 changes: 25 additions & 11 deletions src/css-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ export type PlainCSSNode = {
property?: string
value?: string | number | null
unit?: string
prelude?: string
prelude?: PlainCSSNode | null

// Flags (only when true)
is_important?: boolean
Expand Down Expand Up @@ -307,16 +307,24 @@ export class CSSNode {
}

/**
* Get the prelude node (for at-rules: structured prelude with media queries, layer names, etc.)
* Returns the AT_RULE_PRELUDE wrapper node containing prelude children, or null if no prelude
* Get the prelude node:
* - For at-rules: AT_RULE_PRELUDE wrapper containing structured prelude children (media queries, layer names, etc.)
* - For style rules: SELECTOR_LIST or SELECTOR node
* Returns null if no prelude exists
*/
get prelude(): CSSNode | null {
if (this.type !== AT_RULE) return null
let first = this.first_child
if (first && first.type === AT_RULE_PRELUDE) {
return first
get prelude(): CSSNode | null | undefined {
if (this.type === AT_RULE) {
let first = this.first_child
if (first && first.type === AT_RULE_PRELUDE) {
return first
}
return null
}
return null
if (this.type === STYLE_RULE) {
// For style rules, prelude is the selector (first child)
return this.first_child
}
return undefined
}

/**
Expand Down Expand Up @@ -382,9 +390,15 @@ export class CSSNode {
return this.arena.has_flag(this.index, FLAG_HAS_ERROR)
}

/** Check if this at-rule has a prelude */
/** Check if this node has a prelude (at-rules and style rules) */
get has_prelude(): boolean {
return this.arena.get_value_length(this.index) > 0
if (this.type === AT_RULE) {
return this.arena.get_value_length(this.index) > 0
}
if (this.type === STYLE_RULE) {
return this.first_child !== null
}
return false
}

/** Check if this rule has a block { } */
Expand Down