@ismail-elkorchi/css-parser 0.1.1 → 0.2.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.
Files changed (82) hide show
  1. package/README.md +64 -78
  2. package/dist/internal/cssom/declarations.d.ts +36 -0
  3. package/dist/internal/cssom/declarations.js +177 -0
  4. package/dist/internal/generated/css-data.d.ts +2 -0
  5. package/dist/internal/generated/css-data.js +16229 -0
  6. package/dist/internal/grammar/catalog-types.d.ts +26 -0
  7. package/dist/internal/grammar/value-definition.d.ts +63 -0
  8. package/dist/internal/grammar/value-definition.js +439 -0
  9. package/dist/internal/properties/matcher.d.ts +29 -0
  10. package/dist/internal/properties/matcher.js +791 -0
  11. package/dist/internal/properties/registry.d.ts +24 -0
  12. package/dist/internal/properties/registry.js +52 -0
  13. package/dist/internal/selectors/matcher.d.ts +96 -0
  14. package/dist/internal/selectors/matcher.js +616 -0
  15. package/dist/internal/selectors/parser.d.ts +2 -0
  16. package/dist/internal/selectors/parser.js +702 -0
  17. package/dist/internal/selectors/specificity.d.ts +3 -0
  18. package/dist/internal/selectors/specificity.js +77 -0
  19. package/dist/internal/selectors/types.d.ts +110 -0
  20. package/dist/internal/syntax/ast.d.ts +70 -0
  21. package/dist/internal/syntax/ast.js +1 -0
  22. package/dist/internal/syntax/characters.d.ts +8 -0
  23. package/dist/internal/syntax/characters.js +45 -0
  24. package/dist/internal/syntax/encoding.d.ts +15 -0
  25. package/dist/internal/syntax/encoding.js +161 -0
  26. package/dist/internal/syntax/input.d.ts +23 -0
  27. package/dist/internal/syntax/input.js +184 -0
  28. package/dist/internal/syntax/parser.d.ts +26 -0
  29. package/dist/internal/syntax/parser.js +581 -0
  30. package/dist/internal/syntax/resources.d.ts +28 -0
  31. package/dist/internal/syntax/resources.js +135 -0
  32. package/dist/internal/syntax/serialize.d.ts +10 -0
  33. package/dist/internal/syntax/serialize.js +630 -0
  34. package/dist/internal/syntax/token-stream.d.ts +16 -0
  35. package/dist/internal/syntax/token-stream.js +66 -0
  36. package/dist/internal/syntax/tokenizer.d.ts +21 -0
  37. package/dist/internal/syntax/tokenizer.js +571 -0
  38. package/dist/internal/syntax/tokens.d.ts +115 -0
  39. package/dist/internal/syntax/tokens.js +1 -0
  40. package/dist/internal/syntax/types.d.ts +46 -0
  41. package/dist/internal/syntax/types.js +1 -0
  42. package/dist/mod.d.ts +7 -1
  43. package/dist/mod.js +7 -1
  44. package/dist/public/edits.d.ts +12 -0
  45. package/dist/public/edits.js +195 -0
  46. package/dist/public/mod.d.ts +13 -69
  47. package/dist/public/mod.js +12 -1794
  48. package/dist/public/parse.d.ts +37 -0
  49. package/dist/public/parse.js +298 -0
  50. package/dist/public/traversal.d.ts +13 -0
  51. package/dist/public/traversal.js +96 -0
  52. package/dist/public/types.d.ts +78 -264
  53. package/package.json +32 -56
  54. package/THIRD_PARTY_NOTICES.md +0 -19
  55. package/dist/internal/csstree-runtime.d.ts +0 -20
  56. package/dist/internal/csstree-runtime.js +0 -21
  57. package/dist/internal/encoding/mod.d.ts +0 -1
  58. package/dist/internal/encoding/mod.js +0 -1
  59. package/dist/internal/encoding/sniff.d.ts +0 -14
  60. package/dist/internal/encoding/sniff.js +0 -95
  61. package/dist/internal/serializer/mod.d.ts +0 -1
  62. package/dist/internal/serializer/mod.js +0 -1
  63. package/dist/internal/serializer/serialize.d.ts +0 -3
  64. package/dist/internal/serializer/serialize.js +0 -89
  65. package/dist/internal/tokenizer/mod.d.ts +0 -2
  66. package/dist/internal/tokenizer/mod.js +0 -1
  67. package/dist/internal/tokenizer/tokenize.d.ts +0 -2
  68. package/dist/internal/tokenizer/tokenize.js +0 -39
  69. package/dist/internal/tokenizer/tokens.d.ts +0 -23
  70. package/dist/internal/tree/build.d.ts +0 -2
  71. package/dist/internal/tree/build.js +0 -85
  72. package/dist/internal/tree/mod.d.ts +0 -2
  73. package/dist/internal/tree/mod.js +0 -1
  74. package/dist/internal/tree/types.d.ts +0 -25
  75. package/dist/internal/vendor/csstree/LICENSE +0 -19
  76. package/dist/internal/vendor/csstree/csstree.esm.js +0 -12
  77. package/dist/internal/version.d.ts +0 -1
  78. package/dist/internal/version.js +0 -1
  79. package/dist/public/index.d.ts +0 -1
  80. package/dist/public/index.js +0 -1
  81. /package/dist/internal/{tokenizer/tokens.js → grammar/catalog-types.js} +0 -0
  82. /package/dist/internal/{tree → selectors}/types.js +0 -0
