@tbela99/css-parser 0.7.1 → 0.9.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 (90) hide show
  1. package/.editorconfig +484 -0
  2. package/README.md +140 -84
  3. package/dist/index-umd-web.js +8461 -51655
  4. package/dist/index.cjs +8437 -51636
  5. package/dist/index.d.ts +220 -68
  6. package/dist/lib/ast/expand.js +46 -9
  7. package/dist/lib/ast/features/calc.js +76 -12
  8. package/dist/lib/ast/features/inlinecssvariables.js +6 -1
  9. package/dist/lib/ast/features/prefix.js +17 -9
  10. package/dist/lib/ast/features/shorthand.js +1 -0
  11. package/dist/lib/ast/math/expression.js +299 -11
  12. package/dist/lib/ast/math/math.js +7 -1
  13. package/dist/lib/ast/minify.js +30 -16
  14. package/dist/lib/ast/types.js +59 -49
  15. package/dist/lib/ast/walk.js +92 -18
  16. package/dist/lib/parser/declaration/list.js +1 -0
  17. package/dist/lib/parser/declaration/map.js +60 -52
  18. package/dist/lib/parser/declaration/set.js +1 -12
  19. package/dist/lib/parser/parse.js +371 -119
  20. package/dist/lib/parser/tokenize.js +31 -6
  21. package/dist/lib/parser/utils/declaration.js +2 -2
  22. package/dist/lib/parser/utils/type.js +6 -6
  23. package/dist/lib/renderer/color/a98rgb.js +1 -0
  24. package/dist/lib/renderer/color/color.js +1 -0
  25. package/dist/lib/renderer/color/colormix.js +1 -0
  26. package/dist/lib/renderer/color/hex.js +2 -1
  27. package/dist/lib/renderer/color/hsl.js +2 -1
  28. package/dist/lib/renderer/color/hwb.js +3 -2
  29. package/dist/lib/renderer/color/lab.js +2 -1
  30. package/dist/lib/renderer/color/lch.js +2 -1
  31. package/dist/lib/renderer/color/oklab.js +3 -2
  32. package/dist/lib/renderer/color/oklch.js +2 -1
  33. package/dist/lib/renderer/color/p3.js +2 -1
  34. package/dist/lib/renderer/color/prophotoRgb.js +56 -0
  35. package/dist/lib/renderer/color/prophotorgb.js +1 -1
  36. package/dist/lib/renderer/color/rec2020.js +1 -0
  37. package/dist/lib/renderer/color/relativecolor.js +52 -28
  38. package/dist/lib/renderer/color/rgb.js +2 -1
  39. package/dist/lib/renderer/color/srgb.js +3 -2
  40. package/dist/lib/renderer/color/utils/components.js +1 -0
  41. package/dist/lib/renderer/color/utils/constants.js +2 -1
  42. package/dist/lib/renderer/color/xyz.js +2 -1
  43. package/dist/lib/renderer/color/xyzd50.js +1 -0
  44. package/dist/lib/renderer/render.js +62 -12
  45. package/dist/lib/syntax/syntax.js +362 -4
  46. package/dist/lib/validation/at-rules/container.js +353 -0
  47. package/dist/lib/validation/at-rules/counter-style.js +78 -0
  48. package/dist/lib/validation/at-rules/custom-media.js +52 -0
  49. package/dist/lib/validation/at-rules/document.js +114 -0
  50. package/dist/lib/validation/at-rules/else.js +5 -0
  51. package/dist/lib/validation/at-rules/font-feature-values.js +52 -0
  52. package/dist/lib/validation/at-rules/import.js +199 -0
  53. package/dist/lib/validation/at-rules/keyframes.js +70 -0
  54. package/dist/lib/validation/at-rules/layer.js +30 -0
  55. package/dist/lib/validation/at-rules/media.js +254 -0
  56. package/dist/lib/validation/at-rules/namespace.js +85 -0
  57. package/dist/lib/validation/at-rules/page-margin-box.js +56 -0
  58. package/dist/lib/validation/at-rules/page.js +88 -0
  59. package/dist/lib/validation/at-rules/supports.js +262 -0
  60. package/dist/lib/validation/at-rules/when.js +178 -0
  61. package/dist/lib/validation/atrule.js +187 -0
  62. package/dist/lib/validation/config.js +35 -2
  63. package/dist/lib/validation/config.json.js +1683 -50905
  64. package/dist/lib/validation/declaration.js +102 -0
  65. package/dist/lib/validation/parser/parse.js +1137 -7
  66. package/dist/lib/validation/parser/types.js +28 -12
  67. package/dist/lib/validation/selector.js +26 -444
  68. package/dist/lib/validation/syntax.js +1475 -0
  69. package/dist/lib/validation/syntaxes/complex-selector-list.js +45 -0
  70. package/dist/lib/validation/syntaxes/complex-selector.js +53 -0
  71. package/dist/lib/validation/syntaxes/compound-selector.js +226 -0
  72. package/dist/lib/validation/syntaxes/family-name.js +91 -0
  73. package/dist/lib/validation/syntaxes/image.js +29 -0
  74. package/dist/lib/validation/syntaxes/keyframe-block-list.js +27 -0
  75. package/dist/lib/validation/syntaxes/keyframe-selector.js +137 -0
  76. package/dist/lib/validation/syntaxes/layer-name.js +67 -0
  77. package/dist/lib/validation/syntaxes/relative-selector-list.js +57 -0
  78. package/dist/lib/validation/syntaxes/relative-selector.js +36 -0
  79. package/dist/lib/validation/syntaxes/selector-list.js +5 -0
  80. package/dist/lib/validation/syntaxes/selector.js +5 -0
  81. package/dist/lib/validation/syntaxes/url.js +75 -0
  82. package/dist/lib/validation/utils/list.js +24 -0
  83. package/dist/lib/validation/utils/whitespace.js +22 -0
  84. package/dist/node/index.js +5 -5
  85. package/dist/web/index.js +5 -1
  86. package/dist/web/load.js +1 -0
  87. package/package.json +16 -14
  88. package/dist/lib/ast/utils/minifyfeature.js +0 -9
  89. package/dist/lib/iterable/weakset.js +0 -58
  90. package/dist/lib/parser/utils/syntax.js +0 -450
