@tbela99/css-parser 0.0.1-alpha5 → 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,59 +1,303 @@
1
- import { isIdentStart, isWhiteSpace } from './utils/syntax.js';
2
- import { getConfig } from './utils/config.js';
3
- import { PropertyList } from './declaration/list.js';
4
- import { eq } from './utils/eq.js';
1
+ import { isIdentStart, isIdent, isFunction, isWhiteSpace } from '../parser/utils/syntax.js';
2
+ import { PropertyList } from '../parser/declaration/list.js';
3
+ import { eq } from '../parser/utils/eq.js';
5
4
  import { render } from '../renderer/render.js';
5
+ import '../renderer/utils/color.js';
6
6
 
7
- const configuration = getConfig();
8
7
  const combinators = ['+', '>', '~'];
9
8
  const notEndingWith = ['(', '['].concat(combinators);
10
- function wrapNodes(previous, node, match, ast, i, nodeIndex) {
11
- // @ts-ignore
12
- let pSel = match.selector1.reduce(reducer, []).join(',');
13
- // @ts-ignore
14
- let nSel = match.selector2.reduce(reducer, []).join(',');
15
- // @ts-ignore
16
- const wrapper = { ...previous, chi: [], sel: match.match.reduce(reducer, []).join(',') };
17
- // @ts-ignore
18
- Object.defineProperty(wrapper, 'raw', {
19
- enumerable: false,
20
- writable: true,
9
+ function minify(ast, options = {}, recursive = false) {
10
+ function wrapNodes(previous, node, match, ast, i, nodeIndex) {
21
11
  // @ts-ignore
22
- value: match.match.map(t => t.slice())
23
- });
24
- if (pSel == '&' || pSel === '') {
12
+ let pSel = match.selector1.reduce(reducer, []).join(',');
25
13
  // @ts-ignore
26
- wrapper.chi.push(...previous.chi);
14
+ let nSel = match.selector2.reduce(reducer, []).join(',');
27
15
  // @ts-ignore
28
- if ((nSel == '&' || nSel === '') && hasOnlyDeclarations(previous)) {
16
+ const wrapper = { ...previous, chi: [], sel: match.match.reduce(reducer, []).join(',') };
17
+ // @ts-ignore
18
+ Object.defineProperty(wrapper, 'raw', {
19
+ enumerable: false,
20
+ writable: true,
21
+ // @ts-ignore
22
+ value: match.match.map(t => t.slice())
23
+ });
24
+ if (pSel == '&' || pSel === '') {
25
+ // @ts-ignore
26
+ wrapper.chi.push(...previous.chi);
29
27
  // @ts-ignore
30
- wrapper.chi.push(...node.chi);
28
+ if ((nSel == '&' || nSel === '') && hasOnlyDeclarations(previous)) {
29
+ // @ts-ignore
30
+ wrapper.chi.push(...node.chi);
31
+ }
32
+ else {
33
+ // @ts-ignore
34
+ wrapper.chi.push(node);
35
+ }
31
36
  }
32
37
  else {
33
38
  // @ts-ignore
34
- wrapper.chi.push(node);
39
+ wrapper.chi.push(previous, node);
35
40
  }
41
+ // @ts-ignore
42
+ ast.chi.splice(i, 1, wrapper);
43
+ // @ts-ignore
44
+ ast.chi.splice(nodeIndex, 1);
45
+ // @ts-ignore
46
+ previous.sel = pSel;
47
+ // @ts-ignore
48
+ previous.raw = match.selector1;
49
+ // @ts-ignore
50
+ node.sel = nSel;
51
+ // @ts-ignore
52
+ node.raw = match.selector2;
53
+ reduceRuleSelector(wrapper);
54
+ return wrapper;
55
+ }
56
+ function reducer(acc, curr, index, array) {
57
+ // trim :is()
58
+ if (array.length == 1 && array[0][0] == ':is(' && array[0].at(-1) == ')') {
59
+ curr = curr.slice(1, -1);
60
+ }
61
+ if (curr[0] == '&') {
62
+ if (curr[1] == ' ' && !isIdent(curr[2]) && !isFunction(curr[2])) {
63
+ curr.splice(0, 2);
64
+ }
65
+ else if (combinators.includes(curr[1])) {
66
+ curr.splice(0, 1);
67
+ }
68
+ }
69
+ else if (ast.typ == 'Rule' && (isIdent(curr[0]) || isFunction(curr[0]))) {
70
+ curr.unshift('&', ' ');
71
+ }
72
+ acc.push(curr.join(''));
73
+ return acc;
36
74
  }
37
- else {
75
+ function diff(n1, n2, options = {}) {
76
+ let node1 = n1;
77
+ let node2 = n2;
78
+ let exchanged = false;
79
+ if (node1.chi.length > node2.chi.length) {
80
+ const t = node1;
81
+ node1 = node2;
82
+ node2 = t;
83
+ exchanged = true;
84
+ }
85
+ let i = node1.chi.length;
86
+ let j = node2.chi.length;
87
+ if (i == 0 || j == 0) {
88
+ // @ts-ignore
89
+ return null;
90
+ }
91
+ // @ts-ignore
92
+ const raw1 = node1.raw;
38
93
  // @ts-ignore
39
- wrapper.chi.push(previous, node);
94
+ const raw2 = node2.raw;
95
+ // @ts-ignore
96
+ node1 = { ...node1, chi: node1.chi.slice() };
97
+ node2 = { ...node2, chi: node2.chi.slice() };
98
+ if (raw1 != null) {
99
+ Object.defineProperty(node1, 'raw', { enumerable: false, writable: true, value: raw1 });
100
+ }
101
+ if (raw2 != null) {
102
+ Object.defineProperty(node2, 'raw', { enumerable: false, writable: true, value: raw2 });
103
+ }
104
+ const intersect = [];
105
+ while (i--) {
106
+ if (node1.chi[i].typ == 'Comment') {
107
+ continue;
108
+ }
109
+ j = node2.chi.length;
110
+ if (j == 0) {
111
+ break;
112
+ }
113
+ while (j--) {
114
+ if (node2.chi[j].typ == 'Comment') {
115
+ continue;
116
+ }
117
+ if (node1.chi[i].nam == node2.chi[j].nam) {
118
+ if (eq(node1.chi[i], node2.chi[j])) {
119
+ intersect.push(node1.chi[i]);
120
+ node1.chi.splice(i, 1);
121
+ node2.chi.splice(j, 1);
122
+ break;
123
+ }
124
+ }
125
+ }
126
+ }
127
+ // @ts-ignore
128
+ const result = (intersect.length == 0 ? null : {
129
+ ...node1,
130
+ // @ts-ignore
131
+ sel: [...new Set([...(n1?.raw?.reduce(reducer, []) || splitRule(n1.sel)).concat(n2?.raw?.reduce(reducer, []) || splitRule(n2.sel))])].join(','),
132
+ chi: intersect.reverse()
133
+ });
134
+ if (result == null || [n1, n2].reduce((acc, curr) => curr.chi.length == 0 ? acc : acc + render(curr, options).code.length, 0) <= [node1, node2, result].reduce((acc, curr) => curr.chi.length == 0 ? acc : acc + render(curr, options).code.length, 0)) {
135
+ // @ts-ignore
136
+ return null;
137
+ }
138
+ return { result, node1: exchanged ? node2 : node1, node2: exchanged ? node2 : node2 };
139
+ }
140
+ function matchSelectors(selector1, selector2, parentType) {
141
+ let match = [[]];
142
+ const j = Math.min(selector1.reduce((acc, curr) => Math.min(acc, curr.length), selector1.length > 0 ? selector1[0].length : 0), selector2.reduce((acc, curr) => Math.min(acc, curr.length), selector2.length > 0 ? selector2[0].length : 0));
143
+ let i = 0;
144
+ let k;
145
+ let l;
146
+ let token;
147
+ let matching = true;
148
+ let matchFunction = 0;
149
+ let inAttr = 0;
150
+ for (; i < j; i++) {
151
+ k = 0;
152
+ token = selector1[0][i];
153
+ for (; k < selector1.length; k++) {
154
+ if (selector1[k][i] != token) {
155
+ matching = false;
156
+ break;
157
+ }
158
+ }
159
+ if (matching) {
160
+ l = 0;
161
+ for (; l < selector2.length; l++) {
162
+ if (selector2[l][i] != token) {
163
+ matching = false;
164
+ break;
165
+ }
166
+ }
167
+ }
168
+ if (!matching) {
169
+ break;
170
+ }
171
+ if (token == ',') {
172
+ match.push([]);
173
+ }
174
+ else {
175
+ if (token.endsWith('(')) {
176
+ matchFunction++;
177
+ }
178
+ if (token.endsWith('[')) {
179
+ inAttr++;
180
+ }
181
+ else if (token == ')') {
182
+ matchFunction--;
183
+ }
184
+ else if (token == ']') {
185
+ inAttr--;
186
+ }
187
+ match.at(-1).push(token);
188
+ }
189
+ }
190
+ // invalid function
191
+ if (matchFunction != 0 || inAttr != 0) {
192
+ return null;
193
+ }
194
+ if (parentType != 'Rule') {
195
+ for (const part of match) {
196
+ if (part.length > 0 && combinators.includes(part[0].charAt(0))) {
197
+ return null;
198
+ }
199
+ }
200
+ }
201
+ if (match.length > 1) {
202
+ console.error(`unsupported multilevel matching`);
203
+ console.error({ match, selector1, selector2 });
204
+ return null;
205
+ }
206
+ for (const part of match) {
207
+ while (part.length > 0) {
208
+ const token = part.at(-1);
209
+ if (token == ' ' || combinators.includes(token) || notEndingWith.includes(token.at(-1))) {
210
+ part.pop();
211
+ continue;
212
+ }
213
+ break;
214
+ }
215
+ }
216
+ if (match.every(t => t.length == 0)) {
217
+ return null;
218
+ }
219
+ if (eq([['&']], match)) {
220
+ return null;
221
+ }
222
+ function reduce(acc, curr) {
223
+ if (acc === null) {
224
+ return null;
225
+ }
226
+ let hasCompoundSelector = true;
227
+ curr = curr.slice(match[0].length);
228
+ while (curr.length > 0) {
229
+ if (curr[0] == ' ') {
230
+ hasCompoundSelector = false;
231
+ curr.unshift('&');
232
+ continue;
233
+ }
234
+ break;
235
+ }
236
+ // invalid function match
237
+ if (curr.length > 0 && curr[0].endsWith('(') && curr.at(-1) != ')') {
238
+ return null;
239
+ }
240
+ if (curr.length == 1 && combinators.includes(curr[0].charAt(0))) {
241
+ return null;
242
+ }
243
+ if (hasCompoundSelector && curr.length > 0) {
244
+ hasCompoundSelector = !['&'].concat(combinators).includes(curr[0].charAt(0));
245
+ }
246
+ if (curr[0] == ':is(') {
247
+ let inFunction = 0;
248
+ let canReduce = true;
249
+ const isCompound = curr.reduce((acc, token, index) => {
250
+ if (index == 0) {
251
+ inFunction++;
252
+ canReduce = curr[1] == '&';
253
+ }
254
+ else if (token.endsWith('(')) {
255
+ if (inFunction == 0) {
256
+ canReduce = false;
257
+ }
258
+ inFunction++;
259
+ }
260
+ else if (token == ')') {
261
+ inFunction--;
262
+ }
263
+ else if (token == ',') {
264
+ if (!canReduce) {
265
+ canReduce = curr[index + 1] == '&';
266
+ }
267
+ acc.push([]);
268
+ }
269
+ else
270
+ acc.at(-1)?.push(token);
271
+ return acc;
272
+ }, [[]]);
273
+ if (inFunction > 0) {
274
+ canReduce = false;
275
+ }
276
+ if (canReduce) {
277
+ curr = isCompound.reduce((acc, curr) => {
278
+ if (acc.length > 0) {
279
+ acc.push(',');
280
+ }
281
+ acc.push(...curr);
282
+ return acc;
283
+ }, []);
284
+ }
285
+ }
286
+ // @todo: check hasCompoundSelector && curr[0] == '&' && curr[1] == ' '
287
+ acc.push(match.length == 0 ? ['&'] : (hasCompoundSelector && curr[0] != '&' && (curr.length == 0 || !combinators.includes(curr[0].charAt(0))) ? ['&'].concat(curr) : curr));
288
+ return acc;
289
+ }
290
+ // @ts-ignore
291
+ selector1 = selector1.reduce(reduce, []);
292
+ // @ts-ignore
293
+ selector2 = selector2.reduce(reduce, []);
294
+ return selector1 == null || selector2 == null ? null : {
295
+ eq: eq(selector1, selector2),
296
+ match,
297
+ selector1,
298
+ selector2
299
+ };
40
300
  }
41
- // @ts-ignore
42
- ast.chi.splice(i, 1, wrapper);
43
- // @ts-ignore
44
- ast.chi.splice(nodeIndex, 1);
45
- // @ts-ignore
46
- previous.sel = pSel;
47
- // @ts-ignore
48
- previous.raw = match.selector1;
49
- // @ts-ignore
50
- node.sel = nSel;
51
- // @ts-ignore
52
- node.raw = match.selector2;
53
- reduceRuleSelector(wrapper);
54
- return wrapper;
55
- }
56
- function deduplicate(ast, options = {}, recursive = false) {
57
301
  // @ts-ignore
58
302
  if (('chi' in ast) && ast.chi?.length > 0) {
59
303
  let i = 0;
@@ -93,7 +337,8 @@ function deduplicate(ast, options = {}, recursive = false) {
93
337
  // @ts-ignore
94
338
  if (options.nestingRules) {
95
339
  // @ts-ignore
96
- if (previous != null && previous.typ == 'Rule') {
340
+ if (previous?.typ == 'Rule') {
341
+ // @ts-ignore
97
342
  reduceRuleSelector(previous);
98
343
  // @ts-ignore
99
344
  match = matchSelectors(previous.raw, node.raw, ast.typ);
@@ -132,7 +377,7 @@ function deduplicate(ast, options = {}, recursive = false) {
132
377
  nodeIndex = --i;
133
378
  // @ts-ignore
134
379
  previous = ast.chi[nodeIndex];
135
- deduplicate(wrapper, options, recursive);
380
+ minify(wrapper, options, recursive);
136
381
  continue;
137
382
  }
138
383
  // @ts-ignore
@@ -181,13 +426,24 @@ function deduplicate(ast, options = {}, recursive = false) {
181
426
  }
182
427
  else if (combinators.includes(curr[0])) {
183
428
  curr.unshift('&');
429
+ wrap = false;
184
430
  }
185
431
  // @ts-ignore
186
- acc.push(curr.map(t => t.replaceAll('&', node.optimized.optimized[0])).join(''));
432
+ acc.push(curr);
187
433
  return acc;
188
434
  }, []);
435
+ if (!wrap) {
436
+ wrap = selector.some(s => s[0] != '&');
437
+ }
438
+ const rule = selector.map(s => {
439
+ if (s[0] == '&') {
440
+ // @ts-ignore
441
+ s[0] = node.optimized.optimized[0];
442
+ }
443
+ return s.join('');
444
+ }).join(',');
189
445
  // @ts-ignore
190
- node.sel = (wrap ? node.optimized.optimized[0] : '') + `:is(${selector.join(',')})`;
446
+ node.sel = wrap ? node.optimized.optimized[0] + `:is(${rule})` : rule;
191
447
  }
192
448
  }
193
449
  // @ts-ignore
@@ -218,10 +474,10 @@ function deduplicate(ast, options = {}, recursive = false) {
218
474
  // @ts-ignore
219
475
  if (hasDeclaration(node)) {
220
476
  // @ts-ignore
221
- deduplicateRule(node);
477
+ minifyRule(node);
222
478
  }
223
479
  else {
224
- deduplicate(node, options, recursive);
480
+ minify(node, options, recursive);
225
481
  }
226
482
  i--;
227
483
  previous = node;
@@ -263,10 +519,10 @@ function deduplicate(ast, options = {}, recursive = false) {
263
519
  // @ts-ignore
264
520
  if (hasDeclaration(previous)) {
265
521
  // @ts-ignore
266
- deduplicateRule(previous);
522
+ minifyRule(previous);
267
523
  }
268
524
  else {
269
- deduplicate(previous, options, recursive);
525
+ minify(previous, options, recursive);
270
526
  }
271
527
  }
272
528
  }
@@ -277,18 +533,97 @@ function deduplicate(ast, options = {}, recursive = false) {
277
533
  if (recursive && node != null && ('chi' in node)) {
278
534
  // @ts-ignore
279
535
  if (node.chi.some(n => n.typ == 'Declaration')) {
280
- deduplicateRule(node);
536
+ minifyRule(node);
281
537
  }
282
538
  else {
283
539
  // @ts-ignore
284
540
  if (!(node.typ == 'AtRule' && node.nam != 'font-face')) {
285
- deduplicate(node, options, recursive);
541
+ minify(node, options, recursive);
286
542
  }
287
543
  }
288
544
  }
289
545
  }
290
546
  return ast;
291
547
  }
548
+ function reduceSelector(selector) {
549
+ if (selector.length == 0) {
550
+ return null;
551
+ }
552
+ const optimized = [];
553
+ const k = selector.reduce((acc, curr) => acc == 0 ? curr.length : (curr.length == 0 ? acc : Math.min(acc, curr.length)), 0);
554
+ let i = 0;
555
+ let j;
556
+ let match;
557
+ for (; i < k; i++) {
558
+ const item = selector[0][i];
559
+ match = true;
560
+ for (j = 1; j < selector.length; j++) {
561
+ if (item != selector[j][i]) {
562
+ match = false;
563
+ break;
564
+ }
565
+ }
566
+ if (!match) {
567
+ break;
568
+ }
569
+ optimized.push(item);
570
+ }
571
+ while (optimized.length > 0) {
572
+ const last = optimized.at(-1);
573
+ if ((last == ' ' || combinators.includes(last))) {
574
+ optimized.pop();
575
+ continue;
576
+ }
577
+ break;
578
+ }
579
+ selector.forEach((selector) => selector.splice(0, optimized.length));
580
+ // combinator
581
+ if (combinators.includes(optimized.at(-1))) {
582
+ const combinator = optimized.pop();
583
+ selector.forEach(selector => selector.unshift(combinator));
584
+ }
585
+ let reducible = optimized.length == 1;
586
+ if (optimized[0] == '&' && optimized[1] == ' ') {
587
+ optimized.splice(0, 2);
588
+ }
589
+ if (optimized.length == 0 ||
590
+ (optimized[0].charAt(0) == '&' ||
591
+ selector.length == 1)) {
592
+ return {
593
+ match: false,
594
+ optimized,
595
+ selector: selector.map(selector => selector[0] == '&' && selector[1] == ' ' ? selector.slice(2) : selector),
596
+ reducible: selector.length > 1 && selector.every((selector) => !combinators.includes(selector[0]))
597
+ };
598
+ }
599
+ return {
600
+ match: true,
601
+ optimized,
602
+ selector: selector.reduce((acc, curr) => {
603
+ let hasCompound = true;
604
+ if (hasCompound && curr.length > 0) {
605
+ hasCompound = !['&'].concat(combinators).includes(curr[0].charAt(0));
606
+ }
607
+ // @ts-ignore
608
+ if (hasCompound && curr[0] == ' ') {
609
+ hasCompound = false;
610
+ curr.unshift('&');
611
+ }
612
+ if (curr.length == 0) {
613
+ curr.push('&');
614
+ hasCompound = false;
615
+ }
616
+ if (reducible) {
617
+ const chr = curr[0].charAt(0);
618
+ // @ts-ignore
619
+ reducible = chr == '.' || chr == ':' || isIdentStart(chr.codePointAt(0));
620
+ }
621
+ acc.push(hasCompound ? ['&'].concat(curr) : curr);
622
+ return acc;
623
+ }, []),
624
+ reducible: selector.every((selector) => !['>', '+', '~', '&'].includes(selector[0]))
625
+ };
626
+ }
292
627
  function hasOnlyDeclarations(node) {
293
628
  let k = node.chi.length;
294
629
  while (k--) {
@@ -311,7 +646,7 @@ function hasDeclaration(node) {
311
646
  }
312
647
  return true;
313
648
  }
314
- function deduplicateRule(ast) {
649
+ function minifyRule(ast) {
315
650
  // @ts-ignore
316
651
  if (!('chi' in ast) || ast.chi?.length <= 1) {
317
652
  return ast;
@@ -319,45 +654,19 @@ function deduplicateRule(ast) {
319
654
  // @ts-ignore
320
655
  const j = ast.chi.length;
321
656
  let k = 0;
322
- let map = new Map;
657
+ let properties = new PropertyList();
323
658
  // @ts-ignore
324
659
  for (; k < j; k++) {
325
660
  // @ts-ignore
326
661
  const node = ast.chi[k];
327
- if (node.typ == 'Comment') {
328
- // @ts-ignore
329
- map.set(node, node);
662
+ if (node.typ == 'Comment' || node.typ == 'Declaration') {
663
+ properties.add(node);
330
664
  continue;
331
665
  }
332
- else if (node.typ != 'Declaration') {
333
- break;
334
- }
335
- if (node.nam in configuration.map ||
336
- node.nam in configuration.properties) {
337
- // @ts-ignore
338
- const shorthand = node.nam in configuration.map ? configuration.map[node.nam].shorthand : configuration.properties[node.nam].shorthand;
339
- if (!map.has(shorthand)) {
340
- map.set(shorthand, new PropertyList());
341
- }
342
- map.get(shorthand).add(node);
343
- }
344
- else {
345
- map.set(node.nam, node);
346
- }
347
- }
348
- const children = [];
349
- for (let child of map.values()) {
350
- if (child instanceof PropertyList) {
351
- // @ts-ignore
352
- children.push(...child);
353
- }
354
- else {
355
- // @ts-ignore
356
- children.push(child);
357
- }
666
+ break;
358
667
  }
359
668
  // @ts-ignore
360
- ast.chi = children.concat(ast.chi?.slice(k));
669
+ ast.chi = [...properties].concat(ast.chi.slice(k));
361
670
  return ast;
362
671
  }
363
672
  function splitRule(buffer) {
@@ -483,335 +792,5 @@ function reduceRuleSelector(node) {
483
792
  }
484
793
  // }
485
794
  }
486
- function diff(n1, n2, options = {}) {
487
- let node1 = n1;
488
- let node2 = n2;
489
- let exchanged = false;
490
- if (node1.chi.length > node2.chi.length) {
491
- const t = node1;
492
- node1 = node2;
493
- node2 = t;
494
- exchanged = true;
495
- }
496
- let i = node1.chi.length;
497
- let j = node2.chi.length;
498
- if (i == 0 || j == 0) {
499
- // @ts-ignore
500
- return null;
501
- }
502
- // @ts-ignore
503
- const raw1 = node1.raw;
504
- // @ts-ignore
505
- // const optimized1 = node1.optimized;
506
- // @ts-ignore
507
- const raw2 = node2.raw;
508
- // @ts-ignore
509
- // const optimized2 = node2.optimized;
510
- node1 = { ...node1, chi: node1.chi.slice() };
511
- node2 = { ...node2, chi: node2.chi.slice() };
512
- if (raw1 != null) {
513
- Object.defineProperty(node1, 'raw', { enumerable: false, writable: true, value: raw1 });
514
- }
515
- // if (optimized1 != null) {
516
- // Object.defineProperty(node1, 'optimized', {enumerable: false, writable: true, value: optimized1});
517
- // }
518
- if (raw2 != null) {
519
- Object.defineProperty(node2, 'raw', { enumerable: false, writable: true, value: raw2 });
520
- }
521
- // if (optimized2 != null) {
522
- // Object.defineProperty(node2, 'optimized', {enumerable: false, writable: true, value: optimized2});
523
- // }
524
- const intersect = [];
525
- while (i--) {
526
- if (node1.chi[i].typ == 'Comment') {
527
- continue;
528
- }
529
- j = node2.chi.length;
530
- if (j == 0) {
531
- break;
532
- }
533
- while (j--) {
534
- if (node2.chi[j].typ == 'Comment') {
535
- continue;
536
- }
537
- if (node1.chi[i].nam == node2.chi[j].nam) {
538
- if (eq(node1.chi[i], node2.chi[j])) {
539
- intersect.push(node1.chi[i]);
540
- node1.chi.splice(i, 1);
541
- node2.chi.splice(j, 1);
542
- break;
543
- }
544
- }
545
- }
546
- }
547
- // @ts-ignore
548
- const result = (intersect.length == 0 ? null : {
549
- ...node1,
550
- // @ts-ignore
551
- sel: [...new Set([...(n1?.raw?.reduce(reducer, []) || splitRule(n1.sel)).concat(n2?.raw?.reduce(reducer, []) || splitRule(n2.sel))])].join(','),
552
- chi: intersect.reverse()
553
- });
554
- if (result == null || [n1, n2].reduce((acc, curr) => curr.chi.length == 0 ? acc : acc + render(curr, options).code.length, 0) <= [node1, node2, result].reduce((acc, curr) => curr.chi.length == 0 ? acc : acc + render(curr, options).code.length, 0)) {
555
- // @ts-ignore
556
- return null;
557
- }
558
- return { result, node1: exchanged ? node2 : node1, node2: exchanged ? node2 : node2 };
559
- }
560
- function matchSelectors(selector1, selector2, parentType) {
561
- let match = [[]];
562
- const j = Math.min(selector1.reduce((acc, curr) => Math.min(acc, curr.length), selector1.length > 0 ? selector1[0].length : 0), selector2.reduce((acc, curr) => Math.min(acc, curr.length), selector2.length > 0 ? selector2[0].length : 0));
563
- let i = 0;
564
- let k;
565
- let l;
566
- let token;
567
- let matching = true;
568
- let matchFunction = 0;
569
- let inAttr = 0;
570
- for (; i < j; i++) {
571
- k = 0;
572
- token = selector1[0][i];
573
- for (; k < selector1.length; k++) {
574
- if (selector1[k][i] != token) {
575
- matching = false;
576
- break;
577
- }
578
- }
579
- if (matching) {
580
- l = 0;
581
- for (; l < selector2.length; l++) {
582
- if (selector2[l][i] != token) {
583
- matching = false;
584
- break;
585
- }
586
- }
587
- }
588
- if (!matching) {
589
- break;
590
- }
591
- if (token == ',') {
592
- match.push([]);
593
- }
594
- else {
595
- if (token.endsWith('(')) {
596
- matchFunction++;
597
- }
598
- if (token.endsWith('[')) {
599
- inAttr++;
600
- }
601
- else if (token == ')') {
602
- matchFunction--;
603
- }
604
- else if (token == ']') {
605
- inAttr--;
606
- }
607
- match.at(-1).push(token);
608
- }
609
- }
610
- // invalid function
611
- if (matchFunction != 0 || inAttr != 0) {
612
- return null;
613
- }
614
- if (parentType != 'Rule') {
615
- for (const part of match) {
616
- if (part.length > 0 && combinators.includes(part[0].charAt(0))) {
617
- return null;
618
- }
619
- }
620
- }
621
- if (match.length > 1) {
622
- console.error(`unsupported multilevel matching`);
623
- console.error({ match, selector1, selector2 });
624
- return null;
625
- }
626
- for (const part of match) {
627
- while (part.length > 0) {
628
- const token = part.at(-1);
629
- if (token == ' ' || combinators.includes(token) || notEndingWith.includes(token.at(-1))) {
630
- part.pop();
631
- continue;
632
- }
633
- break;
634
- }
635
- }
636
- if (match.every(t => t.length == 0)) {
637
- return null;
638
- }
639
- if (eq([['&']], match)) {
640
- return null;
641
- }
642
- function reduce(acc, curr) {
643
- if (acc === null) {
644
- return null;
645
- }
646
- let hasCompoundSelector = true;
647
- curr = curr.slice(match[0].length);
648
- while (curr.length > 0) {
649
- if (curr[0] == ' ') {
650
- hasCompoundSelector = false;
651
- curr.unshift('&');
652
- continue;
653
- }
654
- break;
655
- }
656
- // invalid function match
657
- if (curr.length > 0 && curr[0].endsWith('(') && curr.at(-1) != ')') {
658
- return null;
659
- }
660
- if (curr.length == 1 && combinators.includes(curr[0].charAt(0))) {
661
- return null;
662
- }
663
- if (hasCompoundSelector && curr.length > 0) {
664
- hasCompoundSelector = !['&'].concat(combinators).includes(curr[0].charAt(0));
665
- }
666
- if (curr[0] == ':is(') {
667
- let inFunction = 0;
668
- let canReduce = true;
669
- const isCompound = curr.reduce((acc, token, index) => {
670
- if (index == 0) {
671
- inFunction++;
672
- canReduce = curr[1] == '&';
673
- }
674
- else if (token.endsWith('(')) {
675
- if (inFunction == 0) {
676
- canReduce = false;
677
- }
678
- inFunction++;
679
- }
680
- else if (token == ')') {
681
- inFunction--;
682
- }
683
- else if (token == ',') {
684
- if (!canReduce) {
685
- canReduce = curr[index + 1] == '&';
686
- }
687
- acc.push([]);
688
- }
689
- else
690
- acc.at(-1)?.push(token);
691
- return acc;
692
- }, [[]]);
693
- if (inFunction > 0) {
694
- canReduce = false;
695
- }
696
- if (canReduce) {
697
- curr = isCompound.reduce((acc, curr) => {
698
- if (acc.length > 0) {
699
- acc.push(',');
700
- }
701
- acc.push(...curr);
702
- return acc;
703
- }, []);
704
- }
705
- }
706
- // @todo: check hasCompoundSelector && curr[0] == '&' && curr[1] == ' '
707
- acc.push(match.length == 0 ? ['&'] : (hasCompoundSelector && curr[0] != '&' && (curr.length == 0 || !combinators.includes(curr[0].charAt(0))) ? ['&'].concat(curr) : curr));
708
- return acc;
709
- }
710
- // @ts-ignore
711
- selector1 = selector1.reduce(reduce, []);
712
- // @ts-ignore
713
- selector2 = selector2.reduce(reduce, []);
714
- return selector1 == null || selector2 == null ? null : {
715
- eq: eq(selector1, selector2),
716
- match,
717
- selector1,
718
- selector2
719
- };
720
- }
721
- function reduceSelector(selector) {
722
- if (selector.length == 0) {
723
- return null;
724
- }
725
- const optimized = [];
726
- const k = selector.reduce((acc, curr) => acc == 0 ? curr.length : (curr.length == 0 ? acc : Math.min(acc, curr.length)), 0);
727
- let i = 0;
728
- let j;
729
- let match;
730
- for (; i < k; i++) {
731
- const item = selector[0][i];
732
- match = true;
733
- for (j = 1; j < selector.length; j++) {
734
- if (item != selector[j][i]) {
735
- match = false;
736
- break;
737
- }
738
- }
739
- if (!match) {
740
- break;
741
- }
742
- optimized.push(item);
743
- }
744
- while (optimized.length > 0) {
745
- const last = optimized.at(-1);
746
- if ((last == ' ' || combinators.includes(last))) {
747
- optimized.pop();
748
- continue;
749
- }
750
- break;
751
- }
752
- selector.forEach((selector) => selector.splice(0, optimized.length));
753
- // combinator
754
- if (combinators.includes(optimized.at(-1))) {
755
- const combinator = optimized.pop();
756
- selector.forEach(selector => selector.unshift(combinator));
757
- }
758
- let reducible = optimized.length == 1;
759
- if (optimized[0] == '&' && optimized[1] == ' ') {
760
- optimized.splice(0, 2);
761
- }
762
- if (optimized.length == 0 ||
763
- (optimized[0].charAt(0) == '&' ||
764
- selector.length == 1)) {
765
- return {
766
- match: false,
767
- optimized,
768
- selector: selector.map(selector => selector[0] == '&' && selector[1] == ' ' ? selector.slice(2) : selector),
769
- reducible: selector.length > 1 && selector.every((selector) => !combinators.includes(selector[0]))
770
- };
771
- }
772
- return {
773
- match: true,
774
- optimized,
775
- selector: selector.reduce((acc, curr) => {
776
- let hasCompound = true;
777
- if (hasCompound && curr.length > 0) {
778
- hasCompound = !['&'].concat(combinators).includes(curr[0].charAt(0));
779
- }
780
- // @ts-ignore
781
- if (hasCompound && curr[0] == ' ') {
782
- hasCompound = false;
783
- curr.unshift('&');
784
- }
785
- if (curr.length == 0) {
786
- curr.push('&');
787
- hasCompound = false;
788
- }
789
- if (reducible) {
790
- const chr = curr[0].charAt(0);
791
- // @ts-ignore
792
- reducible = chr == '.' || chr == ':' || isIdentStart(chr.codePointAt(0));
793
- }
794
- acc.push(hasCompound ? ['&'].concat(curr) : curr);
795
- return acc;
796
- }, []),
797
- reducible: selector.every((selector) => !['>', '+', '~', '&'].includes(selector[0]))
798
- };
799
- }
800
- function reducer(acc, curr, index, array) {
801
- // trim :is()
802
- if (array.length == 1 && array[0][0] == ':is(' && array[0].at(-1) == ')') {
803
- curr = curr.slice(1, -1);
804
- }
805
- if (curr[0] == '&') {
806
- if (curr[1] == ' ') {
807
- curr.splice(0, 2);
808
- }
809
- else if (combinators.includes(curr[1])) {
810
- curr.splice(0, 1);
811
- }
812
- }
813
- acc.push(curr.join(''));
814
- return acc;
815
- }
816
795
 
817
- export { deduplicate, deduplicateRule, hasDeclaration, reduceSelector };
796
+ export { combinators, hasDeclaration, minify, minifyRule, reduceSelector };