@@ -0,0 +1,581 @@
1
+ import { ResourceGuard } from "./resources.js";
2
+ import { TokenStream } from "./token-stream.js";
3
+ import { CssTokenizer } from "./tokenizer.js";
4
+ const CSS_SYNTAX = "https://drafts.csswg.org/css-syntax/";
5
+ function freezeArray(values) {
6
+ return Object.freeze(values);
7
+ }
8
+ function lowerAscii(value) {
9
+ return value.replace(/[A-Z]/gu, (character) => character.toLowerCase());
10
+ }
11
+ function positionAtEnd(input) {
12
+ let line = 1;
13
+ let column = 1;
14
+ for (let offset = 0; offset < input.length;) {
15
+ const first = input.charCodeAt(offset);
16
+ if (first === 0x0d) {
17
+ offset += input.charCodeAt(offset + 1) === 0x0a ? 2 : 1;
18
+ line += 1;
19
+ column = 1;
20
+ }
21
+ else if (first === 0x0a || first === 0x0c) {
22
+ offset += 1;
23
+ line += 1;
24
+ column = 1;
25
+ }
26
+ else if (first >= 0xd800 &&
27
+ first <= 0xdbff &&
28
+ input.charCodeAt(offset + 1) >= 0xdc00 &&
29
+ input.charCodeAt(offset + 1) <= 0xdfff) {
30
+ offset += 2;
31
+ column += 1;
32
+ }
33
+ else {
34
+ offset += 1;
35
+ column += 1;
36
+ }
37
+ }
38
+ return Object.freeze({ offset: input.length, line, column });
39
+ }
40
+ function emptySpan(input) {
41
+ const end = positionAtEnd(input);
42
+ return Object.freeze({ start: end, end });
43
+ }
44
+ function isClosingToken(token) {
45
+ return (token.kind === "close-square" ||
46
+ token.kind === "close-paren" ||
47
+ token.kind === "close-curly");
48
+ }
49
+ function isPreservedToken(token) {
50
+ return (token.kind !== "eof" &&
51
+ token.kind !== "function" &&
52
+ token.kind !== "open-square" &&
53
+ token.kind !== "open-paren" &&
54
+ token.kind !== "open-curly");
55
+ }
56
+ function isWhitespace(value) {
57
+ return value.kind === "whitespace";
58
+ }
59
+ function remapPosition(local, base) {
60
+ return Object.freeze({
61
+ offset: base.offset + local.offset,
62
+ line: base.line + local.line - 1,
63
+ column: local.line === 1 ? base.column + local.column - 1 : local.column
64
+ });
65
+ }
66
+ function remapSpan(local, base) {
67
+ return Object.freeze({
68
+ start: remapPosition(local.start, base),
69
+ end: remapPosition(local.end, base)
70
+ });
71
+ }
72
+ function remapToken(token, base) {
73
+ return Object.freeze({
74
+ ...token,
75
+ span: remapSpan(token.span, base)
76
+ });
77
+ }
78
+ function closingKind(opening) {
79
+ switch (opening) {
80
+ case "open-square":
81
+ return "close-square";
82
+ case "open-paren":
83
+ return "close-paren";
84
+ case "open-curly":
85
+ return "close-curly";
86
+ }
87
+ }
88
+ export class CssSyntaxParser {
89
+ #input;
90
+ #guard;
91
+ #diagnostics = [];
92
+ #tokenizerErrors = [];
93
+ #nextNodeId = 1;
94
+ constructor(input, options = {}) {
95
+ this.#input = input;
96
+ this.#guard = new ResourceGuard(options.limits, options.signal);
97
+ }
98
+ parseStylesheet() {
99
+ const { stream, tokenizerErrors } = this.#tokenize();
100
+ this.#tokenizerErrors = tokenizerErrors;
101
+ const start = stream.index;
102
+ const rules = this.#consumeStylesheetContents(stream, 2);
103
+ const stylesheet = this.#stylesheet(rules, stream.spanFrom(start), 1);
104
+ return this.#success(stylesheet);
105
+ }
106
+ parseStylesheetContents() {
107
+ const { stream, tokenizerErrors } = this.#tokenize();
108
+ this.#tokenizerErrors = tokenizerErrors;
109
+ return this.#success(this.#consumeStylesheetContents(stream, 1));
110
+ }
111
+ parseBlockContents() {
112
+ const { stream, tokenizerErrors } = this.#tokenize();
113
+ this.#tokenizerErrors = tokenizerErrors;
114
+ return this.#success(this.#consumeBlockContents(stream, 1));
115
+ }
116
+ parseRule() {
117
+ const { stream, tokenizerErrors } = this.#tokenize();
118
+ this.#tokenizerErrors = tokenizerErrors;
119
+ stream.discardWhitespace();
120
+ if (stream.empty) {
121
+ this.#diagnostic("empty-input", "A rule was requested from empty input.", emptySpan(this.#input), "#parse-a-rule");
122
+ return this.#failure();
123
+ }
124
+ const rule = stream.next.kind === "at-keyword"
125
+ ? this.#consumeAtRule(stream, false, 1)
126
+ : this.#consumeQualifiedRule(stream, false, undefined, 1);
127
+ if (rule === null) {
128
+ this.#diagnostic("invalid-rule", "The input does not contain a complete rule.", stream.next.span, "#parse-a-rule");
129
+ return this.#failure();
130
+ }
131
+ stream.discardWhitespace();
132
+ if (this.#nextKind(stream) !== "eof") {
133
+ this.#diagnostic("trailing-input", "A single-rule input contains trailing tokens.", stream.next.span, "#parse-a-rule");
134
+ return this.#failure();
135
+ }
136
+ return this.#success(rule);
137
+ }
138
+ parseDeclaration() {
139
+ const { stream, tokenizerErrors } = this.#tokenize();
140
+ this.#tokenizerErrors = tokenizerErrors;
141
+ stream.discardWhitespace();
142
+ const declaration = this.#consumeDeclaration(stream, false, 1);
143
+ if (declaration === null) {
144
+ this.#diagnostic("invalid-declaration", "The input does not contain a syntactically valid declaration.", stream.next.span, "#parse-a-declaration");
145
+ return this.#failure();
146
+ }
147
+ return this.#success(declaration);
148
+ }
149
+ parseComponentValue() {
150
+ const { stream, tokenizerErrors } = this.#tokenize();
151
+ this.#tokenizerErrors = tokenizerErrors;
152
+ stream.discardWhitespace();
153
+ if (stream.empty) {
154
+ this.#diagnostic("empty-input", "A component value was requested from empty input.", emptySpan(this.#input), "#parse-a-component-value");
155
+ return this.#failure();
156
+ }
157
+ const value = this.#consumeComponentValue(stream, 1);
158
+ stream.discardWhitespace();
159
+ if (this.#nextKind(stream) !== "eof") {
160
+ this.#diagnostic("trailing-input", "A single-component-value input contains trailing tokens.", stream.next.span, "#parse-a-component-value");
161
+ return this.#failure();
162
+ }
163
+ return this.#success(value);
164
+ }
165
+ parseComponentValues() {
166
+ const { stream, tokenizerErrors } = this.#tokenize();
167
+ this.#tokenizerErrors = tokenizerErrors;
168
+ return this.#success(this.#consumeComponentValues(stream, undefined, false, 1));
169
+ }
170
+ parseCommaSeparatedComponentValues() {
171
+ const { stream, tokenizerErrors } = this.#tokenize();
172
+ this.#tokenizerErrors = tokenizerErrors;
173
+ const groups = [];
174
+ while (!stream.empty) {
175
+ groups.push(this.#consumeComponentValues(stream, "comma", false, 1));
176
+ stream.discard();
177
+ }
178
+ return this.#success(freezeArray(groups));
179
+ }
180
+ #tokenize() {
181
+ const tokenizer = new CssTokenizer(this.#input, { guard: this.#guard });
182
+ const result = tokenizer.tokenize();
183
+ return {
184
+ stream: new TokenStream(result.tokens, result.end),
185
+ tokenizerErrors: result.errors
186
+ };
187
+ }
188
+ #consumeStylesheetContents(stream, depth) {
189
+ const rules = [];
190
+ while (!stream.empty) {
191
+ const token = stream.next;
192
+ if (token.kind === "whitespace" ||
193
+ token.kind === "cdo" ||
194
+ token.kind === "cdc") {
195
+ stream.discard();
196
+ }
197
+ else if (token.kind === "at-keyword") {
198
+ rules.push(this.#consumeAtRule(stream, false, depth));
199
+ }
200
+ else {
201
+ const start = stream.next.span;
202
+ const rule = this.#consumeQualifiedRule(stream, false, undefined, depth);
203
+ if (rule !== null) {
204
+ rules.push(rule);
205
+ }
206
+ else {
207
+ this.#diagnostic("invalid-rule", "The stylesheet contains an incomplete qualified rule.", start, "#consume-a-stylesheets-contents");
208
+ }
209
+ }
210
+ }
211
+ return freezeArray(rules);
212
+ }
213
+ #consumeAtRule(stream, nested, depth) {
214
+ const start = stream.index;
215
+ const nameToken = stream.consume();
216
+ if (nameToken.kind !== "at-keyword") {
217
+ throw new Error("at-rule consumption requires an at-keyword token");
218
+ }
219
+ const prelude = [];
220
+ for (;;) {
221
+ const token = stream.next;
222
+ if (token.kind === "semicolon") {
223
+ stream.discard();
224
+ return this.#atRule(nameToken.value, prelude, null, stream.spanFrom(start), depth);
225
+ }
226
+ if (token.kind === "eof" || (nested && token.kind === "close-curly")) {
227
+ return this.#atRule(nameToken.value, prelude, null, stream.spanFrom(start), depth);
228
+ }
229
+ if (token.kind === "open-curly") {
230
+ const block = this.#consumeBlock(stream, depth + 1);
231
+ return this.#atRule(nameToken.value, prelude, block, stream.spanFrom(start), depth);
232
+ }
233
+ prelude.push(this.#consumeComponentValue(stream, depth + 1));
234
+ }
235
+ }
236
+ #consumeQualifiedRule(stream, nested, stopKind, depth) {
237
+ const start = stream.index;
238
+ const prelude = [];
239
+ for (;;) {
240
+ const token = stream.next;
241
+ if (token.kind === "eof" || token.kind === stopKind)
242
+ return null;
243
+ if (token.kind === "close-curly") {
244
+ if (nested)
245
+ return null;
246
+ this.#diagnostic("unexpected-closing-token", "A closing curly bracket has no matching block.", token.span, "#consume-a-qualified-rule");
247
+ }
248
+ if (token.kind === "open-curly") {
249
+ if (this.#looksLikeCustomPropertyRule(prelude)) {
250
+ this.#consumeBadDeclarationRemnants(stream, nested);
251
+ return null;
252
+ }
253
+ const block = this.#consumeBlock(stream, depth + 1);
254
+ return this.#qualifiedRule(prelude, block, stream.spanFrom(start), depth);
255
+ }
256
+ prelude.push(this.#consumeComponentValue(stream, depth + 1));
257
+ }
258
+ }
259
+ #consumeBlock(stream, depth) {
260
+ const start = stream.index;
261
+ const opening = stream.consume();
262
+ if (opening.kind !== "open-curly") {
263
+ throw new Error("block consumption requires an opening curly token");
264
+ }
265
+ const items = this.#consumeBlockContents(stream, depth + 1);
266
+ if (this.#nextKind(stream) === "close-curly")
267
+ stream.discard();
268
+ this.#guard.createNode(depth);
269
+ return Object.freeze({
270
+ id: this.#takeNodeId(),
271
+ kind: "block",
272
+ items,
273
+ span: stream.spanFrom(start)
274
+ });
275
+ }
276
+ #consumeBlockContents(stream, depth) {
277
+ const items = [];
278
+ while (!stream.empty && stream.next.kind !== "close-curly") {
279
+ if (stream.next.kind === "whitespace" || stream.next.kind === "semicolon") {
280
+ stream.discard();
281
+ continue;
282
+ }
283
+ if (stream.next.kind === "at-keyword") {
284
+ items.push(this.#consumeAtRule(stream, true, depth));
285
+ continue;
286
+ }
287
+ const start = stream.next.span;
288
+ const startsLikeDeclaration = stream.next.kind === "ident";
289
+ stream.mark();
290
+ const declaration = this.#consumeDeclaration(stream, true, depth);
291
+ if (declaration !== null) {
292
+ stream.discardMark();
293
+ items.push(declaration);
294
+ continue;
295
+ }
296
+ stream.restore();
297
+ const rule = this.#consumeQualifiedRule(stream, true, "semicolon", depth);
298
+ if (rule !== null) {
299
+ items.push(rule);
300
+ }
301
+ else {
302
+ this.#diagnostic(startsLikeDeclaration ? "invalid-declaration" : "invalid-rule", startsLikeDeclaration
303
+ ? "A declaration-like construct has no valid name, colon, or value."
304
+ : "Block content does not form a complete nested rule.", start, "#consume-a-blocks-contents");
305
+ if (this.#nextKind(stream) !== "semicolon" &&
306
+ this.#nextKind(stream) !== "close-curly") {
307
+ this.#consumeBadDeclarationRemnants(stream, true);
308
+ }
309
+ }
310
+ }
311
+ return freezeArray(items);
312
+ }
313
+ #consumeDeclaration(stream, nested, depth) {
314
+ const start = stream.index;
315
+ const nameToken = stream.next;
316
+ if (nameToken.kind !== "ident")
317
+ return null;
318
+ stream.discard();
319
+ stream.discardWhitespace();
320
+ if (stream.next.kind !== "colon")
321
+ return null;
322
+ stream.discard();
323
+ stream.discardWhitespace();
324
+ let value = [...this.#consumeComponentValues(stream, "semicolon", nested, depth + 1)];
325
+ const custom = nameToken.value.startsWith("--");
326
+ if (!custom && this.#hasAmbiguousCurlyBlock(value))
327
+ return null;
328
+ let important = false;
329
+ const nonWhitespace = value
330
+ .map((item, index) => ({ item, index }))
331
+ .filter(({ item }) => !isWhitespace(item));
332
+ const last = nonWhitespace.at(-1);
333
+ const previous = nonWhitespace.at(-2);
334
+ if (previous?.item.kind === "delim" &&
335
+ previous.item.value === 0x21 &&
336
+ last?.item.kind === "ident" &&
337
+ lowerAscii(last.item.value) === "important") {
338
+ value.splice(previous.index);
339
+ important = true;
340
+ }
341
+ while (value.at(-1)?.kind === "whitespace")
342
+ value.pop();
343
+ if (lowerAscii(nameToken.value) === "unicode-range") {
344
+ value = [...this.#parseUnicodeRangeValue(value, depth + 1)];
345
+ }
346
+ const span = stream.spanFrom(start);
347
+ const originalTextStart = value.at(0)?.span.start.offset;
348
+ const originalTextEnd = value.at(-1)?.span.end.offset;
349
+ this.#guard.createNode(depth);
350
+ return Object.freeze({
351
+ id: this.#takeNodeId(),
352
+ kind: "declaration",
353
+ name: nameToken.value,
354
+ value: freezeArray(value),
355
+ important,
356
+ ...(custom
357
+ ? {
358
+ originalText: originalTextStart === undefined || originalTextEnd === undefined
359
+ ? ""
360
+ : this.#input.slice(originalTextStart, originalTextEnd)
361
+ }
362
+ : {}),
363
+ span
364
+ });
365
+ }
366
+ #consumeBadDeclarationRemnants(stream, nested) {
367
+ while (!stream.empty) {
368
+ if (stream.next.kind === "semicolon") {
369
+ stream.discard();
370
+ return;
371
+ }
372
+ if (stream.next.kind === "close-curly" && nested)
373
+ return;
374
+ this.#consumeComponentValue(stream, 1);
375
+ }
376
+ }
377
+ #consumeComponentValues(stream, stopKind, nested, depth) {
378
+ const values = [];
379
+ while (!stream.empty && stream.next.kind !== stopKind) {
380
+ if (nested && stream.next.kind === "close-curly")
381
+ break;
382
+ values.push(this.#consumeComponentValue(stream, depth));
383
+ }
384
+ return freezeArray(values);
385
+ }
386
+ #consumeComponentValue(stream, depth) {
387
+ const token = stream.next;
388
+ if (token.kind === "open-square" ||
389
+ token.kind === "open-paren" ||
390
+ token.kind === "open-curly") {
391
+ return this.#consumeSimpleBlock(stream, depth);
392
+ }
393
+ if (token.kind === "function")
394
+ return this.#consumeFunction(stream, depth);
395
+ const consumed = stream.consume();
396
+ if (!isPreservedToken(consumed)) {
397
+ throw new Error("unexpected non-preserved CSS token");
398
+ }
399
+ if (isClosingToken(consumed)) {
400
+ this.#diagnostic("unexpected-closing-token", "A closing token has no matching block.", consumed.span, "#consume-a-list-of-component-values");
401
+ }
402
+ else if (consumed.kind === "bad-string") {
403
+ this.#diagnostic("bad-string-token", "A malformed string token was preserved for error recovery.", consumed.span, "#consume-a-string-token");
404
+ }
405
+ else if (consumed.kind === "bad-url") {
406
+ this.#diagnostic("bad-url-token", "A malformed URL token was preserved for error recovery.", consumed.span, "#consume-a-url-token");
407
+ }
408
+ return consumed;
409
+ }
410
+ #consumeSimpleBlock(stream, depth) {
411
+ const start = stream.index;
412
+ const opening = stream.consume();
413
+ if (opening.kind !== "open-square" &&
414
+ opening.kind !== "open-paren" &&
415
+ opening.kind !== "open-curly") {
416
+ throw new Error("simple-block consumption requires an opening token");
417
+ }
418
+ const endKind = closingKind(opening.kind);
419
+ const values = [];
420
+ while (!stream.empty && stream.next.kind !== endKind) {
421
+ values.push(this.#consumeComponentValue(stream, depth + 1));
422
+ }
423
+ if (stream.next.kind === endKind)
424
+ stream.discard();
425
+ this.#guard.createNode(depth);
426
+ return Object.freeze({
427
+ id: this.#takeNodeId(),
428
+ kind: "simple-block",
429
+ associatedToken: opening.kind,
430
+ value: freezeArray(values),
431
+ span: stream.spanFrom(start)
432
+ });
433
+ }
434
+ #consumeFunction(stream, depth) {
435
+ const start = stream.index;
436
+ const opening = stream.consume();
437
+ if (opening.kind !== "function") {
438
+ throw new Error("function consumption requires a function token");
439
+ }
440
+ const values = [];
441
+ while (!stream.empty && stream.next.kind !== "close-paren") {
442
+ values.push(this.#consumeComponentValue(stream, depth + 1));
443
+ }
444
+ if (stream.next.kind === "close-paren")
445
+ stream.discard();
446
+ this.#guard.createNode(depth);
447
+ return Object.freeze({
448
+ id: this.#takeNodeId(),
449
+ kind: "function-block",
450
+ name: opening.value,
451
+ value: freezeArray(values),
452
+ span: stream.spanFrom(start)
453
+ });
454
+ }
455
+ #parseUnicodeRangeValue(currentValue, depth) {
456
+ const first = currentValue.at(0);
457
+ const last = currentValue.at(-1);
458
+ if (first === undefined || last === undefined)
459
+ return currentValue;
460
+ const source = this.#input.slice(first.span.start.offset, last.span.end.offset);
461
+ const tokenizer = new CssTokenizer(source, {
462
+ guard: this.#guard,
463
+ unicodeRanges: true
464
+ });
465
+ const tokenized = tokenizer.tokenize();
466
+ const remappedTokens = tokenized.tokens.map((token) => remapToken(token, first.span.start));
467
+ if (tokenized.errors.length > 0) {
468
+ this.#tokenizerErrors = freezeArray([
469
+ ...this.#tokenizerErrors,
470
+ ...tokenized.errors.map((error) => Object.freeze({
471
+ ...error,
472
+ span: remapSpan(error.span, first.span.start)
473
+ }))
474
+ ]);
475
+ }
476
+ return this.#consumeComponentValues(new TokenStream(remappedTokens, remapPosition(tokenized.end, first.span.start)), undefined, false, depth);
477
+ }
478
+ #looksLikeCustomPropertyRule(prelude) {
479
+ const values = prelude.filter((value) => !isWhitespace(value));
480
+ return (values[0]?.kind === "ident" &&
481
+ values[0].value.startsWith("--") &&
482
+ values[1]?.kind === "colon");
483
+ }
484
+ #hasAmbiguousCurlyBlock(value) {
485
+ const significant = value.filter((item) => !isWhitespace(item));
486
+ const curlyBlocks = significant.filter((item) => item.kind === "simple-block" && item.associatedToken === "open-curly");
487
+ return curlyBlocks.length > 0 && significant.length !== 1;
488
+ }
489
+ #stylesheet(rules, span, depth) {
490
+ this.#guard.createNode(depth);
491
+ return Object.freeze({
492
+ id: this.#takeNodeId(),
493
+ kind: "stylesheet",
494
+ rules,
495
+ span
496
+ });
497
+ }
498
+ #atRule(name, prelude, block, span, depth) {
499
+ this.#guard.createNode(depth);
500
+ return Object.freeze({
501
+ id: this.#takeNodeId(),
502
+ kind: "at-rule",
503
+ name,
504
+ prelude: freezeArray(prelude),
505
+ block,
506
+ span
507
+ });
508
+ }
509
+ #qualifiedRule(prelude, block, span, depth) {
510
+ this.#guard.createNode(depth);
511
+ return Object.freeze({
512
+ id: this.#takeNodeId(),
513
+ kind: "qualified-rule",
514
+ prelude: freezeArray(prelude),
515
+ block,
516
+ span
517
+ });
518
+ }
519
+ #takeNodeId() {
520
+ const id = this.#nextNodeId;
521
+ this.#nextNodeId += 1;
522
+ return id;
523
+ }
524
+ #nextKind(stream) {
525
+ return stream.next.kind;
526
+ }
527
+ #diagnostic(code, message, span, anchor) {
528
+ this.#diagnostics.push(Object.freeze({
529
+ kind: "parser",
530
+ code,
531
+ message,
532
+ span,
533
+ specRef: `${CSS_SYNTAX}${anchor}`
534
+ }));
535
+ }
536
+ #errors() {
537
+ return freezeArray([...this.#tokenizerErrors, ...this.#diagnostics]);
538
+ }
539
+ #usage() {
540
+ return this.#guard.snapshot();
541
+ }
542
+ #success(value) {
543
+ return Object.freeze({
544
+ ok: true,
545
+ value,
546
+ errors: this.#errors(),
547
+ usage: this.#usage()
548
+ });
549
+ }
550
+ #failure() {
551
+ return Object.freeze({
552
+ ok: false,
553
+ errors: this.#errors(),
554
+ usage: this.#usage()
555
+ });
556
+ }
557
+ }
558
+ export function parseCssStylesheet(input, options = {}) {
559
+ return new CssSyntaxParser(input, options).parseStylesheet();
560
+ }
561
+ export function parseCssStylesheetContents(input, options = {}) {
562
+ return new CssSyntaxParser(input, options).parseStylesheetContents();
563
+ }
564
+ export function parseCssBlockContents(input, options = {}) {
565
+ return new CssSyntaxParser(input, options).parseBlockContents();
566
+ }
567
+ export function parseCssRule(input, options = {}) {
568
+ return new CssSyntaxParser(input, options).parseRule();
569
+ }
570
+ export function parseCssDeclaration(input, options = {}) {
571
+ return new CssSyntaxParser(input, options).parseDeclaration();
572
+ }
573
+ export function parseCssComponentValue(input, options = {}) {
574
+ return new CssSyntaxParser(input, options).parseComponentValue();
575
+ }
576
+ export function parseCssComponentValues(input, options = {}) {
577
+ return new CssSyntaxParser(input, options).parseComponentValues();
578
+ }
579
+ export function parseCssCommaSeparatedComponentValues(input, options = {}) {
580
+ return new CssSyntaxParser(input, options).parseCommaSeparatedComponentValues();
581
+ }
@@ -0,0 +1,28 @@
1
+ import type { ResourceLimitName, ResourceLimits, ResourceUsage } from "./types.js";
2
+ export declare class SyntaxResourceError extends Error {
3
+ readonly limitName: ResourceLimitName;
4
+ readonly limit: number;
5
+ readonly actual: number;
6
+ readonly code = "CSS_RESOURCE_LIMIT_EXCEEDED";
7
+ constructor(limitName: ResourceLimitName, limit: number, actual: number);
8
+ }
9
+ export declare class SyntaxAbortError extends Error {
10
+ readonly reason: unknown;
11
+ readonly code = "CSS_OPERATION_ABORTED";
12
+ constructor(reason: unknown);
13
+ }
14
+ export declare class ResourceGuard {
15
+ #private;
16
+ readonly signal?: AbortSignal | undefined;
17
+ readonly limits: ResourceLimits;
18
+ constructor(limits?: ResourceLimits, signal?: AbortSignal | undefined, initialUsage?: ResourceUsage);
19
+ assertActive(): void;
20
+ setInputBytes(value: number): void;
21
+ observeBufferedBytes(value: number): void;
22
+ emitToken(count?: number): void;
23
+ createNode(depth: number): void;
24
+ step(count?: number): void;
25
+ snapshot(): ResourceUsage;
26
+ assertIncrement(value: number): void;
27
+ assertObservedValue(value: number, name: string): void;
28
+ }