@projectwallace/css-parser 0.7.3 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/arena.js CHANGED
@@ -1,4 +1,4 @@
1
- let BYTES_PER_NODE = 40;
1
+ let BYTES_PER_NODE = 32;
2
2
  const STYLESHEET = 1;
3
3
  const STYLE_RULE = 2;
4
4
  const AT_RULE = 3;
@@ -38,7 +38,6 @@ const PRELUDE_OPERATOR = 38;
38
38
  const FLAG_IMPORTANT = 1 << 0;
39
39
  const FLAG_HAS_ERROR = 1 << 1;
40
40
  const FLAG_HAS_BLOCK = 1 << 3;
41
- const FLAG_VENDOR_PREFIXED = 1 << 4;
42
41
  const FLAG_HAS_DECLARATIONS = 1 << 5;
43
42
  const FLAG_HAS_PARENS = 1 << 6;
44
43
  const ATTR_OPERATOR_NONE = 0;
@@ -99,59 +98,55 @@ class CSSDataArena {
99
98
  }
100
99
  // Read start offset in source
101
100
  get_start_offset(node_index) {
102
- return this.view.getUint32(this.node_offset(node_index) + 4, true);
101
+ return this.view.getUint32(this.node_offset(node_index) + 12, true);
103
102
  }
104
103
  // Read length in source
105
104
  get_length(node_index) {
106
- return this.view.getUint16(this.node_offset(node_index) + 8, true);
105
+ return this.view.getUint16(this.node_offset(node_index) + 2, true);
107
106
  }
108
107
  // Read content start offset (stored as delta from startOffset)
109
108
  get_content_start(node_index) {
110
109
  const startOffset = this.get_start_offset(node_index);
111
- const delta = this.view.getUint16(this.node_offset(node_index) + 12, true);
110
+ const delta = this.view.getUint16(this.node_offset(node_index) + 16, true);
112
111
  return startOffset + delta;
113
112
  }
114
113
  // Read content length
115
114
  get_content_length(node_index) {
116
- return this.view.getUint16(this.node_offset(node_index) + 14, true);
115
+ return this.view.getUint16(this.node_offset(node_index) + 20, true);
117
116
  }
118
117
  // Read attribute operator (for NODE_SELECTOR_ATTRIBUTE)
119
118
  get_attr_operator(node_index) {
120
- return this.view.getUint8(this.node_offset(node_index) + 2);
119
+ return this.view.getUint8(this.node_offset(node_index) + 30);
121
120
  }
122
121
  // Read attribute flags (for NODE_SELECTOR_ATTRIBUTE)
123
122
  get_attr_flags(node_index) {
124
- return this.view.getUint8(this.node_offset(node_index) + 3);
123
+ return this.view.getUint8(this.node_offset(node_index) + 31);
125
124
  }
126
125
  // Read first child index (0 = no children)
127
126
  get_first_child(node_index) {
128
- return this.view.getUint32(this.node_offset(node_index) + 20, true);
129
- }
130
- // Read last child index (0 = no children)
131
- get_last_child(node_index) {
132
- return this.view.getUint32(this.node_offset(node_index) + 24, true);
127
+ return this.view.getUint32(this.node_offset(node_index) + 4, true);
133
128
  }
134
129
  // Read next sibling index (0 = no sibling)
135
130
  get_next_sibling(node_index) {
136
- return this.view.getUint32(this.node_offset(node_index) + 28, true);
131
+ return this.view.getUint32(this.node_offset(node_index) + 8, true);
137
132
  }
138
133
  // Read start line
139
134
  get_start_line(node_index) {
140
- return this.view.getUint32(this.node_offset(node_index) + 32, true);
135
+ return this.view.getUint32(this.node_offset(node_index) + 24, true);
141
136
  }
142
137
  // Read start column
143
138
  get_start_column(node_index) {
144
- return this.view.getUint16(this.node_offset(node_index) + 36, true);
139
+ return this.view.getUint16(this.node_offset(node_index) + 28, true);
145
140
  }
146
141
  // Read value start offset (stored as delta from startOffset, declaration value / at-rule prelude)
147
142
  get_value_start(node_index) {
148
143
  const startOffset = this.get_start_offset(node_index);
149
- const delta = this.view.getUint16(this.node_offset(node_index) + 16, true);
144
+ const delta = this.view.getUint16(this.node_offset(node_index) + 18, true);
150
145
  return startOffset + delta;
151
146
  }
152
147
  // Read value length
153
148
  get_value_length(node_index) {
154
- return this.view.getUint16(this.node_offset(node_index) + 18, true);
149
+ return this.view.getUint16(this.node_offset(node_index) + 22, true);
155
150
  }
156
151
  // --- Write Methods ---
157
152
  // Write node type
@@ -164,55 +159,51 @@ class CSSDataArena {
164
159
  }
165
160
  // Write start offset in source
166
161
  set_start_offset(node_index, offset) {
167
- this.view.setUint32(this.node_offset(node_index) + 4, offset, true);
162
+ this.view.setUint32(this.node_offset(node_index) + 12, offset, true);
168
163
  }
169
164
  // Write length in source
170
165
  set_length(node_index, length) {
171
- this.view.setUint16(this.node_offset(node_index) + 8, length, true);
166
+ this.view.setUint16(this.node_offset(node_index) + 2, length, true);
172
167
  }
173
168
  // Write content start delta (offset from startOffset)
174
169
  set_content_start_delta(node_index, delta) {
175
- this.view.setUint16(this.node_offset(node_index) + 12, delta, true);
170
+ this.view.setUint16(this.node_offset(node_index) + 16, delta, true);
176
171
  }
177
172
  // Write content length
178
173
  set_content_length(node_index, length) {
179
- this.view.setUint16(this.node_offset(node_index) + 14, length, true);
174
+ this.view.setUint16(this.node_offset(node_index) + 20, length, true);
180
175
  }
181
176
  // Write attribute operator (for NODE_SELECTOR_ATTRIBUTE)
182
177
  set_attr_operator(node_index, operator) {
183
- this.view.setUint8(this.node_offset(node_index) + 2, operator);
178
+ this.view.setUint8(this.node_offset(node_index) + 30, operator);
184
179
  }
185
180
  // Write attribute flags (for NODE_SELECTOR_ATTRIBUTE)
186
181
  set_attr_flags(node_index, flags) {
187
- this.view.setUint8(this.node_offset(node_index) + 3, flags);
182
+ this.view.setUint8(this.node_offset(node_index) + 31, flags);
188
183
  }
189
184
  // Write first child index
190
185
  set_first_child(node_index, childIndex) {
191
- this.view.setUint32(this.node_offset(node_index) + 20, childIndex, true);
192
- }
193
- // Write last child index
194
- set_last_child(node_index, childIndex) {
195
- this.view.setUint32(this.node_offset(node_index) + 24, childIndex, true);
186
+ this.view.setUint32(this.node_offset(node_index) + 4, childIndex, true);
196
187
  }
197
188
  // Write next sibling index
198
189
  set_next_sibling(node_index, siblingIndex) {
199
- this.view.setUint32(this.node_offset(node_index) + 28, siblingIndex, true);
190
+ this.view.setUint32(this.node_offset(node_index) + 8, siblingIndex, true);
200
191
  }
201
192
  // Write start line
202
193
  set_start_line(node_index, line) {
203
- this.view.setUint32(this.node_offset(node_index) + 32, line, true);
194
+ this.view.setUint32(this.node_offset(node_index) + 24, line, true);
204
195
  }
205
196
  // Write start column
206
197
  set_start_column(node_index, column) {
207
- this.view.setUint16(this.node_offset(node_index) + 36, column, true);
198
+ this.view.setUint16(this.node_offset(node_index) + 28, column, true);
208
199
  }
209
200
  // Write value start delta (offset from startOffset, declaration value / at-rule prelude)
210
201
  set_value_start_delta(node_index, delta) {
211
- this.view.setUint16(this.node_offset(node_index) + 16, delta, true);
202
+ this.view.setUint16(this.node_offset(node_index) + 18, delta, true);
212
203
  }
213
204
  // Write value length
214
205
  set_value_length(node_index, length) {
215
- this.view.setUint16(this.node_offset(node_index) + 18, length, true);
206
+ this.view.setUint16(this.node_offset(node_index) + 22, length, true);
216
207
  }
217
208
  // --- Node Creation ---
218
209
  // Grow the arena by 1.3x when capacity is exceeded
@@ -234,10 +225,10 @@ class CSSDataArena {
234
225
  this.count++;
235
226
  const offset = node_index * BYTES_PER_NODE;
236
227
  this.view.setUint8(offset, type);
237
- this.view.setUint32(offset + 4, start_offset, true);
238
- this.view.setUint16(offset + 8, length, true);
239
- this.view.setUint32(offset + 32, start_line, true);
240
- this.view.setUint16(offset + 36, start_column, true);
228
+ this.view.setUint16(offset + 2, length, true);
229
+ this.view.setUint32(offset + 12, start_offset, true);
230
+ this.view.setUint32(offset + 24, start_line, true);
231
+ this.view.setUint16(offset + 28, start_column, true);
241
232
  return node_index;
242
233
  }
