@tbela99/css-parser 0.0.1-alpha3

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.
@@ -0,0 +1,1178 @@
1
+ import { isWhiteSpace, isPseudo, isAtKeyword, isFunction, isNumber, isDimension, parseDimension, isPercentage, isIdent, isHash, isNewLine, isIdentStart, isHexColor } from './utils/syntax.js';
2
+ import { renderToken } from '../renderer/render.js';
3
+ import { COLORS_NAMES } from '../renderer/utils/color.js';
4
+ import { hasDeclaration, deduplicateRule, deduplicate } from './deduplicate.js';
5
+
6
+ const urlTokenMatcher = /^(["']?)[a-zA-Z0-9_/.-][a-zA-Z0-9_/:.#?-]+(\1)$/;
7
+ const funcLike = ['Start-parens', 'Func', 'UrlFunc', 'Pseudo-class-func'];
8
+ async function parse(iterator, opt = {}) {
9
+ const errors = [];
10
+ const options = {
11
+ src: '',
12
+ sourcemap: false,
13
+ compress: false,
14
+ resolveImport: false,
15
+ resolveUrls: false,
16
+ removeEmpty: true,
17
+ ...opt
18
+ };
19
+ if (options.resolveImport) {
20
+ options.resolveUrls = true;
21
+ }
22
+ let ind = -1;
23
+ let lin = 1;
24
+ let col = 0;
25
+ const tokens = [];
26
+ const src = options.src;
27
+ const stack = [];
28
+ const ast = {
29
+ typ: "StyleSheet",
30
+ chi: []
31
+ };
32
+ const position = {
33
+ ind: Math.max(ind, 0),
34
+ lin: lin,
35
+ col: Math.max(col, 1)
36
+ };
37
+ let value;
38
+ let buffer = '';
39
+ let total = iterator.length;
40
+ let bytesIn = total;
41
+ let map = new Map;
42
+ let context = ast;
43
+ if (options.sourcemap) {
44
+ ast.loc = {
45
+ sta: {
46
+ ind: ind,
47
+ lin: lin,
48
+ col: col
49
+ },
50
+ src: ''
51
+ };
52
+ }
53
+ function getType(val) {
54
+ if (val === '') {
55
+ throw new Error('empty string?');
56
+ }
57
+ if (val == ':') {
58
+ return { typ: 'Colon' };
59
+ }
60
+ if (val == ')') {
61
+ return { typ: 'End-parens' };
62
+ }
63
+ if (val == '(') {
64
+ return { typ: 'Start-parens' };
65
+ }
66
+ if (val == '=') {
67
+ return { typ: 'Delim', val };
68
+ }
69
+ if (val == ';') {
70
+ return { typ: 'Semi-colon' };
71
+ }
72
+ if (val == ',') {
73
+ return { typ: 'Comma' };
74
+ }
75
+ if (val == '<') {
76
+ return { typ: 'Lt' };
77
+ }
78
+ if (val == '>') {
79
+ return { typ: 'Gt' };
80
+ }
81
+ if (isPseudo(val)) {
82
+ return val.endsWith('(') ? {
83
+ typ: 'Pseudo-class-func',
84
+ val: val.slice(0, -1),
85
+ chi: []
86
+ }
87
+ : {
88
+ typ: 'Pseudo-class',
89
+ val
90
+ };
91
+ }
92
+ if (isAtKeyword(val)) {
93
+ return {
94
+ typ: 'At-rule',
95
+ val: val.slice(1)
96
+ };
97
+ }
98
+ if (isFunction(val)) {
99
+ val = val.slice(0, -1);
100
+ return {
101
+ typ: val == 'url' ? 'UrlFunc' : 'Func',
102
+ val,
103
+ chi: []
104
+ };
105
+ }
106
+ if (isNumber(val)) {
107
+ return {
108
+ typ: 'Number',
109
+ val
110
+ };
111
+ }
112
+ if (isDimension(val)) {
113
+ return parseDimension(val);
114
+ }
115
+ if (isPercentage(val)) {
116
+ return {
117
+ typ: 'Perc',
118
+ val: val.slice(0, -1)
119
+ };
120
+ }
121
+ if (val == 'currentColor') {
122
+ return {
123
+ typ: 'Color',
124
+ val,
125
+ kin: 'lit'
126
+ };
127
+ }
128
+ if (isIdent(val)) {
129
+ return {
130
+ typ: 'Iden',
131
+ val
132
+ };
133
+ }
134
+ if (val.charAt(0) == '#' && isHash(val)) {
135
+ return {
136
+ typ: 'Hash',
137
+ val
138
+ };
139
+ }
140
+ if ('"\''.includes(val.charAt(0))) {
141
+ return {
142
+ typ: 'Unclosed-string',
143
+ val
144
+ };
145
+ }
146
+ return {
147
+ typ: 'Literal',
148
+ val
149
+ };
150
+ }
151
+ // consume and throw away
152
+ function consume(open, close) {
153
+ let count = 1;
154
+ let chr;
155
+ while (true) {
156
+ chr = next();
157
+ if (chr == '\\') {
158
+ if (peek() === '') {
159
+ break;
160
+ }
161
+ continue;
162
+ }
163
+ else if (chr == '/' && peek() == '*') {
164
+ next();
165
+ while (true) {
166
+ chr = next();
167
+ if (chr === '') {
168
+ break;
169
+ }
170
+ if (chr == '*' && peek() == '/') {
171
+ next();
172
+ break;
173
+ }
174
+ }
175
+ }
176
+ else if (chr == close) {
177
+ count--;
178
+ }
179
+ else if (chr == open) {
180
+ count++;
181
+ }
182
+ if (chr === '' || count == 0) {
183
+ break;
184
+ }
185
+ }
186
+ }
187
+ async function parseNode(tokens) {
188
+ let i;
189
+ let loc;
190
+ for (i = 0; i < tokens.length; i++) {
191
+ if (tokens[i].typ == 'Comment') {
192
+ // @ts-ignore
193
+ context.chi.push(tokens[i]);
194
+ const position = map.get(tokens[i]);
195
+ loc = {
196
+ sta: position,
197
+ src
198
+ };
199
+ if (options.sourcemap) {
200
+ tokens[i].loc = loc;
201
+ }
202
+ }
203
+ else if (tokens[i].typ != 'Whitespace') {
204
+ break;
205
+ }
206
+ }
207
+ tokens = tokens.slice(i);
208
+ if (tokens.length == 0) {
209
+ return null;
210
+ }
211
+ let delim = tokens.at(-1);
212
+ if (delim.typ == 'Semi-colon' || delim.typ == 'Block-start' || delim.typ == 'Block-end') {
213
+ tokens.pop();
214
+ }
215
+ else {
216
+ delim = { typ: 'Semi-colon' };
217
+ }
218
+ // @ts-ignore
219
+ while (['Whitespace', 'Bad-string', 'Bad-comment'].includes(tokens.at(-1)?.typ)) {
220
+ tokens.pop();
221
+ }
222
+ if (tokens.length == 0) {
223
+ return null;
224
+ }
225
+ if (tokens[0]?.typ == 'At-rule') {
226
+ const atRule = tokens.shift();
227
+ const position = map.get(atRule);
228
+ if (atRule.val == 'charset' && position.ind > 0) {
229
+ errors.push({ action: 'drop', message: 'invalid @charset', location: { src, ...position } });
230
+ return null;
231
+ }
232
+ // @ts-ignore
233
+ while (['Whitespace'].includes(tokens[0]?.typ)) {
234
+ tokens.shift();
235
+ }
236
+ if (atRule.val == 'import') {
237
+ // only @charset and @layer are accepted before @import
238
+ if (context.chi.length > 0) {
239
+ let i = context.chi.length;
240
+ while (i--) {
241
+ const type = context.chi[i].typ;
242
+ if (type == 'Comment') {
243
+ continue;
244
+ }
245
+ if (type != 'AtRule') {
246
+ errors.push({ action: 'drop', message: 'invalid @import', location: { src, ...position } });
247
+ return null;
248
+ }
249
+ const name = context.chi[i].nam;
250
+ if (name != 'charset' && name != 'import' && name != 'layer') {
251
+ errors.push({ action: 'drop', message: 'invalid @import', location: { src, ...position } });
252
+ return null;
253
+ }
254
+ break;
255
+ }
256
+ }
257
+ // @ts-ignore
258
+ if (tokens[0]?.typ != 'String' && tokens[0]?.typ != 'UrlFunc') {
259
+ errors.push({ action: 'drop', message: 'invalid @import', location: { src, ...position } });
260
+ return null;
261
+ }
262
+ // @ts-ignore
263
+ if (tokens[0].typ == 'UrlFunc' && tokens[1]?.typ != 'Url-token' && tokens[1]?.typ != 'String') {
264
+ errors.push({ action: 'drop', message: 'invalid @import', location: { src, ...position } });
265
+ return null;
266
+ }
267
+ }
268
+ if (atRule.val == 'import') {
269
+ // @ts-ignore
270
+ if (tokens[0].typ == 'UrlFunc' && tokens[1].typ == 'Url-token') {
271
+ tokens.shift();
272
+ // @ts-ignore
273
+ tokens[0].typ = 'String';
274
+ // @ts-ignore
275
+ tokens[0].val = `"${tokens[0].val}"`;
276
+ }
277
+ // @ts-ignore
278
+ if (tokens[0].typ == 'String') {
279
+ if (options.resolveImport) {
280
+ const url = tokens[0].val.slice(1, -1);
281
+ try {
282
+ // @ts-ignore
283
+ const root = await options.load(url, options.src).then((src) => {
284
+ bytesIn += src.length;
285
+ // @ts-ignore
286
+ return parse(src, Object.assign({}, options, { src: options.resolve(url, options.src).absolute }));
287
+ });
288
+ context.chi.push(...root.ast.chi);
289
+ if (root.errors.length > 0) {
290
+ errors.push(...root.errors);
291
+ }
292
+ return null;
293
+ }
294
+ catch (error) {
295
+ console.error(error);
296
+ }
297
+ }
298
+ }
299
+ }
300
+ // https://www.w3.org/TR/css-nesting-1/#conditionals
301
+ // allowed nesting at-rules
302
+ // there must be a top level rule in the stack
303
+ const node = {
304
+ typ: 'AtRule',
305
+ nam: renderToken(atRule, { removeComments: true }),
306
+ val: tokens.reduce((acc, curr, index, array) => {
307
+ if (curr.typ == 'Whitespace') {
308
+ if (array[index + 1]?.typ == 'Start-parens' ||
309
+ array[index - 1]?.typ == 'End-parens' ||
310
+ array[index - 1]?.typ == 'Func') {
311
+ return acc;
312
+ }
313
+ }
314
+ return acc + renderToken(curr, { removeComments: true });
315
+ }, '')
316
+ };
317
+ if (delim.typ == 'Block-start') {
318
+ node.chi = [];
319
+ }
320
+ loc = {
321
+ sta: position,
322
+ src
323
+ };
324
+ if (options.sourcemap) {
325
+ node.loc = loc;
326
+ }
327
+ // @ts-ignore
328
+ context.chi.push(node);
329
+ return delim.typ == 'Block-start' ? node : null;
330
+ }
331
+ else {
332
+ // rule
333
+ if (delim.typ == 'Block-start') {
334
+ const position = map.get(tokens[0]);
335
+ if (context.typ == 'Rule') {
336
+ if (tokens[0]?.typ == 'Iden') {
337
+ errors.push({ action: 'drop', message: 'invalid nesting rule', location: { src, ...position } });
338
+ return null;
339
+ }
340
+ }
341
+ const sel = parseTokens(tokens, { compress: options.compress }).map(curr => renderToken(curr, { compress: true }));
342
+ const raw = [...new Set(sel.reduce((acc, curr) => {
343
+ if (curr == ',') {
344
+ acc.push('');
345
+ }
346
+ else {
347
+ acc[acc.length - 1] += curr;
348
+ }
349
+ return acc;
350
+ }, ['']))];
351
+ const node = {
352
+ typ: 'Rule',
353
+ // @ts-ignore
354
+ sel: raw.join(','),
355
+ chi: []
356
+ };
357
+ Object.defineProperty(node, 'raw', { enumerable: false, get: () => raw });
358
+ loc = {
359
+ sta: position,
360
+ src
361
+ };
362
+ if (options.sourcemap) {
363
+ node.loc = loc;
364
+ }
365
+ // @ts-ignore
366
+ context.chi.push(node);
367
+ return node;
368
+ }
369
+ else {
370
+ // declaration
371
+ // @ts-ignore
372
+ let name = null;
373
+ // @ts-ignore
374
+ let value = null;
375
+ for (let i = 0; i < tokens.length; i++) {
376
+ if (tokens[i].typ == 'Comment') {
377
+ continue;
378
+ }
379
+ if (tokens[i].typ == 'Colon') {
380
+ name = tokens.slice(0, i);
381
+ value = parseTokens(tokens.slice(i + 1), { parseColor: true, src: options.src, resolveUrls: options.resolveUrls, resolve: options.resolve, cwd: options.cwd });
382
+ }
383
+ }
384
+ if (name == null) {
385
+ name = tokens;
386
+ }
387
+ const position = map.get(name[0]);
388
+ if (name.length > 0) {
389
+ for (let i = 1; i < name.length; i++) {
390
+ if (name[i].typ != 'Whitespace' && name[i].typ != 'Comment') {
391
+ errors.push({
392
+ action: 'drop',
393
+ message: 'invalid declaration',
394
+ location: { src, ...position }
395
+ });
396
+ return null;
397
+ }
398
+ }
399
+ }
400
+ // if (name.length == 0) {
401
+ //
402
+ // errors.push({action: 'drop', message: 'invalid declaration', location: {src, ...position}});
403
+ // return null;
404
+ // }
405
+ if (value == null) {
406
+ errors.push({ action: 'drop', message: 'invalid declaration', location: { src, ...position } });
407
+ return null;
408
+ }
409
+ if (value.length == 0) {
410
+ errors.push({ action: 'drop', message: 'invalid declaration', location: { src, ...position } });
411
+ return null;
412
+ }
413
+ const node = {
414
+ typ: 'Declaration',
415
+ // @ts-ignore
416
+ nam: renderToken(name.shift(), { removeComments: true }),
417
+ // @ts-ignore
418
+ val: value
419
+ };
420
+ while (node.val[0]?.typ == 'Whitespace') {
421
+ node.val.shift();
422
+ }
423
+ if (node.val.length == 0) {
424
+ errors.push({ action: 'drop', message: 'invalid declaration', location: { src, ...position } });
425
+ return null;
426
+ }
427
+ // // location not needed for declaration
428
+ // loc = <Location>{
429
+ // sta: position,
430
+ // src
431
+ // };
432
+ //
433
+ // if (options.sourcemap) {
434
+ //
435
+ // node.loc = loc
436
+ // }
437
+ // @ts-ignore
438
+ context.chi.push(node);
439
+ return null;
440
+ }
441
+ }
442
+ }
443
+ function peek(count = 1) {
444
+ if (count == 1) {
445
+ return iterator.charAt(ind + 1);
446
+ }
447
+ return iterator.slice(ind + 1, ind + count + 1);
448
+ }
449
+ function prev(count = 1) {
450
+ if (count == 1) {
451
+ return ind == 0 ? '' : iterator.charAt(ind - 1);
452
+ }
453
+ return iterator.slice(ind - 1 - count, ind - 1);
454
+ }
455
+ function next(count = 1) {
456
+ let char = '';
457
+ while (count-- > 0 && ind < total) {
458
+ const codepoint = iterator.charCodeAt(++ind);
459
+ if (isNaN(codepoint)) {
460
+ return char;
461
+ }
462
+ char += iterator.charAt(ind);
463
+ if (isNewLine(codepoint)) {
464
+ lin++;
465
+ col = 0;
466
+ }
467
+ else {
468
+ col++;
469
+ }
470
+ }
471
+ return char;
472
+ }
473
+ function pushToken(token) {
474
+ tokens.push(token);
475
+ map.set(token, { ...position });
476
+ position.ind = ind;
477
+ position.lin = lin;
478
+ position.col = col == 0 ? 1 : col;
479
+ // }
480
+ }
481
+ function consumeWhiteSpace() {
482
+ let count = 0;
483
+ while (isWhiteSpace(iterator.charAt(count + ind + 1).charCodeAt(0))) {
484
+ count++;
485
+ }
486
+ next(count);
487
+ return count;
488
+ }
489
+ function consumeString(quoteStr) {
490
+ const quote = quoteStr;
491
+ let value;
492
+ let hasNewLine = false;
493
+ if (buffer.length > 0) {
494
+ pushToken(getType(buffer));
495
+ buffer = '';
496
+ }
497
+ buffer += quoteStr;
498
+ while (ind < total) {
499
+ value = peek();
500
+ if (ind >= total) {
501
+ pushToken({ typ: hasNewLine ? 'Bad-string' : 'Unclosed-string', val: buffer });
502
+ break;
503
+ }
504
+ if (value == '\\') {
505
+ const sequence = peek(6);
506
+ let escapeSequence = '';
507
+ let codepoint;
508
+ let i;
509
+ for (i = 1; i < sequence.length; i++) {
510
+ codepoint = sequence.charCodeAt(i);
511
+ if (codepoint == 0x20 ||
512
+ (codepoint >= 0x61 && codepoint <= 0x66) ||
513
+ (codepoint >= 0x41 && codepoint <= 0x46) ||
514
+ (codepoint >= 0x30 && codepoint <= 0x39)) {
515
+ escapeSequence += sequence[i];
516
+ if (codepoint == 0x20) {
517
+ break;
518
+ }
519
+ continue;
520
+ }
521
+ break;
522
+ }
523
+ // not hex or new line
524
+ // @ts-ignore
525
+ if (i == 1 && !isNewLine(codepoint)) {
526
+ buffer += sequence[i];
527
+ next(2);
528
+ continue;
529
+ }
530
+ if (escapeSequence.trimEnd().length > 0) {
531
+ const codepoint = Number(`0x${escapeSequence.trimEnd()}`);
532
+ if (codepoint == 0 ||
533
+ // leading surrogate
534
+ (0xD800 <= codepoint && codepoint <= 0xDBFF) ||
535
+ // trailing surrogate
536
+ (0xDC00 <= codepoint && codepoint <= 0xDFFF)) {
537
+ buffer += String.fromCodePoint(0xFFFD);
538
+ }
539
+ else {
540
+ buffer += String.fromCodePoint(codepoint);
541
+ }
542
+ next(escapeSequence.length + 1);
543
+ continue;
544
+ }
545
+ // buffer += value;
546
+ if (ind >= total) {
547
+ // drop '\\' at the end
548
+ pushToken(getType(buffer));
549
+ break;
550
+ }
551
+ buffer += next(2);
552
+ continue;
553
+ }
554
+ if (value == quote) {
555
+ buffer += value;
556
+ pushToken({ typ: hasNewLine ? 'Bad-string' : 'String', val: buffer });
557
+ next();
558
+ // i += value.length;
559
+ buffer = '';
560
+ break;
561
+ }
562
+ if (isNewLine(value.charCodeAt(0))) {
563
+ hasNewLine = true;
564
+ }
565
+ if (hasNewLine && value == ';') {
566
+ pushToken({ typ: 'Bad-string', val: buffer });
567
+ buffer = '';
568
+ break;
569
+ }
570
+ buffer += value;
571
+ // i += value.length;
572
+ next();
573
+ }
574
+ }
575
+ while (ind < total) {
576
+ value = next();
577
+ if (ind >= total) {
578
+ if (buffer.length > 0) {
579
+ pushToken(getType(buffer));
580
+ buffer = '';
581
+ }
582
+ break;
583
+ }
584
+ if (isWhiteSpace(value.charCodeAt(0))) {
585
+ if (buffer.length > 0) {
586
+ pushToken(getType(buffer));
587
+ buffer = '';
588
+ }
589
+ while (ind < total) {
590
+ value = next();
591
+ if (ind >= total) {
592
+ break;
593
+ }
594
+ if (!isWhiteSpace(value.charCodeAt(0))) {
595
+ break;
596
+ }
597
+ }
598
+ pushToken({ typ: 'Whitespace' });
599
+ buffer = '';
600
+ if (ind >= total) {
601
+ break;
602
+ }
603
+ }
604
+ switch (value) {
605
+ case '/':
606
+ if (buffer.length > 0 && tokens.at(-1)?.typ == 'Whitespace') {
607
+ pushToken(getType(buffer));
608
+ buffer = '';
609
+ if (peek() != '*') {
610
+ pushToken(getType(value));
611
+ break;
612
+ }
613
+ }
614
+ buffer += value;
615
+ if (peek() == '*') {
616
+ buffer += '*';
617
+ // i++;
618
+ next();
619
+ while (ind < total) {
620
+ value = next();
621
+ if (ind >= total) {
622
+ pushToken({
623
+ typ: 'Bad-comment', val: buffer
624
+ });
625
+ break;
626
+ }
627
+ if (value == '\\') {
628
+ buffer += value;
629
+ value = next();
630
+ if (ind >= total) {
631
+ pushToken({
632
+ typ: 'Bad-comment',
633
+ val: buffer
634
+ });
635
+ break;
636
+ }
637
+ buffer += value;
638
+ continue;
639
+ }
640
+ if (value == '*') {
641
+ buffer += value;
642
+ value = next();
643
+ if (ind >= total) {
644
+ pushToken({
645
+ typ: 'Bad-comment', val: buffer
646
+ });
647
+ break;
648
+ }
649
+ buffer += value;
650
+ if (value == '/') {
651
+ pushToken({ typ: 'Comment', val: buffer });
652
+ buffer = '';
653
+ break;
654
+ }
655
+ }
656
+ else {
657
+ buffer += value;
658
+ }
659
+ }
660
+ }
661
+ // else {
662
+ //
663
+ // pushToken(getType(buffer));
664
+ // buffer = '';
665
+ // }
666
+ break;
667
+ case '<':
668
+ if (buffer.length > 0) {
669
+ pushToken(getType(buffer));
670
+ buffer = '';
671
+ }
672
+ buffer += value;
673
+ value = next();
674
+ if (ind >= total) {
675
+ break;
676
+ }
677
+ if (peek(3) == '!--') {
678
+ while (ind < total) {
679
+ value = next();
680
+ if (ind >= total) {
681
+ break;
682
+ }
683
+ buffer += value;
684
+ if (value == '>' && prev(2) == '--') {
685
+ pushToken({
686
+ typ: 'CDOCOMM',
687
+ val: buffer
688
+ });
689
+ buffer = '';
690
+ break;
691
+ }
692
+ }
693
+ }
694
+ if (ind >= total) {
695
+ pushToken({ typ: 'BADCDO', val: buffer });
696
+ buffer = '';
697
+ }
698
+ break;
699
+ case '\\':
700
+ value = next();
701
+ // EOF
702
+ if (ind + 1 >= total) {
703
+ // end of stream ignore \\
704
+ pushToken(getType(buffer));
705
+ buffer = '';
706
+ break;
707
+ }
708
+ buffer += value;
709
+ break;
710
+ case '"':
711
+ case "'":
712
+ consumeString(value);
713
+ break;
714
+ case '~':
715
+ case '|':
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
+ buffer = value;
738
+ break;
739
+ case '>':
740
+ if (tokens[tokens.length - 1]?.typ == 'Whitespace') {
741
+ tokens.pop();
742
+ }
743
+ pushToken({ typ: 'Gt' });
744
+ consumeWhiteSpace();
745
+ break;
746
+ case ':':
747
+ case ',':
748
+ case '=':
749
+ if (buffer.length > 0) {
750
+ pushToken(getType(buffer));
751
+ buffer = '';
752
+ }
753
+ if (value == ':' && ':' == peek()) {
754
+ buffer += value + next();
755
+ break;
756
+ }
757
+ // if (value == ',' && tokens[tokens.length - 1]?.typ == 'Whitespace') {
758
+ //
759
+ // tokens.pop();
760
+ // }
761
+ pushToken(getType(value));
762
+ buffer = '';
763
+ while (isWhiteSpace(peek().charCodeAt(0))) {
764
+ next();
765
+ }
766
+ break;
767
+ case ')':
768
+ if (buffer.length > 0) {
769
+ pushToken(getType(buffer));
770
+ buffer = '';
771
+ }
772
+ pushToken({ typ: 'End-parens' });
773
+ break;
774
+ case '(':
775
+ if (buffer.length == 0) {
776
+ pushToken({ typ: 'Start-parens' });
777
+ }
778
+ else {
779
+ buffer += value;
780
+ pushToken(getType(buffer));
781
+ buffer = '';
782
+ const token = tokens[tokens.length - 1];
783
+ if (token.typ == 'UrlFunc' /* && token.chi == 'url' */) {
784
+ // consume either string or url token
785
+ let whitespace = '';
786
+ value = peek();
787
+ while (isWhiteSpace(value.charCodeAt(0))) {
788
+ whitespace += value;
789
+ }
790
+ if (whitespace.length > 0) {
791
+ next(whitespace.length);
792
+ }
793
+ value = peek();
794
+ if (value == '"' || value == "'") {
795
+ consumeString(next());
796
+ let token = tokens[tokens.length - 1];
797
+ if (['String', 'Literal'].includes(token.typ) && urlTokenMatcher.test(token.val)) {
798
+ if (token.val.slice(1, 6) != 'data:') {
799
+ if (token.typ == 'String') {
800
+ token.val = token.val.slice(1, -1);
801
+ }
802
+ // @ts-ignore
803
+ token.typ = 'Url-token';
804
+ }
805
+ }
806
+ break;
807
+ }
808
+ else {
809
+ buffer = '';
810
+ do {
811
+ let cp = value.charCodeAt(0);
812
+ // EOF -
813
+ if (cp == null) {
814
+ pushToken({ typ: 'Bad-url-token', val: buffer });
815
+ break;
816
+ }
817
+ // ')'
818
+ if (cp == 0x29 || cp == null) {
819
+ if (buffer.length == 0) {
820
+ pushToken({ typ: 'Bad-url-token', val: '' });
821
+ }
822
+ else {
823
+ pushToken({ typ: 'Url-token', val: buffer });
824
+ }
825
+ if (cp != null) {
826
+ pushToken(getType(next()));
827
+ }
828
+ break;
829
+ }
830
+ if (isWhiteSpace(cp)) {
831
+ whitespace = next();
832
+ while (true) {
833
+ value = peek();
834
+ cp = value.charCodeAt(0);
835
+ if (isWhiteSpace(cp)) {
836
+ whitespace += value;
837
+ continue;
838
+ }
839
+ break;
840
+ }
841
+ if (cp == null || cp == 0x29) {
842
+ continue;
843
+ }
844
+ // bad url token
845
+ buffer += next(whitespace.length);
846
+ do {
847
+ value = peek();
848
+ cp = value.charCodeAt(0);
849
+ if (cp == null || cp == 0x29) {
850
+ break;
851
+ }
852
+ buffer += next();
853
+ } while (true);
854
+ pushToken({ typ: 'Bad-url-token', val: buffer });
855
+ continue;
856
+ }
857
+ buffer += next();
858
+ value = peek();
859
+ } while (true);
860
+ buffer = '';
861
+ }
862
+ }
863
+ }
864
+ break;
865
+ case '[':
866
+ case ']':
867
+ case '{':
868
+ case '}':
869
+ case ';':
870
+ if (buffer.length > 0) {
871
+ pushToken(getType(buffer));
872
+ buffer = '';
873
+ }
874
+ pushToken(getBlockType(value));
875
+ let node = null;
876
+ if (value == '{' || value == ';') {
877
+ node = await parseNode(tokens);
878
+ if (node != null) {
879
+ stack.push(node);
880
+ // @ts-ignore
881
+ context = node;
882
+ }
883
+ else if (value == '{') {
884
+ // node == null
885
+ // consume and throw away until the closing '}' or EOF
886
+ consume('{', '}');
887
+ }
888
+ tokens.length = 0;
889
+ map.clear();
890
+ }
891
+ else if (value == '}') {
892
+ await parseNode(tokens);
893
+ const previousNode = stack.pop();
894
+ // @ts-ignore
895
+ context = stack[stack.length - 1] || ast;
896
+ // @ts-ignore
897
+ if (options.removeEmpty && previousNode != null && previousNode.chi.length == 0 && context.chi[context.chi.length - 1] == previousNode) {
898
+ context.chi.pop();
899
+ }
900
+ else if (previousNode != null && previousNode != ast && options.compress) {
901
+ // @ts-ignore
902
+ if (hasDeclaration(previousNode)) {
903
+ deduplicateRule(previousNode);
904
+ }
905
+ else {
906
+ deduplicate(previousNode, options);
907
+ }
908
+ }
909
+ tokens.length = 0;
910
+ map.clear();
911
+ buffer = '';
912
+ }
913
+ break;
914
+ case '!':
915
+ if (buffer.length > 0) {
916
+ pushToken(getType(buffer));
917
+ buffer = '';
918
+ }
919
+ const important = peek(9);
920
+ if (important == 'important') {
921
+ if (tokens[tokens.length - 1]?.typ == 'Whitespace') {
922
+ tokens.pop();
923
+ }
924
+ pushToken({ typ: 'Important' });
925
+ next(9);
926
+ buffer = '';
927
+ break;
928
+ }
929
+ buffer = '!';
930
+ break;
931
+ default:
932
+ buffer += value;
933
+ break;
934
+ }
935
+ }
936
+ if (buffer.length > 0) {
937
+ pushToken(getType(buffer));
938
+ }
939
+ if (tokens.length > 0) {
940
+ await parseNode(tokens);
941
+ }
942
+ if (options.compress) {
943
+ while (stack.length > 0) {
944
+ const node = stack.pop();
945
+ if (hasDeclaration(node)) {
946
+ deduplicateRule(node, options);
947
+ }
948
+ else {
949
+ deduplicate(node, options);
950
+ }
951
+ }
952
+ if (ast.chi.length > 0) {
953
+ deduplicate(ast, options);
954
+ }
955
+ }
956
+ return { ast, errors, bytesIn };
957
+ }
958
+ function parseTokens(tokens, options = {}) {
959
+ for (let i = 0; i < tokens.length; i++) {
960
+ const t = tokens[i];
961
+ if (t.typ == 'Whitespace' && ((i == 0 ||
962
+ i + 1 == tokens.length ||
963
+ ['Comma', 'Start-parens'].includes(tokens[i + 1].typ) ||
964
+ (i > 0 && funcLike.includes(tokens[i - 1].typ))))) {
965
+ tokens.splice(i--, 1);
966
+ continue;
967
+ }
968
+ if (t.typ == 'Colon') {
969
+ const typ = tokens[i + 1]?.typ;
970
+ if (typ != null) {
971
+ if (typ == 'Func') {
972
+ tokens[i + 1].val = ':' + tokens[i + 1].val;
973
+ tokens[i + 1].typ = 'Pseudo-class-func';
974
+ }
975
+ else if (typ == 'Iden') {
976
+ tokens[i + 1].val = ':' + tokens[i + 1].val;
977
+ tokens[i + 1].typ = 'Pseudo-class';
978
+ }
979
+ if (typ == 'Func' || typ == 'Iden') {
980
+ tokens.splice(i, 1);
981
+ i--;
982
+ continue;
983
+ }
984
+ }
985
+ }
986
+ if (t.typ == 'Attr-start') {
987
+ let k = i;
988
+ let inAttr = 1;
989
+ while (++k < tokens.length) {
990
+ if (tokens[k].typ == 'Attr-end') {
991
+ inAttr--;
992
+ }
993
+ else if (tokens[k].typ == 'Attr-start') {
994
+ inAttr++;
995
+ }
996
+ if (inAttr == 0) {
997
+ break;
998
+ }
999
+ }
1000
+ Object.assign(t, { typ: 'Attr', chi: tokens.splice(i + 1, k - i) });
1001
+ // @ts-ignore
1002
+ if (t.chi.at(-1).typ == 'Attr-end') {
1003
+ // @ts-ignore
1004
+ t.chi.pop();
1005
+ // @ts-ignore
1006
+ if (t.chi.length > 1) {
1007
+ /*(<AttrToken>t).chi =*/
1008
+ // @ts-ignore
1009
+ parseTokens(t.chi, options);
1010
+ }
1011
+ // @ts-ignore
1012
+ t.chi.forEach(val => {
1013
+ if (val.typ == 'String') {
1014
+ const slice = val.val.slice(1, -1);
1015
+ if ((slice.charAt(0) != '-' || (slice.charAt(0) == '-' && isIdentStart(slice.charCodeAt(1)))) && isIdent(slice)) {
1016
+ Object.assign(val, { typ: 'Iden', val: slice });
1017
+ }
1018
+ }
1019
+ });
1020
+ }
1021
+ continue;
1022
+ }
1023
+ if (funcLike.includes(t.typ)) {
1024
+ let parens = 1;
1025
+ let k = i;
1026
+ while (++k < tokens.length) {
1027
+ if (tokens[k].typ == 'Colon') {
1028
+ const typ = tokens[k + 1]?.typ;
1029
+ if (typ != null) {
1030
+ if (typ == 'Iden') {
1031
+ tokens[k + 1].typ = 'Pseudo-class';
1032
+ tokens[k + 1].val = ':' + tokens[k + 1].val;
1033
+ }
1034
+ else if (typ == 'Func') {
1035
+ tokens[k + 1].typ = 'Pseudo-class-func';
1036
+ tokens[k + 1].val = ':' + tokens[k + 1].val;
1037
+ }
1038
+ if (typ == 'Func' || typ == 'Iden') {
1039
+ tokens.splice(k, 1);
1040
+ k--;
1041
+ continue;
1042
+ }
1043
+ }
1044
+ }
1045
+ if (funcLike.includes(tokens[k].typ)) {
1046
+ parens++;
1047
+ }
1048
+ else if (tokens[k].typ == 'End-parens') {
1049
+ parens--;
1050
+ }
1051
+ if (parens == 0) {
1052
+ break;
1053
+ }
1054
+ }
1055
+ // @ts-ignore
1056
+ t.chi = tokens.splice(i + 1, k - i);
1057
+ // @ts-ignore
1058
+ if (t.chi.at(-1)?.typ == 'End-parens') {
1059
+ // @ts-ignore
1060
+ t.chi.pop();
1061
+ }
1062
+ // @ts-ignore
1063
+ if (options.parseColor && ['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk'].includes(t.val)) {
1064
+ let isColor = true;
1065
+ // @ts-ignore
1066
+ for (const v of t.chi) {
1067
+ if (v.typ == 'Func' && v.val == 'var') {
1068
+ isColor = false;
1069
+ break;
1070
+ }
1071
+ }
1072
+ if (!isColor) {
1073
+ continue;
1074
+ }
1075
+ // @ts-ignore
1076
+ t.typ = 'Color';
1077
+ // @ts-ignore
1078
+ t.kin = t.val;
1079
+ // @ts-ignore
1080
+ let m = t.chi.length;
1081
+ while (m-- > 0) {
1082
+ // @ts-ignore
1083
+ if (t.chi[m].typ == 'Literal') {
1084
+ // @ts-ignore
1085
+ if (t.chi[m + 1]?.typ == 'Whitespace') {
1086
+ // @ts-ignore
1087
+ t.chi.splice(m + 1, 1);
1088
+ }
1089
+ // @ts-ignore
1090
+ if (t.chi[m - 1]?.typ == 'Whitespace') {
1091
+ // @ts-ignore
1092
+ t.chi.splice(m - 1, 1);
1093
+ m--;
1094
+ }
1095
+ }
1096
+ }
1097
+ }
1098
+ else if (t.typ == 'UrlFunc') {
1099
+ // @ts-ignore
1100
+ if (t.chi[0]?.typ == 'String') {
1101
+ // @ts-ignore
1102
+ const value = t.chi[0].val.slice(1, -1);
1103
+ if (t.chi[0].val.slice(1, 5) != 'data:' && urlTokenMatcher.test(value)) {
1104
+ // @ts-ignore
1105
+ t.chi[0].typ = 'Url-token';
1106
+ // @ts-ignore
1107
+ t.chi[0].val = options.src !== '' && options.resolveUrls ? options.resolve(value, options.src).absolute : value;
1108
+ }
1109
+ }
1110
+ if (t.chi[0]?.typ == 'Url-token') {
1111
+ if (options.src !== '' && options.resolveUrls) {
1112
+ // @ts-ignore
1113
+ t.chi[0].val = options.resolve(t.chi[0].val, options.src, options.cwd).relative;
1114
+ }
1115
+ }
1116
+ }
1117
+ // @ts-ignore
1118
+ if (t.chi.length > 0) {
1119
+ // @ts-ignore
1120
+ parseTokens(t.chi, options);
1121
+ if (t.typ == 'Pseudo-class-func' && t.val == ':is' && options.compress) {
1122
+ //
1123
+ const count = t.chi.filter(t => t.typ != 'Comment').length;
1124
+ if (count == 1 ||
1125
+ (i == 0 &&
1126
+ (tokens[i + 1]?.typ == 'Comma' || tokens.length == i + 1)) ||
1127
+ (tokens[i - 1]?.typ == 'Comma' && (tokens[i + 1]?.typ == 'Comma' || tokens.length == i + 1))) {
1128
+ tokens.splice(i, 1, ...t.chi);
1129
+ i = Math.max(0, i - t.chi.length);
1130
+ }
1131
+ }
1132
+ }
1133
+ continue;
1134
+ }
1135
+ if (options.parseColor) {
1136
+ if (t.typ == 'Iden') {
1137
+ // named color
1138
+ const value = t.val.toLowerCase();
1139
+ if (COLORS_NAMES[value] != null) {
1140
+ Object.assign(t, {
1141
+ typ: 'Color',
1142
+ val: COLORS_NAMES[value].length < value.length ? COLORS_NAMES[value] : value,
1143
+ kin: 'hex'
1144
+ });
1145
+ }
1146
+ continue;
1147
+ }
1148
+ if (t.typ == 'Hash' && isHexColor(t.val)) {
1149
+ // hex color
1150
+ // @ts-ignore
1151
+ t.typ = 'Color';
1152
+ // @ts-ignore
1153
+ t.kin = 'hex';
1154
+ }
1155
+ }
1156
+ }
1157
+ return tokens;
1158
+ }
1159
+ function getBlockType(chr) {
1160
+ if (chr == ';') {
1161
+ return { typ: 'Semi-colon' };
1162
+ }
1163
+ if (chr == '{') {
1164
+ return { typ: 'Block-start' };
1165
+ }
1166
+ if (chr == '}') {
1167
+ return { typ: 'Block-end' };
1168
+ }
1169
+ if (chr == '[') {
1170
+ return { typ: 'Attr-start' };
1171
+ }
1172
+ if (chr == ']') {
1173
+ return { typ: 'Attr-end' };
1174
+ }
1175
+ throw new Error(`unhandled token: '${chr}'`);
1176
+ }
1177
+
1178
+ export { parse };