postcss-calc 11.1.2 → 11.2.0

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.
Files changed (41) hide show
  1. package/README.md +40 -18
  2. package/package.json +16 -12
  3. package/src/index.js +8 -5
  4. package/src/lib/analyze.js +228 -0
  5. package/src/lib/block-index.js +118 -0
  6. package/src/lib/calculation-type.js +25 -0
  7. package/src/lib/compile.js +80 -0
  8. package/src/lib/functions.js +364 -0
  9. package/src/lib/limits.js +49 -0
  10. package/src/lib/node.js +63 -21
  11. package/src/lib/opaque.js +12 -32
  12. package/src/lib/parser.js +383 -328
  13. package/src/lib/print.js +75 -0
  14. package/src/lib/regex.js +4 -0
  15. package/src/lib/scan.js +61 -0
  16. package/src/lib/serialize.js +574 -215
  17. package/src/lib/simplify/call.js +5 -98
  18. package/src/lib/simplify/round.js +4 -4
  19. package/src/lib/simplify/sum.js +14 -5
  20. package/src/lib/simplify.js +20 -5
  21. package/src/reduce.js +46 -115
  22. package/types/index.d.ts +4 -0
  23. package/types/lib/analyze.d.ts +18 -0
  24. package/types/lib/block-index.d.ts +35 -0
  25. package/types/lib/calculation-type.d.ts +18 -0
  26. package/types/lib/compile.d.ts +37 -0
  27. package/types/lib/functions.d.ts +40 -0
  28. package/types/lib/limits.d.ts +8 -0
  29. package/types/lib/node.d.ts +19 -3
  30. package/types/lib/opaque.d.ts +8 -14
  31. package/types/lib/parser.d.ts +42 -48
  32. package/types/lib/print.d.ts +16 -0
  33. package/types/lib/regex.d.ts +2 -0
  34. package/types/lib/scan.d.ts +35 -0
  35. package/types/lib/serialize.d.ts +19 -2
  36. package/types/lib/simplify/call.d.ts +4 -18
  37. package/types/lib/simplify/round.d.ts +3 -1
  38. package/types/lib/simplify.d.ts +5 -2
  39. package/types/reduce.d.ts +47 -6
  40. package/src/lib/tokenizer.js +0 -10
  41. package/types/lib/tokenizer.d.ts +0 -3
@@ -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 { getComponents, setComponents, simplifyComponents } from '../opaque.js';
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 === 'calc' || name === '-webkit-calc' || name === '-moz-calc') {
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 components = getComponents(node);
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
- // min/max preserve the call's original casing in their opaque-args
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 num(Number.NaN);
47
+ return foldResult(fold, Number.NaN);
48
48
  }
49
49
  if (!Number.isFinite(b)) {
50
50
  if (!Number.isFinite(a)) {
51
- return num(Number.NaN);
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 num(Number.NaN);
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 };
@@ -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
- numTotal += sign * n.value;
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
- // mkSum drops zero-valued Nums, so pushing the numeric total
96
- // unconditionally is harmless. Zero-valued unit buckets are kept for
97
- // type info (WPT calc-serialization-002).
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 = [{ sign: 1, node: num(denoise(numTotal, numScale)) }];
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,
@@ -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, simplify);
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, simplify);
45
+ return simplifySum(node, child);
31
46
  case 'Product':
32
- return simplifyProduct(node, simplify);
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
- tokenize as cssTokenize,
6
- TokenType as CssType,
7
- } from '@csstools/css-tokenizer';
8
- import { parse } from './lib/parser.js';
9
- import { simplify } from './lib/simplify.js';
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] Serialize finite negative results without a `calc()` wrapper. Defaults to `false`.
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 {import('./lib/node.js').Node} node
51
- * @property {string} calcName
37
+ * @property {CalculationResult} result
52
38
  */