@@ -1,14 +1,19 @@
1
- import { isPseudo, isAtKeyword, isFunction, isNumber, isPercentage, isFlex, isDimension, parseDimension, isIdent, isHexColor, isHash, isIdentStart, isColor } from '../syntax/syntax.js';
1
+ import { webkitPseudoAliasMap, isIdentStart, isIdent, mathFuncs, isColor, isHexColor, isPseudo, isAtKeyword, isFunction, isNumber, isPercentage, isFlex, isDimension, parseDimension, isHash, mediaTypes } from '../syntax/syntax.js';
2
2
  import './utils/config.js';
3
3
  import { EnumToken, funcLike, ValidationLevel } from '../ast/types.js';
4
4
  import { minify, definedPropertySettings, combinators } from '../ast/minify.js';
5
5
  import { walkValues, walk } from '../ast/walk.js';
6
6
  import { expand } from '../ast/expand.js';
7
- import { parseDeclaration } from './utils/declaration.js';
7
+ import { parseDeclarationNode } from './utils/declaration.js';
8
8
  import { renderToken } from '../renderer/render.js';
9
9
  import { COLORS_NAMES, systemColors, deprecatedSystemColors } from '../renderer/color/utils/constants.js';
10
10
  import { tokenize } from './tokenize.js';
11
+ import '../validation/config.js';
12
+ import '../validation/parser/types.js';
13
+ import '../validation/parser/parse.js';
11
14
  import { validateSelector } from '../validation/selector.js';
15
+ import { validateAtRule } from '../validation/atrule.js';
16
+ import '../validation/syntaxes/complex-selector.js';
12
17
 
