postcss-calc 11.0.0 → 11.0.2
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/README.md +2 -0
- package/package.json +7 -6
- package/src/lib/parser.js +63 -64
- package/src/lib/serialize.js +21 -2
- package/types/lib/parser.d.ts +0 -4
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "postcss-calc",
|
|
3
|
-
"version": "11.0.
|
|
3
|
+
"version": "11.0.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "PostCSS plugin to reduce calc()",
|
|
6
6
|
"keywords": [
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"homepage": "https://github.com/postcss/postcss-calc",
|
|
14
14
|
"repository": {
|
|
15
15
|
"type": "git",
|
|
16
|
-
"url": "https://github.com/postcss/postcss-calc.git"
|
|
16
|
+
"url": "git+https://github.com/postcss/postcss-calc.git"
|
|
17
17
|
},
|
|
18
18
|
"exports": {
|
|
19
19
|
".": {
|
|
@@ -34,16 +34,16 @@
|
|
|
34
34
|
"devEngines": {
|
|
35
35
|
"packageManager": {
|
|
36
36
|
"name": "pnpm",
|
|
37
|
-
"version": "11.
|
|
37
|
+
"version": "11.25.0"
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@csstools/css-calc": "^3.3.0",
|
|
42
42
|
"@rmenke/css-tokenizer-tests": "^1.2.0",
|
|
43
|
-
"@types/node": "^26.
|
|
43
|
+
"@types/node": "^26.4.0",
|
|
44
44
|
"fast-check": "^4.9.0",
|
|
45
|
-
"oxfmt": "^0.
|
|
46
|
-
"oxlint": "^1.
|
|
45
|
+
"oxfmt": "^0.65.0",
|
|
46
|
+
"oxlint": "^1.80.0",
|
|
47
47
|
"postcss": "^8.5.26",
|
|
48
48
|
"typescript": "~7.0.2"
|
|
49
49
|
},
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"scripts": {
|
|
58
58
|
"lint": "oxlint . && tsc && oxfmt --check",
|
|
59
59
|
"fmt": "oxfmt",
|
|
60
|
+
"benchmark:arithmetic-chains": "node scripts/benchmark-arithmetic-chains.mjs",
|
|
60
61
|
"test": "node --test --test-reporter=dot 'test/**/*.test.mjs' test/index.cjs test/convertUnit.cjs",
|
|
61
62
|
"test:mutation:corpus": "node test/mutation/corpus-selection.mjs",
|
|
62
63
|
"test:corpus:full": "POSTCSS_CALC_FULL_CORPUS=1 node --test test/conformance/corpus.test.mjs"
|
package/src/lib/parser.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
// Pratt parser. +/- emit Sum nodes; */÷ emit Product nodes. node.js
|
|
2
2
|
// constructors flatten and normalize on construction, while parenthesized
|
|
3
3
|
// sums retain a grouping marker for the opaque-subtraction invariant.
|
|
4
|
-
import { mkSum, mkProduct, negate, ident, call } from './node.js';
|
|
4
|
+
import { mkSum, mkProduct, negate, num, dim, ident, call } from './node.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @typedef {import('./tokenizer.js').Token} Token
|
|
8
8
|
* @typedef {import('./tokenizer.js').TokenType} TokenType
|
|
9
9
|
* @typedef {import('./node.js').Node} Node
|
|
10
10
|
* @typedef {(p: Parser, token: Token) => Node} PrefixParselet
|
|
11
|
-
* @typedef {{lbp: number, parse: (p: Parser, left: Node, token: Token) => Node}} InfixParselet
|
|
12
11
|
*/
|
|
13
12
|
|
|
14
13
|
/**
|
|
@@ -30,17 +29,17 @@ function foldCalcKeyword(name) {
|
|
|
30
29
|
// form arrives as a single ident because CSS Syntax tokenizes leading
|
|
31
30
|
// `-` + ident-start as one ident-token.
|
|
32
31
|
if (name === 'NaN' || name === '-NaN') {
|
|
33
|
-
return
|
|
32
|
+
return num(Number.NaN);
|
|
34
33
|
}
|
|
35
34
|
switch (name.toLowerCase()) {
|
|
36
35
|
case 'pi':
|
|
37
|
-
return
|
|
36
|
+
return num(Math.PI);
|
|
38
37
|
case 'e':
|
|
39
|
-
return
|
|
38
|
+
return num(Math.E);
|
|
40
39
|
case 'infinity':
|
|
41
|
-
return
|
|
40
|
+
return num(Infinity);
|
|
42
41
|
case '-infinity':
|
|
43
|
-
return
|
|
42
|
+
return num(-Infinity);
|
|
44
43
|
}
|
|
45
44
|
return null;
|
|
46
45
|
}
|
|
@@ -102,40 +101,58 @@ class Parser {
|
|
|
102
101
|
if (!rule || rule.lbp < minBp) {
|
|
103
102
|
break;
|
|
104
103
|
}
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
if (infixKey === '+' || infixKey === '-') {
|
|
105
|
+
/** @type {import('./node.js').SumTerm[]} */
|
|
106
|
+
const terms = [{ sign: /** @type {1} */ (1), node: left }];
|
|
107
|
+
do {
|
|
108
|
+
const token = this.next();
|
|
109
|
+
requireSurroundingWs(this, token);
|
|
110
|
+
terms.push({
|
|
111
|
+
sign: /** @type {1 | -1} */ (token.value === '+' ? 1 : -1),
|
|
112
|
+
node: this.parseExpr(ADD_BP + 1),
|
|
113
|
+
});
|
|
114
|
+
const next = this.peek();
|
|
115
|
+
if (
|
|
116
|
+
next.type !== 'punct' ||
|
|
117
|
+
(next.value !== '+' && next.value !== '-')
|
|
118
|
+
) {
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
} while (ADD_BP >= minBp);
|
|
122
|
+
left = mkSum(terms);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (infixKey === '*' || infixKey === '/') {
|
|
127
|
+
/** @type {import('./node.js').ProductFactor[]} */
|
|
128
|
+
const factors = [{ exponent: /** @type {1} */ (1), node: left }];
|
|
129
|
+
do {
|
|
130
|
+
const token = this.next();
|
|
131
|
+
factors.push({
|
|
132
|
+
exponent: /** @type {1 | -1} */ (token.value === '*' ? 1 : -1),
|
|
133
|
+
node: this.parseExpr(MUL_BP + 1),
|
|
134
|
+
});
|
|
135
|
+
const next = this.peek();
|
|
136
|
+
if (
|
|
137
|
+
next.type !== 'punct' ||
|
|
138
|
+
(next.value !== '*' && next.value !== '/')
|
|
139
|
+
) {
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
} while (MUL_BP >= minBp);
|
|
143
|
+
left = mkProduct(factors);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Every infix operator is handled above. Keep this defensive exit in
|
|
148
|
+
// case a future parselet is added without a chain implementation.
|
|
149
|
+
break;
|
|
107
150
|
}
|
|
108
151
|
|
|
109
152
|
return left;
|
|
110
153
|
}
|
|
111
154
|
}
|
|
112
155
|
|
|
113
|
-
/**
|
|
114
|
-
* @param {Node} left
|
|
115
|
-
* @param {Node} right
|
|
116
|
-
* @param {1 | -1} rightSign
|
|
117
|
-
* @return {Node}
|
|
118
|
-
*/
|
|
119
|
-
function addTerm(left, right, rightSign) {
|
|
120
|
-
return mkSum([
|
|
121
|
-
{ sign: 1, node: left },
|
|
122
|
-
{ sign: rightSign, node: right },
|
|
123
|
-
]);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* @param {Node} left
|
|
128
|
-
* @param {Node} right
|
|
129
|
-
* @param {1 | -1} rightExp
|
|
130
|
-
* @return {Node}
|
|
131
|
-
*/
|
|
132
|
-
function mulFactor(left, right, rightExp) {
|
|
133
|
-
return mkProduct([
|
|
134
|
-
{ exponent: 1, node: left },
|
|
135
|
-
{ exponent: rightExp, node: right },
|
|
136
|
-
]);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
156
|
const ADD_BP = 1;
|
|
140
157
|
const MUL_BP = 3;
|
|
141
158
|
const UNARY_BP = 7;
|
|
@@ -228,14 +245,14 @@ function requireSurroundingWs(p, token) {
|
|
|
228
245
|
|
|
229
246
|
/** @type {Record<string, PrefixParselet>} */
|
|
230
247
|
const PREFIX = {
|
|
231
|
-
number: (_p, t) => (
|
|
248
|
+
number: (_p, t) => num(Number.parseFloat(t.value)),
|
|
232
249
|
|
|
233
250
|
// Unit case normalization per §10.12: `1PX` serializes as `1px`.
|
|
234
|
-
dimension: (_p, t) =>
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
251
|
+
dimension: (_p, t) =>
|
|
252
|
+
dim(
|
|
253
|
+
Number.parseFloat(t.value),
|
|
254
|
+
t.unit === '%' ? '%' : /** @type {string} */ (t.unit).toLowerCase()
|
|
255
|
+
),
|
|
239
256
|
|
|
240
257
|
ident: (p, t) => {
|
|
241
258
|
const nxt = p.peek();
|
|
@@ -273,30 +290,12 @@ const PREFIX = {
|
|
|
273
290
|
'+': (p) => p.parseExpr(UNARY_BP),
|
|
274
291
|
};
|
|
275
292
|
|
|
276
|
-
/** @type {Record<string,
|
|
293
|
+
/** @type {Record<string, {lbp: number}>} */
|
|
277
294
|
const INFIX = {
|
|
278
|
-
'+': {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
return addTerm(left, p.parseExpr(ADD_BP + 1), 1);
|
|
283
|
-
},
|
|
284
|
-
},
|
|
285
|
-
'-': {
|
|
286
|
-
lbp: ADD_BP,
|
|
287
|
-
parse: (p, left, token) => {
|
|
288
|
-
requireSurroundingWs(p, token);
|
|
289
|
-
return addTerm(left, p.parseExpr(ADD_BP + 1), -1);
|
|
290
|
-
},
|
|
291
|
-
},
|
|
292
|
-
'*': {
|
|
293
|
-
lbp: MUL_BP,
|
|
294
|
-
parse: (p, left) => mulFactor(left, p.parseExpr(MUL_BP + 1), 1),
|
|
295
|
-
},
|
|
296
|
-
'/': {
|
|
297
|
-
lbp: MUL_BP,
|
|
298
|
-
parse: (p, left) => mulFactor(left, p.parseExpr(MUL_BP + 1), -1),
|
|
299
|
-
},
|
|
295
|
+
'+': { lbp: ADD_BP },
|
|
296
|
+
'-': { lbp: ADD_BP },
|
|
297
|
+
'*': { lbp: MUL_BP },
|
|
298
|
+
'/': { lbp: MUL_BP },
|
|
300
299
|
};
|
|
301
300
|
|
|
302
301
|
/**
|
package/src/lib/serialize.js
CHANGED
|
@@ -59,6 +59,25 @@ function degenerateKeyword(v) {
|
|
|
59
59
|
return v > 0 ? 'infinity' : '-infinity';
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Serialize a finite CSS number. CSS numbers may omit the zero before a
|
|
64
|
+
* fractional value between -1 and 1 (`.5`, `-.5`). Scientific notation is
|
|
65
|
+
* left untouched because it already has no leading zero to remove.
|
|
66
|
+
*
|
|
67
|
+
* @param {number} v
|
|
68
|
+
* @return {string}
|
|
69
|
+
*/
|
|
70
|
+
function serializeNumber(v) {
|
|
71
|
+
const text = String(v);
|
|
72
|
+
if (text.startsWith('0.')) {
|
|
73
|
+
return text.slice(1);
|
|
74
|
+
}
|
|
75
|
+
if (text.startsWith('-0.')) {
|
|
76
|
+
return `-${text.slice(2)}`;
|
|
77
|
+
}
|
|
78
|
+
return text;
|
|
79
|
+
}
|
|
80
|
+
|
|
62
81
|
/**
|
|
63
82
|
* @param {Node} node
|
|
64
83
|
* @param {SerializeOptions} [opts]
|
|
@@ -127,7 +146,7 @@ function serializeExpr(node, prec) {
|
|
|
127
146
|
if (isDegenerate(node.value)) {
|
|
128
147
|
return degenerateKeyword(node.value);
|
|
129
148
|
}
|
|
130
|
-
return
|
|
149
|
+
return serializeNumber(round(node.value, prec));
|
|
131
150
|
case 'Dim':
|
|
132
151
|
if (isDegenerate(node.value)) {
|
|
133
152
|
// Nested degenerate Dim wraps in calc() so the `<kw> * 1<unit>` form
|
|
@@ -135,7 +154,7 @@ function serializeExpr(node, prec) {
|
|
|
135
154
|
// inside a Product — `0 * Dim(Infinity, px)` would re-fold as NaN.
|
|
136
155
|
return `calc(${degenerateKeyword(node.value)} * 1${node.unit})`;
|
|
137
156
|
}
|
|
138
|
-
return `${round(node.value, prec)}${node.unit}`;
|
|
157
|
+
return `${serializeNumber(round(node.value, prec))}${node.unit}`;
|
|
139
158
|
case 'Ident':
|
|
140
159
|
return node.name;
|
|
141
160
|
case 'Call': {
|
package/types/lib/parser.d.ts
CHANGED
|
@@ -2,10 +2,6 @@ export type Token = import('./tokenizer.js').Token;
|
|
|
2
2
|
export type TokenType = import('./tokenizer.js').TokenType;
|
|
3
3
|
export type Node = import('./node.js').Node;
|
|
4
4
|
export type PrefixParselet = (p: Parser, token: Token) => Node;
|
|
5
|
-
export type InfixParselet = {
|
|
6
|
-
lbp: number;
|
|
7
|
-
parse: (p: Parser, left: Node, token: Token) => Node;
|
|
8
|
-
};
|
|
9
5
|
declare class Parser {
|
|
10
6
|
/** @private */
|
|
11
7
|
i;
|