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
15 changes: 15 additions & 0 deletions src/parse-atrule-prelude.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -985,6 +985,7 @@ describe('At-Rule Prelude Nodes', () => {
// URL node in @import returns the string with quotes
expect(url?.value).toBe('"example.com"')
})

it('should parse with anonymous layer', () => {
const css = '@import url("styles.css") layer;'
const ast = parse(css, { parse_atrule_preludes: true })
Expand Down Expand Up @@ -1177,6 +1178,20 @@ describe('At-Rule Prelude Nodes', () => {

expect(atRule?.prelude?.text).toBe('url("styles.css") layer(base) screen')
})

it('should parse unquoted URL that contains ;', () => {
const url = `https://fonts.googleapis.com/css2?family=Archivo:ital,wght@0,800;0,900;1,800&family=Roboto+Condensed:ital,wght@0,400;0,500;0,700;1,700&family=Roboto:ital,wght@0,300;0,400;0,500;0,700;0,900;1,300;1,400;1,500;1,700;1,900&display=swap`
const css = `@import url(${url});`
const ast = parse(css)
const atRule = ast.first_child!

// Prelude text should not include trailing semicolon
expect.soft(atRule.prelude?.text).toBe(`url(${url})`)
const url_node = atRule.prelude?.first_child
expect(url_node).not.toBeNull()
expect.soft(url_node?.type_name).toBe('Url')
expect.soft(url_node?.value).toBe(url)
})
})

describe('Length property correctness (regression tests for commit 5c6e2cd)', () => {
Expand Down
1 change: 1 addition & 0 deletions src/parse-declaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ export class DeclarationParser {
let has_important = false
let last_end = lexer.token_end
// Track parenthesis depth to handle semicolons inside functions (e.g., url(data:image/png;base64,...))
// NOTE: Same pattern exists in parse.ts for at-rule prelude parsing - keep in sync
let paren_depth = 0

// Process tokens until we hit semicolon, EOF, or end of input
Expand Down
16 changes: 15 additions & 1 deletion src/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
TOKEN_RIGHT_BRACKET,
TOKEN_COMMA,
TOKEN_COLON,
TOKEN_FUNCTION,
} from './token-types'
import { trim_boundaries } from './parse-utils'
import { CHAR_PERIOD, CHAR_GREATER_THAN, CHAR_PLUS, CHAR_TILDE, CHAR_AMPERSAND } from './string-utils'
Expand Down Expand Up @@ -346,11 +347,24 @@ export class Parser {
// Track prelude start and end
let prelude_start = this.lexer.token_start
let prelude_end = prelude_start
// Track parenthesis depth to handle semicolons inside functions (e.g., url(data:image/png;base64,...))
// NOTE: Same pattern exists in parse-declaration.ts for value parsing - keep in sync
let paren_depth = 0

// Parse prelude (everything before '{' or ';')
while (!this.is_eof()) {
let token_type = this.peek_type()
if (token_type === TOKEN_LEFT_BRACE || token_type === TOKEN_SEMICOLON) break

// Track parenthesis depth
if (token_type === TOKEN_LEFT_PAREN || token_type === TOKEN_FUNCTION) {
paren_depth++
} else if (token_type === TOKEN_RIGHT_PAREN) {
paren_depth--
}

// Only break on '{' or ';' when outside all parentheses
if (token_type === TOKEN_LEFT_BRACE && paren_depth === 0) break
if (token_type === TOKEN_SEMICOLON && paren_depth === 0) break
prelude_end = this.lexer.token_end
this.next_token()
}
Expand Down