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
71 changes: 68 additions & 3 deletions src/parse-atrule-prelude.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
IDENTIFIER,
PRELUDE_OPERATOR,
URL,
FUNCTION,
} from './arena'

describe('At-Rule Prelude Nodes', () => {
Expand Down Expand Up @@ -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) {
/* <stylesheet> */
}`
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')
})
})

Expand Down
41 changes: 41 additions & 0 deletions src/parse-atrule-prelude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
IDENTIFIER,
PRELUDE_OPERATOR,
URL,
FUNCTION,
} from './arena'
import {
TOKEN_IDENT,
Expand Down Expand Up @@ -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)
Expand Down