@tbela99/css-parser 0.0.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,58 +1,38 @@
1
- import { isIdentStart, isWhiteSpace, isIdent, isFunction } from '../parser/utils/syntax.js';
2
- import { PropertyList } from '../parser/declaration/list.js';
3
- import { eq } from '../parser/utils/eq.js';
4
- import { render, renderToken } from '../renderer/render.js';
5
1
  import { parseString } from '../parser/parse.js';
6
- import { replaceCompound } from './expand.js';
2
+ import { isWhiteSpace, isIdent, isFunction, isIdentStart } from '../parser/utils/syntax.js';
3
+ import { EnumToken } from './types.js';
7
4
  import { walkValues } from './walk.js';
5
+ import { replaceCompound } from './expand.js';
6
+ import { eq } from '../parser/utils/eq.js';
7
+ import { renderToken, doRender } from '../renderer/render.js';
8
+ import * as index from './features/index.js';
8
9
 
9
- const combinators = ['+', '>', '~'];
10
+ const combinators = ['+', '>', '~', '||'];
10
11
  const notEndingWith = ['(', '['].concat(combinators);
11
12
  const definedPropertySettings = { configurable: true, enumerable: false, writable: true };
12
- function minify(ast, options = {}, recursive = false, errors, nestingContent) {
13
- function wrapNodes(previous, node, match, ast, i, nodeIndex) {
14
- // @ts-ignore
15
- let pSel = match.selector1.reduce(reducer, []).join(',');
16
- // @ts-ignore
17
- let nSel = match.selector2.reduce(reducer, []).join(',');
13
+ // @ts-ignore
14
+ const features = Object.values(index).sort((a, b) => a.ordering - b.ordering);
15
+ function minify(ast, options = {}, recursive = false, errors, nestingContent, context = {}) {
16
+ if (!('nodes' in context)) {
17
+ context.nodes = new WeakSet;
18
+ }
19
+ if (context.nodes.has(ast)) {
20
+ // console.error('skipped', ast.typ);
21
+ return ast;
22
+ }
23
+ context.nodes.add(ast);
24
+ if (!('features' in options)) {
18
25
  // @ts-ignore
19
- const wrapper = { ...previous, chi: [], sel: match.match.reduce(reducer, []).join(',') };
26
+ options = {
27
+ removeDuplicateDeclarations: true,
28
+ computeShorthand: true,
29
+ computeCalcExpression: true,
30
+ features: [], ...options
31
+ };
20
32
  // @ts-ignore
21
- Object.defineProperty(wrapper, 'raw', { ...definedPropertySettings,
22
- // @ts-ignore
23
- value: match.match.map(t => t.slice())
24
- });
25
- if (pSel == '&' || pSel === '') {
26
- // @ts-ignore
27
- wrapper.chi.push(...previous.chi);
28
- // @ts-ignore
29
- if ((nSel == '&' || nSel === '')) {
30
- // @ts-ignore
31
- wrapper.chi.push(...node.chi);
32
- }
33
- else {
34
- // @ts-ignore
35
- wrapper.chi.push(node);
36
- }
33
+ for (const feature of features) {
34
+ feature.register(options);
37
35
  }
38
- else {
39
- // @ts-ignore
40
- wrapper.chi.push(previous, node);
41
- }
42
- // @ts-ignore
43
- ast.chi.splice(i, 1, wrapper);
44
- // @ts-ignore
45
- ast.chi.splice(nodeIndex, 1);
46
- // @ts-ignore
47
- previous.sel = pSel;
48
- // @ts-ignore
49
- previous.raw = match.selector1;
50
- // @ts-ignore
51
- node.sel = nSel;
52
- // @ts-ignore
53
- node.raw = match.selector2;
54
- reduceRuleSelector(wrapper);
55
- return wrapper;
56
36
  }
57
37
  function reducer(acc, curr, index, array) {
58
38
  // trim :is()
@@ -67,258 +47,16 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent) {
67
47
  curr.splice(0, 1);
68
48
  }
69
49
  }
70
- else if (ast.typ == 'Rule' && (isIdent(curr[0]) || isFunction(curr[0]))) {
50
+ else if (ast.typ == EnumToken.RuleNodeType && (isIdent(curr[0]) || isFunction(curr[0]))) {
71
51
  curr.unshift('&', ' ');
72
52
  }
73
53
  acc.push(curr.join(''));
74
54
  return acc;
75
55
  }
76
- function diff(n1, n2, options = {}) {
77
- let node1 = n1;
78
- let node2 = n2;
79
- let exchanged = false;
80
- if (node1.chi.length > node2.chi.length) {
81
- const t = node1;
82
- node1 = node2;
83
- node2 = t;
84
- exchanged = true;
85
- }
86
- let i = node1.chi.length;
87
- let j = node2.chi.length;
88
- if (i == 0 || j == 0) {
89
- // @ts-ignore
90
- return null;
91
- }
92
- // @ts-ignore
93
- const raw1 = node1.raw;
94
- // @ts-ignore
95
- const raw2 = node2.raw;
96
- // @ts-ignore
97
- node1 = { ...node1, chi: node1.chi.slice() };
98
- node2 = { ...node2, chi: node2.chi.slice() };
99
- if (raw1 != null) {
100
- Object.defineProperty(node1, 'raw', { ...definedPropertySettings, value: raw1 });
101
- }
102
- if (raw2 != null) {
103
- Object.defineProperty(node2, 'raw', { ...definedPropertySettings, value: raw2 });
104
- }
105
- const intersect = [];
106
- while (i--) {
107
- if (node1.chi[i].typ == 'Comment') {
108
- continue;
109
- }
110
- j = node2.chi.length;
111
- if (j == 0) {
112
- break;
113
- }
114
- while (j--) {
115
- if (node2.chi[j].typ == 'Comment') {
116
- continue;
117
- }
118
- if (node1.chi[i].nam == node2.chi[j].nam) {
119
- if (eq(node1.chi[i], node2.chi[j])) {
120
- intersect.push(node1.chi[i]);
121
- node1.chi.splice(i, 1);
122
- node2.chi.splice(j, 1);
123
- break;
124
- }
125
- }
126
- }
127
- }
128
- // @ts-ignore
129
- const result = (intersect.length == 0 ? null : {
130
- ...node1,
131
- // @ts-ignore
132
- sel: [...new Set([...(n1?.raw?.reduce(reducer, []) || splitRule(n1.sel)).concat(n2?.raw?.reduce(reducer, []) || splitRule(n2.sel))])].join(','),
133
- chi: intersect.reverse()
134
- });
135
- 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)) {
136
- // @ts-ignore
137
- return null;
138
- }
139
- return { result, node1: exchanged ? node2 : node1, node2: exchanged ? node1 : node2 };
140
- }
141
- function matchSelectors(selector1, selector2, parentType) {
142
- let match = [[]];
143
- 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));
144
- let i = 0;
145
- let k;
146
- let l;
147
- let token;
148
- let matching = true;
149
- let matchFunction = 0;
150
- let inAttr = 0;
151
- for (; i < j; i++) {
152
- k = 0;
153
- token = selector1[0][i];
154
- for (; k < selector1.length; k++) {
155
- if (selector1[k][i] != token) {
156
- matching = false;
157
- break;
158
- }
159
- }
160
- if (matching) {
161
- l = 0;
162
- for (; l < selector2.length; l++) {
163
- if (selector2[l][i] != token) {
164
- matching = false;
165
- break;
166
- }
167
- }
168
- }
169
- if (!matching) {
170
- break;
171
- }
172
- if (token == ',') {
173
- match.push([]);
174
- }
175
- else {
176
- if (token.endsWith('(')) {
177
- matchFunction++;
178
- }
179
- if (token.endsWith('[')) {
180
- inAttr++;
181
- }
182
- else if (token == ')') {
183
- matchFunction--;
184
- }
185
- else if (token == ']') {
186
- inAttr--;
187
- }
188
- match.at(-1).push(token);
189
- }
190
- }
191
- // invalid function
192
- if (matchFunction != 0 || inAttr != 0) {
193
- return null;
194
- }
195
- if (parentType != 'Rule') {
196
- for (const part of match) {
197
- if (part.length > 0 && combinators.includes(part[0].charAt(0))) {
198
- return null;
199
- }
200
- }
201
- }
202
- if (match.length > 1) {
203
- errors?.push({ action: 'ignore', message: `minify: unsupported multilevel matching\n${JSON.stringify({ match, selector1, selector2 }, null, 1)}` });
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
- };
300
- }
301
- function fixSelector(node) {
302
- // @ts-ignore
303
- if (node.sel.includes('&')) {
304
- const attributes = parseString(node.sel);
305
- for (const attr of walkValues(attributes)) {
306
- if (attr.value.typ == 'Pseudo-class-func' && attr.value.val == ':is') {
307
- let i = attr.value.chi.length;
308
- while (i--) {
309
- if (attr.value.chi[i].typ == 'Literal' && attr.value.chi[i].val == '&') {
310
- attr.value.chi.splice(i, 1);
311
- }
312
- }
313
- }
314
- }
315
- node.sel = attributes.reduce((acc, curr) => acc + renderToken(curr), '');
316
- }
317
- }
318
56
  // @ts-ignore
