@projectwallace/css-parser 0.6.7 → 0.7.0

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.
@@ -1,876 +0,0 @@
1
- let BYTES_PER_NODE = 44;
2
- const NODE_STYLESHEET = 1;
3
- const NODE_STYLE_RULE = 2;
4
- const NODE_AT_RULE = 3;
5
- const NODE_DECLARATION = 4;
6
- const NODE_SELECTOR = 5;
7
- const NODE_COMMENT = 6;
8
- const NODE_BLOCK = 7;
9
- const NODE_VALUE_KEYWORD = 10;
10
- const NODE_VALUE_NUMBER = 11;
11
- const NODE_VALUE_DIMENSION = 12;
12
- const NODE_VALUE_STRING = 13;
13
- const NODE_VALUE_COLOR = 14;
14
- const NODE_VALUE_FUNCTION = 15;
15
- const NODE_VALUE_OPERATOR = 16;
16
- const NODE_VALUE_PARENTHESIS = 17;
17
- const NODE_SELECTOR_LIST = 20;
18
- const NODE_SELECTOR_TYPE = 21;
19
- const NODE_SELECTOR_CLASS = 22;
20
- const NODE_SELECTOR_ID = 23;
21
- const NODE_SELECTOR_ATTRIBUTE = 24;
22
- const NODE_SELECTOR_PSEUDO_CLASS = 25;
23
- const NODE_SELECTOR_PSEUDO_ELEMENT = 26;
24
- const NODE_SELECTOR_COMBINATOR = 27;
25
- const NODE_SELECTOR_UNIVERSAL = 28;
26
- const NODE_SELECTOR_NESTING = 29;
27
- const NODE_SELECTOR_NTH = 30;
28
- const NODE_SELECTOR_NTH_OF = 31;
29
- const NODE_SELECTOR_LANG = 56;
30
- const NODE_PRELUDE_MEDIA_QUERY = 32;
31
- const NODE_PRELUDE_MEDIA_FEATURE = 33;
32
- const NODE_PRELUDE_MEDIA_TYPE = 34;
33
- const NODE_PRELUDE_CONTAINER_QUERY = 35;
34
- const NODE_PRELUDE_SUPPORTS_QUERY = 36;
35
- const NODE_PRELUDE_LAYER_NAME = 37;
36
- const NODE_PRELUDE_IDENTIFIER = 38;
37
- const NODE_PRELUDE_OPERATOR = 39;
38
- const NODE_PRELUDE_IMPORT_URL = 40;
39
- const NODE_PRELUDE_IMPORT_LAYER = 41;
40
- const NODE_PRELUDE_IMPORT_SUPPORTS = 42;
41
- const FLAG_IMPORTANT = 1 << 0;
42
- const FLAG_HAS_ERROR = 1 << 1;
43
- const FLAG_HAS_BLOCK = 1 << 3;
44
- const FLAG_VENDOR_PREFIXED = 1 << 4;
45
- const FLAG_HAS_DECLARATIONS = 1 << 5;
46
- const FLAG_HAS_PARENS = 1 << 6;
47
- const ATTR_OPERATOR_NONE = 0;
48
- const ATTR_OPERATOR_EQUAL = 1;
49
- const ATTR_OPERATOR_TILDE_EQUAL = 2;
50
- const ATTR_OPERATOR_PIPE_EQUAL = 3;
51
- const ATTR_OPERATOR_CARET_EQUAL = 4;
52
- const ATTR_OPERATOR_DOLLAR_EQUAL = 5;
53
- const ATTR_OPERATOR_STAR_EQUAL = 6;
54
- const ATTR_FLAG_NONE = 0;
55
- const ATTR_FLAG_CASE_INSENSITIVE = 1;
56
- const ATTR_FLAG_CASE_SENSITIVE = 2;
57
- class CSSDataArena {
58
- buffer;
59
- view;
60
- capacity;
61
- // Number of nodes that can fit
62
- count;
63
- // Number of nodes currently allocated
64
- // Growth multiplier when capacity is exceeded
65
- static GROWTH_FACTOR = 1.3;
66
- // Estimated nodes per KB of CSS (based on real-world data)
67
- static NODES_PER_KB = 60;
68
- // Buffer to avoid frequent growth (15%)
69
- static CAPACITY_BUFFER = 1.15;
70
- constructor(initial_capacity = 1024) {
71
- this.capacity = initial_capacity;
72
- this.count = 1;
73
- this.buffer = new ArrayBuffer(initial_capacity * BYTES_PER_NODE);
74
- this.view = new DataView(this.buffer);
75
- }
76
- // Calculate recommended initial capacity based on CSS source size
77
- static capacity_for_source(source_length) {
78
- let size_in_kb = source_length / 1024;
79
- let estimated_nodes = Math.ceil(size_in_kb * CSSDataArena.NODES_PER_KB);
80
- let capacity = Math.ceil(estimated_nodes * CSSDataArena.CAPACITY_BUFFER);
81
- return Math.max(16, capacity);
82
- }
83
- // Get the number of nodes currently in the arena
84
- get_count() {
85
- return this.count;
86
- }
87
- // Get the capacity (max nodes without reallocation)
88
- get_capacity() {
89
- return this.capacity;
90
- }
91
- // Calculate byte offset for a node
92
- node_offset(node_index) {
93
- return node_index * BYTES_PER_NODE;
94
- }
95
- // Read node type
96
- get_type(node_index) {
97
- return this.view.getUint8(this.node_offset(node_index));
98
- }
99
- // Read node flags
100
- get_flags(node_index) {
101
- return this.view.getUint8(this.node_offset(node_index) + 1);
102
- }
103
- // Read start offset in source
104
- get_start_offset(node_index) {
105
- return this.view.getUint32(this.node_offset(node_index) + 4, true);
106
- }
107
- // Read length in source
108
- get_length(node_index) {
109
- return this.view.getUint16(this.node_offset(node_index) + 8, true);
110
- }
111
- // Read content start offset
112
- get_content_start(node_index) {
113
- return this.view.getUint32(this.node_offset(node_index) + 12, true);
114
- }
115
- // Read content length
116
- get_content_length(node_index) {
117
- return this.view.getUint16(this.node_offset(node_index) + 16, true);
118
- }
119
- // Read attribute operator (for NODE_SELECTOR_ATTRIBUTE)
120
- get_attr_operator(node_index) {
121
- return this.view.getUint8(this.node_offset(node_index) + 2);
122
- }
123
- // Read attribute flags (for NODE_SELECTOR_ATTRIBUTE)
124
- get_attr_flags(node_index) {
125
- return this.view.getUint8(this.node_offset(node_index) + 3);
126
- }
127
- // Read first child index (0 = no children)
128
- get_first_child(node_index) {
129
- return this.view.getUint32(this.node_offset(node_index) + 20, true);
130
- }
131
- // Read last child index (0 = no children)
132
- get_last_child(node_index) {
133
- return this.view.getUint32(this.node_offset(node_index) + 24, true);
134
- }
135
- // Read next sibling index (0 = no sibling)
136
- get_next_sibling(node_index) {
137
- return this.view.getUint32(this.node_offset(node_index) + 28, true);
138
- }
139
- // Read start line
140
- get_start_line(node_index) {
141
- return this.view.getUint32(this.node_offset(node_index) + 32, true);
142
- }
143
- // Read start column
144
- get_start_column(node_index) {
145
- return this.view.getUint16(this.node_offset(node_index) + 42, true);
146
- }
147
- // Read value start offset (declaration value / at-rule prelude)
148
- get_value_start(node_index) {
149
- return this.view.getUint32(this.node_offset(node_index) + 36, true);
150
- }
151
- // Read value length
152
- get_value_length(node_index) {
153
- return this.view.getUint16(this.node_offset(node_index) + 40, true);
154
- }
155
- // --- Write Methods ---
156
- // Write node type
157
- set_type(node_index, type) {
158
- this.view.setUint8(this.node_offset(node_index), type);
159
- }
160
- // Write node flags
161
- set_flags(node_index, flags) {
162
- this.view.setUint8(this.node_offset(node_index) + 1, flags);
163
- }
164
- // Write start offset in source
165
- set_start_offset(node_index, offset) {
166
- this.view.setUint32(this.node_offset(node_index) + 4, offset, true);
167
- }
168
- // Write length in source
169
- set_length(node_index, length) {
170
- this.view.setUint16(this.node_offset(node_index) + 8, length, true);
171
- }
172
- // Write content start offset
173
- set_content_start(node_index, offset) {
174
- this.view.setUint32(this.node_offset(node_index) + 12, offset, true);
175
- }
176
- // Write content length
177
- set_content_length(node_index, length) {
178
- this.view.setUint16(this.node_offset(node_index) + 16, length, true);
179
- }
180
- // Write attribute operator (for NODE_SELECTOR_ATTRIBUTE)
181
- set_attr_operator(node_index, operator) {
182
- this.view.setUint8(this.node_offset(node_index) + 2, operator);
183
- }
184
- // Write attribute flags (for NODE_SELECTOR_ATTRIBUTE)
185
- set_attr_flags(node_index, flags) {
186
- this.view.setUint8(this.node_offset(node_index) + 3, flags);
187
- }
188
- // Write first child index
189
- set_first_child(node_index, childIndex) {
190
- this.view.setUint32(this.node_offset(node_index) + 20, childIndex, true);
191
- }
192
- // Write last child index
193
- set_last_child(node_index, childIndex) {
194
- this.view.setUint32(this.node_offset(node_index) + 24, childIndex, true);
195
- }
196
- // Write next sibling index
197
- set_next_sibling(node_index, siblingIndex) {
198
- this.view.setUint32(this.node_offset(node_index) + 28, siblingIndex, true);
199
- }
200
- // Write start line
201
- set_start_line(node_index, line) {
202
- this.view.setUint32(this.node_offset(node_index) + 32, line, true);
203
- }
204
- // Write start column
205
- set_start_column(node_index, column) {
206
- this.view.setUint16(this.node_offset(node_index) + 42, column, true);
207
- }
208
- // Write value start offset (declaration value / at-rule prelude)
209
- set_value_start(node_index, offset) {
210
- this.view.setUint32(this.node_offset(node_index) + 36, offset, true);
211
- }
212
- // Write value length
213
- set_value_length(node_index, length) {
214
- this.view.setUint16(this.node_offset(node_index) + 40, length, true);
215
- }
216
- // --- Node Creation ---
217
- // Grow the arena by 1.3x when capacity is exceeded
218
- grow() {
219
- let new_capacity = Math.ceil(this.capacity * CSSDataArena.GROWTH_FACTOR);
220
- let new_buffer = new ArrayBuffer(new_capacity * BYTES_PER_NODE);
221
- new Uint8Array(new_buffer).set(new Uint8Array(this.buffer));
222
- this.buffer = new_buffer;
223
- this.view = new DataView(new_buffer);
224
- this.capacity = new_capacity;
225
- }
226
- // Allocate a new node and return its index
227
- // The node is zero-initialized by default (ArrayBuffer guarantees this)
228
- // Automatically grows the arena if capacity is exceeded
229
- create_node() {
230
- if (this.count >= this.capacity) {
231
- this.grow();
232
- }
233
- let node_index = this.count;
234
- this.count++;
235
- return node_index;
236
- }
237
- // --- Tree Building Helpers ---
238
- // Add a child node to a parent node
239
- // This appends to the end of the child list using the sibling chain
240
- // O(1) operation using lastChild pointer
241
- append_child(parentIndex, childIndex) {
242
- let last_child = this.get_last_child(parentIndex);
243
- if (last_child === 0) {
244
- this.set_first_child(parentIndex, childIndex);
245
- this.set_last_child(parentIndex, childIndex);
246
- } else {
247
- this.set_next_sibling(last_child, childIndex);
248
- this.set_last_child(parentIndex, childIndex);
249
- }
250
- }
251
- // Check if a node has any children
252
- has_children(node_index) {
253
- return this.get_first_child(node_index) !== 0;
254
- }
255
- // Check if a node has a next sibling
256
- has_next_sibling(node_index) {
257
- return this.get_next_sibling(node_index) !== 0;
258
- }
259
- // --- Flag Management Helpers ---
260
- // Set a specific flag bit (doesn't clear other flags)
261
- set_flag(node_index, flag) {
262
- let current_flags = this.get_flags(node_index);
263
- this.set_flags(node_index, current_flags | flag);
264
- }
265
- // Clear a specific flag bit (doesn't affect other flags)
266
- clear_flag(node_index, flag) {
267
- let current_flags = this.get_flags(node_index);
268
- this.set_flags(node_index, current_flags & ~flag);
269
- }
270
- // Check if a specific flag is set
271
- has_flag(node_index, flag) {
272
- return (this.get_flags(node_index) & flag) !== 0;
273
- }
274
- }
275
-
276
- const CHAR_SPACE = 32;
277
- const CHAR_TAB = 9;
278
- const CHAR_NEWLINE = 10;
279
- const CHAR_CARRIAGE_RETURN = 13;
280
- const CHAR_FORM_FEED = 12;
281
- const CHAR_FORWARD_SLASH = 47;
282
- const CHAR_ASTERISK = 42;
283
- const CHAR_MINUS_HYPHEN = 45;
284
- const CHAR_SINGLE_QUOTE = 39;
285
- const CHAR_DOUBLE_QUOTE = 34;
286
- const CHAR_PLUS = 43;
287
- const CHAR_PERIOD = 46;
288
- const CHAR_TILDE = 126;
289
- const CHAR_GREATER_THAN = 62;
290
- const CHAR_AMPERSAND = 38;
291
- const CHAR_EQUALS = 61;
292
- const CHAR_PIPE = 124;
293
- const CHAR_DOLLAR = 36;
294
- const CHAR_CARET = 94;
295
- const CHAR_COLON = 58;
296
- function is_whitespace(ch) {
297
- return ch === CHAR_SPACE || ch === CHAR_TAB || ch === CHAR_NEWLINE || ch === CHAR_CARRIAGE_RETURN || ch === CHAR_FORM_FEED;
298
- }
299
- function is_combinator(ch) {
300
- return ch === CHAR_GREATER_THAN || ch === CHAR_PLUS || ch == CHAR_TILDE;
301
- }
302
- function is_digit(ch) {
303
- return ch >= 48 && ch <= 57;
304
- }
305
- function str_equals(a, b) {
306
- if (a.length !== b.length) {
307
- return false;
308
- }
309
- for (let i = 0; i < a.length; i++) {
310
- let ca = a.charCodeAt(i);
311
- let cb = b.charCodeAt(i);
312
- cb |= 32;
313
- if (ca !== cb) {
314
- return false;
315
- }
316
- }
317
- return true;
318
- }
319
- function is_vendor_prefixed(source, start, end) {
320
- if (source.charCodeAt(start) !== CHAR_MINUS_HYPHEN) {
321
- return false;
322
- }
323
- if (source.charCodeAt(start + 1) === CHAR_MINUS_HYPHEN) {
324
- return false;
325
- }
326
- let length = end - start;
327
- if (length < 3) {
328
- return false;
329
- }
330
- let secondHyphenPos = source.indexOf("-", start + 2);
331
- return secondHyphenPos !== -1 && secondHyphenPos < end;
332
- }
333
-
334
- function parse_dimension(text) {
335
- let numEnd = 0;
336
- for (let i = 0; i < text.length; i++) {
337
- let ch = text.charCodeAt(i);
338
- if (ch === 101 || ch === 69) {
339
- if (i + 1 < text.length) {
340
- let nextCh = text.charCodeAt(i + 1);
341
- if (is_digit(nextCh)) {
342
- numEnd = i + 1;
343
- continue;
344
- }
345
- if ((nextCh === 43 || nextCh === 45) && i + 2 < text.length) {
346
- let afterSign = text.charCodeAt(i + 2);
347
- if (is_digit(afterSign)) {
348
- numEnd = i + 1;
349
- continue;
350
- }
351
- }
352
- }
353
- break;
354
- }
355
- if (is_digit(ch) || ch === CHAR_PERIOD || ch === CHAR_MINUS_HYPHEN || ch === CHAR_PLUS) {
356
- numEnd = i + 1;
357
- } else {
358
- break;
359
- }
360
- }
361
- let numStr = text.substring(0, numEnd);
362
- let unit = text.substring(numEnd);
363
- let value = numStr ? parseFloat(numStr) : 0;
364
- return { value, unit };
365
- }
366
- function skip_whitespace_forward(source, pos, end) {
367
- while (pos < end && is_whitespace(source.charCodeAt(pos))) {
368
- pos++;
369
- }
370
- return pos;
371
- }
372
- function skip_whitespace_and_comments_forward(source, pos, end) {
373
- while (pos < end) {
374
- let ch = source.charCodeAt(pos);
375
- if (is_whitespace(ch)) {
376
- pos++;
377
- continue;
378
- }
379
- if (ch === CHAR_FORWARD_SLASH && pos + 1 < end && source.charCodeAt(pos + 1) === CHAR_ASTERISK) {
380
- pos += 2;
381
- while (pos < end) {
382
- if (source.charCodeAt(pos) === CHAR_ASTERISK && pos + 1 < end && source.charCodeAt(pos + 1) === CHAR_FORWARD_SLASH) {
383
- pos += 2;
384
- break;
385
- }
386
- pos++;
387
- }
388
- continue;
389
- }
390
- break;
391
- }
392
- return pos;
393
- }
394
- function skip_whitespace_and_comments_backward(source, pos, start) {
395
- while (pos > start) {
396
- let ch = source.charCodeAt(pos - 1);
397
- if (is_whitespace(ch)) {
398
- pos--;
399
- continue;
400
- }
401
- if (pos >= 2 && ch === CHAR_FORWARD_SLASH && source.charCodeAt(pos - 2) === CHAR_ASTERISK) {
402
- pos -= 2;
403
- while (pos > start) {
404
- if (pos >= 2 && source.charCodeAt(pos - 2) === CHAR_FORWARD_SLASH && source.charCodeAt(pos - 1) === CHAR_ASTERISK) {
405
- pos -= 2;
406
- break;
407
- }
408
- pos--;
409
- }
410
- continue;
411
- }
412
- break;
413
- }
414
- return pos;
415
- }
416
- function trim_boundaries(source, start, end) {
417
- start = skip_whitespace_and_comments_forward(source, start, end);
418
- end = skip_whitespace_and_comments_backward(source, end, start);
419
- if (start >= end) return null;
420
- return [start, end];
421
- }
422
-
423
- const TYPE_NAMES = {
424
- [NODE_STYLESHEET]: "stylesheet",
425
- [NODE_STYLE_RULE]: "rule",
426
- [NODE_AT_RULE]: "atrule",
427
- [NODE_DECLARATION]: "declaration",
428
- [NODE_SELECTOR]: "selector",
429
- [NODE_COMMENT]: "comment",
430
- [NODE_BLOCK]: "block",
431
- [NODE_VALUE_KEYWORD]: "keyword",
432
- [NODE_VALUE_NUMBER]: "number",
433
- [NODE_VALUE_DIMENSION]: "dimension",
434
- [NODE_VALUE_STRING]: "string",
435
- [NODE_VALUE_COLOR]: "color",
436
- [NODE_VALUE_FUNCTION]: "function",
437
- [NODE_VALUE_OPERATOR]: "operator",
438
- [NODE_VALUE_PARENTHESIS]: "parenthesis",
439
- [NODE_SELECTOR_LIST]: "selectorlist",
440
- [NODE_SELECTOR_TYPE]: "type-selector",
441
- [NODE_SELECTOR_CLASS]: "class-selector",
442
- [NODE_SELECTOR_ID]: "id-selector",
443
- [NODE_SELECTOR_ATTRIBUTE]: "attribute-selector",
444
- [NODE_SELECTOR_PSEUDO_CLASS]: "pseudoclass-selector",
445
- [NODE_SELECTOR_PSEUDO_ELEMENT]: "pseudoelement-selector",
446
- [NODE_SELECTOR_COMBINATOR]: "selector-combinator",
447
- [NODE_SELECTOR_UNIVERSAL]: "universal-selector",
448
- [NODE_SELECTOR_NESTING]: "nesting-selector",
449
- [NODE_SELECTOR_NTH]: "nth-selector",
450
- [NODE_SELECTOR_NTH_OF]: "nth-of-selector",
451
- [NODE_SELECTOR_LANG]: "lang-selector",
452
- [NODE_PRELUDE_MEDIA_QUERY]: "media-query",
453
- [NODE_PRELUDE_MEDIA_FEATURE]: "media-feature",
454
- [NODE_PRELUDE_MEDIA_TYPE]: "media-type",
455
- [NODE_PRELUDE_CONTAINER_QUERY]: "container-query",
456
- [NODE_PRELUDE_SUPPORTS_QUERY]: "supports-query",
457
- [NODE_PRELUDE_LAYER_NAME]: "layer-name",
458
- [NODE_PRELUDE_IDENTIFIER]: "identifier",
459
- [NODE_PRELUDE_OPERATOR]: "operator",
460
- [NODE_PRELUDE_IMPORT_URL]: "import-url",
461
- [NODE_PRELUDE_IMPORT_LAYER]: "import-layer",
462
- [NODE_PRELUDE_IMPORT_SUPPORTS]: "import-supports"
463
- };
464
- class CSSNode {
465
- arena;
466
- source;
467
- index;
468
- constructor(arena, source, index) {
469
- this.arena = arena;
470
- this.source = source;
471
- this.index = index;
472
- }
473
- // Get the node index (for internal use)
474
- get_index() {
475
- return this.index;
476
- }
477
- // Get node type as number (for performance)
478
- get type() {
479
- return this.arena.get_type(this.index);
480
- }
481
- // Get node type as human-readable string
482
- get type_name() {
483
- return TYPE_NAMES[this.type] || "unknown";
484
- }
485
- // Get the full text of this node from source
486
- get text() {
487
- let start = this.arena.get_start_offset(this.index);
488
- let length = this.arena.get_length(this.index);
489
- return this.source.substring(start, start + length);
490
- }
491
- // Get the "content" text (property name for declarations, at-rule name for at-rules, layer name for import layers)
492
- get name() {
493
- let start = this.arena.get_content_start(this.index);
494
- let length = this.arena.get_content_length(this.index);
495
- if (length === 0) return "";
496
- return this.source.substring(start, start + length);
497
- }
498
- // Alias for name (for declarations: "color" in "color: blue")
499
- // More semantic than `name` for declaration nodes
500
- get property() {
501
- return this.name;
502
- }
503
- // Get the value text (for declarations: "blue" in "color: blue")
504
- // For dimension/number nodes: returns the numeric value as a number
505
- // For string nodes: returns the string content without quotes
506
- get value() {
507
- if (this.type === NODE_VALUE_DIMENSION || this.type === NODE_VALUE_NUMBER) {
508
- return parse_dimension(this.text).value;
509
- }
510
- let start = this.arena.get_value_start(this.index);
511
- let length = this.arena.get_value_length(this.index);
512
- if (length === 0) return null;
513
- return this.source.substring(start, start + length);
514
- }
515
- // Get the prelude text (for at-rules: "(min-width: 768px)" in "@media (min-width: 768px)")
516
- // This is an alias for `value` to make at-rule usage more semantic
517
- get prelude() {
518
- let val = this.value;
519
- return typeof val === "string" ? val : null;
520
- }
521
- // Get the attribute operator (for attribute selectors: =, ~=, |=, ^=, $=, *=)
522
- // Returns one of the ATTR_OPERATOR_* constants
523
- get attr_operator() {
524
- return this.arena.get_attr_operator(this.index);
525
- }
526
- // Get the attribute flags (for attribute selectors: i, s)
527
- // Returns one of the ATTR_FLAG_* constants
528
- get attr_flags() {
529
- return this.arena.get_attr_flags(this.index);
530
- }
531
- // Get the unit for dimension nodes (e.g., "px" from "100px", "%" from "50%")
532
- get unit() {
533
- if (this.type !== NODE_VALUE_DIMENSION) return null;
534
- return parse_dimension(this.text).unit;
535
- }
536
- // Check if this declaration has !important
537
- get is_important() {
538
- if (this.type !== NODE_DECLARATION) return null;
539
- return this.arena.has_flag(this.index, FLAG_IMPORTANT);
540
- }
541
- // Check if this has a vendor prefix (flag-based for performance)
542
- get is_vendor_prefixed() {
543
- return this.arena.has_flag(this.index, FLAG_VENDOR_PREFIXED);
544
- }
545
- // Check if this node has an error
546
- get has_error() {
547
- return this.arena.has_flag(this.index, FLAG_HAS_ERROR);
548
- }
549
- // Check if this at-rule has a prelude
550
- get has_prelude() {
551
- return this.arena.get_value_length(this.index) > 0;
552
- }
553
- // Check if this rule has a block { }
554
- get has_block() {
555
- return this.arena.has_flag(this.index, FLAG_HAS_BLOCK);
556
- }
557
- // Check if this style rule has declarations
558
- get has_declarations() {
559
- return this.arena.has_flag(this.index, FLAG_HAS_DECLARATIONS);
560
- }
561
- // Get the block node (for style rules and at-rules with blocks)
562
- get block() {
563
- if (this.type === NODE_STYLE_RULE) {
564
- let first = this.first_child;
565
- if (!first) return null;
566
- let blockNode = first.next_sibling;
567
- if (blockNode && blockNode.type === NODE_BLOCK) {
568
- return blockNode;
569
- }
570
- return null;
571
- }
572
- if (this.type === NODE_AT_RULE) {
573
- let child = this.first_child;
574
- while (child) {
575
- if (child.type === NODE_BLOCK && !child.next_sibling) {
576
- return child;
577
- }
578
- child = child.next_sibling;
579
- }
580
- return null;
581
- }
582
- return null;
583
- }
584
- // Check if this block is empty (no declarations or rules, only comments allowed)
585
- get is_empty() {
586
- if (this.type !== NODE_BLOCK) return false;
587
- let child = this.first_child;
588
- while (child) {
589
- if (child.type !== NODE_COMMENT) {
590
- return false;
591
- }
592
- child = child.next_sibling;
593
- }
594
- return true;
595
- }
596
- // --- Value Node Access (for declarations) ---
597
- // Get array of parsed value nodes (for declarations only)
598
- get values() {
599
- let result = [];
600
- let child = this.first_child;
601
- while (child) {
602
- result.push(child);
603
- child = child.next_sibling;
604
- }
605
- return result;
606
- }
607
- // Get count of value nodes
608
- get value_count() {
609
- let count = 0;
610
- let child = this.first_child;
611
- while (child) {
612
- count++;
613
- child = child.next_sibling;
614
- }
615
- return count;
616
- }
617
- // Get start line number
618
- get line() {
619
- return this.arena.get_start_line(this.index);
620
- }
621
- // Get start column number
622
- get column() {
623
- return this.arena.get_start_column(this.index);
624
- }
625
- // Get start offset in source
626
- get offset() {
627
- return this.arena.get_start_offset(this.index);
628
- }
629
- // Get length in source
630
- get length() {
631
- return this.arena.get_length(this.index);
632
- }
633
- // --- Tree Traversal ---
634
- // Get first child node
635
- get first_child() {
636
- let child_index = this.arena.get_first_child(this.index);
637
- if (child_index === 0) return null;
638
- return new CSSNode(this.arena, this.source, child_index);
639
- }
640
- // Get next sibling node
641
- get next_sibling() {
642
- let sibling_index = this.arena.get_next_sibling(this.index);
643
- if (sibling_index === 0) return null;
644
- return new CSSNode(this.arena, this.source, sibling_index);
645
- }
646
- get has_next() {
647
- let sibling_index = this.arena.get_next_sibling(this.index);
648
- return sibling_index !== 0;
649
- }
650
- // Check if this node has children
651
- // For pseudo-class/pseudo-element functions, returns true if FLAG_HAS_PARENS is set
652
- // This allows formatters to distinguish :lang() from :hover
653
- get has_children() {
654
- if (this.type === NODE_SELECTOR_PSEUDO_CLASS || this.type === NODE_SELECTOR_PSEUDO_ELEMENT) {
655
- if (this.arena.has_flag(this.index, FLAG_HAS_PARENS)) {
656
- return true;
657
- }
658
- }
659
- return this.arena.has_children(this.index);
660
- }
661
- // Get all children as an array
662
- get children() {
663
- let result = [];
664
- let child = this.first_child;
665
- while (child) {
666
- result.push(child);
667
- child = child.next_sibling;
668
- }
669
- return result;
670
- }
671
- // Make CSSNode iterable over its children
672
- *[Symbol.iterator]() {
673
- let child = this.first_child;
674
- while (child) {
675
- yield child;
676
- child = child.next_sibling;
677
- }
678
- }
679
- // --- An+B Expression Helpers (for NODE_SELECTOR_NTH) ---
680
- // Get the 'a' coefficient from An+B expression (e.g., "2n" from "2n+1", "odd" from "odd")
681
- get nth_a() {
682
- if (this.type !== NODE_SELECTOR_NTH) return null;
683
- let len = this.arena.get_content_length(this.index);
684
- if (len === 0) return null;
685
- let start = this.arena.get_content_start(this.index);
686
- return this.source.substring(start, start + len);
687
- }
688
- // Get the 'b' coefficient from An+B expression (e.g., "+1" from "2n+1")
689
- get nth_b() {
690
- if (this.type !== NODE_SELECTOR_NTH) return null;
691
- let len = this.arena.get_value_length(this.index);
692
- if (len === 0) return null;
693
- let start = this.arena.get_value_start(this.index);
694
- let value = this.source.substring(start, start + len);
695
- let check_pos = start - 1;
696
- while (check_pos >= 0) {
697
- let ch = this.source.charCodeAt(check_pos);
698
- if (is_whitespace(ch)) {
699
- check_pos--;
700
- continue;
701
- }
702
- if (ch === CHAR_MINUS_HYPHEN) {
703
- value = "-" + value;
704
- } else if (ch === CHAR_PLUS) {
705
- value = "+" + value;
706
- }
707
- break;
708
- }
709
- return value;
710
- }
711
- // --- Pseudo-Class Nth-Of Helpers (for NODE_SELECTOR_NTH_OF) ---
712
- // Get the An+B formula node from :nth-child(2n+1 of .foo)
713
- get nth() {
714
- if (this.type !== NODE_SELECTOR_NTH_OF) return null;
715
- return this.first_child;
716
- }
717
- // Get the selector list from :nth-child(2n+1 of .foo)
718
- get selector() {
719
- if (this.type !== NODE_SELECTOR_NTH_OF) return null;
720
- let first = this.first_child;
721
- return first ? first.next_sibling : null;
722
- }
723
- // --- Pseudo-Class Selector List Helper ---
724
- // Get selector list from pseudo-class functions
725
- // Works for :is(.a), :not(.b), :has(.c), :where(.d), :nth-child(2n of .e)
726
- get selector_list() {
727
- if (this.type !== NODE_SELECTOR_PSEUDO_CLASS) return null;
728
- let child = this.first_child;
729
- if (!child) return null;
730
- if (child.type === NODE_SELECTOR_LIST) {
731
- return child;
732
- }
733
- if (child.type === NODE_SELECTOR_NTH_OF) {
734
- return child.selector;
735
- }
736
- return null;
737
- }
738
- // --- Compound Selector Helpers (for NODE_SELECTOR) ---
739
- // Iterator over first compound selector parts (zero allocation)
740
- // Yields parts before the first combinator
741
- *compound_parts() {
742
- if (this.type !== NODE_SELECTOR) return;
743
- let child = this.first_child;
744
- while (child) {
745
- if (child.type === NODE_SELECTOR_COMBINATOR) break;
746
- yield child;
747
- child = child.next_sibling;
748
- }
749
- }
750
- // Get first compound selector as array
751
- // Returns array of parts before first combinator
752
- get first_compound() {
753
- if (this.type !== NODE_SELECTOR) return [];
754
- let result = [];
755
- let child = this.first_child;
756
- while (child) {
757
- if (child.type === NODE_SELECTOR_COMBINATOR) break;
758
- result.push(child);
759
- child = child.next_sibling;
760
- }
761
- return result;
762
- }
763
- // Split selector into compound selectors
764
- // Returns array of compound arrays split by combinators
765
- get all_compounds() {
766
- if (this.type !== NODE_SELECTOR) return [];
767
- let compounds = [];
768
- let current_compound = [];
769
- let child = this.first_child;
770
- while (child) {
771
- if (child.type === NODE_SELECTOR_COMBINATOR) {
772
- if (current_compound.length > 0) {
773
- compounds.push(current_compound);
774
- current_compound = [];
775
- }
776
- } else {
777
- current_compound.push(child);
778
- }
779
- child = child.next_sibling;
780
- }
781
- if (current_compound.length > 0) {
782
- compounds.push(current_compound);
783
- }
784
- return compounds;
785
- }
786
- // Check if selector is compound (no combinators)
787
- get is_compound() {
788
- if (this.type !== NODE_SELECTOR) return false;
789
- let child = this.first_child;
790
- while (child) {
791
- if (child.type === NODE_SELECTOR_COMBINATOR) return false;
792
- child = child.next_sibling;
793
- }
794
- return true;
795
- }
796
- // Get text of first compound selector (no node allocation)
797
- get first_compound_text() {
798
- if (this.type !== NODE_SELECTOR) return "";
799
- let start = -1;
800
- let end = -1;
801
- let child = this.first_child;
802
- while (child) {
803
- if (child.type === NODE_SELECTOR_COMBINATOR) break;
804
- if (start === -1) start = child.offset;
805
- end = child.offset + child.length;
806
- child = child.next_sibling;
807
- }
808
- if (start === -1) return "";
809
- return this.source.substring(start, end);
810
- }
811
- // --- Node Cloning ---
812
- /**
813
- * Clone this node as a mutable plain JavaScript object
814
- *
815
- * Extracts all properties from the arena into a plain object with children as an array.
816
- * The resulting object can be freely modified.
817
- *
818
- * @param options - Cloning configuration
819
- * @param options.deep - Recursively clone children (default: true)
820
- * @param options.locations - Include line/column/offset/length (default: false)
821
- * @returns Plain object with children as array
822
- *
823
- * @example
824
- * const ast = parse('div { color: red; }')
825
- * const decl = ast.first_child.block.first_child
826
- * const plain = decl.clone()
827
- *
828
- * // Access children as array
829
- * plain.children.length
830
- * plain.children[0]
831
- * plain.children.push(newChild)
832
- */
833
- clone(options = {}) {
834
- const { deep = true, locations = false } = options;
835
- let plain = {
836
- type: this.type,
837
- type_name: this.type_name,
838
- text: this.text,
839
- children: []
840
- };
841
- if (this.name) plain.name = this.name;
842
- if (this.type === NODE_DECLARATION) plain.property = this.name;
843
- if (this.value !== void 0 && this.value !== null) {
844
- plain.value = this.value;
845
- if (this.unit) plain.unit = this.unit;
846
- }
847
- if (this.type === NODE_AT_RULE && this.prelude) {
848
- plain.prelude = this.prelude;
849
- }
850
- if (this.type === NODE_DECLARATION) plain.is_important = this.is_important;
851
- plain.is_vendor_prefixed = this.is_vendor_prefixed;
852
- plain.has_error = this.has_error;
853
- if (this.type === NODE_SELECTOR_ATTRIBUTE) {
854
- plain.attr_operator = this.attr_operator;
855
- plain.attr_flags = this.attr_flags;
856
- }
857
- if (this.type === NODE_SELECTOR_NTH || this.type === NODE_SELECTOR_NTH_OF) {
858
- plain.nth_a = this.nth_a;
859
- plain.nth_b = this.nth_b;
860
- }
861
- if (locations) {
862
- plain.line = this.line;
863
- plain.column = this.column;
864
- plain.offset = this.offset;
865
- plain.length = this.length;
866
- }
867
- if (deep) {
868
- for (let child of this.children) {
869
- plain.children.push(child.clone({ deep: true, locations }));
870
- }
871
- }
872
- return plain;
873
- }
874
- }
875
-
876
- export { NODE_BLOCK as $, ATTR_OPERATOR_NONE as A, NODE_SELECTOR_ATTRIBUTE as B, CSSNode as C, NODE_SELECTOR_PSEUDO_CLASS as D, NODE_SELECTOR_PSEUDO_ELEMENT as E, NODE_SELECTOR_COMBINATOR as F, NODE_SELECTOR_UNIVERSAL as G, NODE_SELECTOR_NESTING as H, NODE_SELECTOR_NTH as I, NODE_SELECTOR_NTH_OF as J, NODE_SELECTOR_LANG as K, NODE_PRELUDE_MEDIA_QUERY as L, NODE_PRELUDE_MEDIA_FEATURE as M, NODE_STYLE_RULE as N, NODE_PRELUDE_MEDIA_TYPE as O, NODE_PRELUDE_CONTAINER_QUERY as P, NODE_PRELUDE_SUPPORTS_QUERY as Q, NODE_PRELUDE_LAYER_NAME as R, NODE_PRELUDE_IDENTIFIER as S, TYPE_NAMES as T, NODE_PRELUDE_OPERATOR as U, NODE_PRELUDE_IMPORT_URL as V, NODE_PRELUDE_IMPORT_LAYER as W, NODE_PRELUDE_IMPORT_SUPPORTS as X, FLAG_IMPORTANT as Y, CSSDataArena as Z, FLAG_HAS_BLOCK as _, ATTR_OPERATOR_EQUAL as a, FLAG_HAS_DECLARATIONS as a0, is_vendor_prefixed as a1, FLAG_VENDOR_PREFIXED as a2, trim_boundaries as a3, CHAR_GREATER_THAN as a4, CHAR_PLUS as a5, CHAR_TILDE as a6, CHAR_PERIOD as a7, CHAR_ASTERISK as a8, CHAR_AMPERSAND as a9, CHAR_PIPE as aa, is_whitespace as ab, is_combinator as ac, skip_whitespace_and_comments_forward as ad, skip_whitespace_and_comments_backward as ae, CHAR_EQUALS as af, CHAR_CARET as ag, CHAR_DOLLAR as ah, CHAR_SINGLE_QUOTE as ai, CHAR_DOUBLE_QUOTE as aj, CHAR_COLON as ak, FLAG_HAS_PARENS as al, skip_whitespace_forward as am, str_equals as an, CHAR_MINUS_HYPHEN as ao, CHAR_FORWARD_SLASH as ap, ATTR_OPERATOR_TILDE_EQUAL as b, ATTR_OPERATOR_PIPE_EQUAL as c, ATTR_OPERATOR_CARET_EQUAL as d, ATTR_OPERATOR_DOLLAR_EQUAL as e, ATTR_OPERATOR_STAR_EQUAL as f, ATTR_FLAG_NONE as g, ATTR_FLAG_CASE_INSENSITIVE as h, ATTR_FLAG_CASE_SENSITIVE as i, NODE_AT_RULE as j, NODE_COMMENT as k, NODE_DECLARATION as l, NODE_SELECTOR as m, NODE_STYLESHEET as n, NODE_VALUE_KEYWORD as o, NODE_VALUE_NUMBER as p, NODE_VALUE_DIMENSION as q, NODE_VALUE_STRING as r, NODE_VALUE_COLOR as s, NODE_VALUE_FUNCTION as t, NODE_VALUE_OPERATOR as u, NODE_VALUE_PARENTHESIS as v, NODE_SELECTOR_LIST as w, NODE_SELECTOR_TYPE as x, NODE_SELECTOR_CLASS as y, NODE_SELECTOR_ID as z };