13
18
  const urlTokenMatcher = /^(["']?)[a-zA-Z0-9_/.-][a-zA-Z0-9_/:.#?-]+(\1)$/;
14
19
  const trimWhiteSpace = [EnumToken.CommentTokenType, EnumToken.GtTokenType, EnumToken.GteTokenType, EnumToken.LtTokenType, EnumToken.LteTokenType, EnumToken.ColumnCombinatorTokenType];
@@ -25,51 +30,14 @@ const enumTokenHints = new Set([
25
30
  EnumToken.StartMatchTokenType, EnumToken.EndMatchTokenType, EnumToken.IncludeMatchTokenType, EnumToken.DashMatchTokenType, EnumToken.ContainMatchTokenType,
26
31
  EnumToken.EOFTokenType
27
32
  ]);
28
- const webkitPseudoAliasMap = {
29
- '-webkit-autofill': 'autofill',
30
- '-webkit-any': 'is',
31
- '-moz-any': 'is',
32
- '-webkit-border-after': 'border-block-end',
33
- '-webkit-border-after-color': 'border-block-end-color',
34
- '-webkit-border-after-style': 'border-block-end-style',
35
- '-webkit-border-after-width': 'border-block-end-width',
36
- '-webkit-border-before': 'border-block-start',
37
- '-webkit-border-before-color': 'border-block-start-color',
38
- '-webkit-border-before-style': 'border-block-start-style',
39
- '-webkit-border-before-width': 'border-block-start-width',
40
- '-webkit-border-end': 'border-inline-end',
41
- '-webkit-border-end-color': 'border-inline-end-color',
42
- '-webkit-border-end-style': 'border-inline-end-style',
43
- '-webkit-border-end-width': 'border-inline-end-width',
44
- '-webkit-border-start': 'border-inline-start',
45
- '-webkit-border-start-color': 'border-inline-start-color',
46
- '-webkit-border-start-style': 'border-inline-start-style',
47
- '-webkit-border-start-width': 'border-inline-start-width',
48
- '-webkit-box-align': 'align-items',
49
- '-webkit-box-direction': 'flex-direction',
50
- '-webkit-box-flex': 'flex-grow',
51
- '-webkit-box-lines': 'flex-flow',
52
- '-webkit-box-ordinal-group': 'order',
53
- '-webkit-box-orient': 'flex-direction',
54
- '-webkit-box-pack': 'justify-content',
55
- '-webkit-column-break-after': 'break-after',
56
- '-webkit-column-break-before': 'break-before',
57
- '-webkit-column-break-inside': 'break-inside',
58
- '-webkit-font-feature-settings': 'font-feature-settings',
59
- '-webkit-hyphenate-character': 'hyphenate-character',
60
- '-webkit-initial-letter': 'initial-letter',
61
- '-webkit-margin-end': 'margin-block-end',
62
- '-webkit-margin-start': 'margin-block-start',
63
- '-webkit-padding-after': 'padding-block-end',
64
- '-webkit-padding-before': 'padding-block-start',
65
- '-webkit-padding-end': 'padding-inline-end',
66
- '-webkit-padding-start': 'padding-inline-start',
67
- '-webkit-min-device-pixel-ratio': 'min-resolution',
68
- '-webkit-max-device-pixel-ratio': 'max-resolution'
69
- };
70
33
  function reject(reason) {
71
34
  throw new Error(reason ?? 'Parsing aborted');
72
35
  }
36
+ /**
37
+ * parse css string
38
+ * @param iterator
39
+ * @param options
40
+ */
73
41
  async function doParse(iterator, options = {}) {
74
42
  if (options.signal != null) {
75
43
  options.signal.addEventListener('abort', reject);
@@ -90,7 +58,8 @@ async function doParse(iterator, options = {}) {
90
58
  inlineCssVariables: false,
91
59
  setParent: true,
92
60
  removePrefix: false,
93
- validation: false,
61
+ validation: true,
62
+ lenient: true,
94
63
  ...options
95
64
  };
96
65
  if (options.expandNestingRules) {
@@ -129,9 +98,10 @@ async function doParse(iterator, options = {}) {
129
98
  }
130
99
  const iter = tokenize(iterator);
131
100
  let item;
101
+ const rawTokens = [];
132
102
  while (item = iter.next().value) {
133
103
  stats.bytesIn = item.bytesIn;
134
- //
104
+ rawTokens.push(item);
135
105
  // doParse error
136
106
  if (item.hint != null && BadTokensTypes.includes(item.hint)) {
137
107
  // bad token
@@ -141,15 +111,15 @@ async function doParse(iterator, options = {}) {
141
111
  tokens.push(item);
142
112
  }
143
113
  if (item.token == ';' || item.token == '{') {
144
- let node = await parseNode(tokens, context, stats, options, errors, src, map);
114
+ let node = await parseNode(tokens, context, stats, options, errors, src, map, rawTokens);
115
+ rawTokens.length = 0;
145
116
  if (node != null) {
117
+ // @ts-ignore
146
118
  stack.push(node);
147
119
  // @ts-ignore
148
120
  context = node;
149
121
  }
150
122
  else if (item.token == '{') {
151
- // node == null
152
- // consume and throw away until the closing '}' or EOF
153
123
  let inBlock = 1;
154
124
  do {
155
125
  item = iter.next().value;
@@ -168,18 +138,23 @@ async function doParse(iterator, options = {}) {
168
138
  map = new Map;
169
139
  }
170
140
  else if (item.token == '}') {
171
- await parseNode(tokens, context, stats, options, errors, src, map);
141
+ await parseNode(tokens, context, stats, options, errors, src, map, rawTokens);
142
+ rawTokens.length = 0;
172
143
  const previousNode = stack.pop();
173
144
  // @ts-ignore
174
145
  context = stack[stack.length - 1] ?? ast;
146
+ // @ts-ignore
175
147
  if (previousNode != null && previousNode.typ == EnumToken.InvalidRuleTokenType) {
148
+ // @ts-ignore
176
149
  const index = context.chi.findIndex(node => node == previousNode);
177
150
  if (index > -1) {
151
+ // @ts-ignore
178
152
  context.chi.splice(index, 1);
179
153
  }
180
154
  }
181
155
  // @ts-ignore
182
156
  if (options.removeEmpty && previousNode != null && previousNode.chi.length == 0 && context.chi[context.chi.length - 1] == previousNode) {
157
+ // @ts-ignore
183
158
  context.chi.pop();
184
159
  }
185
160
  tokens = [];
@@ -187,7 +162,8 @@ async function doParse(iterator, options = {}) {
187
162
  }
188
163
  }
189
164
  if (tokens.length > 0) {
190
- await parseNode(tokens, context, stats, options, errors, src, map);
165
+ await parseNode(tokens, context, stats, options, errors, src, map, rawTokens);
166
+ rawTokens.length = 0;
191
167
  if (context != null && context.typ == EnumToken.InvalidRuleTokenType) {
192
168
  const index = context.chi.findIndex(node => node == context);
193
169
  if (index > -1) {
@@ -199,8 +175,10 @@ async function doParse(iterator, options = {}) {
199
175
  const previousNode = stack.pop();
200
176
  // @ts-ignore
201
177
  context = stack[stack.length - 1] ?? ast;
178
+ // remove empty nodes
202
179
  // @ts-ignore
203
180
  if (options.removeEmpty && previousNode != null && previousNode.chi.length == 0 && context.chi[context.chi.length - 1] == previousNode) {
181
+ // @ts-ignore
204
182
  context.chi.pop();
205
183
  continue;
206
184
  }
@@ -250,25 +228,6 @@ async function doParse(iterator, options = {}) {
250
228
  minify(ast, options, true, errors, false);
251
229
  }
252
230
  }
253
- if (options.setParent) {
254
- const nodes = [ast];
255
- let node;
256
- while ((node = nodes.shift())) {
257
- // @ts-ignore
258
- if (node.chi.length > 0) {
259
- // @ts-ignore
260
- for (const child of node.chi) {
261
- if (child.parent != node) {
262
- Object.defineProperty(child, 'parent', { ...definedPropertySettings, value: node });
263
- }
264
- if ('chi' in child && child.chi.length > 0) {
265
- // @ts-ignore
266
- nodes.push(child);
267
- }
268
- }
269
- }
270
- }
271
- }
272
231
  const endTime = performance.now();
273
232
  if (options.signal != null) {
274
233
  options.signal.removeEventListener('abort', reject);
@@ -284,9 +243,18 @@ async function doParse(iterator, options = {}) {
284
243
  total: `${(endTime - startTime).toFixed(2)}ms`
285
244
  }
286
245
  };
287
- // });
288
246
  }
289
- async function parseNode(results, context, stats, options, errors, src, map) {
247
+ function getLastNode(context) {
248
+ let i = context.chi.length;
249
+ while (i--) {
250
+ if ([EnumToken.CommentTokenType, EnumToken.CDOCOMMTokenType, EnumToken.WhitespaceTokenType].includes(context.chi[i].typ)) {
251
+ continue;
252
+ }
253
+ return context.chi[i];
254
+ }
255
+ return null;
256
+ }
257
+ async function parseNode(results, context, stats, options, errors, src, map, rawTokens) {
290
258
  let tokens = [];
291
259
  for (const t of results) {
292
260
  const node = getTokenType(t.token, t.hint);
@@ -341,28 +309,18 @@ async function parseNode(results, context, stats, options, errors, src, map) {
341
309
  if (tokens[0]?.typ == EnumToken.AtRuleTokenType) {
342
310
  const atRule = tokens.shift();
343
311
  const position = map.get(atRule);
344
- if (atRule.val == 'charset') {
345
- if (position.ind > 0) {
346
- errors.push({
347
- action: 'drop',
348
- message: 'doParse: invalid @charset',
349
- location: { src, ...position }
350
- });
351
- return null;
352
- }
353
- if (options.removeCharset) {
354
- return null;
355
- }
356
- }
357
312
  // @ts-ignore
358
313
  while ([EnumToken.WhitespaceTokenType].includes(tokens[0]?.typ)) {
359
314
  tokens.shift();
360
315
  }
361
316
  if (atRule.val == 'import') {
362
317
  // only @charset and @layer are accepted before @import
318
+ // @ts-ignore
363
319
  if (context.chi.length > 0) {
320
+ // @ts-ignore
364
321
  let i = context.chi.length;
365
322
  while (i--) {
323
+ // @ts-ignore
366
324
  const type = context.chi[i].typ;
367
325
  if (type == EnumToken.CommentNodeType) {
368
326
  continue;
@@ -371,6 +329,7 @@ async function parseNode(results, context, stats, options, errors, src, map) {
371
329
  errors.push({ action: 'drop', message: 'invalid @import', location: { src, ...position } });
372
330
  return null;
373
331
  }
332
+ // @ts-ignore
374
333
  const name = context.chi[i].nam;
375
334
  if (name != 'charset' && name != 'import' && name != 'layer') {
376
335
  errors.push({ action: 'drop', message: 'invalid @import', location: { src, ...position } });
@@ -428,6 +387,7 @@ async function parseNode(results, context, stats, options, errors, src, map) {
428
387
  stats.importedBytesIn += root.stats.bytesIn;
429
388
  if (root.ast.chi.length > 0) {
430
389
  // @todo - filter charset, layer and scope
390
+ // @ts-ignore
431
391
  context.chi.push(...root.ast.chi);
432
392
  }
433
393
  if (root.errors.length > 0) {
@@ -445,16 +405,59 @@ async function parseNode(results, context, stats, options, errors, src, map) {
445
405
  // https://www.w3.org/TR/css-nesting-1/#conditionals
446
406
  // allowed nesting at-rules
447
407
  // there must be a top level rule in the stack
448
- const raw = parseTokens(tokens, { minify: options.minify }).reduce((acc, curr) => {
408
+ if (atRule.val == 'charset') {
409
+ let spaces = 0;
410
+ // https://developer.mozilla.org/en-US/docs/Web/CSS/@charset
411
+ for (let k = 1; k < rawTokens.length; k++) {
412
+ if (rawTokens[k].hint == EnumToken.WhitespaceTokenType) {
413
+ spaces += rawTokens[k].len;
414
+ continue;
415
+ }
416
+ if (rawTokens[k].hint == EnumToken.CommentTokenType) {
417
+ continue;
418
+ }
419
+ if (rawTokens[k].hint == EnumToken.CDOCOMMTokenType) {
420
+ continue;
421
+ }
422
+ if (spaces > 1) {
423
+ errors.push({
424
+ action: 'drop',
425
+ message: '@charset must have only one space',
426
+ // @ts-ignore
427
+ location: { src, ...(map.get(atRule) ?? position) }
428
+ });
429
+ return null;
430
+ }
431
+ if (rawTokens[k].hint != EnumToken.StringTokenType || rawTokens[k].token[0] != '"') {
432
+ errors.push({
433
+ action: 'drop',
434
+ message: '@charset expects a "<charset>"',
435
+ // @ts-ignore
436
+ location: { src, ...(map.get(atRule) ?? position) }
437
+ });
438
+ return null;
439
+ }
440
+ break;
441
+ }
442
+ if (options.removeCharset) {
443
+ return null;
444
+ }
445
+ }
446
+ const t = parseAtRulePrelude(parseTokens(tokens, { minify: options.minify }), atRule);
447
+ const raw = t.reduce((acc, curr) => {
449
448
  acc.push(renderToken(curr, { removeComments: true }));
450
449
  return acc;
451
450
  }, []);
452
451
  const node = {
453
452
  typ: EnumToken.AtRuleNodeType,
454
453
  nam: renderToken(atRule, { removeComments: true }),
454
+ tokens: t,
455
455
  val: raw.join('')
456
456
  };
457
- Object.defineProperty(node, 'raw', { ...definedPropertySettings, value: raw });
457
+ Object.defineProperties(node, {
458
+ tokens: { ...definedPropertySettings, enumerable: true, value: tokens.slice() },
459
+ raw: { ...definedPropertySettings, value: raw }
460
+ });
458
461
  if (delim.typ == EnumToken.BlockStartTokenType) {
459
462
  node.chi = [];
460
463
  }
@@ -465,8 +468,41 @@ async function parseNode(results, context, stats, options, errors, src, map) {
465
468
  if (options.sourcemap) {
466
469
  node.loc = loc;
467
470
  }
471
+ if (options.validation) {
472
+ let isValid = true;
473
+ if (node.nam == 'else') {
474
+ const prev = getLastNode(context);
475
+ if (prev != null && prev.typ == EnumToken.AtRuleNodeType && ['when', 'else'].includes(prev.nam)) {
476
+ if (prev.nam == 'else') {
477
+ isValid = Array.isArray(prev.tokens) && prev.tokens.length > 0;
478
+ }
479
+ }
480
+ else {
481
+ isValid = false;
482
+ }
483
+ }
484
+ const valid = isValid ? validateAtRule(node, options, context) : {
485
+ valid: ValidationLevel.Drop,
486
+ node,
487
+ syntax: '@' + node.nam,
488
+ error: '@' + node.nam + ' not allowed here'};
489
+ if (valid.valid == ValidationLevel.Drop) {
490
+ errors.push({
491
+ action: 'drop',
492
+ message: valid.error + ' - "' + tokens.reduce((acc, curr) => acc + renderToken(curr, { minify: false }), '') + '"',
493
+ // @ts-ignore
494
+ location: { src, ...(map.get(valid.node) ?? position) }
495
+ });
496
+ // @ts-ignore
497
+ node.typ = EnumToken.InvalidAtRuleTokenType;
498
+ }
499
+ else {
500
+ node.val = node.tokens.reduce((acc, curr) => acc + renderToken(curr, { minify: false, removeComments: true }), '');
501
+ }
502
+ }
468
503
  // @ts-ignore
469
504
  context.chi.push(node);
505
+ Object.defineProperty(node, 'parent', { ...definedPropertySettings, value: context });
470
506
  return delim.typ == EnumToken.BlockStartTokenType ? node : null;
471
507
  }
472
508
  else {
@@ -474,7 +510,6 @@ async function parseNode(results, context, stats, options, errors, src, map) {
474
510
  if (delim.typ == EnumToken.BlockStartTokenType) {
475
511
  const position = map.get(tokens[0]);
476
512
  const uniq = new Map;
477
- // const uniqTokens: Token[][] = [[]];
478
513
  parseTokens(tokens, { minify: true }).reduce((acc, curr, index, array) => {
479
514
  if (curr.typ == EnumToken.CommentTokenType) {
480
515
  return acc;
@@ -514,21 +549,28 @@ async function parseNode(results, context, stats, options, errors, src, map) {
514
549
  }, uniq);
515
550
  const ruleType = context.typ == EnumToken.AtRuleNodeType && context.nam == 'keyframes' ? EnumToken.KeyFrameRuleNodeType : EnumToken.RuleNodeType;
516
551
  if (ruleType == EnumToken.RuleNodeType) {
517
- const valid = validateSelector(parseSelector(tokens), options, context);
518
- if (valid.valid != ValidationLevel.Valid) {
519
- const node = {
520
- typ: EnumToken.InvalidRuleTokenType,
552
+ parseSelector(tokens);
553
+ if (options.validation) {
554
+ // @ts-ignore
555
+ const valid = validateSelector(tokens, options, context);
556
+ if (valid.valid != ValidationLevel.Valid) {
557
+ const node = {
558
+ typ: EnumToken.InvalidRuleTokenType,
559
+ // @ts-ignore
560
+ sel: tokens.reduce((acc, curr) => acc + renderToken(curr, { minify: false }), ''),
561
+ chi: []
562
+ };
563
+ errors.push({
564
+ action: 'drop',
565
+ message: valid.error + ' - "' + tokens.reduce((acc, curr) => acc + renderToken(curr, { minify: false }), '') + '"',
566
+ // @ts-ignore
567
+ location: { src, ...(map.get(valid.node) ?? position) }
568
+ });
521
569
  // @ts-ignore
522
- sel: tokens.reduce((acc, curr) => acc + renderToken(curr, { minify: false }), ''),
523
- chi: []
524
- };
525
- errors.push({
526
- action: 'drop',
527
- message: valid.error + ' - "' + tokens.reduce((acc, curr) => acc + renderToken(curr, { minify: false }), '') + '"',
528
- location: { src, ...(map.get(valid.node) ?? position) }
529
- });
530
- context.chi.push(node);
531
- return node;
570
+ context.chi.push(node);
571
+ Object.defineProperty(node, 'parent', { ...definedPropertySettings, value: context });
572
+ return node;
573
+ }
532
574
  }
533
575
  }
534
576
  const node = {
@@ -536,6 +578,11 @@ async function parseNode(results, context, stats, options, errors, src, map) {
536
578
  sel: [...uniq.keys()].join(','),
537
579
  chi: []
538
580
  };
581
+ Object.defineProperty(node, 'tokens', {
582
+ ...definedPropertySettings,
583
+ enumerable: true,
584
+ value: tokens.slice()
585
+ });
539
586
  let raw = [...uniq.values()];
540
587
  Object.defineProperty(node, 'raw', {
541
588
  enumerable: false,
@@ -552,18 +599,39 @@ async function parseNode(results, context, stats, options, errors, src, map) {
552
599
  }
553
600
  // @ts-ignore
554
601
  context.chi.push(node);
602
+ Object.defineProperty(node, 'parent', { ...definedPropertySettings, value: context });
555
603
  return node;
556
604
  }
557
605
  else {
558
- // declaration
559
- // @ts-ignore
560
606
  let name = null;
561
- // @ts-ignore
562
607
  let value = null;
563
608
  for (let i = 0; i < tokens.length; i++) {
564
609
  if (tokens[i].typ == EnumToken.CommentTokenType) {
565
610
  continue;
566
611
  }
612
+ if (name == null && [EnumToken.IdenTokenType, EnumToken.DashedIdenTokenType].includes(tokens[i].typ)) {
613
+ name = tokens.slice(0, i + 1);
614
+ }
615
+ else if (name != null && funcLike.concat([
616
+ EnumToken.LiteralTokenType,
617
+ EnumToken.IdenTokenType, EnumToken.DashedIdenTokenType,
618
+ EnumToken.PseudoClassTokenType, EnumToken.PseudoClassFuncTokenType
619
+ ]).includes(tokens[i].typ)) {
620
+ if (tokens[i].val.charAt(0) == ':') {
621
+ Object.assign(tokens[i], getTokenType(tokens[i].val.slice(1)));
622
+ }
623
+ if ('chi' in tokens[i]) {
624
+ tokens[i].typ = EnumToken.FunctionTokenType;
625
+ }
626
+ value = parseTokens(tokens.slice(i), {
627
+ parseColor: options.parseColor,
628
+ src: options.src,
629
+ resolveUrls: options.resolveUrls,
630
+ resolve: options.resolve,
631
+ cwd: options.cwd
632
+ });
633
+ break;
634
+ }
567
635
  if (tokens[i].typ == EnumToken.ColonTokenType) {
568
636
  name = tokens.slice(0, i);
569
637
  value = parseTokens(tokens.slice(i + 1), {
@@ -573,6 +641,7 @@ async function parseNode(results, context, stats, options, errors, src, map) {
573
641
  resolve: options.resolve,
574
642
  cwd: options.cwd
575
643
  });
644
+ break;
576
645
  }
577
646
  }
578
647
  if (name == null) {
@@ -606,15 +675,175 @@ async function parseNode(results, context, stats, options, errors, src, map) {
606
675
  // @ts-ignore
607
676
  val: value
608
677
  };
609
- const result = parseDeclaration(node, errors, src, position);
678
+ const result = parseDeclarationNode(node, errors, src, position);
610
679
  if (result != null) {
680
+ // if (options.validation) {
681
+ //
682
+ // const valid: ValidationResult = validateDeclaration(result, options, context);
683
+ //
684
+ // console.error({valid});
685
+ //
686
+ // if (valid.valid == ValidationLevel.Drop) {
687
+ //
688
+ // errors.push({
689
+ // action: 'drop',
690
+ // message: valid.error + ' - "' + tokens.reduce((acc, curr) => acc + renderToken(curr, {minify: false}), '') + '"',
691
+ // // @ts-ignore
692
+ // location: {src, ...(map.get(valid.node) ?? position)}
693
+ // });
694
+ //
695
+ // return null;
696
+ // }
697
+ // }
611
698
  // @ts-ignore
612
- context.chi.push(node);
699
+ context.chi.push(result);
700
+ Object.defineProperty(result, 'parent', { ...definedPropertySettings, value: context });
613
701
  }
614
702
  return null;
615
703
  }
616
704
  }
617
705
  }
706
+ /**
707
+ * parse at-rule prelude
708
+ * @param tokens
709
+ * @param atRule
710
+ */
711
+ function parseAtRulePrelude(tokens, atRule) {
712
+ // @ts-ignore
713
+ for (const { value, parent } of walkValues(tokens, null, null, true)) {
714
+ if (value.typ == EnumToken.CommentTokenType ||
715
+ value.typ == EnumToken.WhitespaceTokenType ||
716
+ value.typ == EnumToken.CommaTokenType) {
717
+ continue;
718
+ }
719
+ if (atRule.val == 'page' && value.typ == EnumToken.PseudoClassTokenType) {
720
+ if ([':left', ':right', ':first', ':blank'].includes(value.val)) {
721
+ // @ts-ignore
722
+ value.typ = EnumToken.PseudoPageTokenType;
723
+ }
724
+ }
725
+ if (atRule.val == 'layer') {
726
+ if (parent == null && value.typ == EnumToken.LiteralTokenType) {
727
+ if (value.val.charAt(0) == '.') {
728
+ if (isIdent(value.val.slice(1))) {
729
+ // @ts-ignore
730
+ value.typ = EnumToken.ClassSelectorTokenType;
731
+ }
732
+ }
733
+ }
734
+ }
735
+ if (value.typ == EnumToken.IdenTokenType) {
736
+ if (parent == null && mediaTypes.some((t) => {
737
+ if (value.val.localeCompare(t, 'en', { sensitivity: 'base' }) == 0) {
738
+ // @ts-ignore
739
+ value.typ = EnumToken.MediaFeatureTokenType;
740
+ return true;
741
+ }
742
+ return false;
743
+ })) {
744
+ continue;
745
+ }
746
+ if (value.typ == EnumToken.IdenTokenType && 'and'.localeCompare(value.val, 'en', { sensitivity: 'base' }) == 0) {
747
+ // @ts-ignore
748
+ value.typ = EnumToken.MediaFeatureAndTokenType;
749
+ continue;
750
+ }
751
+ if (value.typ == EnumToken.IdenTokenType && 'or'.localeCompare(value.val, 'en', { sensitivity: 'base' }) == 0) {
752
+ // @ts-ignore
753
+ value.typ = EnumToken.MediaFeatureOrTokenType;
754
+ continue;
755
+ }
756
+ if (value.typ == EnumToken.IdenTokenType &&
757
+ ['not', 'only'].some((t) => t.localeCompare(value.val, 'en', { sensitivity: 'base' }) == 0)) {
758
+ // @ts-ignore
759
+ const array = parent?.chi ?? tokens;
760
+ const startIndex = array.indexOf(value);
761
+ let index = startIndex + 1;
762
+ if (index == 0) {
763
+ continue;
764
+ }
765
+ while (index < array.length && [EnumToken.CommentTokenType, EnumToken.WhitespaceTokenType].includes(array[index].typ)) {
766
+ index++;
767
+ }
768
+ if (array[index] == null || array[index].typ == EnumToken.CommaTokenType) {
769
+ continue;
770
+ }
771
+ Object.assign(array[startIndex], {
772
+ typ: value.val.toLowerCase() == 'not' ? EnumToken.MediaFeatureNotTokenType : EnumToken.MediaFeatureOnlyTokenType,
773
+ val: array[index]
774
+ });
775
+ array.splice(startIndex + 1, index - startIndex);
776
+ continue;
777
+ }
778
+ }
779
+ if (value.typ == EnumToken.ParensTokenType || (value.typ == EnumToken.FunctionTokenType && ['media', 'supports', 'style', 'scroll-state'].includes(value.val))) {
780
+ // @todo parse range and declarations
781
+ // parseDeclaration(parent.chi);
782
+ let i;
783
+ let nameIndex = -1;
784
+ let valueIndex = -1;
785
+ const dashedIdent = value.typ == EnumToken.FunctionTokenType && value.val == 'style';
786
+ for (let i = 0; i < value.chi.length; i++) {
787
+ if (value.chi[i].typ == EnumToken.CommentTokenType || value.chi[i].typ == EnumToken.WhitespaceTokenType) {
788
+ continue;
789
+ }
790
+ if ((dashedIdent && value.chi[i].typ == EnumToken.DashedIdenTokenType) || value.chi[i].typ == EnumToken.IdenTokenType || value.chi[i].typ == EnumToken.FunctionTokenType || value.chi[i].typ == EnumToken.ColorTokenType) {
791
+ nameIndex = i;
792
+ }
793
+ break;
794
+ }
795
+ if (nameIndex == -1) {
796
+ continue;
797
+ }
798
+ for (let i = nameIndex + 1; i < value.chi.length; i++) {
799
+ if (value.chi[i].typ == EnumToken.CommentTokenType || value.chi[i].typ == EnumToken.WhitespaceTokenType) {
800
+ continue;
801
+ }
802
+ valueIndex = i;
803
+ break;
804
+ }
805
+ if (valueIndex == -1) {
806
+ // @ts-ignore
807
+ // value.chi[nameIndex].typ = EnumToken.MediaFeatureTokenType;
808
+ continue;
809
+ // return tokens;
810
+ }
811
+ for (i = nameIndex + 1; i < value.chi.length; i++) {
812
+ if ([
813
+ EnumToken.GtTokenType, EnumToken.LtTokenType,
814
+ EnumToken.GteTokenType, EnumToken.LteTokenType,
815
+ EnumToken.ColonTokenType
816
+ ].includes(value.chi[valueIndex].typ)) {
817
+ const val = value.chi.splice(valueIndex, 1)[0];
818
+ const node = value.chi.splice(nameIndex, 1)[0];
819
+ // 'background'
820
+ // @ts-ignore
821
+ if (node.typ == EnumToken.ColorTokenType && node.kin == 'dpsys') {
822
+ // @ts-ignore
823
+ delete node.kin;
824
+ node.typ = EnumToken.IdenTokenType;
825
+ }
826
+ while (value.chi[0]?.typ == EnumToken.WhitespaceTokenType) {
827
+ value.chi.shift();
828
+ }
829
+ const t = [{
830
+ typ: EnumToken.MediaQueryConditionTokenType,
831
+ l: node,
832
+ op: { typ: val.typ },
833
+ r: value.chi.slice()
834
+ }];
835
+ value.chi.length = 0;
836
+ value.chi.push(...t);
837
+ }
838
+ }
839
+ }
840
+ }
841
+ return tokens;
842
+ }
843
+ /**
844
+ * parse selector
845
+ * @param tokens
846
+ */
618
847
  function parseSelector(tokens) {
619
848
  for (const { value, previousValue, nextValue, parent } of walkValues(tokens)) {
620
849
  if (value.typ == EnumToken.CommentTokenType ||
@@ -754,6 +983,15 @@ function parseSelector(tokens) {
754
983
  }
755
984
  return tokens;
756
985
  }
986
+ // export async function parseDeclarations(src: string, options: ParserOptions = {}): Promise<AstDeclaration[]> {
987
+ //
988
+ // return doParse(`.x{${src}`, options).then((result: ParseResult) => <AstDeclaration[]>(<AstRule>result.ast.chi[0]).chi.filter(t => t.typ == EnumToken.DeclarationNodeType));
989
+ // }
990
+ /**
991
+ * parse string
992
+ * @param src
993
+ * @param options
994
+ */
757
995
  function parseString(src, options = { location: false }) {
758
996
  return parseTokens([...tokenize(src)].map(t => {
759
997
  const token = getTokenType(t.token, t.hint);
@@ -764,9 +1002,6 @@ function parseString(src, options = { location: false }) {
764
1002
  }));
765
1003
  }
766
1004
  function getTokenType(val, hint) {
767
- // if (val === '' && hint == null) {
768
- // throw new Error('empty string?');
769
- // }
770
1005
  if (hint != null) {
771
1006
  return enumTokenHints.has(hint) ? { typ: hint } : { typ: hint, val };
772
1007
  }
@@ -838,7 +1073,7 @@ function getTokenType(val, hint) {
838
1073
  chi: []
839
1074
  };
840
1075
  }
841
- if (['linear-gradient', 'radial-gradient', 'repeating-linear-gradient', 'repeating-radial-gradient', 'conic-gradient', 'image', 'image-set', 'element', 'cross-fade'].includes(val)) {
1076
+ if (['linear-gradient', 'radial-gradient', 'repeating-linear-gradient', 'repeating-radial-gradient', 'conic-gradient', 'image', 'image-set', 'element', 'cross-fade', 'paint'].includes(val)) {
842
1077
  return {
843
1078
  typ: EnumToken.ImageFunctionTokenType,
844
1079
  val,
@@ -938,17 +1173,28 @@ function getTokenType(val, hint) {
938
1173
  val
939
1174
  };
940
1175
  }
1176
+ /**
1177
+ * parse token list
1178
+ * @param tokens
1179
+ * @param options
1180
+ */
941
1181
  function parseTokens(tokens, options = {}) {
942
1182
  for (let i = 0; i < tokens.length; i++) {
943
1183
  const t = tokens[i];
1184
+ if (t.typ == EnumToken.PseudoClassFuncTokenType) {
1185
+ if (t.val.slice(1) in webkitPseudoAliasMap) {
1186
+ t.val = ':' + webkitPseudoAliasMap[t.val.slice(1)];
1187
+ }
1188
+ }
1189
+ else if (t.typ == EnumToken.PseudoClassTokenType) {
1190
+ if (t.val.slice(1) in webkitPseudoAliasMap) {
1191
+ t.val = ':' + webkitPseudoAliasMap[t.val.slice(1)];
1192
+ }
1193
+ }
944
1194
  if (t.typ == EnumToken.WhitespaceTokenType && ((i == 0 ||
945
1195
  i + 1 == tokens.length ||
946
1196
  [EnumToken.CommaTokenType, EnumToken.GteTokenType, EnumToken.LteTokenType, EnumToken.ColumnCombinatorTokenType].includes(tokens[i + 1].typ)) ||
947
- (i > 0 &&
948
- // tokens[i + 1]?.typ != Literal ||
949
- // funcLike.includes(tokens[i - 1].typ) &&
950
- // !['var', 'calc'].includes((<FunctionToken>tokens[i - 1]).val)))) &&
951
- trimWhiteSpace.includes(tokens[i - 1].typ)))) {
1197
+ (i > 0 && trimWhiteSpace.includes(tokens[i - 1].typ)))) {
952
1198
  tokens.splice(i--, 1);
953
1199
  continue;
954
1200
  }
@@ -1040,7 +1286,7 @@ function parseTokens(tokens, options = {}) {
1040
1286
  }
1041
1287
  }
1042
1288
  else if ([
1043
- EnumToken.DashMatchTokenType, EnumToken.StartMatchTokenType, EnumToken.ContainMatchTokenType, EnumToken.EndMatchTokenType, EnumToken.IncludeMatchTokenType
1289
+ EnumToken.DashMatchTokenType, EnumToken.StartMatchTokenType, EnumToken.ContainMatchTokenType, EnumToken.EndMatchTokenType, EnumToken.IncludeMatchTokenType, EnumToken.DelimTokenType
1044
1290
  ].includes(t.chi[m].typ)) {
1045
1291
  let upper = m;
1046
1292
  let lower = m;
@@ -1070,9 +1316,15 @@ function parseTokens(tokens, options = {}) {
1070
1316
  Object.assign(val, { typ: EnumToken.IdenTokenType, val: slice });
1071
1317
  }
1072
1318
  }
1319
+ // @ts-ignore
1320
+ const typ = t.chi[m].typ;
1321
+ // @ts-ignore
1073
1322
  t.chi[m] = {
1074
1323
  typ: EnumToken.MatchExpressionTokenType,
1075
- op: t.chi[m].typ,
1324
+ op: {
1325
+ // @ts-ignore
1326
+ typ: typ == EnumToken.DelimTokenType ? EnumToken.EqualMatchTokenType : typ
1327
+ },
1076
1328
  l: t.chi[lower],
1077
1329
  r: t.chi[upper]
1078
1330
  };
@@ -1141,7 +1393,7 @@ function parseTokens(tokens, options = {}) {
1141
1393
  // @ts-ignore
1142
1394
  parseTokens(t.chi, options);
1143
1395
  }
1144
- if (t.typ == EnumToken.FunctionTokenType && t.val == 'calc') {
1396
+ if (t.typ == EnumToken.FunctionTokenType && mathFuncs.includes(t.val)) {
1145
1397
  for (const { value, parent } of walkValues(t.chi)) {
1146
1398
  if (value.typ == EnumToken.WhitespaceTokenType) {
1147
1399
  const p = (parent ?? t);