319
- if (('chi' in ast) && ast.chi?.length > 0) {
57
+ if ('chi' in ast && ast.chi.length > 0) {
320
58
  if (!nestingContent) {
321
- nestingContent = options.nestingRules && ast.typ == 'Rule';
59
+ nestingContent = options.nestingRules && ast.typ == EnumToken.RuleNodeType;
322
60
  }
323
61
  let i = 0;
324
62
  let previous;
@@ -327,7 +65,7 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent) {
327
65
  // @ts-ignore
328
66
  for (; i < ast.chi.length; i++) {
329
67
  // @ts-ignore
330
- if (ast.chi[i].typ == 'Comment') {
68
+ if (ast.chi[i].typ == EnumToken.CommentNodeType) {
331
69
  continue;
332
70
  }
333
71
  // @ts-ignore
@@ -339,10 +77,10 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent) {
339
77
  i--;
340
78
  continue;
341
79
  }
342
- if (node.typ == 'AtRule' && node.nam == 'font-face') {
80
+ if (node.typ == EnumToken.AtRuleNodeType && node.nam == 'font-face') {
343
81
  continue;
344
82
  }
345
- if (node.typ == 'AtRule') {
83
+ if (node.typ == EnumToken.AtRuleNodeType) {
346
84
  if (node.nam == 'media' && node.val == 'all') {
347
85
  // @ts-ignore
348
86
  ast.chi?.splice(i, 1, ...node.chi);
@@ -350,47 +88,41 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent) {
350
88
  continue;
351
89
  }
352
90
  // @ts-ignore
353
- if (previous?.typ == 'AtRule' &&
91
+ if (previous?.typ == EnumToken.AtRuleNodeType &&
354
92
  previous.nam == node.nam &&
355
93
  previous.val == node.val) {
356
94
  if ('chi' in node) {
357
95
  // @ts-ignore
358
96
  previous.chi.push(...node.chi);
359
97
  }
360
- // else {
361
98
  ast?.chi?.splice(i--, 1);
362
99
  continue;
363
- // }
364
100
  }
365
101
  // @ts-ignore
366
- if (hasDeclaration(node)) {
367
- // @ts-ignore
368
- minifyRule(node);
369
- }
370
- else {
371
- minify(node, options, recursive, errors, nestingContent);
102
+ if (!hasDeclaration(node)) {
103
+ minify(node, options, recursive, errors, nestingContent, context);
372
104
  }
373
105
  previous = node;
374
106
  nodeIndex = i;
375
107
  continue;
376
108
  }
377
109
  // @ts-ignore
378
- if (node.typ == 'Rule') {
110
+ if (node.typ == EnumToken.RuleNodeType) {
379
111
  reduceRuleSelector(node);
380
112
  let wrapper;
381
113
  let match;
382
114
  // @ts-ignore
383
115
  if (options.nestingRules) {
384
116
  // @ts-ignore
385
- if (previous?.typ == 'Rule') {
117
+ if (previous?.typ == EnumToken.RuleNodeType) {
386
118
  // @ts-ignore
387
119
  reduceRuleSelector(previous);
388
120
  // @ts-ignore
389
- match = matchSelectors(previous.raw, node.raw, ast.typ);
121
+ match = matchSelectors(previous.raw, node.raw, ast.typ, errors);
390
122
  // @ts-ignore
391
123
  if (match != null) {
392
124
  // @ts-ignore
393
- wrapper = wrapNodes(previous, node, match, ast, i, nodeIndex);
125
+ wrapper = wrapNodes(previous, node, match, ast, reducer, i, nodeIndex);
394
126
  nodeIndex = i - 1;
395
127
  // @ts-ignore
396
128
  previous = ast.chi[nodeIndex];
@@ -403,26 +135,23 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent) {
403
135
  // @ts-ignore
404
136
  const nextNode = ast.chi[i];
405
137
  // @ts-ignore
406
- if (nextNode.typ != 'Rule') {
407
- // i--;
408
- // previous = wrapper;
409
- // nodeIndex = i;
138
+ if (nextNode.typ != EnumToken.RuleNodeType) {
410
139
  break;
411
140
  }
412
141
  reduceRuleSelector(nextNode);
413
142
  // @ts-ignore
414
- match = matchSelectors(wrapper.raw, nextNode.raw, ast.typ);
143
+ match = matchSelectors(wrapper.raw, nextNode.raw, ast.typ, errors);
415
144
  // @ts-ignore
416
145
  if (match == null) {
417
146
  break;
418
147
  }
419
148
  // @ts-ignore
420
- wrapper = wrapNodes(wrapper, nextNode, match, ast, i, nodeIndex);
149
+ wrapper = wrapNodes(wrapper, nextNode, match, ast, reducer, i, nodeIndex);
421
150
  }
422
151
  nodeIndex = --i;
423
152
  // @ts-ignore
424
153
  previous = ast.chi[nodeIndex];
425
- minify(wrapper, options, recursive, errors, nestingContent);
154
+ minify(wrapper, options, recursive, errors, nestingContent, context);
426
155
  continue;
427
156
  }
428
157
  // @ts-ignore
@@ -460,7 +189,7 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent) {
460
189
  curr.splice(0, 2);
461
190
  }
462
191
  else {
463
- if (ast.typ != 'Rule' && combinators.includes(curr[1])) {
192
+ if (ast.typ != EnumToken.RuleNodeType && combinators.includes(curr[1])) {
464
193
  wrap = false;
465
194
  }
466
195
  else {
@@ -477,7 +206,7 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent) {
477
206
  return acc;
478
207
  }, []);
479
208
  if (!wrap) {
480
- wrap = selector.some(s => s[0] != '&');
209
+ wrap = selector.some((s) => s[0] != '&');
481
210
  }
482
211
  let rule = selector.map(s => {
483
212
  if (s[0] == '&') {
@@ -508,37 +237,36 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent) {
508
237
  let k = previous.chi.length;
509
238
  while (k-- > 0) {
510
239
  // @ts-ignore
511
- if (previous.chi[k].typ == 'Comment') {
240
+ if (previous.chi[k].typ == EnumToken.CommentNodeType) {
512
241
  continue;
513
242
  }
514
243
  // @ts-ignore
515
- shouldMerge = previous.chi[k].typ == 'Declaration';
244
+ shouldMerge = previous.chi[k].typ == EnumToken.DeclarationNodeType;
516
245
  break;
517
246
  }
518
247
  if (shouldMerge) {
519
248
  // @ts-ignore
520
- if ((node.typ == 'Rule' && node.sel == previous.sel) ||
249
+ if ((node.typ == EnumToken.RuleNodeType && node.sel == previous.sel) ||
521
250
  // @ts-ignore
522
- (node.typ == 'AtRule') && node.val != 'font-face' && node.val == previous.val) {
251
+ (node.typ == EnumToken.AtRuleNodeType) && node.val != 'font-face' && node.val == previous.val) {
523
252
  // @ts-ignore
524
253
  node.chi.unshift(...previous.chi);
525
254
  // @ts-ignore
526
255
  ast.chi.splice(nodeIndex, 1);
527
256
  // @ts-ignore
528
- if (hasDeclaration(node)) {
257
+ if (!hasDeclaration(node)) {
529
258
  // @ts-ignore
530
- minifyRule(node);
531
- }
532
- else {
533
- minify(node, options, recursive, errors, nestingContent);
259
+ // minifyRule(node, <MinifyOptions>options, ast, context);
260
+ // } else {
261
+ minify(node, options, recursive, errors, nestingContent, context);
534
262
  }
535
263
  i--;
536
264
  previous = node;
537
265
  nodeIndex = i;
538
266
  continue;
539
267
  }
540
- else if (node.typ == 'Rule' && previous?.typ == 'Rule') {
541
- const intersect = diff(previous, node, options);
268
+ else if (node.typ == EnumToken.RuleNodeType && previous?.typ == EnumToken.RuleNodeType) {
269
+ const intersect = diff(previous, node, reducer, options);
542
270
  if (intersect != null) {
543
271
  if (intersect.node1.chi.length == 0) {
544
272
  // @ts-ignore
@@ -570,24 +298,22 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent) {
570
298
  // @ts-ignore
571
299
  if (recursive && previous != node) {
572
300
  // @ts-ignore
573
- if (hasDeclaration(previous)) {
301
+ if (!hasDeclaration(previous)) {
574
302
  // @ts-ignore
575
- minifyRule(previous);
576
- }
577
- else {
578
- minify(previous, options, recursive, errors, nestingContent);
303
+ // minifyRule(previous, <MinifyOptions>options, ast, context);
304
+ // } else {
305
+ minify(previous, options, recursive, errors, nestingContent, context);
579
306
  }
580
307
  }
581
308
  }
582
309
  else {
583
310
  if ('chi' in previous) {
584
311
  // @ts-ignore
585
- if (hasDeclaration(previous)) {
312
+ if (!hasDeclaration(previous)) {
586
313
  // @ts-ignore
587
- minifyRule(previous);
588
- }
589
- else {
590
- minify(previous, options, recursive, errors, nestingContent);
314
+ // minifyRule(previous, <MinifyOptions>options, ast, context);
315
+ // } else {
316
+ minify(previous, options, recursive, errors, nestingContent, context);
591
317
  }
592
318
  }
593
319
  }
@@ -596,7 +322,7 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent) {
596
322
  // @ts-ignore
597
323
  previous != null &&
598
324
  // previous.optimized != null &&
599
- previous.typ == 'Rule' &&
325
+ previous.typ == EnumToken.RuleNodeType &&
600
326
  previous.sel.includes('&')) {
601
327
  fixSelector(previous);
602
328
  }
@@ -606,13 +332,10 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent) {
606
332
  // @ts-ignore
607
333
  if (recursive && node != null && ('chi' in node)) {
608
334
  // @ts-ignore
609
- if (node.chi.some(n => n.typ == 'Declaration')) {
610
- minifyRule(node);
611
- }
612
- else {
335
+ if (!node.chi.some(n => n.typ == EnumToken.DeclarationNodeType)) {
613
336
  // @ts-ignore
614
- if (!(node.typ == 'AtRule' && node.nam != 'font-face')) {
615
- minify(node, options, recursive, errors, nestingContent);
337
+ if (!(node.typ == EnumToken.AtRuleNodeType && node.nam != 'font-face')) {
338
+ minify(node, options, recursive, errors, nestingContent, context);
616
339
  }
617
340
  }
618
341
  }
@@ -620,11 +343,46 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent) {
620
343
  // @ts-ignore
621
344
  node != null &&
622
345
  // previous.optimized != null &&
623
- node.typ == 'Rule' &&
346
+ node.typ == EnumToken.RuleNodeType &&
624
347
  node.sel.includes('&')) {
625
348
  fixSelector(node);
626
349
  }
627
350
  }
351
+ if (ast.typ == EnumToken.StyleSheetNodeType) {
352
+ let parent;
353
+ let parents = [ast];
354
+ while (parents.length > 0) {
355
+ parent = parents.shift();
356
+ // @ts-ignore
357
+ for (let k = 0; k < parent.chi.length; k++) {
358
+ // @ts-ignore
359
+ const node = parent.chi[k];
360
+ if (!('chi' in node) || node.typ == EnumToken.StyleSheetNodeType || (node.typ == EnumToken.AtRuleNodeType && node.nam == 'font-face')) {
361
+ continue;
362
+ }
363
+ // @ts-ignore
364
+ if (node.chi.length > 0) {
365
+ parents.push(node);
366
+ Object.defineProperty(node, 'parent', { ...definedPropertySettings, value: parent });
367
+ for (const feature of options.features) {
368
+ feature.run(node, options, parent, context);
369
+ }
370
+ }
371
+ // @ts-ignore
372
+ if (options.removeEmpty && node.chi.length == 0) {
373
+ // @ts-ignore
374
+ parent.chi.splice(k, 1);
375
+ k--;
376
+ }
377
+ }
378
+ }
379
+ for (const feature of options.features) {
380
+ if ('cleanup' in feature) {
381
+ // @ts-ignore
382
+ feature.cleanup(ast, options, context);
383
+ }
384
+ }
385
+ }
628
386
  return ast;
629
387
  }
630
388
  function reduceSelector(selector) {
@@ -710,37 +468,14 @@ function hasDeclaration(node) {
710
468
  // @ts-ignore
711
469
  for (let i = 0; i < node.chi?.length; i++) {
712
470
  // @ts-ignore
713
- if (node.chi[i].typ == 'Comment') {
471
+ if (node.chi[i].typ == EnumToken.CommentNodeType) {
714
472
  continue;
715
473
  }
716
474
  // @ts-ignore
717
- return node.chi[i].typ == 'Declaration';
475
+ return node.chi[i].typ == EnumToken.DeclarationNodeType;
718
476
  }
719
477
  return true;
720
478
  }
721
- function minifyRule(ast) {
722
- // @ts-ignore
723
- if (!('chi' in ast) || ast.chi?.length <= 1) {
724
- return ast;
725
- }
726
- // @ts-ignore
727
- const j = ast.chi.length;
728
- let k = 0;
729
- let properties = new PropertyList();
730
- // @ts-ignore
731
- for (; k < j; k++) {
732
- // @ts-ignore
733
- const node = ast.chi[k];
734
- if (node.typ == 'Comment' || node.typ == 'Declaration') {
735
- properties.add(node);
736
- continue;
737
- }
738
- break;
739
- }
740
- // @ts-ignore
741
- ast.chi = [...properties].concat(ast.chi.slice(k));
742
- return ast;
743
- }
744
479
  function splitRule(buffer) {
745
480
  const result = [[]];
746
481
  let str = '';
@@ -829,6 +564,300 @@ function splitRule(buffer) {
829
564
  }
830
565
  return result;
831
566
  }
567
+ function matchSelectors(selector1, selector2, parentType, errors) {
568
+ let match = [[]];
569
+ 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));
570
+ let i = 0;
571
+ let k;
572
+ let l;
573
+ let token;
574
+ let matching = true;
575
+ let matchFunction = 0;
576
+ let inAttr = 0;
577
+ for (; i < j; i++) {
578
+ k = 0;
579
+ token = selector1[0][i];
580
+ for (; k < selector1.length; k++) {
581
+ if (selector1[k][i] != token) {
582
+ matching = false;
583
+ break;
584
+ }
585
+ }
586
+ if (matching) {
587
+ l = 0;
588
+ for (; l < selector2.length; l++) {
589
+ if (selector2[l][i] != token) {
590
+ matching = false;
591
+ break;
592
+ }
593
+ }
594
+ }
595
+ if (!matching) {
596
+ break;
597
+ }
598
+ if (token == ',') {
599
+ match.push([]);
600
+ }
601
+ else {
602
+ if (token.endsWith('(')) {
603
+ matchFunction++;
604
+ }
605
+ if (token.endsWith('[')) {
606
+ inAttr++;
607
+ }
608
+ else if (token == ')') {
609
+ matchFunction--;
610
+ }
611
+ else if (token == ']') {
612
+ inAttr--;
613
+ }
614
+ match.at(-1).push(token);
615
+ }
616
+ }
617
+ // invalid function
618
+ if (matchFunction != 0 || inAttr != 0) {
619
+ return null;
620
+ }
621
+ if (parentType != EnumToken.RuleNodeType) {
622
+ for (const part of match) {
623
+ if (part.length > 0 && combinators.includes(part[0].charAt(0))) {
624
+ return null;
625
+ }
626
+ }
627
+ }
628
+ if (match.length > 1) {
629
+ errors?.push({
630
+ action: 'ignore',
631
+ message: `minify: unsupported multilevel matching\n${JSON.stringify({
632
+ match,
633
+ selector1,
634
+ selector2
635
+ }, null, 1)}`
636
+ });
637
+ return null;
638
+ }
639
+ for (const part of match) {
640
+ while (part.length > 0) {
641
+ const token = part.at(-1);
642
+ if (token == ' ' || combinators.includes(token) || notEndingWith.includes(token.at(-1))) {
643
+ part.pop();
644
+ continue;
645
+ }
646
+ break;
647
+ }
648
+ }
649
+ if (match.every(t => t.length == 0)) {
650
+ return null;
651
+ }
652
+ if (eq([['&']], match)) {
653
+ return null;
654
+ }
655
+ function reduce(acc, curr) {
656
+ if (acc === null) {
657
+ return null;
658
+ }
659
+ let hasCompoundSelector = true;
660
+ curr = curr.slice(match[0].length);
661
+ while (curr.length > 0) {
662
+ if (curr[0] == ' ') {
663
+ hasCompoundSelector = false;
664
+ curr.unshift('&');
665
+ continue;
666
+ }
667
+ break;
668
+ }
669
+ // invalid function match
670
+ if (curr.length > 0 && curr[0].endsWith('(') && curr.at(-1) != ')') {
671
+ return null;
672
+ }
673
+ if (curr.length == 1 && combinators.includes(curr[0].charAt(0))) {
674
+ return null;
675
+ }
676
+ if (hasCompoundSelector && curr.length > 0) {
677
+ hasCompoundSelector = !['&'].concat(combinators).includes(curr[0].charAt(0));
678
+ }
679
+ if (curr[0] == ':is(') {
680
+ let inFunction = 0;
681
+ let canReduce = true;
682
+ const isCompound = curr.reduce((acc, token, index) => {
683
+ if (index == 0) {
684
+ inFunction++;
685
+ canReduce = curr[1] == '&';
686
+ }
687
+ else if (token.endsWith('(')) {
688
+ if (inFunction == 0) {
689
+ canReduce = false;
690
+ }
691
+ inFunction++;
692
+ }
693
+ else if (token == ')') {
694
+ inFunction--;
695
+ }
696
+ else if (token == ',') {
697
+ if (!canReduce) {
698
+ canReduce = curr[index + 1] == '&';
699
+ }
700
+ acc.push([]);
701
+ }
702
+ else
703
+ acc.at(-1)?.push(token);
704
+ return acc;
705
+ }, [[]]);
706
+ if (inFunction > 0) {
707
+ canReduce = false;
708
+ }
709
+ if (canReduce) {
710
+ curr = isCompound.reduce((acc, curr) => {
711
+ if (acc.length > 0) {
712
+ acc.push(',');
713
+ }
714
+ acc.push(...curr);
715
+ return acc;
716
+ }, []);
717
+ }
718
+ }
719
+ // @todo: check hasCompoundSelector && curr[0] == '&' && curr[1] == ' '
720
+ acc.push(match.length == 0 ? ['&'] : (hasCompoundSelector && curr[0] != '&' && (curr.length == 0 || !combinators.includes(curr[0].charAt(0))) ? ['&'].concat(curr) : curr));
721
+ return acc;
722
+ }
723
+ // @ts-ignore
724
+ selector1 = selector1.reduce(reduce, []);
725
+ // @ts-ignore
726
+ selector2 = selector2.reduce(reduce, []);
727
+ return selector1 == null || selector2 == null ? null : {
728
+ eq: eq(selector1, selector2),
729
+ match,
730
+ selector1,
731
+ selector2
732
+ };
733
+ }
734
+ function fixSelector(node) {
735
+ // @ts-ignore
736
+ if (node.sel.includes('&')) {
737
+ const attributes = parseString(node.sel);
738
+ for (const attr of walkValues(attributes)) {
739
+ if (attr.value.typ == EnumToken.PseudoClassFuncTokenType && attr.value.val == ':is') {
740
+ let i = attr.value.chi.length;
741
+ while (i--) {
742
+ if (attr.value.chi[i].typ == EnumToken.LiteralTokenType && attr.value.chi[i].val == '&') {
743
+ attr.value.chi.splice(i, 1);
744
+ }
745
+ }
746
+ }
747
+ }
748
+ node.sel = attributes.reduce((acc, curr) => acc + renderToken(curr), '');
749
+ }
750
+ }
751
+ function wrapNodes(previous, node, match, ast, reducer, i, nodeIndex) {
752
+ // @ts-ignore
753
+ let pSel = match.selector1.reduce(reducer, []).join(',');
754
+ // @ts-ignore
755
+ let nSel = match.selector2.reduce(reducer, []).join(',');
756
+ // @ts-ignore
757
+ const wrapper = { ...previous, chi: [], sel: match.match.reduce(reducer, []).join(',') };
758
+ // @ts-ignore
759
+ Object.defineProperty(wrapper, 'raw', {
760
+ ...definedPropertySettings,
761
+ // @ts-ignore
762
+ value: match.match.map(t => t.slice())
763
+ });
764
+ if (pSel == '&' || pSel === '') {
765
+ // @ts-ignore
766
+ wrapper.chi.push(...previous.chi);
767
+ // @ts-ignore
768
+ if ((nSel == '&' || nSel === '')) {
769
+ // @ts-ignore
770
+ wrapper.chi.push(...node.chi);
771
+ }
772
+ else {
773
+ // @ts-ignore
774
+ wrapper.chi.push(node);
775
+ }
776
+ }
777
+ else {
778
+ // @ts-ignore
779
+ wrapper.chi.push(previous, node);
780
+ }
781
+ // @ts-ignore
782
+ ast.chi.splice(i, 1, wrapper);
783
+ // @ts-ignore
784
+ ast.chi.splice(nodeIndex, 1);
785
+ // @ts-ignore
786
+ previous.sel = pSel;
787
+ // @ts-ignore
788
+ previous.raw = match.selector1;
789
+ // @ts-ignore
790
+ node.sel = nSel;
791
+ // @ts-ignore
792
+ node.raw = match.selector2;
793
+ reduceRuleSelector(wrapper);
794
+ return wrapper;
795
+ }
796
+ function diff(n1, n2, reducer, options = {}) {
797
+ let node1 = n1;
798
+ let node2 = n2;
799
+ let exchanged = false;
800
+ if (node1.chi.length > node2.chi.length) {
801
+ const t = node1;
802
+ node1 = node2;
803
+ node2 = t;
804
+ exchanged = true;
805
+ }
806
+ let i = node1.chi.length;
807
+ let j = node2.chi.length;
808
+ if (i == 0 || j == 0) {
809
+ // @ts-ignore
810
+ return null;
811
+ }
812
+ // @ts-ignore
813
+ const raw1 = node1.raw;
814
+ // @ts-ignore
815
+ const raw2 = node2.raw;
816
+ // @ts-ignore
817
+ node1 = { ...node1, chi: node1.chi.slice() };
818
+ node2 = { ...node2, chi: node2.chi.slice() };
819
+ if (raw1 != null) {
820
+ Object.defineProperty(node1, 'raw', { ...definedPropertySettings, value: raw1 });
821
+ }
822
+ if (raw2 != null) {
823
+ Object.defineProperty(node2, 'raw', { ...definedPropertySettings, value: raw2 });
824
+ }
825
+ const intersect = [];
826
+ while (i--) {
827
+ if (node1.chi[i].typ == EnumToken.CommentNodeType) {
828
+ continue;
829
+ }
830
+ j = node2.chi.length;
831
+ if (j == 0) {
832
+ break;
833
+ }
834
+ while (j--) {
835
+ if (node2.chi[j].typ == EnumToken.CommentNodeType) {
836
+ continue;
837
+ }
838
+ if (node1.chi[i].nam == node2.chi[j].nam) {
839
+ if (eq(node1.chi[i], node2.chi[j])) {
840
+ intersect.push(node1.chi[i]);
841
+ node1.chi.splice(i, 1);
842
+ node2.chi.splice(j, 1);
843
+ break;
844
+ }
845
+ }
846
+ }
847
+ }
848
+ // @ts-ignore
849
+ const result = (intersect.length == 0 ? null : {
850
+ ...node1,
851
+ // @ts-ignore
852
+ sel: [...new Set([...(n1?.raw?.reduce(reducer, []) || splitRule(n1.sel)).concat(n2?.raw?.reduce(reducer, []) || splitRule(n2.sel))])].join(','),
853
+ chi: intersect.reverse()
854
+ });
855
+ if (result == null || [n1, n2].reduce((acc, curr) => curr.chi.length == 0 ? acc : acc + doRender(curr, options).code.length, 0) <= [node1, node2, result].reduce((acc, curr) => curr.chi.length == 0 ? acc : acc + doRender(curr, options).code.length, 0)) {
856
+ // @ts-ignore
857
+ return null;
858
+ }
859
+ return { result, node1: exchanged ? node2 : node1, node2: exchanged ? node1 : node2 };
860
+ }
832
861
  function reduceRuleSelector(node) {
833
862
  if (node.raw == null) {
834
863
  Object.defineProperty(node, 'raw', { ...definedPropertySettings, value: splitRule(node.sel) });
@@ -864,4 +893,4 @@ function reduceRuleSelector(node) {
864
893
  }
865
894
  }
866
895
 
867
- export { combinators, hasDeclaration, minify, minifyRule, reduceSelector, splitRule };
896
+ export { combinators, hasDeclaration, minify, reduceSelector, splitRule };