postcss-calc 11.1.2 → 11.2.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/README.md +40 -18
- package/package.json +18 -14
- package/src/index.js +8 -5
- package/src/lib/analyze.js +272 -0
- package/src/lib/block-index.js +118 -0
- package/src/lib/compile.js +80 -0
- package/src/lib/functions.js +394 -0
- package/src/lib/limits.js +49 -0
- package/src/lib/node.js +63 -21
- package/src/lib/opaque.js +12 -32
- package/src/lib/parser.js +373 -328
- package/src/lib/print.js +75 -0
- package/src/lib/regex.js +4 -0
- package/src/lib/scan.js +61 -0
- package/src/lib/serialize.js +626 -223
- package/src/lib/simplify/call.js +5 -98
- package/src/lib/simplify/round.js +4 -4
- package/src/lib/simplify/sum.js +14 -5
- package/src/lib/simplify.js +20 -5
- package/src/reduce.js +46 -115
- package/types/index.d.ts +4 -0
- package/types/lib/analyze.d.ts +45 -0
- package/types/lib/block-index.d.ts +35 -0
- package/types/lib/calculation-type.d.ts +18 -0
- package/types/lib/compile.d.ts +37 -0
- package/types/lib/functions.d.ts +58 -0
- package/types/lib/limits.d.ts +8 -0
- package/types/lib/node.d.ts +19 -3
- package/types/lib/opaque.d.ts +8 -14
- package/types/lib/parser.d.ts +42 -48
- package/types/lib/print.d.ts +16 -0
- package/types/lib/regex.d.ts +2 -0
- package/types/lib/scan.d.ts +35 -0
- package/types/lib/serialize.d.ts +19 -2
- package/types/lib/simplify/call.d.ts +4 -18
- package/types/lib/simplify/round.d.ts +3 -1
- package/types/lib/simplify.d.ts +5 -2
- package/types/reduce.d.ts +47 -6
- package/src/lib/tokenizer.js +0 -10
- package/types/lib/tokenizer.d.ts +0 -3
package/src/lib/simplify/call.js
CHANGED
|
@@ -1,90 +1,13 @@
|
|
|
1
1
|
// Pre-simplify args once, route by name. Leaf folds receive simplified
|
|
2
2
|
// args so they don't need to recurse into `simplify` themselves.
|
|
3
3
|
|
|
4
|
-
import { simplifyMinMax } from './min-max.js';
|
|
5
|
-
import { simplifyClamp } from './clamp.js';
|
|
6
|
-
import { simplifyAbs } from './abs.js';
|
|
7
|
-
import { simplifySign } from './sign.js';
|
|
8
|
-
import { simplifyModRem } from './mod-rem.js';
|
|
9
|
-
import { simplifyRound } from './round.js';
|
|
10
|
-
import { simplifyTrig } from './trig.js';
|
|
11
|
-
import { simplifyInverseTrig } from './inverse-trig.js';
|
|
12
|
-
import { simplifyAtan2 } from './atan2.js';
|
|
13
|
-
import { simplifyPow } from './pow.js';
|
|
14
|
-
import { simplifySqrt } from './sqrt.js';
|
|
15
|
-
import { simplifyExp } from './exp.js';
|
|
16
|
-
import { simplifyLog } from './log.js';
|
|
17
|
-
import { simplifyHypot } from './hypot.js';
|
|
18
|
-
|
|
19
4
|
import { call } from '../node.js';
|
|
20
|
-
import {
|
|
5
|
+
import { mathFunctions, isCalculationFunction } from '../functions.js';
|
|
21
6
|
|
|
22
7
|
/** @typedef {import('../node.js').Node} Node */
|
|
23
8
|
/** @typedef {import('../simplify.js').SimplifyFn} SimplifyFn */
|
|
24
|
-
|
|
25
9
|
/** @typedef {(name: string, args: Node[]) => Node} MathSimplifier */
|
|
26
10
|
|
|
27
|
-
// Bare CSS math functions with implemented simplification semantics, keyed
|
|
28
|
-
// by lowercase name. calc() and its vendor-prefixed forms are handled
|
|
29
|
-
// separately as wrappers in simplifyCall. This map is the single source of
|
|
30
|
-
// truth for dispatch, `isSupportedMathFunction`, and `QUICK_MATH_TEST`.
|
|
31
|
-
/** @type {Map<string, MathSimplifier>} */
|
|
32
|
-
const MATH_SIMPLIFIERS = new Map([
|
|
33
|
-
['min', simplifyMinMax],
|
|
34
|
-
['max', simplifyMinMax],
|
|
35
|
-
['clamp', (_name, args) => simplifyClamp(args)],
|
|
36
|
-
['abs', (_name, args) => simplifyAbs(args)],
|
|
37
|
-
['sign', (_name, args) => simplifySign(args)],
|
|
38
|
-
['mod', (_name, args) => simplifyModRem('mod', args)],
|
|
39
|
-
['rem', (_name, args) => simplifyModRem('rem', args)],
|
|
40
|
-
['round', (_name, args) => simplifyRound(args)],
|
|
41
|
-
['sin', (_name, args) => simplifyTrig('sin', args)],
|
|
42
|
-
['cos', (_name, args) => simplifyTrig('cos', args)],
|
|
43
|
-
['tan', (_name, args) => simplifyTrig('tan', args)],
|
|
44
|
-
['asin', (_name, args) => simplifyInverseTrig('asin', args)],
|
|
45
|
-
['acos', (_name, args) => simplifyInverseTrig('acos', args)],
|
|
46
|
-
['atan', (_name, args) => simplifyInverseTrig('atan', args)],
|
|
47
|
-
['atan2', (_name, args) => simplifyAtan2(args)],
|
|
48
|
-
['pow', (_name, args) => simplifyPow(args)],
|
|
49
|
-
['sqrt', (_name, args) => simplifySqrt(args)],
|
|
50
|
-
['hypot', (_name, args) => simplifyHypot(args)],
|
|
51
|
-
['log', (_name, args) => simplifyLog(args)],
|
|
52
|
-
['exp', (_name, args) => simplifyExp(args)],
|
|
53
|
-
]);
|
|
54
|
-
|
|
55
|
-
const mathFnNames = [...MATH_SIMPLIFIERS.keys()].sort(
|
|
56
|
-
(a, b) => b.length - a.length
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
const QUICK_MATH_TEST = new RegExp(
|
|
60
|
-
`(?:-(?:webkit|moz)-)?(?:calc|${mathFnNames.join('|')})\\(`,
|
|
61
|
-
'i'
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Fast check to determine whether a CSS component value could contain
|
|
66
|
-
* a supported calculation or math function call (or an escape sequence
|
|
67
|
-
* that could decode to one).
|
|
68
|
-
*
|
|
69
|
-
* @param {string} value
|
|
70
|
-
* @return {boolean}
|
|
71
|
-
*/
|
|
72
|
-
function hasPotentialMathFunction(value) {
|
|
73
|
-
return (
|
|
74
|
-
value.includes('(') && (QUICK_MATH_TEST.test(value) || value.includes('\\'))
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Whether a bare CSS math function has an implemented simplifier.
|
|
80
|
-
*
|
|
81
|
-
* @param {string} name
|
|
82
|
-
* @return {boolean}
|
|
83
|
-
*/
|
|
84
|
-
function isSupportedMathFunction(name) {
|
|
85
|
-
return MATH_SIMPLIFIERS.has(name.toLowerCase());
|
|
86
|
-
}
|
|
87
|
-
|
|
88
11
|
/**
|
|
89
12
|
* @param {Extract<Node, { type: 'Call' }>} node
|
|
90
13
|
* @param {SimplifyFn} simplify
|
|
@@ -93,7 +16,7 @@ function isSupportedMathFunction(name) {
|
|
|
93
16
|
function simplifyCall(node, simplify) {
|
|
94
17
|
const name = node.name.toLowerCase();
|
|
95
18
|
|
|
96
|
-
if (name
|
|
19
|
+
if (isCalculationFunction(name)) {
|
|
97
20
|
if (node.args.length !== 1) {
|
|
98
21
|
throw new Error(`${node.name}() takes exactly one argument`);
|
|
99
22
|
}
|
|
@@ -102,28 +25,12 @@ function simplifyCall(node, simplify) {
|
|
|
102
25
|
|
|
103
26
|
const args = node.args.map((a) => simplify(a));
|
|
104
27
|
|
|
105
|
-
const
|
|
106
|
-
if (components) {
|
|
107
|
-
const result = call(node.name, args, node.rawName);
|
|
108
|
-
return setComponents(result, simplifyComponents(components, simplify));
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const simplifier = MATH_SIMPLIFIERS.get(name);
|
|
28
|
+
const simplifier = mathFunctions.get(name)?.simplify;
|
|
112
29
|
if (simplifier) {
|
|
113
|
-
|
|
114
|
-
// fallback; the rest normalize to lowercase internally.
|
|
115
|
-
return simplifier(
|
|
116
|
-
name === 'min' || name === 'max' ? node.name : name,
|
|
117
|
-
args
|
|
118
|
-
);
|
|
30
|
+
return simplifier(name, args);
|
|
119
31
|
}
|
|
120
32
|
|
|
121
33
|
return call(node.name, args, node.rawName);
|
|
122
34
|
}
|
|
123
35
|
|
|
124
|
-
export {
|
|
125
|
-
isSupportedMathFunction,
|
|
126
|
-
simplifyCall,
|
|
127
|
-
hasPotentialMathFunction,
|
|
128
|
-
QUICK_MATH_TEST,
|
|
129
|
-
};
|
|
36
|
+
export { simplifyCall };
|
|
@@ -44,11 +44,11 @@ function simplifyRound(args) {
|
|
|
44
44
|
// to applyRound, where floor*b===ceil*b===±∞ collapses back to A
|
|
45
45
|
// (§10.3.1 "result is the same infinity").
|
|
46
46
|
if (Number.isNaN(b)) {
|
|
47
|
-
return
|
|
47
|
+
return foldResult(fold, Number.NaN);
|
|
48
48
|
}
|
|
49
49
|
if (!Number.isFinite(b)) {
|
|
50
50
|
if (!Number.isFinite(a)) {
|
|
51
|
-
return
|
|
51
|
+
return foldResult(fold, Number.NaN);
|
|
52
52
|
}
|
|
53
53
|
let result;
|
|
54
54
|
if (strategy === 'up' && a > 0) {
|
|
@@ -63,7 +63,7 @@ function simplifyRound(args) {
|
|
|
63
63
|
|
|
64
64
|
const result = applyRound(strategy, a, b);
|
|
65
65
|
if (Number.isNaN(result)) {
|
|
66
|
-
return
|
|
66
|
+
return foldResult(fold, Number.NaN);
|
|
67
67
|
}
|
|
68
68
|
return foldResult(fold, result);
|
|
69
69
|
}
|
|
@@ -116,4 +116,4 @@ function applyRound(strategy, a, b) {
|
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
export { simplifyRound };
|
|
119
|
+
export { ROUND_STRATEGIES, simplifyRound };
|
package/src/lib/simplify/sum.js
CHANGED
|
@@ -34,6 +34,7 @@ function simplifySum(sum, simplify) {
|
|
|
34
34
|
// encountered unit. `100vh - 5rem - 10rem - 100px` → `-15rem` in phase 1,
|
|
35
35
|
// then vh/rem/px stay separate in phase 2 (none convert to each other).
|
|
36
36
|
let numTotal = 0;
|
|
37
|
+
let hasNum = false;
|
|
37
38
|
let numScale = 0;
|
|
38
39
|
/** @type {Map<string, UnitBucket>} */
|
|
39
40
|
const byUnit = new Map();
|
|
@@ -64,7 +65,11 @@ function simplifySum(sum, simplify) {
|
|
|
64
65
|
return;
|
|
65
66
|
}
|
|
66
67
|
if (n.type === 'Num') {
|
|
67
|
-
|
|
68
|
+
// Do not add an artificial +0 before the first value: `+0 + -0`
|
|
69
|
+
// becomes +0 in IEEE-754 and would discard a parsed/simplified -0.
|
|
70
|
+
const value = sign * n.value;
|
|
71
|
+
numTotal = hasNum ? numTotal + value : value;
|
|
72
|
+
hasNum = true;
|
|
68
73
|
numScale = Math.max(numScale, Math.abs(n.value));
|
|
69
74
|
return;
|
|
70
75
|
}
|
|
@@ -92,11 +97,15 @@ function simplifySum(sum, simplify) {
|
|
|
92
97
|
processTerm(t.sign, simplify(t.node));
|
|
93
98
|
}
|
|
94
99
|
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
// type
|
|
100
|
+
// Keep a numeric total, including zero, when the source sum contained a
|
|
101
|
+
// number. In a mixed or unresolved sum that value constrains the other
|
|
102
|
+
// terms to <number>; mkSum retains it as a type anchor. A negative zero is
|
|
103
|
+
// also deliberately retained. Zero-valued unit buckets are kept for type
|
|
104
|
+
// info (WPT calc-serialization-002).
|
|
98
105
|
/** @type {SumTerm[]} */
|
|
99
|
-
const terms =
|
|
106
|
+
const terms = hasNum
|
|
107
|
+
? [{ sign: /** @type {1} */ (1), node: num(denoise(numTotal, numScale)) }]
|
|
108
|
+
: [];
|
|
100
109
|
for (const bucket of mergeConvertibleBuckets([...byUnit.values()])) {
|
|
101
110
|
terms.push({
|
|
102
111
|
sign: 1,
|
package/src/lib/simplify.js
CHANGED
|
@@ -5,31 +5,46 @@
|
|
|
5
5
|
import { simplifySum } from './simplify/sum.js';
|
|
6
6
|
import { simplifyProduct } from './simplify/product.js';
|
|
7
7
|
import { simplifyCall } from './simplify/call.js';
|
|
8
|
+
import { simplifyComponents } from './opaque.js';
|
|
9
|
+
import { opaqueCall } from './node.js';
|
|
10
|
+
import { assertDepth } from './limits.js';
|
|
8
11
|
|
|
9
12
|
/**
|
|
10
13
|
* @typedef {import('./node.js').Node} Node
|
|
11
14
|
*
|
|
12
|
-
* Recursive simplifier reference, threaded into Sum/Product/Call. Lets
|
|
15
|
+
* Recursive simplifier reference, threaded into Sum/Product/Call/OpaqueCall. Lets
|
|
13
16
|
* leaf fold modules avoid circular imports of the entry function.
|
|
14
17
|
* @typedef {(node: Node) => Node} SimplifyFn
|
|
15
18
|
*/
|
|
16
19
|
|
|
17
20
|
/**
|
|
21
|
+
* Simplify is an independent, composable AST transformation. It may
|
|
22
|
+
* synthesize canonical nodes while preserving the Node -> Node contract.
|
|
18
23
|
* @param {Node} node
|
|
24
|
+
* @param {number} [depth]
|
|
19
25
|
* @return {Node}
|
|
20
26
|
*/
|
|
21
|
-
function simplify(node) {
|
|
27
|
+
function simplify(node, depth = 0) {
|
|
28
|
+
assertDepth(depth);
|
|
29
|
+
/** @param {Node} value */
|
|
30
|
+
const child = (value) => simplify(value, depth + 1);
|
|
22
31
|
switch (node.type) {
|
|
23
32
|
case 'Num':
|
|
24
33
|
case 'Dim':
|
|
25
34
|
case 'Ident':
|
|
26
35
|
return node;
|
|
27
36
|
case 'Call':
|
|
28
|
-
return simplifyCall(node,
|
|
37
|
+
return simplifyCall(node, child);
|
|
38
|
+
case 'OpaqueCall':
|
|
39
|
+
return opaqueCall(
|
|
40
|
+
node.name,
|
|
41
|
+
simplifyComponents(node.components, child),
|
|
42
|
+
node.rawName
|
|
43
|
+
);
|
|
29
44
|
case 'Sum':
|
|
30
|
-
return simplifySum(node,
|
|
45
|
+
return simplifySum(node, child);
|
|
31
46
|
case 'Product':
|
|
32
|
-
return simplifyProduct(node,
|
|
47
|
+
return simplifyProduct(node, child);
|
|
33
48
|
}
|
|
34
49
|
}
|
|
35
50
|
|
package/src/reduce.js
CHANGED
|
@@ -1,32 +1,20 @@
|
|
|
1
1
|
// CSS component-value reducer. This module deliberately has no PostCSS
|
|
2
2
|
// dependency so it can also be used for individual declaration values,
|
|
3
3
|
// at-rule parameters, or selector text.
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
} from '
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
isSupportedMathFunction,
|
|
12
|
-
hasPotentialMathFunction,
|
|
13
|
-
QUICK_MATH_TEST,
|
|
14
|
-
} from './lib/simplify/call.js';
|
|
15
|
-
import { serialize } from './lib/serialize.js';
|
|
16
|
-
|
|
17
|
-
const MATCH_CALC = /^(?:-(?:moz|webkit)-)?calc$/i;
|
|
18
|
-
|
|
19
|
-
const BLOCK_CLOSE = new Map([
|
|
20
|
-
[CssType.OpenParen, CssType.CloseParen],
|
|
21
|
-
[CssType.OpenSquare, CssType.CloseSquare],
|
|
22
|
-
[CssType.OpenCurly, CssType.CloseCurly],
|
|
23
|
-
]);
|
|
4
|
+
import { tokenize as cssTokenize } from '@csstools/css-tokenizer';
|
|
5
|
+
import { indexBlocks } from './lib/block-index.js';
|
|
6
|
+
import { hasPotentialMathFunction } from './lib/functions.js';
|
|
7
|
+
import { assertDepth } from './lib/limits.js';
|
|
8
|
+
import { findCalculations } from './lib/scan.js';
|
|
9
|
+
import { compileCandidates } from './lib/compile.js';
|
|
10
|
+
import { applyReplacements } from './lib/print.js';
|
|
24
11
|
|
|
25
12
|
/**
|
|
26
13
|
* @typedef {object} ReduceCalcOptions
|
|
27
14
|
* @property {number | false} [precision]
|
|
28
15
|
* @property {boolean} [warnWhenCannotResolve]
|
|
29
|
-
* @property {boolean} [unwrapSingleNegativeNumber]
|
|
16
|
+
* @property {boolean} [unwrapSingleNegativeNumber] Deprecated alias for `unwrapSingleValue`.
|
|
17
|
+
* @property {boolean} [unwrapSingleValue] Serialize fully resolved finite scalar results without calculation syntax. Defaults to `false`.
|
|
30
18
|
* @property {(error: Error, input: string) => void} [onParseError] Invoked when parse/simplify throws.
|
|
31
19
|
* @property {(message: string) => void} [onWarn] Invoked when `warnWhenCannotResolve` is set and an expression cannot be reduced to a single value.
|
|
32
20
|
*/
|
|
@@ -40,87 +28,23 @@ const BLOCK_CLOSE = new Map([
|
|
|
40
28
|
* @property {ResolvedReduceCalcOptions} options
|
|
41
29
|
* @property {string} value
|
|
42
30
|
* @property {import('@csstools/css-tokenizer').CSSToken[]} tokens
|
|
43
|
-
* @property {Replacement[]} replacements
|
|
44
31
|
*/
|
|
45
32
|
|
|
46
33
|
/**
|
|
47
34
|
* @typedef {object} Replacement
|
|
48
35
|
* @property {number} start
|
|
49
36
|
* @property {number} end
|
|
50
|
-
* @property {
|
|
51
|
-
* @property {string} calcName
|
|
37
|
+
* @property {CalculationResult} result
|
|
52
38
|
*/
|
|
53
|
-
|
|
54
39
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* @
|
|
61
|
-
* @
|
|
62
|
-
* @param {TransformContext} ctx
|
|
63
|
-
* @param {boolean} transform
|
|
64
|
-
* @return {number} Index of the matching closer, or the EOF token.
|
|
40
|
+
* @typedef {object} CalculationResult
|
|
41
|
+
* @property {import('./lib/node.js').Node} tree
|
|
42
|
+
* @property {'resolved' | 'unresolved'} status
|
|
43
|
+
* @property {string} rootName
|
|
44
|
+
* @property {string} rootSpelling
|
|
45
|
+
* @property {boolean} calculation
|
|
46
|
+
* @property {string | undefined} original
|
|
65
47
|
*/
|
|
66
|
-
function walkTokens(start, expectedClose, ctx, transform) {
|
|
67
|
-
for (let i = start; i < ctx.tokens.length; i++) {
|
|
68
|
-
const token = ctx.tokens[i];
|
|
69
|
-
if (token[0] === CssType.EOF || token[0] === expectedClose) return i;
|
|
70
|
-
|
|
71
|
-
const blockClose = BLOCK_CLOSE.get(token[0]);
|
|
72
|
-
if (blockClose) {
|
|
73
|
-
i = walkTokens(i + 1, blockClose, ctx, transform);
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (token[0] !== CssType.Function) continue;
|
|
78
|
-
|
|
79
|
-
const name = token[4].value;
|
|
80
|
-
const isCalc = MATCH_CALC.test(name);
|
|
81
|
-
const isMath = !isCalc && isSupportedMathFunction(name);
|
|
82
|
-
if (!transform || (!isCalc && !isMath)) {
|
|
83
|
-
i = walkTokens(i + 1, CssType.CloseParen, ctx, transform);
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Locate the complete outer function without transforming its children.
|
|
88
|
-
const close = walkTokens(i + 1, CssType.CloseParen, ctx, false);
|
|
89
|
-
const closed = ctx.tokens[close][0] === CssType.CloseParen;
|
|
90
|
-
const end = closed ? ctx.tokens[close][3] + 1 : ctx.value.length;
|
|
91
|
-
const sliceStart = isCalc ? i + 1 : i;
|
|
92
|
-
const sliceEnd = closed ? close + (isCalc ? 0 : 1) : close;
|
|
93
|
-
const inputStart = isCalc ? token[3] + 1 : token[2];
|
|
94
|
-
const inputEnd = closed && isCalc ? ctx.tokens[close][2] : end;
|
|
95
|
-
const contents = ctx.value.slice(inputStart, inputEnd);
|
|
96
|
-
try {
|
|
97
|
-
const node = simplify(parse(ctx.tokens, sliceStart, sliceEnd));
|
|
98
|
-
ctx.replacements.push({
|
|
99
|
-
start: token[2],
|
|
100
|
-
end,
|
|
101
|
-
node,
|
|
102
|
-
calcName: isCalc ? name : 'calc',
|
|
103
|
-
});
|
|
104
|
-
} catch (error) {
|
|
105
|
-
const err = error instanceof Error ? error : new Error('Error');
|
|
106
|
-
ctx.options.onParseError?.(err, contents);
|
|
107
|
-
}
|
|
108
|
-
i = close;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return ctx.tokens.length - 1;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* @param {import('./lib/node.js').Node} node
|
|
116
|
-
* @return {boolean}
|
|
117
|
-
*/
|
|
118
|
-
function isUnresolvedResult(node) {
|
|
119
|
-
if (node.type === 'Sum' || node.type === 'Product') {
|
|
120
|
-
return true;
|
|
121
|
-
}
|
|
122
|
-
return node.type === 'Call' && isSupportedMathFunction(node.name);
|
|
123
|
-
}
|
|
124
48
|
|
|
125
49
|
/**
|
|
126
50
|
* Simplify every supported CSS math function in a component-value string.
|
|
@@ -140,34 +64,41 @@ function reduceCalc(value, opts) {
|
|
|
140
64
|
precision: 5,
|
|
141
65
|
warnWhenCannotResolve: false,
|
|
142
66
|
unwrapSingleNegativeNumber: false,
|
|
67
|
+
unwrapSingleValue: false,
|
|
143
68
|
...opts,
|
|
144
69
|
};
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
70
|
+
/** @type {import('@csstools/css-tokenizer').CSSToken[]} */
|
|
71
|
+
let tokens;
|
|
72
|
+
/** @type {ReturnType<typeof indexBlocks>} */
|
|
73
|
+
let index;
|
|
74
|
+
try {
|
|
75
|
+
tokens = cssTokenize({ css: value });
|
|
76
|
+
index = indexBlocks(tokens);
|
|
77
|
+
assertDepth(index.maxDepth);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
options.onParseError?.(
|
|
80
|
+
error instanceof Error ? error : new Error('Error', { cause: error }),
|
|
81
|
+
value
|
|
82
|
+
);
|
|
151
83
|
return value;
|
|
152
84
|
}
|
|
153
85
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
options.onWarn?.('Could not reduce expression: ' + value);
|
|
164
|
-
}
|
|
165
|
-
output += value.slice(lastIndex, replacement.start) + text;
|
|
166
|
-
lastIndex = replacement.end;
|
|
86
|
+
const candidates = findCalculations(value, tokens, index);
|
|
87
|
+
const replacements = compileCandidates(candidates, {
|
|
88
|
+
options,
|
|
89
|
+
value,
|
|
90
|
+
tokens,
|
|
91
|
+
index,
|
|
92
|
+
});
|
|
93
|
+
if (replacements.length === 0) {
|
|
94
|
+
return value;
|
|
167
95
|
}
|
|
168
|
-
|
|
169
|
-
|
|
96
|
+
const serializationOptions = {
|
|
97
|
+
precision: options.precision,
|
|
98
|
+
unwrapSingleNegativeNumber: options.unwrapSingleNegativeNumber,
|
|
99
|
+
unwrapSingleValue: options.unwrapSingleValue,
|
|
100
|
+
};
|
|
101
|
+
return applyReplacements(value, replacements, options, serializationOptions);
|
|
170
102
|
}
|
|
171
103
|
|
|
172
|
-
export { QUICK_MATH_TEST, hasPotentialMathFunction };
|
|
173
104
|
export default reduceCalc;
|
package/types/index.d.ts
CHANGED
|
@@ -3,6 +3,10 @@ export type PostCssCalcOptions = {
|
|
|
3
3
|
warnWhenCannotResolve?: boolean;
|
|
4
4
|
mediaQueries?: boolean;
|
|
5
5
|
selectors?: boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Serialize fully resolved finite scalar results without calculation syntax. Defaults to `false`.
|
|
8
|
+
*/
|
|
9
|
+
unwrapSingleValue?: boolean;
|
|
6
10
|
/**
|
|
7
11
|
* Invoked when parse/simplify throws. Replaces the default `result.warn`.
|
|
8
12
|
*/
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export type Node = import('./node.js').Node;
|
|
2
|
+
export type CalculationType = import('./functions.js').CalculationType;
|
|
3
|
+
export type DimensionType = Extract<CalculationType, {
|
|
4
|
+
kind: 'dimension';
|
|
5
|
+
}>;
|
|
6
|
+
export type AnalysisType = 'number' | 'unknown' | {
|
|
7
|
+
dimension: string | null;
|
|
8
|
+
};
|
|
9
|
+
export type Analysis = {
|
|
10
|
+
type: AnalysisType;
|
|
11
|
+
valid: boolean;
|
|
12
|
+
unresolved: boolean;
|
|
13
|
+
};
|
|
14
|
+
export type ProductFactors = {
|
|
15
|
+
valid: boolean;
|
|
16
|
+
structurallyValid: boolean;
|
|
17
|
+
hasUnresolved: boolean;
|
|
18
|
+
numerator: DimensionType | null;
|
|
19
|
+
denominator: DimensionType | null;
|
|
20
|
+
hasOpaqueNumerator: boolean;
|
|
21
|
+
hasOpaqueDenominator: boolean;
|
|
22
|
+
};
|
|
23
|
+
/** @typedef {import('./node.js').Node} Node */
|
|
24
|
+
/** @typedef {import('./functions.js').CalculationType} CalculationType */
|
|
25
|
+
/** @typedef {Extract<CalculationType, {kind: 'dimension'}>} DimensionType */
|
|
26
|
+
/** @typedef {'number' | 'unknown' | {dimension: string | null}} AnalysisType */
|
|
27
|
+
/** @typedef {{type: AnalysisType, valid: boolean, unresolved: boolean}} Analysis */
|
|
28
|
+
/**
|
|
29
|
+
* @typedef {Object} ProductFactors
|
|
30
|
+
* @property {boolean} valid
|
|
31
|
+
* @property {boolean} structurallyValid
|
|
32
|
+
* @property {boolean} hasUnresolved
|
|
33
|
+
* @property {DimensionType | null} numerator
|
|
34
|
+
* @property {DimensionType | null} denominator
|
|
35
|
+
* @property {boolean} hasOpaqueNumerator
|
|
36
|
+
* @property {boolean} hasOpaqueDenominator
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* Analyze the original complete tree and return its root summary. Analysis
|
|
40
|
+
* validates and classifies the tree; it is not a rewrite plan.
|
|
41
|
+
* @param {Node} node
|
|
42
|
+
* @return {Analysis}
|
|
43
|
+
*/
|
|
44
|
+
declare function analyze(node: Node): Analysis;
|
|
45
|
+
export { analyze };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export type CSSToken = import('@csstools/css-tokenizer').CSSToken;
|
|
2
|
+
/**
|
|
3
|
+
* Read-only delimiter navigation for one native token stream range.
|
|
4
|
+
*/
|
|
5
|
+
declare class BlockIndex {
|
|
6
|
+
#private;
|
|
7
|
+
/**
|
|
8
|
+
* Build the delimiter index once for a token stream range. Matching remains
|
|
9
|
+
* LIFO: a mismatched closer is ignored and does not disturb the open stack.
|
|
10
|
+
*
|
|
11
|
+
* @param {CSSToken[]} tokens
|
|
12
|
+
* @param {number} start
|
|
13
|
+
* @param {number} end
|
|
14
|
+
*/
|
|
15
|
+
constructor(tokens: CSSToken[], start: number, end: number);
|
|
16
|
+
/** @return {number} */
|
|
17
|
+
get maxDepth(): number;
|
|
18
|
+
/** @param {number} openPosition @param {number} [endPosition] @return {number} */
|
|
19
|
+
closeOf(openPosition: number, endPosition?: number): number;
|
|
20
|
+
/** @param {number} position @param {number} endPosition @return {number} */
|
|
21
|
+
nextComponent(position: number, endPosition: number): number;
|
|
22
|
+
/** @param {number} startPosition @param {number} endPosition @return {number} */
|
|
23
|
+
firstTopLevelComma(startPosition: number, endPosition: number): number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Build the delimiter index once for a token stream range. Matching remains
|
|
27
|
+
* LIFO: a mismatched closer is ignored and does not disturb the open stack.
|
|
28
|
+
*
|
|
29
|
+
* @param {CSSToken[]} tokens
|
|
30
|
+
* @param {number} [start]
|
|
31
|
+
* @param {number} [end]
|
|
32
|
+
* @return {BlockIndex}
|
|
33
|
+
*/
|
|
34
|
+
declare function indexBlocks(tokens: CSSToken[], start?: number, end?: number): BlockIndex;
|
|
35
|
+
export { indexBlocks };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { analyze } from './analyze.js';
|
|
2
|
+
import { MAX_CALCULATION_DEPTH, checkCalculationDepth } from './limits.js';
|
|
3
|
+
export type Node = import('./node.js').Node;
|
|
4
|
+
export type CalculationType = {
|
|
5
|
+
kind: 'number';
|
|
6
|
+
} | {
|
|
7
|
+
kind: 'dimension';
|
|
8
|
+
base: string | null;
|
|
9
|
+
} | {
|
|
10
|
+
kind: 'unknown';
|
|
11
|
+
} | {
|
|
12
|
+
kind: 'failure';
|
|
13
|
+
};
|
|
14
|
+
/** @typedef {import('./node.js').Node} Node */
|
|
15
|
+
/** @typedef {{kind: 'number'} | {kind: 'dimension', base: string | null} | {kind: 'unknown'} | {kind: 'failure'}} CalculationType */
|
|
16
|
+
/** @param {Node} node @return {CalculationType} */
|
|
17
|
+
declare function checkCalculationType(node: Node): CalculationType;
|
|
18
|
+
export { MAX_CALCULATION_DEPTH, checkCalculationDepth, checkCalculationType, analyze, };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type Candidate = import('./scan.js').Candidate;
|
|
2
|
+
export type ResolvedReduceCalcOptions = import('../reduce.js').ResolvedReduceCalcOptions;
|
|
3
|
+
export type Replacement = import('../reduce.js').Replacement;
|
|
4
|
+
export type CSSToken = import('@csstools/css-tokenizer').CSSToken;
|
|
5
|
+
export type BlockIndex = ReturnType<typeof import('./block-index.js').indexBlocks>;
|
|
6
|
+
export type CompileContext = {
|
|
7
|
+
options: ResolvedReduceCalcOptions;
|
|
8
|
+
value: string;
|
|
9
|
+
tokens: CSSToken[];
|
|
10
|
+
index: BlockIndex;
|
|
11
|
+
};
|
|
12
|
+
/** @typedef {import('./scan.js').Candidate} Candidate */
|
|
13
|
+
/** @typedef {import('../reduce.js').ResolvedReduceCalcOptions} ResolvedReduceCalcOptions */
|
|
14
|
+
/** @typedef {import('../reduce.js').Replacement} Replacement */
|
|
15
|
+
/** @typedef {import('@csstools/css-tokenizer').CSSToken} CSSToken */
|
|
16
|
+
/** @typedef {ReturnType<typeof import('./block-index.js').indexBlocks>} BlockIndex */
|
|
17
|
+
/** @typedef {{options: ResolvedReduceCalcOptions, value: string, tokens: CSSToken[], index: BlockIndex}} CompileContext */
|
|
18
|
+
/**
|
|
19
|
+
* Parse, analyze, and simplify one candidate. Analysis is the validity/status
|
|
20
|
+
* gate over the original tree; simplification then runs independently as a
|
|
21
|
+
* composable AST transformation that may synthesize nodes.
|
|
22
|
+
*
|
|
23
|
+
* @param {Candidate} candidate
|
|
24
|
+
* @param {CompileContext} ctx
|
|
25
|
+
* @return {Replacement}
|
|
26
|
+
*/
|
|
27
|
+
declare function compileCandidate(candidate: Candidate, ctx: CompileContext): Replacement;
|
|
28
|
+
/**
|
|
29
|
+
* Compile candidates independently so one malformed calculation is preserved
|
|
30
|
+
* without preventing unrelated candidates from being reduced.
|
|
31
|
+
*
|
|
32
|
+
* @param {Candidate[]} candidates
|
|
33
|
+
* @param {CompileContext} ctx
|
|
34
|
+
* @return {Replacement[]}
|
|
35
|
+
*/
|
|
36
|
+
declare function compileCandidates(candidates: Candidate[], ctx: CompileContext): Replacement[];
|
|
37
|
+
export { compileCandidate, compileCandidates };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export type Node = import('./node.js').Node;
|
|
2
|
+
export type CalculationType = {
|
|
3
|
+
kind: 'number';
|
|
4
|
+
} | {
|
|
5
|
+
kind: 'dimension';
|
|
6
|
+
base: string | null;
|
|
7
|
+
} | {
|
|
8
|
+
kind: 'unknown';
|
|
9
|
+
percent?: true;
|
|
10
|
+
} | {
|
|
11
|
+
kind: 'failure';
|
|
12
|
+
};
|
|
13
|
+
export type MathSimplifier = (name: string, args: Node[]) => Node;
|
|
14
|
+
export type TypeAnalyzer = (args: CalculationType[], nodes: Node[]) => CalculationType;
|
|
15
|
+
export type MathFunction = {
|
|
16
|
+
analyze: TypeAnalyzer;
|
|
17
|
+
simplify?: MathSimplifier;
|
|
18
|
+
isKeyword?: (node: Node, index: number) => boolean;
|
|
19
|
+
calculation?: boolean;
|
|
20
|
+
};
|
|
21
|
+
/** @typedef {import('./node.js').Node} Node */
|
|
22
|
+
/**
|
|
23
|
+
* `percent` marks a value that resolves in the same percentage context as its
|
|
24
|
+
* peers (a pure percentage). It is only produced at leaves, by abs(), and by
|
|
25
|
+
* homogeneous sums and calls — never by a product, where an unpaired
|
|
26
|
+
* percentage could no longer cancel against anything.
|
|
27
|
+
* @typedef {{kind: 'number'} | {kind: 'dimension', base: string | null} | {kind: 'unknown', percent?: true} | {kind: 'failure'}} CalculationType
|
|
28
|
+
*/
|
|
29
|
+
/** @typedef {(name: string, args: Node[]) => Node} MathSimplifier */
|
|
30
|
+
/** @typedef {(args: CalculationType[], nodes: Node[]) => CalculationType} TypeAnalyzer */
|
|
31
|
+
/** @typedef {{analyze: TypeAnalyzer, simplify?: MathSimplifier, isKeyword?: (node: Node, index: number) => boolean, calculation?: boolean}} MathFunction */
|
|
32
|
+
/** @type {CalculationType} */ declare const numberType: CalculationType;
|
|
33
|
+
/** @type {CalculationType} */ declare const unknownType: CalculationType;
|
|
34
|
+
/** @type {CalculationType} */ declare const percentageType: CalculationType;
|
|
35
|
+
/** @type {CalculationType} */ declare const failureType: CalculationType;
|
|
36
|
+
/** @param {CalculationType} type @return {boolean} */
|
|
37
|
+
declare function isFailure(type: CalculationType): boolean;
|
|
38
|
+
/** @param {CalculationType} type @return {boolean} */
|
|
39
|
+
declare function isPercentage(type: CalculationType): boolean;
|
|
40
|
+
/** @param {CalculationType} a @param {CalculationType} b @return {CalculationType} */
|
|
41
|
+
declare function addTypes(a: CalculationType, b: CalculationType): CalculationType;
|
|
42
|
+
declare const mathFunctions: Map<string, MathFunction>;
|
|
43
|
+
/**
|
|
44
|
+
* @param {string} name
|
|
45
|
+
* @return {{normalizedName: string, definition: MathFunction} | undefined}
|
|
46
|
+
*/
|
|
47
|
+
declare function lookupMathFunction(name: string): {
|
|
48
|
+
normalizedName: string;
|
|
49
|
+
definition: MathFunction;
|
|
50
|
+
} | undefined;
|
|
51
|
+
declare const QUICK_MATH_TEST: RegExp;
|
|
52
|
+
/** @param {string} name @return {boolean} */
|
|
53
|
+
declare function isCalculationFunction(name: string): boolean;
|
|
54
|
+
/** @param {string} name @return {boolean} */
|
|
55
|
+
declare function isSupportedMathFunction(name: string): boolean;
|
|
56
|
+
/** @param {string} value @return {boolean} */
|
|
57
|
+
declare function hasPotentialMathFunction(value: string): boolean;
|
|
58
|
+
export { addTypes, failureType, hasPotentialMathFunction, isCalculationFunction, isFailure, isPercentage, isSupportedMathFunction, lookupMathFunction, mathFunctions, numberType, percentageType, QUICK_MATH_TEST, unknownType, };
|