53
-
54
39
  /**
55
- * Walk one component-value level. Unsupported functions and simple blocks are
56
- * traversed, while a supported function is treated as one opaque calculation
57
- * even when parsing it fails. A missing closer consumes through EOF, matching
58
- * CSS component-value parsing's error recovery.
59
- *
60
- * @param {number} start
61
- * @param {import('@csstools/css-tokenizer').TokenType | undefined} expectedClose
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
- const tokens = cssTokenize({ css: value });
146
- /** @type {Replacement[]} */
147
- const replacements = [];
148
- walkTokens(0, undefined, { options, value, tokens, replacements }, true);
149
-
150
- if (replacements.length === 0) {
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
- let output = '';
155
- let lastIndex = 0;
156
- for (const replacement of replacements) {
157
- const text = serialize(replacement.node, {
158
- precision: options.precision,
159
- calcName: replacement.calcName,
160
- unwrapSingleNegativeNumber: options.unwrapSingleNegativeNumber,
161
- });
162
- if (options.warnWhenCannotResolve && isUnresolvedResult(replacement.node)) {
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
- output += value.slice(lastIndex);
169
- return output;
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,18 @@
1
+ export type Node = import('./node.js').Node;
2
+ export type CalculationType = import('./functions.js').CalculationType;
3
+ export type AnalysisType = 'number' | 'unknown' | {
4
+ dimension: string | null;
5
+ };
6
+ export type Analysis = {
7
+ type: AnalysisType;
8
+ valid: boolean;
9
+ unresolved: boolean;
10
+ };
11
+ /**
12
+ * Analyze the original complete tree and return its root summary. Analysis
13
+ * validates and classifies the tree; it is not a rewrite plan.
14
+ * @param {Node} node
15
+ * @return {Analysis}
16
+ */
17
+ declare function analyze(node: Node): Analysis;
18
+ 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,40 @@
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
+ } | {
10
+ kind: 'failure';
11
+ };
12
+ export type MathSimplifier = (name: string, args: Node[]) => Node;
13
+ export type TypeAnalyzer = (args: CalculationType[], nodes: Node[]) => CalculationType;
14
+ export type MathFunction = {
15
+ analyze: TypeAnalyzer;
16
+ simplify?: MathSimplifier;
17
+ isKeyword?: (node: Node, index: number) => boolean;
18
+ calculation?: boolean;
19
+ };
20
+ /** @param {CalculationType} type @return {boolean} */
21
+ declare function isFailure(type: CalculationType): boolean;
22
+ /** @param {CalculationType} a @param {CalculationType} b @return {CalculationType} */
23
+ declare function addTypes(a: CalculationType, b: CalculationType): CalculationType;
24
+ declare const mathFunctions: Map<string, MathFunction>;
25
+ /**
26
+ * @param {string} name
27
+ * @return {{normalizedName: string, definition: MathFunction} | undefined}
28
+ */
29
+ declare function lookupMathFunction(name: string): {
30
+ normalizedName: string;
31
+ definition: MathFunction;
32
+ } | undefined;
33
+ declare const QUICK_MATH_TEST: RegExp;
34
+ /** @param {string} name @return {boolean} */
35
+ declare function isCalculationFunction(name: string): boolean;
36
+ /** @param {string} name @return {boolean} */
37
+ declare function isSupportedMathFunction(name: string): boolean;
38
+ /** @param {string} value @return {boolean} */
39
+ declare function hasPotentialMathFunction(value: string): boolean;
40
+ export { addTypes, mathFunctions, lookupMathFunction, QUICK_MATH_TEST, isFailure, isCalculationFunction, isSupportedMathFunction, hasPotentialMathFunction, };
@@ -0,0 +1,8 @@
1
+ /** @typedef {import('./node.js').Node} Node */
2
+ export type Node = import('./node.js').Node;
3
+ declare const MAX_CALCULATION_DEPTH = 1024;
4
+ /** @param {number} depth @return {void} */
5
+ declare function assertDepth(depth: number): void;
6
+ /** @param {Node} node @param {number} [depth] @return {void} */
7
+ declare function checkCalculationDepth(node: Node, depth?: number): void;
8
+ export { MAX_CALCULATION_DEPTH, assertDepth, checkCalculationDepth };