@tbela99/css-parser 0.0.1-alpha4 → 0.0.1-rc1

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,22 @@
1
- import { isWhiteSpace, 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 = {}) {
9
15
  const errors = [];
10
16
  const options = {
11
17
  src: '',
12
18
  sourcemap: false,
13
- compress: false,
19
+ minify: true,
14
20
  nestingRules: false,
15
21
  resolveImport: false,
16
22
  resolveUrls: false,
@@ -20,172 +26,28 @@ async function parse(iterator, opt = {}) {
20
26
  if (options.resolveImport) {
21
27
  options.resolveUrls = true;
22
28
  }
23
- let ind = -1;
24
- let lin = 1;
25
- let col = 0;
26
- const tokens = [];
27
29
  const src = options.src;
28
30
  const stack = [];
29
31
  const ast = {
30
32
  typ: "StyleSheet",
31
33
  chi: []
32
34
  };
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;
35
+ let tokens = [];
42
36
  let map = new Map;
37
+ let bytesIn = 0;
43
38
  let context = ast;
44
39
  if (options.sourcemap) {
45
40
  ast.loc = {
46
41
  sta: {
47
- ind: ind,
48
- lin: lin,
49
- col: col
42
+ ind: 0,
43
+ lin: 1,
44
+ col: 1
50
45
  },
51
46
  src: ''
52
47
  };
53
48
  }
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) {
49
+ async function parseNode(results) {
50
+ let tokens = results.map(mapToken);
189
51
  let i;
190
52
  let loc;
191
53
  for (i = 0; i < tokens.length; i++) {
@@ -283,7 +145,7 @@ async function parse(iterator, opt = {}) {
283
145
  // @ts-ignore
284
146
  const root = await options.load(url, options.src).then((src) => {
285
147
  return parse(src, Object.assign({}, options, {
286
- compress: false,
148
+ minify: false,
287
149
  // @ts-ignore
288
150
  src: options.resolve(url, options.src).absolute
289
151
  }));
@@ -306,7 +168,7 @@ async function parse(iterator, opt = {}) {
306
168
  // https://www.w3.org/TR/css-nesting-1/#conditionals
307
169
  // allowed nesting at-rules
308
170
  // there must be a top level rule in the stack
309
- const raw = tokens.reduce((acc, curr, index, array) => {
171
+ const raw = tokens.reduce((acc, curr) => {
310
172
  acc.push(renderToken(curr, { removeComments: true }));
311
173
  return acc;
312
174
  }, []);
@@ -334,15 +196,24 @@ async function parse(iterator, opt = {}) {
334
196
  // rule
335
197
  if (delim.typ == 'Block-start') {
336
198
  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
- }
199
+ // if (context.typ == 'Rule') {
200
+ //
201
+ // if (tokens[0]?.typ == 'Iden') {
202
+ // errors.push({action: 'drop', message: 'invalid nesting rule', location: {src, ...position}});
203
+ // return null;
204
+ // }
205
+ // }
343
206
  const uniq = new Map;
344
- parseTokens(tokens, { compress: options.compress }).reduce((acc, curr) => {
345
- let t = renderToken(curr, { compress: true });
207
+ parseTokens(tokens, { minify: options.minify }).reduce((acc, curr, index, array) => {
208
+ if (curr.typ == 'Whitespace') {
209
+ if (array[index - 1]?.typ == 'Gt' ||
210
+ array[index + 1]?.typ == 'Gt' ||
211
+ combinators.includes(array[index - 1]?.val) ||
212
+ combinators.includes(array[index + 1]?.val)) {
213
+ return acc;
214
+ }
215
+ }
216
+ let t = renderToken(curr, { minify: true });
346
217
  if (t == ',') {
347
218
  acc.push([]);
348
219
  }
@@ -411,11 +282,19 @@ async function parse(iterator, opt = {}) {
411
282
  }
412
283
  }
413
284
  if (value == null) {
414
- errors.push({ action: 'drop', message: 'invalid declaration', location: { src, ...position } });
285
+ errors.push({
286
+ action: 'drop',
287
+ message: 'invalid declaration',
288
+ location: { src, ...position }
289
+ });
415
290
  return null;
416
291
  }
417
292
  if (value.length == 0) {
418
- errors.push({ action: 'drop', message: 'invalid declaration', location: { src, ...position } });
293
+ errors.push({
294
+ action: 'drop',
295
+ message: 'invalid declaration',
296
+ location: { src, ...position }
297
+ });
419
298
  return null;
420
299
  }
421
300
  const node = {
@@ -429,7 +308,11 @@ async function parse(iterator, opt = {}) {
429
308
  node.val.shift();
430
309
  }
431
310
  if (node.val.length == 0) {
432
- errors.push({ action: 'drop', message: 'invalid declaration', location: { src, ...position } });
311
+ errors.push({
312
+ action: 'drop',
313
+ message: 'invalid declaration',
314
+ location: { src, ...position }
315
+ });
433
316
  return null;
434
317
  }
435
318
  // @ts-ignore
@@ -438,498 +321,210 @@ async function parse(iterator, opt = {}) {
438
321
  }
439
322
  }
440
323
  }
441
- function peek(count = 1) {
442
- if (count == 1) {
443
- return iterator.charAt(ind + 1);
444
- }
445
- return iterator.slice(ind + 1, ind + count + 1);
446
- }
447
- function prev(count = 1) {
448
- if (count == 1) {
449
- return ind == 0 ? '' : iterator.charAt(ind - 1);
450
- }
451
- return iterator.slice(ind - 1 - count, ind - 1);
324
+ function mapToken(token) {
325
+ const node = getTokenType(token.token, token.hint);
326
+ map.set(node, token.position);
327
+ return node;
452
328
  }
453
- function next(count = 1) {
454
- let char = '';
455
- while (count-- > 0 && ind < total) {
456
- const codepoint = iterator.charCodeAt(++ind);
457
- if (isNaN(codepoint)) {
458
- return char;
459
- }
460
- char += iterator.charAt(ind);
461
- if (isNewLine(codepoint)) {
462
- lin++;
463
- col = 0;
464
- }
465
- else {
466
- col++;
467
- }
468
- }
469
- return char;
470
- }
471
- function pushToken(token) {
472
- tokens.push(token);
473
- map.set(token, { ...position });
474
- position.ind = ind;
475
- position.lin = lin;
476
- position.col = col == 0 ? 1 : col;
477
- // }
478
- }
479
- function consumeWhiteSpace() {
480
- let count = 0;
481
- while (isWhiteSpace(iterator.charAt(count + ind + 1).charCodeAt(0))) {
482
- count++;
483
- }
484
- next(count);
485
- return count;
486
- }
487
- function consumeString(quoteStr) {
488
- const quote = quoteStr;
489
- let value;
490
- let hasNewLine = false;
491
- if (buffer.length > 0) {
492
- pushToken(getType(buffer));
493
- buffer = '';
329
+ const iter = tokenize(iterator);
330
+ let item;
331
+ while (true) {
332
+ item = iter.next().value;
333
+ if (item == null) {
334
+ break;
494
335
  }
495
- buffer += quoteStr;
496
- while (ind < total) {
497
- value = peek();
498
- if (ind >= total) {
499
- pushToken({ typ: hasNewLine ? 'Bad-string' : 'Unclosed-string', val: buffer });
500
- break;
501
- }
502
- if (value == '\\') {
503
- const sequence = peek(6);
504
- let escapeSequence = '';
505
- let codepoint;
506
- let i;
507
- for (i = 1; i < sequence.length; i++) {
508
- codepoint = sequence.charCodeAt(i);
509
- if (codepoint == 0x20 ||
510
- (codepoint >= 0x61 && codepoint <= 0x66) ||
511
- (codepoint >= 0x41 && codepoint <= 0x46) ||
512
- (codepoint >= 0x30 && codepoint <= 0x39)) {
513
- escapeSequence += sequence[i];
514
- if (codepoint == 0x20) {
515
- break;
516
- }
517
- continue;
518
- }
519
- break;
520
- }
521
- // not hex or new line
336
+ tokens.push(item);
337
+ bytesIn = item.bytesIn;
338
+ if (item.token == ';' || item.token == '{') {
339
+ let node = await parseNode(tokens);
340
+ if (node != null) {
341
+ stack.push(node);
522
342
  // @ts-ignore
523
- if (i == 1 && !isNewLine(codepoint)) {
524
- buffer += sequence[i];
525
- next(2);
526
- continue;
527
- }
528
- if (escapeSequence.trimEnd().length > 0) {
529
- const codepoint = Number(`0x${escapeSequence.trimEnd()}`);
530
- if (codepoint == 0 ||
531
- // leading surrogate
532
- (0xD800 <= codepoint && codepoint <= 0xDBFF) ||
533
- // trailing surrogate
534
- (0xDC00 <= codepoint && codepoint <= 0xDFFF)) {
535
- buffer += String.fromCodePoint(0xFFFD);
343
+ context = node;
344
+ }
345
+ else if (item.token == '{') {
346
+ // node == null
347
+ // consume and throw away until the closing '}' or EOF
348
+ let inBlock = 1;
349
+ do {
350
+ item = iter.next().value;
351
+ if (item == null) {
352
+ break;
536
353
  }
537
- else {
538
- buffer += String.fromCodePoint(codepoint);
354
+ if (item.token == '{') {
355
+ inBlock++;
539
356
  }
540
- next(escapeSequence.length + 1);
541
- continue;
542
- }
543
- // buffer += value;
544
- if (ind >= total) {
545
- // drop '\\' at the end
546
- pushToken(getType(buffer));
547
- break;
548
- }
549
- buffer += next(2);
550
- continue;
551
- }
552
- if (value == quote) {
553
- buffer += value;
554
- pushToken({ typ: hasNewLine ? 'Bad-string' : 'String', val: buffer });
555
- next();
556
- // i += value.length;
557
- buffer = '';
558
- break;
559
- }
560
- if (isNewLine(value.charCodeAt(0))) {
561
- hasNewLine = true;
562
- }
563
- if (hasNewLine && value == ';') {
564
- pushToken({ typ: 'Bad-string', val: buffer });
565
- buffer = '';
566
- break;
567
- }
568
- buffer += value;
569
- // i += value.length;
570
- next();
571
- }
572
- }
573
- while (ind < total) {
574
- value = next();
575
- if (ind >= total) {
576
- if (buffer.length > 0) {
577
- pushToken(getType(buffer));
578
- buffer = '';
357
+ else if (item.token == '}') {
358
+ inBlock--;
359
+ }
360
+ } while (inBlock != 0);
579
361
  }
580
- break;
362
+ tokens = [];
363
+ map = new Map;
581
364
  }
582
- if (isWhiteSpace(value.charCodeAt(0))) {
583
- if (buffer.length > 0) {
584
- pushToken(getType(buffer));
585
- buffer = '';
586
- }
587
- while (ind < total) {
588
- value = next();
589
- if (ind >= total) {
590
- break;
591
- }
592
- if (!isWhiteSpace(value.charCodeAt(0))) {
593
- break;
594
- }
595
- }
596
- pushToken({ typ: 'Whitespace' });
597
- buffer = '';
598
- if (ind >= total) {
599
- break;
365
+ else if (item.token == '}') {
366
+ await parseNode(tokens);
367
+ const previousNode = stack.pop();
368
+ // @ts-ignore
369
+ context = stack[stack.length - 1] || ast;
370
+ // @ts-ignore
371
+ if (options.removeEmpty && previousNode != null && previousNode.chi.length == 0 && context.chi[context.chi.length - 1] == previousNode) {
372
+ context.chi.pop();
600
373
  }
374
+ tokens = [];
375
+ map = new Map;
601
376
  }
602
- switch (value) {
603
- case '/':
604
- if (buffer.length > 0 && tokens.at(-1)?.typ == 'Whitespace') {
605
- pushToken(getType(buffer));
606
- buffer = '';
607
- if (peek() != '*') {
608
- pushToken(getType(value));
609
- break;
610
- }
611
- }
612
- buffer += value;
613
- if (peek() == '*') {
614
- buffer += '*';
615
- // i++;
616
- next();
617
- while (ind < total) {
618
- value = next();
619
- if (ind >= total) {
620
- pushToken({
621
- typ: 'Bad-comment', val: buffer
622
- });
623
- break;
624
- }
625
- if (value == '\\') {
626
- buffer += value;
627
- value = next();
628
- if (ind >= total) {
629
- pushToken({
630
- typ: 'Bad-comment',
631
- val: buffer
632
- });
633
- break;
634
- }
635
- buffer += value;
636
- continue;
637
- }
638
- if (value == '*') {
639
- buffer += value;
640
- value = next();
641
- if (ind >= total) {
642
- pushToken({
643
- typ: 'Bad-comment', val: buffer
644
- });
645
- break;
646
- }
647
- buffer += value;
648
- if (value == '/') {
649
- pushToken({ typ: 'Comment', val: buffer });
650
- buffer = '';
651
- break;
652
- }
653
- }
654
- else {
655
- buffer += value;
656
- }
657
- }
658
- }
659
- break;
660
- case '<':
661
- if (buffer.length > 0) {
662
- pushToken(getType(buffer));
663
- buffer = '';
664
- }
665
- buffer += value;
666
- value = next();
667
- if (ind >= total) {
668
- break;
669
- }
670
- if (peek(3) == '!--') {
671
- while (ind < total) {
672
- value = next();
673
- if (ind >= total) {
674
- break;
675
- }
676
- buffer += value;
677
- if (value == '>' && prev(2) == '--') {
678
- pushToken({
679
- typ: 'CDOCOMM',
680
- val: buffer
681
- });
682
- buffer = '';
683
- break;
684
- }
685
- }
686
- }
687
- if (ind >= total) {
688
- pushToken({ typ: 'BADCDO', val: buffer });
689
- buffer = '';
690
- }
691
- break;
692
- case '\\':
693
- value = next();
694
- // EOF
695
- if (ind + 1 >= total) {
696
- // end of stream ignore \\
697
- pushToken(getType(buffer));
698
- buffer = '';
699
- break;
700
- }
701
- buffer += value;
702
- break;
703
- case '"':
704
- case "'":
705
- consumeString(value);
706
- break;
707
- case '~':
708
- case '|':
709
- if (buffer.length > 0) {
710
- pushToken(getType(buffer));
711
- buffer = '';
712
- }
713
- buffer += value;
714
- value = next();
715
- if (ind >= total) {
716
- pushToken(getType(buffer));
717
- buffer = '';
718
- break;
719
- }
720
- if (value == '=') {
721
- buffer += value;
722
- pushToken({
723
- typ: buffer[0] == '~' ? 'Includes' : 'Dash-matches',
724
- val: buffer
725
- });
726
- buffer = '';
727
- break;
728
- }
729
- pushToken(getType(buffer));
730
- buffer = value;
731
- break;
732
- case '>':
733
- if (tokens[tokens.length - 1]?.typ == 'Whitespace') {
734
- tokens.pop();
735
- }
736
- if (buffer !== '') {
737
- pushToken(getType(buffer));
738
- buffer = '';
739
- }
740
- pushToken({ typ: 'Gt' });
741
- consumeWhiteSpace();
742
- break;
743
- case ':':
744
- case ',':
745
- case '=':
746
- if (buffer.length > 0) {
747
- pushToken(getType(buffer));
748
- buffer = '';
749
- }
750
- if (value == ':' && ':' == peek()) {
751
- buffer += value + next();
752
- break;
753
- }
754
- pushToken(getType(value));
755
- buffer = '';
756
- while (isWhiteSpace(peek().charCodeAt(0))) {
757
- next();
758
- }
759
- break;
760
- case ')':
761
- if (buffer.length > 0) {
762
- pushToken(getType(buffer));
763
- buffer = '';
764
- }
765
- pushToken({ typ: 'End-parens' });
766
- break;
767
- case '(':
768
- if (buffer.length == 0) {
769
- pushToken({ typ: 'Start-parens' });
770
- }
771
- else {
772
- buffer += value;
773
- pushToken(getType(buffer));
774
- buffer = '';
775
- const token = tokens[tokens.length - 1];
776
- if (token.typ == 'UrlFunc') {
777
- // consume either string or url token
778
- let whitespace = '';
779
- value = peek();
780
- while (isWhiteSpace(value.charCodeAt(0))) {
781
- whitespace += value;
782
- }
783
- if (whitespace.length > 0) {
784
- next(whitespace.length);
785
- }
786
- value = peek();
787
- if (value == '"' || value == "'") {
788
- consumeString(next());
789
- let token = tokens[tokens.length - 1];
790
- if (['String', 'Literal'].includes(token.typ) && urlTokenMatcher.test(token.val)) {
791
- if (token.val.slice(1, 6) != 'data:') {
792
- if (token.typ == 'String') {
793
- token.val = token.val.slice(1, -1);
794
- }
795
- // @ts-ignore
796
- token.typ = 'Url-token';
797
- }
798
- }
799
- break;
800
- }
801
- else {
802
- buffer = '';
803
- do {
804
- let cp = value.charCodeAt(0);
805
- // EOF -
806
- if (cp == null) {
807
- pushToken({ typ: 'Bad-url-token', val: buffer });
808
- break;
809
- }
810
- // ')'
811
- if (cp == 0x29 || cp == null) {
812
- if (buffer.length == 0) {
813
- pushToken({ typ: 'Bad-url-token', val: '' });
814
- }
815
- else {
816
- pushToken({ typ: 'Url-token', val: buffer });
817
- }
818
- if (cp != null) {
819
- pushToken(getType(next()));
820
- }
821
- break;
822
- }
823
- if (isWhiteSpace(cp)) {
824
- whitespace = next();
825
- while (true) {
826
- value = peek();
827
- cp = value.charCodeAt(0);
828
- if (isWhiteSpace(cp)) {
829
- whitespace += value;
830
- continue;
831
- }
832
- break;
833
- }
834
- if (cp == null || cp == 0x29) {
835
- continue;
836
- }
837
- // bad url token
838
- buffer += next(whitespace.length);
839
- do {
840
- value = peek();
841
- cp = value.charCodeAt(0);
842
- if (cp == null || cp == 0x29) {
843
- break;
844
- }
845
- buffer += next();
846
- } while (true);
847
- pushToken({ typ: 'Bad-url-token', val: buffer });
848
- continue;
849
- }
850
- buffer += next();
851
- value = peek();
852
- } while (true);
853
- buffer = '';
854
- }
855
- }
856
- }
857
- break;
858
- case '[':
859
- case ']':
860
- case '{':
861
- case '}':
862
- case ';':
863
- if (buffer.length > 0) {
864
- pushToken(getType(buffer));
865
- buffer = '';
866
- }
867
- pushToken(getBlockType(value));
868
- let node = null;
869
- if (value == '{' || value == ';') {
870
- node = await parseNode(tokens);
871
- if (node != null) {
872
- stack.push(node);
873
- // @ts-ignore
874
- context = node;
875
- }
876
- else if (value == '{') {
877
- // node == null
878
- // consume and throw away until the closing '}' or EOF
879
- consume('{', '}');
880
- }
881
- tokens.length = 0;
882
- map.clear();
883
- }
884
- else if (value == '}') {
885
- await parseNode(tokens);
886
- const previousNode = stack.pop();
887
- // @ts-ignore
888
- context = stack[stack.length - 1] || ast;
889
- // @ts-ignore
890
- if (options.removeEmpty && previousNode != null && previousNode.chi.length == 0 && context.chi[context.chi.length - 1] == previousNode) {
891
- context.chi.pop();
892
- }
893
- tokens.length = 0;
894
- map.clear();
895
- buffer = '';
896
- }
897
- break;
898
- case '!':
899
- if (buffer.length > 0) {
900
- pushToken(getType(buffer));
901
- buffer = '';
902
- }
903
- const important = peek(9);
904
- if (important == 'important') {
905
- if (tokens[tokens.length - 1]?.typ == 'Whitespace') {
906
- tokens.pop();
907
- }
908
- pushToken({ typ: 'Important' });
909
- next(9);
910
- buffer = '';
911
- break;
912
- }
913
- buffer = '!';
914
- break;
915
- default:
916
- buffer += value;
917
- break;
918
- }
919
- }
920
- if (buffer.length > 0) {
921
- pushToken(getType(buffer));
922
377
  }
923
378
  if (tokens.length > 0) {
924
379
  await parseNode(tokens);
925
380
  }
926
- if (options.compress) {
381
+ if (options.minify) {
927
382
  if (ast.chi.length > 0) {
928
- deduplicate(ast, options, true);
383
+ minify(ast, options, true);
929
384
  }
930
385
  }
931
386
  return { ast, errors, bytesIn };
932
387
  }
388
+ function parseString(src, options = { location: false }) {
389
+ return [...tokenize(src)].map(t => {
390
+ const token = getTokenType(t.token, t.hint);
391
+ if (options.location) {
392
+ Object.assign(token, { loc: t.position });
393
+ }
394
+ return token;
395
+ });
396
+ }
397
+ function getTokenType(val, hint) {
398
+ if (val === '' && hint == null) {
399
+ throw new Error('empty string?');
400
+ }
401
+ if (hint != null) {
402
+ return ([
403
+ 'Whitespace', 'Semi-colon', 'Colon', 'Block-start',
404
+ 'Block-start', 'Attr-start', 'Attr-end', 'Start-parens', 'End-parens',
405
+ 'Comma', 'Gt', 'Lt'
406
+ ].includes(hint) ? { typ: hint } : { typ: hint, val });
407
+ }
408
+ if (val == ' ') {
409
+ return { typ: 'Whitespace' };
410
+ }
411
+ if (val == ';') {
412
+ return { typ: 'Semi-colon' };
413
+ }
414
+ if (val == '{') {
415
+ return { typ: 'Block-start' };
416
+ }
417
+ if (val == '}') {
418
+ return { typ: 'Block-end' };
419
+ }
420
+ if (val == '[') {
421
+ return { typ: 'Attr-start' };
422
+ }
423
+ if (val == ']') {
424
+ return { typ: 'Attr-end' };
425
+ }
426
+ if (val == ':') {
427
+ return { typ: 'Colon' };
428
+ }
429
+ if (val == ')') {
430
+ return { typ: 'End-parens' };
431
+ }
432
+ if (val == '(') {
433
+ return { typ: 'Start-parens' };
434
+ }
435
+ if (val == '=') {
436
+ return { typ: 'Delim', val };
437
+ }
438
+ if (val == ';') {
439
+ return { typ: 'Semi-colon' };
440
+ }
441
+ if (val == ',') {
442
+ return { typ: 'Comma' };
443
+ }
444
+ if (val == '<') {
445
+ return { typ: 'Lt' };
446
+ }
447
+ if (val == '>') {
448
+ return { typ: 'Gt' };
449
+ }
450
+ if (isPseudo(val)) {
451
+ return val.endsWith('(') ? {
452
+ typ: 'Pseudo-class-func',
453
+ val: val.slice(0, -1),
454
+ chi: []
455
+ }
456
+ : {
457
+ typ: 'Pseudo-class',
458
+ val
459
+ };
460
+ }
461
+ if (isAtKeyword(val)) {
462
+ return {
463
+ typ: 'At-rule',
464
+ val: val.slice(1)
465
+ };
466
+ }
467
+ if (isFunction(val)) {
468
+ val = val.slice(0, -1);
469
+ return {
470
+ typ: val == 'url' ? 'UrlFunc' : 'Func',
471
+ val,
472
+ chi: []
473
+ };
474
+ }
475
+ if (isNumber(val)) {
476
+ return {
477
+ typ: 'Number',
478
+ val
479
+ };
480
+ }
481
+ if (isDimension(val)) {
482
+ return parseDimension(val);
483
+ }
484
+ if (isPercentage(val)) {
485
+ return {
486
+ typ: 'Perc',
487
+ val: val.slice(0, -1)
488
+ };
489
+ }
490
+ const v = val.toLowerCase();
491
+ if (v == 'currentcolor' || val == 'transparent' || v in COLORS_NAMES) {
492
+ return {
493
+ typ: 'Color',
494
+ val,
495
+ kin: 'lit'
496
+ };
497
+ }
498
+ if (isIdent(val)) {
499
+ return {
500
+ typ: 'Iden',
501
+ val
502
+ };
503
+ }
504
+ if (val.charAt(0) == '#' && isHexColor(val)) {
505
+ return {
506
+ typ: 'Color',
507
+ val,
508
+ kin: 'hex'
509
+ };
510
+ }
511
+ if (val.charAt(0) == '#' && isHash(val)) {
512
+ return {
513
+ typ: 'Hash',
514
+ val
515
+ };
516
+ }
517
+ if ('"\''.includes(val.charAt(0))) {
518
+ return {
519
+ typ: 'Unclosed-string',
520
+ val
521
+ };
522
+ }
523
+ return {
524
+ typ: 'Literal',
525
+ val
526
+ };
527
+ }
933
528
  function parseTokens(tokens, options = {}) {
934
529
  for (let i = 0; i < tokens.length; i++) {
935
530
  const t = tokens[i];
@@ -937,6 +532,7 @@ function parseTokens(tokens, options = {}) {
937
532
  i + 1 == tokens.length ||
938
533
  ['Comma'].includes(tokens[i + 1].typ) ||
939
534
  (i > 0 &&
535
+ tokens[i + 1]?.typ != 'Literal' &&
940
536
  funcLike.includes(tokens[i - 1].typ) &&
941
537
  !['var', 'calc'].includes(tokens[i - 1].val))))) {
942
538
  tokens.splice(i--, 1);
@@ -983,7 +579,7 @@ function parseTokens(tokens, options = {}) {
983
579
  if (t.chi.length > 1) {
984
580
  /*(<AttrToken>t).chi =*/
985
581
  // @ts-ignore
986
- parseTokens(t.chi, options);
582
+ parseTokens(t.chi, t.typ);
987
583
  }
988
584
  // @ts-ignore
989
585
  t.chi.forEach(val => {
@@ -1095,8 +691,8 @@ function parseTokens(tokens, options = {}) {
1095
691
  // @ts-ignore
1096
692
  if (t.chi.length > 0) {
1097
693
  // @ts-ignore
1098
- parseTokens(t.chi, options);
1099
- if (t.typ == 'Pseudo-class-func' && t.val == ':is' && options.compress) {
694
+ parseTokens(t.chi, t.typ);
695
+ if (t.typ == 'Pseudo-class-func' && t.val == ':is' && options.minify) {
1100
696
  //
1101
697
  const count = t.chi.filter(t => t.typ != 'Comment').length;
1102
698
  if (count == 1 ||
@@ -1134,23 +730,5 @@ function parseTokens(tokens, options = {}) {
1134
730
  }
1135
731
  return tokens;
1136
732
  }
1137
- function getBlockType(chr) {
1138
- if (chr == ';') {
1139
- return { typ: 'Semi-colon' };
1140
- }
1141
- if (chr == '{') {
1142
- return { typ: 'Block-start' };
1143
- }
1144
- if (chr == '}') {
1145
- return { typ: 'Block-end' };
1146
- }
1147
- if (chr == '[') {
1148
- return { typ: 'Attr-start' };
1149
- }
1150
- if (chr == ']') {
1151
- return { typ: 'Attr-end' };
1152
- }
1153
- throw new Error(`unhandled token: '${chr}'`);
1154
- }
1155
733
 
1156
- export { parse };
734
+ export { parse, parseString, urlTokenMatcher };