243
234
  // --- Tree Building Helpers ---
@@ -246,8 +237,7 @@ class CSSDataArena {
246
237
  append_children(parent_index, children) {
247
238
  if (children.length === 0) return;
248
239
  const offset = this.node_offset(parent_index);
249
- this.view.setUint32(offset + 20, children[0], true);
250
- this.view.setUint32(offset + 24, children[children.length - 1], true);
240
+ this.view.setUint32(offset + 4, children[0], true);
251
241
  for (let i = 0; i < children.length - 1; i++) {
252
242
  this.set_next_sibling(children[i], children[i + 1]);
253
243
  }
@@ -277,4 +267,4 @@ class CSSDataArena {
277
267
  }
278
268
  }
279
269
 
280
- export { ATTRIBUTE_SELECTOR, ATTR_FLAG_CASE_INSENSITIVE, ATTR_FLAG_CASE_SENSITIVE, ATTR_FLAG_NONE, ATTR_OPERATOR_CARET_EQUAL, ATTR_OPERATOR_DOLLAR_EQUAL, ATTR_OPERATOR_EQUAL, ATTR_OPERATOR_NONE, ATTR_OPERATOR_PIPE_EQUAL, ATTR_OPERATOR_STAR_EQUAL, ATTR_OPERATOR_TILDE_EQUAL, AT_RULE, BLOCK, CLASS_SELECTOR, COMBINATOR, COMMENT, CONTAINER_QUERY, CSSDataArena, DECLARATION, DIMENSION, FLAG_HAS_BLOCK, FLAG_HAS_DECLARATIONS, FLAG_HAS_ERROR, FLAG_HAS_PARENS, FLAG_IMPORTANT, FLAG_VENDOR_PREFIXED, FUNCTION, HASH, IDENTIFIER, ID_SELECTOR, LANG_SELECTOR, LAYER_NAME, MEDIA_FEATURE, MEDIA_QUERY, MEDIA_TYPE, NESTING_SELECTOR, NTH_OF_SELECTOR, NTH_SELECTOR, NUMBER, OPERATOR, PARENTHESIS, PRELUDE_OPERATOR, PSEUDO_CLASS_SELECTOR, PSEUDO_ELEMENT_SELECTOR, SELECTOR, SELECTOR_LIST, STRING, STYLESHEET, STYLE_RULE, SUPPORTS_QUERY, TYPE_SELECTOR, UNIVERSAL_SELECTOR, URL };
270
+ export { ATTRIBUTE_SELECTOR, ATTR_FLAG_CASE_INSENSITIVE, ATTR_FLAG_CASE_SENSITIVE, ATTR_FLAG_NONE, ATTR_OPERATOR_CARET_EQUAL, ATTR_OPERATOR_DOLLAR_EQUAL, ATTR_OPERATOR_EQUAL, ATTR_OPERATOR_NONE, ATTR_OPERATOR_PIPE_EQUAL, ATTR_OPERATOR_STAR_EQUAL, ATTR_OPERATOR_TILDE_EQUAL, AT_RULE, BLOCK, CLASS_SELECTOR, COMBINATOR, COMMENT, CONTAINER_QUERY, CSSDataArena, DECLARATION, DIMENSION, FLAG_HAS_BLOCK, FLAG_HAS_DECLARATIONS, FLAG_HAS_ERROR, FLAG_HAS_PARENS, FLAG_IMPORTANT, FUNCTION, HASH, IDENTIFIER, ID_SELECTOR, LANG_SELECTOR, LAYER_NAME, MEDIA_FEATURE, MEDIA_QUERY, MEDIA_TYPE, NESTING_SELECTOR, NTH_OF_SELECTOR, NTH_SELECTOR, NUMBER, OPERATOR, PARENTHESIS, PRELUDE_OPERATOR, PSEUDO_CLASS_SELECTOR, PSEUDO_ELEMENT_SELECTOR, SELECTOR, SELECTOR_LIST, STRING, STYLESHEET, STYLE_RULE, SUPPORTS_QUERY, TYPE_SELECTOR, UNIVERSAL_SELECTOR, URL };
@@ -1 +1,40 @@
1
- export { 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, ATTR_OPERATOR_TILDE_EQUAL, ATTR_OPERATOR_PIPE_EQUAL, ATTR_OPERATOR_CARET_EQUAL, ATTR_OPERATOR_DOLLAR_EQUAL, ATTR_OPERATOR_STAR_EQUAL, ATTR_FLAG_NONE, ATTR_FLAG_CASE_INSENSITIVE, ATTR_FLAG_CASE_SENSITIVE, } from './arena';
1
+ import { 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, ATTR_OPERATOR_TILDE_EQUAL, ATTR_OPERATOR_PIPE_EQUAL, ATTR_OPERATOR_CARET_EQUAL, ATTR_OPERATOR_DOLLAR_EQUAL, ATTR_OPERATOR_STAR_EQUAL, ATTR_FLAG_NONE, ATTR_FLAG_CASE_INSENSITIVE, ATTR_FLAG_CASE_SENSITIVE } from './arena';
2
+ export { 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, ATTR_OPERATOR_TILDE_EQUAL, ATTR_OPERATOR_PIPE_EQUAL, ATTR_OPERATOR_CARET_EQUAL, ATTR_OPERATOR_DOLLAR_EQUAL, ATTR_OPERATOR_STAR_EQUAL, ATTR_FLAG_NONE, ATTR_FLAG_CASE_INSENSITIVE, ATTR_FLAG_CASE_SENSITIVE, };
3
+ export declare const NODE_TYPES: {
4
+ readonly STYLESHEET: 1;
5
+ readonly STYLE_RULE: 2;
6
+ readonly AT_RULE: 3;
7
+ readonly DECLARATION: 4;
8
+ readonly SELECTOR: 5;
9
+ readonly COMMENT: 6;
10
+ readonly BLOCK: 7;
11
+ readonly IDENTIFIER: 10;
12
+ readonly NUMBER: 11;
13
+ readonly DIMENSION: 12;
14
+ readonly STRING: 13;
15
+ readonly HASH: 14;
16
+ readonly FUNCTION: 15;
17
+ readonly OPERATOR: 16;
18
+ readonly PARENTHESIS: 17;
19
+ readonly URL: 18;
20
+ readonly SELECTOR_LIST: 20;
21
+ readonly TYPE_SELECTOR: 21;
22
+ readonly CLASS_SELECTOR: 22;
23
+ readonly ID_SELECTOR: 23;
24
+ readonly ATTRIBUTE_SELECTOR: 24;
25
+ readonly PSEUDO_CLASS_SELECTOR: 25;
26
+ readonly PSEUDO_ELEMENT_SELECTOR: 26;
27
+ readonly COMBINATOR: 27;
28
+ readonly UNIVERSAL_SELECTOR: 28;
29
+ readonly NESTING_SELECTOR: 29;
30
+ readonly NTH_SELECTOR: 30;
31
+ readonly NTH_OF_SELECTOR: 31;
32
+ readonly LANG_SELECTOR: 56;
33
+ readonly MEDIA_QUERY: 32;
34
+ readonly MEDIA_FEATURE: 33;
35
+ readonly MEDIA_TYPE: 34;
36
+ readonly CONTAINER_QUERY: 35;
37
+ readonly SUPPORTS_QUERY: 36;
38
+ readonly LAYER_NAME: 37;
39
+ readonly PRELUDE_OPERATOR: 38;
40
+ };
@@ -0,0 +1,47 @@
1
+ import { PRELUDE_OPERATOR, LAYER_NAME, SUPPORTS_QUERY, CONTAINER_QUERY, MEDIA_TYPE, MEDIA_FEATURE, MEDIA_QUERY, LANG_SELECTOR, NTH_OF_SELECTOR, NTH_SELECTOR, NESTING_SELECTOR, UNIVERSAL_SELECTOR, COMBINATOR, PSEUDO_ELEMENT_SELECTOR, PSEUDO_CLASS_SELECTOR, ATTRIBUTE_SELECTOR, ID_SELECTOR, CLASS_SELECTOR, TYPE_SELECTOR, SELECTOR_LIST, URL, PARENTHESIS, OPERATOR, FUNCTION, HASH, STRING, DIMENSION, NUMBER, IDENTIFIER, BLOCK, COMMENT, SELECTOR, DECLARATION, AT_RULE, STYLE_RULE, STYLESHEET } from './arena.js';
2
+ export { ATTR_FLAG_CASE_INSENSITIVE, ATTR_FLAG_CASE_SENSITIVE, ATTR_FLAG_NONE, ATTR_OPERATOR_CARET_EQUAL, ATTR_OPERATOR_DOLLAR_EQUAL, ATTR_OPERATOR_EQUAL, ATTR_OPERATOR_NONE, ATTR_OPERATOR_PIPE_EQUAL, ATTR_OPERATOR_STAR_EQUAL, ATTR_OPERATOR_TILDE_EQUAL, FLAG_IMPORTANT } from './arena.js';
3
+
4
+ const NODE_TYPES = {
5
+ // Core nodes
6
+ STYLESHEET,
7
+ STYLE_RULE,
8
+ AT_RULE,
9
+ DECLARATION,
10
+ SELECTOR,
11
+ COMMENT,
12
+ BLOCK,
13
+ // Value nodes
14
+ IDENTIFIER,
15
+ NUMBER,
16
+ DIMENSION,
17
+ STRING,
18
+ HASH,
19
+ FUNCTION,
20
+ OPERATOR,
21
+ PARENTHESIS,
22
+ URL,
23
+ // Selector nodes
24
+ SELECTOR_LIST,
25
+ TYPE_SELECTOR,
26
+ CLASS_SELECTOR,
27
+ ID_SELECTOR,
28
+ ATTRIBUTE_SELECTOR,
29
+ PSEUDO_CLASS_SELECTOR,
30
+ PSEUDO_ELEMENT_SELECTOR,
31
+ COMBINATOR,
32
+ UNIVERSAL_SELECTOR,
33
+ NESTING_SELECTOR,
34
+ NTH_SELECTOR,
35
+ NTH_OF_SELECTOR,
36
+ LANG_SELECTOR,
37
+ // At-rule prelude nodes
38
+ MEDIA_QUERY,
39
+ MEDIA_FEATURE,
40
+ MEDIA_TYPE,
41
+ CONTAINER_QUERY,
42
+ SUPPORTS_QUERY,
43
+ LAYER_NAME,
44
+ PRELUDE_OPERATOR
45
+ };
46
+
47
+ export { ATTRIBUTE_SELECTOR, AT_RULE, BLOCK, CLASS_SELECTOR, COMBINATOR, COMMENT, CONTAINER_QUERY, DECLARATION, DIMENSION, FUNCTION, HASH, IDENTIFIER, ID_SELECTOR, LANG_SELECTOR, LAYER_NAME, MEDIA_FEATURE, MEDIA_QUERY, MEDIA_TYPE, NESTING_SELECTOR, NODE_TYPES, NTH_OF_SELECTOR, NTH_SELECTOR, NUMBER, OPERATOR, PARENTHESIS, PRELUDE_OPERATOR, PSEUDO_CLASS_SELECTOR, PSEUDO_ELEMENT_SELECTOR, SELECTOR, SELECTOR_LIST, STRING, STYLESHEET, STYLE_RULE, SUPPORTS_QUERY, TYPE_SELECTOR, UNIVERSAL_SELECTOR, URL };
@@ -47,7 +47,7 @@ export interface CloneOptions {
47
47
  */
48
48
  deep?: boolean;
49
49
  /**
50
- * Include location information (line, column, offset, length)
50
+ * Include location information (line, column, start, length)
51
51
  * @default false
52
52
  */
53
53
  locations?: boolean;
@@ -71,54 +71,133 @@ export type PlainCSSNode = {
71
71
  nth_b?: string | null;
72
72
  line?: number;
73
73
  column?: number;
74
- offset?: number;
74
+ start?: number;
75
75
  length?: number;
76
+ end?: number;
76
77
  };
77
78
  export declare class CSSNode {
78
79
  private arena;
79
80
  private source;
80
81
  private index;
81
82
  constructor(arena: CSSDataArena, source: string, index: number);
82
- get_index(): number;
83
+ /** Get node type as number (for performance) */
83
84
  get type(): CSSNodeType;
85
+ /** Get node type as human-readable string */
84
86
  get type_name(): TypeName;
87
+ /** Get the full text of this node from source */
85
88
  get text(): string;
89
+ /** Get the "content" text (property name for declarations, at-rule name for at-rules, layer name for import layers) */
86
90
  get name(): string;
91
+ /**
92
+ * Alias for name (for declarations: "color" in "color: blue")
93
+ * More semantic than `name` for declaration nodes
94
+ */
87
95
  get property(): string;
96
+ /**
97
+ * Get the value text (for declarations: "blue" in "color: blue")
98
+ * For dimension/number nodes: returns the numeric value as a number
99
+ * For string nodes: returns the string content without quotes
100
+ * For URL nodes with quoted string: returns the string with quotes (consistent with STRING node)
101
+ * For URL nodes with unquoted URL: returns the URL content without quotes
102
+ */
88
103
  get value(): string | number | null;
104
+ /** Get the numeric value for NUMBER and DIMENSION nodes, or null for other node types */
105
+ get value_as_number(): number | null;
106
+ /**
107
+ * Get the prelude text (for at-rules: "(min-width: 768px)" in "@media (min-width: 768px)")
108
+ * This is an alias for `value` to make at-rule usage more semantic
109
+ */
89
110
  get prelude(): string | null;
111
+ /**
112
+ * Get the attribute operator (for attribute selectors: =, ~=, |=, ^=, $=, *=)
113
+ * Returns one of the ATTR_OPERATOR_* constants
114
+ */
90
115
  get attr_operator(): number;
116
+ /**
117
+ * Get the attribute flags (for attribute selectors: i, s)
118
+ * Returns one of the ATTR_FLAG_* constants
119
+ */
91
120
  get attr_flags(): number;
121
+ /** Get the unit for dimension nodes (e.g., "px" from "100px", "%" from "50%") */
92
122
  get unit(): string | null;
123
+ /** Check if this declaration has !important */
93
124
  get is_important(): boolean | null;
125
+ /** Check if this has a vendor prefix (computed on-demand) */
94
126
  get is_vendor_prefixed(): boolean;
127
+ /** Check if this node has an error */
95
128
  get has_error(): boolean;
129
+ /** Check if this at-rule has a prelude */
96
130
  get has_prelude(): boolean;
131
+ /** Check if this rule has a block { } */
97
132
  get has_block(): boolean;
133
+ /** Check if this style rule has declarations */
98
134
  get has_declarations(): boolean;
135
+ /** Get the block node (for style rules and at-rules with blocks) */
99
136
  get block(): CSSNode | null;
137
+ /** Check if this block is empty (no declarations or rules, only comments allowed) */
100
138
  get is_empty(): boolean;
139
+ /** Get array of parsed value nodes (for declarations only) */
101
140
  get values(): CSSNode[];
102
- get value_count(): number;
141
+ /** Get start line number */
103
142
  get line(): number;
143
+ /** Get start column number */
104
144
  get column(): number;
105
- get offset(): number;
145
+ /** Get start offset in source */
146
+ get start(): number;
147
+ /** Get length in source */
106
148
  get length(): number;
149
+ /**
150
+ * Get end offset in source
151
+ * End is not stored, must be calculated
152
+ */
153
+ get end(): number;
154
+ /** Get first child node */
107
155
  get first_child(): CSSNode | null;
156
+ /** Get next sibling node */
108
157
  get next_sibling(): CSSNode | null;
158
+ /** Check if this node has a next sibling */
109
159
  get has_next(): boolean;
160
+ /**
161
+ * Check if this node has children
162
+ * For pseudo-class/pseudo-element functions, returns true if FLAG_HAS_PARENS is set
163
+ * This allows formatters to distinguish :lang() from :hover
164
+ */
110
165
  get has_children(): boolean;
166
+ /** Get all children as an array */
111
167
  get children(): CSSNode[];
168
+ /** Make CSSNode iterable over its children */
112
169
  [Symbol.iterator](): Iterator<CSSNode>;
170
+ /** Get the 'a' coefficient from An+B expression (e.g., "2n" from "2n+1", "odd" from "odd") */
113
171
  get nth_a(): string | null;
172
+ /** Get the 'b' coefficient from An+B expression (e.g., "+1" from "2n+1") */
114
173
  get nth_b(): string | null;
174
+ /** Get the An+B formula node from :nth-child(2n+1 of .foo) */
115
175
  get nth(): CSSNode | null;
176
+ /** Get the selector list from :nth-child(2n+1 of .foo) */
116
177
  get selector(): CSSNode | null;
178
+ /**
179
+ * Get selector list from pseudo-class functions
180
+ * Works for :is(.a), :not(.b), :has(.c), :where(.d), :nth-child(2n of .e)
181
+ */
117
182
  get selector_list(): CSSNode | null;
183
+ /**
184
+ * Iterator over first compound selector parts (zero allocation)
185
+ * Yields parts before the first combinator
186
+ */
118
187
  compound_parts(): IterableIterator<CSSNode>;
188
+ /**
189
+ * Get first compound selector as array
190
+ * Returns array of parts before first combinator
191
+ */
119
192
  get first_compound(): CSSNode[];
193
+ /**
194
+ * Split selector into compound selectors
195
+ * Returns array of compound arrays split by combinators
196
+ */
120
197
  get all_compounds(): CSSNode[][];
198
+ /** Check if selector is compound (no combinators) */
121
199
  get is_compound(): boolean;
200
+ /** Get text of first compound selector (no node allocation) */
122
201
  get first_compound_text(): string;
123
202
  /**
124
203
  * Clone this node as a mutable plain JavaScript object
@@ -128,7 +207,7 @@ export declare class CSSNode {
128
207
  *
129
208
  * @param options - Cloning configuration
130
209
  * @param options.deep - Recursively clone children (default: true)
131
- * @param options.locations - Include line/column/offset/length (default: false)
210
+ * @param options.locations - Include line/column/start/length (default: false)
132
211
  * @returns Plain object with children as array
133
212
  *
134
213
  * @example
package/dist/css-node.js CHANGED
@@ -1,5 +1,5 @@
1
- import { URL, STRING, DIMENSION, NUMBER, DECLARATION, FLAG_IMPORTANT, FLAG_VENDOR_PREFIXED, FLAG_HAS_ERROR, FLAG_HAS_BLOCK, FLAG_HAS_DECLARATIONS, STYLE_RULE, BLOCK, AT_RULE, COMMENT, PSEUDO_CLASS_SELECTOR, PSEUDO_ELEMENT_SELECTOR, FLAG_HAS_PARENS, NTH_SELECTOR, NTH_OF_SELECTOR, SELECTOR_LIST, SELECTOR, COMBINATOR, ATTRIBUTE_SELECTOR, PRELUDE_OPERATOR, LAYER_NAME, SUPPORTS_QUERY, CONTAINER_QUERY, MEDIA_TYPE, MEDIA_FEATURE, MEDIA_QUERY, LANG_SELECTOR, NESTING_SELECTOR, UNIVERSAL_SELECTOR, ID_SELECTOR, CLASS_SELECTOR, TYPE_SELECTOR, PARENTHESIS, OPERATOR, FUNCTION, HASH, IDENTIFIER, STYLESHEET } from './arena.js';
2
- import { str_starts_with, is_whitespace, CHAR_MINUS_HYPHEN, CHAR_PLUS } from './string-utils.js';
1
+ import { DIMENSION, NUMBER, URL, STRING, DECLARATION, FLAG_IMPORTANT, IDENTIFIER, FUNCTION, AT_RULE, PSEUDO_ELEMENT_SELECTOR, PSEUDO_CLASS_SELECTOR, FLAG_HAS_ERROR, FLAG_HAS_BLOCK, FLAG_HAS_DECLARATIONS, STYLE_RULE, BLOCK, COMMENT, FLAG_HAS_PARENS, NTH_SELECTOR, NTH_OF_SELECTOR, SELECTOR_LIST, SELECTOR, COMBINATOR, ATTRIBUTE_SELECTOR, PRELUDE_OPERATOR, LAYER_NAME, SUPPORTS_QUERY, CONTAINER_QUERY, MEDIA_TYPE, MEDIA_FEATURE, MEDIA_QUERY, LANG_SELECTOR, NESTING_SELECTOR, UNIVERSAL_SELECTOR, ID_SELECTOR, CLASS_SELECTOR, TYPE_SELECTOR, PARENTHESIS, OPERATOR, HASH, STYLESHEET } from './arena.js';
2
+ import { str_starts_with, is_vendor_prefixed, is_whitespace, CHAR_MINUS_HYPHEN, CHAR_PLUS } from './string-utils.js';
3
3
  import { parse_dimension } from './parse-utils.js';
4
4
 
5
5
  const TYPE_NAMES = {
@@ -49,51 +49,57 @@ class CSSNode {
49
49
  this.source = source;
50
50
  this.index = index;
51
51
  }
52
- // Get the node index (for internal use)
53
- get_index() {
54
- return this.index;
55
- }
56
- // Get node type as number (for performance)
52
+ /** Get node type as number (for performance) */
57
53
  get type() {
58
54
  return this.arena.get_type(this.index);
59
55
  }
60
- // Get node type as human-readable string
56
+ /** Get node type as human-readable string */
61
57
  get type_name() {
62
58
  return TYPE_NAMES[this.type] || "unknown";
63
59
  }
64
- // Get the full text of this node from source
60
+ /** Get the full text of this node from source */
65
61
  get text() {
66
62
  let start = this.arena.get_start_offset(this.index);
67
63
  let length = this.arena.get_length(this.index);
68
64
  return this.source.substring(start, start + length);
69
65
  }
70
- // Get the "content" text (property name for declarations, at-rule name for at-rules, layer name for import layers)
66
+ /** Get the "content" text (property name for declarations, at-rule name for at-rules, layer name for import layers) */
71
67
  get name() {
72
68
  let start = this.arena.get_content_start(this.index);
73
69
  let length = this.arena.get_content_length(this.index);
74
70
  if (length === 0) return "";
75
71
  return this.source.substring(start, start + length);
76
72
  }
77
- // Alias for name (for declarations: "color" in "color: blue")
78
- // More semantic than `name` for declaration nodes
73
+ /**
74
+ * Alias for name (for declarations: "color" in "color: blue")
75
+ * More semantic than `name` for declaration nodes
76
+ */
79
77
  get property() {
80
78
  return this.name;
81
79
  }
82
- // Get the value text (for declarations: "blue" in "color: blue")
83
- // For dimension/number nodes: returns the numeric value as a number
84
- // For string nodes: returns the string content without quotes
85
- // For URL nodes with quoted string: returns the string with quotes (consistent with STRING node)
86
- // For URL nodes with unquoted URL: returns the URL content without quotes
80
+ /**
81
+ * Get the value text (for declarations: "blue" in "color: blue")
82
+ * For dimension/number nodes: returns the numeric value as a number
83
+ * For string nodes: returns the string content without quotes
84
+ * For URL nodes with quoted string: returns the string with quotes (consistent with STRING node)
85
+ * For URL nodes with unquoted URL: returns the URL content without quotes
86
+ */
87
87
  get value() {
88
- if (this.type === URL) {
89
- const firstChild = this.first_child;
88
+ let { type, text } = this;
89
+ if (type === DIMENSION) {
90
+ return parse_dimension(text).value;
91
+ }
92
+ if (type === NUMBER) {
93
+ return Number.parseFloat(this.text);
94
+ }
95
+ if (type === URL) {
96
+ let firstChild = this.first_child;
90
97
  if (firstChild && firstChild.type === STRING) {
91
98
  return firstChild.text;
92
99
  }
93
- const text = this.text;
94
100
  if (str_starts_with(text, "url(")) {
95
- const openParen = text.indexOf("(");
96
- const closeParen = text.lastIndexOf(")");
101
+ let openParen = text.indexOf("(");
102
+ let closeParen = text.lastIndexOf(")");
97
103
  if (openParen !== -1 && closeParen !== -1 && closeParen > openParen) {
98
104
  let content = text.substring(openParen + 1, closeParen).trim();
99
105
  return content;
@@ -102,61 +108,89 @@ class CSSNode {
102
108
  return text;
103
109
  }
104
110
  }
105
- if (this.type === DIMENSION || this.type === NUMBER) {
106
- return parse_dimension(this.text).value;
107
- }
108
111
  let start = this.arena.get_value_start(this.index);
109
112
  let length = this.arena.get_value_length(this.index);
110
113
  if (length === 0) return null;
111
114
  return this.source.substring(start, start + length);
112
115
  }
113
- // Get the prelude text (for at-rules: "(min-width: 768px)" in "@media (min-width: 768px)")
114
- // This is an alias for `value` to make at-rule usage more semantic
116
+ /** Get the numeric value for NUMBER and DIMENSION nodes, or null for other node types */
117
+ get value_as_number() {
118
+ let text = this.text;
119
+ if (this.type === NUMBER) {
120
+ return Number.parseFloat(text);
121
+ }
122
+ if (this.type === DIMENSION) {
123
+ return parse_dimension(text).value;
124
+ }
125
+ return null;
126
+ }
127
+ /**
128
+ * Get the prelude text (for at-rules: "(min-width: 768px)" in "@media (min-width: 768px)")
129
+ * This is an alias for `value` to make at-rule usage more semantic
130
+ */
115
131
  get prelude() {
116
132
  let val = this.value;
117
133
  return typeof val === "string" ? val : null;
118
134
  }
119
- // Get the attribute operator (for attribute selectors: =, ~=, |=, ^=, $=, *=)
120
- // Returns one of the ATTR_OPERATOR_* constants
135
+ /**
136
+ * Get the attribute operator (for attribute selectors: =, ~=, |=, ^=, $=, *=)
137
+ * Returns one of the ATTR_OPERATOR_* constants
138
+ */
121
139
  get attr_operator() {
122
140
  return this.arena.get_attr_operator(this.index);
123
141
  }
124
- // Get the attribute flags (for attribute selectors: i, s)
125
- // Returns one of the ATTR_FLAG_* constants
142
+ /**
143
+ * Get the attribute flags (for attribute selectors: i, s)
144
+ * Returns one of the ATTR_FLAG_* constants
145
+ */
126
146
  get attr_flags() {
127
147
  return this.arena.get_attr_flags(this.index);
128
148
  }
129
- // Get the unit for dimension nodes (e.g., "px" from "100px", "%" from "50%")
149
+ /** Get the unit for dimension nodes (e.g., "px" from "100px", "%" from "50%") */
130
150
  get unit() {
131
151
  if (this.type !== DIMENSION) return null;
132
152
  return parse_dimension(this.text).unit;
133
153
  }
134
- // Check if this declaration has !important
154
+ /** Check if this declaration has !important */
135
155
  get is_important() {
136
156
  if (this.type !== DECLARATION) return null;
137
157
  return this.arena.has_flag(this.index, FLAG_IMPORTANT);
138
158
  }
139
- // Check if this has a vendor prefix (flag-based for performance)
159
+ /** Check if this has a vendor prefix (computed on-demand) */
140
160
  get is_vendor_prefixed() {
141
- return this.arena.has_flag(this.index, FLAG_VENDOR_PREFIXED);
161
+ switch (this.type) {
162
+ case DECLARATION:
163
+ return is_vendor_prefixed(this.name);
164
+ case PSEUDO_CLASS_SELECTOR:
165
+ case PSEUDO_ELEMENT_SELECTOR:
166
+ return is_vendor_prefixed(this.name);
167
+ case AT_RULE:
168
+ return is_vendor_prefixed(this.name);
169
+ case FUNCTION:
170
+ return is_vendor_prefixed(this.name);
171
+ case IDENTIFIER:
172
+ return is_vendor_prefixed(this.text);
173
+ default:
174
+ return false;
175
+ }
142
176
  }
143
- // Check if this node has an error
177
+ /** Check if this node has an error */
144
178
  get has_error() {
145
179
  return this.arena.has_flag(this.index, FLAG_HAS_ERROR);
146
180
  }
147
- // Check if this at-rule has a prelude
181
+ /** Check if this at-rule has a prelude */
148
182
  get has_prelude() {
149
183
  return this.arena.get_value_length(this.index) > 0;
150
184
  }
151
- // Check if this rule has a block { }
185
+ /** Check if this rule has a block { } */
152
186
  get has_block() {
153
187
  return this.arena.has_flag(this.index, FLAG_HAS_BLOCK);
154
188
  }
155
- // Check if this style rule has declarations
189
+ /** Check if this style rule has declarations */
156
190
  get has_declarations() {
157
191
  return this.arena.has_flag(this.index, FLAG_HAS_DECLARATIONS);
158
192
  }
159
- // Get the block node (for style rules and at-rules with blocks)
193
+ /** Get the block node (for style rules and at-rules with blocks) */
160
194
  get block() {
161
195
  if (this.type === STYLE_RULE) {
162
196
  let first = this.first_child;
@@ -179,7 +213,7 @@ class CSSNode {
179
213
  }
180
214
  return null;
181
215
  }
182
- // Check if this block is empty (no declarations or rules, only comments allowed)
216
+ /** Check if this block is empty (no declarations or rules, only comments allowed) */
183
217
  get is_empty() {
184
218
  if (this.type !== BLOCK) return false;
185
219
  let child = this.first_child;
@@ -192,7 +226,7 @@ class CSSNode {
192
226
  return true;
193
227
  }
194
228
  // --- Value Node Access (for declarations) ---
195
- // Get array of parsed value nodes (for declarations only)
229
+ /** Get array of parsed value nodes (for declarations only) */
196
230
  get values() {
197
231
  let result = [];
198
232
  let child = this.first_child;
@@ -202,52 +236,52 @@ class CSSNode {
202
236
  }
203
237
  return result;
204
238
  }
205
- // Get count of value nodes
206
- get value_count() {
207
- let count = 0;
208
- let child = this.first_child;
209
- while (child) {
210
- count++;
211
- child = child.next_sibling;
212
- }
213
- return count;
214
- }
215
- // Get start line number
239
+ /** Get start line number */
216
240
  get line() {
217
241
  return this.arena.get_start_line(this.index);
218
242
  }
219
- // Get start column number
243
+ /** Get start column number */
220
244
  get column() {
221
245
  return this.arena.get_start_column(this.index);
222
246
  }
223
- // Get start offset in source
224
- get offset() {
247
+ /** Get start offset in source */
248
+ get start() {
225
249
  return this.arena.get_start_offset(this.index);
226
250
  }
227
- // Get length in source
251
+ /** Get length in source */
228
252
  get length() {
229
253
  return this.arena.get_length(this.index);
230
254
  }
255
+ /**
256
+ * Get end offset in source
257
+ * End is not stored, must be calculated
258
+ */
259
+ get end() {
260
+ return this.start + this.length;
261
+ }
231
262
  // --- Tree Traversal ---
232
- // Get first child node
263
+ /** Get first child node */
233
264
  get first_child() {
234
265
  let child_index = this.arena.get_first_child(this.index);
235
266
  if (child_index === 0) return null;
236
267
  return new CSSNode(this.arena, this.source, child_index);
237
268
  }
238
- // Get next sibling node
269
+ /** Get next sibling node */
239
270
  get next_sibling() {
240
271
  let sibling_index = this.arena.get_next_sibling(this.index);
241
272
  if (sibling_index === 0) return null;
242
273
  return new CSSNode(this.arena, this.source, sibling_index);
243
274
  }
275
+ /** Check if this node has a next sibling */
244
276
  get has_next() {
245
277
  let sibling_index = this.arena.get_next_sibling(this.index);
246
278
  return sibling_index !== 0;
247
279
  }
248
- // Check if this node has children
249
- // For pseudo-class/pseudo-element functions, returns true if FLAG_HAS_PARENS is set
250
- // This allows formatters to distinguish :lang() from :hover
280
+ /**
281
+ * Check if this node has children
282
+ * For pseudo-class/pseudo-element functions, returns true if FLAG_HAS_PARENS is set
283
+ * This allows formatters to distinguish :lang() from :hover
284
+ */
251
285
  get has_children() {
252
286
  if (this.type === PSEUDO_CLASS_SELECTOR || this.type === PSEUDO_ELEMENT_SELECTOR) {
253
287
  if (this.arena.has_flag(this.index, FLAG_HAS_PARENS)) {
@@ -256,7 +290,7 @@ class CSSNode {
256
290
  }
257
291
  return this.arena.has_children(this.index);
258
292
  }
259
- // Get all children as an array
293
+ /** Get all children as an array */
260
294
  get children() {
261
295
  let result = [];
262
296
  let child = this.first_child;
@@ -266,7 +300,7 @@ class CSSNode {
266
300
  }
267
301
  return result;
268
302
  }
269
- // Make CSSNode iterable over its children
303
+ /** Make CSSNode iterable over its children */
270
304
  *[Symbol.iterator]() {
271
305
  let child = this.first_child;
272
306
  while (child) {
@@ -275,7 +309,7 @@ class CSSNode {
275
309
  }
276
310
  }
277
311
  // --- An+B Expression Helpers (for NODE_SELECTOR_NTH) ---
278
- // Get the 'a' coefficient from An+B expression (e.g., "2n" from "2n+1", "odd" from "odd")
312
+ /** Get the 'a' coefficient from An+B expression (e.g., "2n" from "2n+1", "odd" from "odd") */
279
313
  get nth_a() {
280
314
  if (this.type !== NTH_SELECTOR) return null;
281
315
  let len = this.arena.get_content_length(this.index);
@@ -283,7 +317,7 @@ class CSSNode {
283
317
  let start = this.arena.get_content_start(this.index);
284
318
  return this.source.substring(start, start + len);
285
319
  }
286
- // Get the 'b' coefficient from An+B expression (e.g., "+1" from "2n+1")
320
+ /** Get the 'b' coefficient from An+B expression (e.g., "+1" from "2n+1") */
287
321
  get nth_b() {
288
322
  if (this.type !== NTH_SELECTOR) return null;
289
323
  let len = this.arena.get_value_length(this.index);
@@ -307,20 +341,22 @@ class CSSNode {
307
341
  return value;
308
342
  }
309
343
  // --- Pseudo-Class Nth-Of Helpers (for NODE_SELECTOR_NTH_OF) ---
310
- // Get the An+B formula node from :nth-child(2n+1 of .foo)
344
+ /** Get the An+B formula node from :nth-child(2n+1 of .foo) */
311
345
  get nth() {
312
346
  if (this.type !== NTH_OF_SELECTOR) return null;
313
347
  return this.first_child;
314
348
  }
315
- // Get the selector list from :nth-child(2n+1 of .foo)
349
+ /** Get the selector list from :nth-child(2n+1 of .foo) */
316
350
  get selector() {
317
351
  if (this.type !== NTH_OF_SELECTOR) return null;
318
352
  let first = this.first_child;
319
353
  return first ? first.next_sibling : null;
320
354
  }
321
355
  // --- Pseudo-Class Selector List Helper ---
322
- // Get selector list from pseudo-class functions
323
- // Works for :is(.a), :not(.b), :has(.c), :where(.d), :nth-child(2n of .e)
356
+ /**
357
+ * Get selector list from pseudo-class functions
358
+ * Works for :is(.a), :not(.b), :has(.c), :where(.d), :nth-child(2n of .e)
359
+ */
324
360
  get selector_list() {
325
361
  if (this.type !== PSEUDO_CLASS_SELECTOR) return null;
326
362
  let child = this.first_child;
@@ -334,8 +370,10 @@ class CSSNode {
334
370
  return null;
335
371
  }
336
372
  // --- Compound Selector Helpers (for NODE_SELECTOR) ---
337
- // Iterator over first compound selector parts (zero allocation)
338
- // Yields parts before the first combinator
373
+ /**
374
+ * Iterator over first compound selector parts (zero allocation)
375
+ * Yields parts before the first combinator
376
+ */
339
377
  *compound_parts() {
340
378
  if (this.type !== SELECTOR) return;
341
379
  let child = this.first_child;
@@ -345,8 +383,10 @@ class CSSNode {
345
383
  child = child.next_sibling;
346
384
  }
347
385
  }
348
- // Get first compound selector as array
349
- // Returns array of parts before first combinator
386
+ /**
387
+ * Get first compound selector as array
388
+ * Returns array of parts before first combinator
389
+ */
350
390
  get first_compound() {
351
391
  if (this.type !== SELECTOR) return [];
352
392
  let result = [];
@@ -358,8 +398,10 @@ class CSSNode {
358
398
  }
359
399
  return result;
360
400
  }
361
- // Split selector into compound selectors
362
- // Returns array of compound arrays split by combinators
401
+ /**
402
+ * Split selector into compound selectors
403
+ * Returns array of compound arrays split by combinators
404
+ */
363
405
  get all_compounds() {
364
406
  if (this.type !== SELECTOR) return [];
365
407
  let compounds = [];
@@ -381,7 +423,7 @@ class CSSNode {
381
423
  }
382
424
  return compounds;
383
425
  }
384
- // Check if selector is compound (no combinators)
426
+ /** Check if selector is compound (no combinators) */
385
427
  get is_compound() {
386
428
  if (this.type !== SELECTOR) return false;
387
429
  let child = this.first_child;
@@ -391,7 +433,7 @@ class CSSNode {
391
433
  }
392
434
  return true;
393
435
  }
394
- // Get text of first compound selector (no node allocation)
436
+ /** Get text of first compound selector (no node allocation) */
395
437
  get first_compound_text() {
396
438
  if (this.type !== SELECTOR) return "";
397
439
  let start = -1;
@@ -399,8 +441,8 @@ class CSSNode {
399
441
  let child = this.first_child;
400
442
  while (child) {
401
443
  if (child.type === COMBINATOR) break;
402
- if (start === -1) start = child.offset;
403
- end = child.offset + child.length;
444
+ if (start === -1) start = child.start;
445
+ end = child.start + child.length;
404
446
  child = child.next_sibling;
405
447
  }
406
448
  if (start === -1) return "";
@@ -415,7 +457,7 @@ class CSSNode {
415
457
  *
416
458
  * @param options - Cloning configuration
417
459
  * @param options.deep - Recursively clone children (default: true)
418
- * @param options.locations - Include line/column/offset/length (default: false)
460
+ * @param options.locations - Include line/column/start/length (default: false)
419
461
  * @returns Plain object with children as array
420
462
  *
421
463
  * @example
@@ -459,8 +501,9 @@ class CSSNode {
459
501
  if (locations) {
460
502
  plain.line = this.line;
461
503
  plain.column = this.column;
462
- plain.offset = this.offset;
504
+ plain.start = this.start;
463
505
  plain.length = this.length;
506
+ plain.end = this.end;
464
507
  }
465
508
  if (deep) {
466
509
  for (let child of this.children) {
package/dist/index.d.ts CHANGED
@@ -3,10 +3,11 @@ export { parse_selector } from './parse-selector';
3
3
  export { parse_atrule_prelude } from './parse-atrule-prelude';
4
4
  export { parse_value } from './parse-value';
5
5
  export { tokenize } from './tokenize';
6
- export { walk, traverse } from './walk';
6
+ export { walk, traverse, SKIP, BREAK } from './walk';
7
7
  export { type ParserOptions } from './parse';
8
8
  export { CSSNode, type CSSNodeType, TYPE_NAMES, type CloneOptions, type PlainCSSNode } from './css-node';
9
9
  export type { LexerPosition } from './lexer';
10
10
  export { ATTR_OPERATOR_NONE, ATTR_OPERATOR_EQUAL, ATTR_OPERATOR_TILDE_EQUAL, ATTR_OPERATOR_PIPE_EQUAL, ATTR_OPERATOR_CARET_EQUAL, ATTR_OPERATOR_DOLLAR_EQUAL, ATTR_OPERATOR_STAR_EQUAL, ATTR_FLAG_NONE, ATTR_FLAG_CASE_INSENSITIVE, ATTR_FLAG_CASE_SENSITIVE, } from './arena';
11
11
  export * from './constants';
12
12
  export * from './token-types';
13
+ export { NODE_TYPES } from './constants';
package/dist/index.js CHANGED
@@ -3,7 +3,8 @@ export { parse_selector } from './parse-selector.js';
3
3
  export { parse_atrule_prelude } from './parse-atrule-prelude.js';
4
4
  export { parse_value } from './parse-value.js';
5
5
  export { tokenize } from './tokenize.js';
6
- export { traverse, walk } from './walk.js';
6
+ export { BREAK, SKIP, traverse, walk } from './walk.js';
7
7
  export { CSSNode, TYPE_NAMES } from './css-node.js';
8
8
  export { ATTRIBUTE_SELECTOR, ATTR_FLAG_CASE_INSENSITIVE, ATTR_FLAG_CASE_SENSITIVE, ATTR_FLAG_NONE, ATTR_OPERATOR_CARET_EQUAL, ATTR_OPERATOR_DOLLAR_EQUAL, ATTR_OPERATOR_EQUAL, ATTR_OPERATOR_NONE, ATTR_OPERATOR_PIPE_EQUAL, ATTR_OPERATOR_STAR_EQUAL, ATTR_OPERATOR_TILDE_EQUAL, AT_RULE, BLOCK, CLASS_SELECTOR, COMBINATOR, COMMENT, CONTAINER_QUERY, DECLARATION, DIMENSION, FLAG_IMPORTANT, FUNCTION, HASH, IDENTIFIER, ID_SELECTOR, LANG_SELECTOR, LAYER_NAME, MEDIA_FEATURE, MEDIA_QUERY, MEDIA_TYPE, NESTING_SELECTOR, NTH_OF_SELECTOR, NTH_SELECTOR, NUMBER, OPERATOR, PARENTHESIS, PRELUDE_OPERATOR, PSEUDO_CLASS_SELECTOR, PSEUDO_ELEMENT_SELECTOR, SELECTOR, SELECTOR_LIST, STRING, STYLESHEET, STYLE_RULE, SUPPORTS_QUERY, TYPE_SELECTOR, UNIVERSAL_SELECTOR, URL } from './arena.js';
9
+ export { NODE_TYPES } from './constants.js';
9
10
  export { TOKEN_AT_KEYWORD, TOKEN_BAD_STRING, TOKEN_BAD_URL, TOKEN_CDC, TOKEN_CDO, TOKEN_COLON, TOKEN_COMMA, TOKEN_COMMENT, TOKEN_DELIM, TOKEN_DIMENSION, TOKEN_EOF, TOKEN_FUNCTION, TOKEN_HASH, TOKEN_IDENT, TOKEN_LEFT_BRACE, TOKEN_LEFT_BRACKET, TOKEN_LEFT_PAREN, TOKEN_NUMBER, TOKEN_PERCENTAGE, TOKEN_RIGHT_BRACE, TOKEN_RIGHT_BRACKET, TOKEN_RIGHT_PAREN, TOKEN_SEMICOLON, TOKEN_STRING, TOKEN_URL, TOKEN_WHITESPACE } from './token-types.js';
@@ -1,8 +1,8 @@
1
1
  import { Lexer } from './lexer.js';
2
- import { CSSDataArena, SELECTOR_LIST, SELECTOR, COMBINATOR, NESTING_SELECTOR, ID_SELECTOR, TYPE_SELECTOR, UNIVERSAL_SELECTOR, CLASS_SELECTOR, ATTRIBUTE_SELECTOR, ATTR_OPERATOR_NONE, ATTR_FLAG_NONE, ATTR_OPERATOR_EQUAL, ATTR_OPERATOR_TILDE_EQUAL, ATTR_OPERATOR_PIPE_EQUAL, ATTR_OPERATOR_CARET_EQUAL, ATTR_OPERATOR_DOLLAR_EQUAL, ATTR_OPERATOR_STAR_EQUAL, ATTR_FLAG_CASE_INSENSITIVE, ATTR_FLAG_CASE_SENSITIVE, PSEUDO_ELEMENT_SELECTOR, PSEUDO_CLASS_SELECTOR, FLAG_VENDOR_PREFIXED, FLAG_HAS_PARENS, LANG_SELECTOR, NTH_OF_SELECTOR } from './arena.js';
2
+ import { CSSDataArena, SELECTOR_LIST, SELECTOR, COMBINATOR, NESTING_SELECTOR, ID_SELECTOR, TYPE_SELECTOR, UNIVERSAL_SELECTOR, CLASS_SELECTOR, ATTRIBUTE_SELECTOR, ATTR_OPERATOR_NONE, ATTR_FLAG_NONE, ATTR_OPERATOR_EQUAL, ATTR_OPERATOR_TILDE_EQUAL, ATTR_OPERATOR_PIPE_EQUAL, ATTR_OPERATOR_CARET_EQUAL, ATTR_OPERATOR_DOLLAR_EQUAL, ATTR_OPERATOR_STAR_EQUAL, ATTR_FLAG_CASE_INSENSITIVE, ATTR_FLAG_CASE_SENSITIVE, PSEUDO_ELEMENT_SELECTOR, PSEUDO_CLASS_SELECTOR, FLAG_HAS_PARENS, LANG_SELECTOR, NTH_OF_SELECTOR } from './arena.js';
3
3
  import { TOKEN_COMMA, TOKEN_DELIM, TOKEN_EOF, TOKEN_WHITESPACE, TOKEN_FUNCTION, TOKEN_COLON, TOKEN_LEFT_BRACKET, TOKEN_HASH, TOKEN_IDENT, TOKEN_RIGHT_BRACKET, TOKEN_LEFT_PAREN, TOKEN_RIGHT_PAREN, TOKEN_STRING } from './token-types.js';
4
4
  import { skip_whitespace_and_comments_forward, skip_whitespace_and_comments_backward, skip_whitespace_forward } from './parse-utils.js';
5
- import { CHAR_GREATER_THAN, CHAR_PLUS, CHAR_TILDE, CHAR_PERIOD, CHAR_ASTERISK, CHAR_AMPERSAND, CHAR_PIPE, CHAR_SPACE, CHAR_NEWLINE, CHAR_CARRIAGE_RETURN, CHAR_FORM_FEED, is_combinator, is_whitespace, CHAR_EQUALS, CHAR_CARET, CHAR_DOLLAR, CHAR_SINGLE_QUOTE, CHAR_DOUBLE_QUOTE, CHAR_COLON, is_vendor_prefixed, str_equals } from './string-utils.js';
5
+ import { CHAR_GREATER_THAN, CHAR_PLUS, CHAR_TILDE, CHAR_PERIOD, CHAR_ASTERISK, CHAR_AMPERSAND, CHAR_PIPE, CHAR_SPACE, CHAR_TAB, CHAR_NEWLINE, CHAR_CARRIAGE_RETURN, CHAR_FORM_FEED, is_combinator, is_whitespace, CHAR_EQUALS, CHAR_CARET, CHAR_DOLLAR, CHAR_SINGLE_QUOTE, CHAR_DOUBLE_QUOTE, CHAR_COLON, str_equals } from './string-utils.js';
6
6
  import { ANplusBParser } from './parse-anplusb.js';
7
7
  import { CSSNode } from './css-node.js';
8
8
 
@@ -44,7 +44,6 @@ class SelectorParser {
44
44
  next_sibling = this.arena.get_next_sibling(last_component);
45
45
  }
46
46
  this.arena.set_first_child(selector_wrapper, complex_selector);
47
- this.arena.set_last_child(selector_wrapper, last_component);
48
47
  selectors.push(selector_wrapper);
49
48
  }
50
49
  this.skip_whitespace();
@@ -109,7 +108,10 @@ class SelectorParser {
109
108
  }
110
109
  const saved = this.lexer.save_position();
111
110
  this.skip_whitespace();
112
- if (this.lexer.pos >= this.selector_end) break;
111
+ if (this.lexer.pos >= this.selector_end) {
112
+ this.lexer.restore_position(saved);
113
+ break;
114
+ }
113
115
  this.lexer.next_token_fast(false);
114
116
  let token_type = this.lexer.token_type;
115
117
  if (token_type === TOKEN_COMMA || this.lexer.pos >= this.selector_end) {
@@ -240,14 +242,17 @@ class SelectorParser {
240
242
  let has_whitespace = false;
241
243
  while (this.lexer.pos < this.selector_end) {
242
244
  let ch = this.source.charCodeAt(this.lexer.pos);
243
- if (ch === CHAR_SPACE || ch === CHAR_NEWLINE || ch === CHAR_CARRIAGE_RETURN || ch === CHAR_FORM_FEED) {
245
+ if (ch === CHAR_SPACE || ch === CHAR_TAB || ch === CHAR_NEWLINE || ch === CHAR_CARRIAGE_RETURN || ch === CHAR_FORM_FEED) {
244
246
  has_whitespace = true;
245
247
  this.lexer.pos++;
246
248
  } else {
247
249
  break;
248
250
  }
249
251
  }
250
- if (this.lexer.pos >= this.selector_end) return null;
252
+ if (this.lexer.pos >= this.selector_end) {
253
+ this.lexer.pos = whitespace_start;
254
+ return null;
255
+ }
251
256
  this.lexer.next_token_fast(false);
252
257
  if (this.lexer.token_type === TOKEN_DELIM) {
253
258
  let ch = this.source.charCodeAt(this.lexer.token_start);
@@ -259,7 +264,7 @@ class SelectorParser {
259
264
  this.lexer.pos = whitespace_start;
260
265
  while (this.lexer.pos < this.selector_end) {
261
266
  let ch = this.source.charCodeAt(this.lexer.pos);
262
- if (ch === CHAR_SPACE || ch === CHAR_NEWLINE || ch === CHAR_CARRIAGE_RETURN || ch === CHAR_FORM_FEED) {
267
+ if (ch === CHAR_SPACE || ch === CHAR_TAB || ch === CHAR_NEWLINE || ch === CHAR_CARRIAGE_RETURN || ch === CHAR_FORM_FEED) {
263
268
  this.lexer.pos++;
264
269
  } else {
265
270
  break;
@@ -422,9 +427,6 @@ class SelectorParser {
422
427
  let node = this.create_node(is_pseudo_element ? PSEUDO_ELEMENT_SELECTOR : PSEUDO_CLASS_SELECTOR, start, this.lexer.token_end);
423
428
  this.arena.set_content_start_delta(node, this.lexer.token_start - start);
424
429
  this.arena.set_content_length(node, this.lexer.token_end - this.lexer.token_start);
425
- if (is_vendor_prefixed(this.source, this.lexer.token_start, this.lexer.token_end)) {
426
- this.arena.set_flag(node, FLAG_VENDOR_PREFIXED);
427
- }
428
430
  return node;
429
431
  } else if (token_type === TOKEN_FUNCTION) {
430
432
  return this.parse_pseudo_function_after_colon(start, is_pseudo_element);
@@ -462,16 +464,12 @@ class SelectorParser {
462
464
  this.arena.set_content_start_delta(node, func_name_start - start);
463
465
  this.arena.set_content_length(node, func_name_end - func_name_start);
464
466
  this.arena.set_flag(node, FLAG_HAS_PARENS);
465
- if (is_vendor_prefixed(this.source, func_name_start, func_name_end)) {
466
- this.arena.set_flag(node, FLAG_VENDOR_PREFIXED);
467
- }
468
467
  if (content_end > content_start) {
469
468
  let func_name_substr = this.source.substring(func_name_start, func_name_end);
470
469
  if (this.is_nth_pseudo(func_name_substr)) {
471
470
  let child = this.parse_nth_expression(content_start, content_end);
472
471
  if (child !== null) {
473
472
  this.arena.set_first_child(node, child);
474
- this.arena.set_last_child(node, child);
475
473
  }
476
474
  } else if (str_equals("lang", func_name_substr)) {
477
475
  this.parse_lang_identifiers(content_start, content_end, node);
@@ -484,7 +482,6 @@ class SelectorParser {
484
482
  this.lexer.restore_position(saved);
485
483
  if (child_selector !== null) {
486
484
  this.arena.set_first_child(node, child_selector);
487
- this.arena.set_last_child(node, child_selector);
488
485
  }
489
486
  }
490
487
  }
@@ -531,9 +528,6 @@ class SelectorParser {
531
528
  if (first_child !== null) {
532
529
  this.arena.set_first_child(parent_node, first_child);
533
530
  }
534
- if (last_child !== null) {
535
- this.arena.set_last_child(parent_node, last_child);
536
- }
537
531
  this.selector_end = saved_selector_end;
538
532
  this.lexer.restore_position(saved);
539
533
  }
@@ -562,11 +556,9 @@ class SelectorParser {
562
556
  );
563
557
  if (anplusb_node !== null && selector_list !== null) {
564
558
  this.arena.set_first_child(of_node, anplusb_node);
565
- this.arena.set_last_child(of_node, selector_list);
566
559
  this.arena.set_next_sibling(anplusb_node, selector_list);
567
560
  } else if (anplusb_node !== null) {
568
561
  this.arena.set_first_child(of_node, anplusb_node);
569
- this.arena.set_last_child(of_node, anplusb_node);
570
562
  }
571
563
  return of_node;
572
564
  } else {
package/dist/parse.js CHANGED
@@ -1,11 +1,10 @@
1
1
  import { Lexer } from './lexer.js';
2
- import { CSSDataArena, STYLESHEET, STYLE_RULE, FLAG_HAS_BLOCK, BLOCK, FLAG_HAS_DECLARATIONS, SELECTOR_LIST, DECLARATION, FLAG_VENDOR_PREFIXED, FLAG_IMPORTANT, AT_RULE } from './arena.js';
2
+ import { CSSDataArena, STYLESHEET, STYLE_RULE, FLAG_HAS_BLOCK, BLOCK, FLAG_HAS_DECLARATIONS, SELECTOR_LIST, DECLARATION, FLAG_IMPORTANT, AT_RULE } from './arena.js';
3
3
  import { CSSNode } from './css-node.js';
4
4
  import { ValueParser } from './parse-value.js';
5
5
  import { SelectorParser } from './parse-selector.js';
6
6
  import { AtRulePreludeParser } from './parse-atrule-prelude.js';
7
7
  import { TOKEN_EOF, TOKEN_AT_KEYWORD, TOKEN_LEFT_BRACE, TOKEN_RIGHT_BRACE, TOKEN_IDENT, TOKEN_COLON, TOKEN_SEMICOLON, TOKEN_DELIM } from './token-types.js';
8
- import { is_vendor_prefixed } from './string-utils.js';
9
8
  import { trim_boundaries } from './parse-utils.js';
10
9
 
11
10
  let DECLARATION_AT_RULES = /* @__PURE__ */ new Set(["font-face", "font-feature-values", "page", "property", "counter-style"]);
@@ -213,9 +212,6 @@ class Parser {
213
212
  );
214
213
  this.arena.set_content_start_delta(declaration, 0);
215
214
  this.arena.set_content_length(declaration, prop_end - prop_start);
216
- if (is_vendor_prefixed(this.source, prop_start, prop_end)) {
217
- this.arena.set_flag(declaration, FLAG_VENDOR_PREFIXED);
218
- }
219
215
  let value_start = this.lexer.token_start;
220
216
  let value_end = value_start;
221
217
  let has_important = false;
@@ -65,4 +65,5 @@ export declare function str_index_of(str: string, searchChar: string): number;
65
65
  * - `--custom-property` → false (CSS custom property)
66
66
  * - `border-radius` → false (doesn't start with hyphen)
67
67
  */
68
+ export declare function is_vendor_prefixed(text: string): boolean;
68
69
  export declare function is_vendor_prefixed(source: string, start: number, end: number): boolean;
@@ -88,6 +88,10 @@ function str_index_of(str, searchChar) {
88
88
  return -1;
89
89
  }
90
90
  function is_vendor_prefixed(source, start, end) {
91
+ if (start === void 0 || end === void 0) {
92
+ start = 0;
93
+ end = source.length;
94
+ }
91
95
  if (source.charCodeAt(start) !== CHAR_MINUS_HYPHEN) {
92
96
  return false;
93
97
  }
package/dist/walk.d.ts CHANGED
@@ -1,20 +1,74 @@
1
1
  import type { CSSNode } from './css-node';
2
- type WalkCallback = (node: CSSNode, depth: number) => void;
2
+ export declare const SKIP: unique symbol;
3
+ export declare const BREAK: unique symbol;
4
+ type WalkCallback = (node: CSSNode, depth: number) => void | typeof SKIP | typeof BREAK;
3
5
  /**
4
6
  * Walk the AST in depth-first order, calling the callback for each node
7
+ *
5
8
  * @param node - The root node to start walking from
6
- * @param callback - Function to call for each node visited. Receives the node and its depth (0 for root)
9
+ * @param callback - Function to call for each node visited. Receives the node and its depth (0 for root).
10
+ * Return SKIP to skip children of current node, or BREAK to stop traversal entirely.
11
+ * @param depth - Starting depth (default: 0)
12
+ *
13
+ * @example
14
+ * import { parse, walk, SKIP, BREAK } from '@projectwallace/css-parser'
15
+ *
16
+ * const ast = parse('.a { .b { .c { color: red; } } }')
17
+ *
18
+ * // Skip nested rules
19
+ * walk(ast, (node) => {
20
+ * if (node.type === STYLE_RULE) {
21
+ * console.log(node.text)
22
+ * return SKIP // Don't visit nested rules
23
+ * }
24
+ * })
25
+ * // Output: .a { ... }, but not .b or .c
26
+ *
27
+ * // Stop on first declaration
28
+ * walk(ast, (node) => {
29
+ * if (node.type === DECLARATION) {
30
+ * console.log(node.name)
31
+ * return BREAK // Stop traversal
32
+ * }
33
+ * })
7
34
  */
8
- export declare function walk(node: CSSNode, callback: WalkCallback, depth?: number): void;
9
- type WalkEnterLeaveCallback = (node: CSSNode) => void;
35
+ export declare function walk(node: CSSNode, callback: WalkCallback, depth?: number): boolean;
36
+ type WalkEnterLeaveCallback = (node: CSSNode) => void | typeof SKIP | typeof BREAK;
10
37
  interface WalkEnterLeaveOptions {
11
38
  enter?: WalkEnterLeaveCallback;
12
39
  leave?: WalkEnterLeaveCallback;
13
40
  }
14
41
  /**
15
42
  * Walk the AST in depth-first order, calling enter before visiting children and leave after
43
+ *
16
44
  * @param node - The root node to start walking from
17
45
  * @param options - Object with optional enter and leave callback functions
46
+ * @param options.enter - Called before visiting children. Return SKIP to skip children (leave still called),
47
+ * or BREAK to stop traversal entirely (leave NOT called).
48
+ * @param options.leave - Called after visiting children. Return BREAK to stop traversal.
49
+ *
50
+ * @example
51
+ * import { parse, traverse, SKIP, BREAK } from '@projectwallace/css-parser'
52
+ *
53
+ * const ast = parse('@media screen { .a { color: red; } }')
54
+ *
55
+ * // Track context with skip
56
+ * let depth = 0
57
+ * traverse(ast, {
58
+ * enter(node) {
59
+ * depth++
60
+ * if (node.type === AT_RULE) {
61
+ * console.log('Entering media query at depth', depth)
62
+ * return SKIP // Skip contents but still call leave
63
+ * }
64
+ * },
65
+ * leave(node) {
66
+ * if (node.type === AT_RULE) {
67
+ * console.log('Leaving media query at depth', depth)
68
+ * }
69
+ * depth--
70
+ * }
71
+ * })
18
72
  */
19
- export declare function traverse(node: CSSNode, { enter, leave }?: WalkEnterLeaveOptions): void;
73
+ export declare function traverse(node: CSSNode, { enter, leave }?: WalkEnterLeaveOptions): boolean;
20
74
  export {};
package/dist/walk.js CHANGED
@@ -1,21 +1,45 @@
1
+ const SKIP = Symbol("SKIP");
2
+ const BREAK = Symbol("BREAK");
1
3
  function walk(node, callback, depth = 0) {
2
- callback(node, depth);
4
+ const result = callback(node, depth);
5
+ if (result === BREAK) {
6
+ return false;
7
+ }
8
+ if (result === SKIP) {
9
+ return true;
10
+ }
3
11
  let child = node.first_child;
4
12
  while (child) {
5
- walk(child, callback, depth + 1);
13
+ const should_continue = walk(child, callback, depth + 1);
14
+ if (!should_continue) {
15
+ return false;
16
+ }
6
17
  child = child.next_sibling;
7
18
  }
19
+ return true;
8
20
  }
9
21
  const NOOP = function() {
10
22
  };
11
23
  function traverse(node, { enter = NOOP, leave = NOOP } = {}) {
12
- enter(node);
13
- let child = node.first_child;
14
- while (child) {
15
- traverse(child, { enter, leave });
16
- child = child.next_sibling;
24
+ const enter_result = enter(node);
25
+ if (enter_result === BREAK) {
26
+ return false;
27
+ }
28
+ if (enter_result !== SKIP) {
29
+ let child = node.first_child;
30
+ while (child) {
31
+ const should_continue = traverse(child, { enter, leave });
32
+ if (!should_continue) {
33
+ return false;
34
+ }
35
+ child = child.next_sibling;
36
+ }
37
+ }
38
+ const leave_result = leave(node);
39
+ if (leave_result === BREAK) {
40
+ return false;
17
41
  }
18
- leave(node);
42
+ return true;
19
43
  }
20
44
 
21
- export { traverse, walk };
45
+ export { BREAK, SKIP, traverse, walk };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@projectwallace/css-parser",
3
- "version": "0.7.3",
3
+ "version": "0.8.1",
4
4
  "description": "High-performance CSS lexer and parser, optimized for CSS inspection and analysis",
5
5
  "author": "Bart Veneman <bat@projectwallace.com>",
6
6
  "license": "MIT",