diff --git a/src/parse-atrule-prelude.test.ts b/src/parse-atrule-prelude.test.ts index e25888f..e401d9c 100644 --- a/src/parse-atrule-prelude.test.ts +++ b/src/parse-atrule-prelude.test.ts @@ -13,6 +13,7 @@ import { IDENTIFIER, PRELUDE_OPERATOR, URL, + FUNCTION, } from './arena' describe('At-Rule Prelude Nodes', () => { @@ -498,10 +499,74 @@ describe('At-Rule Prelude Nodes', () => { expect(children[0].type).toBe(CONTAINER_QUERY) - const queryChildren = children[0].children + const [ident, media_feature] = children[0].children // Should have name and feature - expect(queryChildren.some((c) => c.type === IDENTIFIER)).toBe(true) - expect(queryChildren.some((c) => c.type === MEDIA_FEATURE)).toBe(true) + expect(ident.type).toBe(IDENTIFIER) + expect(media_feature.type).toBe(MEDIA_FEATURE) + }) + + it('should parse style container query', () => { + const css = '@container style(--custom: 1) { }' + const ast = parse(css) + const atRule = ast.first_child + const children = atRule?.children || [] + + expect(children[0].type).toBe(CONTAINER_QUERY) + + const [fn] = children[0].children + expect(fn.type_name).toBe('Function') + expect(fn.text).toBe('style(--custom: 1)') + expect(fn.value).toBe('--custom: 1') + }) + + it('should parse named style container query', () => { + const css = '@container mytest style(--custom: 1) { }' + const ast = parse(css) + const atRule = ast.first_child + const children = atRule?.children || [] + + expect(children[0].type).toBe(CONTAINER_QUERY) + + const [ident, fn] = children[0].children + expect(ident.type_name).toBe('Identifier') + expect(ident.text).toBe('mytest') + expect(fn.type_name).toBe('Function') + expect(fn.text).toBe('style(--custom: 1)') + expect(fn.value).toBe('--custom: 1') + }) + + it('should handle a very complex container query', () => { + const css = `@container style(--themeBackground), + not style(background-color: red), + style(color: green) and style(background-color: transparent), + style(--themeColor: blue) or style(--themeColor: purple) { + /* */ + }` + const ast = parse(css) + const atRule = ast.first_child + const children = atRule?.children || [] + expect(children[0].type).toBe(CONTAINER_QUERY) + + const container = children[0] + const [style1, not1, style2, style3, and, style4, style5, or, style6] = container.children + expect(style1.type_name).toBe('Function') + expect(style1.name).toBe('style') + expect(not1.type_name).toBe('Operator') + expect(not1.text).toBe('not') + expect(style2.type_name).toBe('Function') + expect(style2.name).toBe('style') + expect(style3.type_name).toBe('Function') + expect(style3.name).toBe('style') + expect(and.type_name).toBe('Operator') + expect(and.text).toBe('and') + expect(style4.type_name).toBe('Function') + expect(style4.name).toBe('style') + expect(style5.type_name).toBe('Function') + expect(style5.name).toBe('style') + expect(or.type_name).toBe('Operator') + expect(or.text).toBe('or') + expect(style6.type_name).toBe('Function') + expect(style6.name).toBe('style') }) }) diff --git a/src/parse-atrule-prelude.ts b/src/parse-atrule-prelude.ts index 2440330..5bc943f 100644 --- a/src/parse-atrule-prelude.ts +++ b/src/parse-atrule-prelude.ts @@ -11,6 +11,7 @@ import { IDENTIFIER, PRELUDE_OPERATOR, URL, + FUNCTION, } from './arena' import { TOKEN_IDENT, @@ -244,6 +245,46 @@ export class AtRulePreludeParser { components.push(feature) } } + // Function: style(--custom: 1) + else if (token_type === TOKEN_FUNCTION) { + let func_name = this.source.substring(this.lexer.token_start, this.lexer.token_end - 1) // -1 to exclude '(' + + // For now, treat all functions (like style()) as FUNCTION nodes + let func_start = this.lexer.token_start + let content_start = this.lexer.token_end // After '(' + + // Find matching closing paren + let paren_depth = 1 + let func_end = this.lexer.token_end + let content_end = content_start + + while (this.lexer.pos < this.prelude_end && paren_depth > 0) { + this.next_token() + let inner_token = this.lexer.token_type + if (inner_token === TOKEN_LEFT_PAREN || inner_token === TOKEN_FUNCTION) { + paren_depth++ + } else if (inner_token === TOKEN_RIGHT_PAREN) { + paren_depth-- + if (paren_depth === 0) { + content_end = this.lexer.token_start + func_end = this.lexer.token_end + } + } else if (inner_token === TOKEN_EOF) { + break + } + } + + // Create function node + let func_node = this.create_node(FUNCTION, func_start, func_end) + // Set content fields to function name + this.arena.set_content_start_delta(func_node, 0) + this.arena.set_content_length(func_node, func_name.length) + // Set value fields to content inside parentheses + this.arena.set_value_start_delta(func_node, content_start - func_start) + this.arena.set_value_length(func_node, content_end - content_start) + + components.push(func_node) + } // Identifier: operator (and, or, not) or container name else if (token_type === TOKEN_IDENT) { let text = this.source.substring(this.lexer.token_start, this.lexer.token_end)