@ismail-elkorchi/css-parser 0.1.0 → 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 +67 -69
  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 -35
  47. package/dist/public/mod.js +12 -1740
  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 -53
  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,616 @@
1
+ import { ResourceGuard } from "../syntax/resources.js";
2
+ export class SelectorTreeError extends TypeError {
3
+ reason;
4
+ code = "CSS_SELECTOR_INVALID_TREE";
5
+ constructor(reason) {
6
+ super(`Selector trees cannot contain a ${reason === "cycle" ? "cycle" : "shared node"}.`);
7
+ this.reason = reason;
8
+ this.name = "SelectorTreeError";
9
+ }
10
+ }
11
+ function known(decision) {
12
+ return Object.freeze({ decision, reasons: Object.freeze([]) });
13
+ }
14
+ function unknown(reason) {
15
+ return Object.freeze({
16
+ decision: "unknown",
17
+ reasons: Object.freeze([reason])
18
+ });
19
+ }
20
+ function uniqueReasons(values) {
21
+ const keys = new Set();
22
+ const result = [];
23
+ for (const value of values) {
24
+ const key = `${value.code}:${value.name}:${String(value.span.start.offset)}`;
25
+ if (keys.has(key))
26
+ continue;
27
+ keys.add(key);
28
+ result.push(value);
29
+ }
30
+ return Object.freeze(result);
31
+ }
32
+ function and(left, right) {
33
+ if (left.decision === "no-match" || right.decision === "no-match") {
34
+ return known("no-match");
35
+ }
36
+ if (left.decision === "match" && right.decision === "match") {
37
+ return known("match");
38
+ }
39
+ return Object.freeze({
40
+ decision: "unknown",
41
+ reasons: uniqueReasons([...left.reasons, ...right.reasons])
42
+ });
43
+ }
44
+ function or(values) {
45
+ const reasons = [];
46
+ for (const value of values) {
47
+ if (value.decision === "match")
48
+ return known("match");
49
+ if (value.decision === "unknown")
50
+ reasons.push(...value.reasons);
51
+ }
52
+ return reasons.length === 0
53
+ ? known("no-match")
54
+ : Object.freeze({
55
+ decision: "unknown",
56
+ reasons: uniqueReasons(reasons)
57
+ });
58
+ }
59
+ function invert(value) {
60
+ if (value.decision === "unknown")
61
+ return value;
62
+ return known(value.decision === "match" ? "no-match" : "match");
63
+ }
64
+ function lowerAscii(value) {
65
+ return value.replace(/[A-Z]/gu, (character) => character.toLowerCase());
66
+ }
67
+ function equalAsciiInsensitive(left, right) {
68
+ return lowerAscii(left) === lowerAscii(right);
69
+ }
70
+ function isAsciiWhitespace(character) {
71
+ return (character === "\t" ||
72
+ character === "\n" ||
73
+ character === "\f" ||
74
+ character === "\r" ||
75
+ character === " ");
76
+ }
77
+ function whitespaceTokens(value) {
78
+ const result = [];
79
+ let token = "";
80
+ for (const character of value) {
81
+ if (isAsciiWhitespace(character)) {
82
+ if (token.length > 0)
83
+ result.push(token);
84
+ token = "";
85
+ }
86
+ else {
87
+ token += character;
88
+ }
89
+ }
90
+ if (token.length > 0)
91
+ result.push(token);
92
+ return Object.freeze(result);
93
+ }
94
+ function buildTreeIndex(root, environment, guard) {
95
+ const parent = new Map();
96
+ const children = new Map();
97
+ const elements = [];
98
+ const active = new Set();
99
+ const stack = [{ node: root, parent: null, depth: 0, leaving: false }];
100
+ while (stack.length > 0) {
101
+ guard.step();
102
+ const frame = stack.pop();
103
+ if (frame === undefined)
104
+ continue;
105
+ if (frame.leaving) {
106
+ active.delete(frame.node);
107
+ continue;
108
+ }
109
+ if (active.has(frame.node))
110
+ throw new SelectorTreeError("cycle");
111
+ if (parent.has(frame.node))
112
+ throw new SelectorTreeError("shared-node");
113
+ active.add(frame.node);
114
+ parent.set(frame.node, frame.parent);
115
+ guard.createNode(frame.depth);
116
+ if (environment.tree.data(frame.node).kind === "element") {
117
+ elements.push(frame.node);
118
+ }
119
+ const nodeChildren = Object.freeze([
120
+ ...environment.tree.children(frame.node)
121
+ ]);
122
+ children.set(frame.node, nodeChildren);
123
+ stack.push({ ...frame, leaving: true });
124
+ for (let index = nodeChildren.length - 1; index >= 0; index -= 1) {
125
+ const child = nodeChildren[index];
126
+ if (child !== undefined) {
127
+ stack.push({
128
+ node: child,
129
+ parent: frame.node,
130
+ depth: frame.depth + 1,
131
+ leaving: false
132
+ });
133
+ }
134
+ }
135
+ }
136
+ return Object.freeze({
137
+ root,
138
+ parent,
139
+ children,
140
+ elements: Object.freeze(elements)
141
+ });
142
+ }
143
+ class SelectorMatcher {
144
+ environment;
145
+ options;
146
+ #guard;
147
+ #index;
148
+ #scopes;
149
+ constructor(root, environment, options) {
150
+ this.environment = environment;
151
+ this.options = options;
152
+ this.#guard = new ResourceGuard(options.limits, options.signal);
153
+ this.#index = buildTreeIndex(root, environment, this.#guard);
154
+ this.#scopes = options.scopes === undefined
155
+ ? new Set([root])
156
+ : new Set([...options.scopes].filter((node) => this.#index.parent.has(node)));
157
+ }
158
+ usage() {
159
+ return this.#guard.snapshot();
160
+ }
161
+ elements() {
162
+ return this.#index.elements;
163
+ }
164
+ matches(list, node) {
165
+ if (!this.#index.parent.has(node))
166
+ return known("no-match");
167
+ return or(list.selectors.map((selector) => this.#complex(selector, node, null)));
168
+ }
169
+ #complex(selector, node, anchor) {
170
+ return this.#complexAt(selector, selector.compounds.length - 1, node, anchor);
171
+ }
172
+ #complexAt(selector, index, node, anchor) {
173
+ this.#guard.step();
174
+ const compound = selector.compounds[index];
175
+ if (compound === undefined)
176
+ return known("no-match");
177
+ const own = this.#compound(compound, node);
178
+ if (own.decision === "no-match")
179
+ return own;
180
+ if (index === 0) {
181
+ const relation = anchor === null
182
+ ? known("match")
183
+ : this.#anchorRelation(selector.leadingCombinator ?? " ", anchor, node);
184
+ return and(own, relation);
185
+ }
186
+ const combinator = selector.combinators[index - 1];
187
+ const candidates = this.#leftCandidates(node, combinator);
188
+ const related = or(candidates.map((candidate) => this.#complexAt(selector, index - 1, candidate, anchor)));
189
+ return and(own, related);
190
+ }
191
+ #compound(compound, node) {
192
+ const data = this.environment.tree.data(node);
193
+ if (data.kind !== "element")
194
+ return known("no-match");
195
+ let result = compound.type === null
196
+ ? known("match")
197
+ : this.#type(compound.type, data);
198
+ for (const simple of compound.simples) {
199
+ result = and(result, this.#simple(simple, node, data));
200
+ if (result.decision === "no-match")
201
+ return result;
202
+ }
203
+ return result;
204
+ }
205
+ #type(selector, element) {
206
+ const namespace = this.#namespace(selector.namespace, true, element, selector.name, selector.span);
207
+ if (namespace.decision !== "match")
208
+ return namespace;
209
+ if (selector.name === "*")
210
+ return known("match");
211
+ const equal = this.environment.documentMode.syntax === "html" &&
212
+ element.namespace === "http://www.w3.org/1999/xhtml"
213
+ ? equalAsciiInsensitive(element.localName, selector.name)
214
+ : element.localName === selector.name;
215
+ return known(equal ? "match" : "no-match");
216
+ }
217
+ #simple(simple, node, element) {
218
+ this.#guard.step();
219
+ switch (simple.kind) {
220
+ case "id":
221
+ return known(this.environment.idValues(node, element).some((value) => this.#identityEqual(value, simple.value))
222
+ ? "match"
223
+ : "no-match");
224
+ case "class": {
225
+ return known(this.environment.classNames(node, element).some((value) => this.#identityEqual(value, simple.value))
226
+ ? "match"
227
+ : "no-match");
228
+ }
229
+ case "attribute":
230
+ return this.#attributeSelector(simple, element);
231
+ case "pseudo-class":
232
+ return this.#pseudo(simple, node, element);
233
+ case "pseudo-element":
234
+ return known("no-match");
235
+ case "nesting":
236
+ return this.options.nesting === undefined
237
+ ? known(this.#scopes.has(node) ? "match" : "no-match")
238
+ : this.matches(this.options.nesting, node);
239
+ }
240
+ }
241
+ #identityEqual(left, right) {
242
+ return this.environment.documentMode.syntax === "html" &&
243
+ this.environment.documentMode.quirks === "quirks"
244
+ ? equalAsciiInsensitive(left, right)
245
+ : left === right;
246
+ }
247
+ #attributeSelector(selector, element) {
248
+ const namespace = this.#attributeNamespace(selector);
249
+ if (namespace.status === "unknown") {
250
+ return unknown({
251
+ code: "namespace-prefix",
252
+ name: selector.namespace ?? "",
253
+ span: selector.span
254
+ });
255
+ }
256
+ const attribute = this.#attribute(element, namespace.namespace, selector.name);
257
+ if (attribute === null)
258
+ return known("no-match");
259
+ if (selector.matcher === null)
260
+ return known("match");
261
+ const expected = selector.value;
262
+ if (expected === null)
263
+ return known("no-match");
264
+ const sensitivity = selector.modifier === "i"
265
+ ? "ascii-insensitive"
266
+ : selector.modifier === "s"
267
+ ? "sensitive"
268
+ : this.environment.attributeValueCaseSensitivity(element, attribute);
269
+ const left = sensitivity === "ascii-insensitive"
270
+ ? lowerAscii(attribute.value)
271
+ : attribute.value;
272
+ const right = sensitivity === "ascii-insensitive"
273
+ ? lowerAscii(expected)
274
+ : expected;
275
+ if (right.length === 0 &&
276
+ (selector.matcher === "^=" ||
277
+ selector.matcher === "$=" ||
278
+ selector.matcher === "*=")) {
279
+ return known("no-match");
280
+ }
281
+ switch (selector.matcher) {
282
+ case "=":
283
+ return known(left === right ? "match" : "no-match");
284
+ case "~=":
285
+ return known(whitespaceTokens(left).includes(right) ? "match" : "no-match");
286
+ case "|=":
287
+ return known(left === right || left.startsWith(`${right}-`)
288
+ ? "match"
289
+ : "no-match");
290
+ case "^=":
291
+ return known(left.startsWith(right) ? "match" : "no-match");
292
+ case "$=":
293
+ return known(left.endsWith(right) ? "match" : "no-match");
294
+ case "*=":
295
+ return known(left.includes(right) ? "match" : "no-match");
296
+ }
297
+ }
298
+ #pseudo(pseudo, node, element) {
299
+ const name = pseudo.name;
300
+ if ((name === "is" || name === "where") &&
301
+ pseudo.argument.kind === "selector-list") {
302
+ return or(pseudo.argument.selectors.map((selector) => this.#complex(selector, node, null)));
303
+ }
304
+ if (name === "not" &&
305
+ pseudo.argument.kind === "selector-list") {
306
+ return invert(or(pseudo.argument.selectors.map((selector) => this.#complex(selector, node, null))));
307
+ }
308
+ if (name === "has" &&
309
+ pseudo.argument.kind === "selector-list") {
310
+ const results = [];
311
+ for (const selector of pseudo.argument.selectors) {
312
+ for (const candidate of this.#index.elements) {
313
+ results.push(this.#complex(selector, candidate, node));
314
+ }
315
+ }
316
+ return or(results);
317
+ }
318
+ if (name === "scope") {
319
+ if (pseudo.argument.kind !== "none")
320
+ return known("no-match");
321
+ return known(this.#scopes.has(node) ? "match" : "no-match");
322
+ }
323
+ if (name === "root") {
324
+ if (pseudo.argument.kind !== "none")
325
+ return known("no-match");
326
+ const parent = this.#index.parent.get(node) ?? null;
327
+ return known(parent === null ||
328
+ this.environment.tree.data(parent).kind !== "element"
329
+ ? "match"
330
+ : "no-match");
331
+ }
332
+ if (name === "empty") {
333
+ return pseudo.argument.kind === "none"
334
+ ? this.#empty(node)
335
+ : known("no-match");
336
+ }
337
+ const indexed = this.#indexedPseudo(name, pseudo, node, element);
338
+ if (indexed !== null)
339
+ return indexed;
340
+ const decision = this.environment.matchPseudoClass(node, pseudo, Object.freeze({
341
+ root: this.#index.root,
342
+ scopes: this.#scopes
343
+ }));
344
+ return decision === "unknown"
345
+ ? unknown({
346
+ code: "pseudo-class",
347
+ name,
348
+ span: pseudo.span
349
+ })
350
+ : known(decision);
351
+ }
352
+ #empty(node) {
353
+ for (const child of this.#index.children.get(node) ?? []) {
354
+ const data = this.environment.tree.data(child);
355
+ if (data.kind === "element")
356
+ return known("no-match");
357
+ if (data.kind === "text" && data.value.length > 0) {
358
+ return known("no-match");
359
+ }
360
+ }
361
+ return known("match");
362
+ }
363
+ #indexedPseudo(name, pseudo, node, element) {
364
+ const aliases = {
365
+ "first-child": { a: 0, b: 1, fromEnd: false, sameType: false },
366
+ "last-child": { a: 0, b: 1, fromEnd: true, sameType: false },
367
+ "only-child": { a: 0, b: 1, fromEnd: false, sameType: false },
368
+ "first-of-type": { a: 0, b: 1, fromEnd: false, sameType: true },
369
+ "last-of-type": { a: 0, b: 1, fromEnd: true, sameType: true },
370
+ "only-of-type": { a: 0, b: 1, fromEnd: false, sameType: true }
371
+ };
372
+ const alias = aliases[name];
373
+ if (alias !== undefined) {
374
+ if (pseudo.argument.kind !== "none")
375
+ return known("no-match");
376
+ const first = this.#indexPosition(node, element, alias.sameType, alias.fromEnd, Object.freeze([]));
377
+ if (name === "only-child" ||
378
+ name === "only-of-type") {
379
+ const last = this.#indexPosition(node, element, alias.sameType, true, Object.freeze([]));
380
+ const combined = and(first.result, last.result);
381
+ return combined.decision === "match" &&
382
+ first.index === 1 &&
383
+ last.index === 1
384
+ ? known("match")
385
+ : combined.decision === "unknown"
386
+ ? combined
387
+ : known("no-match");
388
+ }
389
+ return first.result.decision === "match" && first.index === 1
390
+ ? known("match")
391
+ : first.result.decision === "unknown"
392
+ ? first.result
393
+ : known("no-match");
394
+ }
395
+ if (pseudo.argument.kind !== "nth")
396
+ return null;
397
+ const modes = {
398
+ "nth-child": { fromEnd: false, sameType: false },
399
+ "nth-last-child": { fromEnd: true, sameType: false },
400
+ "nth-of-type": { fromEnd: false, sameType: true },
401
+ "nth-last-of-type": { fromEnd: true, sameType: true }
402
+ };
403
+ const mode = modes[name];
404
+ if (mode === undefined)
405
+ return null;
406
+ const position = this.#indexPosition(node, element, mode.sameType, mode.fromEnd, pseudo.argument.of);
407
+ if (position.result.decision !== "match")
408
+ return position.result;
409
+ return known(matchesAnPlusB(position.index, pseudo.argument.a, pseudo.argument.b)
410
+ ? "match"
411
+ : "no-match");
412
+ }
413
+ #indexPosition(node, element, sameType, fromEnd, filter) {
414
+ const parent = this.#index.parent.get(node) ?? null;
415
+ if (parent === null)
416
+ return { index: 0, result: known("no-match") };
417
+ const siblings = (this.#index.children.get(parent) ?? []).filter((sibling) => {
418
+ const data = this.environment.tree.data(sibling);
419
+ return data.kind === "element" &&
420
+ (!sameType ||
421
+ (data.namespace === element.namespace &&
422
+ this.#sameElementType(data, element)));
423
+ });
424
+ const ordered = fromEnd ? [...siblings].reverse() : siblings;
425
+ let index = 0;
426
+ const reasons = [];
427
+ for (const sibling of ordered) {
428
+ let included;
429
+ if (filter.length === 0) {
430
+ included = known("match");
431
+ }
432
+ else {
433
+ included = or(filter.map((selector) => this.#complex(selector, sibling, null)));
434
+ }
435
+ if (included.decision === "unknown")
436
+ reasons.push(...included.reasons);
437
+ if (included.decision === "match")
438
+ index += 1;
439
+ if (sibling === node) {
440
+ if (included.decision === "no-match") {
441
+ return { index: 0, result: known("no-match") };
442
+ }
443
+ return reasons.length > 0
444
+ ? {
445
+ index,
446
+ result: Object.freeze({
447
+ decision: "unknown",
448
+ reasons: uniqueReasons(reasons)
449
+ })
450
+ }
451
+ : { index, result: known("match") };
452
+ }
453
+ }
454
+ return { index: 0, result: known("no-match") };
455
+ }
456
+ #attribute(element, namespace, name) {
457
+ for (const attribute of element.attributes) {
458
+ const nameEqual = this.environment.documentMode.syntax === "html" &&
459
+ element.namespace === "http://www.w3.org/1999/xhtml" &&
460
+ attribute.namespace === null
461
+ ? equalAsciiInsensitive(attribute.localName, name)
462
+ : attribute.localName === name;
463
+ if ((namespace === "*" || attribute.namespace === namespace) &&
464
+ nameEqual) {
465
+ return attribute;
466
+ }
467
+ }
468
+ return null;
469
+ }
470
+ #sameElementType(left, right) {
471
+ return this.environment.documentMode.syntax === "html" &&
472
+ left.namespace === "http://www.w3.org/1999/xhtml"
473
+ ? equalAsciiInsensitive(left.localName, right.localName)
474
+ : left.localName === right.localName;
475
+ }
476
+ #attributeNamespace(selector) {
477
+ if (selector.namespace === null || selector.namespace === "") {
478
+ return Object.freeze({ status: "resolved", namespace: null });
479
+ }
480
+ if (selector.namespace === "*") {
481
+ return Object.freeze({ status: "resolved", namespace: "*" });
482
+ }
483
+ return this.environment.resolveNamespacePrefix(selector.namespace);
484
+ }
485
+ #namespace(selectorNamespace, useDefault, element, name, span) {
486
+ if (selectorNamespace === "*")
487
+ return known("match");
488
+ let resolution;
489
+ if (selectorNamespace === null) {
490
+ if (!useDefault) {
491
+ resolution = Object.freeze({ status: "resolved", namespace: null });
492
+ }
493
+ else if (this.environment.defaultNamespace.kind === "any") {
494
+ return known("match");
495
+ }
496
+ else {
497
+ resolution = Object.freeze({
498
+ status: "resolved",
499
+ namespace: this.environment.defaultNamespace.namespace
500
+ });
501
+ }
502
+ }
503
+ else if (selectorNamespace === "") {
504
+ resolution = Object.freeze({ status: "resolved", namespace: null });
505
+ }
506
+ else {
507
+ resolution = this.environment.resolveNamespacePrefix(selectorNamespace);
508
+ }
509
+ if (resolution.status === "unknown") {
510
+ return unknown({
511
+ code: "namespace-prefix",
512
+ name: selectorNamespace ?? name,
513
+ span
514
+ });
515
+ }
516
+ return known(resolution.namespace === element.namespace ? "match" : "no-match");
517
+ }
518
+ #leftCandidates(node, combinator) {
519
+ const parent = this.#index.parent.get(node) ?? null;
520
+ if (combinator === ">") {
521
+ return parent !== null &&
522
+ this.environment.tree.data(parent).kind === "element"
523
+ ? Object.freeze([parent])
524
+ : Object.freeze([]);
525
+ }
526
+ if (combinator === "+" || combinator === "~") {
527
+ if (parent === null)
528
+ return Object.freeze([]);
529
+ const siblings = this.#index.children.get(parent) ?? [];
530
+ const nodeIndex = siblings.indexOf(node);
531
+ const previous = siblings
532
+ .slice(0, nodeIndex)
533
+ .filter((sibling) => this.environment.tree.data(sibling).kind === "element")
534
+ .reverse();
535
+ return Object.freeze(combinator === "+" ? previous.slice(0, 1) : previous);
536
+ }
537
+ const ancestors = [];
538
+ let candidate = parent;
539
+ while (candidate !== null) {
540
+ if (this.environment.tree.data(candidate).kind === "element") {
541
+ ancestors.push(candidate);
542
+ }
543
+ candidate = this.#index.parent.get(candidate) ?? null;
544
+ }
545
+ return Object.freeze(ancestors);
546
+ }
547
+ #anchorRelation(combinator, anchor, node) {
548
+ if (combinator === ">") {
549
+ return known(this.#index.parent.get(node) === anchor ? "match" : "no-match");
550
+ }
551
+ const parent = this.#index.parent.get(anchor) ?? null;
552
+ if (combinator === "+" || combinator === "~") {
553
+ if (parent === null || this.#index.parent.get(node) !== parent) {
554
+ return known("no-match");
555
+ }
556
+ const siblings = (this.#index.children.get(parent) ?? []).filter((sibling) => this.environment.tree.data(sibling).kind === "element");
557
+ const anchorIndex = siblings.indexOf(anchor);
558
+ const nodeIndex = siblings.indexOf(node);
559
+ return known(combinator === "+"
560
+ ? nodeIndex === anchorIndex + 1
561
+ ? "match"
562
+ : "no-match"
563
+ : nodeIndex > anchorIndex
564
+ ? "match"
565
+ : "no-match");
566
+ }
567
+ let candidate = this.#index.parent.get(node) ?? null;
568
+ while (candidate !== null) {
569
+ if (candidate === anchor)
570
+ return known("match");
571
+ candidate = this.#index.parent.get(candidate) ?? null;
572
+ }
573
+ return known("no-match");
574
+ }
575
+ }
576
+ function matchesAnPlusB(index, a, b) {
577
+ if (a === 0)
578
+ return index === b;
579
+ const quotient = (index - b) / a;
580
+ return Number.isInteger(quotient) && quotient >= 0;
581
+ }
582
+ function publicResult(result, usage) {
583
+ if (result.decision === "unknown") {
584
+ return Object.freeze({
585
+ status: "unknown",
586
+ reasons: result.reasons,
587
+ usage
588
+ });
589
+ }
590
+ return Object.freeze({ status: result.decision, usage });
591
+ }
592
+ export function matchSelectorList(selector, node, root, environment, options = {}) {
593
+ const matcher = new SelectorMatcher(root, environment, options);
594
+ return publicResult(matcher.matches(selector, node), matcher.usage());
595
+ }
596
+ export function querySelectorList(selector, root, environment, options = {}) {
597
+ const matcher = new SelectorMatcher(root, environment, options);
598
+ const matches = [];
599
+ const unknownResults = [];
600
+ for (const node of matcher.elements()) {
601
+ const result = matcher.matches(selector, node);
602
+ if (result.decision === "match")
603
+ matches.push(node);
604
+ else if (result.decision === "unknown") {
605
+ unknownResults.push(Object.freeze({
606
+ node,
607
+ reasons: result.reasons
608
+ }));
609
+ }
610
+ }
611
+ return Object.freeze({
612
+ matches: Object.freeze(matches),
613
+ unknown: Object.freeze(unknownResults),
614
+ usage: matcher.usage()
615
+ });
616
+ }
@@ -0,0 +1,2 @@
1
+ import type { SelectorParseResult, SelectorParserOptions } from "./types.js";
2
+ export declare function parseSelectorList(source: string, options?: SelectorParserOptions): SelectorParseResult;