Skip to content

Commit aa9aad2

Browse files
authored
fix: URL-node .value should be the contents of url() (#49)
1 parent a2245d1 commit aa9aad2

File tree

4 files changed

+69
-1
lines changed

4 files changed

+69
-1
lines changed

API.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ function parse(source: string, options?: ParserOptions): CSSNode
3636
- `text` - Full text of the node from source
3737
- `name` - Property name, at-rule name, or layer name
3838
- `property` - Alias for `name` (for declarations)
39-
- `value` - Value text (for declarations) or `null`
39+
- `value` - Value text (for declarations), URL content (for URL nodes with quoted strings, includes quotes to match STRING node behavior), or `null`
4040
- `prelude` - At-rule prelude text or `null`
4141
- `line` - Starting line number (1-based)
4242
- `offset` - Starting offset in source
@@ -174,6 +174,7 @@ console.log(importRule.has_children) // true (has prelude nodes)
174174
const [url, layer, supports, media] = importRule.children
175175
console.log(url.type) // URL
176176
console.log(url.text) // 'url("styles.css")'
177+
console.log(url.value) // '"styles.css"'
177178

178179
console.log(layer.type) // LAYER_NAME
179180
console.log(layer.name) // "base"

src/css-node.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,34 @@ export class CSSNode {
226226
// Get the value text (for declarations: "blue" in "color: blue")
227227
// For dimension/number nodes: returns the numeric value as a number
228228
// For string nodes: returns the string content without quotes
229+
// For URL nodes with quoted string: returns the string with quotes (consistent with STRING node)
230+
// For URL nodes with unquoted URL: returns the URL content without quotes
229231
get value(): string | number | null {
232+
// Special handling for URL nodes
233+
if (this.type === URL) {
234+
const firstChild = this.first_child
235+
if (firstChild && firstChild.type === STRING) {
236+
// Return the string as-is (with quotes) - consistent with STRING node
237+
return firstChild.text
238+
}
239+
// For URL nodes without children (e.g., @import url(...)), extract from text
240+
// Handle both url("...") and url('...') and just "..." or '...'
241+
const text = this.text
242+
if (text.startsWith('url(')) {
243+
// url("...") or url('...') or url(...) - extract content between parens
244+
const openParen = text.indexOf('(')
245+
const closeParen = text.lastIndexOf(')')
246+
if (openParen !== -1 && closeParen !== -1 && closeParen > openParen) {
247+
let content = text.substring(openParen + 1, closeParen).trim()
248+
return content
249+
}
250+
} else if (text.startsWith('"') || text.startsWith("'")) {
251+
// Just a quoted string: "..." or '...'
252+
return text
253+
}
254+
// For unquoted URLs, fall through to value delta logic below
255+
}
256+
230257
// For dimension and number nodes, parse and return as number
231258
if (this.type === DIMENSION || this.type === NUMBER) {
232259
return parse_dimension(this.text).value

src/parse-atrule-prelude.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,30 @@ describe('At-Rule Prelude Nodes', () => {
674674
expect(children[0].text).toBe('"styles.css"')
675675
})
676676

677+
678+
it('should have .value property for URL with quoted url() function', () => {
679+
const css = '@import url("example.com");'
680+
const ast = parse(css, { parse_atrule_preludes: true })
681+
const atRule = ast.first_child
682+
const url = atRule?.children[0]
683+
684+
expect(url?.type).toBe(URL)
685+
expect(url?.text).toBe('url("example.com")')
686+
// URL node in @import returns the content with quotes
687+
expect(url?.value).toBe('"example.com"')
688+
})
689+
690+
it('should have .value property for URL with quoted string', () => {
691+
const css = '@import "example.com";'
692+
const ast = parse(css, { parse_atrule_preludes: true })
693+
const atRule = ast.first_child
694+
const url = atRule?.children[0]
695+
696+
expect(url?.type).toBe(URL)
697+
expect(url?.text).toBe('"example.com"')
698+
// URL node in @import returns the string with quotes
699+
expect(url?.value).toBe('"example.com"')
700+
})
677701
it('should parse with anonymous layer', () => {
678702
const css = '@import url("styles.css") layer;'
679703
const ast = parse(css, { parse_atrule_preludes: true })

src/parse-value.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,8 @@ describe('Value Node Types', () => {
547547
expect(decl?.values[0].children).toHaveLength(1)
548548
expect(decl?.values[0].children[0].type).toBe(STRING)
549549
expect(decl?.values[0].children[0].text).toBe('"image.png"')
550+
// URL node with quoted string returns the string value with quotes
551+
expect(decl?.values[0].value).toBe('"image.png"')
550552
})
551553

552554
it('should parse url() function with unquoted URL containing dots', () => {
@@ -575,6 +577,20 @@ describe('Value Node Types', () => {
575577
expect(func?.value).toBe('myfont.woff2')
576578
})
577579

580+
it('should parse url() function with single-quoted string', () => {
581+
const root = parse("body { background: url('image.png'); }")
582+
const decl = root.first_child?.first_child?.next_sibling?.first_child
583+
584+
expect(decl?.values).toHaveLength(1)
585+
expect(decl?.values[0].type).toBe(URL)
586+
expect(decl?.values[0].name).toBe('url')
587+
expect(decl?.values[0].children).toHaveLength(1)
588+
expect(decl?.values[0].children[0].type).toBe(STRING)
589+
expect(decl?.values[0].children[0].text).toBe("'image.png'")
590+
// URL node with single-quoted string returns the string value with quotes
591+
expect(decl?.values[0].value).toBe("'image.png'")
592+
})
593+
578594
it('should parse url() with base64 data URL', () => {
579595
const root = parse('body { background: url(data:image/png;base64,iVBORw0KGg); }')
580596
const decl = root.first_child?.first_child?.next_sibling?.first_child

0 commit comments

Comments
 (0)