From cf3bfe8963d33840226431ac3735696a2fb54954 Mon Sep 17 00:00:00 2001 From: Bart Veneman Date: Sat, 3 Jan 2026 20:37:51 +0100 Subject: [PATCH] breaking: add structured prelude property to AT_RULE and STYLE_RULE --- src/api.test.ts | 18 +++++++++++++----- src/css-node.ts | 36 +++++++++++++++++++++++++----------- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/api.test.ts b/src/api.test.ts index 1f6c65b..728eb8f 100644 --- a/src/api.test.ts +++ b/src/api.test.ts @@ -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 }) @@ -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') }) }) diff --git a/src/css-node.ts b/src/css-node.ts index cea4d5a..38b30f6 100644 --- a/src/css-node.ts +++ b/src/css-node.ts @@ -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 @@ -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 } /** @@ -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 { } */