Skip to content

Commit 1b71e93

Browse files
authored
fix: parse container prelude correctly (#94)
1 parent d239179 commit 1b71e93

File tree

2 files changed

+109
-3
lines changed

2 files changed

+109
-3
lines changed

src/parse-atrule-prelude.test.ts

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
IDENTIFIER,
1414
PRELUDE_OPERATOR,
1515
URL,
16+
FUNCTION,
1617
} from './arena'
1718

1819
describe('At-Rule Prelude Nodes', () => {
@@ -498,10 +499,74 @@ describe('At-Rule Prelude Nodes', () => {
498499

499500
expect(children[0].type).toBe(CONTAINER_QUERY)
500501

501-
const queryChildren = children[0].children
502+
const [ident, media_feature] = children[0].children
502503
// Should have name and feature
503-
expect(queryChildren.some((c) => c.type === IDENTIFIER)).toBe(true)
504-
expect(queryChildren.some((c) => c.type === MEDIA_FEATURE)).toBe(true)
504+
expect(ident.type).toBe(IDENTIFIER)
505+
expect(media_feature.type).toBe(MEDIA_FEATURE)
506+
})
507+
508+
it('should parse style container query', () => {
509+
const css = '@container style(--custom: 1) { }'
510+
const ast = parse(css)
511+
const atRule = ast.first_child
512+
const children = atRule?.children || []
513+
514+
expect(children[0].type).toBe(CONTAINER_QUERY)
515+
516+
const [fn] = children[0].children
517+
expect(fn.type_name).toBe('Function')
518+
expect(fn.text).toBe('style(--custom: 1)')
519+
expect(fn.value).toBe('--custom: 1')
520+
})
521+
522+
it('should parse named style container query', () => {
523+
const css = '@container mytest style(--custom: 1) { }'
524+
const ast = parse(css)
525+
const atRule = ast.first_child
526+
const children = atRule?.children || []
527+
528+
expect(children[0].type).toBe(CONTAINER_QUERY)
529+
530+
const [ident, fn] = children[0].children
531+
expect(ident.type_name).toBe('Identifier')
532+
expect(ident.text).toBe('mytest')
533+
expect(fn.type_name).toBe('Function')
534+
expect(fn.text).toBe('style(--custom: 1)')
535+
expect(fn.value).toBe('--custom: 1')
536+
})
537+
538+
it('should handle a very complex container query', () => {
539+
const css = `@container style(--themeBackground),
540+
not style(background-color: red),
541+
style(color: green) and style(background-color: transparent),
542+
style(--themeColor: blue) or style(--themeColor: purple) {
543+
/* <stylesheet> */
544+
}`
545+
const ast = parse(css)
546+
const atRule = ast.first_child
547+
const children = atRule?.children || []
548+
expect(children[0].type).toBe(CONTAINER_QUERY)
549+
550+
const container = children[0]
551+
const [style1, not1, style2, style3, and, style4, style5, or, style6] = container.children
552+
expect(style1.type_name).toBe('Function')
553+
expect(style1.name).toBe('style')
554+
expect(not1.type_name).toBe('Operator')
555+
expect(not1.text).toBe('not')
556+
expect(style2.type_name).toBe('Function')
557+
expect(style2.name).toBe('style')
558+
expect(style3.type_name).toBe('Function')
559+
expect(style3.name).toBe('style')
560+
expect(and.type_name).toBe('Operator')
561+
expect(and.text).toBe('and')
562+
expect(style4.type_name).toBe('Function')
563+
expect(style4.name).toBe('style')
564+
expect(style5.type_name).toBe('Function')
565+
expect(style5.name).toBe('style')
566+
expect(or.type_name).toBe('Operator')
567+
expect(or.text).toBe('or')
568+
expect(style6.type_name).toBe('Function')
569+
expect(style6.name).toBe('style')
505570
})
506571
})
507572

src/parse-atrule-prelude.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
IDENTIFIER,
1212
PRELUDE_OPERATOR,
1313
URL,
14+
FUNCTION,
1415
} from './arena'
1516
import {
1617
TOKEN_IDENT,
@@ -244,6 +245,46 @@ export class AtRulePreludeParser {
244245
components.push(feature)
245246
}
246247
}
248+
// Function: style(--custom: 1)
249+
else if (token_type === TOKEN_FUNCTION) {
250+
let func_name = this.source.substring(this.lexer.token_start, this.lexer.token_end - 1) // -1 to exclude '('
251+
252+
// For now, treat all functions (like style()) as FUNCTION nodes
253+
let func_start = this.lexer.token_start
254+
let content_start = this.lexer.token_end // After '('
255+
256+
// Find matching closing paren
257+
let paren_depth = 1
258+
let func_end = this.lexer.token_end
259+
let content_end = content_start
260+
261+
while (this.lexer.pos < this.prelude_end && paren_depth > 0) {
262+
this.next_token()
263+
let inner_token = this.lexer.token_type
264+
if (inner_token === TOKEN_LEFT_PAREN || inner_token === TOKEN_FUNCTION) {
265+
paren_depth++
266+
} else if (inner_token === TOKEN_RIGHT_PAREN) {
267+
paren_depth--
268+
if (paren_depth === 0) {
269+
content_end = this.lexer.token_start
270+
func_end = this.lexer.token_end
271+
}
272+
} else if (inner_token === TOKEN_EOF) {
273+
break
274+
}
275+
}
276+
277+
// Create function node
278+
let func_node = this.create_node(FUNCTION, func_start, func_end)
279+
// Set content fields to function name
280+
this.arena.set_content_start_delta(func_node, 0)
281+
this.arena.set_content_length(func_node, func_name.length)
282+
// Set value fields to content inside parentheses
283+
this.arena.set_value_start_delta(func_node, content_start - func_start)
284+
this.arena.set_value_length(func_node, content_end - content_start)
285+
286+
components.push(func_node)
287+
}
247288
// Identifier: operator (and, or, not) or container name
248289
else if (token_type === TOKEN_IDENT) {
249290
let text = this.source.substring(this.lexer.token_start, this.lexer.token_end)

0 commit comments

Comments
 (0)