postcss-calc 11.2.2 → 11.2.3
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 +17 -33
- package/package.json +8 -8
- package/src/lib/function-analyzers.js +119 -0
- package/src/lib/functions.js +25 -175
- package/src/lib/parser/opaque.js +3 -2
- package/src/lib/serialize/expression.js +110 -157
- package/src/lib/serialize/precedence.js +47 -0
- package/src/lib/serialize/precision.js +21 -21
- package/src/lib/serialize.js +1 -1
- package/src/lib/simplify/product-barrier.js +55 -0
- package/src/lib/simplify/product-quotient.js +141 -0
- package/src/lib/simplify/product.js +7 -178
- package/src/lib/types.js +94 -0
- package/types/lib/function-analyzers.d.ts +27 -0
- package/types/lib/functions.d.ts +3 -28
- package/types/lib/serialize/expression.d.ts +9 -49
- package/types/lib/serialize/precedence.d.ts +18 -0
- package/types/lib/serialize/precision.d.ts +11 -10
- package/types/lib/simplify/product-barrier.d.ts +26 -0
- package/types/lib/simplify/product-quotient.d.ts +48 -0
- package/types/lib/types.d.ts +48 -0
- package/types/lib/calculation-type.d.ts +0 -18
package/README.md
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
[PostCSS Calc] lets you reduce `calc()` references whenever it's possible.
|
|
7
7
|
When an expression mixes units that cannot be combined exactly (such as `px`
|
|
8
8
|
and `em`), or contains values only known at runtime (such as `var()`), the
|
|
9
|
-
unresolved part is left for the browser's [
|
|
9
|
+
unresolved part is left for the browser's [CSS Values 4][css-values-4]
|
|
10
|
+
implementation.
|
|
10
11
|
|
|
11
12
|
## Installation
|
|
12
13
|
|
|
@@ -69,9 +70,8 @@ reduceCalc('min(50px, calc(2 * 40px))');
|
|
|
69
70
|
// => 'calc(50px)'
|
|
70
71
|
```
|
|
71
72
|
|
|
72
|
-
It accepts `precision`, `unwrapSingleValue`,
|
|
73
|
-
`
|
|
74
|
-
`warnWhenCannotResolve`, `onParseError`, and `onWarn`:
|
|
73
|
+
It accepts `precision`, `unwrapSingleValue`, `warnWhenCannotResolve`,
|
|
74
|
+
`onParseError`, and `onWarn`:
|
|
75
75
|
|
|
76
76
|
```js
|
|
77
77
|
const result = reduceCalc('calc(100% + var(--gap))', {
|
|
@@ -191,20 +191,20 @@ Example:
|
|
|
191
191
|
With `mediaQueries: true`, this becomes:
|
|
192
192
|
|
|
193
193
|
```css
|
|
194
|
-
@media (min-width: 200px) {
|
|
194
|
+
@media (min-width: calc(200px)) {
|
|
195
195
|
div {
|
|
196
196
|
width: 100px;
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
```
|
|
200
200
|
|
|
201
|
+
Add `unwrapSingleValue: true` to get the bare value, `(min-width: 200px)`.
|
|
202
|
+
|
|
201
203
|
#### `selectors` (default: `false`)
|
|
202
204
|
|
|
203
205
|
Reduces `calc()` functions found in selectors. Selectors do not accept
|
|
204
|
-
`calc()` functions, so the plugin replaces them with their reduced values
|
|
205
|
-
|
|
206
|
-
because a selector cannot contain a `calc()` function; the plugin enables the
|
|
207
|
-
`unwrapSingleValue` automatically for selectors.
|
|
206
|
+
`calc()` functions, so the plugin replaces them with their reduced values, as
|
|
207
|
+
if `unwrapSingleValue` were enabled.
|
|
208
208
|
|
|
209
209
|
```js
|
|
210
210
|
var out = postcss()
|
|
@@ -224,8 +224,8 @@ With `selectors: true`, this becomes `div:nth-child(3)`.
|
|
|
224
224
|
|
|
225
225
|
#### `onParseError`
|
|
226
226
|
|
|
227
|
-
Callback invoked when a `calc()` body fails
|
|
228
|
-
|
|
227
|
+
Callback invoked with the error and the input value when a `calc()` body fails
|
|
228
|
+
to parse or simplify:
|
|
229
229
|
|
|
230
230
|
```js
|
|
231
231
|
postcss().use(
|
|
@@ -250,13 +250,13 @@ canonical-form decisions:
|
|
|
250
250
|
- **Strict whitespace (§10.1).** `calc(2px+3px)` is invalid CSS (binary
|
|
251
251
|
`+` / `-` require surrounding whitespace) and is preserved with a
|
|
252
252
|
warning instead of reduced.
|
|
253
|
-
- **Canonical operand order.**
|
|
254
|
-
|
|
253
|
+
- **Canonical operand order.** Sums serialize numbers first, then
|
|
254
|
+
dimensions in first-seen order, then unresolved terms:
|
|
255
255
|
`calc(var(--foo) + 10px)` → `calc(10px + var(--foo))`.
|
|
256
256
|
- **Zero buckets are kept.** `calc(100px - (100px - 100%))` →
|
|
257
257
|
`calc(0px + 100%)`, not `100%` — [WPT calc-serialization-002][wpt-calc-serialization]
|
|
258
258
|
requires the zero term because it carries the length-percentage type.
|
|
259
|
-
- **Constant folding.** `calc(43 + pi)` now folds to `46.14159` (§10.7.1).
|
|
259
|
+
- **Constant folding.** `calc(43 + pi)` now folds to `calc(46.14159)` (§10.7.1).
|
|
260
260
|
Previously `pi` / `e` stayed symbolic.
|
|
261
261
|
- **Distributive multiplication.** `calc(0.5 * (100vw - 10px))` becomes
|
|
262
262
|
`calc(50vw - 5px)`.
|
|
@@ -270,7 +270,6 @@ canonical-form decisions:
|
|
|
270
270
|
if you want validation behavior.
|
|
271
271
|
|
|
272
272
|
[css-values-4]: https://www.w3.org/TR/css-values-4/
|
|
273
|
-
[csstools-css-calc]: https://www.npmjs.com/package/@csstools/css-calc
|
|
274
273
|
[wpt-calc-serialization]: https://github.com/web-platform-tests/wpt/blob/master/css/css-values/calc-serialization-002.html
|
|
275
274
|
[jison]: https://github.com/zaach/jison
|
|
276
275
|
|
|
@@ -288,6 +287,7 @@ for the full guidelines. The project uses [pnpm](https://pnpm.io/).
|
|
|
288
287
|
|
|
289
288
|
```bash
|
|
290
289
|
git clone git@github.com:postcss/postcss-calc.git
|
|
290
|
+
cd postcss-calc
|
|
291
291
|
git checkout -b patch-1
|
|
292
292
|
pnpm install
|
|
293
293
|
pnpm test
|
|
@@ -302,23 +302,8 @@ when changing parsing/simplification behavior:
|
|
|
302
302
|
pnpm test:corpus:full
|
|
303
303
|
```
|
|
304
304
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
`pnpm benchmark:nested-fallbacks` compare the working tree against `HEAD` using
|
|
308
|
-
20 fresh-process paired blocks by default and write git-ignored schema-v2
|
|
309
|
-
reports under `reports/benchmarks/`. Re-check a saved report with
|
|
310
|
-
`pnpm benchmark:reanalyze <report>`. `pnpm benchmark:corpus` compares the whole
|
|
311
|
-
pipeline with `@csstools/css-calc` on real-world expressions and is
|
|
312
|
-
report-only.
|
|
313
|
-
|
|
314
|
-
These benchmarks time only the parser (or, for the corpus, the whole reducer)
|
|
315
|
-
on one machine, and a `pass` means "no regression detected at the declared
|
|
316
|
-
margin", not "no change". Read [BENCHMARKS.md](BENCHMARKS.md) before
|
|
317
|
-
interpreting results.
|
|
318
|
-
|
|
319
|
-
The PostCSS benchmark awaits `postcss().process(...)`, and that await already
|
|
320
|
-
triggers result stringification. It therefore does not add a redundant
|
|
321
|
-
`result.css` access.
|
|
305
|
+
Check performance changes with the benchmarks described in
|
|
306
|
+
[BENCHMARKS.md](BENCHMARKS.md), and read it before interpreting results.
|
|
322
307
|
|
|
323
308
|
## [Changelog](CHANGELOG.md)
|
|
324
309
|
|
|
@@ -332,4 +317,3 @@ triggers result stringification. It therefore does not add a redundant
|
|
|
332
317
|
[PostCSS Calc]: https://github.com/postcss/postcss-calc
|
|
333
318
|
[PostCSS Custom Properties]: https://github.com/postcss/postcss-custom-properties
|
|
334
319
|
[tests]: test/
|
|
335
|
-
[W3C calc() implementation]: https://www.w3.org/TR/css3-values/#calc-notation
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "postcss-calc",
|
|
3
|
-
"version": "11.2.
|
|
3
|
+
"version": "11.2.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "PostCSS plugin to reduce calc()",
|
|
6
6
|
"keywords": [
|
|
@@ -38,23 +38,23 @@
|
|
|
38
38
|
"devEngines": {
|
|
39
39
|
"packageManager": {
|
|
40
40
|
"name": "pnpm",
|
|
41
|
-
"version": "12.
|
|
41
|
+
"version": "12.10.1"
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@csstools/css-calc": "^3.4.
|
|
46
|
-
"@types/node": "^26.6.
|
|
45
|
+
"@csstools/css-calc": "^3.4.3",
|
|
46
|
+
"@types/node": "^26.6.4",
|
|
47
47
|
"fast-check": "^4.10.2",
|
|
48
|
-
"oxfmt": "^0.
|
|
49
|
-
"oxlint": "^1.
|
|
50
|
-
"postcss": "^8.5.
|
|
48
|
+
"oxfmt": "^0.72.0",
|
|
49
|
+
"oxlint": "^1.87.0",
|
|
50
|
+
"postcss": "^8.5.29",
|
|
51
51
|
"typescript": "~7.0.2"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@csstools/css-tokenizer": "^4.0.2"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
|
-
"postcss": "^8.5.
|
|
57
|
+
"postcss": "^8.5.29"
|
|
58
58
|
},
|
|
59
59
|
"scripts": {
|
|
60
60
|
"lint": "oxlint . && tsc && oxfmt --check",
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { ROUND_STRATEGIES } from './simplify/round.js';
|
|
2
|
+
import {
|
|
3
|
+
addTypes,
|
|
4
|
+
failureType,
|
|
5
|
+
isFailure,
|
|
6
|
+
matchingArguments,
|
|
7
|
+
numberType,
|
|
8
|
+
unknownType,
|
|
9
|
+
} from './types.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {import('./node.js').Node} Node
|
|
13
|
+
* @typedef {import('./types.js').CalculationType} CalculationType
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** @param {CalculationType[]} args @return {CalculationType} */
|
|
17
|
+
function analyzeTrig(args) {
|
|
18
|
+
return args.length === 1 &&
|
|
19
|
+
(args[0].kind === 'unknown' ||
|
|
20
|
+
args[0].kind === 'number' ||
|
|
21
|
+
(args[0].kind === 'dimension' && args[0].base === 'angle'))
|
|
22
|
+
? numberType
|
|
23
|
+
: failureType;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** @param {CalculationType[]} args @return {CalculationType} */
|
|
27
|
+
function analyzeInverseTrig(args) {
|
|
28
|
+
if (args.length !== 1 || args[0].kind === 'dimension') {
|
|
29
|
+
return failureType;
|
|
30
|
+
}
|
|
31
|
+
return args[0].kind === 'unknown'
|
|
32
|
+
? unknownType
|
|
33
|
+
: { kind: 'dimension', base: 'angle' };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** @param {CalculationType[]} args @return {CalculationType} */
|
|
37
|
+
function analyzeIdentity(args) {
|
|
38
|
+
return args.length === 1 ? args[0] : failureType;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** @param {CalculationType[]} args @return {CalculationType} */
|
|
42
|
+
function analyzeSign(args) {
|
|
43
|
+
return args.length === 1 ? numberType : failureType;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** @param {CalculationType[]} args @param {Node[]} nodes @return {CalculationType} */
|
|
47
|
+
function analyzeRound(args, nodes) {
|
|
48
|
+
const strategy = nodes[0];
|
|
49
|
+
const hasStrategy =
|
|
50
|
+
strategy?.type === 'Ident' &&
|
|
51
|
+
ROUND_STRATEGIES.has(strategy.name.toLowerCase());
|
|
52
|
+
const start = hasStrategy ? 1 : 0;
|
|
53
|
+
const valueCount = args.length - start;
|
|
54
|
+
if (valueCount === 1) {
|
|
55
|
+
const value = args[start];
|
|
56
|
+
if (value.kind === 'dimension') return failureType;
|
|
57
|
+
if (value.kind === 'unknown') return unknownType;
|
|
58
|
+
return numberType;
|
|
59
|
+
}
|
|
60
|
+
if (valueCount !== 2) return failureType;
|
|
61
|
+
const type = addTypes(args[start], args[start + 1]);
|
|
62
|
+
return isFailure(type) ? failureType : type;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** @param {CalculationType[]} args @return {CalculationType} */
|
|
66
|
+
function analyzeAtan2(args) {
|
|
67
|
+
const type = matchingArguments(args, 2, 2);
|
|
68
|
+
if (isFailure(type)) return type;
|
|
69
|
+
// The type table gives atan2() «["angle" → 1]»; an unresolved result is
|
|
70
|
+
// plain unknown, never the percentage of its arguments.
|
|
71
|
+
if (type.kind === 'unknown') return unknownType;
|
|
72
|
+
return { kind: 'dimension', base: 'angle' };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** @param {CalculationType[]} args @return {CalculationType} */
|
|
76
|
+
function analyzeCalc(args) {
|
|
77
|
+
return args.length === 1 ? args[0] : failureType;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** @param {Node} node @return {boolean} */
|
|
81
|
+
function isRoundStrategy(node) {
|
|
82
|
+
return node.type === 'Ident' && ROUND_STRATEGIES.has(node.name.toLowerCase());
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** @param {Node} node @param {number} index @return {boolean} */
|
|
86
|
+
function isClampKeyword(node, index) {
|
|
87
|
+
return (
|
|
88
|
+
(index === 0 || index === 2) &&
|
|
89
|
+
node.type === 'Ident' &&
|
|
90
|
+
node.name.toLowerCase() === 'none'
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** @param {CalculationType[]} args @param {Node[]} nodes @return {CalculationType} */
|
|
95
|
+
function analyzeClamp(args, nodes) {
|
|
96
|
+
if (args.length !== 3) return failureType;
|
|
97
|
+
let valueCount = 0;
|
|
98
|
+
/** @type {CalculationType} */ let result = failureType;
|
|
99
|
+
for (let index = 0; index < args.length; index++) {
|
|
100
|
+
if (isClampKeyword(nodes[index], index)) continue;
|
|
101
|
+
result = valueCount === 0 ? args[index] : addTypes(result, args[index]);
|
|
102
|
+
valueCount++;
|
|
103
|
+
}
|
|
104
|
+
if (valueCount < 1 || isFailure(result)) return failureType;
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export {
|
|
109
|
+
analyzeAtan2,
|
|
110
|
+
analyzeCalc,
|
|
111
|
+
analyzeClamp,
|
|
112
|
+
analyzeIdentity,
|
|
113
|
+
analyzeInverseTrig,
|
|
114
|
+
analyzeRound,
|
|
115
|
+
analyzeSign,
|
|
116
|
+
analyzeTrig,
|
|
117
|
+
isClampKeyword,
|
|
118
|
+
isRoundStrategy,
|
|
119
|
+
};
|
package/src/lib/functions.js
CHANGED
|
@@ -3,7 +3,7 @@ import { simplifyClamp } from './simplify/clamp.js';
|
|
|
3
3
|
import { simplifyAbs } from './simplify/abs.js';
|
|
4
4
|
import { simplifySign } from './simplify/sign.js';
|
|
5
5
|
import { simplifyModRem } from './simplify/mod-rem.js';
|
|
6
|
-
import {
|
|
6
|
+
import { simplifyRound } from './simplify/round.js';
|
|
7
7
|
import { simplifyTrig } from './simplify/trig.js';
|
|
8
8
|
import { simplifyInverseTrig } from './simplify/inverse-trig.js';
|
|
9
9
|
import { simplifyAtan2 } from './simplify/atan2.js';
|
|
@@ -12,186 +12,36 @@ import { simplifySqrt } from './simplify/sqrt.js';
|
|
|
12
12
|
import { simplifyExp } from './simplify/exp.js';
|
|
13
13
|
import { simplifyLog } from './simplify/log.js';
|
|
14
14
|
import { simplifyHypot } from './simplify/hypot.js';
|
|
15
|
+
import {
|
|
16
|
+
analyzeAtan2,
|
|
17
|
+
analyzeCalc,
|
|
18
|
+
analyzeClamp,
|
|
19
|
+
analyzeIdentity,
|
|
20
|
+
analyzeInverseTrig,
|
|
21
|
+
analyzeRound,
|
|
22
|
+
analyzeSign,
|
|
23
|
+
analyzeTrig,
|
|
24
|
+
isClampKeyword,
|
|
25
|
+
isRoundStrategy,
|
|
26
|
+
} from './function-analyzers.js';
|
|
27
|
+
import {
|
|
28
|
+
addTypes,
|
|
29
|
+
failureType,
|
|
30
|
+
isFailure,
|
|
31
|
+
isPercentage,
|
|
32
|
+
matchingArguments,
|
|
33
|
+
numberArguments,
|
|
34
|
+
numberType,
|
|
35
|
+
percentageType,
|
|
36
|
+
unknownType,
|
|
37
|
+
} from './types.js';
|
|
15
38
|
|
|
16
39
|
/** @typedef {import('./node.js').Node} Node */
|
|
17
|
-
/**
|
|
18
|
-
* `percent` marks a value that resolves in the same percentage context as its
|
|
19
|
-
* peers (a pure percentage). It is only produced at leaves, by abs(), and by
|
|
20
|
-
* homogeneous sums and calls — never by a product, where an unpaired
|
|
21
|
-
* percentage could no longer cancel against anything.
|
|
22
|
-
* @typedef {{kind: 'number'} | {kind: 'dimension', base: string | null} | {kind: 'unknown', percent?: true} | {kind: 'failure'}} CalculationType
|
|
23
|
-
*/
|
|
40
|
+
/** @typedef {import('./types.js').CalculationType} CalculationType */
|
|
24
41
|
/** @typedef {(name: string, args: Node[]) => Node} MathSimplifier */
|
|
25
42
|
/** @typedef {(args: CalculationType[], nodes: Node[]) => CalculationType} TypeAnalyzer */
|
|
26
43
|
/** @typedef {{analyze: TypeAnalyzer, simplify?: MathSimplifier, isKeyword?: (node: Node, index: number) => boolean, calculation?: boolean}} MathFunction */
|
|
27
44
|
|
|
28
|
-
/** @type {CalculationType} */ const numberType = { kind: 'number' };
|
|
29
|
-
/** @type {CalculationType} */ const unknownType = { kind: 'unknown' };
|
|
30
|
-
/** @type {CalculationType} */ const percentageType = {
|
|
31
|
-
kind: 'unknown',
|
|
32
|
-
percent: true,
|
|
33
|
-
};
|
|
34
|
-
/** @type {CalculationType} */ const failureType = { kind: 'failure' };
|
|
35
|
-
|
|
36
|
-
/** @param {CalculationType} type @return {boolean} */
|
|
37
|
-
function isFailure(type) {
|
|
38
|
-
return type.kind === 'failure';
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** @param {CalculationType} type @return {boolean} */
|
|
42
|
-
function isPercentage(type) {
|
|
43
|
-
return type.kind === 'unknown' && type.percent === true;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** @param {CalculationType} a @param {CalculationType} b @return {CalculationType} */
|
|
47
|
-
function addTypes(a, b) {
|
|
48
|
-
if (isFailure(a) || isFailure(b)) return failureType;
|
|
49
|
-
if (a.kind === 'unknown' || b.kind === 'unknown') {
|
|
50
|
-
// A pure percentage keeps its contextual type so a surrounding product
|
|
51
|
-
// can cancel `% / %`; mixing it with any other opaque operand loses the
|
|
52
|
-
// guarantee that it resolves in the same context as its peers. A known
|
|
53
|
-
// number + percentage sum is likewise kept unknown and valid: the spec
|
|
54
|
-
// resolves the percentage against its surrounding context, which this
|
|
55
|
-
// coarse type model does not track.
|
|
56
|
-
return isPercentage(a) && isPercentage(b) ? percentageType : unknownType;
|
|
57
|
-
}
|
|
58
|
-
if (a.kind === 'number' && b.kind === 'number') return numberType;
|
|
59
|
-
if (a.kind === 'dimension' && b.kind === 'dimension') {
|
|
60
|
-
if (a.base === null || b.base === null) return unknownType;
|
|
61
|
-
return a.base === b.base ? a : failureType;
|
|
62
|
-
}
|
|
63
|
-
return failureType;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Check an all-number function without assuming anything about an unresolved
|
|
68
|
-
* operand. A concrete dimension can never become a number, so it is still a
|
|
69
|
-
* definite error when another argument is opaque.
|
|
70
|
-
* @param {CalculationType[]} args
|
|
71
|
-
* @param {number} min
|
|
72
|
-
* @param {number} max
|
|
73
|
-
* @return {CalculationType}
|
|
74
|
-
*/
|
|
75
|
-
function numberArguments(args, min, max) {
|
|
76
|
-
if (args.length < min || args.length > max) return failureType;
|
|
77
|
-
let hasUnknown = false;
|
|
78
|
-
for (const arg of args) {
|
|
79
|
-
if (arg.kind === 'dimension') return failureType;
|
|
80
|
-
hasUnknown = hasUnknown || arg.kind === 'unknown';
|
|
81
|
-
}
|
|
82
|
-
return hasUnknown ? unknownType : numberType;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Check values that must share a calculation type. Unknown operands remain
|
|
87
|
-
* unknown: they might resolve to the concrete type required by their peers.
|
|
88
|
-
* @param {CalculationType[]} args
|
|
89
|
-
* @param {number} min
|
|
90
|
-
* @param {number} max
|
|
91
|
-
* @return {CalculationType}
|
|
92
|
-
*/
|
|
93
|
-
function matchingArguments(args, min, max) {
|
|
94
|
-
if (args.length < min || args.length > max) return failureType;
|
|
95
|
-
let result = args[0];
|
|
96
|
-
for (let index = 1; index < args.length; index++) {
|
|
97
|
-
result = addTypes(result, args[index]);
|
|
98
|
-
if (isFailure(result)) return failureType;
|
|
99
|
-
}
|
|
100
|
-
return result;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/** @param {CalculationType[]} args @return {CalculationType} */
|
|
104
|
-
function analyzeTrig(args) {
|
|
105
|
-
return args.length === 1 &&
|
|
106
|
-
(args[0].kind === 'unknown' ||
|
|
107
|
-
args[0].kind === 'number' ||
|
|
108
|
-
(args[0].kind === 'dimension' && args[0].base === 'angle'))
|
|
109
|
-
? numberType
|
|
110
|
-
: failureType;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/** @param {CalculationType[]} args @return {CalculationType} */
|
|
114
|
-
function analyzeInverseTrig(args) {
|
|
115
|
-
if (args.length !== 1 || args[0].kind === 'dimension') {
|
|
116
|
-
return failureType;
|
|
117
|
-
}
|
|
118
|
-
return args[0].kind === 'unknown'
|
|
119
|
-
? unknownType
|
|
120
|
-
: { kind: 'dimension', base: 'angle' };
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/** @param {CalculationType[]} args @return {CalculationType} */
|
|
124
|
-
function analyzeIdentity(args) {
|
|
125
|
-
return args.length === 1 ? args[0] : failureType;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/** @param {CalculationType[]} args @return {CalculationType} */
|
|
129
|
-
function analyzeSign(args) {
|
|
130
|
-
return args.length === 1 ? numberType : failureType;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/** @param {CalculationType[]} args @param {Node[]} nodes @return {CalculationType} */
|
|
134
|
-
function analyzeRound(args, nodes) {
|
|
135
|
-
const strategy = nodes[0];
|
|
136
|
-
const hasStrategy =
|
|
137
|
-
strategy?.type === 'Ident' &&
|
|
138
|
-
ROUND_STRATEGIES.has(strategy.name.toLowerCase());
|
|
139
|
-
const start = hasStrategy ? 1 : 0;
|
|
140
|
-
const valueCount = args.length - start;
|
|
141
|
-
if (valueCount === 1) {
|
|
142
|
-
const value = args[start];
|
|
143
|
-
if (value.kind === 'dimension') return failureType;
|
|
144
|
-
if (value.kind === 'unknown') return unknownType;
|
|
145
|
-
return numberType;
|
|
146
|
-
}
|
|
147
|
-
if (valueCount !== 2) return failureType;
|
|
148
|
-
const type = addTypes(args[start], args[start + 1]);
|
|
149
|
-
return isFailure(type) ? failureType : type;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/** @param {CalculationType[]} args @return {CalculationType} */
|
|
153
|
-
function analyzeAtan2(args) {
|
|
154
|
-
const type = matchingArguments(args, 2, 2);
|
|
155
|
-
if (isFailure(type)) return type;
|
|
156
|
-
// The type table gives atan2() «["angle" → 1]»; an unresolved result is
|
|
157
|
-
// plain unknown, never the percentage of its arguments.
|
|
158
|
-
if (type.kind === 'unknown') return unknownType;
|
|
159
|
-
return { kind: 'dimension', base: 'angle' };
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/** @param {CalculationType[]} args @return {CalculationType} */
|
|
163
|
-
function analyzeCalc(args) {
|
|
164
|
-
return args.length === 1 ? args[0] : failureType;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/** @param {Node} node @return {boolean} */
|
|
168
|
-
function isRoundStrategy(node) {
|
|
169
|
-
return node.type === 'Ident' && ROUND_STRATEGIES.has(node.name.toLowerCase());
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/** @param {Node} node @param {number} index @return {boolean} */
|
|
173
|
-
function isClampKeyword(node, index) {
|
|
174
|
-
return (
|
|
175
|
-
(index === 0 || index === 2) &&
|
|
176
|
-
node.type === 'Ident' &&
|
|
177
|
-
node.name.toLowerCase() === 'none'
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/** @param {CalculationType[]} args @param {Node[]} nodes @return {CalculationType} */
|
|
182
|
-
function analyzeClamp(args, nodes) {
|
|
183
|
-
if (args.length !== 3) return failureType;
|
|
184
|
-
let valueCount = 0;
|
|
185
|
-
/** @type {CalculationType} */ let result = failureType;
|
|
186
|
-
for (let index = 0; index < args.length; index++) {
|
|
187
|
-
if (isClampKeyword(nodes[index], index)) continue;
|
|
188
|
-
result = valueCount === 0 ? args[index] : addTypes(result, args[index]);
|
|
189
|
-
valueCount++;
|
|
190
|
-
}
|
|
191
|
-
if (valueCount < 1 || isFailure(result)) return failureType;
|
|
192
|
-
return result;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
45
|
const mathFunctions = new Map(
|
|
196
46
|
/** @type {[string, MathFunction][]} */ ([
|
|
197
47
|
['calc', { analyze: analyzeCalc, calculation: true }],
|
package/src/lib/parser/opaque.js
CHANGED
|
@@ -71,10 +71,11 @@ function customProperty(tokens, start, end) {
|
|
|
71
71
|
/**
|
|
72
72
|
* Append the component tree of `[start, end)` to `root` and return it.
|
|
73
73
|
* Callers seed `root` so leading components need no copy or spread.
|
|
74
|
-
* @param {ParseInput} input @param {number} start @param {number}
|
|
74
|
+
* @param {ParseInput} input @param {number} start @param {number} rangeEnd @param {ParseRange} parseRange @param {OpaqueComponent[]} root @return {OpaqueComponent[]}
|
|
75
75
|
*/
|
|
76
|
-
function componentTree(input, start,
|
|
76
|
+
function componentTree(input, start, rangeEnd, parseRange, root) {
|
|
77
77
|
/** @type {OpaqueComponent[]} */ let tree = root;
|
|
78
|
+
let end = rangeEnd;
|
|
78
79
|
/** @type {{parent: OpaqueComponent[], tree: OpaqueComponent[], close: number, end: number}[]} */
|
|
79
80
|
const frames = [];
|
|
80
81
|
const { tokens } = input;
|