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
44 changes: 43 additions & 1 deletion src/parse-atrule-prelude.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -976,6 +976,23 @@ describe('At-Rule Prelude Nodes', () => {
expect(children[2].text).toBe('utilities')
expect((children[2] as LayerName).value).toBe('utilities')
})

test('should keep dotted layer names intact among comma-separated names', () => {
const css = '@layer a.b, c;'
const ast = parse(css)
const atRule = ast.first_child! as Atrule

const children = (atRule.prelude as AtrulePrelude | null)?.children || []
expect(children.length).toBe(2)

expect(children[0].type).toBe(LAYER_NAME)
expect(children[0].text).toBe('a.b')
expect((children[0] as LayerName).value).toBe('a.b')

expect(children[1].type).toBe(LAYER_NAME)
expect(children[1].text).toBe('c')
expect((children[1] as LayerName).value).toBe('c')
})
})

describe('@keyframes', () => {
Expand Down Expand Up @@ -1969,7 +1986,32 @@ describe('parse_atrule_prelude()', () => {
test('should parse dotted layer name', () => {
const result = parse_atrule_prelude('layer', 'framework.base')

expect(result.length).toBeGreaterThan(0)
expect(result.length).toBe(1)
expect(result[0].type).toBe(LAYER_NAME)
expect(result[0].text).toBe('framework.base')
})

test('should parse a nested dotted layer name mixed with a simple one', () => {
const result = parse_atrule_prelude('layer', 'a, b.c')

expect(result.length).toBe(2)
expect(result[0].type).toBe(LAYER_NAME)
expect(result[0].text).toBe('a')
expect(result[1].type).toBe(LAYER_NAME)
expect(result[1].text).toBe('b.c')
})

test('should parse deeply nested dotted layer names', () => {
const result = parse_atrule_prelude('layer', 'a, b.c, d.e.f')

expect(result.length).toBe(3)
expect(result.map((n) => n.text)).toEqual(['a', 'b.c', 'd.e.f'])
})

test('should not glue a dot separated by whitespace onto the layer name', () => {
const result = parse_atrule_prelude('layer', 'a. b')

expect(result.map((n) => n.text)).toEqual(['a', 'b'])
})
})

Expand Down
37 changes: 34 additions & 3 deletions src/parse-atrule-prelude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
TOKEN_NUMBER,
TOKEN_PERCENTAGE,
TOKEN_DIMENSION,
TOKEN_DELIM,
type TokenType,
} from './token-types'
import {
Expand All @@ -44,6 +45,7 @@ import {
CHAR_LESS_THAN,
CHAR_GREATER_THAN,
CHAR_EQUALS,
CHAR_PERIOD,
} from './string-utils'
import { trim_boundaries, skip_whitespace_and_comments_forward } from './parse-utils'
import { CSSNode } from './css-node'
Expand Down Expand Up @@ -553,6 +555,8 @@ export class AtRulePreludeParser {
}

// Parse layer names: base, components, utilities
// A single name may be dotted for nested layers: base.normalize
// <layer-name> = <ident> ['.' <ident>]* with no whitespace around the dots.
private parse_layer_names(): number[] {
let nodes: number[] = []

Expand All @@ -564,10 +568,37 @@ export class AtRulePreludeParser {

let token_type = this.lexer.token_type
if (token_type === TOKEN_IDENT) {
// Layer name
let layer = this.create_node(LAYER_NAME, this.lexer.token_start, this.lexer.token_end)
let name_start = this.lexer.token_start
let name_end = this.lexer.token_end

// Glue on '.' ident segments immediately following, with no gaps.
while (this.lexer.pos < this.prelude_end) {
let saved = this.lexer.save_position()

let dot_token_type = this.next_token()
if (
dot_token_type !== TOKEN_DELIM ||
this.source.charCodeAt(this.lexer.token_start) !== CHAR_PERIOD ||
this.lexer.token_start !== name_end
) {
this.lexer.restore_position(saved)
break
}
let dot_end = this.lexer.token_end

let segment_token_type = this.next_token()
if (segment_token_type !== TOKEN_IDENT || this.lexer.token_start !== dot_end) {
this.lexer.restore_position(saved)
break
}

name_end = this.lexer.token_end
}

// Layer name (possibly dotted)
let layer = this.create_node(LAYER_NAME, name_start, name_end)
this.arena.set_content_start_delta(layer, 0)
this.arena.set_content_length(layer, this.lexer.token_end - this.lexer.token_start)
this.arena.set_content_length(layer, name_end - name_start)
nodes.push(layer)
} else if (token_type === TOKEN_COMMA) {
// Skip comma separator
Expand Down
Loading