postcss-calc 6.0.1 → 10.1.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/LICENSE +0 -0
- package/README.md +36 -54
- package/package.json +30 -34
- package/src/index.js +51 -0
- package/src/lib/convertUnit.js +160 -0
- package/src/lib/reducer.js +396 -0
- package/src/lib/stringifier.js +106 -0
- package/src/lib/transform.js +109 -0
- package/src/parser.d.ts +89 -0
- package/src/parser.js +4222 -0
- package/types/index.d.ts +25 -0
- package/types/lib/convertUnit.d.ts +8 -0
- package/types/lib/reducer.d.ts +14 -0
- package/types/lib/stringifier.d.ts +5 -0
- package/types/lib/transform.d.ts +6 -0
- package/CHANGELOG.md +0 -72
- package/dist/index.js +0 -34
- package/dist/lib/transform.js +0 -67
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const convertUnit = require('./convertUnit.js');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param {import('../parser').CalcNode} node
|
|
6
|
+
* @return {node is import('../parser').ValueExpression}
|
|
7
|
+
*/
|
|
8
|
+
function isValueType(node) {
|
|
9
|
+
switch (node.type) {
|
|
10
|
+
case 'LengthValue':
|
|
11
|
+
case 'AngleValue':
|
|
12
|
+
case 'TimeValue':
|
|
13
|
+
case 'FrequencyValue':
|
|
14
|
+
case 'ResolutionValue':
|
|
15
|
+
case 'EmValue':
|
|
16
|
+
case 'ExValue':
|
|
17
|
+
case 'ChValue':
|
|
18
|
+
case 'RemValue':
|
|
19
|
+
case 'VhValue':
|
|
20
|
+
case 'SvhValue':
|
|
21
|
+
case 'LvhValue':
|
|
22
|
+
case 'DvhValue':
|
|
23
|
+
case 'VwValue':
|
|
24
|
+
case 'SvwValue':
|
|
25
|
+
case 'LvwValue':
|
|
26
|
+
case 'DvwValue':
|
|
27
|
+
case 'VminValue':
|
|
28
|
+
case 'SvminValue':
|
|
29
|
+
case 'LvminValue':
|
|
30
|
+
case 'DvminValue':
|
|
31
|
+
case 'VmaxValue':
|
|
32
|
+
case 'SvmaxValue':
|
|
33
|
+
case 'LvmaxValue':
|
|
34
|
+
case 'DvmaxValue':
|
|
35
|
+
case 'VbValue':
|
|
36
|
+
case 'SvbValue':
|
|
37
|
+
case 'LvbValue':
|
|
38
|
+
case 'DvbValue':
|
|
39
|
+
case 'ViValue':
|
|
40
|
+
case 'SviValue':
|
|
41
|
+
case 'LviValue':
|
|
42
|
+
case 'DviValue':
|
|
43
|
+
case 'CqwValue':
|
|
44
|
+
case 'CqhValue':
|
|
45
|
+
case 'CqiValue':
|
|
46
|
+
case 'CqbValue':
|
|
47
|
+
case 'CqminValue':
|
|
48
|
+
case 'CqmaxValue':
|
|
49
|
+
case 'PercentageValue':
|
|
50
|
+
case 'LhValue':
|
|
51
|
+
case 'RlhValue':
|
|
52
|
+
case 'Number':
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** @param {'-'|'+'} operator */
|
|
59
|
+
function flip(operator) {
|
|
60
|
+
return operator === '+' ? '-' : '+';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @param {string} operator
|
|
65
|
+
* @returns {operator is '+'|'-'}
|
|
66
|
+
*/
|
|
67
|
+
function isAddSubOperator(operator) {
|
|
68
|
+
return operator === '+' || operator === '-';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @typedef {{preOperator: '+'|'-', node: import('../parser').CalcNode}} Collectible
|
|
73
|
+
*/
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @param {'+'|'-'} preOperator
|
|
77
|
+
* @param {import('../parser').CalcNode} node
|
|
78
|
+
* @param {Collectible[]} collected
|
|
79
|
+
* @param {number} precision
|
|
80
|
+
*/
|
|
81
|
+
function collectAddSubItems(preOperator, node, collected, precision) {
|
|
82
|
+
if (!isAddSubOperator(preOperator)) {
|
|
83
|
+
throw new Error(`invalid operator ${preOperator}`);
|
|
84
|
+
}
|
|
85
|
+
if (isValueType(node)) {
|
|
86
|
+
const itemIndex = collected.findIndex((x) => x.node.type === node.type);
|
|
87
|
+
if (itemIndex >= 0) {
|
|
88
|
+
if (node.value === 0) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// can cast because of the criterion used to find itemIndex
|
|
92
|
+
const otherValueNode = /** @type import('../parser').ValueExpression*/ (
|
|
93
|
+
collected[itemIndex].node
|
|
94
|
+
);
|
|
95
|
+
const { left: reducedNode, right: current } = convertNodesUnits(
|
|
96
|
+
otherValueNode,
|
|
97
|
+
node,
|
|
98
|
+
precision
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
if (collected[itemIndex].preOperator === '-') {
|
|
102
|
+
collected[itemIndex].preOperator = '+';
|
|
103
|
+
reducedNode.value *= -1;
|
|
104
|
+
}
|
|
105
|
+
if (preOperator === '+') {
|
|
106
|
+
reducedNode.value += current.value;
|
|
107
|
+
} else {
|
|
108
|
+
reducedNode.value -= current.value;
|
|
109
|
+
}
|
|
110
|
+
// make sure reducedNode.value >= 0
|
|
111
|
+
if (reducedNode.value >= 0) {
|
|
112
|
+
collected[itemIndex] = { node: reducedNode, preOperator: '+' };
|
|
113
|
+
} else {
|
|
114
|
+
reducedNode.value *= -1;
|
|
115
|
+
collected[itemIndex] = { node: reducedNode, preOperator: '-' };
|
|
116
|
+
}
|
|
117
|
+
} else {
|
|
118
|
+
// make sure node.value >= 0
|
|
119
|
+
if (node.value >= 0) {
|
|
120
|
+
collected.push({ node, preOperator });
|
|
121
|
+
} else {
|
|
122
|
+
node.value *= -1;
|
|
123
|
+
collected.push({ node, preOperator: flip(preOperator) });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
} else if (node.type === 'MathExpression') {
|
|
127
|
+
if (isAddSubOperator(node.operator)) {
|
|
128
|
+
collectAddSubItems(preOperator, node.left, collected, precision);
|
|
129
|
+
const collectRightOperator =
|
|
130
|
+
preOperator === '-' ? flip(node.operator) : node.operator;
|
|
131
|
+
collectAddSubItems(
|
|
132
|
+
collectRightOperator,
|
|
133
|
+
node.right,
|
|
134
|
+
collected,
|
|
135
|
+
precision
|
|
136
|
+
);
|
|
137
|
+
} else {
|
|
138
|
+
// * or /
|
|
139
|
+
const reducedNode = reduce(node, precision);
|
|
140
|
+
// prevent infinite recursive call
|
|
141
|
+
if (
|
|
142
|
+
reducedNode.type !== 'MathExpression' ||
|
|
143
|
+
isAddSubOperator(reducedNode.operator)
|
|
144
|
+
) {
|
|
145
|
+
collectAddSubItems(preOperator, reducedNode, collected, precision);
|
|
146
|
+
} else {
|
|
147
|
+
collected.push({ node: reducedNode, preOperator });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
} else if (node.type === 'ParenthesizedExpression') {
|
|
151
|
+
collectAddSubItems(preOperator, node.content, collected, precision);
|
|
152
|
+
} else {
|
|
153
|
+
collected.push({ node, preOperator });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* @param {import('../parser').CalcNode} node
|
|
159
|
+
* @param {number} precision
|
|
160
|
+
*/
|
|
161
|
+
function reduceAddSubExpression(node, precision) {
|
|
162
|
+
/** @type Collectible[] */
|
|
163
|
+
const collected = [];
|
|
164
|
+
collectAddSubItems('+', node, collected, precision);
|
|
165
|
+
|
|
166
|
+
const withoutZeroItem = collected.filter(
|
|
167
|
+
(item) => !(isValueType(item.node) && item.node.value === 0)
|
|
168
|
+
);
|
|
169
|
+
const firstNonZeroItem = withoutZeroItem[0]; // could be undefined
|
|
170
|
+
|
|
171
|
+
// prevent producing "calc(-var(--a))" or "calc()"
|
|
172
|
+
// which is invalid css
|
|
173
|
+
if (
|
|
174
|
+
!firstNonZeroItem ||
|
|
175
|
+
(firstNonZeroItem.preOperator === '-' &&
|
|
176
|
+
!isValueType(firstNonZeroItem.node))
|
|
177
|
+
) {
|
|
178
|
+
const firstZeroItem = collected.find(
|
|
179
|
+
(item) => isValueType(item.node) && item.node.value === 0
|
|
180
|
+
);
|
|
181
|
+
if (firstZeroItem) {
|
|
182
|
+
withoutZeroItem.unshift(firstZeroItem);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// make sure the preOperator of the first item is +
|
|
187
|
+
if (
|
|
188
|
+
withoutZeroItem[0].preOperator === '-' &&
|
|
189
|
+
isValueType(withoutZeroItem[0].node)
|
|
190
|
+
) {
|
|
191
|
+
withoutZeroItem[0].node.value *= -1;
|
|
192
|
+
withoutZeroItem[0].preOperator = '+';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let root = withoutZeroItem[0].node;
|
|
196
|
+
for (let i = 1; i < withoutZeroItem.length; i++) {
|
|
197
|
+
root = {
|
|
198
|
+
type: 'MathExpression',
|
|
199
|
+
operator: withoutZeroItem[i].preOperator,
|
|
200
|
+
left: root,
|
|
201
|
+
right: withoutZeroItem[i].node,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return root;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* @param {import('../parser').MathExpression} node
|
|
209
|
+
*/
|
|
210
|
+
function reduceDivisionExpression(node) {
|
|
211
|
+
if (!isValueType(node.right)) {
|
|
212
|
+
return node;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (node.right.type !== 'Number') {
|
|
216
|
+
throw new Error(`Cannot divide by "${node.right.unit}", number expected`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return applyNumberDivision(node.left, node.right.value);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* apply (expr) / number
|
|
224
|
+
*
|
|
225
|
+
* @param {import('../parser').CalcNode} node
|
|
226
|
+
* @param {number} divisor
|
|
227
|
+
* @return {import('../parser').CalcNode}
|
|
228
|
+
*/
|
|
229
|
+
function applyNumberDivision(node, divisor) {
|
|
230
|
+
if (divisor === 0) {
|
|
231
|
+
throw new Error('Cannot divide by zero');
|
|
232
|
+
}
|
|
233
|
+
if (isValueType(node)) {
|
|
234
|
+
node.value /= divisor;
|
|
235
|
+
return node;
|
|
236
|
+
}
|
|
237
|
+
if (node.type === 'MathExpression' && isAddSubOperator(node.operator)) {
|
|
238
|
+
// turn (a + b) / num into a/num + b/num
|
|
239
|
+
// is good for further reduction
|
|
240
|
+
// checkout the test case
|
|
241
|
+
// "should reduce division before reducing additions"
|
|
242
|
+
return {
|
|
243
|
+
type: 'MathExpression',
|
|
244
|
+
operator: node.operator,
|
|
245
|
+
left: applyNumberDivision(node.left, divisor),
|
|
246
|
+
right: applyNumberDivision(node.right, divisor),
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
// it is impossible to reduce it into a single value
|
|
250
|
+
// .e.g the node contains css variable
|
|
251
|
+
// so we just preserve the division and let browser do it
|
|
252
|
+
return {
|
|
253
|
+
type: 'MathExpression',
|
|
254
|
+
operator: '/',
|
|
255
|
+
left: node,
|
|
256
|
+
right: {
|
|
257
|
+
type: 'Number',
|
|
258
|
+
value: divisor,
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* @param {import('../parser').MathExpression} node
|
|
264
|
+
*/
|
|
265
|
+
function reduceMultiplicationExpression(node) {
|
|
266
|
+
// (expr) * number
|
|
267
|
+
if (node.right.type === 'Number') {
|
|
268
|
+
return applyNumberMultiplication(node.left, node.right.value);
|
|
269
|
+
}
|
|
270
|
+
// number * (expr)
|
|
271
|
+
if (node.left.type === 'Number') {
|
|
272
|
+
return applyNumberMultiplication(node.right, node.left.value);
|
|
273
|
+
}
|
|
274
|
+
return node;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* apply (expr) * number
|
|
279
|
+
* @param {number} multiplier
|
|
280
|
+
* @param {import('../parser').CalcNode} node
|
|
281
|
+
* @return {import('../parser').CalcNode}
|
|
282
|
+
*/
|
|
283
|
+
function applyNumberMultiplication(node, multiplier) {
|
|
284
|
+
if (isValueType(node)) {
|
|
285
|
+
node.value *= multiplier;
|
|
286
|
+
return node;
|
|
287
|
+
}
|
|
288
|
+
if (node.type === 'MathExpression' && isAddSubOperator(node.operator)) {
|
|
289
|
+
// turn (a + b) * num into a*num + b*num
|
|
290
|
+
// is good for further reduction
|
|
291
|
+
// checkout the test case
|
|
292
|
+
// "should reduce multiplication before reducing additions"
|
|
293
|
+
return {
|
|
294
|
+
type: 'MathExpression',
|
|
295
|
+
operator: node.operator,
|
|
296
|
+
left: applyNumberMultiplication(node.left, multiplier),
|
|
297
|
+
right: applyNumberMultiplication(node.right, multiplier),
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
// it is impossible to reduce it into a single value
|
|
301
|
+
// .e.g the node contains css variable
|
|
302
|
+
// so we just preserve the division and let browser do it
|
|
303
|
+
return {
|
|
304
|
+
type: 'MathExpression',
|
|
305
|
+
operator: '*',
|
|
306
|
+
left: node,
|
|
307
|
+
right: {
|
|
308
|
+
type: 'Number',
|
|
309
|
+
value: multiplier,
|
|
310
|
+
},
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* @param {import('../parser').ValueExpression} left
|
|
316
|
+
* @param {import('../parser').ValueExpression} right
|
|
317
|
+
* @param {number} precision
|
|
318
|
+
*/
|
|
319
|
+
function convertNodesUnits(left, right, precision) {
|
|
320
|
+
switch (left.type) {
|
|
321
|
+
case 'LengthValue':
|
|
322
|
+
case 'AngleValue':
|
|
323
|
+
case 'TimeValue':
|
|
324
|
+
case 'FrequencyValue':
|
|
325
|
+
case 'ResolutionValue':
|
|
326
|
+
if (right.type === left.type && right.unit && left.unit) {
|
|
327
|
+
const converted = convertUnit(
|
|
328
|
+
right.value,
|
|
329
|
+
right.unit,
|
|
330
|
+
left.unit,
|
|
331
|
+
precision
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
right = {
|
|
335
|
+
type: left.type,
|
|
336
|
+
value: converted,
|
|
337
|
+
unit: left.unit,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return { left, right };
|
|
342
|
+
default:
|
|
343
|
+
return { left, right };
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* @param {import('../parser').ParenthesizedExpression} node
|
|
349
|
+
*/
|
|
350
|
+
function includesNoCssProperties(node) {
|
|
351
|
+
return (
|
|
352
|
+
node.content.type !== 'Function' &&
|
|
353
|
+
(node.content.type !== 'MathExpression' ||
|
|
354
|
+
(node.content.right.type !== 'Function' &&
|
|
355
|
+
node.content.left.type !== 'Function'))
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* @param {import('../parser').CalcNode} node
|
|
360
|
+
* @param {number} precision
|
|
361
|
+
* @return {import('../parser').CalcNode}
|
|
362
|
+
*/
|
|
363
|
+
function reduce(node, precision) {
|
|
364
|
+
if (
|
|
365
|
+
node.type === 'MathExpression' &&
|
|
366
|
+
(node.left.type === 'CalcKeyword' || node.right.type === 'CalcKeyword')
|
|
367
|
+
) {
|
|
368
|
+
return node;
|
|
369
|
+
}
|
|
370
|
+
if (node.type === 'MathExpression') {
|
|
371
|
+
if (isAddSubOperator(node.operator)) {
|
|
372
|
+
// reduceAddSubExpression will call reduce recursively
|
|
373
|
+
return reduceAddSubExpression(node, precision);
|
|
374
|
+
}
|
|
375
|
+
node.left = reduce(node.left, precision);
|
|
376
|
+
node.right = reduce(node.right, precision);
|
|
377
|
+
switch (node.operator) {
|
|
378
|
+
case '/':
|
|
379
|
+
return reduceDivisionExpression(node);
|
|
380
|
+
case '*':
|
|
381
|
+
return reduceMultiplicationExpression(node);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return node;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (node.type === 'ParenthesizedExpression') {
|
|
388
|
+
if (includesNoCssProperties(node)) {
|
|
389
|
+
return reduce(node.content, precision);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return node;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
module.exports = reduce;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const order = {
|
|
3
|
+
'*': 0,
|
|
4
|
+
'/': 0,
|
|
5
|
+
'+': 1,
|
|
6
|
+
'-': 1,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {number} value
|
|
11
|
+
* @param {number | false} prec
|
|
12
|
+
*/
|
|
13
|
+
function round(value, prec) {
|
|
14
|
+
if (prec !== false) {
|
|
15
|
+
const precision = Math.pow(10, prec);
|
|
16
|
+
return Math.round(value * precision) / precision;
|
|
17
|
+
}
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {number | false} prec
|
|
23
|
+
* @param {import('../parser').CalcNode} node
|
|
24
|
+
*
|
|
25
|
+
* @return {string}
|
|
26
|
+
*/
|
|
27
|
+
function stringify(node, prec) {
|
|
28
|
+
switch (node.type) {
|
|
29
|
+
case 'MathExpression': {
|
|
30
|
+
const { left, right, operator: op } = node;
|
|
31
|
+
let str = '';
|
|
32
|
+
if (left.type === 'MathExpression' && order[op] < order[left.operator]) {
|
|
33
|
+
str += `(${stringify(left, prec)})`;
|
|
34
|
+
} else if (left.type === 'CalcKeyword') {
|
|
35
|
+
str += left.value;
|
|
36
|
+
} else {
|
|
37
|
+
str += stringify(left, prec);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
str += order[op] ? ` ${node.operator} ` : node.operator;
|
|
41
|
+
|
|
42
|
+
if (
|
|
43
|
+
right.type === 'MathExpression' &&
|
|
44
|
+
order[op] < order[right.operator]
|
|
45
|
+
) {
|
|
46
|
+
str += `(${stringify(right, prec)})`;
|
|
47
|
+
} else if (right.type === 'CalcKeyword') {
|
|
48
|
+
str += right.value;
|
|
49
|
+
} else {
|
|
50
|
+
str += stringify(right, prec);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return str;
|
|
54
|
+
}
|
|
55
|
+
case 'Number':
|
|
56
|
+
return round(node.value, prec).toString();
|
|
57
|
+
case 'Function':
|
|
58
|
+
return node.value.toString();
|
|
59
|
+
case 'ParenthesizedExpression':
|
|
60
|
+
return `(${stringify(node.content, prec)})`;
|
|
61
|
+
case 'CalcKeyword':
|
|
62
|
+
return node.value;
|
|
63
|
+
default:
|
|
64
|
+
return round(node.value, prec) + node.unit;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @param {string} calc
|
|
70
|
+
* @param {import('../parser').CalcNode} node
|
|
71
|
+
* @param {string} originalValue
|
|
72
|
+
* @param {{precision: number | false, warnWhenCannotResolve: boolean}} options
|
|
73
|
+
* @param {import("postcss").Result} result
|
|
74
|
+
* @param {import("postcss").ChildNode} item
|
|
75
|
+
*
|
|
76
|
+
* @returns {string}
|
|
77
|
+
*/
|
|
78
|
+
module.exports = function (calc, node, originalValue, options, result, item) {
|
|
79
|
+
let str = stringify(node, options.precision);
|
|
80
|
+
|
|
81
|
+
const shouldPrintCalc =
|
|
82
|
+
node.type === 'MathExpression' ||
|
|
83
|
+
node.type === 'Function' ||
|
|
84
|
+
node.type === 'ParenthesizedExpression' ||
|
|
85
|
+
node.type === 'CalcKeyword';
|
|
86
|
+
|
|
87
|
+
if (shouldPrintCalc) {
|
|
88
|
+
// if calc expression couldn't be resolved to a single value, re-wrap it as
|
|
89
|
+
// a calc()
|
|
90
|
+
if (node.type === 'ParenthesizedExpression') {
|
|
91
|
+
str = `${calc}${str}`;
|
|
92
|
+
} else {
|
|
93
|
+
str = `${calc}(${str})`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// if the warnWhenCannotResolve option is on, inform the user that the calc
|
|
97
|
+
// expression could not be resolved to a single value
|
|
98
|
+
if (options.warnWhenCannotResolve) {
|
|
99
|
+
result.warn('Could not reduce expression: ' + originalValue, {
|
|
100
|
+
plugin: 'postcss-calc',
|
|
101
|
+
node: item,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return str;
|
|
106
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const selectorParser = require('postcss-selector-parser');
|
|
3
|
+
const valueParser = require('postcss-value-parser');
|
|
4
|
+
|
|
5
|
+
const { parser } = require('../parser.js');
|
|
6
|
+
|
|
7
|
+
const reducer = require('./reducer.js');
|
|
8
|
+
const stringifier = require('./stringifier.js');
|
|
9
|
+
|
|
10
|
+
const MATCH_CALC = /((?:-(moz|webkit)-)?calc(?!-))/i;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {string} value
|
|
14
|
+
* @param {{precision: number, warnWhenCannotResolve: boolean}} options
|
|
15
|
+
* @param {import("postcss").Result} result
|
|
16
|
+
* @param {import("postcss").ChildNode} item
|
|
17
|
+
*/
|
|
18
|
+
function transformValue(value, options, result, item) {
|
|
19
|
+
return valueParser(value)
|
|
20
|
+
.walk((node) => {
|
|
21
|
+
// skip anything which isn't a calc() function
|
|
22
|
+
if (node.type !== 'function' || !MATCH_CALC.test(node.value)) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// stringify calc expression and produce an AST
|
|
27
|
+
const contents = valueParser.stringify(node.nodes);
|
|
28
|
+
const ast = parser.parse(contents);
|
|
29
|
+
|
|
30
|
+
// reduce AST to its simplest form, that is, either to a single value
|
|
31
|
+
// or a simplified calc expression
|
|
32
|
+
const reducedAst = reducer(ast, options.precision);
|
|
33
|
+
|
|
34
|
+
// stringify AST and write it back
|
|
35
|
+
/** @type {valueParser.Node} */ (node).type = 'word';
|
|
36
|
+
node.value = stringifier(
|
|
37
|
+
node.value,
|
|
38
|
+
reducedAst,
|
|
39
|
+
value,
|
|
40
|
+
options,
|
|
41
|
+
result,
|
|
42
|
+
item
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
return false;
|
|
46
|
+
})
|
|
47
|
+
.toString();
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* @param {import("postcss-selector-parser").Selectors} value
|
|
51
|
+
* @param {{precision: number, warnWhenCannotResolve: boolean}} options
|
|
52
|
+
* @param {import("postcss").Result} result
|
|
53
|
+
* @param {import("postcss").ChildNode} item
|
|
54
|
+
*/
|
|
55
|
+
function transformSelector(value, options, result, item) {
|
|
56
|
+
return selectorParser((selectors) => {
|
|
57
|
+
selectors.walk((node) => {
|
|
58
|
+
// attribute value
|
|
59
|
+
// e.g. the "calc(3*3)" part of "div[data-size="calc(3*3)"]"
|
|
60
|
+
if (node.type === 'attribute' && node.value) {
|
|
61
|
+
node.setValue(transformValue(node.value, options, result, item));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// tag value
|
|
65
|
+
// e.g. the "calc(3*3)" part of "div:nth-child(2n + calc(3*3))"
|
|
66
|
+
if (node.type === 'tag') {
|
|
67
|
+
node.value = transformValue(node.value, options, result, item);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return;
|
|
71
|
+
});
|
|
72
|
+
}).processSync(value);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @param {any} node
|
|
77
|
+
* @param {{precision: number, preserve: boolean, warnWhenCannotResolve: boolean}} options
|
|
78
|
+
* @param {'value'|'params'|'selector'} property
|
|
79
|
+
* @param {import("postcss").Result} result
|
|
80
|
+
*/
|
|
81
|
+
module.exports = (node, property, options, result) => {
|
|
82
|
+
let value = node[property];
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
value =
|
|
86
|
+
property === 'selector'
|
|
87
|
+
? transformSelector(node[property], options, result, node)
|
|
88
|
+
: transformValue(node[property], options, result, node);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (error instanceof Error) {
|
|
91
|
+
result.warn(error.message, { node });
|
|
92
|
+
} else {
|
|
93
|
+
result.warn('Error', { node });
|
|
94
|
+
}
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// if the preserve option is enabled and the value has changed, write the
|
|
99
|
+
// transformed value into a cloned node which is inserted before the current
|
|
100
|
+
// node, preserving the original value. Otherwise, overwrite the original
|
|
101
|
+
// value.
|
|
102
|
+
if (options.preserve && node[property] !== value) {
|
|
103
|
+
const clone = node.clone();
|
|
104
|
+
clone[property] = value;
|
|
105
|
+
node.parent.insertBefore(node, clone);
|
|
106
|
+
} else {
|
|
107
|
+
node[property] = value;
|
|
108
|
+
}
|
|
109
|
+
};
|
package/src/parser.d.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
export interface MathExpression {
|
|
2
|
+
type: 'MathExpression';
|
|
3
|
+
right: CalcNode;
|
|
4
|
+
left: CalcNode;
|
|
5
|
+
operator: '*' | '+' | '-' | '/';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface ParenthesizedExpression {
|
|
9
|
+
type: 'ParenthesizedExpression';
|
|
10
|
+
content: CalcNode;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface DimensionExpression {
|
|
14
|
+
type:
|
|
15
|
+
| 'LengthValue'
|
|
16
|
+
| 'AngleValue'
|
|
17
|
+
| 'TimeValue'
|
|
18
|
+
| 'FrequencyValue'
|
|
19
|
+
| 'PercentageValue'
|
|
20
|
+
| 'ResolutionValue'
|
|
21
|
+
| 'EmValue'
|
|
22
|
+
| 'ExValue'
|
|
23
|
+
| 'ChValue'
|
|
24
|
+
| 'RemValue'
|
|
25
|
+
| 'VhValue'
|
|
26
|
+
| 'SvhValue'
|
|
27
|
+
| 'LvhValue'
|
|
28
|
+
| 'DvhValue'
|
|
29
|
+
| 'VwValue'
|
|
30
|
+
| 'SvwValue'
|
|
31
|
+
| 'LvwValue'
|
|
32
|
+
| 'DvwValue'
|
|
33
|
+
| 'VminValue'
|
|
34
|
+
| 'SvminValue'
|
|
35
|
+
| 'LvminValue'
|
|
36
|
+
| 'DvminValue'
|
|
37
|
+
| 'VmaxValue'
|
|
38
|
+
| 'SvmaxValue'
|
|
39
|
+
| 'LvmaxValue'
|
|
40
|
+
| 'DvmaxValue'
|
|
41
|
+
| 'VbValue'
|
|
42
|
+
| 'SvbValue'
|
|
43
|
+
| 'LvbValue'
|
|
44
|
+
| 'DvbValue'
|
|
45
|
+
| 'ViValue'
|
|
46
|
+
| 'SviValue'
|
|
47
|
+
| 'LviValue'
|
|
48
|
+
| 'DviValue'
|
|
49
|
+
| 'CqwValue'
|
|
50
|
+
| 'CqhValue'
|
|
51
|
+
| 'CqbValue'
|
|
52
|
+
| 'CqiValue'
|
|
53
|
+
| 'CqminValue'
|
|
54
|
+
| 'CqmaxValue'
|
|
55
|
+
| 'LhValue'
|
|
56
|
+
| 'RlhValue';
|
|
57
|
+
value: number;
|
|
58
|
+
unit: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface NumberExpression {
|
|
62
|
+
type: 'Number';
|
|
63
|
+
value: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface FunctionExpression {
|
|
67
|
+
type: 'Function';
|
|
68
|
+
value: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface CalcKeywordExpression {
|
|
72
|
+
type: 'CalcKeyword';
|
|
73
|
+
value: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export type ValueExpression = DimensionExpression | NumberExpression;
|
|
77
|
+
|
|
78
|
+
export type CalcNode =
|
|
79
|
+
| MathExpression
|
|
80
|
+
| ValueExpression
|
|
81
|
+
| FunctionExpression
|
|
82
|
+
| ParenthesizedExpression
|
|
83
|
+
| CalcKeywordExpression;
|
|
84
|
+
|
|
85
|
+
export interface Parser {
|
|
86
|
+
parse: (arg: string) => CalcNode;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export const parser: Parser;
|