@projectwallace/css-parser 0.1.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.
package/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # CSS Parser
2
+
3
+ **High-performance CSS parser optimized for static analysis and formatting**
4
+
5
+ Built for speed and efficiency, this parser handles large CSS files with minimal memory overhead and blazing-fast parse times. Designed with a data-oriented architecture using a single contiguous memory arena for zero allocations during parsing.
6
+
7
+ ## Features
8
+
9
+ - **Modern CSS support** - CSS Nesting, `:is()`, `:where()`, `:has()`, `@layer`, `@container`
10
+ - **Error recovery** - Continues parsing on malformed CSS
11
+ - **Comment preservation** - Comments stored as first-class AST nodes
12
+ - **Location tracking** - Line, column, offset, and length for all nodes
13
+ - **Vendor prefix detection** - Automatic detection of `-webkit-`, `-moz-`, etc.
14
+ - **Structured parsing** - Deep parsing of selectors, values, and at-rule preludes
15
+
16
+ ## Performance
17
+
18
+ - **Tiny install size**
19
+ - **Zero allocations during parsing** - all memory allocated upfront based on real world heuristics, which also helps prevent garbage collection running often
20
+ - **Cache-friendly data layout** - contiguous memory for sequential access
21
+ - **First-class comment and location support** - while still being performant because analysis requires constant access to lines and columns
22
+ - **No syntax validation** - focusing only on the raw data we can skip expensive syntax files and MDN data syncs
23
+
24
+ This parser was heavily influenced by [CSSTree](https://github.com/csstree/csstree), one of the most robust CSS parsers available.
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ npm install @projectwallace/css-parser
30
+ ```
31
+
32
+ ## Usage
33
+
34
+ ```typescript
35
+ import { parse, NODE_STYLE_RULE, NODE_DECLARATION } from '@projectwallace/css-parser'
36
+
37
+ const ast = parse(`
38
+ body {
39
+ color: red;
40
+ margin: 0;
41
+ }
42
+
43
+ @media (min-width: 768px) {
44
+ .container {
45
+ max-width: 1200px;
46
+ }
47
+ }
48
+ `)
49
+
50
+ // Iterate over top-level rules
51
+ for (const rule of ast) {
52
+ if (rule.type === NODE_STYLE_RULE) {
53
+ const selector = rule.first_child
54
+ console.log(`Selector: ${selector.text}`)
55
+
56
+ // Iterate over declarations
57
+ for (const node of rule) {
58
+ if (node.type === NODE_DECLARATION) {
59
+ console.log(` ${node.property}: ${node.value}`)
60
+ }
61
+ }
62
+ }
63
+ }
64
+ ```
65
+
66
+ ## Documentation
67
+
68
+ See [API.md](./API.md) for complete documentation of all parser functions and options.
69
+
70
+ ## License
71
+
72
+ MIT
@@ -0,0 +1,84 @@
1
+ export declare const NODE_STYLESHEET = 1;
2
+ export declare const NODE_STYLE_RULE = 2;
3
+ export declare const NODE_AT_RULE = 3;
4
+ export declare const NODE_DECLARATION = 4;
5
+ export declare const NODE_SELECTOR = 5;
6
+ export declare const NODE_COMMENT = 6;
7
+ export declare const NODE_VALUE_KEYWORD = 10;
8
+ export declare const NODE_VALUE_NUMBER = 11;
9
+ export declare const NODE_VALUE_DIMENSION = 12;
10
+ export declare const NODE_VALUE_STRING = 13;
11
+ export declare const NODE_VALUE_COLOR = 14;
12
+ export declare const NODE_VALUE_FUNCTION = 15;
13
+ export declare const NODE_VALUE_OPERATOR = 16;
14
+ export declare const NODE_SELECTOR_LIST = 20;
15
+ export declare const NODE_SELECTOR_TYPE = 21;
16
+ export declare const NODE_SELECTOR_CLASS = 22;
17
+ export declare const NODE_SELECTOR_ID = 23;
18
+ export declare const NODE_SELECTOR_ATTRIBUTE = 24;
19
+ export declare const NODE_SELECTOR_PSEUDO_CLASS = 25;
20
+ export declare const NODE_SELECTOR_PSEUDO_ELEMENT = 26;
21
+ export declare const NODE_SELECTOR_COMBINATOR = 27;
22
+ export declare const NODE_SELECTOR_UNIVERSAL = 28;
23
+ export declare const NODE_SELECTOR_NESTING = 29;
24
+ export declare const NODE_PRELUDE_MEDIA_QUERY = 30;
25
+ export declare const NODE_PRELUDE_MEDIA_FEATURE = 31;
26
+ export declare const NODE_PRELUDE_MEDIA_TYPE = 32;
27
+ export declare const NODE_PRELUDE_CONTAINER_QUERY = 33;
28
+ export declare const NODE_PRELUDE_SUPPORTS_QUERY = 34;
29
+ export declare const NODE_PRELUDE_LAYER_NAME = 35;
30
+ export declare const NODE_PRELUDE_IDENTIFIER = 36;
31
+ export declare const NODE_PRELUDE_OPERATOR = 37;
32
+ export declare const NODE_PRELUDE_IMPORT_URL = 38;
33
+ export declare const NODE_PRELUDE_IMPORT_LAYER = 39;
34
+ export declare const NODE_PRELUDE_IMPORT_SUPPORTS = 40;
35
+ export declare const FLAG_IMPORTANT: number;
36
+ export declare const FLAG_HAS_ERROR: number;
37
+ export declare const FLAG_LENGTH_OVERFLOW: number;
38
+ export declare const FLAG_HAS_BLOCK: number;
39
+ export declare class CSSDataArena {
40
+ private buffer;
41
+ private view;
42
+ private capacity;
43
+ private count;
44
+ private static readonly GROWTH_FACTOR;
45
+ private static readonly NODES_PER_KB;
46
+ private static readonly CAPACITY_BUFFER;
47
+ constructor(initial_capacity?: number);
48
+ static capacity_for_source(source_length: number): number;
49
+ get_count(): number;
50
+ get_capacity(): number;
51
+ private node_offset;
52
+ get_type(node_index: number): number;
53
+ get_flags(node_index: number): number;
54
+ get_start_offset(node_index: number): number;
55
+ get_length(node_index: number): number;
56
+ get_content_start(node_index: number): number;
57
+ get_content_length(node_index: number): number;
58
+ get_first_child(node_index: number): number;
59
+ get_last_child(node_index: number): number;
60
+ get_next_sibling(node_index: number): number;
61
+ get_start_line(node_index: number): number;
62
+ get_value_start(node_index: number): number;
63
+ get_value_length(node_index: number): number;
64
+ set_type(node_index: number, type: number): void;
65
+ set_flags(node_index: number, flags: number): void;
66
+ set_start_offset(node_index: number, offset: number): void;
67
+ set_length(node_index: number, length: number): void;
68
+ set_content_start(node_index: number, offset: number): void;
69
+ set_content_length(node_index: number, length: number): void;
70
+ set_first_child(node_index: number, childIndex: number): void;
71
+ set_last_child(node_index: number, childIndex: number): void;
72
+ set_next_sibling(node_index: number, siblingIndex: number): void;
73
+ set_start_line(node_index: number, line: number): void;
74
+ set_value_start(node_index: number, offset: number): void;
75
+ set_value_length(node_index: number, length: number): void;
76
+ private grow;
77
+ create_node(): number;
78
+ append_child(parentIndex: number, childIndex: number): void;
79
+ has_children(node_index: number): boolean;
80
+ has_next_sibling(node_index: number): boolean;
81
+ set_flag(node_index: number, flag: number): void;
82
+ clear_flag(node_index: number, flag: number): void;
83
+ has_flag(node_index: number, flag: number): boolean;
84
+ }
@@ -0,0 +1,464 @@
1
+ import { L as Lexer, q as TOKEN_COMMA, T as TOKEN_IDENT, t as TOKEN_LEFT_PAREN, u as TOKEN_RIGHT_PAREN, l as TOKEN_WHITESPACE, f as TOKEN_URL, a as TOKEN_FUNCTION, d as TOKEN_STRING, y as TOKEN_EOF } from './lexer-DXablYMZ.js';
2
+ import { O as str_equals, E as NODE_PRELUDE_OPERATOR, y as NODE_PRELUDE_MEDIA_TYPE, w as NODE_PRELUDE_MEDIA_QUERY, x as NODE_PRELUDE_MEDIA_FEATURE, M as trim_boundaries, D as NODE_PRELUDE_IDENTIFIER, z as NODE_PRELUDE_CONTAINER_QUERY, A as NODE_PRELUDE_SUPPORTS_QUERY, B as NODE_PRELUDE_LAYER_NAME, F as NODE_PRELUDE_IMPORT_URL, G as NODE_PRELUDE_IMPORT_LAYER, H as NODE_PRELUDE_IMPORT_SUPPORTS, P as CHAR_SPACE, Q as CHAR_TAB, R as CHAR_NEWLINE, S as CHAR_CARRIAGE_RETURN, T as CHAR_FORM_FEED } from './string-utils-B-rJII-E.js';
3
+
4
+ class AtRulePreludeParser {
5
+ lexer;
6
+ arena;
7
+ source;
8
+ prelude_end;
9
+ constructor(arena, source) {
10
+ this.arena = arena;
11
+ this.source = source;
12
+ this.lexer = new Lexer(source, false);
13
+ this.prelude_end = 0;
14
+ }
15
+ // Parse an at-rule prelude into nodes based on the at-rule type
16
+ parse_prelude(at_rule_name, start, end, line = 1) {
17
+ this.prelude_end = end;
18
+ this.lexer.pos = start;
19
+ this.lexer.line = line;
20
+ if (str_equals("media", at_rule_name)) {
21
+ return this.parse_media_query_list();
22
+ } else if (str_equals("container", at_rule_name)) {
23
+ return this.parse_container_query();
24
+ } else if (str_equals("supports", at_rule_name)) {
25
+ return this.parse_supports_query();
26
+ } else if (str_equals("layer", at_rule_name)) {
27
+ return this.parse_layer_names();
28
+ } else if (str_equals("keyframes", at_rule_name)) {
29
+ return this.parse_identifier();
30
+ } else if (str_equals("property", at_rule_name)) {
31
+ return this.parse_identifier();
32
+ } else if (str_equals("import", at_rule_name)) {
33
+ return this.parse_import_prelude();
34
+ }
35
+ return [];
36
+ }
37
+ // Parse media query list: screen, (min-width: 768px), ...
38
+ parse_media_query_list() {
39
+ let nodes = [];
40
+ while (this.lexer.pos < this.prelude_end) {
41
+ this.skip_whitespace();
42
+ if (this.lexer.pos >= this.prelude_end) break;
43
+ let query = this.parse_single_media_query();
44
+ if (query !== null) {
45
+ nodes.push(query);
46
+ }
47
+ this.skip_whitespace();
48
+ if (this.peek_token_type() === TOKEN_COMMA) {
49
+ this.next_token();
50
+ }
51
+ }
52
+ return nodes;
53
+ }
54
+ is_and_or_not(str) {
55
+ if (str.length > 3 || str.length < 2) return false;
56
+ return str_equals("and", str) || str_equals("or", str) || str_equals("not", str);
57
+ }
58
+ // Parse a single media query: screen and (min-width: 768px)
59
+ parse_single_media_query() {
60
+ let query_start = this.lexer.pos;
61
+ let query_line = this.lexer.line;
62
+ this.skip_whitespace();
63
+ if (this.lexer.pos >= this.prelude_end) return null;
64
+ let token_start = this.lexer.pos;
65
+ this.next_token();
66
+ if (this.lexer.token_type === TOKEN_IDENT) {
67
+ let text = this.source.substring(this.lexer.token_start, this.lexer.token_end);
68
+ if (!str_equals("only", text) && !str_equals("not", text)) {
69
+ this.lexer.pos = token_start;
70
+ }
71
+ } else {
72
+ this.lexer.pos = token_start;
73
+ }
74
+ let components = [];
75
+ while (this.lexer.pos < this.prelude_end) {
76
+ this.skip_whitespace();
77
+ if (this.lexer.pos >= this.prelude_end) break;
78
+ if (this.peek_token_type() === TOKEN_COMMA) break;
79
+ this.next_token();
80
+ let token_type = this.lexer.token_type;
81
+ if (token_type === TOKEN_LEFT_PAREN) {
82
+ let feature = this.parse_media_feature();
83
+ if (feature !== null) {
84
+ components.push(feature);
85
+ }
86
+ } else if (token_type === TOKEN_IDENT) {
87
+ let text = this.source.substring(this.lexer.token_start, this.lexer.token_end);
88
+ if (this.is_and_or_not(text)) {
89
+ let op = this.arena.create_node();
90
+ this.arena.set_type(op, NODE_PRELUDE_OPERATOR);
91
+ this.arena.set_start_offset(op, this.lexer.token_start);
92
+ this.arena.set_length(op, this.lexer.token_end - this.lexer.token_start);
93
+ this.arena.set_start_line(op, this.lexer.token_line);
94
+ components.push(op);
95
+ } else {
96
+ let media_type = this.arena.create_node();
97
+ this.arena.set_type(media_type, NODE_PRELUDE_MEDIA_TYPE);
98
+ this.arena.set_start_offset(media_type, this.lexer.token_start);
99
+ this.arena.set_length(media_type, this.lexer.token_end - this.lexer.token_start);
100
+ this.arena.set_start_line(media_type, this.lexer.token_line);
101
+ components.push(media_type);
102
+ }
103
+ } else {
104
+ break;
105
+ }
106
+ }
107
+ if (components.length === 0) return null;
108
+ let query_node = this.arena.create_node();
109
+ this.arena.set_type(query_node, NODE_PRELUDE_MEDIA_QUERY);
110
+ this.arena.set_start_offset(query_node, query_start);
111
+ this.arena.set_length(query_node, this.lexer.pos - query_start);
112
+ this.arena.set_start_line(query_node, query_line);
113
+ for (let component of components) {
114
+ this.arena.append_child(query_node, component);
115
+ }
116
+ return query_node;
117
+ }
118
+ // Parse media feature: (min-width: 768px)
119
+ parse_media_feature() {
120
+ let feature_start = this.lexer.token_start;
121
+ let feature_line = this.lexer.token_line;
122
+ let depth = 1;
123
+ let content_start = this.lexer.pos;
124
+ while (this.lexer.pos < this.prelude_end && depth > 0) {
125
+ this.next_token();
126
+ let token_type = this.lexer.token_type;
127
+ if (token_type === TOKEN_LEFT_PAREN) {
128
+ depth++;
129
+ } else if (token_type === TOKEN_RIGHT_PAREN) {
130
+ depth--;
131
+ }
132
+ }
133
+ if (depth !== 0) return null;
134
+ let content_end = this.lexer.token_start;
135
+ let feature_end = this.lexer.token_end;
136
+ let feature = this.arena.create_node();
137
+ this.arena.set_type(feature, NODE_PRELUDE_MEDIA_FEATURE);
138
+ this.arena.set_start_offset(feature, feature_start);
139
+ this.arena.set_length(feature, feature_end - feature_start);
140
+ this.arena.set_start_line(feature, feature_line);
141
+ let trimmed = trim_boundaries(this.source, content_start, content_end);
142
+ if (trimmed) {
143
+ this.arena.set_value_start(feature, trimmed[0]);
144
+ this.arena.set_value_length(feature, trimmed[1] - trimmed[0]);
145
+ }
146
+ return feature;
147
+ }
148
+ // Parse container query: [name] and (min-width: 400px)
149
+ parse_container_query() {
150
+ let nodes = [];
151
+ let query_start = this.lexer.pos;
152
+ let query_line = this.lexer.line;
153
+ let components = [];
154
+ while (this.lexer.pos < this.prelude_end) {
155
+ this.skip_whitespace();
156
+ if (this.lexer.pos >= this.prelude_end) break;
157
+ this.next_token();
158
+ let token_type = this.lexer.token_type;
159
+ if (token_type === TOKEN_LEFT_PAREN) {
160
+ let feature = this.parse_media_feature();
161
+ if (feature !== null) {
162
+ components.push(feature);
163
+ }
164
+ } else if (token_type === TOKEN_IDENT) {
165
+ let text = this.source.substring(this.lexer.token_start, this.lexer.token_end);
166
+ if (this.is_and_or_not(text)) {
167
+ let op = this.arena.create_node();
168
+ this.arena.set_type(op, NODE_PRELUDE_OPERATOR);
169
+ this.arena.set_start_offset(op, this.lexer.token_start);
170
+ this.arena.set_length(op, this.lexer.token_end - this.lexer.token_start);
171
+ this.arena.set_start_line(op, this.lexer.token_line);
172
+ components.push(op);
173
+ } else {
174
+ let name = this.arena.create_node();
175
+ this.arena.set_type(name, NODE_PRELUDE_IDENTIFIER);
176
+ this.arena.set_start_offset(name, this.lexer.token_start);
177
+ this.arena.set_length(name, this.lexer.token_end - this.lexer.token_start);
178
+ this.arena.set_start_line(name, this.lexer.token_line);
179
+ components.push(name);
180
+ }
181
+ }
182
+ }
183
+ if (components.length === 0) return [];
184
+ let query_node = this.arena.create_node();
185
+ this.arena.set_type(query_node, NODE_PRELUDE_CONTAINER_QUERY);
186
+ this.arena.set_start_offset(query_node, query_start);
187
+ this.arena.set_length(query_node, this.lexer.pos - query_start);
188
+ this.arena.set_start_line(query_node, query_line);
189
+ for (let component of components) {
190
+ this.arena.append_child(query_node, component);
191
+ }
192
+ nodes.push(query_node);
193
+ return nodes;
194
+ }
195
+ // Parse supports query: (display: flex) and (gap: 1rem)
196
+ parse_supports_query() {
197
+ let nodes = [];
198
+ while (this.lexer.pos < this.prelude_end) {
199
+ this.skip_whitespace();
200
+ if (this.lexer.pos >= this.prelude_end) break;
201
+ this.next_token();
202
+ let token_type = this.lexer.token_type;
203
+ if (token_type === TOKEN_LEFT_PAREN) {
204
+ let feature_start = this.lexer.token_start;
205
+ let feature_line = this.lexer.token_line;
206
+ let depth = 1;
207
+ let content_start = this.lexer.pos;
208
+ while (this.lexer.pos < this.prelude_end && depth > 0) {
209
+ this.next_token();
210
+ let inner_token_type = this.lexer.token_type;
211
+ if (inner_token_type === TOKEN_LEFT_PAREN) {
212
+ depth++;
213
+ } else if (inner_token_type === TOKEN_RIGHT_PAREN) {
214
+ depth--;
215
+ }
216
+ }
217
+ if (depth === 0) {
218
+ let content_end = this.lexer.token_start;
219
+ let feature_end = this.lexer.token_end;
220
+ let query = this.arena.create_node();
221
+ this.arena.set_type(query, NODE_PRELUDE_SUPPORTS_QUERY);
222
+ this.arena.set_start_offset(query, feature_start);
223
+ this.arena.set_length(query, feature_end - feature_start);
224
+ this.arena.set_start_line(query, feature_line);
225
+ let trimmed = trim_boundaries(this.source, content_start, content_end);
226
+ if (trimmed) {
227
+ this.arena.set_value_start(query, trimmed[0]);
228
+ this.arena.set_value_length(query, trimmed[1] - trimmed[0]);
229
+ }
230
+ nodes.push(query);
231
+ }
232
+ } else if (token_type === TOKEN_IDENT) {
233
+ let text = this.source.substring(this.lexer.token_start, this.lexer.token_end);
234
+ if (this.is_and_or_not(text)) {
235
+ let op = this.arena.create_node();
236
+ this.arena.set_type(op, NODE_PRELUDE_OPERATOR);
237
+ this.arena.set_start_offset(op, this.lexer.token_start);
238
+ this.arena.set_length(op, this.lexer.token_end - this.lexer.token_start);
239
+ this.arena.set_start_line(op, this.lexer.token_line);
240
+ nodes.push(op);
241
+ }
242
+ }
243
+ }
244
+ return nodes;
245
+ }
246
+ // Parse layer names: base, components, utilities
247
+ parse_layer_names() {
248
+ let nodes = [];
249
+ while (this.lexer.pos < this.prelude_end) {
250
+ this.skip_whitespace();
251
+ if (this.lexer.pos >= this.prelude_end) break;
252
+ this.next_token();
253
+ let token_type = this.lexer.token_type;
254
+ if (token_type === TOKEN_IDENT) {
255
+ let layer = this.arena.create_node();
256
+ this.arena.set_type(layer, NODE_PRELUDE_LAYER_NAME);
257
+ this.arena.set_start_offset(layer, this.lexer.token_start);
258
+ this.arena.set_length(layer, this.lexer.token_end - this.lexer.token_start);
259
+ this.arena.set_start_line(layer, this.lexer.token_line);
260
+ nodes.push(layer);
261
+ } else if (token_type === TOKEN_COMMA) {
262
+ continue;
263
+ } else if (token_type === TOKEN_WHITESPACE) {
264
+ continue;
265
+ }
266
+ }
267
+ return nodes;
268
+ }
269
+ // Parse single identifier: keyframe name, property name
270
+ parse_identifier() {
271
+ this.skip_whitespace();
272
+ if (this.lexer.pos >= this.prelude_end) return [];
273
+ this.next_token();
274
+ if (this.lexer.token_type !== TOKEN_IDENT) return [];
275
+ let ident = this.arena.create_node();
276
+ this.arena.set_type(ident, NODE_PRELUDE_IDENTIFIER);
277
+ this.arena.set_start_offset(ident, this.lexer.token_start);
278
+ this.arena.set_length(ident, this.lexer.token_end - this.lexer.token_start);
279
+ this.arena.set_start_line(ident, this.lexer.token_line);
280
+ return [ident];
281
+ }
282
+ // Parse @import prelude: url() [layer] [supports()] [media-query-list]
283
+ // @import url("styles.css") layer(base) supports(display: grid) screen and (min-width: 768px);
284
+ parse_import_prelude() {
285
+ let nodes = [];
286
+ this.skip_whitespace();
287
+ if (this.lexer.pos >= this.prelude_end) return [];
288
+ let url_node = this.parse_import_url();
289
+ if (url_node !== null) {
290
+ nodes.push(url_node);
291
+ } else {
292
+ return [];
293
+ }
294
+ this.skip_whitespace();
295
+ if (this.lexer.pos >= this.prelude_end) return nodes;
296
+ let layer_node = this.parse_import_layer();
297
+ if (layer_node !== null) {
298
+ nodes.push(layer_node);
299
+ }
300
+ this.skip_whitespace();
301
+ if (this.lexer.pos >= this.prelude_end) return nodes;
302
+ let supports_node = this.parse_import_supports();
303
+ if (supports_node !== null) {
304
+ nodes.push(supports_node);
305
+ }
306
+ this.skip_whitespace();
307
+ if (this.lexer.pos >= this.prelude_end) return nodes;
308
+ let media_nodes = this.parse_media_query_list();
309
+ nodes.push(...media_nodes);
310
+ return nodes;
311
+ }
312
+ // Parse import URL: url("file.css") or "file.css"
313
+ parse_import_url() {
314
+ this.next_token();
315
+ if (this.lexer.token_type !== TOKEN_URL && this.lexer.token_type !== TOKEN_FUNCTION && this.lexer.token_type !== TOKEN_STRING) {
316
+ return null;
317
+ }
318
+ let url_start = this.lexer.token_start;
319
+ let url_end = this.lexer.token_end;
320
+ let url_line = this.lexer.token_line;
321
+ if (this.lexer.token_type === TOKEN_FUNCTION) {
322
+ let paren_depth = 1;
323
+ while (this.lexer.pos < this.prelude_end && paren_depth > 0) {
324
+ let tokenType = this.next_token();
325
+ if (tokenType === TOKEN_LEFT_PAREN) {
326
+ paren_depth++;
327
+ } else if (tokenType === TOKEN_RIGHT_PAREN) {
328
+ paren_depth--;
329
+ if (paren_depth === 0) {
330
+ url_end = this.lexer.token_end;
331
+ }
332
+ } else if (tokenType === TOKEN_EOF) {
333
+ break;
334
+ }
335
+ }
336
+ }
337
+ let url_node = this.arena.create_node();
338
+ this.arena.set_type(url_node, NODE_PRELUDE_IMPORT_URL);
339
+ this.arena.set_start_offset(url_node, url_start);
340
+ this.arena.set_length(url_node, url_end - url_start);
341
+ this.arena.set_start_line(url_node, url_line);
342
+ return url_node;
343
+ }
344
+ // Parse import layer: layer or layer(name)
345
+ parse_import_layer() {
346
+ let saved_pos = this.lexer.pos;
347
+ let saved_line = this.lexer.line;
348
+ this.next_token();
349
+ if (this.lexer.token_type === TOKEN_IDENT || this.lexer.token_type === TOKEN_FUNCTION) {
350
+ let text = this.source.substring(this.lexer.token_start, this.lexer.token_end);
351
+ if (this.lexer.token_type === TOKEN_FUNCTION && text.endsWith("(")) {
352
+ text = text.slice(0, -1);
353
+ }
354
+ if (str_equals("layer", text)) {
355
+ let layer_start = this.lexer.token_start;
356
+ let layer_end = this.lexer.token_end;
357
+ let layer_line = this.lexer.token_line;
358
+ let content_start = 0;
359
+ let content_length = 0;
360
+ if (this.lexer.token_type === TOKEN_FUNCTION) {
361
+ content_start = this.lexer.pos;
362
+ let paren_depth = 1;
363
+ while (this.lexer.pos < this.prelude_end && paren_depth > 0) {
364
+ let tokenType = this.next_token();
365
+ if (tokenType === TOKEN_LEFT_PAREN) {
366
+ paren_depth++;
367
+ } else if (tokenType === TOKEN_RIGHT_PAREN) {
368
+ paren_depth--;
369
+ if (paren_depth === 0) {
370
+ content_length = this.lexer.token_start - content_start;
371
+ layer_end = this.lexer.token_end;
372
+ }
373
+ } else if (tokenType === TOKEN_EOF) {
374
+ break;
375
+ }
376
+ }
377
+ }
378
+ let layer_node = this.arena.create_node();
379
+ this.arena.set_type(layer_node, NODE_PRELUDE_IMPORT_LAYER);
380
+ this.arena.set_start_offset(layer_node, layer_start);
381
+ this.arena.set_length(layer_node, layer_end - layer_start);
382
+ this.arena.set_start_line(layer_node, layer_line);
383
+ if (content_length > 0) {
384
+ let trimmed = trim_boundaries(this.source, content_start, content_start + content_length);
385
+ if (trimmed) {
386
+ this.arena.set_content_start(layer_node, trimmed[0]);
387
+ this.arena.set_content_length(layer_node, trimmed[1] - trimmed[0]);
388
+ }
389
+ }
390
+ return layer_node;
391
+ }
392
+ }
393
+ this.lexer.pos = saved_pos;
394
+ this.lexer.line = saved_line;
395
+ return null;
396
+ }
397
+ // Parse import supports: supports(condition)
398
+ parse_import_supports() {
399
+ let saved_pos = this.lexer.pos;
400
+ let saved_line = this.lexer.line;
401
+ this.next_token();
402
+ if (this.lexer.token_type === TOKEN_FUNCTION) {
403
+ let text = this.source.substring(this.lexer.token_start, this.lexer.token_end - 1);
404
+ if (str_equals("supports", text)) {
405
+ let supports_start = this.lexer.token_start;
406
+ let supports_line = this.lexer.token_line;
407
+ let paren_depth = 1;
408
+ let supports_end = this.lexer.token_end;
409
+ while (this.lexer.pos < this.prelude_end && paren_depth > 0) {
410
+ let tokenType = this.next_token();
411
+ if (tokenType === TOKEN_LEFT_PAREN) {
412
+ paren_depth++;
413
+ } else if (tokenType === TOKEN_RIGHT_PAREN) {
414
+ paren_depth--;
415
+ if (paren_depth === 0) {
416
+ supports_end = this.lexer.token_end;
417
+ }
418
+ } else if (tokenType === TOKEN_EOF) {
419
+ break;
420
+ }
421
+ }
422
+ let supports_node = this.arena.create_node();
423
+ this.arena.set_type(supports_node, NODE_PRELUDE_IMPORT_SUPPORTS);
424
+ this.arena.set_start_offset(supports_node, supports_start);
425
+ this.arena.set_length(supports_node, supports_end - supports_start);
426
+ this.arena.set_start_line(supports_node, supports_line);
427
+ return supports_node;
428
+ }
429
+ }
430
+ this.lexer.pos = saved_pos;
431
+ this.lexer.line = saved_line;
432
+ return null;
433
+ }
434
+ // Helper: Skip whitespace
435
+ skip_whitespace() {
436
+ while (this.lexer.pos < this.prelude_end) {
437
+ let ch = this.source.charCodeAt(this.lexer.pos);
438
+ if (ch !== CHAR_SPACE && ch !== CHAR_TAB && ch !== CHAR_NEWLINE && ch !== CHAR_CARRIAGE_RETURN && ch !== CHAR_FORM_FEED) {
439
+ break;
440
+ }
441
+ this.lexer.pos++;
442
+ }
443
+ }
444
+ // Helper: Peek at next token type without consuming
445
+ peek_token_type() {
446
+ let saved_pos = this.lexer.pos;
447
+ let saved_line = this.lexer.line;
448
+ this.next_token();
449
+ let type = this.lexer.token_type;
450
+ this.lexer.pos = saved_pos;
451
+ this.lexer.line = saved_line;
452
+ return type;
453
+ }
454
+ // Helper: Get next token
455
+ next_token() {
456
+ if (this.lexer.pos >= this.prelude_end) {
457
+ this.lexer.token_type = TOKEN_EOF;
458
+ return TOKEN_EOF;
459
+ }
460
+ return this.lexer.next_token_fast(false);
461
+ }
462
+ }
463
+
464
+ export { AtRulePreludeParser as A };
@@ -0,0 +1,24 @@
1
+ import type { CSSDataArena } from './arena';
2
+ export declare class AtRulePreludeParser {
3
+ private lexer;
4
+ private arena;
5
+ private source;
6
+ private prelude_end;
7
+ constructor(arena: CSSDataArena, source: string);
8
+ parse_prelude(at_rule_name: string, start: number, end: number, line?: number): number[];
9
+ private parse_media_query_list;
10
+ private is_and_or_not;
11
+ private parse_single_media_query;
12
+ private parse_media_feature;
13
+ private parse_container_query;
14
+ private parse_supports_query;
15
+ private parse_layer_names;
16
+ private parse_identifier;
17
+ private parse_import_prelude;
18
+ private parse_import_url;
19
+ private parse_import_layer;
20
+ private parse_import_supports;
21
+ private skip_whitespace;
22
+ private peek_token_type;
23
+ private next_token;
24
+ }
@@ -0,0 +1,7 @@
1
+ export declare function is_digit(ch: number): boolean;
2
+ export declare function is_hex_digit(ch: number): boolean;
3
+ export declare function is_alpha(ch: number): boolean;
4
+ export declare function is_whitespace(ch: number): boolean;
5
+ export declare function is_newline(ch: number): boolean;
6
+ export declare function is_ident_start(ch: number): boolean;
7
+ export declare function is_ident_char(ch: number): boolean;
@@ -0,0 +1,31 @@
1
+ import type { CSSDataArena } from './arena';
2
+ import { NODE_STYLESHEET, NODE_STYLE_RULE, NODE_AT_RULE, NODE_DECLARATION, NODE_SELECTOR, NODE_COMMENT, NODE_VALUE_KEYWORD, NODE_VALUE_NUMBER, NODE_VALUE_DIMENSION, NODE_VALUE_STRING, NODE_VALUE_COLOR, NODE_VALUE_FUNCTION, NODE_VALUE_OPERATOR, NODE_SELECTOR_LIST, NODE_SELECTOR_TYPE, NODE_SELECTOR_CLASS, NODE_SELECTOR_ID, NODE_SELECTOR_ATTRIBUTE, NODE_SELECTOR_PSEUDO_CLASS, NODE_SELECTOR_PSEUDO_ELEMENT, NODE_SELECTOR_COMBINATOR, NODE_SELECTOR_UNIVERSAL, NODE_SELECTOR_NESTING, NODE_PRELUDE_MEDIA_QUERY, NODE_PRELUDE_MEDIA_FEATURE, NODE_PRELUDE_MEDIA_TYPE, NODE_PRELUDE_CONTAINER_QUERY, NODE_PRELUDE_SUPPORTS_QUERY, NODE_PRELUDE_LAYER_NAME, NODE_PRELUDE_IDENTIFIER, NODE_PRELUDE_OPERATOR, NODE_PRELUDE_IMPORT_URL, NODE_PRELUDE_IMPORT_LAYER, NODE_PRELUDE_IMPORT_SUPPORTS } from './arena';
3
+ export type CSSNodeType = typeof NODE_STYLESHEET | typeof NODE_STYLE_RULE | typeof NODE_AT_RULE | typeof NODE_DECLARATION | typeof NODE_SELECTOR | typeof NODE_COMMENT | typeof NODE_VALUE_KEYWORD | typeof NODE_VALUE_NUMBER | typeof NODE_VALUE_DIMENSION | typeof NODE_VALUE_STRING | typeof NODE_VALUE_COLOR | typeof NODE_VALUE_FUNCTION | typeof NODE_VALUE_OPERATOR | typeof NODE_SELECTOR_LIST | typeof NODE_SELECTOR_TYPE | typeof NODE_SELECTOR_CLASS | typeof NODE_SELECTOR_ID | typeof NODE_SELECTOR_ATTRIBUTE | typeof NODE_SELECTOR_PSEUDO_CLASS | typeof NODE_SELECTOR_PSEUDO_ELEMENT | typeof NODE_SELECTOR_COMBINATOR | typeof NODE_SELECTOR_UNIVERSAL | typeof NODE_SELECTOR_NESTING | typeof NODE_PRELUDE_MEDIA_QUERY | typeof NODE_PRELUDE_MEDIA_FEATURE | typeof NODE_PRELUDE_MEDIA_TYPE | typeof NODE_PRELUDE_CONTAINER_QUERY | typeof NODE_PRELUDE_SUPPORTS_QUERY | typeof NODE_PRELUDE_LAYER_NAME | typeof NODE_PRELUDE_IDENTIFIER | typeof NODE_PRELUDE_OPERATOR | typeof NODE_PRELUDE_IMPORT_URL | typeof NODE_PRELUDE_IMPORT_LAYER | typeof NODE_PRELUDE_IMPORT_SUPPORTS;
4
+ export declare class CSSNode {
5
+ private arena;
6
+ private source;
7
+ private index;
8
+ constructor(arena: CSSDataArena, source: string, index: number);
9
+ get_index(): number;
10
+ get type(): CSSNodeType;
11
+ get text(): string;
12
+ get name(): string;
13
+ get property(): string;
14
+ get value(): string | null;
15
+ get prelude(): string | null;
16
+ get is_important(): boolean;
17
+ get is_vendor_prefixed(): boolean;
18
+ get has_error(): boolean;
19
+ get has_prelude(): boolean;
20
+ get has_block(): boolean;
21
+ get values(): CSSNode[];
22
+ get value_count(): number;
23
+ get line(): number;
24
+ get offset(): number;
25
+ get length(): number;
26
+ get first_child(): CSSNode | null;
27
+ get next_sibling(): CSSNode | null;
28
+ get has_children(): boolean;
29
+ get children(): CSSNode[];
30
+ [Symbol.iterator](): Iterator<CSSNode>;
31
+ }