diff --git a/API.md b/API.md index 32380c6..adcd3dd 100644 --- a/API.md +++ b/API.md @@ -36,7 +36,7 @@ function parse(source: string, options?: ParserOptions): CSSNode - `text` - Full text of the node from source - `name` - Property name, at-rule name, or layer name - `property` - Alias for `name` (for declarations) -- `value` - Value text (for declarations) or `null` +- `value` - Value text (for declarations), URL content (for URL nodes with quoted strings, includes quotes to match STRING node behavior), or `null` - `prelude` - At-rule prelude text or `null` - `line` - Starting line number (1-based) - `offset` - Starting offset in source @@ -174,6 +174,7 @@ console.log(importRule.has_children) // true (has prelude nodes) const [url, layer, supports, media] = importRule.children console.log(url.type) // URL console.log(url.text) // 'url("styles.css")' +console.log(url.value) // '"styles.css"' console.log(layer.type) // LAYER_NAME console.log(layer.name) // "base" diff --git a/src/css-node.ts b/src/css-node.ts index b53706b..be8ab71 100644 --- a/src/css-node.ts +++ b/src/css-node.ts @@ -226,7 +226,34 @@ export class CSSNode { // Get the value text (for declarations: "blue" in "color: blue") // For dimension/number nodes: returns the numeric value as a number // For string nodes: returns the string content without quotes + // For URL nodes with quoted string: returns the string with quotes (consistent with STRING node) + // For URL nodes with unquoted URL: returns the URL content without quotes get value(): string | number | null { + // Special handling for URL nodes + if (this.type === URL) { + const firstChild = this.first_child + if (firstChild && firstChild.type === STRING) { + // Return the string as-is (with quotes) - consistent with STRING node + return firstChild.text + } + // For URL nodes without children (e.g., @import url(...)), extract from text + // Handle both url("...") and url('...') and just "..." or '...' + const text = this.text + if (text.startsWith('url(')) { + // url("...") or url('...') or url(...) - extract content between parens + const openParen = text.indexOf('(') + const closeParen = text.lastIndexOf(')') + if (openParen !== -1 && closeParen !== -1 && closeParen > openParen) { + let content = text.substring(openParen + 1, closeParen).trim() + return content + } + } else if (text.startsWith('"') || text.startsWith("'")) { + // Just a quoted string: "..." or '...' + return text + } + // For unquoted URLs, fall through to value delta logic below + } + // For dimension and number nodes, parse and return as number if (this.type === DIMENSION || this.type === NUMBER) { return parse_dimension(this.text).value diff --git a/src/parse-atrule-prelude.test.ts b/src/parse-atrule-prelude.test.ts index 584f6c7..793b3ed 100644 --- a/src/parse-atrule-prelude.test.ts +++ b/src/parse-atrule-prelude.test.ts @@ -674,6 +674,30 @@ describe('At-Rule Prelude Nodes', () => { expect(children[0].text).toBe('"styles.css"') }) + + it('should have .value property for URL with quoted url() function', () => { + const css = '@import url("example.com");' + const ast = parse(css, { parse_atrule_preludes: true }) + const atRule = ast.first_child + const url = atRule?.children[0] + + expect(url?.type).toBe(URL) + expect(url?.text).toBe('url("example.com")') + // URL node in @import returns the content with quotes + expect(url?.value).toBe('"example.com"') + }) + + it('should have .value property for URL with quoted string', () => { + const css = '@import "example.com";' + const ast = parse(css, { parse_atrule_preludes: true }) + const atRule = ast.first_child + const url = atRule?.children[0] + + expect(url?.type).toBe(URL) + expect(url?.text).toBe('"example.com"') + // 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 }) diff --git a/src/parse-value.test.ts b/src/parse-value.test.ts index 02d2f6f..07125b5 100644 --- a/src/parse-value.test.ts +++ b/src/parse-value.test.ts @@ -547,6 +547,8 @@ describe('Value Node Types', () => { expect(decl?.values[0].children).toHaveLength(1) expect(decl?.values[0].children[0].type).toBe(STRING) expect(decl?.values[0].children[0].text).toBe('"image.png"') + // URL node with quoted string returns the string value with quotes + expect(decl?.values[0].value).toBe('"image.png"') }) it('should parse url() function with unquoted URL containing dots', () => { @@ -575,6 +577,20 @@ describe('Value Node Types', () => { expect(func?.value).toBe('myfont.woff2') }) + it('should parse url() function with single-quoted string', () => { + const root = parse("body { background: url('image.png'); }") + const decl = root.first_child?.first_child?.next_sibling?.first_child + + expect(decl?.values).toHaveLength(1) + expect(decl?.values[0].type).toBe(URL) + expect(decl?.values[0].name).toBe('url') + expect(decl?.values[0].children).toHaveLength(1) + expect(decl?.values[0].children[0].type).toBe(STRING) + expect(decl?.values[0].children[0].text).toBe("'image.png'") + // URL node with single-quoted string returns the string value with quotes + expect(decl?.values[0].value).toBe("'image.png'") + }) + it('should parse url() with base64 data URL', () => { const root = parse('body { background: url(data:image/png;base64,iVBORw0KGg); }') const decl = root.first_child?.first_child?.next_sibling?.first_child