@tbela99/css-parser 0.0.1-rc7 → 0.1.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.
@@ -1,37 +1,47 @@
1
1
  import { isPseudo, isAtKeyword, isFunction, isNumber, isDimension, parseDimension, isPercentage, isIdent, isHexColor, isHash, isIdentStart, isColor } from './utils/syntax.js';
2
+ import { EnumToken } from '../ast/types.js';
3
+ import { minify, combinators } from '../ast/minify.js';
4
+ import { walkValues } from '../ast/walk.js';
5
+ import { expand } from '../ast/expand.js';
2
6
  import { renderToken } from '../renderer/render.js';
3
7
  import { COLORS_NAMES } from '../renderer/utils/color.js';
4
- import { minify, combinators } from '../ast/minify.js';
5
8
  import { tokenize } from './tokenize.js';
6
9
 
7
10
  const urlTokenMatcher = /^(["']?)[a-zA-Z0-9_/.-][a-zA-Z0-9_/:.#?-]+(\1)$/;
8
- const trimWhiteSpace = ['Gt', 'Gte', 'Lt', 'Lte'];
9
- const funcLike = ['Start-parens', 'Func', 'UrlFunc', 'Pseudo-class-func'];
10
- /**
11
- *
12
- * @param iterator
13
- * @param opt
14
- */
15
- async function parse(iterator, opt = {}) {
16
- const startTime = performance.now();
17
- const errors = [];
18
- const options = {
11
+ const trimWhiteSpace = [EnumToken.GtTokenType, EnumToken.GteTokenType, EnumToken.LtTokenType, EnumToken.LteTokenType];
12
+ const funcLike = [EnumToken.ParensTokenType, EnumToken.StartParensTokenType, EnumToken.FunctionTokenType, EnumToken.UrlFunctionTokenType, EnumToken.PseudoClassFuncTokenType];
13
+ const BadTokensTypes = [EnumToken.BadCommentTokenType,
14
+ EnumToken.BadCdoTokenType,
15
+ EnumToken.BadUrlTokenType,
16
+ EnumToken.BadStringTokenType];
17
+ async function doParse(iterator, options = {}) {
18
+ options = {
19
19
  src: '',
20
20
  sourcemap: false,
21
21
  minify: true,
22
22
  nestingRules: false,
23
23
  resolveImport: false,
24
24
  resolveUrls: false,
25
+ removeCharset: false,
25
26
  removeEmpty: true,
26
- ...opt
27
+ removeDuplicateDeclarations: true,
28
+ computeShorthand: true,
29
+ computeCalcExpression: true,
30
+ inlineCssVariables: false,
31
+ ...options
27
32
  };
33
+ if (options.expandNestingRules) {
34
+ options.nestingRules = false;
35
+ }
28
36
  if (options.resolveImport) {
29
37
  options.resolveUrls = true;
30
38
  }
39
+ const startTime = performance.now();
40
+ const errors = [];
31
41
  const src = options.src;
32
42
  const stack = [];
33
- const ast = {
34
- typ: "StyleSheet",
43
+ let ast = {
44
+ typ: 2 /* NodeType.StyleSheetNodeType */,
35
45
  chi: []
36
46
  };
37
47
  let tokens = [];
@@ -53,10 +63,14 @@ async function parse(iterator, opt = {}) {
53
63
  let i;
54
64
  let loc;
55
65
  for (i = 0; i < tokens.length; i++) {
56
- if (tokens[i].typ == 'Comment' || tokens[i].typ == 'CDOCOMM') {
66
+ if (tokens[i].typ == EnumToken.CommentTokenType || tokens[i].typ == EnumToken.CDOCOMMTokenType) {
57
67
  const position = map.get(tokens[i]);
58
- if (tokens[i].typ == 'CDOCOMM' && context.typ != 'StyleSheet') {
59
- errors.push({ action: 'drop', message: `CDOCOMM not allowed here ${JSON.stringify(tokens[i], null, 1)}`, location: { src, ...position } });
68
+ if (tokens[i].typ == EnumToken.CDOCOMMTokenType && context.typ != 2 /* NodeType.StyleSheetNodeType */) {
69
+ errors.push({
70
+ action: 'drop',
71
+ message: `CDOCOMM not allowed here ${JSON.stringify(tokens[i], null, 1)}`,
72
+ location: { src, ...position }
73
+ });
60
74
  continue;
61
75
  }
62
76
  loc = {
@@ -69,7 +83,7 @@ async function parse(iterator, opt = {}) {
69
83
  tokens[i].loc = loc;
70
84
  }
71
85
  }
72
- else if (tokens[i].typ != 'Whitespace') {
86
+ else if (tokens[i].typ != EnumToken.WhitespaceTokenType) {
73
87
  break;
74
88
  }
75
89
  }
@@ -78,28 +92,33 @@ async function parse(iterator, opt = {}) {
78
92
  return null;
79
93
  }
80
94
  let delim = tokens.at(-1);
81
- if (delim.typ == 'Semi-colon' || delim.typ == 'Block-start' || delim.typ == 'Block-end') {
95
+ if (delim.typ == EnumToken.SemiColonTokenType || delim.typ == EnumToken.BlockStartTokenType || delim.typ == EnumToken.BlockEndTokenType) {
82
96
  tokens.pop();
83
97
  }
84
98
  else {
85
- delim = { typ: 'Semi-colon' };
99
+ delim = { typ: EnumToken.SemiColonTokenType };
86
100
  }
87
101
  // @ts-ignore
88
- while (['Whitespace', 'Bad-string', 'Bad-comment'].includes(tokens.at(-1)?.typ)) {
102
+ while ([EnumToken.WhitespaceTokenType, EnumToken.BadStringTokenType, EnumToken.BadCommentTokenType].includes(tokens.at(-1)?.typ)) {
89
103
  tokens.pop();
90
104
  }
91
105
  if (tokens.length == 0) {
92
106
  return null;
93
107
  }
94
- if (tokens[0]?.typ == 'At-rule') {
108
+ if (tokens[0]?.typ == EnumToken.AtRuleTokenType) {
95
109
  const atRule = tokens.shift();
96
110
  const position = map.get(atRule);
97
- if (atRule.val == 'charset' && position.ind > 0) {
98
- errors.push({ action: 'drop', message: 'parse: invalid @charset', location: { src, ...position } });
99
- return null;
111
+ if (atRule.val == 'charset') {
112
+ if (position.ind > 0) {
113
+ errors.push({ action: 'drop', message: 'doParse: invalid @charset', location: { src, ...position } });
114
+ return null;
115
+ }
116
+ if (options.removeCharset) {
117
+ return null;
118
+ }
100
119
  }
101
120
  // @ts-ignore
102
- while (['Whitespace'].includes(tokens[0]?.typ)) {
121
+ while ([EnumToken.WhitespaceTokenType].includes(tokens[0]?.typ)) {
103
122
  tokens.shift();
104
123
  }
105
124
  if (atRule.val == 'import') {
@@ -108,10 +127,10 @@ async function parse(iterator, opt = {}) {
108
127
  let i = context.chi.length;
109
128
  while (i--) {
110
129
  const type = context.chi[i].typ;
111
- if (type == 'Comment') {
130
+ if (type == 0 /* NodeType.CommentNodeType */) {
112
131
  continue;
113
132
  }
114
- if (type != 'AtRule') {
133
+ if (type != 3 /* NodeType.AtRuleNodeType */) {
115
134
  errors.push({ action: 'drop', message: 'invalid @import', location: { src, ...position } });
116
135
  return null;
117
136
  }
@@ -124,33 +143,33 @@ async function parse(iterator, opt = {}) {
124
143
  }
125
144
  }
126
145
  // @ts-ignore
127
- if (tokens[0]?.typ != 'String' && tokens[0]?.typ != 'UrlFunc') {
128
- errors.push({ action: 'drop', message: 'parse: invalid @import', location: { src, ...position } });
146
+ if (tokens[0]?.typ != EnumToken.StringTokenType && tokens[0]?.typ != EnumToken.UrlFunctionTokenType) {
147
+ errors.push({ action: 'drop', message: 'doParse: invalid @import', location: { src, ...position } });
129
148
  return null;
130
149
  }
131
150
  // @ts-ignore
132
- if (tokens[0].typ == 'UrlFunc' && tokens[1]?.typ != 'Url-token' && tokens[1]?.typ != 'String') {
133
- errors.push({ action: 'drop', message: 'parse: invalid @import', location: { src, ...position } });
151
+ if (tokens[0].typ == EnumToken.UrlFunctionTokenType && tokens[1]?.typ != EnumToken.UrlTokenTokenType && tokens[1]?.typ != EnumToken.StringTokenType) {
152
+ errors.push({ action: 'drop', message: 'doParse: invalid @import', location: { src, ...position } });
134
153
  return null;
135
154
  }
136
155
  }
137
156
  if (atRule.val == 'import') {
138
157
  // @ts-ignore
139
- if (tokens[0].typ == 'UrlFunc' && tokens[1].typ == 'Url-token') {
158
+ if (tokens[0].typ == EnumToken.UrlFunctionTokenType && tokens[1].typ == EnumToken.UrlTokenTokenType) {
140
159
  tokens.shift();
141
160
  // @ts-ignore
142
- tokens[0].typ = 'String';
161
+ tokens[0].typ = EnumToken.StringTokenType;
143
162
  // @ts-ignore
144
163
  tokens[0].val = `"${tokens[0].val}"`;
145
164
  }
146
165
  // @ts-ignore
147
- if (tokens[0].typ == 'String') {
166
+ if (tokens[0].typ == EnumToken.StringTokenType) {
148
167
  if (options.resolveImport) {
149
168
  const url = tokens[0].val.slice(1, -1);
150
169
  try {
151
170
  // @ts-ignore
152
171
  const root = await options.load(url, options.src).then((src) => {
153
- return parse(src, Object.assign({}, options, {
172
+ return doParse(src, Object.assign({}, options, {
154
173
  minify: false,
155
174
  // @ts-ignore
156
175
  src: options.resolve(url, options.src).absolute
@@ -158,6 +177,7 @@ async function parse(iterator, opt = {}) {
158
177
  });
159
178
  bytesIn += root.stats.bytesIn;
160
179
  if (root.ast.chi.length > 0) {
180
+ // @todo - filter charset, layer and scope
161
181
  context.chi.push(...root.ast.chi);
162
182
  }
163
183
  if (root.errors.length > 0) {
@@ -167,7 +187,7 @@ async function parse(iterator, opt = {}) {
167
187
  }
168
188
  catch (error) {
169
189
  // @ts-ignore
170
- errors.push({ action: 'ignore', message: 'parse: ' + error.message, error });
190
+ errors.push({ action: 'ignore', message: 'doParse: ' + error.message, error });
171
191
  }
172
192
  }
173
193
  }
@@ -180,12 +200,12 @@ async function parse(iterator, opt = {}) {
180
200
  return acc;
181
201
  }, []);
182
202
  const node = {
183
- typ: 'AtRule',
203
+ typ: 3 /* NodeType.AtRuleNodeType */,
184
204
  nam: renderToken(atRule, { removeComments: true }),
185
205
  val: raw.join('')
186
206
  };
187
207
  Object.defineProperty(node, 'raw', { enumerable: false, configurable: true, writable: true, value: raw });
188
- if (delim.typ == 'Block-start') {
208
+ if (delim.typ == EnumToken.BlockStartTokenType) {
189
209
  node.chi = [];
190
210
  }
191
211
  loc = {
@@ -197,15 +217,15 @@ async function parse(iterator, opt = {}) {
197
217
  }
198
218
  // @ts-ignore
199
219
  context.chi.push(node);
200
- return delim.typ == 'Block-start' ? node : null;
220
+ return delim.typ == EnumToken.BlockStartTokenType ? node : null;
201
221
  }
202
222
  else {
203
223
  // rule
204
- if (delim.typ == 'Block-start') {
224
+ if (delim.typ == EnumToken.BlockStartTokenType) {
205
225
  const position = map.get(tokens[0]);
206
226
  const uniq = new Map;
207
227
  parseTokens(tokens, { minify: true }).reduce((acc, curr, index, array) => {
208
- if (curr.typ == 'Whitespace') {
228
+ if (curr.typ == EnumToken.WhitespaceTokenType) {
209
229
  if (trimWhiteSpace.includes(array[index - 1]?.typ) ||
210
230
  trimWhiteSpace.includes(array[index + 1]?.typ) ||
211
231
  combinators.includes(array[index - 1]?.val) ||
@@ -226,7 +246,7 @@ async function parse(iterator, opt = {}) {
226
246
  return acc;
227
247
  }, uniq);
228
248
  const node = {
229
- typ: 'Rule',
249
+ typ: 4 /* NodeType.RuleNodeType */,
230
250
  // @ts-ignore
231
251
  sel: [...uniq.keys()].join(','),
232
252
  chi: []
@@ -251,10 +271,10 @@ async function parse(iterator, opt = {}) {
251
271
  // @ts-ignore
252
272
  let value = null;
253
273
  for (let i = 0; i < tokens.length; i++) {
254
- if (tokens[i].typ == 'Comment') {
274
+ if (tokens[i].typ == EnumToken.CommentTokenType) {
255
275
  continue;
256
276
  }
257
- if (tokens[i].typ == 'Colon') {
277
+ if (tokens[i].typ == EnumToken.ColonTokenType) {
258
278
  name = tokens.slice(0, i);
259
279
  value = parseTokens(tokens.slice(i + 1), {
260
280
  parseColor: true,
@@ -271,46 +291,38 @@ async function parse(iterator, opt = {}) {
271
291
  const position = map.get(name[0]);
272
292
  if (name.length > 0) {
273
293
  for (let i = 1; i < name.length; i++) {
274
- if (name[i].typ != 'Whitespace' && name[i].typ != 'Comment') {
294
+ if (name[i].typ != EnumToken.WhitespaceTokenType && name[i].typ != EnumToken.CommentTokenType) {
275
295
  errors.push({
276
296
  action: 'drop',
277
- message: 'parse: invalid declaration',
297
+ message: 'doParse: invalid declaration',
278
298
  location: { src, ...position }
279
299
  });
280
300
  return null;
281
301
  }
282
302
  }
283
303
  }
284
- if (value == null) {
285
- errors.push({
286
- action: 'drop',
287
- message: 'parse: invalid declaration',
288
- location: { src, ...position }
289
- });
290
- return null;
291
- }
292
- if (value.length == 0) {
304
+ if (value == null || value.length == 0) {
293
305
  errors.push({
294
306
  action: 'drop',
295
- message: 'parse: invalid declaration',
307
+ message: 'doParse: invalid declaration',
296
308
  location: { src, ...position }
297
309
  });
298
310
  return null;
299
311
  }
300
312
  const node = {
301
- typ: 'Declaration',
313
+ typ: 5 /* NodeType.DeclarationNodeType */,
302
314
  // @ts-ignore
303
315
  nam: renderToken(name.shift(), { removeComments: true }),
304
316
  // @ts-ignore
305
317
  val: value
306
318
  };
307
- while (node.val[0]?.typ == 'Whitespace') {
319
+ while (node.val[0]?.typ == EnumToken.WhitespaceTokenType) {
308
320
  node.val.shift();
309
321
  }
310
322
  if (node.val.length == 0) {
311
323
  errors.push({
312
324
  action: 'drop',
313
- message: 'parse: invalid declaration',
325
+ message: 'doParse: invalid declaration',
314
326
  location: { src, ...position }
315
327
  });
316
328
  return null;
@@ -330,8 +342,8 @@ async function parse(iterator, opt = {}) {
330
342
  let item;
331
343
  while (item = iter.next().value) {
332
344
  bytesIn = item.bytesIn;
333
- // parse error
334
- if (item.hint != null && item.hint.startsWith('Bad-')) {
345
+ // doParse error
346
+ if (item.hint != null && BadTokensTypes.includes(item.hint)) {
335
347
  // bad token
336
348
  continue;
337
349
  }
@@ -391,9 +403,12 @@ async function parse(iterator, opt = {}) {
391
403
  break;
392
404
  }
393
405
  const endParseTime = performance.now();
406
+ if (options.expandNestingRules) {
407
+ ast = expand(ast);
408
+ }
394
409
  if (options.minify) {
395
410
  if (ast.chi.length > 0) {
396
- minify(ast, options, true, errors);
411
+ minify(ast, options, true, errors, false);
397
412
  }
398
413
  }
399
414
  const endTime = performance.now();
@@ -423,81 +438,81 @@ function getTokenType(val, hint) {
423
438
  }
424
439
  if (hint != null) {
425
440
  return ([
426
- 'Whitespace', 'Semi-colon', 'Colon', 'Block-start',
427
- 'Block-start', 'Attr-start', 'Attr-end', 'Start-parens', 'End-parens',
428
- 'Comma', 'Gt', 'Lt', 'Gte', 'Lte', 'EOF'
441
+ EnumToken.WhitespaceTokenType, EnumToken.SemiColonTokenType, EnumToken.ColonTokenType, EnumToken.BlockStartTokenType,
442
+ EnumToken.BlockStartTokenType, EnumToken.AttrStartTokenType, EnumToken.AttrEndTokenType, EnumToken.StartParensTokenType, EnumToken.EndParensTokenType,
443
+ EnumToken.CommaTokenType, EnumToken.GtTokenType, EnumToken.LtTokenType, EnumToken.GteTokenType, EnumToken.LteTokenType, EnumToken.EOFTokenType
429
444
  ].includes(hint) ? { typ: hint } : { typ: hint, val });
430
445
  }
431
446
  if (val == ' ') {
432
- return { typ: 'Whitespace' };
447
+ return { typ: EnumToken.WhitespaceTokenType };
433
448
  }
434
449
  if (val == ';') {
435
- return { typ: 'Semi-colon' };
450
+ return { typ: EnumToken.SemiColonTokenType };
436
451
  }
437
452
  if (val == '{') {
438
- return { typ: 'Block-start' };
453
+ return { typ: EnumToken.BlockStartTokenType };
439
454
  }
440
455
  if (val == '}') {
441
- return { typ: 'Block-end' };
456
+ return { typ: EnumToken.BlockEndTokenType };
442
457
  }
443
458
  if (val == '[') {
444
- return { typ: 'Attr-start' };
459
+ return { typ: EnumToken.AttrStartTokenType };
445
460
  }
446
461
  if (val == ']') {
447
- return { typ: 'Attr-end' };
462
+ return { typ: EnumToken.AttrEndTokenType };
448
463
  }
449
464
  if (val == ':') {
450
- return { typ: 'Colon' };
465
+ return { typ: EnumToken.ColonTokenType };
451
466
  }
452
467
  if (val == ')') {
453
- return { typ: 'End-parens' };
468
+ return { typ: EnumToken.EndParensTokenType };
454
469
  }
455
470
  if (val == '(') {
456
- return { typ: 'Start-parens' };
471
+ return { typ: EnumToken.StartParensTokenType };
457
472
  }
458
473
  if (val == '=') {
459
- return { typ: 'Delim', val };
474
+ return { typ: EnumToken.DelimTokenType, val };
460
475
  }
461
476
  if (val == ';') {
462
- return { typ: 'Semi-colon' };
477
+ return { typ: EnumToken.SemiColonTokenType };
463
478
  }
464
479
  if (val == ',') {
465
- return { typ: 'Comma' };
480
+ return { typ: EnumToken.CommaTokenType };
466
481
  }
467
482
  if (val == '<') {
468
- return { typ: 'Lt' };
483
+ return { typ: EnumToken.LtTokenType };
469
484
  }
470
485
  if (val == '>') {
471
- return { typ: 'Gt' };
486
+ return { typ: EnumToken.GtTokenType };
472
487
  }
473
488
  if (isPseudo(val)) {
474
489
  return val.endsWith('(') ? {
475
- typ: 'Pseudo-class-func',
490
+ typ: EnumToken.PseudoClassFuncTokenType,
476
491
  val: val.slice(0, -1),
477
492
  chi: []
478
493
  }
479
494
  : {
480
- typ: 'Pseudo-class',
495
+ typ: EnumToken.PseudoClassTokenType,
481
496
  val
482
497
  };
483
498
  }
484
499
  if (isAtKeyword(val)) {
485
500
  return {
486
- typ: 'At-rule',
501
+ typ: EnumToken.AtRuleTokenType,
487
502
  val: val.slice(1)
488
503
  };
489
504
  }
490
505
  if (isFunction(val)) {
491
506
  val = val.slice(0, -1);
492
507
  return {
493
- typ: val == 'url' ? 'UrlFunc' : 'Func',
508
+ typ: val == 'url' ? EnumToken.UrlFunctionTokenType : EnumToken.FunctionTokenType,
494
509
  val,
495
510
  chi: []
496
511
  };
497
512
  }
498
513
  if (isNumber(val)) {
499
514
  return {
500
- typ: 'Number',
515
+ typ: EnumToken.NumberTokenType,
501
516
  val
502
517
  };
503
518
  }
@@ -506,97 +521,97 @@ function getTokenType(val, hint) {
506
521
  }
507
522
  if (isPercentage(val)) {
508
523
  return {
509
- typ: 'Perc',
524
+ typ: EnumToken.PercentageTokenType,
510
525
  val: val.slice(0, -1)
511
526
  };
512
527
  }
513
528
  const v = val.toLowerCase();
514
529
  if (v == 'currentcolor' || val == 'transparent' || v in COLORS_NAMES) {
515
530
  return {
516
- typ: 'Color',
531
+ typ: EnumToken.ColorTokenType,
517
532
  val,
518
533
  kin: 'lit'
519
534
  };
520
535
  }
521
536
  if (isIdent(val)) {
522
537
  return {
523
- typ: 'Iden',
538
+ typ: EnumToken.IdenTokenType,
524
539
  val
525
540
  };
526
541
  }
527
542
  if (val.charAt(0) == '#' && isHexColor(val)) {
528
543
  return {
529
- typ: 'Color',
544
+ typ: EnumToken.ColorTokenType,
530
545
  val,
531
546
  kin: 'hex'
532
547
  };
533
548
  }
534
549
  if (val.charAt(0) == '#' && isHash(val)) {
535
550
  return {
536
- typ: 'Hash',
551
+ typ: EnumToken.HashTokenType,
537
552
  val
538
553
  };
539
554
  }
540
555
  if ('"\''.includes(val.charAt(0))) {
541
556
  return {
542
- typ: 'Unclosed-string',
557
+ typ: EnumToken.UnclosedStringTokenType,
543
558
  val
544
559
  };
545
560
  }
546
561
  return {
547
- typ: 'Literal',
562
+ typ: EnumToken.LiteralTokenType,
548
563
  val
549
564
  };
550
565
  }
551
566
  function parseTokens(tokens, options = {}) {
552
567
  for (let i = 0; i < tokens.length; i++) {
553
568
  const t = tokens[i];
554
- if (t.typ == 'Whitespace' && ((i == 0 ||
569
+ if (t.typ == EnumToken.WhitespaceTokenType && ((i == 0 ||
555
570
  i + 1 == tokens.length ||
556
- ['Comma', 'Gte', 'Lte'].includes(tokens[i + 1].typ)) ||
571
+ [EnumToken.CommaTokenType, EnumToken.GteTokenType, EnumToken.LteTokenType].includes(tokens[i + 1].typ)) ||
557
572
  (i > 0 &&
558
- // tokens[i + 1]?.typ != 'Literal' ||
573
+ // tokens[i + 1]?.typ != Literal ||
559
574
  // funcLike.includes(tokens[i - 1].typ) &&
560
575
  // !['var', 'calc'].includes((<FunctionToken>tokens[i - 1]).val)))) &&
561
576
  trimWhiteSpace.includes(tokens[i - 1].typ)))) {
562
577
  tokens.splice(i--, 1);
563
578
  continue;
564
579
  }
565
- if (t.typ == 'Colon') {
580
+ if (t.typ == EnumToken.ColonTokenType) {
566
581
  const typ = tokens[i + 1]?.typ;
567
582
  if (typ != null) {
568
- if (typ == 'Func') {
583
+ if (typ == EnumToken.FunctionTokenType) {
569
584
  tokens[i + 1].val = ':' + tokens[i + 1].val;
570
- tokens[i + 1].typ = 'Pseudo-class-func';
585
+ tokens[i + 1].typ = EnumToken.PseudoClassFuncTokenType;
571
586
  }
572
- else if (typ == 'Iden') {
587
+ else if (typ == EnumToken.IdenTokenType) {
573
588
  tokens[i + 1].val = ':' + tokens[i + 1].val;
574
- tokens[i + 1].typ = 'Pseudo-class';
589
+ tokens[i + 1].typ = EnumToken.PseudoClassTokenType;
575
590
  }
576
- if (typ == 'Func' || typ == 'Iden') {
591
+ if (typ == EnumToken.FunctionTokenType || typ == EnumToken.IdenTokenType) {
577
592
  tokens.splice(i, 1);
578
593
  i--;
579
594
  continue;
580
595
  }
581
596
  }
582
597
  }
583
- if (t.typ == 'Attr-start') {
598
+ if (t.typ == EnumToken.AttrStartTokenType) {
584
599
  let k = i;
585
600
  let inAttr = 1;
586
601
  while (++k < tokens.length) {
587
- if (tokens[k].typ == 'Attr-end') {
602
+ if (tokens[k].typ == EnumToken.AttrEndTokenType) {
588
603
  inAttr--;
589
604
  }
590
- else if (tokens[k].typ == 'Attr-start') {
605
+ else if (tokens[k].typ == EnumToken.AttrStartTokenType) {
591
606
  inAttr++;
592
607
  }
593
608
  if (inAttr == 0) {
594
609
  break;
595
610
  }
596
611
  }
597
- Object.assign(t, { typ: 'Attr', chi: tokens.splice(i + 1, k - i) });
612
+ Object.assign(t, { typ: EnumToken.AttrTokenType, chi: tokens.splice(i + 1, k - i) });
598
613
  // @ts-ignore
599
- if (t.chi.at(-1).typ == 'Attr-end') {
614
+ if (t.chi.at(-1).typ == EnumToken.AttrEndTokenType) {
600
615
  // @ts-ignore
601
616
  t.chi.pop();
602
617
  // @ts-ignore
@@ -607,10 +622,10 @@ function parseTokens(tokens, options = {}) {
607
622
  }
608
623
  // @ts-ignore
609
624
  t.chi.forEach(val => {
610
- if (val.typ == 'String') {
625
+ if (val.typ == EnumToken.StringTokenType) {
611
626
  const slice = val.val.slice(1, -1);
612
627
  if ((slice.charAt(0) != '-' || (slice.charAt(0) == '-' && isIdentStart(slice.charCodeAt(1)))) && isIdent(slice)) {
613
- Object.assign(val, { typ: 'Iden', val: slice });
628
+ Object.assign(val, { typ: EnumToken.IdenTokenType, val: slice });
614
629
  }
615
630
  }
616
631
  });
@@ -621,18 +636,18 @@ function parseTokens(tokens, options = {}) {
621
636
  let parens = 1;
622
637
  let k = i;
623
638
  while (++k < tokens.length) {
624
- if (tokens[k].typ == 'Colon') {
639
+ if (tokens[k].typ == EnumToken.ColonTokenType) {
625
640
  const typ = tokens[k + 1]?.typ;
626
641
  if (typ != null) {
627
- if (typ == 'Iden') {
628
- tokens[k + 1].typ = 'Pseudo-class';
642
+ if (typ == EnumToken.IdenTokenType) {
643
+ tokens[k + 1].typ = EnumToken.PseudoClassTokenType;
629
644
  tokens[k + 1].val = ':' + tokens[k + 1].val;
630
645
  }
631
- else if (typ == 'Func') {
632
- tokens[k + 1].typ = 'Pseudo-class-func';
646
+ else if (typ == EnumToken.FunctionTokenType) {
647
+ tokens[k + 1].typ = EnumToken.PseudoClassFuncTokenType;
633
648
  tokens[k + 1].val = ':' + tokens[k + 1].val;
634
649
  }
635
- if (typ == 'Func' || typ == 'Iden') {
650
+ if (typ == EnumToken.FunctionTokenType || typ == EnumToken.IdenTokenType) {
636
651
  tokens.splice(k, 1);
637
652
  k--;
638
653
  continue;
@@ -642,7 +657,7 @@ function parseTokens(tokens, options = {}) {
642
657
  if (funcLike.includes(tokens[k].typ)) {
643
658
  parens++;
644
659
  }
645
- else if (tokens[k].typ == 'End-parens') {
660
+ else if (tokens[k].typ == EnumToken.EndParensTokenType) {
646
661
  parens--;
647
662
  }
648
663
  if (parens == 0) {
@@ -652,29 +667,55 @@ function parseTokens(tokens, options = {}) {
652
667
  // @ts-ignore
653
668
  t.chi = tokens.splice(i + 1, k - i);
654
669
  // @ts-ignore
655
- if (t.chi.at(-1)?.typ == 'End-parens') {
670
+ if (t.chi.at(-1)?.typ == EnumToken.EndParensTokenType) {
656
671
  // @ts-ignore
657
672
  t.chi.pop();
658
673
  }
674
+ if (t.typ == EnumToken.FunctionTokenType && t.val == 'calc') {
675
+ for (const { value, parent } of walkValues(t.chi)) {
676
+ if (value.typ == EnumToken.WhitespaceTokenType) {
677
+ const p = (parent ?? t);
678
+ for (let i = 0; i < (p).chi.length; i++) {
679
+ // @ts-ignore
680
+ if (p.chi[i] == value) {
681
+ // @ts-ignore
682
+ (p).chi.splice(i, 1);
683
+ i--;
684
+ break;
685
+ }
686
+ }
687
+ }
688
+ else if (value.typ == EnumToken.LiteralTokenType && ['+', '-', '/', '*'].includes(value.val)) {
689
+ // @ts-ignore
690
+ value.typ = value.val == '+' ? EnumToken.Add : (value.val == '-' ? EnumToken.Sub : (value.val == '*' ? EnumToken.Mul : EnumToken.Div));
691
+ // @ts-ignore
692
+ delete value.val;
693
+ }
694
+ }
695
+ }
696
+ else if (t.typ == EnumToken.StartParensTokenType) {
697
+ // @ts-ignore
698
+ t.typ = EnumToken.ParensTokenType;
699
+ }
659
700
  // @ts-ignore
660
- if (options.parseColor && t.typ == 'Func' && isColor(t)) {
701
+ if (options.parseColor && t.typ == EnumToken.FunctionTokenType && isColor(t)) {
661
702
  // if (isColor) {
662
703
  // @ts-ignore
663
- t.typ = 'Color';
704
+ t.typ = EnumToken.ColorTokenType;
664
705
  // @ts-ignore
665
706
  t.kin = t.val;
666
707
  // @ts-ignore
667
708
  let m = t.chi.length;
668
709
  while (m-- > 0) {
669
710
  // @ts-ignore
670
- if (['Literal'].concat(trimWhiteSpace).includes(t.chi[m].typ)) {
711
+ if ([EnumToken.LiteralTokenType].concat(trimWhiteSpace).includes(t.chi[m].typ)) {
671
712
  // @ts-ignore
672
- if (t.chi[m + 1]?.typ == 'Whitespace') {
713
+ if (t.chi[m + 1]?.typ == EnumToken.WhitespaceTokenType) {
673
714
  // @ts-ignore
674
715
  t.chi.splice(m + 1, 1);
675
716
  }
676
717
  // @ts-ignore
677
- if (t.chi[m - 1]?.typ == 'Whitespace') {
718
+ if (t.chi[m - 1]?.typ == EnumToken.WhitespaceTokenType) {
678
719
  // @ts-ignore
679
720
  t.chi.splice(m - 1, 1);
680
721
  m--;
@@ -682,22 +723,21 @@ function parseTokens(tokens, options = {}) {
682
723
  }
683
724
  }
684
725
  continue;
685
- // }
686
726
  }
687
- if (t.typ == 'UrlFunc') {
727
+ if (t.typ == EnumToken.UrlFunctionTokenType) {
688
728
  // @ts-ignore
689
- if (t.chi[0]?.typ == 'String') {
729
+ if (t.chi[0]?.typ == EnumToken.StringTokenType) {
690
730
  // @ts-ignore
691
731
  const value = t.chi[0].val.slice(1, -1);
692
732
  // @ts-ignore
693
733
  if (t.chi[0].val.slice(1, 5) != 'data:' && urlTokenMatcher.test(value)) {
694
734
  // @ts-ignore
695
- t.chi[0].typ = 'Url-token';
735
+ t.chi[0].typ = EnumToken.UrlTokenTokenType;
696
736
  // @ts-ignore
697
737
  t.chi[0].val = options.src !== '' && options.resolveUrls ? options.resolve(value, options.src).absolute : value;
698
738
  }
699
739
  }
700
- if (t.chi[0]?.typ == 'Url-token') {
740
+ if (t.chi[0]?.typ == EnumToken.UrlTokenTokenType) {
701
741
  if (options.src !== '' && options.resolveUrls) {
702
742
  // @ts-ignore
703
743
  t.chi[0].val = options.resolve(t.chi[0].val, options.src, options.cwd).relative;
@@ -708,13 +748,13 @@ function parseTokens(tokens, options = {}) {
708
748
  if (t.chi.length > 0) {
709
749
  // @ts-ignore
710
750
  parseTokens(t.chi, options);
711
- if (t.typ == 'Pseudo-class-func' && t.val == ':is' && options.minify) {
751
+ if (t.typ == EnumToken.PseudoClassFuncTokenType && t.val == ':is' && options.minify) {
712
752
  //
713
- const count = t.chi.filter(t => t.typ != 'Comment').length;
753
+ const count = t.chi.filter(t => t.typ != EnumToken.CommentTokenType).length;
714
754
  if (count == 1 ||
715
755
  (i == 0 &&
716
- (tokens[i + 1]?.typ == 'Comma' || tokens.length == i + 1)) ||
717
- (tokens[i - 1]?.typ == 'Comma' && (tokens[i + 1]?.typ == 'Comma' || tokens.length == i + 1))) {
756
+ (tokens[i + 1]?.typ == EnumToken.CommaTokenType || tokens.length == i + 1)) ||
757
+ (tokens[i - 1]?.typ == EnumToken.CommaTokenType && (tokens[i + 1]?.typ == EnumToken.CommaTokenType || tokens.length == i + 1))) {
718
758
  tokens.splice(i, 1, ...t.chi);
719
759
  i = Math.max(0, i - t.chi.length);
720
760
  }
@@ -723,22 +763,22 @@ function parseTokens(tokens, options = {}) {
723
763
  continue;
724
764
  }
725
765
  if (options.parseColor) {
726
- if (t.typ == 'Iden') {
766
+ if (t.typ == EnumToken.IdenTokenType) {
727
767
  // named color
728
768
  const value = t.val.toLowerCase();
729
769
  if (value in COLORS_NAMES) {
730
770
  Object.assign(t, {
731
- typ: 'Color',
771
+ typ: EnumToken.ColorTokenType,
732
772
  val: COLORS_NAMES[value].length < value.length ? COLORS_NAMES[value] : value,
733
773
  kin: 'hex'
734
774
  });
735
775
  }
736
776
  continue;
737
777
  }
738
- if (t.typ == 'Hash' && isHexColor(t.val)) {
778
+ if (t.typ == EnumToken.HashTokenType && isHexColor(t.val)) {
739
779
  // hex color
740
780
  // @ts-ignore
741
- t.typ = 'Color';
781
+ t.typ = EnumToken.ColorTokenType;
742
782
  // @ts-ignore
743
783
  t.kin = 'hex';
744
784
  }
@@ -747,4 +787,4 @@ function parseTokens(tokens, options = {}) {
747
787
  return tokens;
748
788
  }
749
789
 
750
- export { parse, parseString, urlTokenMatcher };
790
+ export { doParse, parseString, parseTokens, urlTokenMatcher };