@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,791 @@
1
+ import { CSS_WEBREF_DATA } from "../generated/css-data.js";
2
+ import { parseCssValueDefinition } from "../grammar/value-definition.js";
3
+ import { resolveCssProperty } from "./registry.js";
4
+ import { ResourceGuard } from "../syntax/resources.js";
5
+ const CSS_WIDE_KEYWORDS = new Set([
6
+ "initial",
7
+ "inherit",
8
+ "unset",
9
+ "revert",
10
+ "revert-layer",
11
+ "revert-rule"
12
+ ]);
13
+ const LENGTH_UNITS = new Set([
14
+ "cap", "ch", "cm", "dvb", "dvh", "dvi", "dvmax", "dvmin", "dvw",
15
+ "em", "ex", "ic", "in", "lh", "lvb", "lvh", "lvi", "lvmax", "lvmin",
16
+ "lvw", "mm", "pc", "pt", "px", "q", "rcap", "rch", "rem", "rex", "ric",
17
+ "rlh", "svb", "svh", "svi", "svmax", "svmin", "svw", "vb", "vh", "vi",
18
+ "vmax", "vmin", "vw"
19
+ ]);
20
+ const ANGLE_UNITS = new Set(["deg", "grad", "rad", "turn"]);
21
+ const TIME_UNITS = new Set(["ms", "s"]);
22
+ const FREQUENCY_UNITS = new Set(["hz", "khz"]);
23
+ const RESOLUTION_UNITS = new Set(["dpcm", "dpi", "dppx", "x"]);
24
+ const FLEX_UNITS = new Set(["fr"]);
25
+ const MATH_FUNCTIONS = new Set([
26
+ "abs", "acos", "asin", "atan", "atan2", "calc", "clamp", "cos", "exp",
27
+ "hypot", "log", "max", "min", "mod", "pow", "progress", "random",
28
+ "random-item-mix", "rem", "round", "sign", "sin", "sqrt", "tan"
29
+ ]);
30
+ const BORDER_STYLES = new Set([
31
+ "none", "hidden", "dotted", "dashed", "solid", "double", "groove",
32
+ "ridge", "inset", "outset"
33
+ ]);
34
+ const BORDER_WIDTH_KEYWORDS = new Set(["hairline", "thin", "medium", "thick"]);
35
+ const CSS_GENERIC_KEYWORD_EXCLUSIONS = new Set([...CSS_WIDE_KEYWORDS, "default"]);
36
+ const DEFAULT_MAX_STEPS = 250_000;
37
+ const PAINT_SYNTAX = "none | <color> | <url> [ none | <color> ]? | context-fill | context-stroke | <image> | <svg-paint>";
38
+ const TYPE_DATA = groupGrammarData(CSS_WEBREF_DATA.types);
39
+ const FUNCTION_DATA = groupGrammarData(CSS_WEBREF_DATA.functions);
40
+ const GRAMMAR_CACHE = new Map();
41
+ function groupGrammarData(entries) {
42
+ const grouped = new Map();
43
+ for (const entry of entries) {
44
+ const values = grouped.get(entry.name);
45
+ if (values === undefined)
46
+ grouped.set(entry.name, [entry]);
47
+ else
48
+ values.push(entry);
49
+ }
50
+ return grouped;
51
+ }
52
+ function grammar(source) {
53
+ const cached = GRAMMAR_CACHE.get(source);
54
+ if (cached !== undefined)
55
+ return cached;
56
+ const parsed = parseCssValueDefinition(source);
57
+ GRAMMAR_CACHE.set(source, parsed);
58
+ return parsed;
59
+ }
60
+ function lowerAscii(value) {
61
+ return value.replace(/[A-Z]/gu, (character) => character.toLowerCase());
62
+ }
63
+ function significant(values) {
64
+ return Object.freeze(values.filter((value) => value.kind !== "whitespace"));
65
+ }
66
+ function result(ends = [], unsupported = []) {
67
+ return {
68
+ ends: new Set(ends),
69
+ unsupported: new Set(unsupported)
70
+ };
71
+ }
72
+ function mergeResults(results) {
73
+ const ends = new Set();
74
+ const unsupported = new Set();
75
+ for (const item of results) {
76
+ for (const end of item.ends)
77
+ ends.add(end);
78
+ for (const name of item.unsupported)
79
+ unsupported.add(name);
80
+ }
81
+ return { ends, unsupported };
82
+ }
83
+ function singleEnd(end) {
84
+ return result([end]);
85
+ }
86
+ function emptyResult() {
87
+ return result();
88
+ }
89
+ class PropertyGrammarMatcher {
90
+ #guard;
91
+ #arrayIds = new WeakMap();
92
+ #activeReferences = new Set();
93
+ #nextArrayId = 1;
94
+ constructor(options) {
95
+ this.#guard = new ResourceGuard({ maxSteps: options.maxSteps ?? DEFAULT_MAX_STEPS }, options.signal);
96
+ }
97
+ match(definition, values) {
98
+ return this.#match(definition, values, 0);
99
+ }
100
+ usage() {
101
+ return this.#guard.snapshot();
102
+ }
103
+ step() {
104
+ this.#guard.step();
105
+ }
106
+ #match(definition, values, start) {
107
+ this.#guard.step();
108
+ switch (definition.kind) {
109
+ case "keyword":
110
+ return this.#matchKeyword(definition.value, values, start);
111
+ case "literal":
112
+ return this.#matchLiteral(definition.value, values, start);
113
+ case "reference":
114
+ return this.#matchReference(definition, values, start);
115
+ case "function":
116
+ return this.#matchFunction(definition.name, definition.value, values, start);
117
+ case "sequence":
118
+ return this.#matchSequence(definition.values, values, start);
119
+ case "one-of":
120
+ return mergeResults(definition.values.map((value) => this.#match(value, values, start)));
121
+ case "all-of":
122
+ return this.#matchShuffle(definition.values, values, start, true);
123
+ case "any-of":
124
+ return this.#matchShuffle(definition.values, values, start, false);
125
+ case "multiplier":
126
+ return this.#matchMultiplier(definition, values, start);
127
+ case "required":
128
+ return this.#match(definition.value, values, start);
129
+ }
130
+ }
131
+ #matchSequence(definitions, values, start) {
132
+ const ends = new Set();
133
+ const unsupported = new Set();
134
+ this.#matchSequenceFrom(definitions, values, 0, start, false, false, ends, unsupported, new Set());
135
+ return result(ends, unsupported);
136
+ }
137
+ #matchSequenceFrom(definitions, values, definitionIndex, position, hasContent, contentSinceComma, ends, unsupported, visited) {
138
+ this.#guard.step();
139
+ const state = [
140
+ definitionIndex,
141
+ position,
142
+ hasContent ? 1 : 0,
143
+ contentSinceComma ? 1 : 0
144
+ ].join(":");
145
+ if (visited.has(state))
146
+ return;
147
+ visited.add(state);
148
+ if (definitionIndex === definitions.length) {
149
+ ends.add(position);
150
+ return;
151
+ }
152
+ const definition = definitions[definitionIndex];
153
+ if (definition === undefined)
154
+ return;
155
+ const comma = definition.kind === "literal" && definition.value === ",";
156
+ const matched = this.#match(definition, values, position);
157
+ for (const name of matched.unsupported)
158
+ unsupported.add(name);
159
+ for (const end of matched.ends) {
160
+ const consumed = end > position;
161
+ this.#matchSequenceFrom(definitions, values, definitionIndex + 1, end, hasContent || (consumed && !comma), comma && consumed ? false : contentSinceComma || consumed, ends, unsupported, visited);
162
+ }
163
+ if (comma &&
164
+ (!hasContent ||
165
+ !contentSinceComma ||
166
+ this.#sequenceSuffixCanBeEmpty(definitions, definitionIndex + 1))) {
167
+ this.#matchSequenceFrom(definitions, values, definitionIndex + 1, position, hasContent, contentSinceComma, ends, unsupported, visited);
168
+ }
169
+ }
170
+ #sequenceSuffixCanBeEmpty(definitions, start) {
171
+ for (let index = start; index < definitions.length; index += 1) {
172
+ const definition = definitions[index];
173
+ if (definition !== undefined && !this.#canBeEmpty(definition))
174
+ return false;
175
+ }
176
+ return true;
177
+ }
178
+ #canBeEmpty(definition) {
179
+ switch (definition.kind) {
180
+ case "multiplier":
181
+ return definition.minimum === 0;
182
+ case "sequence":
183
+ case "all-of":
184
+ return definition.values.every((value) => this.#canBeEmpty(value));
185
+ case "one-of":
186
+ case "any-of":
187
+ return definition.values.some((value) => this.#canBeEmpty(value));
188
+ case "required":
189
+ case "keyword":
190
+ case "literal":
191
+ case "reference":
192
+ case "function":
193
+ return false;
194
+ }
195
+ }
196
+ #matchMultiplier(definition, values, start) {
197
+ const ends = new Set();
198
+ const unsupported = new Set();
199
+ let states = new Set([start]);
200
+ const inputBound = values.length - start + 1;
201
+ const maximum = Math.min(definition.maximum, inputBound);
202
+ for (let count = 0; count <= maximum; count += 1) {
203
+ this.#guard.step();
204
+ if (count >= definition.minimum) {
205
+ for (const state of states)
206
+ ends.add(state);
207
+ }
208
+ if (count === maximum || states.size === 0)
209
+ break;
210
+ const next = new Set();
211
+ for (const state of states) {
212
+ let itemStart = state;
213
+ if (count > 0 && definition.separator === "comma") {
214
+ if (values[state]?.kind !== "comma")
215
+ continue;
216
+ itemStart += 1;
217
+ }
218
+ const matched = this.#matchRepeatedItem(definition.value, values, itemStart, definition.separator);
219
+ for (const end of matched.ends) {
220
+ if (end > state)
221
+ next.add(end);
222
+ }
223
+ for (const name of matched.unsupported)
224
+ unsupported.add(name);
225
+ }
226
+ states = next;
227
+ }
228
+ return result(ends, unsupported);
229
+ }
230
+ #matchRepeatedItem(definition, values, start, separator) {
231
+ if (separator === "space")
232
+ return this.#match(definition, values, start);
233
+ const nextComma = values.findIndex((value, index) => index >= start && value.kind === "comma");
234
+ const boundary = nextComma < 0 ? values.length : nextComma;
235
+ const item = values.slice(start, boundary);
236
+ const isolated = this.#match(definition, item, 0);
237
+ if (isolated.ends.has(item.length)) {
238
+ return result([boundary], isolated.unsupported);
239
+ }
240
+ return mergeResults([
241
+ result([], isolated.unsupported),
242
+ this.#match(definition, values, start)
243
+ ]);
244
+ }
245
+ #matchShuffle(definitions, values, start, requireAll) {
246
+ const ends = new Set();
247
+ const unsupported = new Set();
248
+ this.#permuteCombination(definitions, values, start, Array.from({ length: definitions.length }, () => false), 0, requireAll, ends, unsupported, new Set());
249
+ return result(ends, unsupported);
250
+ }
251
+ #permuteCombination(definitions, values, position, used, usedCount, requireAll, ends, unsupported, visited) {
252
+ this.#guard.step();
253
+ const state = `${String(position)}:${used.map((value) => value ? "1" : "0").join("")}`;
254
+ if (visited.has(state))
255
+ return;
256
+ visited.add(state);
257
+ if ((requireAll && usedCount === definitions.length) || (!requireAll && usedCount > 0)) {
258
+ ends.add(position);
259
+ }
260
+ if (usedCount === definitions.length)
261
+ return;
262
+ for (let itemIndex = 0; itemIndex < definitions.length; itemIndex += 1) {
263
+ if (used[itemIndex] === true)
264
+ continue;
265
+ const definition = definitions[itemIndex];
266
+ if (definition === undefined)
267
+ continue;
268
+ const matched = this.#match(definition, values, position);
269
+ for (const name of matched.unsupported)
270
+ unsupported.add(name);
271
+ used[itemIndex] = true;
272
+ for (const end of matched.ends) {
273
+ this.#permuteCombination(definitions, values, end, used, usedCount + 1, requireAll, ends, unsupported, visited);
274
+ }
275
+ used[itemIndex] = false;
276
+ }
277
+ }
278
+ #matchKeyword(keyword, values, start) {
279
+ const value = values[start];
280
+ if (value === undefined)
281
+ return emptyResult();
282
+ if (keyword.startsWith("@")) {
283
+ return value.kind === "at-keyword" &&
284
+ lowerAscii(value.value) === lowerAscii(keyword.slice(1))
285
+ ? singleEnd(start + 1)
286
+ : emptyResult();
287
+ }
288
+ if (value.kind === "ident" && lowerAscii(value.value) === lowerAscii(keyword)) {
289
+ return singleEnd(start + 1);
290
+ }
291
+ const numeric = /^([+-]?(?:\d+(?:\.\d*)?|\.\d+))(%|[A-Za-z]+)?$/u.exec(keyword);
292
+ if (numeric === null)
293
+ return emptyResult();
294
+ const numberText = numeric[1];
295
+ const unit = numeric[2] ?? null;
296
+ if (numberText === undefined)
297
+ return emptyResult();
298
+ const number = Number(numberText);
299
+ if (unit === null && value.kind === "number" && value.value === number) {
300
+ return singleEnd(start + 1);
301
+ }
302
+ if (unit === "%" && value.kind === "percentage" && value.value === number) {
303
+ return singleEnd(start + 1);
304
+ }
305
+ return unit !== null &&
306
+ unit !== "%" &&
307
+ value.kind === "dimension" &&
308
+ value.value === number &&
309
+ lowerAscii(value.unit) === lowerAscii(unit)
310
+ ? singleEnd(start + 1)
311
+ : emptyResult();
312
+ }
313
+ #matchLiteral(literal, values, start) {
314
+ const value = values[start];
315
+ if (value === undefined)
316
+ return emptyResult();
317
+ const tokenKind = {
318
+ ",": "comma",
319
+ ":": "colon",
320
+ ";": "semicolon"
321
+ }[literal];
322
+ if (tokenKind !== undefined) {
323
+ return value.kind === tokenKind ? singleEnd(start + 1) : emptyResult();
324
+ }
325
+ if (literal === "(" &&
326
+ value.kind === "simple-block" &&
327
+ value.associatedToken === "open-paren") {
328
+ return singleEnd(start + 1);
329
+ }
330
+ const codePoint = literal.codePointAt(0);
331
+ return codePoint !== undefined &&
332
+ String.fromCodePoint(codePoint) === literal &&
333
+ value.kind === "delim" &&
334
+ value.value === codePoint
335
+ ? singleEnd(start + 1)
336
+ : emptyResult();
337
+ }
338
+ #matchFunction(name, definition, values, start) {
339
+ const value = values[start];
340
+ if (value?.kind !== "function-block" ||
341
+ lowerAscii(value.name) !== lowerAscii(name)) {
342
+ return emptyResult();
343
+ }
344
+ if (definition === null) {
345
+ return value.value.every((item) => item.kind === "whitespace")
346
+ ? singleEnd(start + 1)
347
+ : emptyResult();
348
+ }
349
+ const contents = significant(value.value);
350
+ const matched = this.#match(definition, contents, 0);
351
+ return {
352
+ ends: matched.ends.has(contents.length) ? new Set([start + 1]) : new Set(),
353
+ unsupported: matched.unsupported
354
+ };
355
+ }
356
+ #matchReference(reference, values, start) {
357
+ const key = `${reference.referenceKind}:${reference.name}:${String(this.#arrayId(values))}:${String(start)}`;
358
+ if (this.#activeReferences.has(key)) {
359
+ return result([], [`<${reference.name}>`]);
360
+ }
361
+ this.#activeReferences.add(key);
362
+ try {
363
+ if (reference.referenceKind === "property") {
364
+ const property = resolveCssProperty(reference.name);
365
+ if (property?.kind !== "standard" || property.syntax === null) {
366
+ return result([], [`<'${reference.name}'>`]);
367
+ }
368
+ let propertyGrammar = grammar(property.syntax);
369
+ if (propertyGrammar.kind === "multiplier" &&
370
+ propertyGrammar.separator === "comma") {
371
+ propertyGrammar = propertyGrammar.value;
372
+ }
373
+ return this.#match(propertyGrammar, values, start);
374
+ }
375
+ if (reference.referenceKind === "function") {
376
+ return this.#matchCatalog(FUNCTION_DATA.get(`${reference.name}()`) ?? [], reference, values, start);
377
+ }
378
+ const builtin = this.#matchBuiltin(reference, values, start);
379
+ if (builtin !== null)
380
+ return builtin;
381
+ return this.#applyReferenceRange(reference, this.#matchCatalog(TYPE_DATA.get(reference.name) ?? [], reference, values, start), values, start);
382
+ }
383
+ finally {
384
+ this.#activeReferences.delete(key);
385
+ }
386
+ }
387
+ #matchCatalog(entries, reference, values, start) {
388
+ if (reference.constraint?.kind === "parameter") {
389
+ return result([], [`<${reference.name}[${reference.constraint.value}]>`]);
390
+ }
391
+ const definitions = entries
392
+ .map((entry) => entry.syntax)
393
+ .filter((syntax) => syntax !== undefined);
394
+ if (definitions.length === 0) {
395
+ return result([], [`<${reference.name}>`]);
396
+ }
397
+ return mergeResults(definitions.map((source) => this.#match(grammar(source), values, start)));
398
+ }
399
+ #applyReferenceRange(reference, matched, values, start) {
400
+ if (reference.constraint?.kind !== "range")
401
+ return matched;
402
+ const value = values[start];
403
+ if (value?.kind === "function-block" &&
404
+ MATH_FUNCTIONS.has(lowerAscii(value.name))) {
405
+ return result([], [
406
+ ...matched.unsupported,
407
+ `<${reference.name}:math-function>`
408
+ ]);
409
+ }
410
+ return value !== undefined &&
411
+ matched.ends.has(start + 1) &&
412
+ this.#rangeMatches(reference, value)
413
+ ? result([start + 1], matched.unsupported)
414
+ : result([], matched.unsupported);
415
+ }
416
+ #matchBuiltin(reference, values, start) {
417
+ const value = values[start];
418
+ const atomic = (matches) => matches && value !== undefined && this.#rangeMatches(reference, value)
419
+ ? singleEnd(start + 1)
420
+ : emptyResult();
421
+ switch (reference.name) {
422
+ case "ident":
423
+ case "ident-token":
424
+ case "identifier":
425
+ case "id":
426
+ return atomic(value?.kind === "ident");
427
+ case "custom-ident":
428
+ return atomic(value?.kind === "ident" &&
429
+ !CSS_GENERIC_KEYWORD_EXCLUSIONS.has(lowerAscii(value.value)));
430
+ case "dashed-ident":
431
+ case "custom-property-name":
432
+ return atomic(value?.kind === "ident" &&
433
+ value.value.startsWith("--") &&
434
+ value.value.length > 2);
435
+ case "string":
436
+ case "string-token":
437
+ return atomic(value?.kind === "string");
438
+ case "url":
439
+ case "uri":
440
+ case "url-token":
441
+ return atomic(value?.kind === "url" ||
442
+ (value?.kind === "function-block" && lowerAscii(value.name) === "url"));
443
+ case "hash-token":
444
+ return atomic(value?.kind === "hash");
445
+ case "hex-color":
446
+ return atomic(value?.kind === "hash" &&
447
+ /^[0-9A-Fa-f]{3,4}(?:[0-9A-Fa-f]{2}){0,2}$/u.test(value.value));
448
+ case "number":
449
+ case "number-token":
450
+ return this.#matchNumeric(reference, value, start, false);
451
+ case "integer":
452
+ return this.#matchNumeric(reference, value, start, true);
453
+ case "zero":
454
+ return atomic(value?.kind === "number" && value.value === 0);
455
+ case "percentage":
456
+ case "percentage-token":
457
+ return atomic(value?.kind === "percentage");
458
+ case "dimension":
459
+ case "dimension-token":
460
+ return atomic(value?.kind === "dimension");
461
+ case "length":
462
+ case "quirky-length":
463
+ return this.#matchDimensionType(reference, value, start, LENGTH_UNITS, true);
464
+ case "angle":
465
+ return this.#matchDimensionType(reference, value, start, ANGLE_UNITS, false);
466
+ case "time":
467
+ return this.#matchDimensionType(reference, value, start, TIME_UNITS, false);
468
+ case "frequency":
469
+ return this.#matchDimensionType(reference, value, start, FREQUENCY_UNITS, false);
470
+ case "resolution":
471
+ return this.#matchDimensionType(reference, value, start, RESOLUTION_UNITS, false);
472
+ case "flex":
473
+ return this.#matchDimensionType(reference, value, start, FLEX_UNITS, false);
474
+ case "unicode-range-token":
475
+ return atomic(value?.kind === "unicode-range");
476
+ case "at-keyword-token":
477
+ return atomic(value?.kind === "at-keyword");
478
+ case "function-token":
479
+ return atomic(value?.kind === "function-block");
480
+ case "comma-token":
481
+ return atomic(value?.kind === "comma");
482
+ case "colon-token":
483
+ return atomic(value?.kind === "colon");
484
+ case "semicolon-token":
485
+ return atomic(value?.kind === "semicolon");
486
+ case "delim-token":
487
+ return atomic(value?.kind === "delim");
488
+ case "bad-string-token":
489
+ return atomic(value?.kind === "bad-string");
490
+ case "bad-url-token":
491
+ return atomic(value?.kind === "bad-url");
492
+ case "CDO-token":
493
+ return atomic(value?.kind === "cdo");
494
+ case "CDC-token":
495
+ return atomic(value?.kind === "cdc");
496
+ case "(-token":
497
+ return atomic(value?.kind === "simple-block" && value.associatedToken === "open-paren");
498
+ case "[-token":
499
+ return atomic(value?.kind === "simple-block" && value.associatedToken === "open-square");
500
+ case "{-token":
501
+ return atomic(value?.kind === "simple-block" && value.associatedToken === "open-curly");
502
+ case "border-style":
503
+ return atomic(value?.kind === "ident" && BORDER_STYLES.has(lowerAscii(value.value)));
504
+ case "border-width":
505
+ case "line-width":
506
+ if (value?.kind === "ident" &&
507
+ BORDER_WIDTH_KEYWORDS.has(lowerAscii(value.value))) {
508
+ return singleEnd(start + 1);
509
+ }
510
+ return this.#matchDimensionType(reference, value, start, LENGTH_UNITS, true);
511
+ case "paint":
512
+ return this.#match(grammar(PAINT_SYNTAX), values, start);
513
+ case "any-value":
514
+ return result(Array.from({ length: values.length - start + 1 }, (_, index) => start + index));
515
+ case "declaration-value":
516
+ return result(Array.from({ length: Math.max(0, values.length - start) }, (_, index) => start + index + 1));
517
+ case "whitespace-token":
518
+ case "eof-token":
519
+ case ")-token":
520
+ case "]-token":
521
+ case "}-token":
522
+ return emptyResult();
523
+ default:
524
+ return null;
525
+ }
526
+ }
527
+ #matchNumeric(reference, value, start, integer) {
528
+ if (value?.kind === "function-block" && MATH_FUNCTIONS.has(lowerAscii(value.name))) {
529
+ return result([], [`<${reference.name}:math-function>`]);
530
+ }
531
+ return value?.kind === "number" &&
532
+ (!integer || value.numberType === "integer") &&
533
+ this.#rangeMatches(reference, value)
534
+ ? singleEnd(start + 1)
535
+ : emptyResult();
536
+ }
537
+ #matchDimensionType(reference, value, start, units, acceptsZero) {
538
+ if (value === undefined)
539
+ return emptyResult();
540
+ if (value.kind === "function-block" && MATH_FUNCTIONS.has(lowerAscii(value.name))) {
541
+ return result([], [`<${reference.name}:math-function>`]);
542
+ }
543
+ const matches = (value.kind === "dimension" && units.has(lowerAscii(value.unit))) ||
544
+ (acceptsZero && value.kind === "number" && value.value === 0);
545
+ return matches && this.#rangeMatches(reference, value)
546
+ ? singleEnd(start + 1)
547
+ : emptyResult();
548
+ }
549
+ #rangeMatches(reference, value) {
550
+ if (reference.constraint?.kind !== "range")
551
+ return true;
552
+ const numeric = this.#numericValue(value, reference.constraint.value);
553
+ return numeric !== null &&
554
+ numeric >= reference.constraint.value.minimum.value &&
555
+ numeric <= reference.constraint.value.maximum.value;
556
+ }
557
+ #numericValue(value, range) {
558
+ if (value.kind !== "number" &&
559
+ value.kind !== "percentage" &&
560
+ value.kind !== "dimension") {
561
+ return null;
562
+ }
563
+ const boundaryUnit = range.minimum.unit ?? range.maximum.unit;
564
+ if (boundaryUnit === null || value.kind !== "dimension")
565
+ return value.value;
566
+ return convertUnit(value.value, lowerAscii(value.unit), lowerAscii(boundaryUnit));
567
+ }
568
+ #arrayId(values) {
569
+ const existing = this.#arrayIds.get(values);
570
+ if (existing !== undefined)
571
+ return existing;
572
+ const id = this.#nextArrayId;
573
+ this.#nextArrayId += 1;
574
+ this.#arrayIds.set(values, id);
575
+ return id;
576
+ }
577
+ }
578
+ function convertUnit(value, from, to) {
579
+ if (from === to)
580
+ return value;
581
+ const angleToDegrees = {
582
+ deg: 1,
583
+ grad: 0.9,
584
+ rad: 180 / Math.PI,
585
+ turn: 360
586
+ };
587
+ const timeToSeconds = { ms: 0.001, s: 1 };
588
+ const frequencyToHertz = { hz: 1, khz: 1000 };
589
+ const groups = [angleToDegrees, timeToSeconds, frequencyToHertz];
590
+ for (const group of groups) {
591
+ const sourceFactor = group[from];
592
+ const targetFactor = group[to];
593
+ if (sourceFactor !== undefined && targetFactor !== undefined) {
594
+ return value * sourceFactor / targetFactor;
595
+ }
596
+ }
597
+ return null;
598
+ }
599
+ function isCssWideKeyword(values) {
600
+ return (values.length === 1 &&
601
+ values[0]?.kind === "ident" &&
602
+ CSS_WIDE_KEYWORDS.has(lowerAscii(values[0].value)));
603
+ }
604
+ function customPropertyValueStatus(values, matcher, topLevel = true) {
605
+ let unsupported = false;
606
+ for (const value of values) {
607
+ matcher.step();
608
+ if (value.kind === "bad-string" ||
609
+ value.kind === "bad-url" ||
610
+ value.kind === "close-paren" ||
611
+ value.kind === "close-square" ||
612
+ value.kind === "close-curly" ||
613
+ (topLevel && value.kind === "semicolon") ||
614
+ (topLevel && value.kind === "delim" && value.value === 0x21)) {
615
+ return "invalid";
616
+ }
617
+ if (value.kind === "simple-block") {
618
+ const child = customPropertyValueStatus(value.value, matcher, false);
619
+ if (child === "invalid")
620
+ return "invalid";
621
+ if (child === "unsupported")
622
+ unsupported = true;
623
+ }
624
+ else if (value.kind === "function-block") {
625
+ if (lowerAscii(value.name) === "var") {
626
+ const reference = customReferenceStatus(value);
627
+ if (reference === "invalid")
628
+ return "invalid";
629
+ if (reference === "unsupported")
630
+ unsupported = true;
631
+ }
632
+ const child = customPropertyValueStatus(value.value, matcher, false);
633
+ if (child === "invalid")
634
+ return "invalid";
635
+ if (child === "unsupported")
636
+ unsupported = true;
637
+ }
638
+ }
639
+ return unsupported ? "unsupported" : "valid";
640
+ }
641
+ function customReferenceStatus(value) {
642
+ const arguments_ = significant(value.value);
643
+ const comma = arguments_.findIndex((argument) => argument.kind === "comma");
644
+ const name = arguments_.slice(0, comma < 0 ? arguments_.length : comma);
645
+ if (name.length === 1 &&
646
+ name[0]?.kind === "ident" &&
647
+ resolveCssProperty(name[0].value)?.kind === "custom") {
648
+ return "valid";
649
+ }
650
+ return name.some((argument) => argument.kind === "function-block" &&
651
+ lowerAscii(argument.name) === "var")
652
+ ? "unsupported"
653
+ : "invalid";
654
+ }
655
+ function substitutionStatus(values, matcher) {
656
+ let found = false;
657
+ let unsupported = false;
658
+ for (const value of values) {
659
+ matcher.step();
660
+ if (value.kind === "simple-block") {
661
+ const child = substitutionStatus(value.value, matcher);
662
+ if (child === "invalid")
663
+ return "invalid";
664
+ if (child === "present")
665
+ found = true;
666
+ if (child === "unsupported")
667
+ unsupported = true;
668
+ }
669
+ else if (value.kind === "function-block") {
670
+ if (lowerAscii(value.name) === "var") {
671
+ found = true;
672
+ const reference = customReferenceStatus(value);
673
+ if (reference === "invalid")
674
+ return "invalid";
675
+ if (reference === "unsupported")
676
+ unsupported = true;
677
+ }
678
+ const child = substitutionStatus(value.value, matcher);
679
+ if (child === "invalid")
680
+ return "invalid";
681
+ if (child === "present")
682
+ found = true;
683
+ if (child === "unsupported")
684
+ unsupported = true;
685
+ }
686
+ }
687
+ return unsupported ? "unsupported" : found ? "present" : "none";
688
+ }
689
+ function frozenUsage(matcher) {
690
+ return matcher.usage();
691
+ }
692
+ export function validateCssPropertyValue(declaration, options = {}) {
693
+ const matcher = new PropertyGrammarMatcher(options);
694
+ const property = resolveCssProperty(declaration.name);
695
+ if (property === null) {
696
+ return Object.freeze({
697
+ status: "invalid",
698
+ reason: "unknown-property",
699
+ property: null,
700
+ usage: frozenUsage(matcher)
701
+ });
702
+ }
703
+ if (property.kind === "custom") {
704
+ const customStatus = customPropertyValueStatus(declaration.value, matcher);
705
+ if (customStatus === "invalid") {
706
+ return Object.freeze({
707
+ status: "invalid",
708
+ reason: "invalid-value",
709
+ property,
710
+ usage: frozenUsage(matcher)
711
+ });
712
+ }
713
+ if (customStatus === "unsupported") {
714
+ return Object.freeze({
715
+ status: "unsupported",
716
+ property,
717
+ reason: "dynamic-custom-property-reference",
718
+ unresolvedReferences: Object.freeze(["var(dynamic-name)"]),
719
+ usage: frozenUsage(matcher)
720
+ });
721
+ }
722
+ return Object.freeze({
723
+ status: "valid",
724
+ property,
725
+ valueKind: "custom",
726
+ usage: frozenUsage(matcher)
727
+ });
728
+ }
729
+ const values = significant(declaration.value);
730
+ if (isCssWideKeyword(values)) {
731
+ return Object.freeze({
732
+ status: "valid",
733
+ property,
734
+ valueKind: "css-wide",
735
+ usage: frozenUsage(matcher)
736
+ });
737
+ }
738
+ const substitution = substitutionStatus(values, matcher);
739
+ if (substitution === "invalid") {
740
+ return Object.freeze({
741
+ status: "invalid",
742
+ reason: "invalid-value",
743
+ property,
744
+ usage: frozenUsage(matcher)
745
+ });
746
+ }
747
+ if (substitution === "present" || substitution === "unsupported") {
748
+ return Object.freeze({
749
+ status: "unsupported",
750
+ property,
751
+ reason: "arbitrary-substitution",
752
+ unresolvedReferences: Object.freeze([
753
+ substitution === "unsupported" ? "var(dynamic-name)" : "var()"
754
+ ]),
755
+ usage: frozenUsage(matcher)
756
+ });
757
+ }
758
+ if (property.syntax === null) {
759
+ return Object.freeze({
760
+ status: "unsupported",
761
+ property,
762
+ reason: "missing-property-syntax",
763
+ unresolvedReferences: Object.freeze([]),
764
+ usage: frozenUsage(matcher)
765
+ });
766
+ }
767
+ const matched = matcher.match(grammar(property.syntax), values);
768
+ if (matched.ends.has(values.length)) {
769
+ return Object.freeze({
770
+ status: "valid",
771
+ property,
772
+ valueKind: "grammar",
773
+ usage: frozenUsage(matcher)
774
+ });
775
+ }
776
+ if (matched.unsupported.size > 0) {
777
+ return Object.freeze({
778
+ status: "unsupported",
779
+ property,
780
+ reason: "unresolved-grammar",
781
+ unresolvedReferences: Object.freeze([...matched.unsupported].sort()),
782
+ usage: frozenUsage(matcher)
783
+ });
784
+ }
785
+ return Object.freeze({
786
+ status: "invalid",
787
+ reason: "invalid-value",
788
+ property,
789
+ usage: frozenUsage(matcher)
790
+ });
791
+ }