postcss-calc 11.0.0-rc.0 → 11.0.0-rc.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postcss-calc",
3
- "version": "11.0.0-rc.0",
3
+ "version": "11.0.0-rc.1",
4
4
  "description": "PostCSS plugin to reduce calc()",
5
5
  "keywords": [
6
6
  "css",
@@ -31,6 +31,7 @@
31
31
  "@eslint/js": "^10.0.1",
32
32
  "@rmenke/css-tokenizer-tests": "^1.2.0",
33
33
  "@stryker-mutator/core": "^9.6.1",
34
+ "@stryker-mutator/tap-runner": "^9.6.1",
34
35
  "@stryker-mutator/typescript-checker": "^9.6.1",
35
36
  "@types/node": "^26.1.1",
36
37
  "eslint": "^10.7.0",
@@ -15,7 +15,15 @@
15
15
  * @property {string} [calcName] Wrapper name to use when `calc()` is needed. Default `'calc'`.
16
16
  */
17
17
 
18
+ // Below this is float noise, not a value: `0.1 + 0.2 - 0.3` is 5.5e-17.
19
+ const NOISE_FLOOR = 1e-12;
20
+
18
21
  /**
22
+ * Rounding to `prec` decimal places turns `calc(1/1000000)` into `0`, and a
23
+ * `0` in CSS is often a switch, not a small number (`flex-grow: 0` never
24
+ * grows). So when a value is too small for `prec`, keep its significant digits
25
+ * instead: `1/1000000` -> `0.000001`, `1/3000000` -> `3.3333e-7`.
26
+ *
19
27
  * @param {number} v
20
28
  * @param {number | false} prec
21
29
  * @return {number}
@@ -25,7 +33,12 @@ function round(v, prec) {
25
33
  return v;
26
34
  }
27
35
  const m = Math.pow(10, prec);
28
- return Math.round(v * m) / m;
36
+ const rounded = Math.round(v * m) / m;
37
+ if (rounded === 0 && Math.abs(v) > NOISE_FLOOR) {
38
+ // toPrecision needs at least one significant digit; `prec` may be 0.
39
+ return Number(v.toPrecision(Math.max(prec, 1)));
40
+ }
41
+ return rounded;
29
42
  }
30
43
 
31
44
  // §10.13 / §10.7.2: Infinity/NaN serialize as canonical keywords.
@@ -11,6 +11,7 @@ const { convert } = require('../convertUnits.js');
11
11
  * @typedef {object} UnitBucket
12
12
  * @property {string} unit
13
13
  * @property {number} total
14
+ * @property {number} scale largest |term| accumulated into `total`, for noise detection
14
15
  * @property {import('../convertUnits.js').BaseType | null} base
15
16
  * @property {number} order
16
17
  */
@@ -12,6 +12,19 @@ const { mergeConvertibleBuckets } = require('./bucket.js');
12
12
  * @typedef {import('./bucket.js').UnitBucket} UnitBucket
13
13
  */
14
14
 
15
+ // Subtracting near-equal terms leaves float dust: `0.07 * 1e7 - 700000` is
16
+ // 1.16e-10, not 0. Snap a total that's tiny next to its terms back to 0.
17
+ const NOISE_REL = Number.EPSILON * 8;
18
+
19
+ /**
20
+ * @param {number} total
21
+ * @param {number} scale largest |term| accumulated into `total`
22
+ * @return {number}
23
+ */
24
+ function denoise(total, scale) {
25
+ return Math.abs(total) < scale * NOISE_REL ? 0 : total;
26
+ }
27
+
15
28
  /**
16
29
  * @param {Sum} sum
17
30
  * @param {SimplifyFn} simplify
@@ -23,6 +36,7 @@ function simplifySum(sum, simplify) {
23
36
  // encountered unit. `100vh - 5rem - 10rem - 100px` → `-15rem` in phase 1,
24
37
  // then vh/rem/px stay separate in phase 2 (none convert to each other).
25
38
  let numTotal = 0;
39
+ let numScale = 0;
26
40
  /** @type {Map<string, UnitBucket>} */
27
41
  const byUnit = new Map();
28
42
  /** @type {SumTerm[]} */
@@ -43,6 +57,7 @@ function simplifySum(sum, simplify) {
43
57
  }
44
58
  if (n.type === 'Num') {
45
59
  numTotal += sign * n.value;
60
+ numScale = Math.max(numScale, Math.abs(n.value));
46
61
  return;
47
62
  }
48
63
  if (n.type === 'Dim') {
@@ -50,10 +65,12 @@ function simplifySum(sum, simplify) {
50
65
  const existing = byUnit.get(key);
51
66
  if (existing) {
52
67
  existing.total += sign * n.value;
68
+ existing.scale = Math.max(existing.scale, Math.abs(n.value));
53
69
  } else {
54
70
  byUnit.set(key, {
55
71
  unit: n.unit,
56
72
  total: sign * n.value,
73
+ scale: Math.abs(n.value),
57
74
  base: baseOf(n.unit),
58
75
  order: bucketOrder++,
59
76
  });
@@ -71,9 +88,9 @@ function simplifySum(sum, simplify) {
71
88
  // unconditionally is harmless. Zero-valued unit buckets are kept for
72
89
  // type info (WPT calc-serialization-002).
73
90
  /** @type {SumTerm[]} */
74
- const terms = [{ sign: 1, node: num(numTotal) }];
91
+ const terms = [{ sign: 1, node: num(denoise(numTotal, numScale)) }];
75
92
  for (const bucket of mergeConvertibleBuckets([...byUnit.values()])) {
76
- terms.push({ sign: 1, node: dim(bucket.total, bucket.unit) });
93
+ terms.push({ sign: 1, node: dim(denoise(bucket.total, bucket.scale), bucket.unit) });
77
94
  }
78
95
  terms.push(...opaque);
79
96
 
@@ -1,6 +1,10 @@
1
1
  export type UnitBucket = {
2
2
  unit: string;
3
3
  total: number;
4
+ /**
5
+ * largest |term| accumulated into `total`, for noise detection
6
+ */
7
+ scale: number;
4
8
  base: import("../convertUnits.js").BaseType | null;
5
9
  order: number;
6
10
  };
@@ -8,6 +12,7 @@ export type UnitBucket = {
8
12
  * @typedef {object} UnitBucket
9
13
  * @property {string} unit
10
14
  * @property {number} total
15
+ * @property {number} scale largest |term| accumulated into `total`, for noise detection
11
16
  * @property {import('../convertUnits.js').BaseType | null} base
12
17
  * @property {number} order
13
18
  */
@@ -3,13 +3,6 @@ export type Sum = import("../node.js").Sum;
3
3
  export type SumTerm = import("../node.js").SumTerm;
4
4
  export type SimplifyFn = import("../simplify.js").SimplifyFn;
5
5
  export type UnitBucket = import("./bucket.js").UnitBucket;
6
- /**
7
- * @typedef {import('../node.js').Node} Node
8
- * @typedef {import('../node.js').Sum} Sum
9
- * @typedef {import('../node.js').SumTerm} SumTerm
10
- * @typedef {import('../simplify.js').SimplifyFn} SimplifyFn
11
- * @typedef {import('./bucket.js').UnitBucket} UnitBucket
12
- */
13
6
  /**
14
7
  * @param {Sum} sum
15
8
  * @param {SimplifyFn} simplify