@tbela99/css-parser 1.3.2 → 1.3.4

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 (59) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +59 -20
  3. package/dist/index-umd-web.js +1846 -1075
  4. package/dist/index.cjs +1941 -1202
  5. package/dist/index.d.ts +914 -181
  6. package/dist/lib/ast/expand.js +5 -10
  7. package/dist/lib/ast/features/calc.js +8 -8
  8. package/dist/lib/ast/features/inlinecssvariables.js +9 -8
  9. package/dist/lib/ast/features/prefix.js +5 -15
  10. package/dist/lib/ast/features/shorthand.js +5 -6
  11. package/dist/lib/ast/features/transform.js +18 -25
  12. package/dist/lib/ast/features/type.js +4 -2
  13. package/dist/lib/ast/minify.js +56 -112
  14. package/dist/lib/ast/transform/compute.js +2 -4
  15. package/dist/lib/ast/transform/matrix.js +20 -20
  16. package/dist/lib/ast/transform/minify.js +105 -12
  17. package/dist/lib/ast/transform/rotate.js +11 -11
  18. package/dist/lib/ast/transform/scale.js +6 -6
  19. package/dist/lib/ast/transform/skew.js +4 -4
  20. package/dist/lib/ast/transform/translate.js +3 -3
  21. package/dist/lib/ast/transform/utils.js +30 -37
  22. package/dist/lib/ast/types.js +16 -4
  23. package/dist/lib/ast/walk.js +172 -70
  24. package/dist/lib/fs/resolve.js +12 -7
  25. package/dist/lib/parser/declaration/list.js +3 -1
  26. package/dist/lib/parser/parse.js +441 -161
  27. package/dist/lib/parser/tokenize.js +12 -14
  28. package/dist/lib/renderer/render.js +7 -7
  29. package/dist/lib/syntax/color/cmyk.js +6 -3
  30. package/dist/lib/syntax/color/color-mix.js +2 -3
  31. package/dist/lib/syntax/color/color.js +28 -6
  32. package/dist/lib/syntax/color/hex.js +3 -0
  33. package/dist/lib/syntax/color/hsl.js +18 -7
  34. package/dist/lib/syntax/color/hwb.js +3 -3
  35. package/dist/lib/syntax/color/lab.js +4 -4
  36. package/dist/lib/syntax/color/lch.js +7 -4
  37. package/dist/lib/syntax/color/oklab.js +4 -4
  38. package/dist/lib/syntax/color/oklch.js +18 -6
  39. package/dist/lib/syntax/color/relativecolor.js +9 -56
  40. package/dist/lib/syntax/color/srgb.js +1 -1
  41. package/dist/lib/syntax/syntax.js +36 -18
  42. package/dist/lib/validation/at-rules/container.js +11 -0
  43. package/dist/lib/validation/at-rules/counter-style.js +11 -0
  44. package/dist/lib/validation/at-rules/font-feature-values.js +11 -0
  45. package/dist/lib/validation/at-rules/keyframes.js +11 -0
  46. package/dist/lib/validation/at-rules/layer.js +11 -0
  47. package/dist/lib/validation/at-rules/media.js +11 -0
  48. package/dist/lib/validation/at-rules/page-margin-box.js +11 -0
  49. package/dist/lib/validation/at-rules/page.js +11 -0
  50. package/dist/lib/validation/at-rules/supports.js +11 -0
  51. package/dist/lib/validation/at-rules/when.js +11 -0
  52. package/dist/lib/validation/config.js +0 -2
  53. package/dist/lib/validation/config.json.js +21 -9
  54. package/dist/lib/validation/parser/parse.js +53 -2
  55. package/dist/lib/validation/syntax.js +199 -36
  56. package/dist/node.js +63 -36
  57. package/dist/web.js +84 -25
  58. package/package.json +7 -5
  59. package/dist/lib/validation/parser/types.js +0 -54
@@ -1,12 +1,11 @@
1
- import { ValidationTokenEnum } from './parser/types.js';
2
- import { renderSyntax } from './parser/parse.js';
3
- import { EnumToken, SyntaxValidationResult, ColorType } from '../ast/types.js';
1
+ import { ValidationTokenEnum, renderSyntax } from './parser/parse.js';
2
+ import { SyntaxValidationResult, EnumToken, ColorType } from '../ast/types.js';
4
3
  import '../ast/minify.js';
