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
9 changes: 7 additions & 2 deletions src/css-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,8 @@ export class CSSNode {
/** Get the "content" text (at-rule name for at-rules, layer name for import layers) */
get name(): string | undefined {
let { type } = this
if (type === DECLARATION || type === OPERATOR || type === SELECTOR) return
if (type === DECLARATION || type === OPERATOR || type === SELECTOR || type === MEDIA_FEATURE)
return
return this.get_content()
}

Expand All @@ -296,6 +297,10 @@ export class CSSNode {
get value(): CSSNode | string | number | null | undefined {
let { type, text, first_child } = this

if (type === AT_RULE) return undefined

if (type === MEDIA_TYPE) return text

// For DECLARATION nodes with parsed values, return the VALUE node
// For DECLARATION nodes without parsed values, fall through to get raw text
if (type === DECLARATION && first_child) {
Expand Down Expand Up @@ -446,7 +451,7 @@ export class CSSNode {
get has_prelude(): boolean {
let { type } = this
if (type === AT_RULE) {
return this.arena.get_value_length(this.index) > 0
return this.first_child !== null && this.first_child.type !== BLOCK
}
if (type === STYLE_RULE) {
return this.first_child !== null
Expand Down
43 changes: 28 additions & 15 deletions src/parse-atrule-prelude.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,26 +409,39 @@ describe('At-Rule Prelude Nodes', () => {
// First child should be a media query
expect(children[0].type).toBe(MEDIA_QUERY)

// Query should have a media type child
const queryChildren = children[0].children
expect(queryChildren.some((c) => c.type === MEDIA_TYPE)).toBe(true)
const mediaType = children[0].first_child!
expect(mediaType.type).toBe(MEDIA_TYPE)
expect(mediaType.value).toBe('screen')
})

it('should parse media feature', () => {
it('should parse media feature (min-width: 768px)', () => {
const css = '@media (min-width: 768px) { }'
const ast = parse(css)
const atRule = ast.first_child!
const children = atRule.prelude?.children || []
const prelude = atRule.prelude!
const query = prelude.first_child!

expect(children[0].type).toBe(MEDIA_QUERY)
expect(query.type).toBe(MEDIA_QUERY)

// Query should have a media feature child
const queryChildren = children[0].children
expect(queryChildren.some((c) => c.type === MEDIA_FEATURE)).toBe(true)
// Feature should have content
const feature = query.first_child
expect(feature?.property).toBe('min-width')
expect(feature?.name).toBeUndefined()
})

it('should parse media feature (hover)', () => {
const css = '@media (hover) { }'
const ast = parse(css)
const atRule = ast.first_child!
const prelude = atRule.prelude!
const query = prelude.first_child!

expect(query.type).toBe(MEDIA_QUERY)

// Feature should have content
const feature = queryChildren.find((c) => c.type === MEDIA_FEATURE)
expect(feature?.name).toBe('min-width')
const feature = query.first_child
expect(feature?.property).toBe('hover')
expect(feature?.name).toBeUndefined()
})

it('should trim whitespace and comments from media features', () => {
Expand All @@ -439,7 +452,7 @@ describe('At-Rule Prelude Nodes', () => {
const queryChildren = children[0].children
const feature = queryChildren.find((c) => c.type === MEDIA_FEATURE)

expect(feature?.name).toBe('min-width')
expect(feature?.property).toBe('min-width')
})

it('should parse complex media query with and operator', () => {
Expand Down Expand Up @@ -475,7 +488,7 @@ describe('At-Rule Prelude Nodes', () => {
const queryChildren = atRule.prelude?.children[0].children || []
const feature = queryChildren.find((c) => c.type === MEDIA_FEATURE)

expect(feature?.name).toBe('orientation')
expect(feature?.property).toBe('orientation')
expect(feature?.children.length).toBe(1)
expect(feature?.children[0].type).toBe(IDENTIFIER)
})
Expand All @@ -487,7 +500,7 @@ describe('At-Rule Prelude Nodes', () => {
const queryChildren = atRule.prelude?.children[0].children || []
const feature = queryChildren.find((c) => c.type === MEDIA_FEATURE)

expect(feature?.name).toBe('hover')
expect(feature?.property).toBe('hover')
})

it('should parse feature values as typed children', () => {
Expand All @@ -497,7 +510,7 @@ describe('At-Rule Prelude Nodes', () => {
const queryChildren = atRule.prelude?.children[0].children || []
const feature = queryChildren.find((c) => c.type === MEDIA_FEATURE)

expect(feature?.name).toBe('min-width')
expect(feature?.property).toBe('min-width')
expect(feature?.children.length).toBe(1)
expect(feature?.children[0].type).toBe(DIMENSION)
})
Expand Down
21 changes: 15 additions & 6 deletions src/parse.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1087,7 +1087,6 @@ describe('Core Nodes', () => {

let atrule = root.first_child!
expect(atrule.name).toBe('charset')
expect(atrule.value).toBe('"UTF-8"')
expect(atrule.prelude).not.toBeNull()
expect(atrule.prelude?.text).toBe('"UTF-8"')
})
Expand All @@ -1098,7 +1097,6 @@ describe('Core Nodes', () => {

let atrule = root.first_child!
expect(atrule.name).toBe('namespace')
expect(atrule.value).toBe('svg url(http://www.w3.org/2000/svg)')
// @namespace doesn't have structured prelude parsing, but prelude wrapper exists
expect(atrule.prelude).not.toBeNull()
expect(atrule.prelude?.text).toBe('svg url(http://www.w3.org/2000/svg)')
Expand All @@ -1110,7 +1108,6 @@ describe('Core Nodes', () => {

let atrule = root.first_child!
expect(atrule.name).toBe('imaginary-atrule')
expect(atrule.value).toBe('prelude-stuff')
// Unknown at-rules get a RAW prelude (not AT_RULE_PRELUDE)
expect(atrule.prelude).not.toBeNull()
expect(atrule.prelude?.type).toBe(RAW)
Expand Down Expand Up @@ -1151,6 +1148,21 @@ describe('Core Nodes', () => {
expect(rule.type).toBe(STYLE_RULE)
})

test('unknown at-rule with prelude block can contain style rules', () => {
let source = '@custom test { .a { color: red } }'
let root = parse(source)

let atrule = root.first_child!
let block = atrule.block!
let rule = block.first_child!
expect(rule.type).toBe(STYLE_RULE)

expect(atrule.value).toBeUndefined()

let prelude = atrule.prelude!
expect(prelude.type).toBe(RAW)
})

test('unknown at-rule block can contain nested at-rules', () => {
let source = '@custom { @media (width) { } }'
let root = parse(source)
Expand Down Expand Up @@ -1180,9 +1192,6 @@ describe('Core Nodes', () => {
let root = parse(source)

let atrule = root.first_child!
// value returns the raw string from arena, prelude returns the wrapper node
expect(typeof atrule.value).toBe('string')
expect(atrule.value).toBe('(min-width: 768px)')
expect(atrule.prelude?.text).toBe('(min-width: 768px)')
})

Expand Down
3 changes: 0 additions & 3 deletions src/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,9 +409,6 @@ export class Parser {
let trimmed = trim_boundaries(this.source, prelude_start, prelude_end)
let prelude_wrapper: number | null = null
if (trimmed) {
this.arena.set_value_start_delta(at_rule, trimmed[0] - at_rule_start)
this.arena.set_value_length(at_rule, trimmed[1] - trimmed[0])

// Create AT_RULE_PRELUDE wrapper if prelude parsing is enabled
if (this.prelude_parser) {
// Parse prelude and add structured nodes as children
Expand Down
Loading