diff --git a/src/css-node.ts b/src/css-node.ts index 2876127..b4194ec 100644 --- a/src/css-node.ts +++ b/src/css-node.ts @@ -272,7 +272,8 @@ export class CSSNode { /** Get the "content" text (at-rule name for at-rules, layer name for import layers) */ get name(): string | undefined { let { type } = this - if (type === DECLARATION || type === OPERATOR || type === SELECTOR) return + if (type === DECLARATION || type === OPERATOR || type === SELECTOR || type === MEDIA_FEATURE) + return return this.get_content() } @@ -296,6 +297,10 @@ export class CSSNode { get value(): CSSNode | string | number | null | undefined { let { type, text, first_child } = this + if (type === AT_RULE) return undefined + + if (type === MEDIA_TYPE) return text + // For DECLARATION nodes with parsed values, return the VALUE node // For DECLARATION nodes without parsed values, fall through to get raw text if (type === DECLARATION && first_child) { @@ -446,7 +451,7 @@ export class CSSNode { get has_prelude(): boolean { let { type } = this if (type === AT_RULE) { - return this.arena.get_value_length(this.index) > 0 + return this.first_child !== null && this.first_child.type !== BLOCK } if (type === STYLE_RULE) { return this.first_child !== null diff --git a/src/parse-atrule-prelude.test.ts b/src/parse-atrule-prelude.test.ts index c856b0a..3a46602 100644 --- a/src/parse-atrule-prelude.test.ts +++ b/src/parse-atrule-prelude.test.ts @@ -409,26 +409,39 @@ describe('At-Rule Prelude Nodes', () => { // First child should be a media query expect(children[0].type).toBe(MEDIA_QUERY) - // Query should have a media type child - const queryChildren = children[0].children - expect(queryChildren.some((c) => c.type === MEDIA_TYPE)).toBe(true) + const mediaType = children[0].first_child! + expect(mediaType.type).toBe(MEDIA_TYPE) + expect(mediaType.value).toBe('screen') }) - it('should parse media feature', () => { + it('should parse media feature (min-width: 768px)', () => { const css = '@media (min-width: 768px) { }' const ast = parse(css) const atRule = ast.first_child! - const children = atRule.prelude?.children || [] + const prelude = atRule.prelude! + const query = prelude.first_child! - expect(children[0].type).toBe(MEDIA_QUERY) + expect(query.type).toBe(MEDIA_QUERY) - // Query should have a media feature child - const queryChildren = children[0].children - expect(queryChildren.some((c) => c.type === MEDIA_FEATURE)).toBe(true) + // Feature should have content + const feature = query.first_child + expect(feature?.property).toBe('min-width') + expect(feature?.name).toBeUndefined() + }) + + it('should parse media feature (hover)', () => { + const css = '@media (hover) { }' + const ast = parse(css) + const atRule = ast.first_child! + const prelude = atRule.prelude! + const query = prelude.first_child! + + expect(query.type).toBe(MEDIA_QUERY) // Feature should have content - const feature = queryChildren.find((c) => c.type === MEDIA_FEATURE) - expect(feature?.name).toBe('min-width') + const feature = query.first_child + expect(feature?.property).toBe('hover') + expect(feature?.name).toBeUndefined() }) it('should trim whitespace and comments from media features', () => { @@ -439,7 +452,7 @@ describe('At-Rule Prelude Nodes', () => { const queryChildren = children[0].children const feature = queryChildren.find((c) => c.type === MEDIA_FEATURE) - expect(feature?.name).toBe('min-width') + expect(feature?.property).toBe('min-width') }) it('should parse complex media query with and operator', () => { @@ -475,7 +488,7 @@ describe('At-Rule Prelude Nodes', () => { const queryChildren = atRule.prelude?.children[0].children || [] const feature = queryChildren.find((c) => c.type === MEDIA_FEATURE) - expect(feature?.name).toBe('orientation') + expect(feature?.property).toBe('orientation') expect(feature?.children.length).toBe(1) expect(feature?.children[0].type).toBe(IDENTIFIER) }) @@ -487,7 +500,7 @@ describe('At-Rule Prelude Nodes', () => { const queryChildren = atRule.prelude?.children[0].children || [] const feature = queryChildren.find((c) => c.type === MEDIA_FEATURE) - expect(feature?.name).toBe('hover') + expect(feature?.property).toBe('hover') }) it('should parse feature values as typed children', () => { @@ -497,7 +510,7 @@ describe('At-Rule Prelude Nodes', () => { const queryChildren = atRule.prelude?.children[0].children || [] const feature = queryChildren.find((c) => c.type === MEDIA_FEATURE) - expect(feature?.name).toBe('min-width') + expect(feature?.property).toBe('min-width') expect(feature?.children.length).toBe(1) expect(feature?.children[0].type).toBe(DIMENSION) }) diff --git a/src/parse.test.ts b/src/parse.test.ts index 873ffea..44565e1 100644 --- a/src/parse.test.ts +++ b/src/parse.test.ts @@ -1087,7 +1087,6 @@ describe('Core Nodes', () => { let atrule = root.first_child! expect(atrule.name).toBe('charset') - expect(atrule.value).toBe('"UTF-8"') expect(atrule.prelude).not.toBeNull() expect(atrule.prelude?.text).toBe('"UTF-8"') }) @@ -1098,7 +1097,6 @@ describe('Core Nodes', () => { let atrule = root.first_child! expect(atrule.name).toBe('namespace') - expect(atrule.value).toBe('svg url(http://www.w3.org/2000/svg)') // @namespace doesn't have structured prelude parsing, but prelude wrapper exists expect(atrule.prelude).not.toBeNull() expect(atrule.prelude?.text).toBe('svg url(http://www.w3.org/2000/svg)') @@ -1110,7 +1108,6 @@ describe('Core Nodes', () => { let atrule = root.first_child! expect(atrule.name).toBe('imaginary-atrule') - expect(atrule.value).toBe('prelude-stuff') // Unknown at-rules get a RAW prelude (not AT_RULE_PRELUDE) expect(atrule.prelude).not.toBeNull() expect(atrule.prelude?.type).toBe(RAW) @@ -1151,6 +1148,21 @@ describe('Core Nodes', () => { expect(rule.type).toBe(STYLE_RULE) }) + test('unknown at-rule with prelude block can contain style rules', () => { + let source = '@custom test { .a { color: red } }' + let root = parse(source) + + let atrule = root.first_child! + let block = atrule.block! + let rule = block.first_child! + expect(rule.type).toBe(STYLE_RULE) + + expect(atrule.value).toBeUndefined() + + let prelude = atrule.prelude! + expect(prelude.type).toBe(RAW) + }) + test('unknown at-rule block can contain nested at-rules', () => { let source = '@custom { @media (width) { } }' let root = parse(source) @@ -1180,9 +1192,6 @@ describe('Core Nodes', () => { let root = parse(source) let atrule = root.first_child! - // value returns the raw string from arena, prelude returns the wrapper node - expect(typeof atrule.value).toBe('string') - expect(atrule.value).toBe('(min-width: 768px)') expect(atrule.prelude?.text).toBe('(min-width: 768px)') }) diff --git a/src/parse.ts b/src/parse.ts index 9e5bfbb..9f5e938 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -409,9 +409,6 @@ export class Parser { let trimmed = trim_boundaries(this.source, prelude_start, prelude_end) let prelude_wrapper: number | null = null if (trimmed) { - this.arena.set_value_start_delta(at_rule, trimmed[0] - at_rule_start) - this.arena.set_value_length(at_rule, trimmed[1] - trimmed[0]) - // Create AT_RULE_PRELUDE wrapper if prelude parsing is enabled if (this.prelude_parser) { // Parse prelude and add structured nodes as children