@tbela99/css-parser 0.0.1-alpha5 → 0.0.1-rc2

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,16 +1,23 @@
1
- import { isWhiteSpace, isDigit, isPseudo, isAtKeyword, isFunction, isNumber, isDimension, parseDimension, isPercentage, isIdent, isHash, isNewLine, isIdentStart, isHexColor } from './utils/syntax.js';
1
+ import { isPseudo, isAtKeyword, isFunction, isNumber, isDimension, parseDimension, isPercentage, isIdent, isHexColor, isHash, isIdentStart } from './utils/syntax.js';
2
2
  import { renderToken } from '../renderer/render.js';
3
3
  import { COLORS_NAMES } from '../renderer/utils/color.js';
4
- import { deduplicate } from './deduplicate.js';
4
+ import { minify, combinators } from '../ast/minify.js';
5
+ import { tokenize } from './tokenize.js';
5
6
 
6
7
  const urlTokenMatcher = /^(["']?)[a-zA-Z0-9_/.-][a-zA-Z0-9_/:.#?-]+(\1)$/;
7
8
  const funcLike = ['Start-parens', 'Func', 'UrlFunc', 'Pseudo-class-func'];
9
+ /**
10
+ *
11
+ * @param iterator
12
+ * @param opt
13
+ */
8
14
  async function parse(iterator, opt = {}) {
15
+ const startTime = performance.now();
9
16
  const errors = [];
10
17
  const options = {
11
18
  src: '',
12
19
  sourcemap: false,
13
- compress: false,
20
+ minify: true,
14
21
  nestingRules: false,
15
22
  resolveImport: false,
16
23
  resolveUrls: false,
@@ -20,172 +27,28 @@ async function parse(iterator, opt = {}) {
20
27
  if (options.resolveImport) {
21
28
  options.resolveUrls = true;
22
29
  }
23
- let ind = -1;
24
- let lin = 1;
25
- let col = 0;
26
- const tokens = [];
27
30
  const src = options.src;
28
31
  const stack = [];
29
32
  const ast = {
30
33
  typ: "StyleSheet",
31
34
  chi: []
32
35
  };
33
- const position = {
34
- ind: Math.max(ind, 0),
35
- lin: lin,
36
- col: Math.max(col, 1)
37
- };
38
- let value;
39
- let buffer = '';
40
- let total = iterator.length;
41
- let bytesIn = total;
36
+ let tokens = [];
42
37
  let map = new Map;
38
+ let bytesIn = 0;
43
39
  let context = ast;
44
40
  if (options.sourcemap) {
45
41
  ast.loc = {
46
42
  sta: {
47
- ind: ind,
48
- lin: lin,
49
- col: col
43
+ ind: 0,
44
+ lin: 1,
45
+ col: 1
50
46
  },
51
47
  src: ''
52
48
  };
53
49
  }
54
- function getType(val) {
55
- if (val === '') {
56
- throw new Error('empty string?');
57
- }
58
- if (val == ':') {
59
- return { typ: 'Colon' };
60
- }
61
- if (val == ')') {
62
- return { typ: 'End-parens' };
63
- }
64
- if (val == '(') {
65
- return { typ: 'Start-parens' };
66
- }
67
- if (val == '=') {
68
- return { typ: 'Delim', val };
69
- }
70
- if (val == ';') {
71
- return { typ: 'Semi-colon' };
72
- }
73
- if (val == ',') {
74
- return { typ: 'Comma' };
75
- }
76
- if (val == '<') {
77
- return { typ: 'Lt' };
78
- }
79
- if (val == '>') {
80
- return { typ: 'Gt' };
81
- }
82
- if (isPseudo(val)) {
83
- return val.endsWith('(') ? {
84
- typ: 'Pseudo-class-func',
85
- val: val.slice(0, -1),
86
- chi: []
87
- }
88
- : {
89
- typ: 'Pseudo-class',
90
- val
91
- };
92
- }
93
- if (isAtKeyword(val)) {
94
- return {
95
- typ: 'At-rule',
96
- val: val.slice(1)
97
- };
98
- }
99
- if (isFunction(val)) {
100
- val = val.slice(0, -1);
101
- return {
102
- typ: val == 'url' ? 'UrlFunc' : 'Func',
103
- val,
104
- chi: []
105
- };
106
- }
107
- if (isNumber(val)) {
108
- return {
109
- typ: 'Number',
110
- val
111
- };
112
- }
113
- if (isDimension(val)) {
114
- return parseDimension(val);
115
- }
116
- if (isPercentage(val)) {
117
- return {
118
- typ: 'Perc',
119
- val: val.slice(0, -1)
120
- };
121
- }
122
- if (val == 'currentColor') {
123
- return {
124
- typ: 'Color',
125
- val,
126
- kin: 'lit'
127
- };
128
- }
129
- if (isIdent(val)) {
130
- return {
131
- typ: 'Iden',
132
- val
133
- };
134
- }
135
- if (val.charAt(0) == '#' && isHash(val)) {
136
- return {
137
- typ: 'Hash',
138
- val
139
- };
140
- }
141
- if ('"\''.includes(val.charAt(0))) {
142
- return {
143
- typ: 'Unclosed-string',
144
- val
145
- };
146
- }
147
- return {
148
- typ: 'Literal',
149
- val
150
- };
151
- }
152
- // consume and throw away
153
- function consume(open, close) {
154
- let count = 1;
155
- let chr;
156
- while (true) {
157
- chr = next();
158
- if (chr == '\\') {
159
- if (peek() === '') {
160
- break;
161
- }
162
- continue;
163
- }
164
- else if (chr == '/' && peek() == '*') {
165
- next();
166
- while (true) {
167
- chr = next();
168
- if (chr === '') {
169
- break;
170
- }
171
- if (chr == '*' && peek() == '/') {
172
- next();
173
- break;
174
- }
175
- }
176
- }
177
- else if (chr == close) {
178
- count--;
179
- }
180
- else if (chr == open) {
181
- count++;
182
- }
183
- if (chr === '' || count == 0) {
184
- break;
185
- }
186
- }
187
- }
188
- async function parseNode(tokens) {
50
+ async function parseNode(results) {
51
+ let tokens = results.map(mapToken);
189
52
  let i;
190
53
  let loc;
191
54
  for (i = 0; i < tokens.length; i++) {
@@ -283,12 +146,12 @@ async function parse(iterator, opt = {}) {
283
146
  // @ts-ignore
284
147
  const root = await options.load(url, options.src).then((src) => {
285
148
  return parse(src, Object.assign({}, options, {
286
- compress: false,
149
+ minify: false,
287
150
  // @ts-ignore
288
151
  src: options.resolve(url, options.src).absolute
289
152
  }));
290
153
  });
291
- bytesIn += root.bytesIn;
154
+ bytesIn += root.stats.bytesIn;
292
155
  if (root.ast.chi.length > 0) {
293
156
  context.chi.push(...root.ast.chi);
294
157
  }
@@ -334,20 +197,17 @@ async function parse(iterator, opt = {}) {
334
197
  // rule
335
198
  if (delim.typ == 'Block-start') {
336
199
  const position = map.get(tokens[0]);
337
- if (context.typ == 'Rule') {
338
- if (tokens[0]?.typ == 'Iden') {
339
- errors.push({ action: 'drop', message: 'invalid nesting rule', location: { src, ...position } });
340
- return null;
341
- }
342
- }
343
200
  const uniq = new Map;
344
- parseTokens(tokens, 'Rule', { compress: options.compress }).reduce((acc, curr, index, array) => {
201
+ parseTokens(tokens, { minify: options.minify }).reduce((acc, curr, index, array) => {
345
202
  if (curr.typ == 'Whitespace') {
346
- if (array[index - 1]?.val == '+' || array[index + 1]?.val == '+') {
203
+ if (array[index - 1]?.typ == 'Gt' ||
204
+ array[index + 1]?.typ == 'Gt' ||
205
+ combinators.includes(array[index - 1]?.val) ||
206
+ combinators.includes(array[index + 1]?.val)) {
347
207
  return acc;
348
208
  }
349
209
  }
350
- let t = renderToken(curr, { compress: true });
210
+ let t = renderToken(curr, { minify: true });
351
211
  if (t == ',') {
352
212
  acc.push([]);
353
213
  }
@@ -390,7 +250,7 @@ async function parse(iterator, opt = {}) {
390
250
  }
391
251
  if (tokens[i].typ == 'Colon') {
392
252
  name = tokens.slice(0, i);
393
- value = parseTokens(tokens.slice(i + 1), 'Declaration', {
253
+ value = parseTokens(tokens.slice(i + 1), {
394
254
  parseColor: true,
395
255
  src: options.src,
396
256
  resolveUrls: options.resolveUrls,
@@ -416,11 +276,19 @@ async function parse(iterator, opt = {}) {
416
276
  }
417
277
  }
418
278
  if (value == null) {
419
- errors.push({ action: 'drop', message: 'invalid declaration', location: { src, ...position } });
279
+ errors.push({
280
+ action: 'drop',
281
+ message: 'invalid declaration',
282
+ location: { src, ...position }
283
+ });
420
284
  return null;
421
285
  }
422
286
  if (value.length == 0) {
423
- errors.push({ action: 'drop', message: 'invalid declaration', location: { src, ...position } });
287
+ errors.push({
288
+ action: 'drop',
289
+ message: 'invalid declaration',
290
+ location: { src, ...position }
291
+ });
424
292
  return null;
425
293
  }
426
294
  const node = {
@@ -434,7 +302,11 @@ async function parse(iterator, opt = {}) {
434
302
  node.val.shift();
435
303
  }
436
304
  if (node.val.length == 0) {
437
- errors.push({ action: 'drop', message: 'invalid declaration', location: { src, ...position } });
305
+ errors.push({
306
+ action: 'drop',
307
+ message: 'invalid declaration',
308
+ location: { src, ...position }
309
+ });
438
310
  return null;
439
311
  }
440
312
  // @ts-ignore
@@ -443,517 +315,220 @@ async function parse(iterator, opt = {}) {
443
315
  }
444
316
  }
445
317
  }
446
- function peek(count = 1) {
447
- if (count == 1) {
448
- return iterator.charAt(ind + 1);
449
- }
450
- return iterator.slice(ind + 1, ind + count + 1);
451
- }
452
- function prev(count = 1) {
453
- if (count == 1) {
454
- return ind == 0 ? '' : iterator.charAt(ind - 1);
455
- }
456
- return iterator.slice(ind - 1 - count, ind - 1);
457
- }
458
- function next(count = 1) {
459
- let char = '';
460
- while (count-- > 0 && ind < total) {
461
- const codepoint = iterator.charCodeAt(++ind);
462
- if (isNaN(codepoint)) {
463
- return char;
464
- }
465
- char += iterator.charAt(ind);
466
- if (isNewLine(codepoint)) {
467
- lin++;
468
- col = 0;
469
- }
470
- else {
471
- col++;
472
- }
473
- }
474
- return char;
475
- }
476
- function pushToken(token) {
477
- tokens.push(token);
478
- map.set(token, { ...position });
479
- position.ind = ind;
480
- position.lin = lin;
481
- position.col = col == 0 ? 1 : col;
318
+ function mapToken(token) {
319
+ const node = getTokenType(token.token, token.hint);
320
+ map.set(node, token.position);
321
+ return node;
482
322
  }
483
- function consumeWhiteSpace() {
484
- let count = 0;
485
- while (isWhiteSpace(iterator.charAt(count + ind + 1).charCodeAt(0))) {
486
- count++;
487
- }
488
- next(count);
489
- return count;
490
- }
491
- function consumeString(quoteStr) {
492
- const quote = quoteStr;
493
- let value;
494
- let hasNewLine = false;
495
- if (buffer.length > 0) {
496
- pushToken(getType(buffer));
497
- buffer = '';
323
+ const iter = tokenize(iterator);
324
+ let item;
325
+ while (true) {
326
+ item = iter.next().value;
327
+ if (item == null) {
328
+ break;
498
329
  }
499
- buffer += quoteStr;
500
- while (ind < total) {
501
- value = peek();
502
- if (ind >= total) {
503
- pushToken({ typ: hasNewLine ? 'Bad-string' : 'Unclosed-string', val: buffer });
504
- break;
505
- }
506
- if (value == '\\') {
507
- const sequence = peek(6);
508
- let escapeSequence = '';
509
- let codepoint;
510
- let i;
511
- for (i = 1; i < sequence.length; i++) {
512
- codepoint = sequence.charCodeAt(i);
513
- if (codepoint == 0x20 ||
514
- (codepoint >= 0x61 && codepoint <= 0x66) ||
515
- (codepoint >= 0x41 && codepoint <= 0x46) ||
516
- (codepoint >= 0x30 && codepoint <= 0x39)) {
517
- escapeSequence += sequence[i];
518
- if (codepoint == 0x20) {
519
- break;
520
- }
521
- continue;
522
- }
523
- break;
524
- }
525
- // not hex or new line
330
+ tokens.push(item);
331
+ bytesIn = item.bytesIn;
332
+ if (item.token == ';' || item.token == '{') {
333
+ let node = await parseNode(tokens);
334
+ if (node != null) {
335
+ stack.push(node);
526
336
  // @ts-ignore
527
- if (i == 1 && !isNewLine(codepoint)) {
528
- buffer += sequence[i];
529
- next(2);
530
- continue;
531
- }
532
- if (escapeSequence.trimEnd().length > 0) {
533
- const codepoint = Number(`0x${escapeSequence.trimEnd()}`);
534
- if (codepoint == 0 ||
535
- // leading surrogate
536
- (0xD800 <= codepoint && codepoint <= 0xDBFF) ||
537
- // trailing surrogate
538
- (0xDC00 <= codepoint && codepoint <= 0xDFFF)) {
539
- buffer += String.fromCodePoint(0xFFFD);
337
+ context = node;
338
+ }
339
+ else if (item.token == '{') {
340
+ // node == null
341
+ // consume and throw away until the closing '}' or EOF
342
+ let inBlock = 1;
343
+ do {
344
+ item = iter.next().value;
345
+ if (item == null) {
346
+ break;
540
347
  }
541
- else {
542
- buffer += String.fromCodePoint(codepoint);
348
+ if (item.token == '{') {
349
+ inBlock++;
543
350
  }
544
- next(escapeSequence.length + 1);
545
- continue;
546
- }
547
- // buffer += value;
548
- if (ind >= total) {
549
- // drop '\\' at the end
550
- pushToken(getType(buffer));
551
- break;
552
- }
553
- buffer += next(2);
554
- continue;
555
- }
556
- if (value == quote) {
557
- buffer += value;
558
- pushToken({ typ: hasNewLine ? 'Bad-string' : 'String', val: buffer });
559
- next();
560
- // i += value.length;
561
- buffer = '';
562
- break;
563
- }
564
- if (isNewLine(value.charCodeAt(0))) {
565
- hasNewLine = true;
566
- }
567
- if (hasNewLine && value == ';') {
568
- pushToken({ typ: 'Bad-string', val: buffer });
569
- buffer = '';
570
- break;
571
- }
572
- buffer += value;
573
- // i += value.length;
574
- next();
575
- }
576
- }
577
- while (ind < total) {
578
- value = next();
579
- if (ind >= total) {
580
- if (buffer.length > 0) {
581
- pushToken(getType(buffer));
582
- buffer = '';
351
+ else if (item.token == '}') {
352
+ inBlock--;
353
+ }
354
+ } while (inBlock != 0);
583
355
  }
584
- break;
356
+ tokens = [];
357
+ map = new Map;
585
358
  }
586
- if (isWhiteSpace(value.charCodeAt(0))) {
587
- if (buffer.length > 0) {
588
- pushToken(getType(buffer));
589
- buffer = '';
590
- }
591
- while (ind < total) {
592
- value = next();
593
- if (ind >= total) {
594
- break;
595
- }
596
- if (!isWhiteSpace(value.charCodeAt(0))) {
597
- break;
598
- }
599
- }
600
- pushToken({ typ: 'Whitespace' });
601
- buffer = '';
602
- if (ind >= total) {
603
- break;
359
+ else if (item.token == '}') {
360
+ await parseNode(tokens);
361
+ const previousNode = stack.pop();
362
+ // @ts-ignore
363
+ context = stack[stack.length - 1] || ast;
364
+ // @ts-ignore
365
+ if (options.removeEmpty && previousNode != null && previousNode.chi.length == 0 && context.chi[context.chi.length - 1] == previousNode) {
366
+ context.chi.pop();
604
367
  }
368
+ tokens = [];
369
+ map = new Map;
605
370
  }
606
- switch (value) {
607
- case '/':
608
- if (buffer.length > 0 && tokens.at(-1)?.typ == 'Whitespace') {
609
- pushToken(getType(buffer));
610
- buffer = '';
611
- if (peek() != '*') {
612
- pushToken(getType(value));
613
- break;
614
- }
615
- }
616
- buffer += value;
617
- if (peek() == '*') {
618
- buffer += '*';
619
- // i++;
620
- next();
621
- while (ind < total) {
622
- value = next();
623
- if (ind >= total) {
624
- pushToken({
625
- typ: 'Bad-comment', val: buffer
626
- });
627
- break;
628
- }
629
- if (value == '\\') {
630
- buffer += value;
631
- value = next();
632
- if (ind >= total) {
633
- pushToken({
634
- typ: 'Bad-comment',
635
- val: buffer
636
- });
637
- break;
638
- }
639
- buffer += value;
640
- continue;
641
- }
642
- if (value == '*') {
643
- buffer += value;
644
- value = next();
645
- if (ind >= total) {
646
- pushToken({
647
- typ: 'Bad-comment', val: buffer
648
- });
649
- break;
650
- }
651
- buffer += value;
652
- if (value == '/') {
653
- pushToken({ typ: 'Comment', val: buffer });
654
- buffer = '';
655
- break;
656
- }
657
- }
658
- else {
659
- buffer += value;
660
- }
661
- }
662
- }
663
- break;
664
- case '<':
665
- if (buffer.length > 0) {
666
- pushToken(getType(buffer));
667
- buffer = '';
668
- }
669
- buffer += value;
670
- value = next();
671
- if (ind >= total) {
672
- break;
673
- }
674
- if (peek(3) == '!--') {
675
- while (ind < total) {
676
- value = next();
677
- if (ind >= total) {
678
- break;
679
- }
680
- buffer += value;
681
- if (value == '>' && prev(2) == '--') {
682
- pushToken({
683
- typ: 'CDOCOMM',
684
- val: buffer
685
- });
686
- buffer = '';
687
- break;
688
- }
689
- }
690
- }
691
- if (ind >= total) {
692
- pushToken({ typ: 'BADCDO', val: buffer });
693
- buffer = '';
694
- }
695
- break;
696
- case '\\':
697
- value = next();
698
- // EOF
699
- if (ind + 1 >= total) {
700
- // end of stream ignore \\
701
- pushToken(getType(buffer));
702
- buffer = '';
703
- break;
704
- }
705
- buffer += value;
706
- break;
707
- case '"':
708
- case "'":
709
- consumeString(value);
710
- break;
711
- case '~':
712
- case '|':
713
- if (tokens.at(-1)?.typ == 'Whitespace') {
714
- tokens.pop();
715
- }
716
- if (buffer.length > 0) {
717
- pushToken(getType(buffer));
718
- buffer = '';
719
- }
720
- buffer += value;
721
- value = next();
722
- if (ind >= total) {
723
- pushToken(getType(buffer));
724
- buffer = '';
725
- break;
726
- }
727
- if (value == '=') {
728
- buffer += value;
729
- pushToken({
730
- typ: buffer[0] == '~' ? 'Includes' : 'Dash-matches',
731
- val: buffer
732
- });
733
- buffer = '';
734
- break;
735
- }
736
- pushToken(getType(buffer));
737
- while (isWhiteSpace(value.charCodeAt(0))) {
738
- value = next();
739
- }
740
- buffer = value;
741
- break;
742
- case '>':
743
- if (buffer !== '') {
744
- pushToken(getType(buffer));
745
- buffer = '';
746
- }
747
- if (tokens[tokens.length - 1]?.typ == 'Whitespace') {
748
- tokens.pop();
749
- }
750
- pushToken({ typ: 'Gt' });
751
- consumeWhiteSpace();
752
- break;
753
- case '.':
754
- const codepoint = peek().charCodeAt(0);
755
- if (!isDigit(codepoint) && buffer !== '') {
756
- pushToken(getType(buffer));
757
- buffer = value;
758
- break;
759
- }
760
- buffer += value;
761
- break;
762
- case '+':
763
- case ':':
764
- case ',':
765
- case '=':
766
- if (buffer.length > 0) {
767
- pushToken(getType(buffer));
768
- buffer = '';
769
- }
770
- if (value == ':' && ':' == peek()) {
771
- buffer += value + next();
772
- break;
773
- }
774
- pushToken(getType(value));
775
- buffer = '';
776
- if (value == '+' && isWhiteSpace(peek().charCodeAt(0))) {
777
- pushToken(getType(next()));
778
- }
779
- while (isWhiteSpace(peek().charCodeAt(0))) {
780
- next();
781
- }
782
- break;
783
- case ')':
784
- if (buffer.length > 0) {
785
- pushToken(getType(buffer));
786
- buffer = '';
787
- }
788
- pushToken({ typ: 'End-parens' });
789
- break;
790
- case '(':
791
- if (buffer.length == 0) {
792
- pushToken({ typ: 'Start-parens' });
793
- }
794
- else {
795
- buffer += value;
796
- pushToken(getType(buffer));
797
- buffer = '';
798
- const token = tokens[tokens.length - 1];
799
- if (token.typ == 'UrlFunc') {
800
- // consume either string or url token
801
- let whitespace = '';
802
- value = peek();
803
- while (isWhiteSpace(value.charCodeAt(0))) {
804
- whitespace += value;
805
- }
806
- if (whitespace.length > 0) {
807
- next(whitespace.length);
808
- }
809
- value = peek();
810
- if (value == '"' || value == "'") {
811
- consumeString(next());
812
- let token = tokens[tokens.length - 1];
813
- if (['String', 'Literal'].includes(token.typ) && urlTokenMatcher.test(token.val)) {
814
- if (token.val.slice(1, 6) != 'data:') {
815
- if (token.typ == 'String') {
816
- token.val = token.val.slice(1, -1);
817
- }
818
- // @ts-ignore
819
- token.typ = 'Url-token';
820
- }
821
- }
822
- break;
823
- }
824
- else {
825
- buffer = '';
826
- do {
827
- let cp = value.charCodeAt(0);
828
- // EOF -
829
- if (cp == null) {
830
- pushToken({ typ: 'Bad-url-token', val: buffer });
831
- break;
832
- }
833
- // ')'
834
- if (cp == 0x29 || cp == null) {
835
- if (buffer.length == 0) {
836
- pushToken({ typ: 'Bad-url-token', val: '' });
837
- }
838
- else {
839
- pushToken({ typ: 'Url-token', val: buffer });
840
- }
841
- if (cp != null) {
842
- pushToken(getType(next()));
843
- }
844
- break;
845
- }
846
- if (isWhiteSpace(cp)) {
847
- whitespace = next();
848
- while (true) {
849
- value = peek();
850
- cp = value.charCodeAt(0);
851
- if (isWhiteSpace(cp)) {
852
- whitespace += value;
853
- continue;
854
- }
855
- break;
856
- }
857
- if (cp == null || cp == 0x29) {
858
- continue;
859
- }
860
- // bad url token
861
- buffer += next(whitespace.length);
862
- do {
863
- value = peek();
864
- cp = value.charCodeAt(0);
865
- if (cp == null || cp == 0x29) {
866
- break;
867
- }
868
- buffer += next();
869
- } while (true);
870
- pushToken({ typ: 'Bad-url-token', val: buffer });
871
- continue;
872
- }
873
- buffer += next();
874
- value = peek();
875
- } while (true);
876
- buffer = '';
877
- }
878
- }
879
- }
880
- break;
881
- case '[':
882
- case ']':
883
- case '{':
884
- case '}':
885
- case ';':
886
- if (buffer.length > 0) {
887
- pushToken(getType(buffer));
888
- buffer = '';
889
- }
890
- pushToken(getBlockType(value));
891
- let node = null;
892
- if (value == '{' || value == ';') {
893
- node = await parseNode(tokens);
894
- if (node != null) {
895
- stack.push(node);
896
- // @ts-ignore
897
- context = node;
898
- }
899
- else if (value == '{') {
900
- // node == null
901
- // consume and throw away until the closing '}' or EOF
902
- consume('{', '}');
903
- }
904
- tokens.length = 0;
905
- map.clear();
906
- }
907
- else if (value == '}') {
908
- await parseNode(tokens);
909
- const previousNode = stack.pop();
910
- // @ts-ignore
911
- context = stack[stack.length - 1] || ast;
912
- // @ts-ignore
913
- if (options.removeEmpty && previousNode != null && previousNode.chi.length == 0 && context.chi[context.chi.length - 1] == previousNode) {
914
- context.chi.pop();
915
- }
916
- tokens.length = 0;
917
- map.clear();
918
- buffer = '';
919
- }
920
- break;
921
- case '!':
922
- if (buffer.length > 0) {
923
- pushToken(getType(buffer));
924
- buffer = '';
925
- }
926
- const important = peek(9);
927
- if (important == 'important') {
928
- if (tokens[tokens.length - 1]?.typ == 'Whitespace') {
929
- tokens.pop();
930
- }
931
- pushToken({ typ: 'Important' });
932
- next(9);
933
- buffer = '';
934
- break;
935
- }
936
- buffer = '!';
937
- break;
938
- default:
939
- buffer += value;
940
- break;
941
- }
942
- }
943
- if (buffer.length > 0) {
944
- pushToken(getType(buffer));
945
371
  }
946
372
  if (tokens.length > 0) {
947
373
  await parseNode(tokens);
948
374
  }
949
- if (options.compress) {
375
+ const endParseTime = performance.now();
376
+ if (options.minify) {
950
377
  if (ast.chi.length > 0) {
951
- deduplicate(ast, options, true);
378
+ minify(ast, options, true);
952
379
  }
953
380
  }
954
- return { ast, errors, bytesIn };
381
+ const endTime = performance.now();
382
+ return {
383
+ ast, errors, stats: {
384
+ bytesIn,
385
+ parse: `${(endParseTime - startTime).toFixed(2)}ms`,
386
+ minify: `${(endTime - endParseTime).toFixed(2)}ms`,
387
+ total: `${(endTime - startTime).toFixed(2)}ms`
388
+ }
389
+ };
390
+ }
391
+ function parseString(src, options = { location: false }) {
392
+ return [...tokenize(src)].map(t => {
393
+ const token = getTokenType(t.token, t.hint);
394
+ if (options.location) {
395
+ Object.assign(token, { loc: t.position });
396
+ }
397
+ return token;
398
+ });
399
+ }
400
+ function getTokenType(val, hint) {
401
+ if (val === '' && hint == null) {
402
+ throw new Error('empty string?');
403
+ }
404
+ if (hint != null) {
405
+ return ([
406
+ 'Whitespace', 'Semi-colon', 'Colon', 'Block-start',
407
+ 'Block-start', 'Attr-start', 'Attr-end', 'Start-parens', 'End-parens',
408
+ 'Comma', 'Gt', 'Lt'
409
+ ].includes(hint) ? { typ: hint } : { typ: hint, val });
410
+ }
411
+ if (val == ' ') {
412
+ return { typ: 'Whitespace' };
413
+ }
414
+ if (val == ';') {
415
+ return { typ: 'Semi-colon' };
416
+ }
417
+ if (val == '{') {
418
+ return { typ: 'Block-start' };
419
+ }
420
+ if (val == '}') {
421
+ return { typ: 'Block-end' };
422
+ }
423
+ if (val == '[') {
424
+ return { typ: 'Attr-start' };
425
+ }
426
+ if (val == ']') {
427
+ return { typ: 'Attr-end' };
428
+ }
429
+ if (val == ':') {
430
+ return { typ: 'Colon' };
431
+ }
432
+ if (val == ')') {
433
+ return { typ: 'End-parens' };
434
+ }
435
+ if (val == '(') {
436
+ return { typ: 'Start-parens' };
437
+ }
438
+ if (val == '=') {
439
+ return { typ: 'Delim', val };
440
+ }
441
+ if (val == ';') {
442
+ return { typ: 'Semi-colon' };
443
+ }
444
+ if (val == ',') {
445
+ return { typ: 'Comma' };
446
+ }
447
+ if (val == '<') {
448
+ return { typ: 'Lt' };
449
+ }
450
+ if (val == '>') {
451
+ return { typ: 'Gt' };
452
+ }
453
+ if (isPseudo(val)) {
454
+ return val.endsWith('(') ? {
455
+ typ: 'Pseudo-class-func',
456
+ val: val.slice(0, -1),
457
+ chi: []
458
+ }
459
+ : {
460
+ typ: 'Pseudo-class',
461
+ val
462
+ };
463
+ }
464
+ if (isAtKeyword(val)) {
465
+ return {
466
+ typ: 'At-rule',
467
+ val: val.slice(1)
468
+ };
469
+ }
470
+ if (isFunction(val)) {
471
+ val = val.slice(0, -1);
472
+ return {
473
+ typ: val == 'url' ? 'UrlFunc' : 'Func',
474
+ val,
475
+ chi: []
476
+ };
477
+ }
478
+ if (isNumber(val)) {
479
+ return {
480
+ typ: 'Number',
481
+ val
482
+ };
483
+ }
484
+ if (isDimension(val)) {
485
+ return parseDimension(val);
486
+ }
487
+ if (isPercentage(val)) {
488
+ return {
489
+ typ: 'Perc',
490
+ val: val.slice(0, -1)
491
+ };
492
+ }
493
+ const v = val.toLowerCase();
494
+ if (v == 'currentcolor' || val == 'transparent' || v in COLORS_NAMES) {
495
+ return {
496
+ typ: 'Color',
497
+ val,
498
+ kin: 'lit'
499
+ };
500
+ }
501
+ if (isIdent(val)) {
502
+ return {
503
+ typ: 'Iden',
504
+ val
505
+ };
506
+ }
507
+ if (val.charAt(0) == '#' && isHexColor(val)) {
508
+ return {
509
+ typ: 'Color',
510
+ val,
511
+ kin: 'hex'
512
+ };
513
+ }
514
+ if (val.charAt(0) == '#' && isHash(val)) {
515
+ return {
516
+ typ: 'Hash',
517
+ val
518
+ };
519
+ }
520
+ if ('"\''.includes(val.charAt(0))) {
521
+ return {
522
+ typ: 'Unclosed-string',
523
+ val
524
+ };
525
+ }
526
+ return {
527
+ typ: 'Literal',
528
+ val
529
+ };
955
530
  }
956
- function parseTokens(tokens, nodeType, options = {}) {
531
+ function parseTokens(tokens, options = {}) {
957
532
  for (let i = 0; i < tokens.length; i++) {
958
533
  const t = tokens[i];
959
534
  if (t.typ == 'Whitespace' && ((i == 0 ||
@@ -1007,7 +582,7 @@ function parseTokens(tokens, nodeType, options = {}) {
1007
582
  if (t.chi.length > 1) {
1008
583
  /*(<AttrToken>t).chi =*/
1009
584
  // @ts-ignore
1010
- parseTokens(t.chi, t.typ, options);
585
+ parseTokens(t.chi, t.typ);
1011
586
  }
1012
587
  // @ts-ignore
1013
588
  t.chi.forEach(val => {
@@ -1119,8 +694,8 @@ function parseTokens(tokens, nodeType, options = {}) {
1119
694
  // @ts-ignore
1120
695
  if (t.chi.length > 0) {
1121
696
  // @ts-ignore
1122
- parseTokens(t.chi, t.typ, options);
1123
- if (t.typ == 'Pseudo-class-func' && t.val == ':is' && options.compress) {
697
+ parseTokens(t.chi, t.typ);
698
+ if (t.typ == 'Pseudo-class-func' && t.val == ':is' && options.minify) {
1124
699
  //
1125
700
  const count = t.chi.filter(t => t.typ != 'Comment').length;
1126
701
  if (count == 1 ||
@@ -1158,23 +733,5 @@ function parseTokens(tokens, nodeType, options = {}) {
1158
733
  }
1159
734
  return tokens;
1160
735
  }
1161
- function getBlockType(chr) {
1162
- if (chr == ';') {
1163
- return { typ: 'Semi-colon' };
1164
- }
1165
- if (chr == '{') {
1166
- return { typ: 'Block-start' };
1167
- }
1168
- if (chr == '}') {
1169
- return { typ: 'Block-end' };
1170
- }
1171
- if (chr == '[') {
1172
- return { typ: 'Attr-start' };
1173
- }
1174
- if (chr == ']') {
1175
- return { typ: 'Attr-end' };
1176
- }
1177
- throw new Error(`unhandled token: '${chr}'`);
1178
- }
1179
736
 
1180
- export { parse };
737
+ export { parse, parseString, urlTokenMatcher };