@projectwallace/css-parser 0.18.2 → 0.18.5
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-CFwM4uQs.js → arena-DKhfTFT5.js} +3 -1
- package/dist/{arena-Bx5vp6Sl.d.ts → arena-DcrS77hW.d.ts} +3 -1
- package/dist/constants.d.ts +4 -2
- package/dist/constants.js +5 -3
- package/dist/{css-node-Cf8K9nWd.js → css-node-DKr6cufe.js} +38 -6
- package/dist/index.d.ts +3 -3
- package/dist/index.js +10 -4
- package/dist/{node-types-D41wr72y.d.ts → node-types-u3Sbl3_p.d.ts} +43 -7
- package/dist/parse-anplusb.d.ts +2 -2
- package/dist/parse-anplusb.js +2 -2
- package/dist/parse-atrule-prelude.d.ts +3 -9
- package/dist/parse-atrule-prelude.js +20 -204
- package/dist/parse-declaration.d.ts +2 -2
- package/dist/parse-declaration.js +3 -3
- package/dist/parse-selector-DBdUc04T.js +534 -0
- package/dist/parse-selector.d.ts +2 -2
- package/dist/parse-selector.js +1 -460
- package/dist/parse-value.d.ts +2 -2
- package/dist/parse-value.js +3 -3
- package/dist/parse.d.ts +2 -2
- package/dist/parse.js +3 -4
- package/dist/value-node-parser-JFFFJVX-.js +661 -0
- package/dist/walk.d.ts +1 -1
- package/dist/walk.js +2 -2
- package/package.json +3 -2
- package/dist/parse-utils-D7srNG13.js +0 -65
- package/dist/value-node-parser-jRoWXgOr.js +0 -173
|
@@ -0,0 +1,661 @@
|
|
|
1
|
+
import { t as Lexer } from "./tokenize-DK24ACbY.js";
|
|
2
|
+
import "./arena-DKhfTFT5.js";
|
|
3
|
+
import { a as is_whitespace, o as str_equals } from "./parse-dimension-ouWXNHdx.js";
|
|
4
|
+
import { i as find_colon_at_depth_zero, o as skip_whitespace_and_comments_forward, s as trim_boundaries, t as SelectorParser } from "./parse-selector-DBdUc04T.js";
|
|
5
|
+
//#region src/parse-condition.ts
|
|
6
|
+
/** @internal */
|
|
7
|
+
var ConditionParser = class {
|
|
8
|
+
lexer;
|
|
9
|
+
arena;
|
|
10
|
+
source;
|
|
11
|
+
end = 0;
|
|
12
|
+
value_node_parser;
|
|
13
|
+
selector_parser;
|
|
14
|
+
constructor(arena, source, value_node_parser) {
|
|
15
|
+
this.arena = arena;
|
|
16
|
+
this.source = source;
|
|
17
|
+
this.lexer = new Lexer(source);
|
|
18
|
+
this.value_node_parser = value_node_parser;
|
|
19
|
+
this.selector_parser = new SelectorParser(arena, source);
|
|
20
|
+
}
|
|
21
|
+
get end_position() {
|
|
22
|
+
return [
|
|
23
|
+
this.lexer.pos,
|
|
24
|
+
this.lexer.line,
|
|
25
|
+
this.lexer.column
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
create_node(type, start, end) {
|
|
29
|
+
return this.arena.create_node(type, start, end - start, this.lexer.token_line, this.lexer.token_column);
|
|
30
|
+
}
|
|
31
|
+
next_token() {
|
|
32
|
+
if (this.lexer.pos >= this.end) {
|
|
33
|
+
this.lexer.token_type = 26;
|
|
34
|
+
return 26;
|
|
35
|
+
}
|
|
36
|
+
return this.lexer.next_token_fast(false);
|
|
37
|
+
}
|
|
38
|
+
is_and_or_not(str) {
|
|
39
|
+
return str_equals("and", str) || str_equals("or", str) || str_equals("not", str);
|
|
40
|
+
}
|
|
41
|
+
scan_matching_paren() {
|
|
42
|
+
let depth = 1;
|
|
43
|
+
let content_end = this.lexer.pos;
|
|
44
|
+
let close_end = this.lexer.token_end;
|
|
45
|
+
while (this.lexer.pos < this.end && depth > 0) {
|
|
46
|
+
let token_type = this.next_token();
|
|
47
|
+
if (token_type === 21 || token_type === 2) depth++;
|
|
48
|
+
else if (token_type === 22) {
|
|
49
|
+
depth--;
|
|
50
|
+
if (depth === 0) {
|
|
51
|
+
content_end = this.lexer.token_start;
|
|
52
|
+
close_end = this.lexer.token_end;
|
|
53
|
+
}
|
|
54
|
+
} else if (token_type === 26) break;
|
|
55
|
+
}
|
|
56
|
+
return [
|
|
57
|
+
content_end,
|
|
58
|
+
close_end,
|
|
59
|
+
depth === 0
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
parse_feature_value(start, end) {
|
|
63
|
+
return this.value_node_parser.parse_chain(start, end, this.lexer.line, this.lexer.column);
|
|
64
|
+
}
|
|
65
|
+
wrap_ratio_value(first_node) {
|
|
66
|
+
if (this.arena.get_type(first_node) !== 11) return first_node;
|
|
67
|
+
let op_node = this.arena.get_next_sibling(first_node);
|
|
68
|
+
if (op_node === 0 || this.arena.get_type(op_node) !== 16) return first_node;
|
|
69
|
+
if (this.arena.get_length(op_node) !== 1) return first_node;
|
|
70
|
+
if (this.source.charCodeAt(this.arena.get_start_offset(op_node)) !== 47) return first_node;
|
|
71
|
+
let second_node = this.arena.get_next_sibling(op_node);
|
|
72
|
+
if (second_node === 0 || this.arena.get_type(second_node) !== 11) return first_node;
|
|
73
|
+
if (this.arena.get_next_sibling(second_node) !== 0) return first_node;
|
|
74
|
+
let start = this.arena.get_start_offset(first_node);
|
|
75
|
+
let end = this.arena.get_start_offset(second_node) + this.arena.get_length(second_node);
|
|
76
|
+
let ratio_node = this.arena.create_node(58, start, end - start, this.arena.get_start_line(first_node), this.arena.get_start_column(first_node));
|
|
77
|
+
this.arena.set_first_child(ratio_node, first_node);
|
|
78
|
+
this.arena.set_next_sibling(first_node, second_node);
|
|
79
|
+
return ratio_node;
|
|
80
|
+
}
|
|
81
|
+
create_supports_declaration(content_start, content_end, colon_pos) {
|
|
82
|
+
let prop_trimmed = trim_boundaries(this.source, content_start, colon_pos);
|
|
83
|
+
let val_trimmed = trim_boundaries(this.source, colon_pos + 1, content_end);
|
|
84
|
+
if (!prop_trimmed) return this.create_node(57, content_start, content_end);
|
|
85
|
+
let decl_start = prop_trimmed[0];
|
|
86
|
+
let decl_end = val_trimmed ? val_trimmed[1] : colon_pos + 1;
|
|
87
|
+
let decl = this.create_node(4, decl_start, decl_end);
|
|
88
|
+
this.arena.set_content_start_delta(decl, 0);
|
|
89
|
+
this.arena.set_content_length(decl, prop_trimmed[1] - prop_trimmed[0]);
|
|
90
|
+
if (val_trimmed) {
|
|
91
|
+
let value_first = this.parse_feature_value(val_trimmed[0], val_trimmed[1]);
|
|
92
|
+
let value_node;
|
|
93
|
+
if (value_first === 0) value_node = this.arena.create_node(50, val_trimmed[0], 0, this.lexer.token_line, this.lexer.token_column);
|
|
94
|
+
else {
|
|
95
|
+
value_node = this.arena.create_node(50, val_trimmed[0], val_trimmed[1] - val_trimmed[0], this.lexer.token_line, this.lexer.token_column);
|
|
96
|
+
this.arena.set_first_child(value_node, value_first);
|
|
97
|
+
}
|
|
98
|
+
this.arena.set_first_child(decl, value_node);
|
|
99
|
+
}
|
|
100
|
+
let supports_decl = this.create_node(57, content_start, content_end);
|
|
101
|
+
this.arena.set_content_start_delta(supports_decl, prop_trimmed[0] - content_start);
|
|
102
|
+
this.arena.set_content_length(supports_decl, prop_trimmed[1] - prop_trimmed[0]);
|
|
103
|
+
this.arena.set_first_child(supports_decl, decl);
|
|
104
|
+
return supports_decl;
|
|
105
|
+
}
|
|
106
|
+
parse_feature_range(feature_start, feature_end, content_start, content_end) {
|
|
107
|
+
let range_node = this.create_node(39, feature_start, feature_end);
|
|
108
|
+
let first_child = 0;
|
|
109
|
+
let last_child = 0;
|
|
110
|
+
let feature_name_start = -1;
|
|
111
|
+
let feature_name_end = -1;
|
|
112
|
+
let pos = content_start;
|
|
113
|
+
while (pos < content_end) {
|
|
114
|
+
pos = skip_whitespace_and_comments_forward(this.source, pos, content_end);
|
|
115
|
+
if (pos >= content_end) break;
|
|
116
|
+
let ch = this.source.charCodeAt(pos);
|
|
117
|
+
if (ch === 60 || ch === 62 || ch === 61) {
|
|
118
|
+
let op_start = pos++;
|
|
119
|
+
if (pos < content_end && this.source.charCodeAt(pos) === 61) pos++;
|
|
120
|
+
let op = this.create_node(38, op_start, pos);
|
|
121
|
+
if (first_child === 0) first_child = op;
|
|
122
|
+
else this.arena.set_next_sibling(last_child, op);
|
|
123
|
+
last_child = op;
|
|
124
|
+
} else {
|
|
125
|
+
let saved = this.lexer.save_position();
|
|
126
|
+
this.lexer.pos = pos;
|
|
127
|
+
this.next_token();
|
|
128
|
+
if (this.lexer.token_type === 1) {
|
|
129
|
+
feature_name_start = this.lexer.token_start;
|
|
130
|
+
feature_name_end = this.lexer.token_end;
|
|
131
|
+
} else {
|
|
132
|
+
let value_first = this.parse_feature_value(this.lexer.token_start, this.lexer.token_end);
|
|
133
|
+
if (value_first !== 0) {
|
|
134
|
+
if (first_child === 0) first_child = value_first;
|
|
135
|
+
else this.arena.set_next_sibling(last_child, value_first);
|
|
136
|
+
last_child = this.arena.get_last_sibling(value_first);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
pos = this.lexer.pos;
|
|
140
|
+
this.lexer.restore_position(saved);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (feature_name_start !== -1) {
|
|
144
|
+
this.arena.set_content_start_delta(range_node, feature_name_start - feature_start);
|
|
145
|
+
this.arena.set_content_length(range_node, feature_name_end - feature_name_start);
|
|
146
|
+
}
|
|
147
|
+
if (first_child !== 0) this.arena.set_first_child(range_node, first_child);
|
|
148
|
+
return range_node;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Parse the content of a media feature — everything between its parentheses, already
|
|
152
|
+
* known by the caller as `[content_start, content_end)`. `feature_start`/`feature_end`
|
|
153
|
+
* are the span of the feature node itself: for a real `@media (min-width: 768px)` feature
|
|
154
|
+
* this includes the surrounding parens (`feature_start < content_start`); for if()'s
|
|
155
|
+
* `media(min-width: 768px)`, media()'s own parens already delimit the content, so callers
|
|
156
|
+
* pass `feature_start === content_start` and `feature_end === content_end`.
|
|
157
|
+
* Returns a MEDIA_FEATURE (name/boolean/name:value) or FEATURE_RANGE (comparison syntax) node.
|
|
158
|
+
*/
|
|
159
|
+
parse_media_feature_content(feature_start, feature_end, content_start, content_end) {
|
|
160
|
+
this.end = content_end;
|
|
161
|
+
let has_comparison = false;
|
|
162
|
+
let i = content_start;
|
|
163
|
+
while (i < content_end) {
|
|
164
|
+
i = skip_whitespace_and_comments_forward(this.source, i, content_end);
|
|
165
|
+
if (i >= content_end) break;
|
|
166
|
+
let ch = this.source.charCodeAt(i);
|
|
167
|
+
if (ch === 60 || ch === 62 || ch === 61) {
|
|
168
|
+
has_comparison = true;
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
i++;
|
|
172
|
+
}
|
|
173
|
+
if (has_comparison) return this.parse_feature_range(feature_start, feature_end, content_start, content_end);
|
|
174
|
+
let feature = this.create_node(33, feature_start, feature_end);
|
|
175
|
+
let colon_pos = -1;
|
|
176
|
+
let j = content_start;
|
|
177
|
+
while (j < content_end) {
|
|
178
|
+
j = skip_whitespace_and_comments_forward(this.source, j, content_end);
|
|
179
|
+
if (j >= content_end) break;
|
|
180
|
+
if (this.source.charCodeAt(j) === 58) {
|
|
181
|
+
colon_pos = j;
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
j++;
|
|
185
|
+
}
|
|
186
|
+
if (colon_pos === -1) {
|
|
187
|
+
let trimmed = trim_boundaries(this.source, content_start, content_end);
|
|
188
|
+
if (trimmed) {
|
|
189
|
+
this.arena.set_content_start_delta(feature, trimmed[0] - feature_start);
|
|
190
|
+
this.arena.set_content_length(feature, trimmed[1] - trimmed[0]);
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
let name_trimmed = trim_boundaries(this.source, content_start, colon_pos);
|
|
194
|
+
if (name_trimmed) {
|
|
195
|
+
this.arena.set_content_start_delta(feature, name_trimmed[0] - feature_start);
|
|
196
|
+
this.arena.set_content_length(feature, name_trimmed[1] - name_trimmed[0]);
|
|
197
|
+
}
|
|
198
|
+
let value_trimmed = trim_boundaries(this.source, colon_pos + 1, content_end);
|
|
199
|
+
if (value_trimmed) {
|
|
200
|
+
let value_first = this.parse_feature_value(value_trimmed[0], value_trimmed[1]);
|
|
201
|
+
if (value_first !== 0) this.arena.set_first_child(feature, this.wrap_ratio_value(value_first));
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return feature;
|
|
205
|
+
}
|
|
206
|
+
parse_media_feature(open_paren_start, end, line, column) {
|
|
207
|
+
this.end = end;
|
|
208
|
+
this.lexer.seek(open_paren_start, line, column);
|
|
209
|
+
this.next_token();
|
|
210
|
+
let feature_start = this.lexer.token_start;
|
|
211
|
+
let content_start = this.lexer.pos;
|
|
212
|
+
let [content_end, feature_end, matched] = this.scan_matching_paren();
|
|
213
|
+
if (!matched) return null;
|
|
214
|
+
return this.parse_media_feature_content(feature_start, feature_end, content_start, content_end);
|
|
215
|
+
}
|
|
216
|
+
parse_function_condition(func_start, end, line, column) {
|
|
217
|
+
this.end = end;
|
|
218
|
+
this.lexer.seek(func_start, line, column);
|
|
219
|
+
this.next_token();
|
|
220
|
+
let func_name = this.source.substring(this.lexer.token_start, this.lexer.token_end - 1);
|
|
221
|
+
let content_start = this.lexer.token_end;
|
|
222
|
+
let [content_end, func_end] = this.scan_matching_paren();
|
|
223
|
+
let func_node = this.create_node(15, func_start, func_end);
|
|
224
|
+
this.arena.set_content_start_delta(func_node, 0);
|
|
225
|
+
this.arena.set_content_length(func_node, func_name.length);
|
|
226
|
+
this.arena.set_value_start_delta(func_node, content_start - func_start);
|
|
227
|
+
this.arena.set_value_length(func_node, content_end - content_start);
|
|
228
|
+
if (str_equals("selector", func_name)) {
|
|
229
|
+
let selector_list = this.selector_parser.parse_selector(content_start, content_end, this.lexer.line, this.lexer.column);
|
|
230
|
+
if (selector_list !== null) this.arena.set_first_child(func_node, selector_list);
|
|
231
|
+
} else if (str_equals("style", func_name)) {
|
|
232
|
+
let decl_child = this.parse_supports_declaration_content(content_start, content_end);
|
|
233
|
+
if (decl_child !== null) this.arena.set_first_child(func_node, decl_child);
|
|
234
|
+
}
|
|
235
|
+
return func_node;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Parse a single `(property: value)` supports declaration from a content range — the
|
|
239
|
+
* `<style-feature>`/`<supports-feature>` grammar shared by `style()`, `@supports (…)`, and
|
|
240
|
+
* `@import … supports(…)`. Returns null if no top-level ':' is found.
|
|
241
|
+
*/
|
|
242
|
+
parse_supports_declaration_content(content_start, content_end) {
|
|
243
|
+
let colon_pos = find_colon_at_depth_zero(this.source, content_start, content_end);
|
|
244
|
+
if (colon_pos === -1) return null;
|
|
245
|
+
return this.create_supports_declaration(content_start, content_end, colon_pos);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Parse a `<supports-condition>` — the compound and/or/not grammar shared by `@supports`
|
|
249
|
+
* preludes and if()'s `supports(...)` condition function: parenthesized `(property: value)`
|
|
250
|
+
* declarations, `and`/`or`/`not` logical operators, and nested function conditions
|
|
251
|
+
* (`selector()`, `style()`, etc.), chained as flat siblings.
|
|
252
|
+
* `start`/`end` bound the range to scan; the caller has already positioned nothing — this
|
|
253
|
+
* seeks its own lexer, so it's safe to call with any sub-range of the source.
|
|
254
|
+
*/
|
|
255
|
+
parse_supports_condition(start, end, line, column) {
|
|
256
|
+
let saved_end = this.end;
|
|
257
|
+
this.end = end;
|
|
258
|
+
this.lexer.seek(start, line, column);
|
|
259
|
+
let nodes = [];
|
|
260
|
+
while (this.lexer.pos < this.end) {
|
|
261
|
+
this.lexer.skip_whitespace_in_range(this.end);
|
|
262
|
+
if (this.lexer.pos >= this.end) break;
|
|
263
|
+
this.next_token();
|
|
264
|
+
let token_type = this.lexer.token_type;
|
|
265
|
+
if (token_type === 21) {
|
|
266
|
+
let feature_start = this.lexer.token_start;
|
|
267
|
+
let content_start = this.lexer.pos;
|
|
268
|
+
let [content_end, feature_end, matched] = this.scan_matching_paren();
|
|
269
|
+
if (matched) {
|
|
270
|
+
let query = this.create_node(36, feature_start, feature_end);
|
|
271
|
+
let trimmed = trim_boundaries(this.source, content_start, content_end);
|
|
272
|
+
if (trimmed) {
|
|
273
|
+
this.arena.set_value_start_delta(query, trimmed[0] - feature_start);
|
|
274
|
+
this.arena.set_value_length(query, trimmed[1] - trimmed[0]);
|
|
275
|
+
let decl_child = this.parse_supports_declaration_content(trimmed[0], trimmed[1]);
|
|
276
|
+
if (decl_child !== null) this.arena.set_first_child(query, decl_child);
|
|
277
|
+
}
|
|
278
|
+
nodes.push(query);
|
|
279
|
+
}
|
|
280
|
+
} else if (token_type === 1) {
|
|
281
|
+
let text = this.source.substring(this.lexer.token_start, this.lexer.token_end);
|
|
282
|
+
if (this.is_and_or_not(text)) {
|
|
283
|
+
let op = this.create_node(38, this.lexer.token_start, this.lexer.token_end);
|
|
284
|
+
nodes.push(op);
|
|
285
|
+
}
|
|
286
|
+
} else if (token_type === 2) nodes.push(this.parse_function_condition(this.lexer.token_start, this.end, this.lexer.token_line, this.lexer.token_column));
|
|
287
|
+
}
|
|
288
|
+
this.end = saved_end;
|
|
289
|
+
return nodes;
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
//#endregion
|
|
293
|
+
//#region src/value-node-parser.ts
|
|
294
|
+
/** @internal */
|
|
295
|
+
var ValueNodeParser = class ValueNodeParser {
|
|
296
|
+
lexer;
|
|
297
|
+
arena;
|
|
298
|
+
source;
|
|
299
|
+
end = 0;
|
|
300
|
+
last_chain_node = 0;
|
|
301
|
+
_condition_parser = null;
|
|
302
|
+
get condition_parser() {
|
|
303
|
+
if (this._condition_parser === null) this._condition_parser = new ConditionParser(this.arena, this.source, new ValueNodeParser(this.arena, this.source));
|
|
304
|
+
return this._condition_parser;
|
|
305
|
+
}
|
|
306
|
+
constructor(arena, source) {
|
|
307
|
+
this.arena = arena;
|
|
308
|
+
this.source = source;
|
|
309
|
+
this.lexer = new Lexer(source);
|
|
310
|
+
}
|
|
311
|
+
parse_chain(start, end, start_line, start_column) {
|
|
312
|
+
this.end = end;
|
|
313
|
+
this.lexer.seek(start, start_line, start_column);
|
|
314
|
+
let first_node = 0;
|
|
315
|
+
let last_node = 0;
|
|
316
|
+
while (this.lexer.pos < this.end) {
|
|
317
|
+
this.lexer.next_token_fast(false);
|
|
318
|
+
if (this.lexer.token_start >= this.end) break;
|
|
319
|
+
if (this.lexer.token_type === 26) break;
|
|
320
|
+
if (this.is_whitespace_inline()) continue;
|
|
321
|
+
let node = this.parse_value_node();
|
|
322
|
+
if (node !== null) {
|
|
323
|
+
if (first_node === 0) first_node = node;
|
|
324
|
+
else this.arena.set_next_sibling(last_node, node);
|
|
325
|
+
last_node = node;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
this.last_chain_node = last_node;
|
|
329
|
+
return first_node;
|
|
330
|
+
}
|
|
331
|
+
is_whitespace_inline() {
|
|
332
|
+
if (this.lexer.token_start >= this.lexer.token_end) return false;
|
|
333
|
+
for (let i = this.lexer.token_start; i < this.lexer.token_end; i++) if (!is_whitespace(this.source.charCodeAt(i))) return false;
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
parse_value_node() {
|
|
337
|
+
let token_type = this.lexer.token_type;
|
|
338
|
+
let start = this.lexer.token_start;
|
|
339
|
+
let end = this.lexer.token_end;
|
|
340
|
+
switch (token_type) {
|
|
341
|
+
case 1: return this.create_node(10, start, end);
|
|
342
|
+
case 10: return this.create_node(11, start, end);
|
|
343
|
+
case 11:
|
|
344
|
+
case 12: return this.create_node(12, start, end);
|
|
345
|
+
case 5: return this.create_node(13, start, end);
|
|
346
|
+
case 4: return this.create_node(14, start, end);
|
|
347
|
+
case 27: return this.create_node(19, start, end);
|
|
348
|
+
case 2: return this.parse_function_node(start, end);
|
|
349
|
+
case 9: return this.parse_operator_node(start, end);
|
|
350
|
+
case 18:
|
|
351
|
+
case 16:
|
|
352
|
+
case 17: return this.create_node(16, start, end);
|
|
353
|
+
case 21: return this.parse_parenthesis_node(start, end);
|
|
354
|
+
default: return null;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
create_node(node_type, start, end) {
|
|
358
|
+
let node = this.arena.create_node(node_type, start, end - start, this.lexer.token_line, this.lexer.token_column);
|
|
359
|
+
this.arena.set_content_length(node, end - start);
|
|
360
|
+
return node;
|
|
361
|
+
}
|
|
362
|
+
parse_operator_node(start, end) {
|
|
363
|
+
let ch = this.source.charCodeAt(start);
|
|
364
|
+
if (ch === 43 || ch === 45 || ch === 42 || ch === 47) return this.create_node(16, start, end);
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
scan_matching_paren(bounded) {
|
|
368
|
+
let depth = 1;
|
|
369
|
+
let content_end = this.lexer.pos;
|
|
370
|
+
let close_end = this.lexer.token_end;
|
|
371
|
+
while ((!bounded || this.lexer.pos < this.end) && depth > 0) {
|
|
372
|
+
this.lexer.next_token_fast(false);
|
|
373
|
+
let token_type = this.lexer.token_type;
|
|
374
|
+
if (token_type === 26) break;
|
|
375
|
+
if (bounded && this.lexer.token_start >= this.end) break;
|
|
376
|
+
if (token_type === 21 || token_type === 2) depth++;
|
|
377
|
+
else if (token_type === 22) {
|
|
378
|
+
depth--;
|
|
379
|
+
if (depth === 0) {
|
|
380
|
+
content_end = this.lexer.token_start;
|
|
381
|
+
close_end = this.lexer.token_end;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return [
|
|
386
|
+
content_end,
|
|
387
|
+
close_end,
|
|
388
|
+
depth === 0
|
|
389
|
+
];
|
|
390
|
+
}
|
|
391
|
+
parse_function_node(start, end) {
|
|
392
|
+
let name_end = end - 1;
|
|
393
|
+
let func_name_substr = this.source.substring(start, name_end);
|
|
394
|
+
if (str_equals("if", func_name_substr)) return this.parse_if_function_node(start, end);
|
|
395
|
+
let node = this.arena.create_node(str_equals("url", func_name_substr) ? 18 : 15, start, 0, this.lexer.token_line, this.lexer.token_column);
|
|
396
|
+
this.arena.set_content_start_delta(node, 0);
|
|
397
|
+
this.arena.set_content_length(node, name_end - start);
|
|
398
|
+
if (str_equals("url", func_name_substr) || str_equals("src", func_name_substr)) {
|
|
399
|
+
let save_pos = this.lexer.save_position();
|
|
400
|
+
this.lexer.next_token_fast(false);
|
|
401
|
+
while (this.is_whitespace_inline() && this.lexer.pos < this.end) this.lexer.next_token_fast(false);
|
|
402
|
+
let first_token_type = this.lexer.token_type;
|
|
403
|
+
this.lexer.restore_position(save_pos);
|
|
404
|
+
if (first_token_type === 5) {} else {
|
|
405
|
+
let func_end = end;
|
|
406
|
+
let content_start = end;
|
|
407
|
+
let content_end = end;
|
|
408
|
+
let [scanned_content_end, scanned_func_end, matched] = this.scan_matching_paren(false);
|
|
409
|
+
if (matched) {
|
|
410
|
+
content_end = scanned_content_end;
|
|
411
|
+
func_end = scanned_func_end;
|
|
412
|
+
}
|
|
413
|
+
this.arena.set_length(node, func_end - start);
|
|
414
|
+
this.arena.set_value_start_delta(node, content_start - start);
|
|
415
|
+
this.arena.set_value_length(node, content_end - content_start);
|
|
416
|
+
return node;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
let first_arg = 0;
|
|
420
|
+
let last_arg = 0;
|
|
421
|
+
let paren_depth = 1;
|
|
422
|
+
let func_end = end;
|
|
423
|
+
let content_start = end;
|
|
424
|
+
let content_end = end;
|
|
425
|
+
while (this.lexer.pos < this.end && paren_depth > 0) {
|
|
426
|
+
this.lexer.next_token_fast(false);
|
|
427
|
+
let token_type = this.lexer.token_type;
|
|
428
|
+
if (token_type === 26) break;
|
|
429
|
+
if (this.lexer.token_start >= this.end) break;
|
|
430
|
+
if (token_type === 22) {
|
|
431
|
+
paren_depth--;
|
|
432
|
+
if (paren_depth === 0) {
|
|
433
|
+
content_end = this.lexer.token_start;
|
|
434
|
+
func_end = this.lexer.token_end;
|
|
435
|
+
break;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
if (this.is_whitespace_inline()) continue;
|
|
439
|
+
let arg_node = this.parse_value_node();
|
|
440
|
+
if (arg_node !== null) {
|
|
441
|
+
if (first_arg === 0) first_arg = arg_node;
|
|
442
|
+
else this.arena.set_next_sibling(last_arg, arg_node);
|
|
443
|
+
last_arg = arg_node;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
this.arena.set_length(node, func_end - start);
|
|
447
|
+
this.arena.set_value_start_delta(node, content_start - start);
|
|
448
|
+
this.arena.set_value_length(node, content_end - content_start);
|
|
449
|
+
if (first_arg !== 0) this.arena.set_first_child(node, first_arg);
|
|
450
|
+
return node;
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Parse an if() inline conditional function.
|
|
454
|
+
*
|
|
455
|
+
* Spec grammar (CSS Values Level 5):
|
|
456
|
+
* if( <if-branch>+ )
|
|
457
|
+
* <if-branch> = <if-condition> : <declaration-value>? ;?
|
|
458
|
+
* <if-condition> = style(…) | media(…) | supports(…) | else
|
|
459
|
+
*
|
|
460
|
+
* Each branch becomes an IF_BRANCH child (see the `IfBranch` type in node-types.ts
|
|
461
|
+
* for its shape). Colons/semicolons here are structural separators, not OPERATOR nodes.
|
|
462
|
+
*/
|
|
463
|
+
parse_if_function_node(start, end) {
|
|
464
|
+
let name_end = end - 1;
|
|
465
|
+
let save_line = this.lexer.token_line;
|
|
466
|
+
let save_col = this.lexer.token_column;
|
|
467
|
+
let node = this.arena.create_node(15, start, 0, save_line, save_col);
|
|
468
|
+
this.arena.set_content_start_delta(node, 0);
|
|
469
|
+
this.arena.set_content_length(node, name_end - start);
|
|
470
|
+
let branches = [];
|
|
471
|
+
let func_end = end;
|
|
472
|
+
let content_start = end;
|
|
473
|
+
let content_end = end;
|
|
474
|
+
let if_closed = false;
|
|
475
|
+
while (this.lexer.pos < this.end && !if_closed) {
|
|
476
|
+
this.lexer.next_token_fast(false);
|
|
477
|
+
let tt = this.lexer.token_type;
|
|
478
|
+
if (tt === 26) break;
|
|
479
|
+
if (this.lexer.token_start >= this.end) break;
|
|
480
|
+
if (tt === 22) {
|
|
481
|
+
content_end = this.lexer.token_start;
|
|
482
|
+
func_end = this.lexer.token_end;
|
|
483
|
+
break;
|
|
484
|
+
}
|
|
485
|
+
if (this.is_whitespace_inline() || tt === 17 || tt === 16) continue;
|
|
486
|
+
let branch_start = this.lexer.token_start;
|
|
487
|
+
let branch_line = this.lexer.token_line;
|
|
488
|
+
let branch_col = this.lexer.token_column;
|
|
489
|
+
let condition_node;
|
|
490
|
+
if (tt === 2) condition_node = this.parse_if_condition_function(this.lexer.token_start, this.lexer.token_end);
|
|
491
|
+
else condition_node = this.parse_value_node();
|
|
492
|
+
if (condition_node === null) continue;
|
|
493
|
+
let condition_end_pos = this.arena.get_start_offset(condition_node) + this.arena.get_length(condition_node);
|
|
494
|
+
let colon_found = false;
|
|
495
|
+
while (this.lexer.pos < this.end) {
|
|
496
|
+
this.lexer.next_token_fast(false);
|
|
497
|
+
let t = this.lexer.token_type;
|
|
498
|
+
if (t === 26) break;
|
|
499
|
+
if (this.lexer.token_start >= this.end) break;
|
|
500
|
+
if (this.is_whitespace_inline()) continue;
|
|
501
|
+
if (t === 16) {
|
|
502
|
+
colon_found = true;
|
|
503
|
+
break;
|
|
504
|
+
}
|
|
505
|
+
if (t === 22) {
|
|
506
|
+
content_end = this.lexer.token_start;
|
|
507
|
+
func_end = this.lexer.token_end;
|
|
508
|
+
if_closed = true;
|
|
509
|
+
break;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
let value_tokens = [];
|
|
513
|
+
let value_start = -1;
|
|
514
|
+
let value_last_end = condition_end_pos;
|
|
515
|
+
let value_line = 0;
|
|
516
|
+
let value_col = 0;
|
|
517
|
+
if (colon_found && !if_closed) while (this.lexer.pos < this.end) {
|
|
518
|
+
this.lexer.next_token_fast(false);
|
|
519
|
+
let t = this.lexer.token_type;
|
|
520
|
+
if (t === 26) break;
|
|
521
|
+
if (this.lexer.token_start >= this.end) break;
|
|
522
|
+
if (this.is_whitespace_inline()) continue;
|
|
523
|
+
if (t === 17) break;
|
|
524
|
+
if (t === 22) {
|
|
525
|
+
content_end = this.lexer.token_start;
|
|
526
|
+
func_end = this.lexer.token_end;
|
|
527
|
+
if_closed = true;
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
530
|
+
let vnode = this.parse_value_node();
|
|
531
|
+
if (vnode !== null) {
|
|
532
|
+
let ns = this.arena.get_start_offset(vnode);
|
|
533
|
+
if (value_start === -1) {
|
|
534
|
+
value_start = ns;
|
|
535
|
+
value_line = this.arena.get_start_line(vnode);
|
|
536
|
+
value_col = this.arena.get_start_column(vnode);
|
|
537
|
+
}
|
|
538
|
+
value_tokens.push(vnode);
|
|
539
|
+
value_last_end = ns + this.arena.get_length(vnode);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
let value_node = null;
|
|
543
|
+
if (value_tokens.length > 0) {
|
|
544
|
+
value_node = this.arena.create_node(50, value_start, value_last_end - value_start, value_line, value_col);
|
|
545
|
+
this.arena.append_children(value_node, value_tokens);
|
|
546
|
+
}
|
|
547
|
+
let branch_end = value_node === null ? condition_end_pos : value_last_end;
|
|
548
|
+
let branch_node = this.arena.create_node(59, branch_start, branch_end - branch_start, branch_line, branch_col);
|
|
549
|
+
this.arena.set_content_start_delta(branch_node, 0);
|
|
550
|
+
this.arena.set_content_length(branch_node, condition_end_pos - branch_start);
|
|
551
|
+
if (value_start !== -1) {
|
|
552
|
+
this.arena.set_value_start_delta(branch_node, value_start - branch_start);
|
|
553
|
+
this.arena.set_value_length(branch_node, value_last_end - value_start);
|
|
554
|
+
}
|
|
555
|
+
let branch_children = [condition_node];
|
|
556
|
+
if (value_node !== null) branch_children.push(value_node);
|
|
557
|
+
this.arena.append_children(branch_node, branch_children);
|
|
558
|
+
branches.push(branch_node);
|
|
559
|
+
}
|
|
560
|
+
this.arena.set_length(node, func_end - start);
|
|
561
|
+
this.arena.set_value_start_delta(node, content_start - start);
|
|
562
|
+
this.arena.set_value_length(node, content_end - content_start);
|
|
563
|
+
this.arena.append_children(node, branches);
|
|
564
|
+
return node;
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Parse a condition function inside if() — style(), supports(), or media(). Content parsing
|
|
568
|
+
* is delegated to the shared ConditionParser (see parse-condition.ts), so these produce the
|
|
569
|
+
* same node shapes real `@supports`/`@media` conditions do:
|
|
570
|
+
* style()/supports() → SUPPORTS_DECLARATION(s) — supports() gets the full compound
|
|
571
|
+
* <supports-condition> grammar (and/or/not, nested conditions), same as `@supports`.
|
|
572
|
+
* media() → a single MEDIA_FEATURE or FEATURE_RANGE (comparison syntax), same as `@media`.
|
|
573
|
+
* anything else → generic value nodes, as before.
|
|
574
|
+
*
|
|
575
|
+
* Called with the current token at TOKEN_FUNCTION (the '(' already consumed).
|
|
576
|
+
* @param func_start Offset of the function name's first char.
|
|
577
|
+
* @param token_end Offset right after '(' (== lexer.token_end here).
|
|
578
|
+
*/
|
|
579
|
+
parse_if_condition_function(func_start, token_end) {
|
|
580
|
+
let func_name_end = token_end - 1;
|
|
581
|
+
let func_name = this.source.substring(func_start, func_name_end);
|
|
582
|
+
let func_line = this.lexer.token_line;
|
|
583
|
+
let func_col = this.lexer.token_column;
|
|
584
|
+
let content_start = token_end;
|
|
585
|
+
let content_end = content_start;
|
|
586
|
+
let func_end = content_start;
|
|
587
|
+
let [scanned_content_end, scanned_func_end, matched] = this.scan_matching_paren(true);
|
|
588
|
+
if (matched) {
|
|
589
|
+
content_end = scanned_content_end;
|
|
590
|
+
func_end = scanned_func_end;
|
|
591
|
+
}
|
|
592
|
+
let func_node = this.arena.create_node(15, func_start, func_end - func_start, func_line, func_col);
|
|
593
|
+
this.arena.set_content_start_delta(func_node, 0);
|
|
594
|
+
this.arena.set_content_length(func_node, func_name_end - func_start);
|
|
595
|
+
this.arena.set_value_start_delta(func_node, content_start - func_start);
|
|
596
|
+
this.arena.set_value_length(func_node, content_end - content_start);
|
|
597
|
+
let child_nodes = [];
|
|
598
|
+
if (str_equals("style", func_name)) {
|
|
599
|
+
let decl = this.condition_parser.parse_supports_declaration_content(content_start, content_end);
|
|
600
|
+
if (decl !== null) child_nodes = [decl];
|
|
601
|
+
} else if (str_equals("supports", func_name)) {
|
|
602
|
+
let decl = this.condition_parser.parse_supports_declaration_content(content_start, content_end);
|
|
603
|
+
if (decl === null) child_nodes = this.condition_parser.parse_supports_condition(content_start, content_end, func_line, func_col);
|
|
604
|
+
else child_nodes = [decl];
|
|
605
|
+
} else if (str_equals("media", func_name)) child_nodes = [this.condition_parser.parse_media_feature_content(content_start, content_end, content_start, content_end)];
|
|
606
|
+
else child_nodes = this.parse_value_nodes_in_range(content_start, content_end);
|
|
607
|
+
this.arena.append_children(func_node, child_nodes);
|
|
608
|
+
return func_node;
|
|
609
|
+
}
|
|
610
|
+
/** Parse value tokens in a source sub-range using save/restore to protect lexer state. */
|
|
611
|
+
parse_value_nodes_in_range(start, end) {
|
|
612
|
+
let saved_end = this.end;
|
|
613
|
+
let saved_pos = this.lexer.save_position();
|
|
614
|
+
this.end = end;
|
|
615
|
+
this.lexer.seek(start, this.lexer.line, this.lexer.column);
|
|
616
|
+
let nodes = [];
|
|
617
|
+
while (this.lexer.pos < this.end) {
|
|
618
|
+
this.lexer.next_token_fast(false);
|
|
619
|
+
if (this.lexer.token_start >= this.end) break;
|
|
620
|
+
if (this.lexer.token_type === 26) break;
|
|
621
|
+
if (this.is_whitespace_inline()) continue;
|
|
622
|
+
let node = this.parse_value_node();
|
|
623
|
+
if (node !== null) nodes.push(node);
|
|
624
|
+
}
|
|
625
|
+
this.lexer.restore_position(saved_pos);
|
|
626
|
+
this.end = saved_end;
|
|
627
|
+
return nodes;
|
|
628
|
+
}
|
|
629
|
+
parse_parenthesis_node(start, end) {
|
|
630
|
+
let node = this.arena.create_node(17, start, 0, this.lexer.token_line, this.lexer.token_column);
|
|
631
|
+
let first_child = 0;
|
|
632
|
+
let last_child = 0;
|
|
633
|
+
let paren_depth = 1;
|
|
634
|
+
let paren_end = end;
|
|
635
|
+
while (this.lexer.pos < this.end && paren_depth > 0) {
|
|
636
|
+
this.lexer.next_token_fast(false);
|
|
637
|
+
let token_type = this.lexer.token_type;
|
|
638
|
+
if (token_type === 26) break;
|
|
639
|
+
if (this.lexer.token_start >= this.end) break;
|
|
640
|
+
if (token_type === 22) {
|
|
641
|
+
paren_depth--;
|
|
642
|
+
if (paren_depth === 0) {
|
|
643
|
+
paren_end = this.lexer.token_end;
|
|
644
|
+
break;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
if (this.is_whitespace_inline()) continue;
|
|
648
|
+
let child_node = this.parse_value_node();
|
|
649
|
+
if (child_node !== null) {
|
|
650
|
+
if (first_child === 0) first_child = child_node;
|
|
651
|
+
else this.arena.set_next_sibling(last_child, child_node);
|
|
652
|
+
last_child = child_node;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
this.arena.set_length(node, paren_end - start);
|
|
656
|
+
if (first_child !== 0) this.arena.set_first_child(node, first_child);
|
|
657
|
+
return node;
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
//#endregion
|
|
661
|
+
export { ConditionParser as n, ValueNodeParser as t };
|
package/dist/walk.d.ts
CHANGED
package/dist/walk.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@projectwallace/css-parser",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.5",
|
|
4
4
|
"description": "High-performance CSS lexer and parser, optimized for CSS inspection and analysis",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ast",
|
|
@@ -84,6 +84,7 @@
|
|
|
84
84
|
"knip": "^6.18.0",
|
|
85
85
|
"oxfmt": "^0.60.0",
|
|
86
86
|
"oxlint": "^1.71.0",
|
|
87
|
+
"pkg-pr-new": "^0.0.88",
|
|
87
88
|
"postcss": "^8.5.15",
|
|
88
89
|
"publint": "^0.3.21",
|
|
89
90
|
"tailwindcss": "^2.2.8",
|
|
@@ -103,7 +104,7 @@
|
|
|
103
104
|
"test-build": "pnpm run build && vitest run --config vitest.config.build.ts",
|
|
104
105
|
"build": "tsdown",
|
|
105
106
|
"benchmark": "pnpm run build && node --expose-gc benchmark/index.ts",
|
|
106
|
-
"lint": "oxlint --config .oxlintrc.json
|
|
107
|
+
"lint": "oxlint --config .oxlintrc.json && oxfmt --check",
|
|
107
108
|
"check": "tsc --noEmit",
|
|
108
109
|
"knip": "knip",
|
|
109
110
|
"precommit": "pnpm run test --run; pnpm run lint; pnpm run check"
|