5
4
  import '../ast/walk.js';
6
5
  import '../parser/parse.js';
7
6
  import '../parser/tokenize.js';
8
7
  import '../parser/utils/config.js';
9
- import { wildCardFuncs, isIdentColor, mathFuncs } from '../syntax/syntax.js';
8
+ import { wildCardFuncs, isIdentColor, mathFuncs, isColor } from '../syntax/syntax.js';
10
9
  import { renderToken } from '../renderer/render.js';
11
10
  import '../renderer/sourcemap/lib/encode.js';
12
11
  import { getSyntaxConfig, getParsedSyntax, getSyntax } from './config.js';
@@ -17,6 +16,116 @@ import '../ast/features/type.js';
17
16
  const config = getSyntaxConfig();
18
17
  // @ts-ignore
19
18
  const allValues = getSyntaxConfig()["declarations" /* ValidationSyntaxGroupEnum.Declarations */].all.syntax.trim().split(/[\s|]+/g);
19
+ /**
20
+ * Check if a node is allowed as child in a given context
21
+ * @param node
22
+ * @param context
23
+ */
24
+ function isNodeAllowedInContext(node, context) {
25
+ if (node.typ == EnumToken.CommentNodeType || context == null) {
26
+ return true;
27
+ }
28
+ switch (context?.typ) {
29
+ case EnumToken.StyleSheetNodeType:
30
+ case EnumToken.RuleNodeType:
31
+ return node.typ == EnumToken.RuleNodeType ||
32
+ node.typ == EnumToken.AtRuleNodeType ||
33
+ node.typ == EnumToken.KeyframesAtRuleNodeType ||
34
+ (node.typ == EnumToken.DeclarationNodeType && context.typ == EnumToken.RuleNodeType) ||
35
+ (node.typ == EnumToken.CDOCOMMNodeType && context.typ == EnumToken.StyleSheetNodeType);
36
+ case EnumToken.KeyframesAtRuleNodeType:
37
+ return node.typ == EnumToken.KeyFramesRuleNodeType;
38
+ case EnumToken.KeyFramesRuleNodeType:
39
+ return node.typ == EnumToken.DeclarationNodeType;
40
+ case EnumToken.AtRuleNodeType:
41
+ // @ts-ignore
42
+ const syntax = getParsedSyntax("atRules" /* ValidationSyntaxGroupEnum.AtRules */, '@' + context.nam)?.[0].chi ?? null;
43
+ //
44
+ if (syntax == null) {
45
+ // console.error(`syntax: Not found ${ValidationSyntaxGroupEnum.AtRules}@${(context as AstAtRule).nam}`);
46
+ return true;
47
+ }
48
+ const stack = syntax.slice();
49
+ for (const child of stack) {
50
+ if (Array.isArray(child)) {
51
+ stack.push(...child);
52
+ continue;
53
+ }
54
+ if ('chi' in child && Array.isArray(child.chi)) {
55
+ stack.push(...child.chi);
56
+ continue;
57
+ }
58
+ // @ts-ignore
59
+ if (child.l != null) {
60
+ // @ts-ignore
61
+ stack.push(child.l);
62
+ // @ts-ignore
63
+ if (child.r != null) {
64
+ // @ts-ignore
65
+ stack.push(...(Array.isArray(child.r) ? child.r : [child.r]));
66
+ }
67
+ continue;
68
+ }
69
+ if (node.typ == EnumToken.DeclarationNodeType) {
70
+ if (child.typ == ValidationTokenEnum.DeclarationDefinitionToken) {
71
+ if (node.nam == child.nam) {
72
+ return true;
73
+ }
74
+ }
75
+ }
76
+ if (child.typ == ValidationTokenEnum.PropertyType) {
77
+ if (['group-rule-body', 'block-contents', 'rule-list', 'stylesheet'].includes(child.val)) {
78
+ if ((node.typ == EnumToken.RuleNodeType ||
79
+ node.typ == EnumToken.AtRuleNodeType ||
80
+ node.typ == EnumToken.KeyframesAtRuleNodeType)) {
81
+ return true;
82
+ }
83
+ if (node.typ == EnumToken.DeclarationNodeType) {
84
+ let parent = node.parent;
85
+ while (parent != null) {
86
+ if (parent.parent?.typ == EnumToken.RuleNodeType) {
87
+ return true;
88
+ }
89
+ parent = parent.parent;
90
+ }
91
+ }
92
+ }
93
+ if (['declaration-list', 'feature-value-declaration'].includes(child.val) && node.typ == EnumToken.DeclarationNodeType) {
94
+ return true;
95
+ }
96
+ if (child.val == 'page-body' && (node.typ == EnumToken.DeclarationNodeType || (node.typ == EnumToken.AtRuleNodeType && [
97
+ 'top-left-corner', 'top-left', 'top-center', 'top-right', 'top-right-corner',
98
+ 'bottom-left-corner', 'bottom-left', 'bottom-center', 'bottom-right', 'bottom-right-corner',
99
+ 'left-top', 'left-middle', 'left-bottom', 'right-top', 'right-middle', 'right-bottom'
100
+ ].includes(node.nam)))) {
101
+ return true;
102
+ }
103
+ if (child.val == 'feature-value-block-list' &&
104
+ (node.typ == EnumToken.AtRuleNodeType && ['stylistic', 'historical-forms', 'styleset', 'character-variant', 'swash', 'ornaments', 'annotation'].includes(node.nam))) {
105
+ return true;
106
+ }
107
+ if (['feature-value-declaration-list', 'feature-value-declaration'].includes(child.val) && node.typ == EnumToken.DeclarationNodeType) {
108
+ return true;
109
+ }
110
+ if (child.val == 'page-body') {
111
+ if (node.typ == EnumToken.DeclarationNodeType) {
112
+ return true;
113
+ }
114
+ }
115
+ // console.error(`isNodeAllowedInContext: Not found ${(child as ValidationPropertyToken).val}`, {
116
+ // child,
117
+ // node
118
+ // });
119
+ }
120
+ }
121
+ break;
122
+ }
123
+ return false;
124
+ }
125
+ /**
126
+ * Create a syntax validation context from a list of tokens
127
+ * @param input
128
+ */
20
129
  function createContext(input) {
21
130
  const values = input.slice();
22
131
  const result = values.filter(token => token.typ != EnumToken.CommentTokenType).slice();
@@ -77,7 +186,22 @@ function createContext(input) {
77
186
  }
78
187
  };
