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
59 changes: 32 additions & 27 deletions src/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ describe('CSSNode', () => {

test('should work for other node types that use value field', () => {
const source = 'body { color: red; }'
const root = parse(source)
const root = parse(source, { parse_values: false })
const rule = root.first_child!
const selector = rule.first_child!
const block = selector.next_sibling!
Expand Down Expand Up @@ -509,7 +509,7 @@ describe('CSSNode', () => {
const rule = root.first_child!
const block = rule.block!
const decl = block.first_child!
const value = decl.first_child!
const value = decl.first_child!.first_child!

expect(value.type_name).toBe('Identifier')
})
Expand All @@ -520,7 +520,7 @@ describe('CSSNode', () => {
const rule = root.first_child!
const block = rule.block!
const decl = block.first_child!
const value = decl.first_child!
const value = decl.first_child!.first_child!

expect(value.type_name).toBe('Number')
})
Expand All @@ -531,7 +531,7 @@ describe('CSSNode', () => {
const rule = root.first_child!
const block = rule.block!
const decl = block.first_child!
const value = decl.first_child!
const value = decl.first_child!.first_child!

expect(value.type_name).toBe('Dimension')
})
Expand All @@ -542,7 +542,7 @@ describe('CSSNode', () => {
const rule = root.first_child!
const block = rule.block!
const decl = block.first_child!
const value = decl.first_child!
const value = decl.first_child!.first_child!

expect(value.type_name).toBe('String')
})
Expand All @@ -553,7 +553,7 @@ describe('CSSNode', () => {
const rule = root.first_child!
const block = rule.block!
const decl = block.first_child!
const value = decl.first_child!
const value = decl.first_child!.first_child!

expect(value.type_name).toBe('Hash')
})
Expand All @@ -564,7 +564,7 @@ describe('CSSNode', () => {
const rule = root.first_child!
const block = rule.block!
const decl = block.first_child!
const value = decl.first_child!
const value = decl.first_child!.first_child!

expect(value.type_name).toBe('Function')
})
Expand Down Expand Up @@ -1028,12 +1028,13 @@ describe('CSSNode', () => {

const deep = decl.clone()

expect(deep.children.length).toBe(2)
expect(deep.children[0].type).toBe(DIMENSION)
expect(deep.children[0].value).toBe(10)
expect(deep.children[0].unit).toBe('px')
expect(deep.children[1].value).toBe(20)
expect(deep.children[1].unit).toBe('px')
expect(deep.children.length).toBe(1) // VALUE node
expect(deep.children[0].children.length).toBe(2)
expect(deep.children[0].children[0].type).toBe(DIMENSION)
expect(deep.children[0].children[0].value).toBe(10)
expect(deep.children[0].children[0].unit).toBe('px')
expect(deep.children[0].children[1].value).toBe(20)
expect(deep.children[0].children[1].unit).toBe('px')
})

test('collects multiple children correctly', () => {
Expand All @@ -1042,11 +1043,12 @@ describe('CSSNode', () => {

const clone = decl.clone()

expect(clone.children.length).toBe(4)
expect(clone.children[0].value).toBe(10)
expect(clone.children[1].value).toBe(20)
expect(clone.children[2].value).toBe(30)
expect(clone.children[3].value).toBe(40)
expect(clone.children.length).toBe(1) // VALUE node
expect(clone.children[0].children.length).toBe(4)
expect(clone.children[0].children[0].value).toBe(10)
expect(clone.children[0].children[1].value).toBe(20)
expect(clone.children[0].children[2].value).toBe(30)
expect(clone.children[0].children[3].value).toBe(40)
})

test('handles nested children', () => {
Expand All @@ -1055,11 +1057,12 @@ describe('CSSNode', () => {

const clone = decl.clone()

expect(clone.children.length).toBe(1)
expect(clone.children[0].type).toBe(FUNCTION)
expect(clone.children[0].name).toBe('calc')
expect(clone.children.length).toBe(1) // VALUE node
expect(clone.children[0].children.length).toBe(1)
expect(clone.children[0].children[0].type).toBe(FUNCTION)
expect(clone.children[0].children[0].name).toBe('calc')
// Function should have nested children
expect(clone.children[0].children.length).toBeGreaterThan(0)
expect(clone.children[0].children[0].children.length).toBeGreaterThan(0)
})
})

Expand Down Expand Up @@ -1093,7 +1096,7 @@ describe('CSSNode', () => {
test('extracts dimension value with unit', () => {
const ast = parse('div { width: 100px; }')
const decl = ast.first_child!.block!.first_child!
const dimension = decl.first_child!
const dimension = decl.first_child!.first_child!

const clone = dimension.clone({ deep: false })

Expand All @@ -1106,7 +1109,7 @@ describe('CSSNode', () => {
test('extracts number value', () => {
const ast = parse('div { opacity: 0.5; }')
const decl = ast.first_child!.block!.first_child!
const number = decl.first_child!
const number = decl.first_child!.first_child!

const clone = number.clone({ deep: false })

Expand Down Expand Up @@ -1213,10 +1216,12 @@ describe('CSSNode', () => {

const clone = decl.clone({ locations: true })

expect(clone.children[0].line).toBeDefined()
expect(clone.children[0].line).toBeDefined() // VALUE node
expect(clone.children[0].column).toBeDefined()
expect(clone.children[1].line).toBeDefined()
expect(clone.children[1].column).toBeDefined()
expect(clone.children[0].children[0].line).toBeDefined() // First dimension
expect(clone.children[0].children[0].column).toBeDefined()
expect(clone.children[0].children[1].line).toBeDefined() // Second dimension
expect(clone.children[0].children[1].column).toBeDefined()
})
})
})
Expand Down
5 changes: 4 additions & 1 deletion src/arena.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export const FUNCTION = 15 // function: calc(), var()
export const OPERATOR = 16 // operator: +, -, *, /, comma
export const PARENTHESIS = 17 // parenthesized expression: (100% - 50px)
export const URL = 18 // URL: url("file.css"), url(image.png), used in values and @import
export const VALUE = 19 // Wrapper for declaration values

// Selector node type constants (for detailed selector parsing)
export const SELECTOR_LIST = 20 // comma-separated selectors
Expand Down Expand Up @@ -125,7 +126,9 @@ export class CSSDataArena {
private static readonly GROWTH_FACTOR = 1.3

// Estimated nodes per KB of CSS (based on real-world data)
private static readonly NODES_PER_KB = 270
// Increased from 270 to 325 to account for VALUE wrapper nodes
// (~20% of nodes are declarations, +1 VALUE node per declaration = +20% nodes)
private static readonly NODES_PER_KB = 325

// Buffer to avoid frequent growth (15%)
private static readonly CAPACITY_BUFFER = 1.2
Expand Down
3 changes: 3 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
OPERATOR,
PARENTHESIS,
URL,
VALUE,
SELECTOR_LIST,
TYPE_SELECTOR,
CLASS_SELECTOR,
Expand Down Expand Up @@ -69,6 +70,7 @@ export {
OPERATOR,
PARENTHESIS,
URL,
VALUE,
SELECTOR_LIST,
TYPE_SELECTOR,
CLASS_SELECTOR,
Expand Down Expand Up @@ -123,6 +125,7 @@ export const NODE_TYPES = {
OPERATOR,
PARENTHESIS,
URL,
VALUE,
// Selector nodes
SELECTOR_LIST,
TYPE_SELECTOR,
Expand Down
24 changes: 10 additions & 14 deletions src/css-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
OPERATOR,
PARENTHESIS,
URL,
VALUE,
SELECTOR_LIST,
TYPE_SELECTOR,
CLASS_SELECTOR,
Expand Down Expand Up @@ -67,6 +68,7 @@ export const TYPE_NAMES = {
[OPERATOR]: 'Operator',
[PARENTHESIS]: 'Parentheses',
[URL]: 'Url',
[VALUE]: 'Value',
[SELECTOR_LIST]: 'SelectorList',
[TYPE_SELECTOR]: 'TypeSelector',
[CLASS_SELECTOR]: 'ClassSelector',
Expand Down Expand Up @@ -110,6 +112,7 @@ export type CSSNodeType =
| typeof OPERATOR
| typeof PARENTHESIS
| typeof URL
| typeof VALUE
| typeof SELECTOR_LIST
| typeof TYPE_SELECTOR
| typeof CLASS_SELECTOR
Expand Down Expand Up @@ -240,9 +243,15 @@ export class CSSNode {
* 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 {
get value(): CSSNode | string | number | null {
let { type, text } = this

// 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 && this.first_child) {
return this.first_child // VALUE node (when parse_values=true)
}

if (type === DIMENSION) {
return parse_dimension(text).value
}
Expand Down Expand Up @@ -427,19 +436,6 @@ export class CSSNode {
return true
}

// --- Value Node Access (for declarations) ---

/** Get array of parsed value nodes (for declarations only) */
get values(): CSSNode[] {
let result: CSSNode[] = []
let child = this.first_child
while (child) {
result.push(child)
child = child.next_sibling
}
return result
}

/** Get start line number */
get line(): number {
return this.arena.get_start_line(this.index)
Expand Down
1 change: 0 additions & 1 deletion src/parse-atrule-prelude.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
IDENTIFIER,
PRELUDE_OPERATOR,
URL,
FUNCTION,
DIMENSION,
FEATURE_RANGE,
} from './arena'
Expand Down
Loading