@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,702 @@
1
+ import { CSS_WEBREF_DATA } from "../generated/css-data.js";
2
+ import { ResourceGuard } from "../syntax/resources.js";
3
+ import { parseCssComponentValues } from "../syntax/parser.js";
4
+ const SELECTORS = "https://drafts.csswg.org/selectors/";
5
+ const LEGACY_PSEUDO_ELEMENTS = new Set([
6
+ "after",
7
+ "before",
8
+ "first-letter",
9
+ "first-line"
10
+ ]);
11
+ const FORGIVING_SELECTOR_LIST_PSEUDOS = new Set(["is", "where"]);
12
+ const NTH_PSEUDOS = new Set([
13
+ "nth-child",
14
+ "nth-last-child",
15
+ "nth-of-type",
16
+ "nth-last-of-type"
17
+ ]);
18
+ const EXCLUDED_OBSOLETE_PSEUDOS = new Set(["matches"]);
19
+ function knownPseudos(kind, functional) {
20
+ const prefix = kind === "class" ? ":" : "::";
21
+ const names = CSS_WEBREF_DATA.selectors.flatMap((selector) => {
22
+ if (!selector.startsWith(prefix) ||
23
+ (kind === "class" && selector.startsWith("::"))) {
24
+ return [];
25
+ }
26
+ const notation = selector.slice(prefix.length);
27
+ const isFunctional = notation.endsWith("()");
28
+ const name = isFunctional ? notation.slice(0, -2) : notation;
29
+ return isFunctional === functional && !EXCLUDED_OBSOLETE_PSEUDOS.has(name)
30
+ ? [name]
31
+ : [];
32
+ });
33
+ return new Set(names);
34
+ }
35
+ const FUNCTIONAL_PSEUDO_CLASSES = knownPseudos("class", true);
36
+ const NON_FUNCTIONAL_PSEUDO_CLASSES = knownPseudos("class", false);
37
+ const FUNCTIONAL_PSEUDO_ELEMENTS = knownPseudos("element", true);
38
+ const NON_FUNCTIONAL_PSEUDO_ELEMENTS = knownPseudos("element", false);
39
+ function lowerAscii(value) {
40
+ return value.replace(/[A-Z]/gu, (character) => character.toLowerCase());
41
+ }
42
+ function frozen(values) {
43
+ return Object.freeze(values);
44
+ }
45
+ function isDelim(value, codePoint) {
46
+ return value?.kind === "delim" && value.value === codePoint;
47
+ }
48
+ function position(offset, line, column) {
49
+ return Object.freeze({ offset, line, column });
50
+ }
51
+ function emptySpan() {
52
+ const start = position(0, 1, 1);
53
+ return Object.freeze({ start, end: start });
54
+ }
55
+ function coveringSpan(values) {
56
+ const first = values[0];
57
+ const last = values.at(-1);
58
+ return first === undefined || last === undefined
59
+ ? emptySpan()
60
+ : Object.freeze({ start: first.span.start, end: last.span.end });
61
+ }
62
+ function joinedSpan(start, end) {
63
+ return Object.freeze({ start: start.start, end: end.end });
64
+ }
65
+ function splitAtCommas(values) {
66
+ const groups = [[]];
67
+ for (const value of values) {
68
+ if (value.kind === "comma")
69
+ groups.push([]);
70
+ else
71
+ groups.at(-1)?.push(value);
72
+ }
73
+ return Object.freeze(groups.map((group) => Object.freeze(group)));
74
+ }
75
+ function significant(values) {
76
+ return Object.freeze(values.filter((value) => value.kind !== "whitespace"));
77
+ }
78
+ class SelectorParser {
79
+ values;
80
+ #guard;
81
+ #diagnostics = [];
82
+ #depth = 0;
83
+ #hasDepth = 0;
84
+ constructor(values, options, priorUsage) {
85
+ this.values = values;
86
+ this.#guard = new ResourceGuard(options.limits, options.signal, priorUsage);
87
+ }
88
+ parse(syntaxErrors) {
89
+ const parsed = this.#parseList(this.values, false, false, true);
90
+ const errors = Object.freeze([...syntaxErrors, ...this.#diagnostics]);
91
+ if (syntaxErrors.length > 0 ||
92
+ !parsed.valid ||
93
+ parsed.selectors.length === 0) {
94
+ return Object.freeze({
95
+ ok: false,
96
+ errors,
97
+ usage: this.#usage()
98
+ });
99
+ }
100
+ const value = Object.freeze({
101
+ selectors: parsed.selectors,
102
+ span: coveringSpan(this.values)
103
+ });
104
+ this.#node();
105
+ return Object.freeze({
106
+ ok: true,
107
+ value,
108
+ errors,
109
+ usage: this.#usage()
110
+ });
111
+ }
112
+ #parseList(values, forgiving, relative, allowPseudoElements) {
113
+ return this.#nested(() => {
114
+ const selectors = [];
115
+ let valid = true;
116
+ for (const group of splitAtCommas(values)) {
117
+ this.#guard.step();
118
+ const diagnosticStart = this.#diagnostics.length;
119
+ const localDiagnostics = [];
120
+ const parser = new ComplexSelectorParser(group, relative, allowPseudoElements, this.#guard, (code, message, span) => {
121
+ localDiagnostics.push(Object.freeze({
122
+ kind: "selector",
123
+ code,
124
+ message,
125
+ span,
126
+ specRef: `${SELECTORS}#grammar`
127
+ }));
128
+ }, (functionBlock, kind) => this.#pseudoArgument(functionBlock, kind));
129
+ const selector = parser.parse();
130
+ if (selector === null) {
131
+ valid = false;
132
+ if (forgiving) {
133
+ this.#diagnostics.splice(diagnosticStart);
134
+ }
135
+ else {
136
+ this.#diagnostics.push(...localDiagnostics);
137
+ }
138
+ }
139
+ else {
140
+ this.#diagnostics.push(...localDiagnostics);
141
+ selectors.push(selector);
142
+ }
143
+ }
144
+ return Object.freeze({
145
+ selectors: frozen(selectors),
146
+ valid: forgiving || valid
147
+ });
148
+ });
149
+ }
150
+ #pseudoArgument(value, kind) {
151
+ const name = lowerAscii(value.name);
152
+ if ((kind === "class" && NON_FUNCTIONAL_PSEUDO_CLASSES.has(name)) ||
153
+ (kind === "element" && NON_FUNCTIONAL_PSEUDO_ELEMENTS.has(name))) {
154
+ this.#diagnostic("invalid-pseudo", `:${kind === "element" ? ":" : ""}${name} is not functional.`, value.span);
155
+ return null;
156
+ }
157
+ if (kind === "class" &&
158
+ FORGIVING_SELECTOR_LIST_PSEUDOS.has(name)) {
159
+ const parsed = this.#parseList(value.value, true, false, false);
160
+ return Object.freeze({
161
+ kind: "selector-list",
162
+ selectors: parsed.selectors,
163
+ forgiving: true,
164
+ relative: false
165
+ });
166
+ }
167
+ if (kind === "class" && name === "not") {
168
+ const parsed = this.#parseList(value.value, false, false, false);
169
+ if (!parsed.valid || parsed.selectors.length === 0) {
170
+ this.#diagnostic("invalid-pseudo", ":not() requires at least one valid selector.", value.span);
171
+ return null;
172
+ }
173
+ return Object.freeze({
174
+ kind: "selector-list",
175
+ selectors: parsed.selectors,
176
+ forgiving: false,
177
+ relative: false
178
+ });
179
+ }
180
+ if (kind === "class" && name === "has") {
181
+ if (this.#hasDepth > 0) {
182
+ this.#diagnostic("invalid-pseudo", ":has() cannot be nested.", value.span);
183
+ return null;
184
+ }
185
+ this.#hasDepth += 1;
186
+ let parsed;
187
+ try {
188
+ parsed = this.#parseList(value.value, false, true, false);
189
+ }
190
+ finally {
191
+ this.#hasDepth -= 1;
192
+ }
193
+ if (!parsed.valid || parsed.selectors.length === 0) {
194
+ this.#diagnostic("invalid-pseudo", ":has() requires at least one valid relative selector.", value.span);
195
+ return null;
196
+ }
197
+ return Object.freeze({
198
+ kind: "selector-list",
199
+ selectors: parsed.selectors,
200
+ forgiving: false,
201
+ relative: true
202
+ });
203
+ }
204
+ if (kind === "class" && NTH_PSEUDOS.has(name)) {
205
+ return this.#parseNth(value);
206
+ }
207
+ if (kind === "class" && name === "dir") {
208
+ const parts = significant(value.value);
209
+ if (parts.length !== 1 || parts[0]?.kind !== "ident") {
210
+ this.#diagnostic("invalid-pseudo", ":dir() requires one identifier.", value.span);
211
+ return null;
212
+ }
213
+ }
214
+ if (kind === "class" && name === "lang") {
215
+ const ranges = splitAtCommas(value.value);
216
+ if (ranges.length === 0 ||
217
+ ranges.some((range) => {
218
+ const parts = significant(range);
219
+ return parts.length !== 1 ||
220
+ (parts[0]?.kind !== "ident" && parts[0]?.kind !== "string");
221
+ })) {
222
+ this.#diagnostic("invalid-pseudo", ":lang() requires a comma-separated list of identifiers or strings.", value.span);
223
+ return null;
224
+ }
225
+ }
226
+ if (kind === "element" && name === "slotted") {
227
+ const parsed = this.#parseList(value.value, false, false, false);
228
+ if (!parsed.valid || parsed.selectors.length !== 1) {
229
+ this.#diagnostic("invalid-pseudo", "::slotted() requires one compound selector.", value.span);
230
+ return null;
231
+ }
232
+ return Object.freeze({
233
+ kind: "selector-list",
234
+ selectors: parsed.selectors,
235
+ forgiving: false,
236
+ relative: false
237
+ });
238
+ }
239
+ return Object.freeze({
240
+ kind: "raw",
241
+ value: Object.freeze([...value.value])
242
+ });
243
+ }
244
+ #parseNth(value) {
245
+ const parts = [...value.value];
246
+ let ofIndex = -1;
247
+ for (let index = 0; index < parts.length; index += 1) {
248
+ const part = parts[index];
249
+ if (part?.kind === "ident" && lowerAscii(part.value) === "of") {
250
+ ofIndex = index;
251
+ break;
252
+ }
253
+ }
254
+ const formulaValues = significant(ofIndex < 0 ? parts : parts.slice(0, ofIndex));
255
+ const formula = formulaValues.map((part) => {
256
+ if (part.kind === "ident")
257
+ return part.value;
258
+ if (part.kind === "number")
259
+ return part.representation;
260
+ if (part.kind === "dimension") {
261
+ return `${part.representation}${part.unit}`;
262
+ }
263
+ return part.kind === "delim"
264
+ ? String.fromCodePoint(part.value)
265
+ : "";
266
+ }).join("");
267
+ const coefficients = parseAnPlusB(formula);
268
+ if (coefficients === null) {
269
+ this.#diagnostic("invalid-nth", "Expected an An+B expression.", value.span);
270
+ return null;
271
+ }
272
+ let of = Object.freeze([]);
273
+ if (ofIndex >= 0) {
274
+ const name = lowerAscii(value.name);
275
+ if (name !== "nth-child" && name !== "nth-last-child") {
276
+ this.#diagnostic("invalid-nth", `:${name}() does not accept an of selector list.`, value.span);
277
+ return null;
278
+ }
279
+ const parsed = this.#parseList(parts.slice(ofIndex + 1), false, false, false);
280
+ if (!parsed.valid || parsed.selectors.length === 0) {
281
+ this.#diagnostic("invalid-nth", "The of clause requires a selector list.", value.span);
282
+ return null;
283
+ }
284
+ of = parsed.selectors;
285
+ }
286
+ return Object.freeze({
287
+ kind: "nth",
288
+ ...coefficients,
289
+ of
290
+ });
291
+ }
292
+ #diagnostic(code, message, span) {
293
+ this.#diagnostics.push(Object.freeze({
294
+ kind: "selector",
295
+ code,
296
+ message,
297
+ span,
298
+ specRef: `${SELECTORS}#grammar`
299
+ }));
300
+ }
301
+ #node() {
302
+ this.#guard.createNode(this.#depth);
303
+ }
304
+ #usage() {
305
+ return this.#guard.snapshot();
306
+ }
307
+ #nested(operation) {
308
+ this.#depth += 1;
309
+ try {
310
+ this.#guard.createNode(this.#depth);
311
+ return operation();
312
+ }
313
+ finally {
314
+ this.#depth -= 1;
315
+ }
316
+ }
317
+ }
318
+ class ComplexSelectorParser {
319
+ relative;
320
+ allowPseudoElements;
321
+ guard;
322
+ diagnostic;
323
+ pseudoArgument;
324
+ #values;
325
+ #index = 0;
326
+ #failed = false;
327
+ constructor(values, relative, allowPseudoElements, guard, diagnostic, pseudoArgument) {
328
+ this.relative = relative;
329
+ this.allowPseudoElements = allowPseudoElements;
330
+ this.guard = guard;
331
+ this.diagnostic = diagnostic;
332
+ this.pseudoArgument = pseudoArgument;
333
+ this.#values = values;
334
+ }
335
+ parse() {
336
+ this.#skipWhitespace();
337
+ const leadingCombinator = this.relative ? this.#explicitCombinator() : null;
338
+ if (leadingCombinator !== null)
339
+ this.#skipWhitespace();
340
+ const compounds = [];
341
+ const combinators = [];
342
+ const first = this.#compound();
343
+ if (first === null) {
344
+ if (!this.#failed) {
345
+ this.#fail("empty-selector", "Expected a compound selector.", coveringSpan(this.#values));
346
+ }
347
+ return null;
348
+ }
349
+ compounds.push(first);
350
+ while (this.#index < this.#values.length) {
351
+ const hadWhitespace = this.#skipWhitespace();
352
+ let combinator = this.#explicitCombinator();
353
+ if (combinator === null && hadWhitespace)
354
+ combinator = " ";
355
+ if (combinator === null) {
356
+ this.#fail("invalid-selector", "Expected a combinator or the end of the selector.", this.#currentSpan());
357
+ return null;
358
+ }
359
+ this.#skipWhitespace();
360
+ const compound = this.#compound();
361
+ if (compound === null) {
362
+ this.#fail("invalid-combinator", "A combinator must be followed by a compound selector.", this.#currentSpan());
363
+ return null;
364
+ }
365
+ combinators.push(combinator);
366
+ compounds.push(compound);
367
+ }
368
+ const span = joinedSpan(compounds[0]?.span ?? coveringSpan(this.#values), compounds.at(-1)?.span ?? coveringSpan(this.#values));
369
+ this.guard.createNode(1);
370
+ return Object.freeze({
371
+ leadingCombinator,
372
+ compounds: frozen(compounds),
373
+ combinators: frozen(combinators),
374
+ span
375
+ });
376
+ }
377
+ #compound() {
378
+ const start = this.#index;
379
+ const type = this.#typeSelector();
380
+ const simples = [];
381
+ let pseudoElementSeen = false;
382
+ for (;;) {
383
+ this.guard.step();
384
+ const simple = this.#simpleSelector();
385
+ if (simple === null)
386
+ break;
387
+ if (pseudoElementSeen &&
388
+ simple.kind !== "pseudo-class" &&
389
+ simple.kind !== "pseudo-element") {
390
+ this.#fail("invalid-selector", "Only pseudo selectors may follow a pseudo-element.", simple.span);
391
+ return null;
392
+ }
393
+ if (!this.allowPseudoElements &&
394
+ simple.kind === "pseudo-element") {
395
+ this.#fail("invalid-selector", "Pseudo-elements are not allowed in this selector list.", simple.span);
396
+ return null;
397
+ }
398
+ if (simple.kind === "pseudo-element")
399
+ pseudoElementSeen = true;
400
+ simples.push(simple);
401
+ }
402
+ if (type === null && simples.length === 0)
403
+ return null;
404
+ const consumed = this.#values.slice(start, this.#index);
405
+ this.guard.createNode(2);
406
+ return Object.freeze({
407
+ type,
408
+ simples: frozen(simples),
409
+ span: coveringSpan(consumed)
410
+ });
411
+ }
412
+ #typeSelector() {
413
+ const start = this.#index;
414
+ const first = this.#values[this.#index];
415
+ const firstName = this.#nameOrStar(first);
416
+ const startsEmptyNamespace = isDelim(first, 0x7c);
417
+ if (firstName === null && !startsEmptyNamespace)
418
+ return null;
419
+ let namespace = null;
420
+ let name = firstName;
421
+ if (startsEmptyNamespace) {
422
+ namespace = "";
423
+ this.#index += 1;
424
+ name = this.#nameOrStar(this.#values[this.#index]);
425
+ if (name === null) {
426
+ this.#index = start;
427
+ return null;
428
+ }
429
+ this.#index += 1;
430
+ }
431
+ else if (isDelim(this.#values[this.#index + 1], 0x7c) &&
432
+ !isDelim(this.#values[this.#index + 2], 0x7c)) {
433
+ namespace = firstName;
434
+ this.#index += 2;
435
+ name = this.#nameOrStar(this.#values[this.#index]);
436
+ if (name === null) {
437
+ this.#index = start;
438
+ return null;
439
+ }
440
+ this.#index += 1;
441
+ }
442
+ else {
443
+ this.#index += 1;
444
+ }
445
+ if (name === null) {
446
+ this.#index = start;
447
+ return null;
448
+ }
449
+ const span = coveringSpan(this.#values.slice(start, this.#index));
450
+ this.guard.createNode(3);
451
+ return Object.freeze({
452
+ kind: "type",
453
+ namespace,
454
+ name,
455
+ universal: name === "*",
456
+ span
457
+ });
458
+ }
459
+ #simpleSelector() {
460
+ const value = this.#values[this.#index];
461
+ if (value?.kind === "hash" && value.hashType === "id") {
462
+ this.#index += 1;
463
+ this.guard.createNode(3);
464
+ return Object.freeze({ kind: "id", value: value.value, span: value.span });
465
+ }
466
+ if (isDelim(value, 0x2e)) {
467
+ const name = this.#values[this.#index + 1];
468
+ if (name?.kind !== "ident")
469
+ return null;
470
+ this.#index += 2;
471
+ this.guard.createNode(3);
472
+ return Object.freeze({
473
+ kind: "class",
474
+ value: name.value,
475
+ span: joinedSpan(value?.span ?? name.span, name.span)
476
+ });
477
+ }
478
+ if (value?.kind === "simple-block" &&
479
+ value.associatedToken === "open-square") {
480
+ this.#index += 1;
481
+ return this.#attribute(value);
482
+ }
483
+ if (value?.kind === "colon")
484
+ return this.#pseudo();
485
+ if (isDelim(value, 0x26)) {
486
+ this.#index += 1;
487
+ this.guard.createNode(3);
488
+ return Object.freeze({ kind: "nesting", span: value?.span ?? emptySpan() });
489
+ }
490
+ return null;
491
+ }
492
+ #attribute(block) {
493
+ const parts = significant(block.value);
494
+ let index = 0;
495
+ const firstName = this.#nameOrStar(parts[index]);
496
+ const emptyNamespace = isDelim(parts[index], 0x7c);
497
+ let namespace = null;
498
+ let name = firstName;
499
+ if (emptyNamespace) {
500
+ namespace = "";
501
+ index += 1;
502
+ name = this.#nameOrStar(parts[index]);
503
+ index += 1;
504
+ }
505
+ else if (isDelim(parts[index + 1], 0x7c) &&
506
+ !isDelim(parts[index + 2], 0x3d)) {
507
+ namespace = firstName;
508
+ index += 2;
509
+ name = this.#nameOrStar(parts[index]);
510
+ index += 1;
511
+ }
512
+ else {
513
+ index += 1;
514
+ }
515
+ if (name === null || name === "*") {
516
+ this.#fail("invalid-attribute", "Expected an attribute name.", block.span);
517
+ return null;
518
+ }
519
+ let matcher = null;
520
+ let expected = null;
521
+ let modifier = null;
522
+ if (index < parts.length) {
523
+ const first = parts[index];
524
+ const second = parts[index + 1];
525
+ if (isDelim(first, 0x3d)) {
526
+ matcher = "=";
527
+ index += 1;
528
+ }
529
+ else if (first?.kind === "delim" &&
530
+ [0x7e, 0x7c, 0x5e, 0x24, 0x2a].includes(first.value) &&
531
+ isDelim(second, 0x3d)) {
532
+ matcher = `${String.fromCodePoint(first.value)}=`;
533
+ index += 2;
534
+ }
535
+ else {
536
+ this.#fail("invalid-attribute", "Expected an attribute matcher.", block.span);
537
+ return null;
538
+ }
539
+ const expectedToken = parts[index];
540
+ if (expectedToken?.kind !== "ident" &&
541
+ expectedToken?.kind !== "string") {
542
+ this.#fail("invalid-attribute", "Expected an identifier or string attribute value.", block.span);
543
+ return null;
544
+ }
545
+ expected = expectedToken.value;
546
+ index += 1;
547
+ const modifierToken = parts[index];
548
+ if (modifierToken?.kind === "ident") {
549
+ const candidate = lowerAscii(modifierToken.value);
550
+ if (candidate === "i" || candidate === "s") {
551
+ modifier = candidate;
552
+ index += 1;
553
+ }
554
+ }
555
+ }
556
+ if (index !== parts.length) {
557
+ this.#fail("invalid-attribute", "Unexpected content in attribute selector.", block.span);
558
+ return null;
559
+ }
560
+ this.guard.createNode(3);
561
+ return Object.freeze({
562
+ kind: "attribute",
563
+ namespace,
564
+ name,
565
+ matcher,
566
+ value: expected,
567
+ modifier,
568
+ span: block.span
569
+ });
570
+ }
571
+ #pseudo() {
572
+ const firstColon = this.#values[this.#index];
573
+ let kind = "class";
574
+ this.#index += 1;
575
+ if (this.#values[this.#index]?.kind === "colon") {
576
+ kind = "element";
577
+ this.#index += 1;
578
+ }
579
+ const nameValue = this.#values[this.#index];
580
+ if (nameValue?.kind !== "ident" && nameValue?.kind !== "function-block") {
581
+ this.#fail("invalid-pseudo", "Expected a pseudo selector name.", firstColon?.span ?? emptySpan());
582
+ return null;
583
+ }
584
+ this.#index += 1;
585
+ const name = lowerAscii(nameValue.kind === "ident" ? nameValue.value : nameValue.name);
586
+ if (kind === "class" && LEGACY_PSEUDO_ELEMENTS.has(name))
587
+ kind = "element";
588
+ const knownFunctional = kind === "class"
589
+ ? FUNCTIONAL_PSEUDO_CLASSES
590
+ : FUNCTIONAL_PSEUDO_ELEMENTS;
591
+ const knownNonFunctional = kind === "class"
592
+ ? NON_FUNCTIONAL_PSEUDO_CLASSES
593
+ : NON_FUNCTIONAL_PSEUDO_ELEMENTS;
594
+ if ((nameValue.kind === "ident" && !knownNonFunctional.has(name)) ||
595
+ (nameValue.kind === "function-block" && !knownFunctional.has(name))) {
596
+ this.#fail("invalid-pseudo", `Unknown pseudo-${kind}: ${name}.`, nameValue.span);
597
+ return null;
598
+ }
599
+ if (nameValue.kind === "ident" &&
600
+ knownFunctional.has(name)) {
601
+ this.#fail("invalid-pseudo", `:${kind === "element" ? ":" : ""}${name} requires arguments.`, nameValue.span);
602
+ return null;
603
+ }
604
+ const argument = nameValue.kind === "function-block"
605
+ ? this.pseudoArgument(nameValue, kind)
606
+ : Object.freeze({ kind: "none" });
607
+ if (argument === null) {
608
+ this.#failed = true;
609
+ return null;
610
+ }
611
+ const span = joinedSpan(firstColon?.span ?? nameValue.span, nameValue.span);
612
+ this.guard.createNode(3);
613
+ return kind === "class"
614
+ ? Object.freeze({
615
+ kind: "pseudo-class",
616
+ name,
617
+ argument,
618
+ span
619
+ })
620
+ : Object.freeze({
621
+ kind: "pseudo-element",
622
+ name,
623
+ argument,
624
+ span
625
+ });
626
+ }
627
+ #explicitCombinator() {
628
+ const value = this.#values[this.#index];
629
+ if (isDelim(value, 0x3e)) {
630
+ this.#index += 1;
631
+ return ">";
632
+ }
633
+ if (isDelim(value, 0x2b)) {
634
+ this.#index += 1;
635
+ return "+";
636
+ }
637
+ if (isDelim(value, 0x7e)) {
638
+ this.#index += 1;
639
+ return "~";
640
+ }
641
+ return null;
642
+ }
643
+ #skipWhitespace() {
644
+ const start = this.#index;
645
+ while (this.#values[this.#index]?.kind === "whitespace") {
646
+ this.guard.step();
647
+ this.#index += 1;
648
+ }
649
+ return this.#index !== start;
650
+ }
651
+ #nameOrStar(value) {
652
+ if (value?.kind === "ident")
653
+ return value.value;
654
+ if (isDelim(value, 0x2a))
655
+ return "*";
656
+ return null;
657
+ }
658
+ #currentSpan() {
659
+ return this.#values[this.#index]?.span ??
660
+ this.#values.at(-1)?.span ??
661
+ emptySpan();
662
+ }
663
+ #fail(code, message, span) {
664
+ this.#failed = true;
665
+ this.diagnostic(code, message, span);
666
+ }
667
+ }
668
+ function parseAnPlusB(source) {
669
+ const normalized = lowerAscii(source);
670
+ if (normalized === "odd")
671
+ return Object.freeze({ a: 2, b: 1 });
672
+ if (normalized === "even")
673
+ return Object.freeze({ a: 2, b: 0 });
674
+ if (/^[+-]?\d+$/u.test(normalized)) {
675
+ return Object.freeze({ a: 0, b: Number(normalized) });
676
+ }
677
+ const match = /^([+-]?\d*)n(?:([+-]\d+))?$/u.exec(normalized);
678
+ if (match === null)
679
+ return null;
680
+ const coefficient = match[1];
681
+ const a = coefficient === "" || coefficient === "+"
682
+ ? 1
683
+ : coefficient === "-"
684
+ ? -1
685
+ : Number(coefficient);
686
+ return Object.freeze({ a, b: Number(match[2] ?? 0) });
687
+ }
688
+ export function parseSelectorList(source, options = {}) {
689
+ const syntax = parseCssComponentValues(source, {
690
+ ...(options.limits === undefined ? {} : { limits: options.limits }),
691
+ ...(options.signal === undefined ? {} : { signal: options.signal })
692
+ });
693
+ if (!syntax.ok) {
694
+ return Object.freeze({
695
+ ok: false,
696
+ errors: syntax.errors,
697
+ usage: syntax.usage
698
+ });
699
+ }
700
+ const parser = new SelectorParser(syntax.value, options, syntax.usage);
701
+ return parser.parse(syntax.errors);
702
+ }
@@ -0,0 +1,3 @@
1
+ import type { ComplexSelector, SelectorList, SelectorSpecificity, SelectorSpecificityOptions } from "./types.js";
2
+ export declare function specificityOfComplexSelector(selector: ComplexSelector, options?: SelectorSpecificityOptions): SelectorSpecificity;
3
+ export declare function specificitiesOfSelectorList(list: SelectorList, options?: SelectorSpecificityOptions): readonly SelectorSpecificity[];