79
188
  }
80
- function evaluateSyntax(node, options) {
189
+ /**
190
+ * Evaluate the validity of the syntax of a node
191
+ * @param node
192
+ * @param parent
193
+ * @param options
194
+ */
195
+ function evaluateSyntax(node, parent, options) {
196
+ if (node.validSyntax) {
197
+ return {
198
+ valid: SyntaxValidationResult.Valid,
199
+ node,
200
+ syntax: null,
201
+ error: '',
202
+ context: []
203
+ };
204
+ }
81
205
  let ast;
82
206
  let result;
83
207
  switch (node.typ) {
@@ -85,25 +209,34 @@ function evaluateSyntax(node, options) {
85
209
  if (node.nam.startsWith('--')) {
86
210
  break;
87
211
  }
212
+ let token = null;
213
+ let values = node.val.slice();
88
214
  ast = getParsedSyntax("declarations" /* ValidationSyntaxGroupEnum.Declarations */, node.nam);
89
- if (ast != null) {
90
- let token = null;
91
- const values = node.val.slice();
92
- while (values.length > 0) {
93
- token = values.at(-1);
94
- if (token.typ == EnumToken.WhitespaceTokenType || token.typ == EnumToken.CommentTokenType) {
215
+ while (values.length > 0) {
216
+ token = values.at(-1);
217
+ if (token.typ == EnumToken.WhitespaceTokenType || token.typ == EnumToken.CommentTokenType) {
218
+ values.pop();
219
+ }
220
+ else {
221
+ if (token.typ == EnumToken.ImportantTokenType) {
95
222
  values.pop();
96
- }
97
- else {
98
- if (token.typ == EnumToken.ImportantTokenType) {
223
+ if (values.at(-1)?.typ == EnumToken.WhitespaceTokenType) {
99
224
  values.pop();
100
- if (values.at(-1)?.typ == EnumToken.WhitespaceTokenType) {
101
- values.pop();
102
- }
103
225
  }
104
- break;
226
+ }
227
+ break;
228
+ }
229
+ }
230
+ if (ast == null) {
231
+ if (parent?.typ == EnumToken.AtRuleNodeType) {
232
+ ast = (getParsedSyntax("atRules" /* ValidationSyntaxGroupEnum.AtRules */, ['@' + parent.nam, 'descriptors', node.nam]));
233
+ if (ast == null) {
234
+ ast = (getParsedSyntax("atRules" /* ValidationSyntaxGroupEnum.AtRules */, ['@' + parent.nam, 'descriptors', node.nam]) ?? getParsedSyntax("atRules" /* ValidationSyntaxGroupEnum.AtRules */, '@' + parent.nam))?.[0]?.chi;
235
+ values = [{ ...node, val: values }];
105
236
  }
106
237
  }
238
+ }
239
+ if (ast != null) {
107
240
  result = doEvaluateSyntax(ast, createContext(values), { ...options, visited: new WeakMap() });
108
241
  if (result.valid == SyntaxValidationResult.Valid && !result.context.done()) {
109
242
  let token = null;
@@ -195,7 +328,7 @@ function doEvaluateSyntax(syntaxes, context, options) {
195
328
  continue;
196
329
  }
197
330
  }
198
- else if (options.occurence !== false && syntax.occurence != null) {
331
+ else if (options.occurrence !== false && syntax.occurence != null) {
199
332
  result = matchOccurence(syntax, context, options);
200
333
  }
201
334
  else if (options.atLeastOnce !== false && syntax.atLeastOnce) {
@@ -284,7 +417,7 @@ function matchList(syntax, context, options) {
284
417
  result = doEvaluateSyntax([syntax], createContext(tokens), {
285
418
  ...options,
286
419
  isList: false,
287
- occurence: false
420
+ occurrence: false
288
421
  });
289
422
  if (result.valid == SyntaxValidationResult.Valid) {
290
423
  context = con.clone();
@@ -319,7 +452,7 @@ function matchOccurence(syntax, context, options) {
319
452
  let counter = 0;
320
453
  let result;
321
454
  do {
322
- result = match(syntax, context.clone(), { ...options, occurence: false });
455
+ result = match(syntax, context.clone(), { ...options, occurrence: false });
323
456
  if (result.valid == SyntaxValidationResult.Drop) {
324
457
  break;
325
458
  }
@@ -370,7 +503,7 @@ function match(syntax, context, options) {
370
503
  ...options,
371
504
  isRepeatable: null,
372
505
  isList: null,
373
- occurence: null,
506
+ occurrence: null,
374
507
  atLeastOnce: null
375
508
  });
376
509
  if (result.valid == SyntaxValidationResult.Valid) {
@@ -382,7 +515,7 @@ function match(syntax, context, options) {
382
515
  case ValidationTokenEnum.Keyword:
383
516
  success = (token.typ == EnumToken.IdenTokenType || token.typ == EnumToken.DashedIdenTokenType || isIdentColor(token)) &&
384
517
  (token.val == syntax.val ||
385
- syntax.val === token.val?.toLowerCase?.() ||
518
+ syntax.val.toLowerCase() === token.val?.toLowerCase?.() ||
386
519
  // config.declarations.all
387
520
  allValues.includes(token.val.toLowerCase()));
388
521
  if (success) {
@@ -421,7 +554,7 @@ function match(syntax, context, options) {
421
554
  ...options,
422
555
  isRepeatable: null,
423
556
  isList: null,
424
- occurence: null,
557
+ occurrence: null,
425
558
  atLeastOnce: null
426
559
  });
427
560
  case ValidationTokenEnum.Comma:
@@ -456,7 +589,7 @@ function match(syntax, context, options) {
456
589
  ...options,
457
590
  isRepeatable: null,
458
591
  isList: null,
459
- occurence: null,
592
+ occurrence: null,
460
593
  atLeastOnce: null
461
594
  }).valid == SyntaxValidationResult.Valid;
462
595
  if (success) {
@@ -479,18 +612,19 @@ function match(syntax, context, options) {
479
612
  }
480
613
  function matchPropertyType(syntax, context, options) {
481
614
  if (![
482
- 'bg-position',
615
+ 'color',
483
616
  'integer',
617
+ 'bg-position',
484
618
  'length-percentage', 'flex', 'calc-sum', 'color',
485
619
  'color-base', 'system-color', 'deprecated-system-color',
486
- 'pseudo-class-selector', 'pseudo-element-selector'
620
+ 'pseudo-class-selector', 'pseudo-element-selector', 'feature-value-declaration'
487
621
  ].includes(syntax.val)) {
488
622
  if (syntax.val in config["syntaxes" /* ValidationSyntaxGroupEnum.Syntaxes */]) {
489
623
  return doEvaluateSyntax(getParsedSyntax("syntaxes" /* ValidationSyntaxGroupEnum.Syntaxes */, syntax.val), context, {
490
624
  ...options,
491
625
  isRepeatable: null,
492
626
  isList: null,
493
- occurence: null,
627
+ occurrence: null,
494
628
  atLeastOnce: null
495
629
  });
496
630
  }
@@ -502,7 +636,7 @@ function matchPropertyType(syntax, context, options) {
502
636
  ...options,
503
637
  isRepeatable: null,
504
638
  isList: null,
505
- occurence: null,
639
+ occurrence: null,
506
640
  atLeastOnce: null
507
641
  });
508
642
  if (result.valid == SyntaxValidationResult.Valid) {
@@ -607,6 +741,14 @@ function matchPropertyType(syntax, context, options) {
607
741
  (token.typ == EnumToken.IdenTokenType && typeof Math[token.val.toUpperCase()] == 'number') ||
608
742
  [EnumToken.BinaryExpressionTokenType, EnumToken.NumberTokenType, EnumToken.PercentageTokenType, EnumToken.DimensionTokenType, EnumToken.LengthTokenType, EnumToken.AngleTokenType, EnumToken.TimeTokenType, EnumToken.ResolutionTokenType, EnumToken.FrequencyTokenType].includes(token.typ);
609
743
  break;
744
+ case 'declaration':
745
+ {
746
+ success = token.typ == EnumToken.DeclarationNodeType;
747
+ if (success) {
748
+ success = evaluateSyntax(token, null, options).valid == SyntaxValidationResult.Valid;
749
+ }
750
+ }
751
+ break;
610
752
  case 'declaration-value':
611
753
  while (!context.done()) {
612
754
  context.next();
@@ -633,13 +775,17 @@ function matchPropertyType(syntax, context, options) {
633
775
  break;
634
776
  case 'color':
635
777
  case 'color-base':
636
- success = token.typ == EnumToken.ColorTokenType || (token.typ == EnumToken.IdenTokenType && 'currentcolor' === token.val.toLowerCase()) || (token.typ == EnumToken.IdenTokenType && 'transparent' === token.val.toLowerCase()) || (token.typ == EnumToken.FunctionTokenType && wildCardFuncs.includes(token.val));
778
+ success = token.typ == EnumToken.ColorTokenType ||
779
+ (token.typ == EnumToken.IdenTokenType && 'currentcolor' === token.val.toLowerCase()) ||
780
+ (token.typ == EnumToken.IdenTokenType && 'transparent' === token.val.toLowerCase()) ||
781
+ (token.typ == EnumToken.FunctionTokenType && wildCardFuncs.includes(token.val) ||
782
+ isColor(token));
637
783
  if (!success && token.typ == EnumToken.FunctionTokenType && colorsFunc.includes(token.val)) {
638
784
  success = doEvaluateSyntax(getParsedSyntax("functions" /* ValidationSyntaxGroupEnum.Functions */, token.val)?.[0]?.chi, createContext(token.chi), {
639
785
  ...options,
640
786
  isRepeatable: null,
641
787
  isList: null,
642
- occurence: null,
788
+ occurrence: null,
643
789
  atLeastOnce: null
644
790
  }).valid == SyntaxValidationResult.Valid;
645
791
  }
@@ -647,8 +793,26 @@ function matchPropertyType(syntax, context, options) {
647
793
  case 'hex-color':
648
794
  success = (token.typ == EnumToken.ColorTokenType && token.kin == ColorType.HEX) || (token.typ == EnumToken.FunctionTokenType && wildCardFuncs.includes(token.val));
649
795
  break;
796
+ case 'feature-value-declaration':
797
+ {
798
+ let hasNumber = false;
799
+ success = token.typ == EnumToken.DeclarationNodeType && token.val.length > 0 && token.val.every((val) => {
800
+ if (val.typ == EnumToken.WhitespaceTokenType || val.typ == EnumToken.CommentTokenType) {
801
+ return true;
802
+ }
803
+ const success = (val.typ == EnumToken.NumberTokenType && Number.isInteger(+val.val) && val.val > 0) || (val.typ == EnumToken.FunctionTokenType && mathFuncs.includes(val.val.toLowerCase()) || (val.typ == EnumToken.FunctionTokenType && wildCardFuncs.includes(val.val)));
804
+ if (success) {
805
+ hasNumber = true;
806
+ }
807
+ if ('range' in syntax) {
808
+ return success && +val.val >= +syntax.range[0] && +val.val <= +syntax.range[1];
809
+ }
810
+ return success;
811
+ }) && hasNumber;
812
+ }
813
+ break;
650
814
  case 'integer':
651
- success = (token.typ == EnumToken.NumberTokenType && Number.isInteger(+(token.val))) || (token.typ == EnumToken.FunctionTokenType && mathFuncs.includes(token.val.toLowerCase()) || (token.typ == EnumToken.FunctionTokenType && wildCardFuncs.includes(token.val)));
815
+ success = (token.typ == EnumToken.NumberTokenType && Number.isInteger(+token.val) && token.val > 0) || (token.typ == EnumToken.FunctionTokenType && mathFuncs.includes(token.val.toLowerCase()) || (token.typ == EnumToken.FunctionTokenType && wildCardFuncs.includes(token.val)));
652
816
  if ('range' in syntax) {
653
817
  success = success && +token.val >= +syntax.range[0] && +token.val <= +syntax.range[1];
654
818
  }
@@ -712,7 +876,7 @@ function matchPropertyType(syntax, context, options) {
712
876
  ...options,
713
877
  isRepeatable: null,
714
878
  isList: null,
715
- occurence: null,
879
+ occurrence: null,
716
880
  atLeastOnce: null
717
881
  }).valid == SyntaxValidationResult.Valid;
718
882
  }
@@ -728,7 +892,7 @@ function matchPropertyType(syntax, context, options) {
728
892
  ...options,
729
893
  isRepeatable: null,
730
894
  isList: null,
731
- occurence: null,
895
+ occurrence: null,
732
896
  atLeastOnce: null
733
897
  }).valid == SyntaxValidationResult.Valid;
734
898
  }
@@ -875,7 +1039,6 @@ function allOf(syntax, context, options) {
875
1039
  i = -1;
876
1040
  }
877
1041
  }
878
- // console.error()
879
1042
  const success = syntax.length == 0;
880
1043
  return {
881
1044
  valid: success ? SyntaxValidationResult.Valid : SyntaxValidationResult.Drop,
@@ -902,4 +1065,4 @@ function flatten(syntax) {
902
1065
  return data;
903
1066
  }
904
1067
 
905
- export { createContext, doEvaluateSyntax, evaluateSyntax };
1068
+ export { createContext, doEvaluateSyntax, evaluateSyntax, isNodeAllowedInContext };
package/dist/node.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import process from 'node:process';
2
2
  export { ColorType, EnumToken, ValidationLevel } from './lib/ast/types.js';
3
3
  export { minify } from './lib/ast/minify.js';
4
- export { WalkerOptionEnum, WalkerValueEvent, walk, walkValues } from './lib/ast/walk.js';
4
+ export { WalkerEvent, WalkerOptionEnum, walk, walkValues } from './lib/ast/walk.js';
5
5
  export { expand } from './lib/ast/expand.js';
6
6
  import { doRender } from './lib/renderer/render.js';
7
7
  export { renderToken } from './lib/renderer/render.js';
@@ -14,38 +14,50 @@ export { convertColor } from './lib/syntax/color/color.js';
14
14
  import './lib/syntax/color/utils/constants.js';
15
15
  export { isOkLabClose, okLabDistance } from './lib/syntax/color/utils/distance.js';
16
16
  import './lib/validation/config.js';
17
- import './lib/validation/parser/types.js';
18
17
  import './lib/validation/parser/parse.js';
19
18
  import './lib/validation/syntaxes/complex-selector.js';
20
19
  import './lib/validation/syntax.js';
21
20
  import { resolve, matchUrl, dirname } from './lib/fs/resolve.js';
22
21
  import { Readable } from 'node:stream';
23
22
  import { createReadStream } from 'node:fs';
23
+ import { readFile, lstat } from 'node:fs/promises';
24
24
  export { FeatureWalkMode } from './lib/ast/features/type.js';
25
25
 
26
- /**
27
- * node module entry point
28
- * @module node
29
- */
30
26
  /**
31
27
  * load file or url as stream
32
28
  * @param url
33
29
  * @param currentFile
30
+ * @param asStream
31
+ * @throws Error file not found
34
32
  *
35
33
  * @private
36
34
  */
37
- async function getStream(url, currentFile = '.') {
35
+ async function load(url, currentFile = '.', asStream = false) {
38
36
  const resolved = resolve(url, currentFile);
39
- // @ts-ignore
40
- return matchUrl.test(resolved.absolute) ? fetch(resolved.absolute).then((response) => {
41
- if (!response.ok) {
42
- throw new Error(`${response.status} ${response.statusText} ${response.url}`);
37
+ if (matchUrl.test(resolved.absolute)) {
38
+ return fetch(resolved.absolute).then(async (response) => {
39
+ if (!response.ok) {
40
+ throw new Error(`${response.status} ${response.statusText} ${response.url}`);
41
+ }
42
+ return asStream ? response.body : await response.text();
43
+ });
44
+ }
45
+ try {
46
+ if (!asStream) {
47
+ return readFile(resolved.absolute, 'utf-8');
43
48
  }
44
- return response.body;
45
- }) : Readable.toWeb(createReadStream(resolved.absolute));
49
+ const stats = await lstat(resolved.absolute);
50
+ if (stats.isFile()) {
51
+ return Readable.toWeb(createReadStream(resolved.absolute, { encoding: 'utf-8', highWaterMark: 64 * 1024 }));
52
+ }
53
+ }
54
+ catch (error) {
55
+ console.warn(error);
56
+ }
57
+ throw new Error(`File not found: '${resolved.absolute || url}'`);
46
58
  }
47
59
  /**
48
- * render ast tree
60
+ * render the ast tree
49
61
  * @param data
50
62
  * @param options
51
63
  *
@@ -55,22 +67,33 @@ async function getStream(url, currentFile = '.') {
55
67
  *
56
68
  * import {render, ColorType} from '@tbela99/css-parser';
57
69
  *
58
- * // remote file
59
- * let result = render(ast);
60
- * console.log(result.code);
70
+ * const css = 'body { color: color(from hsl(0 100% 50%) xyz x y z); }';
71
+ * const parseResult = await parse(css);
61
72
  *
62
- * // local file
63
- * result = await parseFile(ast, {beatify: true, convertColor: ColorType.SRGB});
73
+ * let renderResult = render(parseResult.ast);
64
74
  * console.log(result.code);
75
+ *
76
+ * // body{color:red}
77
+ *
78
+ *
79
+ * renderResult = render(parseResult.ast, {beautify: true, convertColor: ColorType.SRGB});
80
+ * console.log(renderResult.code);
81
+ *
82
+ * // body {
83
+ * // color: color(srgb 1 0 0)
84
+ * // }
65
85
  * ```
66
86
  */
67
87
  function render(data, options = {}) {
68
- return doRender(data, Object.assign(options, { getStream, resolve, dirname, cwd: options.cwd ?? process.cwd() }));
88
+ return doRender(data, Object.assign(options, { resolve, dirname, cwd: options.cwd ?? process.cwd() }));
69
89
  }
70
90
  /**
71
91
  * parse css file
72
92
  * @param file url or path
73
93
  * @param options
94
+ * @param asStream load file as stream
95
+ *
96
+ * @throws Error file not found
74
97
  *
75
98
  * Example:
76
99
  *
@@ -87,23 +110,23 @@ function render(data, options = {}) {
87
110
  * console.log(result.ast);
88
111
  * ```
89
112
  */
90
- async function parseFile(file, options = {}) {
91
- return getStream(file).then(stream => parse(stream, { src: file, ...options }));
113
+ async function parseFile(file, options = {}, asStream = false) {
114
+ return Promise.resolve((options.load ?? load)(file, '.', asStream)).then(stream => parse(stream, { src: file, ...options }));
92
115
  }
93
116
  /**
94
117
  * parse css
95
118
  * @param stream
96
- * @param opt
119
+ * @param options
97
120
  *
98
121
  * Example:
99
122
  *
100
123
  * ```ts
101
124
  *
102
- * import {transform} from '@tbela99/css-parser';
125
+ * import {parse} from '@tbela99/css-parser';
103
126
  *
104
127
  * // css string
105
- * let result = await transform(css);
106
- * console.log(result.code);
128
+ * let result = await parse(css);
129
+ * console.log(result.ast);
107
130
  * ```
108
131
  *
109
132
  * Example using stream
@@ -116,35 +139,38 @@ async function parseFile(file, options = {}) {
116
139
  * // usage: node index.ts < styles.css or cat styles.css | node index.ts
117
140
  *
118
141
  * const readableStream = Readable.toWeb(process.stdin);
119
- * const result = await parse(readableStream, {beautify: true});
142
+ * let result = await parse(readableStream, {beautify: true});
120
143
  *
121
144
  * console.log(result.ast);
122
145
  * ```
123
146
  *
124
- * Example using fetch
147
+ * Example using fetch and readable stream
125
148
  *
126
149
  * ```ts
127
150
  *
128
151
  * import {parse} from '@tbela99/css-parser';
129
152
  *
130
153
  * const response = await fetch('https://docs.deno.com/styles.css');
131
- * result = await parse(response.body, {beautify: true});
154
+ * const result = await parse(response.body, {beautify: true});
132
155
  *
133
156
  * console.log(result.ast);
134
157
  * ```
135
158
  */
136
- async function parse(stream, opt = {}) {
159
+ async function parse(stream, options = {}) {
137
160
  return doParse(stream instanceof ReadableStream ? tokenizeStream(stream) : tokenize({
138
161
  stream,
139
162
  buffer: '',
140
163
  position: { ind: 0, lin: 1, col: 1 },
141
164
  currentPosition: { ind: -1, lin: 1, col: 0 }
142
- }), Object.assign(opt, { getStream, resolve, dirname, cwd: opt.cwd ?? process.cwd() }));
165
+ }), Object.assign(options, { load, resolve, dirname, cwd: options.cwd ?? process.cwd() }));
143
166
  }
144
167
  /**
145
168
  * transform css file
146
169
  * @param file url or path
147
170
  * @param options
171
+ * @param asStream load file as stream
172
+ *
173
+ * @throws Error file not found
148
174
  *
149
175
  * Example:
150
176
  *
@@ -161,8 +187,8 @@ async function parse(stream, opt = {}) {
161
187
  * console.log(result.code);
162
188
  * ```
163
189
  */
164
- async function transformFile(file, options = {}) {
165
- return getStream(file).then(stream => transform(stream, { src: file, ...options }));
190
+ async function transformFile(file, options = {}, asStream = false) {
191
+ return Promise.resolve((options.load ?? load)(file, '.', asStream)).then(stream => transform(stream, { src: file, ...options }));
166
192
  }
167
193
  /**
168
194
  * transform css
@@ -176,7 +202,7 @@ async function transformFile(file, options = {}) {
176
202
  * import {transform} from '@tbela99/css-parser';
177
203
  *
178
204
  * // css string
179
- * let result = await transform(css);
205
+ * const result = await transform(css);
180
206
  * console.log(result.code);
181
207
  * ```
182
208
  *
@@ -211,7 +237,8 @@ async function transform(css, options = {}) {
211
237
  options = { minify: true, removeEmpty: true, removeCharset: true, ...options };
212
238
  const startTime = performance.now();
213
239
  return parse(css, options).then((parseResult) => {
214
- const rendered = render(parseResult.ast, options);
240
+ // ast already expanded by parse
241
+ const rendered = render(parseResult.ast, { ...options, expandNestingRules: false });
215
242
  return {
216
243
  ...parseResult,
217
244
  ...rendered,
@@ -226,4 +253,4 @@ async function transform(css, options = {}) {
226
253
  });
227
254
  }
228
255
 
229
- export { dirname, getStream, parse, parseFile, render, resolve, transform, transformFile };
256
+ export { dirname, load, parse, parseFile, render, resolve, transform, transformFile };