@tbela99/css-parser 0.0.1-rc3 → 0.0.1-rc4

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.
@@ -165,37 +165,30 @@
165
165
  return true;
166
166
  }
167
167
  function isDimension(name) {
168
- let index = 0;
169
- while (index++ < name.length) {
170
- if (isDigit(name.charCodeAt(name.length - index))) {
171
- index--;
172
- break;
173
- }
174
- if (index == 3) {
175
- break;
168
+ let index = name.length;
169
+ while (index--) {
170
+ if (isLetter(name.charCodeAt(index))) {
171
+ continue;
176
172
  }
173
+ index++;
174
+ break;
177
175
  }
178
- if (index == 0 || index > 3) {
179
- return false;
180
- }
181
- const number = name.slice(0, -index);
182
- return number.length > 0 && isIdentStart(name.charCodeAt(name.length - index)) && isNumber(number);
176
+ const number = name.slice(0, index);
177
+ return number.length > 0 && isIdentStart(name.charCodeAt(index)) && isNumber(number);
183
178
  }
184
179
  function isPercentage(name) {
185
180
  return name.endsWith('%') && isNumber(name.slice(0, -1));
186
181
  }
187
182
  function parseDimension(name) {
188
- let index = 0;
189
- while (index++ < name.length) {
190
- if (isDigit(name.charCodeAt(name.length - index))) {
191
- index--;
192
- break;
193
- }
194
- if (index == 3) {
195
- break;
183
+ let index = name.length;
184
+ while (index--) {
185
+ if (isLetter(name.charCodeAt(index))) {
186
+ continue;
196
187
  }
188
+ index++;
189
+ break;
197
190
  }
198
- const dimension = { typ: 'Dimension', val: name.slice(0, -index), unit: name.slice(-index) };
191
+ const dimension = { typ: 'Dimension', val: name.slice(0, index), unit: name.slice(index) };
199
192
  if (isAngle(dimension)) {
200
193
  // @ts-ignore
201
194
  dimension.typ = 'Angle';
@@ -1504,7 +1497,7 @@
1504
1497
  return `#${rgb.reduce((acc, curr) => acc + curr.toString(16).padStart(2, '0'), '')}`;
1505
1498
  }
1506
1499
  function getAngle(token) {
1507
- if (token.typ == 'Dimension') {
1500
+ if (token.typ == 'Angle') {
1508
1501
  switch (token.unit) {
1509
1502
  case 'deg':
1510
1503
  // @ts-ignore
@@ -1577,6 +1570,23 @@
1577
1570
  return values;
1578
1571
  }
1579
1572
 
1573
+ function reduceNumber(val) {
1574
+ val = (+val).toString();
1575
+ if (val === '0') {
1576
+ return '0';
1577
+ }
1578
+ const chr = val.charAt(0);
1579
+ if (chr == '-') {
1580
+ const slice = val.slice(0, 2);
1581
+ if (slice == '-0') {
1582
+ return val.length == 2 ? '0' : '-' + val.slice(2);
1583
+ }
1584
+ }
1585
+ if (chr == '0') {
1586
+ return val.slice(1);
1587
+ }
1588
+ return val;
1589
+ }
1580
1590
  function render(data, opt = {}) {
1581
1591
  const startTime = performance.now();
1582
1592
  const options = Object.assign(opt.minify ?? true ? {
@@ -1589,17 +1599,19 @@
1589
1599
  compress: false,
1590
1600
  removeComments: false,
1591
1601
  }, { colorConvert: true, preserveLicense: false }, opt);
1592
- function reducer(acc, curr, index, original) {
1593
- if (curr.typ == 'Comment' && options.removeComments) {
1594
- if (!options.preserveLicense || !curr.val.startsWith('/*!')) {
1595
- return acc;
1602
+ return {
1603
+ code: doRender(data, options, function reducer(acc, curr) {
1604
+ if (curr.typ == 'Comment' && options.removeComments) {
1605
+ if (!options.preserveLicense || !curr.val.startsWith('/*!')) {
1606
+ return acc;
1607
+ }
1608
+ return acc + curr.val;
1596
1609
  }
1597
- }
1598
- return acc + renderToken(curr, options);
1599
- }
1600
- return { code: doRender(data, options, reducer, 0), stats: {
1610
+ return acc + renderToken(curr, options, reducer);
1611
+ }, 0), stats: {
1601
1612
  total: `${(performance.now() - startTime).toFixed(2)}ms`
1602
- } };
1613
+ }
1614
+ };
1603
1615
  }
1604
1616
  // @ts-ignore
1605
1617
  function doRender(data, options, reducer, level = 0, indents = []) {
@@ -1613,9 +1625,9 @@
1613
1625
  const indentSub = indents[level + 1];
1614
1626
  switch (data.typ) {
1615
1627
  case 'Declaration':
1616
- return `${data.nam}:${options.indent}${data.val.reduce((acc, curr) => acc + renderToken(curr), '')}`;
1628
+ return `${data.nam}:${options.indent}${data.val.reduce(reducer, '')}`;
1617
1629
  case 'Comment':
1618
- return options.removeComments ? '' : data.val;
1630
+ return !options.removeComments || (options.preserveLicense && data.val.startsWith('/*!')) ? data.val : '';
1619
1631
  case 'StyleSheet':
1620
1632
  return data.chi.reduce((css, node) => {
1621
1633
  const str = doRender(node, options, reducer, level, indents);
@@ -1636,7 +1648,7 @@
1636
1648
  let children = data.chi.reduce((css, node) => {
1637
1649
  let str;
1638
1650
  if (node.typ == 'Comment') {
1639
- str = options.removeComments ? '' : node.val;
1651
+ str = options.removeComments && (!options.preserveLicense || !node.val.startsWith('/*!')) ? '' : node.val;
1640
1652
  }
1641
1653
  else if (node.typ == 'Declaration') {
1642
1654
  if (node.val.length == 0) {
@@ -1669,7 +1681,18 @@
1669
1681
  }
1670
1682
  return '';
1671
1683
  }
1672
- function renderToken(token, options = {}) {
1684
+ function renderToken(token, options = {}, reducer) {
1685
+ if (reducer == null) {
1686
+ reducer = function (acc, curr) {
1687
+ if (curr.typ == 'Comment' && options.removeComments) {
1688
+ if (!options.preserveLicense || !curr.val.startsWith('/*!')) {
1689
+ return acc;
1690
+ }
1691
+ return acc + curr.val;
1692
+ }
1693
+ return acc + renderToken(curr, options, reducer);
1694
+ };
1695
+ }
1673
1696
  switch (token.typ) {
1674
1697
  case 'Color':
1675
1698
  if (options.minify || options.colorConvert) {
@@ -1720,22 +1743,19 @@
1720
1743
  case 'UrlFunc':
1721
1744
  case 'Pseudo-class-func':
1722
1745
  // @ts-ignore
1723
- return ( /* options.minify && 'Pseudo-class-func' == token.typ && token.val.slice(0, 2) == '::' ? token.val.slice(1) :*/token.val ?? '') + '(' + token.chi.reduce((acc, curr) => {
1724
- if (options.removeComments && curr.typ == 'Comment') {
1725
- if (!options.preserveLicense || !curr.val.startsWith('/*!')) {
1726
- return acc;
1727
- }
1728
- }
1729
- return acc + renderToken(curr, options);
1730
- }, '') + ')';
1746
+ return ( /* options.minify && 'Pseudo-class-func' == token.typ && token.val.slice(0, 2) == '::' ? token.val.slice(1) :*/token.val ?? '') + '(' + token.chi.reduce(reducer, '') + ')';
1731
1747
  case 'Includes':
1732
1748
  return '~=';
1733
1749
  case 'Dash-match':
1734
1750
  return '|=';
1735
1751
  case 'Lt':
1736
1752
  return '<';
1753
+ case 'Lte':
1754
+ return '<=';
1737
1755
  case 'Gt':
1738
1756
  return '>';
1757
+ case 'Gte':
1758
+ return '>=';
1739
1759
  case 'End-parens':
1740
1760
  return ')';
1741
1761
  case 'Attr-start':
@@ -1753,37 +1773,73 @@
1753
1773
  case 'Important':
1754
1774
  return '!important';
1755
1775
  case 'Attr':
1756
- return '[' + token.chi.reduce((acc, curr) => acc + renderToken(curr, options), '') + ']';
1776
+ return '[' + token.chi.reduce(reducer, '') + ']';
1757
1777
  case 'Time':
1758
- case 'Frequency':
1759
1778
  case 'Angle':
1760
1779
  case 'Length':
1761
1780
  case 'Dimension':
1762
- const val = (+token.val).toString();
1781
+ case 'Frequency':
1782
+ case 'Resolution':
1783
+ let val = reduceNumber(token.val);
1784
+ let unit = token.unit;
1785
+ if (token.typ == 'Angle') {
1786
+ const angle = getAngle(token);
1787
+ let v;
1788
+ let value = val + unit;
1789
+ for (const u of ['turn', 'deg', 'rad', 'grad']) {
1790
+ if (token.unit == u) {
1791
+ continue;
1792
+ }
1793
+ switch (u) {
1794
+ case 'turn':
1795
+ v = reduceNumber(angle);
1796
+ if (v.length + 4 < value.length) {
1797
+ val = v;
1798
+ unit = u;
1799
+ value = v + u;
1800
+ }
1801
+ break;
1802
+ case 'deg':
1803
+ v = reduceNumber(angle * 360);
1804
+ if (v.length + 3 < value.length) {
1805
+ val = v;
1806
+ unit = u;
1807
+ value = v + u;
1808
+ }
1809
+ break;
1810
+ case 'rad':
1811
+ v = reduceNumber(angle * (2 * Math.PI));
1812
+ if (v.length + 3 < value.length) {
1813
+ val = v;
1814
+ unit = u;
1815
+ value = v + u;
1816
+ }
1817
+ break;
1818
+ case 'grad':
1819
+ v = reduceNumber(angle * 400);
1820
+ if (v.length + 4 < value.length) {
1821
+ val = v;
1822
+ unit = u;
1823
+ value = v + u;
1824
+ }
1825
+ break;
1826
+ }
1827
+ }
1828
+ }
1763
1829
  if (val === '0') {
1764
- if (token.typ == 'Time') {
1830
+ if (unit == 'Time') {
1765
1831
  return '0s';
1766
1832
  }
1767
- if (token.typ == 'Frequency') {
1833
+ if (unit == 'Frequency') {
1768
1834
  return '0Hz';
1769
1835
  }
1770
1836
  // @ts-ignore
1771
- if (token.typ == 'Resolution') {
1837
+ if (unit == 'Resolution') {
1772
1838
  return '0x';
1773
1839
  }
1774
1840
  return '0';
1775
1841
  }
1776
- const chr = val.charAt(0);
1777
- if (chr == '-') {
1778
- const slice = val.slice(0, 2);
1779
- if (slice == '-0') {
1780
- return (val.length == 2 ? '0' : '-' + val.slice(2)) + token.unit;
1781
- }
1782
- }
1783
- else if (chr == '0') {
1784
- return val.slice(1) + token.unit;
1785
- }
1786
- return val + token.unit;
1842
+ return val + unit;
1787
1843
  case 'Perc':
1788
1844
  return token.val + '%';
1789
1845
  case 'Number':
@@ -1800,7 +1856,7 @@
1800
1856
  }
1801
1857
  return num;
1802
1858
  case 'Comment':
1803
- if (options.removeComments) {
1859
+ if (options.removeComments && (!options.preserveLicense || !token.val.startsWith('/*!'))) {
1804
1860
  return '';
1805
1861
  }
1806
1862
  case 'Url-token':
@@ -2071,6 +2127,7 @@
2071
2127
  i--;
2072
2128
  continue;
2073
2129
  }
2130
+ // @ts-ignore
2074
2131
  if (('propertyName' in acc[i] && acc[i].propertyName == property) || matchType(acc[i], props)) {
2075
2132
  if ('prefix' in props && props.previous != null && !(props.previous in tokens)) {
2076
2133
  return acc;
@@ -2236,17 +2293,18 @@
2236
2293
  if (props.multiple && props.separator != null && props.separator.typ == val.typ && eq(props.separator, val)) {
2237
2294
  continue;
2238
2295
  }
2239
- match = matchType(val, curr[1]);
2296
+ // @ts-ignore
2297
+ match = val.typ == 'Comment' || matchType(val, curr[1]);
2240
2298
  if (isShorthand) {
2241
2299
  isShorthand = match;
2242
2300
  }
2301
+ // @ts-ignore
2243
2302
  if (('propertyName' in val && val.propertyName == property) || match) {
2244
2303
  if (!(curr[0] in tokens)) {
2245
2304
  tokens[curr[0]] = [[]];
2246
2305
  }
2247
2306
  // is default value
2248
2307
  tokens[curr[0]][current].push(val);
2249
- // continue;
2250
2308
  }
2251
2309
  else {
2252
2310
  acc.push(curr[0]);
@@ -2263,7 +2321,9 @@
2263
2321
  if (!isShorthand || Object.entries(this.config.properties).some(entry => {
2264
2322
  // missing required property
2265
2323
  return entry[1].required && !(entry[0] in tokens);
2266
- }) || !Object.values(tokens).every(v => v.length == count)) {
2324
+ }) ||
2325
+ // @ts-ignore
2326
+ !Object.values(tokens).every(v => v.filter(t => t.typ != 'Comment').length == count)) {
2267
2327
  // @ts-ignore
2268
2328
  iterable = this.declarations.values();
2269
2329
  }
@@ -2816,6 +2876,17 @@
2816
2876
  continue;
2817
2877
  // }
2818
2878
  }
2879
+ // @ts-ignore
2880
+ if (hasDeclaration(node)) {
2881
+ // @ts-ignore
2882
+ minifyRule(node);
2883
+ }
2884
+ else {
2885
+ minify(node, options, recursive);
2886
+ }
2887
+ previous = node;
2888
+ nodeIndex = i;
2889
+ continue;
2819
2890
  }
2820
2891
  // @ts-ignore
2821
2892
  if (node.typ == 'Rule') {
@@ -3330,11 +3401,6 @@
3330
3401
  }
3331
3402
  buffer += quoteStr;
3332
3403
  while (value = peek()) {
3333
- // if (ind >= iterator.length) {
3334
- //
3335
- // yield pushToken(buffer, hasNewLine ? 'Bad-string' : 'Unclosed-string');
3336
- // break;
3337
- // }
3338
3404
  if (value == '\\') {
3339
3405
  const sequence = peek(6);
3340
3406
  let escapeSequence = '';
@@ -3354,9 +3420,23 @@
3354
3420
  }
3355
3421
  break;
3356
3422
  }
3423
+ // @ts-ignore
3424
+ if (isNewLine(codepoint)) {
3425
+ if (i == 1) {
3426
+ buffer += value + escapeSequence.slice(0, i);
3427
+ next(i + 1);
3428
+ continue;
3429
+ }
3430
+ // else {
3431
+ yield pushToken(buffer + value + escapeSequence.slice(0, i), 'Bad-string');
3432
+ buffer = '';
3433
+ // }
3434
+ next(i + 1);
3435
+ break;
3436
+ }
3357
3437
  // not hex or new line
3358
3438
  // @ts-ignore
3359
- if (i == 1 && !isNewLine(codepoint)) {
3439
+ else if (i == 1) {
3360
3440
  buffer += value + sequence[i];
3361
3441
  next(2);
3362
3442
  continue;
@@ -3376,13 +3456,6 @@
3376
3456
  next(escapeSequence.length + 1);
3377
3457
  continue;
3378
3458
  }
3379
- // buffer += value;
3380
- // if (ind >= iterator.length) {
3381
- //
3382
- // // drop '\\' at the end
3383
- // yield pushToken(buffer);
3384
- // break;
3385
- // }
3386
3459
  buffer += next(2);
3387
3460
  continue;
3388
3461
  }
@@ -3392,20 +3465,28 @@
3392
3465
  next();
3393
3466
  // i += value.length;
3394
3467
  buffer = '';
3395
- break;
3468
+ return;
3396
3469
  }
3397
3470
  if (isNewLine(value.charCodeAt(0))) {
3398
3471
  hasNewLine = true;
3399
3472
  }
3400
3473
  if (hasNewLine && value == ';') {
3401
- yield pushToken(buffer, 'Bad-string');
3474
+ yield pushToken(buffer + value, 'Bad-string');
3402
3475
  buffer = '';
3476
+ next();
3403
3477
  break;
3404
3478
  }
3405
3479
  buffer += value;
3406
- // i += value.length;
3407
3480
  next();
3408
3481
  }
3482
+ if (hasNewLine) {
3483
+ yield pushToken(buffer, 'Bad-string');
3484
+ }
3485
+ else {
3486
+ // EOF - 'Unclosed-string' fixed
3487
+ yield pushToken(buffer + quote, 'String');
3488
+ }
3489
+ buffer = '';
3409
3490
  }
3410
3491
  function peek(count = 1) {
3411
3492
  if (count == 1) {
@@ -3519,6 +3600,11 @@
3519
3600
  yield pushToken(buffer);
3520
3601
  buffer = '';
3521
3602
  }
3603
+ if (peek() == '=') {
3604
+ yield pushToken('', 'Lte');
3605
+ next();
3606
+ break;
3607
+ }
3522
3608
  buffer += value;
3523
3609
  value = next();
3524
3610
  if (ind >= iterator.length) {
@@ -3587,7 +3673,13 @@
3587
3673
  yield pushToken(buffer);
3588
3674
  buffer = '';
3589
3675
  }
3590
- yield pushToken('', 'Gt');
3676
+ if (peek() == '=') {
3677
+ yield pushToken('', 'Gte');
3678
+ next();
3679
+ }
3680
+ else {
3681
+ yield pushToken('', 'Gt');
3682
+ }
3591
3683
  consumeWhiteSpace();
3592
3684
  break;
3593
3685
  case '.':
@@ -3629,7 +3721,7 @@
3629
3721
  break;
3630
3722
  case '(':
3631
3723
  if (buffer.length == 0) {
3632
- yield pushToken('', 'Start-parens');
3724
+ yield pushToken(value);
3633
3725
  break;
3634
3726
  }
3635
3727
  buffer += value;
@@ -3743,9 +3835,11 @@
3743
3835
  if (buffer.length > 0) {
3744
3836
  yield pushToken(buffer);
3745
3837
  }
3838
+ // yield pushToken('', 'EOF');
3746
3839
  }
3747
3840
 
3748
3841
  const urlTokenMatcher = /^(["']?)[a-zA-Z0-9_/.-][a-zA-Z0-9_/:.#?-]+(\1)$/;
3842
+ const trimWhiteSpace = ['Gt', 'Gte', 'Lt', 'Lte'];
3749
3843
  const funcLike = ['Start-parens', 'Func', 'UrlFunc', 'Pseudo-class-func'];
3750
3844
  /**
3751
3845
  *
@@ -3792,6 +3886,10 @@
3792
3886
  let tokens = results.map(mapToken);
3793
3887
  let i;
3794
3888
  let loc;
3889
+ // if ((<Token>tokens.at(-1))?.typ == 'EOF') {
3890
+ //
3891
+ // tokens.pop();
3892
+ // }
3795
3893
  for (i = 0; i < tokens.length; i++) {
3796
3894
  if (tokens[i].typ == 'Comment') {
3797
3895
  // @ts-ignore
@@ -3910,7 +4008,7 @@
3910
4008
  // https://www.w3.org/TR/css-nesting-1/#conditionals
3911
4009
  // allowed nesting at-rules
3912
4010
  // there must be a top level rule in the stack
3913
- const raw = tokens.reduce((acc, curr) => {
4011
+ const raw = parseTokens(tokens, { minify: options.minify }).reduce((acc, curr) => {
3914
4012
  acc.push(renderToken(curr, { removeComments: true }));
3915
4013
  return acc;
3916
4014
  }, []);
@@ -3941,8 +4039,8 @@
3941
4039
  const uniq = new Map;
3942
4040
  parseTokens(tokens, { minify: options.minify }).reduce((acc, curr, index, array) => {
3943
4041
  if (curr.typ == 'Whitespace') {
3944
- if (array[index - 1]?.typ == 'Gt' ||
3945
- array[index + 1]?.typ == 'Gt' ||
4042
+ if (trimWhiteSpace.includes(array[index - 1]?.typ) ||
4043
+ trimWhiteSpace.includes(array[index + 1]?.typ) ||
3946
4044
  combinators.includes(array[index - 1]?.val) ||
3947
4045
  combinators.includes(array[index + 1]?.val)) {
3948
4046
  return acc;
@@ -4146,7 +4244,7 @@
4146
4244
  return ([
4147
4245
  'Whitespace', 'Semi-colon', 'Colon', 'Block-start',
4148
4246
  'Block-start', 'Attr-start', 'Attr-end', 'Start-parens', 'End-parens',
4149
- 'Comma', 'Gt', 'Lt'
4247
+ 'Comma', 'Gt', 'Lt', 'Gte', 'Lte', 'EOF'
4150
4248
  ].includes(hint) ? { typ: hint } : { typ: hint, val });
4151
4249
  }
4152
4250
  if (val == ' ') {
@@ -4274,11 +4372,12 @@
4274
4372
  const t = tokens[i];
4275
4373
  if (t.typ == 'Whitespace' && ((i == 0 ||
4276
4374
  i + 1 == tokens.length ||
4277
- ['Comma'].includes(tokens[i + 1].typ) ||
4375
+ ['Comma', 'Gte', 'Lte'].includes(tokens[i + 1].typ)) ||
4278
4376
  (i > 0 &&
4279
- tokens[i + 1]?.typ != 'Literal' &&
4280
- funcLike.includes(tokens[i - 1].typ) &&
4281
- !['var', 'calc'].includes(tokens[i - 1].val))))) {
4377
+ // tokens[i + 1]?.typ != 'Literal' ||
4378
+ // funcLike.includes(tokens[i - 1].typ) &&
4379
+ // !['var', 'calc'].includes((<FunctionToken>tokens[i - 1]).val)))) &&
4380
+ trimWhiteSpace.includes(tokens[i - 1].typ)))) {
4282
4381
  tokens.splice(i--, 1);
4283
4382
  continue;
4284
4383
  }
@@ -4395,7 +4494,7 @@
4395
4494
  let m = t.chi.length;
4396
4495
  while (m-- > 0) {
4397
4496
  // @ts-ignore
4398
- if (t.chi[m].typ == 'Literal') {
4497
+ if (['Literal'].concat(trimWhiteSpace).includes(t.chi[m].typ)) {
4399
4498
  // @ts-ignore
4400
4499
  if (t.chi[m + 1]?.typ == 'Whitespace') {
4401
4500
  // @ts-ignore