Skip to content
Merged
240 changes: 118 additions & 122 deletions API.md

Large diffs are not rendered by default.

30 changes: 15 additions & 15 deletions src/arena.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, test, expect } from 'vitest'
import { CSSDataArena, NODE_STYLESHEET, NODE_DECLARATION, FLAG_IMPORTANT, FLAG_HAS_ERROR } from './arena'
import { CSSDataArena, STYLESHEET, DECLARATION, FLAG_IMPORTANT, FLAG_HAS_ERROR } from './arena'

describe('CSSDataArena', () => {
describe('initialization', () => {
Expand Down Expand Up @@ -51,23 +51,23 @@ describe('CSSDataArena', () => {
const node2 = arena.create_node()

// Set data on existing nodes
arena.set_type(node1, NODE_STYLESHEET)
arena.set_type(node1, STYLESHEET)
arena.set_start_offset(node1, 100)
arena.set_type(node2, NODE_DECLARATION)
arena.set_type(node2, DECLARATION)
arena.set_start_offset(node2, 200)

// Trigger growth
const node3 = arena.create_node()

// Verify old data is preserved
expect(arena.get_type(node1)).toBe(NODE_STYLESHEET)
expect(arena.get_type(node1)).toBe(STYLESHEET)
expect(arena.get_start_offset(node1)).toBe(100)
expect(arena.get_type(node2)).toBe(NODE_DECLARATION)
expect(arena.get_type(node2)).toBe(DECLARATION)
expect(arena.get_start_offset(node2)).toBe(200)

// Verify new node can be used
arena.set_type(node3, NODE_STYLESHEET)
expect(arena.get_type(node3)).toBe(NODE_STYLESHEET)
arena.set_type(node3, STYLESHEET)
expect(arena.get_type(node3)).toBe(STYLESHEET)
})
})

Expand All @@ -86,8 +86,8 @@ describe('CSSDataArena', () => {
const arena = new CSSDataArena(10)
const node = arena.create_node()

arena.set_type(node, NODE_STYLESHEET)
expect(arena.get_type(node)).toBe(NODE_STYLESHEET)
arena.set_type(node, STYLESHEET)
expect(arena.get_type(node)).toBe(STYLESHEET)
})

test('should write and read node flags', () => {
Expand All @@ -102,15 +102,15 @@ describe('CSSDataArena', () => {
const arena = new CSSDataArena(10)
const node = arena.create_node()

arena.set_type(node, NODE_DECLARATION)
arena.set_type(node, DECLARATION)
arena.set_flags(node, FLAG_IMPORTANT)
arena.set_start_offset(node, 100)
arena.set_length(node, 50)
arena.set_content_start(node, 110)
arena.set_content_length(node, 30)
arena.set_start_line(node, 5)

expect(arena.get_type(node)).toBe(NODE_DECLARATION)
expect(arena.get_type(node)).toBe(DECLARATION)
expect(arena.get_flags(node)).toBe(FLAG_IMPORTANT)
expect(arena.get_start_offset(node)).toBe(100)
expect(arena.get_length(node)).toBe(50)
Expand All @@ -124,15 +124,15 @@ describe('CSSDataArena', () => {
const node1 = arena.create_node()
const node2 = arena.create_node()

arena.set_type(node1, NODE_STYLESHEET)
arena.set_type(node1, STYLESHEET)
arena.set_start_offset(node1, 0)

arena.set_type(node2, NODE_DECLARATION)
arena.set_type(node2, DECLARATION)
arena.set_start_offset(node2, 100)

expect(arena.get_type(node1)).toBe(NODE_STYLESHEET)
expect(arena.get_type(node1)).toBe(STYLESHEET)
expect(arena.get_start_offset(node1)).toBe(0)
expect(arena.get_type(node2)).toBe(NODE_DECLARATION)
expect(arena.get_type(node2)).toBe(DECLARATION)
expect(arena.get_start_offset(node2)).toBe(100)
})
})
Expand Down
75 changes: 36 additions & 39 deletions src/arena.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,51 +23,48 @@
let BYTES_PER_NODE = 40

// Node type constants
export const NODE_STYLESHEET = 1
export const NODE_STYLE_RULE = 2
export const NODE_AT_RULE = 3
export const NODE_DECLARATION = 4
export const NODE_SELECTOR = 5
export const NODE_COMMENT = 6
export const NODE_BLOCK = 7 // Block container for declarations and nested rules
export const STYLESHEET = 1
export const STYLE_RULE = 2
export const AT_RULE = 3
export const DECLARATION = 4
export const SELECTOR = 5
export const COMMENT = 6
export const BLOCK = 7 // Block container for declarations and nested rules

// Value node type constants (for declaration values)
export const NODE_VALUE_KEYWORD = 10 // identifier: red, auto, inherit
export const NODE_VALUE_NUMBER = 11 // number: 42, 3.14, -5
export const NODE_VALUE_DIMENSION = 12 // number with unit: 10px, 2em, 50%
export const NODE_VALUE_STRING = 13 // quoted string: "hello", 'world'
export const NODE_VALUE_COLOR = 14 // hex color: #fff, #ff0000
export const NODE_VALUE_FUNCTION = 15 // function: calc(), var(), url()
export const NODE_VALUE_OPERATOR = 16 // operator: +, -, *, /, comma
export const NODE_VALUE_PARENTHESIS = 17 // parenthesized expression: (100% - 50px)
export const IDENTIFIER = 10 // identifier: red, auto, inherit
export const NUMBER = 11 // number: 42, 3.14, -5
export const DIMENSION = 12 // number with unit: 10px, 2em, 50%
export const STRING = 13 // quoted string: "hello", 'world'
export const HASH = 14 // hex color: #fff, #ff0000
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

// Selector node type constants (for detailed selector parsing)
export const NODE_SELECTOR_LIST = 20 // comma-separated selectors
export const NODE_SELECTOR_TYPE = 21 // type selector: div, span, p
export const NODE_SELECTOR_CLASS = 22 // class selector: .classname
export const NODE_SELECTOR_ID = 23 // ID selector: #identifier
export const NODE_SELECTOR_ATTRIBUTE = 24 // attribute selector: [attr], [attr=value]
export const NODE_SELECTOR_PSEUDO_CLASS = 25 // pseudo-class: :hover, :nth-child()
export const NODE_SELECTOR_PSEUDO_ELEMENT = 26 // pseudo-element: ::before, ::after
export const NODE_SELECTOR_COMBINATOR = 27 // combinator: >, +, ~, space
export const NODE_SELECTOR_UNIVERSAL = 28 // universal selector: *
export const NODE_SELECTOR_NESTING = 29 // nesting selector: &
export const NODE_SELECTOR_NTH = 30 // An+B expression: 2n+1, odd, even
export const NODE_SELECTOR_NTH_OF = 31 // An+B with "of <selector>" syntax
export const NODE_SELECTOR_LANG = 56 // language identifier for :lang() pseudo-class
export const SELECTOR_LIST = 20 // comma-separated selectors
export const TYPE_SELECTOR = 21 // type selector: div, span, p
export const CLASS_SELECTOR = 22 // class selector: .classname
export const ID_SELECTOR = 23 // ID selector: #identifier
export const ATTRIBUTE_SELECTOR = 24 // attribute selector: [attr], [attr=value]
export const PSEUDO_CLASS_SELECTOR = 25 // pseudo-class: :hover, :nth-child()
export const PSEUDO_ELEMENT_SELECTOR = 26 // pseudo-element: ::before, ::after
export const COMBINATOR = 27 // combinator: >, +, ~, space
export const UNIVERSAL_SELECTOR = 28 // universal selector: *
export const NESTING_SELECTOR = 29 // nesting selector: &
export const NTH_SELECTOR = 30 // An+B expression: 2n+1, odd, even
export const NTH_OF_SELECTOR = 31 // An+B with "of <selector>" syntax
export const LANG_SELECTOR = 56 // language identifier for :lang() pseudo-class

// At-rule prelude node type constants (for at-rule prelude parsing)
export const NODE_PRELUDE_MEDIA_QUERY = 32 // media query: screen, (min-width: 768px)
export const NODE_PRELUDE_MEDIA_FEATURE = 33 // media feature: (min-width: 768px)
export const NODE_PRELUDE_MEDIA_TYPE = 34 // media type: screen, print, all
export const NODE_PRELUDE_CONTAINER_QUERY = 35 // container query: sidebar (min-width: 400px)
export const NODE_PRELUDE_SUPPORTS_QUERY = 36 // supports query: (display: flex)
export const NODE_PRELUDE_LAYER_NAME = 37 // layer name: base, components
export const NODE_PRELUDE_IDENTIFIER = 38 // generic identifier: keyframe name, property name
export const NODE_PRELUDE_OPERATOR = 39 // logical operator: and, or, not
export const NODE_PRELUDE_IMPORT_URL = 40 // import URL: url("file.css") or "file.css"
export const NODE_PRELUDE_IMPORT_LAYER = 41 // import layer: layer or layer(name)
export const NODE_PRELUDE_IMPORT_SUPPORTS = 42 // import supports: supports(condition)
export const MEDIA_QUERY = 32 // media query: screen, (min-width: 768px)
export const MEDIA_FEATURE = 33 // media feature: (min-width: 768px)
export const MEDIA_TYPE = 34 // media type: screen, print, all
export const CONTAINER_QUERY = 35 // container query: sidebar (min-width: 400px)
export const SUPPORTS_QUERY = 36 // supports query: (display: flex)
export const LAYER_NAME = 37 // layer name: base, components
export const PRELUDE_OPERATOR = 38 // logical operator: and, or, not

// Flag constants (bit-packed in 1 byte)
export const FLAG_IMPORTANT = 1 << 0 // Has !important
Expand Down
26 changes: 13 additions & 13 deletions src/column-tracking.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, test, expect } from 'vitest'
import { parse } from './parse'
import { NODE_STYLE_RULE, NODE_DECLARATION, NODE_AT_RULE, NODE_SELECTOR_LIST } from './constants'
import { STYLE_RULE, DECLARATION, AT_RULE, SELECTOR_LIST } from './constants'

describe('Column Tracking', () => {
test('should track column for single-line CSS', () => {
Expand All @@ -14,22 +14,22 @@ describe('Column Tracking', () => {
// First rule (body)
const rule = ast.first_child
expect(rule).not.toBeNull()
expect(rule!.type).toBe(NODE_STYLE_RULE)
expect(rule!.type).toBe(STYLE_RULE)
expect(rule!.line).toBe(1)
expect(rule!.column).toBe(1)

// Selector (body)
const selector = rule!.first_child
expect(selector).not.toBeNull()
expect(selector!.type).toBe(NODE_SELECTOR_LIST)
expect(selector!.type).toBe(SELECTOR_LIST)
expect(selector!.line).toBe(1)
expect(selector!.column).toBe(1)

// Declaration (color: red)
const block = selector!.next_sibling
const decl = block!.first_child
expect(decl).not.toBeNull()
expect(decl!.type).toBe(NODE_DECLARATION)
expect(decl!.type).toBe(DECLARATION)
expect(decl!.line).toBe(1)
expect(decl!.column).toBe(8)
})
Expand All @@ -47,13 +47,13 @@ describe('Column Tracking', () => {

// First declaration (color: red) at line 2, column 3
const decl1 = block.first_child!
expect(decl1.type).toBe(NODE_DECLARATION)
expect(decl1.type).toBe(DECLARATION)
expect(decl1.line).toBe(2)
expect(decl1.column).toBe(3)

// Second declaration (font-size: 16px) at line 3, column 3
const decl2 = decl1.next_sibling!
expect(decl2.type).toBe(NODE_DECLARATION)
expect(decl2.type).toBe(DECLARATION)
expect(decl2.line).toBe(3)
expect(decl2.column).toBe(3)
})
Expand All @@ -64,19 +64,19 @@ describe('Column Tracking', () => {

// At-rule should start at column 1
const atRule = ast.first_child!
expect(atRule.type).toBe(NODE_AT_RULE)
expect(atRule.type).toBe(AT_RULE)
expect(atRule.line).toBe(1)
expect(atRule.column).toBe(1)

// Get the block, then find the nested style rule
const block = atRule.block!
let nestedRule = block.first_child
while (nestedRule && nestedRule.type !== NODE_STYLE_RULE) {
while (nestedRule && nestedRule.type !== STYLE_RULE) {
nestedRule = nestedRule.next_sibling
}

expect(nestedRule).not.toBeNull()
expect(nestedRule!.type).toBe(NODE_STYLE_RULE)
expect(nestedRule!.type).toBe(STYLE_RULE)
expect(nestedRule!.line).toBe(1)
// Column 17 is where 'body' starts (beginning of selector)
expect(nestedRule!.column).toBe(17)
Expand All @@ -88,13 +88,13 @@ describe('Column Tracking', () => {

// First rule at column 1
const rule1 = ast.first_child!
expect(rule1.type).toBe(NODE_STYLE_RULE)
expect(rule1.type).toBe(STYLE_RULE)
expect(rule1.line).toBe(1)
expect(rule1.column).toBe(1)

// Second rule at column 19
const rule2 = rule1.next_sibling!
expect(rule2.type).toBe(NODE_STYLE_RULE)
expect(rule2.type).toBe(STYLE_RULE)
expect(rule2.line).toBe(1)
expect(rule2.column).toBe(19)
})
Expand All @@ -105,7 +105,7 @@ describe('Column Tracking', () => {

// Rule should start at column 5 (after 4 spaces)
const rule = ast.first_child!
expect(rule.type).toBe(NODE_STYLE_RULE)
expect(rule.type).toBe(STYLE_RULE)
expect(rule.line).toBe(1)
expect(rule.column).toBe(5)
})
Expand All @@ -117,7 +117,7 @@ describe('Column Tracking', () => {

// Rule should start at column 15 (after comment and space)
const rule = ast1.first_child!
expect(rule.type).toBe(NODE_STYLE_RULE)
expect(rule.type).toBe(STYLE_RULE)
expect(rule.line).toBe(1)
expect(rule.column).toBe(15)
})
Expand Down
75 changes: 36 additions & 39 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,42 @@
// This breaks the barrel file chain and improves tree-shaking

export {
NODE_STYLESHEET,
NODE_STYLE_RULE,
NODE_AT_RULE,
NODE_DECLARATION,
NODE_SELECTOR,
NODE_COMMENT,
NODE_BLOCK,
NODE_VALUE_KEYWORD,
NODE_VALUE_NUMBER,
NODE_VALUE_DIMENSION,
NODE_VALUE_STRING,
NODE_VALUE_COLOR,
NODE_VALUE_FUNCTION,
NODE_VALUE_OPERATOR,
NODE_VALUE_PARENTHESIS,
NODE_SELECTOR_LIST,
NODE_SELECTOR_TYPE,
NODE_SELECTOR_CLASS,
NODE_SELECTOR_ID,
NODE_SELECTOR_ATTRIBUTE,
NODE_SELECTOR_PSEUDO_CLASS,
NODE_SELECTOR_PSEUDO_ELEMENT,
NODE_SELECTOR_COMBINATOR,
NODE_SELECTOR_UNIVERSAL,
NODE_SELECTOR_NESTING,
NODE_SELECTOR_NTH,
NODE_SELECTOR_NTH_OF,
NODE_SELECTOR_LANG,
NODE_PRELUDE_MEDIA_QUERY,
NODE_PRELUDE_MEDIA_FEATURE,
NODE_PRELUDE_MEDIA_TYPE,
NODE_PRELUDE_CONTAINER_QUERY,
NODE_PRELUDE_SUPPORTS_QUERY,
NODE_PRELUDE_LAYER_NAME,
NODE_PRELUDE_IDENTIFIER,
NODE_PRELUDE_OPERATOR,
NODE_PRELUDE_IMPORT_URL,
NODE_PRELUDE_IMPORT_LAYER,
NODE_PRELUDE_IMPORT_SUPPORTS,
STYLESHEET,
STYLE_RULE,
AT_RULE,
DECLARATION,
SELECTOR,
COMMENT,
BLOCK,
IDENTIFIER,
NUMBER,
DIMENSION,
STRING,
HASH,
FUNCTION,
OPERATOR,
PARENTHESIS,
URL,
SELECTOR_LIST,
TYPE_SELECTOR,
CLASS_SELECTOR,
ID_SELECTOR,
ATTRIBUTE_SELECTOR,
PSEUDO_CLASS_SELECTOR,
PSEUDO_ELEMENT_SELECTOR,
COMBINATOR,
UNIVERSAL_SELECTOR,
NESTING_SELECTOR,
NTH_SELECTOR,
NTH_OF_SELECTOR,
LANG_SELECTOR,
MEDIA_QUERY,
MEDIA_FEATURE,
MEDIA_TYPE,
CONTAINER_QUERY,
SUPPORTS_QUERY,
LAYER_NAME,
PRELUDE_OPERATOR,
FLAG_IMPORTANT,
ATTR_OPERATOR_NONE,
ATTR_OPERATOR_EQUAL,
Expand Down
Loading
Loading