postcss-calc 11.0.0-rc.3 → 11.0.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.
- package/README.md +34 -14
- package/package.json +10 -8
- package/src/index.js +143 -96
- package/src/lib/parser.js +8 -8
- package/src/lib/serialize.js +5 -5
- package/src/lib/simplify/abs.js +3 -3
- package/src/lib/simplify/atan2.js +5 -5
- package/src/lib/simplify/call.js +52 -43
- package/src/lib/simplify/clamp.js +2 -2
- package/src/lib/simplify/exp.js +2 -2
- package/src/lib/simplify/hypot.js +2 -2
- package/src/lib/simplify/inverse-trig.js +5 -5
- package/src/lib/simplify/log.js +2 -2
- package/src/lib/simplify/min-max.js +2 -2
- package/src/lib/simplify/mod-rem.js +10 -10
- package/src/lib/simplify/pow.js +2 -2
- package/src/lib/simplify/round.js +12 -18
- package/src/lib/simplify/sign.js +3 -3
- package/src/lib/simplify/sqrt.js +2 -2
- package/src/lib/simplify/trig.js +3 -3
- package/types/index.d.ts +12 -7
- package/types/lib/simplify/call.d.ts +9 -3
package/README.md
CHANGED
|
@@ -56,7 +56,8 @@ Checkout [tests] for more examples.
|
|
|
56
56
|
|
|
57
57
|
#### `precision` (default: `5`)
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
Allows you to define the precision for decimal numbers. Set it to `false` to
|
|
60
|
+
disable rounding.
|
|
60
61
|
|
|
61
62
|
```js
|
|
62
63
|
var out = postcss()
|
|
@@ -64,17 +65,6 @@ var out = postcss()
|
|
|
64
65
|
.process(css).css;
|
|
65
66
|
```
|
|
66
67
|
|
|
67
|
-
#### `preserve` (default: `false`)
|
|
68
|
-
|
|
69
|
-
Allow you to preserve calc() usage in output so browsers will handle decimal
|
|
70
|
-
precision themselves.
|
|
71
|
-
|
|
72
|
-
```js
|
|
73
|
-
var out = postcss()
|
|
74
|
-
.use(calc({ preserve: true }))
|
|
75
|
-
.process(css).css;
|
|
76
|
-
```
|
|
77
|
-
|
|
78
68
|
#### `warnWhenCannotResolve` (default: `false`)
|
|
79
69
|
|
|
80
70
|
Adds warnings when calc() are not reduced to a single value.
|
|
@@ -87,7 +77,7 @@ var out = postcss()
|
|
|
87
77
|
|
|
88
78
|
#### `mediaQueries` (default: `false`)
|
|
89
79
|
|
|
90
|
-
Allows calc() usage
|
|
80
|
+
Allows calc() usage in media query parameters.
|
|
91
81
|
|
|
92
82
|
```js
|
|
93
83
|
var out = postcss()
|
|
@@ -95,6 +85,26 @@ var out = postcss()
|
|
|
95
85
|
.process(css).css;
|
|
96
86
|
```
|
|
97
87
|
|
|
88
|
+
Example:
|
|
89
|
+
|
|
90
|
+
```css
|
|
91
|
+
@media (min-width: calc(100px + 100px)) {
|
|
92
|
+
div {
|
|
93
|
+
width: 100px;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
With `mediaQueries: true`, this becomes:
|
|
99
|
+
|
|
100
|
+
```css
|
|
101
|
+
@media (min-width: 200px) {
|
|
102
|
+
div {
|
|
103
|
+
width: 100px;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
98
108
|
#### `selectors` (default: `false`)
|
|
99
109
|
|
|
100
110
|
Allows calc() usage as part of selectors.
|
|
@@ -108,11 +118,13 @@ var out = postcss()
|
|
|
108
118
|
Example:
|
|
109
119
|
|
|
110
120
|
```css
|
|
111
|
-
div
|
|
121
|
+
div:nth-child(calc(1 + 2)) {
|
|
112
122
|
width: 100px;
|
|
113
123
|
}
|
|
114
124
|
```
|
|
115
125
|
|
|
126
|
+
With `selectors: true`, this becomes `div:nth-child(3)`.
|
|
127
|
+
|
|
116
128
|
#### `onParseError`
|
|
117
129
|
|
|
118
130
|
Callback invoked when a `calc()` body fails to parse or simplify. Matches
|
|
@@ -186,6 +198,14 @@ npm install
|
|
|
186
198
|
npm test
|
|
187
199
|
```
|
|
188
200
|
|
|
201
|
+
The normal test run uses a deterministic structural sample of the harvested
|
|
202
|
+
real-world corpus. Run the complete differential corpus before releases or
|
|
203
|
+
when changing parsing/simplification behavior:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
pnpm test:corpus:full
|
|
207
|
+
```
|
|
208
|
+
|
|
189
209
|
## [Changelog](CHANGELOG.md)
|
|
190
210
|
|
|
191
211
|
## [License](LICENSE)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "postcss-calc",
|
|
3
|
-
"version": "11.0.0
|
|
3
|
+
"version": "11.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "PostCSS plugin to reduce calc()",
|
|
6
6
|
"keywords": [
|
|
@@ -29,12 +29,12 @@
|
|
|
29
29
|
"author": "Andy Jansson",
|
|
30
30
|
"license": "MIT",
|
|
31
31
|
"engines": {
|
|
32
|
-
"node": "^22.
|
|
32
|
+
"node": "^22.22.3 || ^24.15 || >=26.0"
|
|
33
33
|
},
|
|
34
34
|
"devEngines": {
|
|
35
35
|
"packageManager": {
|
|
36
36
|
"name": "pnpm",
|
|
37
|
-
"version": "11.
|
|
37
|
+
"version": "11.22.0"
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
@@ -42,14 +42,14 @@
|
|
|
42
42
|
"@rmenke/css-tokenizer-tests": "^1.2.0",
|
|
43
43
|
"@types/node": "^26.2.0",
|
|
44
44
|
"fast-check": "^4.9.0",
|
|
45
|
-
"oxfmt": "^0.
|
|
46
|
-
"oxlint": "^1.
|
|
45
|
+
"oxfmt": "^0.64.0",
|
|
46
|
+
"oxlint": "^1.79.0",
|
|
47
47
|
"postcss": "^8.5.26",
|
|
48
48
|
"typescript": "~7.0.2"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@csstools/css-
|
|
52
|
-
"
|
|
51
|
+
"@csstools/css-parser-algorithms": "^4.0.0",
|
|
52
|
+
"@csstools/css-tokenizer": "^4.0.0"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"postcss": "^8.5.25"
|
|
@@ -57,6 +57,8 @@
|
|
|
57
57
|
"scripts": {
|
|
58
58
|
"lint": "oxlint . && tsc && oxfmt --check",
|
|
59
59
|
"fmt": "oxfmt",
|
|
60
|
-
"test": "node --test"
|
|
60
|
+
"test": "node --test --test-reporter=dot 'test/**/*.test.mjs' test/index.cjs test/convertUnit.cjs",
|
|
61
|
+
"test:mutation:corpus": "node test/mutation/corpus-selection.mjs",
|
|
62
|
+
"test:corpus:full": "POSTCSS_CALC_FULL_CORPUS=1 node --test test/conformance/corpus.test.mjs"
|
|
61
63
|
}
|
|
62
64
|
}
|
package/src/index.js
CHANGED
|
@@ -1,50 +1,111 @@
|
|
|
1
1
|
// PostCSS adapter. Walks declaration values (and optionally @rule params
|
|
2
2
|
// and selectors), feeds calc() bodies through tokenize → parse → simplify
|
|
3
3
|
// → serialize, and writes the result back.
|
|
4
|
-
import
|
|
4
|
+
import { tokenize as cssTokenize } from '@csstools/css-tokenizer';
|
|
5
|
+
import {
|
|
6
|
+
isFunctionNode,
|
|
7
|
+
isSimpleBlockNode,
|
|
8
|
+
parseListOfComponentValues,
|
|
9
|
+
} from '@csstools/css-parser-algorithms';
|
|
5
10
|
import { tokenize } from './lib/tokenizer.js';
|
|
6
11
|
import { parse } from './lib/parser.js';
|
|
7
12
|
import { simplify } from './lib/simplify.js';
|
|
13
|
+
import { isSupportedMathFunction } from './lib/simplify/call.js';
|
|
8
14
|
import { serialize } from './lib/serialize.js';
|
|
9
15
|
|
|
10
|
-
|
|
16
|
+
// The outer walk is deliberately forgiving: it only needs to locate calc()/
|
|
17
|
+
// math-function boundaries in otherwise arbitrary (and possibly non-standard)
|
|
18
|
+
// CSS values, so parse errors from the outer tokenizer/parser are swallowed.
|
|
19
|
+
// Genuine syntax problems inside a matched call are
|
|
20
|
+
// caught below via our own tokenize/parse/simplify pipeline.
|
|
21
|
+
const NOOP_PARSE_ERROR = { onParseError: () => {} };
|
|
11
22
|
|
|
12
|
-
|
|
13
|
-
// Mirrors the dispatch in lib/simplify/call.js.
|
|
14
|
-
const MATH_FUNCTIONS = new Set([
|
|
15
|
-
'min',
|
|
16
|
-
'max',
|
|
17
|
-
'clamp',
|
|
18
|
-
'abs',
|
|
19
|
-
'sign',
|
|
20
|
-
'mod',
|
|
21
|
-
'rem',
|
|
22
|
-
'round',
|
|
23
|
-
'sin',
|
|
24
|
-
'cos',
|
|
25
|
-
'tan',
|
|
26
|
-
'asin',
|
|
27
|
-
'acos',
|
|
28
|
-
'atan',
|
|
29
|
-
'atan2',
|
|
30
|
-
'pow',
|
|
31
|
-
'sqrt',
|
|
32
|
-
'hypot',
|
|
33
|
-
'log',
|
|
34
|
-
'exp',
|
|
35
|
-
]);
|
|
23
|
+
const MATCH_CALC = /^(?:-(?:moz|webkit)-)?calc$/i;
|
|
36
24
|
|
|
37
25
|
/**
|
|
38
|
-
* @typedef {object}
|
|
26
|
+
* @typedef {object} PostCssCalcOptions
|
|
39
27
|
* @property {number | false} [precision]
|
|
40
|
-
* @property {boolean} [preserve]
|
|
41
28
|
* @property {boolean} [warnWhenCannotResolve]
|
|
42
29
|
* @property {boolean} [mediaQueries]
|
|
43
30
|
* @property {boolean} [selectors]
|
|
44
31
|
* @property {(error: Error, input: string) => void} [onParseError] Invoked when parse/simplify throws. Replaces the default `result.warn`.
|
|
45
32
|
*/
|
|
46
33
|
|
|
47
|
-
/** @typedef {Required<Omit<
|
|
34
|
+
/** @typedef {Required<Omit<PostCssCalcOptions, 'onParseError'>> & Pick<PostCssCalcOptions, 'onParseError'>} ResolvedOptions */
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Fields threaded unchanged through the recursive `transformList` walk.
|
|
38
|
+
* `value` is the original full property text, used only for the
|
|
39
|
+
* warnWhenCannotResolve message.
|
|
40
|
+
*
|
|
41
|
+
* @typedef {object} TransformContext
|
|
42
|
+
* @property {ResolvedOptions} options
|
|
43
|
+
* @property {import('postcss').Result} result
|
|
44
|
+
* @property {import('postcss').ChildNode} item
|
|
45
|
+
* @property {string} value
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Walks a list of component values in place, replacing matched calc()/math
|
|
50
|
+
* function nodes with their simplified form. Unlike the library's generic
|
|
51
|
+
* `walk` helper, this recurses manually so a matched node's own (stale,
|
|
52
|
+
* pre-simplification) children are never independently re-visited.
|
|
53
|
+
*
|
|
54
|
+
* @param {import('@csstools/css-parser-algorithms').ComponentValue[]} list
|
|
55
|
+
* @param {TransformContext} ctx
|
|
56
|
+
* @return {void}
|
|
57
|
+
*/
|
|
58
|
+
function transformList(list, ctx) {
|
|
59
|
+
for (let i = 0; i < list.length; i++) {
|
|
60
|
+
const node = list[i];
|
|
61
|
+
if (!isFunctionNode(node)) {
|
|
62
|
+
if (isSimpleBlockNode(node)) {
|
|
63
|
+
transformList(node.value, ctx);
|
|
64
|
+
}
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const name = node.getName();
|
|
69
|
+
const isCalc = MATCH_CALC.test(name);
|
|
70
|
+
const isMath = !isCalc && isSupportedMathFunction(name);
|
|
71
|
+
if (!isCalc && !isMath) {
|
|
72
|
+
transformList(node.value, ctx);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// calc(): feed the body. Bare math: feed the whole call.
|
|
77
|
+
const inner = node.value.map((child) => child.toString()).join('');
|
|
78
|
+
const contents = isCalc ? inner : `${name}(${inner})`;
|
|
79
|
+
try {
|
|
80
|
+
const simplified = simplify(parse(tokenize(contents)));
|
|
81
|
+
const str = serialize(simplified, {
|
|
82
|
+
precision: ctx.options.precision,
|
|
83
|
+
calcName: isCalc ? name : 'calc', // preserve vendor prefix on calc()
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
if (ctx.options.warnWhenCannotResolve && str.startsWith(`${name}(`)) {
|
|
87
|
+
ctx.result.warn('Could not reduce expression: ' + ctx.value, {
|
|
88
|
+
plugin: 'postcss-calc',
|
|
89
|
+
node: ctx.item,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const replacement = parseListOfComponentValues(
|
|
94
|
+
cssTokenize({ css: str }),
|
|
95
|
+
NOOP_PARSE_ERROR
|
|
96
|
+
);
|
|
97
|
+
list.splice(i, 1, ...replacement);
|
|
98
|
+
i += replacement.length - 1;
|
|
99
|
+
} catch (error) {
|
|
100
|
+
const err = error instanceof Error ? error : new Error('Error');
|
|
101
|
+
if (ctx.options.onParseError) {
|
|
102
|
+
ctx.options.onParseError(err, contents);
|
|
103
|
+
} else {
|
|
104
|
+
ctx.result.warn(err.message, { node: ctx.item });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
48
109
|
|
|
49
110
|
/**
|
|
50
111
|
* @param {string} value
|
|
@@ -54,60 +115,42 @@ const MATH_FUNCTIONS = new Set([
|
|
|
54
115
|
* @return {string}
|
|
55
116
|
*/
|
|
56
117
|
function transformValue(value, options, result, item) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
const isCalc = MATCH_CALC.test(node.value);
|
|
63
|
-
const isMath = !isCalc && MATH_FUNCTIONS.has(node.value.toLowerCase());
|
|
64
|
-
if (!isCalc && !isMath) {
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
118
|
+
const componentValues = parseListOfComponentValues(
|
|
119
|
+
cssTokenize({ css: value }),
|
|
120
|
+
NOOP_PARSE_ERROR
|
|
121
|
+
);
|
|
67
122
|
|
|
68
|
-
|
|
69
|
-
const inner = valueParser.stringify(node.nodes);
|
|
70
|
-
const contents = isCalc ? inner : `${node.value}(${inner})`;
|
|
71
|
-
try {
|
|
72
|
-
const simplified = simplify(parse(tokenize(contents)));
|
|
73
|
-
const str = serialize(simplified, {
|
|
74
|
-
precision: options.precision,
|
|
75
|
-
calcName: isCalc ? node.value : 'calc', // preserve vendor prefix on calc()
|
|
76
|
-
});
|
|
123
|
+
transformList(componentValues, { options, result, item, value });
|
|
77
124
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
plugin: 'postcss-calc',
|
|
81
|
-
node: item,
|
|
82
|
-
});
|
|
83
|
-
}
|
|
125
|
+
return componentValues.map((node) => node.toString()).join('');
|
|
126
|
+
}
|
|
84
127
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
128
|
+
/**
|
|
129
|
+
* Runs `transformValue` over one text property of a decl/atrule/rule node
|
|
130
|
+
* and updates it in place.
|
|
131
|
+
* `setProp` closes over the property name and the concrete node type at
|
|
132
|
+
* each call site, since `Declaration`/`AtRule`/`Rule` don't share a typed
|
|
133
|
+
* "text property" name to index generically.
|
|
134
|
+
*
|
|
135
|
+
* @param {import('postcss').ChildNode} node
|
|
136
|
+
* @param {string} current
|
|
137
|
+
* @param {(target: import('postcss').ChildNode, value: string) => void} setProp
|
|
138
|
+
* @param {ResolvedOptions} options
|
|
139
|
+
* @param {import('postcss').Result} result
|
|
140
|
+
* @return {void}
|
|
141
|
+
*/
|
|
142
|
+
function applyTransform(node, current, setProp, options, result) {
|
|
143
|
+
setProp(node, transformValue(current, options, result, node));
|
|
100
144
|
}
|
|
101
145
|
|
|
102
146
|
/**
|
|
103
|
-
* @param {
|
|
147
|
+
* @param {PostCssCalcOptions} [opts]
|
|
104
148
|
* @return {import('postcss').Plugin}
|
|
105
149
|
*/
|
|
106
150
|
function pluginCreator(opts) {
|
|
107
151
|
/** @type {ResolvedOptions} */
|
|
108
152
|
const options = {
|
|
109
153
|
precision: 5,
|
|
110
|
-
preserve: false,
|
|
111
154
|
warnWhenCannotResolve: false,
|
|
112
155
|
mediaQueries: false,
|
|
113
156
|
selectors: false,
|
|
@@ -123,45 +166,49 @@ function pluginCreator(opts) {
|
|
|
123
166
|
OnceExit(css, { result }) {
|
|
124
167
|
css.walk((node) => {
|
|
125
168
|
if (node.type === 'decl') {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
169
|
+
applyTransform(
|
|
170
|
+
node,
|
|
171
|
+
node.value,
|
|
172
|
+
(n, v) => {
|
|
173
|
+
/** @type {import('postcss').Declaration} */ (n).value = v;
|
|
174
|
+
},
|
|
175
|
+
options,
|
|
176
|
+
result
|
|
177
|
+
);
|
|
134
178
|
}
|
|
135
179
|
if (node.type === 'atrule' && options.mediaQueries) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
180
|
+
applyTransform(
|
|
181
|
+
node,
|
|
182
|
+
node.params,
|
|
183
|
+
(n, v) => {
|
|
184
|
+
/** @type {import('postcss').AtRule} */ (n).params = v;
|
|
185
|
+
},
|
|
186
|
+
options,
|
|
187
|
+
result
|
|
188
|
+
);
|
|
144
189
|
}
|
|
145
190
|
if (node.type === 'rule' && options.selectors) {
|
|
146
191
|
// Reduces `:nth-child(calc(...))` via the function walk. calc() in a
|
|
147
192
|
// quoted attribute value is a literal match, so it's left untouched.
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
193
|
+
applyTransform(
|
|
194
|
+
node,
|
|
195
|
+
node.selector,
|
|
196
|
+
(n, v) => {
|
|
197
|
+
/** @type {import('postcss').Rule} */ (n).selector = v;
|
|
198
|
+
},
|
|
199
|
+
options,
|
|
200
|
+
result
|
|
201
|
+
);
|
|
156
202
|
}
|
|
157
203
|
});
|
|
158
204
|
},
|
|
159
205
|
};
|
|
160
206
|
}
|
|
161
207
|
|
|
208
|
+
/** @type {true} */
|
|
162
209
|
pluginCreator.postcss = true;
|
|
163
210
|
|
|
164
|
-
export default /** @type import('postcss').PluginCreator<
|
|
211
|
+
export default /** @type import('postcss').PluginCreator<PostCssCalcOptions>*/ (
|
|
165
212
|
pluginCreator
|
|
166
213
|
);
|
|
167
214
|
export { pluginCreator as 'module.exports' };
|
package/src/lib/parser.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Pratt parser. +/- emit Sum nodes; */÷ emit Product nodes. node.js
|
|
2
2
|
// constructors flatten and normalize on construction, while parenthesized
|
|
3
3
|
// sums retain a grouping marker for the opaque-subtraction invariant.
|
|
4
|
-
import { mkSum, mkProduct, negate } from './node.js';
|
|
4
|
+
import { mkSum, mkProduct, negate, ident, call } from './node.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @typedef {import('./tokenizer.js').Token} Token
|
|
@@ -30,7 +30,7 @@ function foldCalcKeyword(name) {
|
|
|
30
30
|
// form arrives as a single ident because CSS Syntax tokenizes leading
|
|
31
31
|
// `-` + ident-start as one ident-token.
|
|
32
32
|
if (name === 'NaN' || name === '-NaN') {
|
|
33
|
-
return { type: 'Num', value: NaN };
|
|
33
|
+
return { type: 'Num', value: Number.NaN };
|
|
34
34
|
}
|
|
35
35
|
switch (name.toLowerCase()) {
|
|
36
36
|
case 'pi':
|
|
@@ -174,7 +174,7 @@ function parseOpaqueCall(p, name) {
|
|
|
174
174
|
const flush = () => {
|
|
175
175
|
const trimmed = buf.trim();
|
|
176
176
|
if (trimmed) {
|
|
177
|
-
args.push(
|
|
177
|
+
args.push(ident(trimmed));
|
|
178
178
|
}
|
|
179
179
|
buf = '';
|
|
180
180
|
};
|
|
@@ -191,7 +191,7 @@ function parseOpaqueCall(p, name) {
|
|
|
191
191
|
if (depth === 0) {
|
|
192
192
|
p.next();
|
|
193
193
|
flush();
|
|
194
|
-
return
|
|
194
|
+
return call(name, args);
|
|
195
195
|
}
|
|
196
196
|
} else if (tk.value === ',' && depth === 1) {
|
|
197
197
|
p.next();
|
|
@@ -228,12 +228,12 @@ function requireSurroundingWs(p, token) {
|
|
|
228
228
|
|
|
229
229
|
/** @type {Record<string, PrefixParselet>} */
|
|
230
230
|
const PREFIX = {
|
|
231
|
-
number: (_p, t) => ({ type: 'Num', value: parseFloat(t.value) }),
|
|
231
|
+
number: (_p, t) => ({ type: 'Num', value: Number.parseFloat(t.value) }),
|
|
232
232
|
|
|
233
233
|
// Unit case normalization per §10.12: `1PX` serializes as `1px`.
|
|
234
234
|
dimension: (_p, t) => ({
|
|
235
235
|
type: 'Dim',
|
|
236
|
-
value: parseFloat(t.value),
|
|
236
|
+
value: Number.parseFloat(t.value),
|
|
237
237
|
unit: t.unit === '%' ? '%' : /** @type {string} */ (t.unit).toLowerCase(),
|
|
238
238
|
}),
|
|
239
239
|
|
|
@@ -254,13 +254,13 @@ const PREFIX = {
|
|
|
254
254
|
}
|
|
255
255
|
}
|
|
256
256
|
p.expect('punct', ')');
|
|
257
|
-
return
|
|
257
|
+
return call(t.value, args);
|
|
258
258
|
}
|
|
259
259
|
const kw = foldCalcKeyword(t.value);
|
|
260
260
|
if (kw) {
|
|
261
261
|
return kw;
|
|
262
262
|
}
|
|
263
|
-
return
|
|
263
|
+
return ident(t.value);
|
|
264
264
|
},
|
|
265
265
|
|
|
266
266
|
'(': (p) => {
|
package/src/lib/serialize.js
CHANGED
|
@@ -45,7 +45,7 @@ function round(v, prec) {
|
|
|
45
45
|
* @return {boolean}
|
|
46
46
|
*/
|
|
47
47
|
function isDegenerate(v) {
|
|
48
|
-
return !isFinite(v) || isNaN(v);
|
|
48
|
+
return !Number.isFinite(v) || Number.isNaN(v);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
/**
|
|
@@ -53,7 +53,7 @@ function isDegenerate(v) {
|
|
|
53
53
|
* @return {string}
|
|
54
54
|
*/
|
|
55
55
|
function degenerateKeyword(v) {
|
|
56
|
-
if (isNaN(v)) {
|
|
56
|
+
if (Number.isNaN(v)) {
|
|
57
57
|
return 'NaN';
|
|
58
58
|
}
|
|
59
59
|
return v > 0 ? 'infinity' : '-infinity';
|
|
@@ -160,13 +160,13 @@ function serializeExpr(node, prec) {
|
|
|
160
160
|
*/
|
|
161
161
|
function displaySign(term) {
|
|
162
162
|
const { sign, node } = term;
|
|
163
|
-
if (node.type === 'Num' && isFinite(node.value) && node.value < 0) {
|
|
163
|
+
if (node.type === 'Num' && Number.isFinite(node.value) && node.value < 0) {
|
|
164
164
|
return {
|
|
165
165
|
sign: /** @type {1 | -1} */ (-sign),
|
|
166
166
|
magnitude: { type: 'Num', value: -node.value },
|
|
167
167
|
};
|
|
168
168
|
}
|
|
169
|
-
if (node.type === 'Dim' && isFinite(node.value) && node.value < 0) {
|
|
169
|
+
if (node.type === 'Dim' && Number.isFinite(node.value) && node.value < 0) {
|
|
170
170
|
return {
|
|
171
171
|
sign: /** @type {1 | -1} */ (-sign),
|
|
172
172
|
magnitude: { type: 'Dim', value: -node.value, unit: node.unit },
|
|
@@ -219,7 +219,7 @@ function serializeLeadingNeg(node, prec) {
|
|
|
219
219
|
node.factors.length > 0 &&
|
|
220
220
|
node.factors[0].exponent === 1 &&
|
|
221
221
|
node.factors[0].node.type === 'Num' &&
|
|
222
|
-
isFinite(node.factors[0].node.value) &&
|
|
222
|
+
Number.isFinite(node.factors[0].node.value) &&
|
|
223
223
|
node.factors[0].node.value !== 0
|
|
224
224
|
) {
|
|
225
225
|
const head = node.factors[0].node;
|
package/src/lib/simplify/abs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { num, dim } from '../node.js';
|
|
1
|
+
import { num, dim, call } from '../node.js';
|
|
2
2
|
|
|
3
3
|
/** @typedef {import('../node.js').Node} Node */
|
|
4
4
|
|
|
@@ -8,7 +8,7 @@ import { num, dim } from '../node.js';
|
|
|
8
8
|
*/
|
|
9
9
|
function simplifyAbs(args) {
|
|
10
10
|
if (args.length !== 1) {
|
|
11
|
-
return
|
|
11
|
+
return call('abs', args);
|
|
12
12
|
}
|
|
13
13
|
const a = args[0];
|
|
14
14
|
if (a.type === 'Num') {
|
|
@@ -17,7 +17,7 @@ function simplifyAbs(args) {
|
|
|
17
17
|
if (a.type === 'Dim' && a.unit !== '%') {
|
|
18
18
|
return dim(Math.abs(a.value), a.unit);
|
|
19
19
|
}
|
|
20
|
-
return
|
|
20
|
+
return call('abs', [a]);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export { simplifyAbs };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* §10.4 — atan2. foldConstArgs already rejects percentages (property-
|
|
2
2
|
context-resolved) and enforces shared base + static convertibility. */
|
|
3
3
|
|
|
4
|
-
import { num, dim } from '../node.js';
|
|
4
|
+
import { num, dim, call } from '../node.js';
|
|
5
5
|
import { foldConstArgs } from './fold.js';
|
|
6
6
|
|
|
7
7
|
/** @typedef {import('../node.js').Node} Node */
|
|
@@ -12,16 +12,16 @@ import { foldConstArgs } from './fold.js';
|
|
|
12
12
|
*/
|
|
13
13
|
function simplifyAtan2(args) {
|
|
14
14
|
if (args.length !== 2) {
|
|
15
|
-
return
|
|
15
|
+
return call('atan2', args);
|
|
16
16
|
}
|
|
17
17
|
const fold = foldConstArgs(args);
|
|
18
18
|
if (fold === null) {
|
|
19
|
-
return
|
|
19
|
+
return call('atan2', args);
|
|
20
20
|
}
|
|
21
21
|
const [y, x] = /** @type {[number, number]} */ (fold.values);
|
|
22
22
|
const radians = Math.atan2(y, x);
|
|
23
|
-
if (isNaN(radians)) {
|
|
24
|
-
return num(NaN);
|
|
23
|
+
if (Number.isNaN(radians)) {
|
|
24
|
+
return num(Number.NaN);
|
|
25
25
|
}
|
|
26
26
|
return dim((radians * 180) / Math.PI, 'deg');
|
|
27
27
|
}
|
package/src/lib/simplify/call.js
CHANGED
|
@@ -16,9 +16,51 @@ import { simplifyExp } from './exp.js';
|
|
|
16
16
|
import { simplifyLog } from './log.js';
|
|
17
17
|
import { simplifyHypot } from './hypot.js';
|
|
18
18
|
|
|
19
|
+
import { call } from '../node.js';
|
|
20
|
+
|
|
19
21
|
/** @typedef {import('../node.js').Node} Node */
|
|
20
22
|
/** @typedef {import('../simplify.js').SimplifyFn} SimplifyFn */
|
|
21
23
|
|
|
24
|
+
/** @typedef {(name: string, args: Node[]) => Node} MathSimplifier */
|
|
25
|
+
|
|
26
|
+
// Bare CSS math functions with implemented simplification semantics, keyed
|
|
27
|
+
// by lowercase name. calc() and its vendor-prefixed forms are handled
|
|
28
|
+
// separately as wrappers in simplifyCall. This map is the single source of
|
|
29
|
+
// truth for both dispatch and `isSupportedMathFunction`.
|
|
30
|
+
/** @type {Map<string, MathSimplifier>} */
|
|
31
|
+
const MATH_SIMPLIFIERS = new Map([
|
|
32
|
+
['min', simplifyMinMax],
|
|
33
|
+
['max', simplifyMinMax],
|
|
34
|
+
['clamp', (_name, args) => simplifyClamp(args)],
|
|
35
|
+
['abs', (_name, args) => simplifyAbs(args)],
|
|
36
|
+
['sign', (_name, args) => simplifySign(args)],
|
|
37
|
+
['mod', (_name, args) => simplifyModRem('mod', args)],
|
|
38
|
+
['rem', (_name, args) => simplifyModRem('rem', args)],
|
|
39
|
+
['round', (_name, args) => simplifyRound(args)],
|
|
40
|
+
['sin', (_name, args) => simplifyTrig('sin', args)],
|
|
41
|
+
['cos', (_name, args) => simplifyTrig('cos', args)],
|
|
42
|
+
['tan', (_name, args) => simplifyTrig('tan', args)],
|
|
43
|
+
['asin', (_name, args) => simplifyInverseTrig('asin', args)],
|
|
44
|
+
['acos', (_name, args) => simplifyInverseTrig('acos', args)],
|
|
45
|
+
['atan', (_name, args) => simplifyInverseTrig('atan', args)],
|
|
46
|
+
['atan2', (_name, args) => simplifyAtan2(args)],
|
|
47
|
+
['pow', (_name, args) => simplifyPow(args)],
|
|
48
|
+
['sqrt', (_name, args) => simplifySqrt(args)],
|
|
49
|
+
['hypot', (_name, args) => simplifyHypot(args)],
|
|
50
|
+
['log', (_name, args) => simplifyLog(args)],
|
|
51
|
+
['exp', (_name, args) => simplifyExp(args)],
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Whether a bare CSS math function has an implemented simplifier.
|
|
56
|
+
*
|
|
57
|
+
* @param {string} name
|
|
58
|
+
* @return {boolean}
|
|
59
|
+
*/
|
|
60
|
+
function isSupportedMathFunction(name) {
|
|
61
|
+
return MATH_SIMPLIFIERS.has(name.toLowerCase());
|
|
62
|
+
}
|
|
63
|
+
|
|
22
64
|
/**
|
|
23
65
|
* @param {Extract<Node, { type: 'Call' }>} node
|
|
24
66
|
* @param {SimplifyFn} simplify
|
|
@@ -36,50 +78,17 @@ function simplifyCall(node, simplify) {
|
|
|
36
78
|
|
|
37
79
|
const args = node.args.map((a) => simplify(a));
|
|
38
80
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
if (name === 'sign') {
|
|
49
|
-
return simplifySign(args);
|
|
50
|
-
}
|
|
51
|
-
if (name === 'mod' || name === 'rem') {
|
|
52
|
-
return simplifyModRem(name, args);
|
|
53
|
-
}
|
|
54
|
-
if (name === 'round') {
|
|
55
|
-
return simplifyRound(args);
|
|
56
|
-
}
|
|
57
|
-
if (name === 'sin' || name === 'cos' || name === 'tan') {
|
|
58
|
-
return simplifyTrig(name, args);
|
|
59
|
-
}
|
|
60
|
-
if (name === 'asin' || name === 'acos' || name === 'atan') {
|
|
61
|
-
return simplifyInverseTrig(name, args);
|
|
62
|
-
}
|
|
63
|
-
if (name === 'atan2') {
|
|
64
|
-
return simplifyAtan2(args);
|
|
65
|
-
}
|
|
66
|
-
if (name === 'pow') {
|
|
67
|
-
return simplifyPow(args);
|
|
68
|
-
}
|
|
69
|
-
if (name === 'sqrt') {
|
|
70
|
-
return simplifySqrt(args);
|
|
71
|
-
}
|
|
72
|
-
if (name === 'hypot') {
|
|
73
|
-
return simplifyHypot(args);
|
|
74
|
-
}
|
|
75
|
-
if (name === 'log') {
|
|
76
|
-
return simplifyLog(args);
|
|
77
|
-
}
|
|
78
|
-
if (name === 'exp') {
|
|
79
|
-
return simplifyExp(args);
|
|
81
|
+
const simplifier = MATH_SIMPLIFIERS.get(name);
|
|
82
|
+
if (simplifier) {
|
|
83
|
+
// min/max preserve the call's original casing in their opaque-args
|
|
84
|
+
// fallback; the rest normalize to lowercase internally.
|
|
85
|
+
return simplifier(
|
|
86
|
+
name === 'min' || name === 'max' ? node.name : name,
|
|
87
|
+
args
|
|
88
|
+
);
|
|
80
89
|
}
|
|
81
90
|
|
|
82
|
-
return
|
|
91
|
+
return call(node.name, args);
|
|
83
92
|
}
|
|
84
93
|
|
|
85
|
-
export { simplifyCall };
|
|
94
|
+
export { isSupportedMathFunction, simplifyCall };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { num, dim } from '../node.js';
|
|
1
|
+
import { num, dim, call } from '../node.js';
|
|
2
2
|
import { foldConstArgs } from './fold.js';
|
|
3
3
|
|
|
4
4
|
/** @typedef {import('../node.js').Node} Node */
|
|
@@ -18,7 +18,7 @@ function simplifyClamp(args) {
|
|
|
18
18
|
return fold.unit === '' ? num(clamped) : dim(clamped, fold.unit);
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
-
return
|
|
21
|
+
return call('clamp', args);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export { simplifyClamp };
|
package/src/lib/simplify/exp.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { num } from '../node.js';
|
|
1
|
+
import { num, call } from '../node.js';
|
|
2
2
|
|
|
3
3
|
/** @typedef {import('../node.js').Node} Node */
|
|
4
4
|
|
|
@@ -8,7 +8,7 @@ import { num } from '../node.js';
|
|
|
8
8
|
*/
|
|
9
9
|
function simplifyExp(args) {
|
|
10
10
|
if (args.length !== 1 || args[0].type !== 'Num') {
|
|
11
|
-
return
|
|
11
|
+
return call('exp', args);
|
|
12
12
|
}
|
|
13
13
|
return num(Math.exp(args[0].value));
|
|
14
14
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// §10.5 — hypot. Empty args return null from foldConstArgs naturally.
|
|
2
2
|
|
|
3
|
-
import { num, dim } from '../node.js';
|
|
3
|
+
import { num, dim, call } from '../node.js';
|
|
4
4
|
import { foldConstArgs } from './fold.js';
|
|
5
5
|
|
|
6
6
|
/** @typedef {import('../node.js').Node} Node */
|
|
@@ -12,7 +12,7 @@ import { foldConstArgs } from './fold.js';
|
|
|
12
12
|
function simplifyHypot(args) {
|
|
13
13
|
const fold = foldConstArgs(args);
|
|
14
14
|
if (fold === null) {
|
|
15
|
-
return
|
|
15
|
+
return call('hypot', args);
|
|
16
16
|
}
|
|
17
17
|
const sumSq = fold.values.reduce((acc, v) => acc + v * v, 0);
|
|
18
18
|
const result = Math.sqrt(sumSq);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// §10.4 — asin/acos/atan. Bare <number> in, <angle> in degrees out.
|
|
2
2
|
|
|
3
|
-
import { num, dim } from '../node.js';
|
|
3
|
+
import { num, dim, call } from '../node.js';
|
|
4
4
|
|
|
5
5
|
/** @typedef {import('../node.js').Node} Node */
|
|
6
6
|
|
|
@@ -17,15 +17,15 @@ const INVERSE_TRIG_OPS = /** @type {const} */ ({
|
|
|
17
17
|
*/
|
|
18
18
|
function simplifyInverseTrig(name, args) {
|
|
19
19
|
if (args.length !== 1) {
|
|
20
|
-
return
|
|
20
|
+
return call(name, args);
|
|
21
21
|
}
|
|
22
22
|
const a = args[0];
|
|
23
23
|
if (a.type !== 'Num') {
|
|
24
|
-
return
|
|
24
|
+
return call(name, args);
|
|
25
25
|
}
|
|
26
26
|
const radians = INVERSE_TRIG_OPS[name](a.value);
|
|
27
|
-
if (isNaN(radians)) {
|
|
28
|
-
return num(NaN);
|
|
27
|
+
if (Number.isNaN(radians)) {
|
|
28
|
+
return num(Number.NaN);
|
|
29
29
|
}
|
|
30
30
|
return dim((radians * 180) / Math.PI, 'deg');
|
|
31
31
|
}
|
package/src/lib/simplify/log.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { num } from '../node.js';
|
|
1
|
+
import { num, call } from '../node.js';
|
|
2
2
|
|
|
3
3
|
/** @typedef {import('../node.js').Node} Node */
|
|
4
4
|
|
|
@@ -13,7 +13,7 @@ function simplifyLog(args) {
|
|
|
13
13
|
if (args.length === 2 && args[0].type === 'Num' && args[1].type === 'Num') {
|
|
14
14
|
return num(Math.log(args[0].value) / Math.log(args[1].value));
|
|
15
15
|
}
|
|
16
|
-
return
|
|
16
|
+
return call('log', args);
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export { simplifyLog };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { num, dim } from '../node.js';
|
|
1
|
+
import { num, dim, call } from '../node.js';
|
|
2
2
|
import { foldConstArgs } from './fold.js';
|
|
3
3
|
|
|
4
4
|
/** @typedef {import('../node.js').Node} Node */
|
|
@@ -15,7 +15,7 @@ function simplifyMinMax(name, args) {
|
|
|
15
15
|
const value = fn(...fold.values);
|
|
16
16
|
return fold.unit === '' ? num(value) : dim(value, fold.unit);
|
|
17
17
|
}
|
|
18
|
-
return
|
|
18
|
+
return call(name, args);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export { simplifyMinMax };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { num, dim } from '../node.js';
|
|
1
|
+
import { num, dim, call } from '../node.js';
|
|
2
2
|
import { foldConstArgs } from './fold.js';
|
|
3
3
|
|
|
4
4
|
/** @typedef {import('../node.js').Node} Node */
|
|
@@ -10,18 +10,18 @@ import { foldConstArgs } from './fold.js';
|
|
|
10
10
|
*/
|
|
11
11
|
function simplifyModRem(name, args) {
|
|
12
12
|
if (args.length !== 2) {
|
|
13
|
-
return
|
|
13
|
+
return call(name, args);
|
|
14
14
|
}
|
|
15
15
|
const fold = foldConstArgs(args);
|
|
16
16
|
if (fold === null) {
|
|
17
|
-
return
|
|
17
|
+
return call(name, args);
|
|
18
18
|
}
|
|
19
19
|
const [a, b] = /** @type {[number, number]} */ (fold.values);
|
|
20
20
|
const result = applyModRem(name, a, b);
|
|
21
21
|
// NaN results drop the unit (`mod(5px, 0px)` → `calc(NaN)`, not
|
|
22
22
|
// `calc(NaN * 1px)`). §10.12 unit-preserving form is a known divergence.
|
|
23
|
-
if (isNaN(result)) {
|
|
24
|
-
return num(NaN);
|
|
23
|
+
if (Number.isNaN(result)) {
|
|
24
|
+
return num(Number.NaN);
|
|
25
25
|
}
|
|
26
26
|
return fold.unit === '' ? num(result) : dim(result, fold.unit);
|
|
27
27
|
}
|
|
@@ -34,16 +34,16 @@ function simplifyModRem(name, args) {
|
|
|
34
34
|
*/
|
|
35
35
|
function applyModRem(name, a, b) {
|
|
36
36
|
if (b === 0) {
|
|
37
|
-
return NaN;
|
|
37
|
+
return Number.NaN;
|
|
38
38
|
}
|
|
39
|
-
if (!isFinite(a)) {
|
|
40
|
-
return NaN;
|
|
39
|
+
if (!Number.isFinite(a)) {
|
|
40
|
+
return Number.NaN;
|
|
41
41
|
}
|
|
42
|
-
if (!isFinite(b)) {
|
|
42
|
+
if (!Number.isFinite(b)) {
|
|
43
43
|
// mod: result is NaN when A has opposite sign to B; otherwise A.
|
|
44
44
|
// rem: result is A regardless of signs.
|
|
45
45
|
if (name === 'mod' && a !== 0 && Math.sign(a) !== Math.sign(b)) {
|
|
46
|
-
return NaN;
|
|
46
|
+
return Number.NaN;
|
|
47
47
|
}
|
|
48
48
|
return a;
|
|
49
49
|
}
|
package/src/lib/simplify/pow.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// §10.5 — pow is <number>-only.
|
|
2
2
|
|
|
3
|
-
import { num } from '../node.js';
|
|
3
|
+
import { num, call } from '../node.js';
|
|
4
4
|
|
|
5
5
|
/** @typedef {import('../node.js').Node} Node */
|
|
6
6
|
|
|
@@ -10,7 +10,7 @@ import { num } from '../node.js';
|
|
|
10
10
|
*/
|
|
11
11
|
function simplifyPow(args) {
|
|
12
12
|
if (args.length !== 2 || args[0].type !== 'Num' || args[1].type !== 'Num') {
|
|
13
|
-
return
|
|
13
|
+
return call('pow', args);
|
|
14
14
|
}
|
|
15
15
|
return num(Math.pow(args[0].value, args[1].value));
|
|
16
16
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { num, dim } from '../node.js';
|
|
1
|
+
import { num, dim, ident, call } from '../node.js';
|
|
2
2
|
import { foldConstArgs } from './fold.js';
|
|
3
3
|
|
|
4
4
|
/** @typedef {import('../node.js').Node} Node */
|
|
@@ -18,21 +18,15 @@ function simplifyRound(args) {
|
|
|
18
18
|
const n = first.name.toLowerCase();
|
|
19
19
|
if (!ROUND_STRATEGIES.has(n)) {
|
|
20
20
|
// Unrecognized strategy ident — opaque rather than guessing intent.
|
|
21
|
-
return
|
|
21
|
+
return call('round', args);
|
|
22
22
|
}
|
|
23
23
|
strategy = /** @type {RoundStrategy} */ (n);
|
|
24
24
|
rest = args.slice(1);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/** @type {() => Node} */
|
|
28
|
-
const passthrough = () =>
|
|
29
|
-
|
|
30
|
-
name: 'round',
|
|
31
|
-
args:
|
|
32
|
-
strategy === 'nearest'
|
|
33
|
-
? rest
|
|
34
|
-
: [{ type: 'Ident', name: strategy }, ...rest],
|
|
35
|
-
});
|
|
28
|
+
const passthrough = () =>
|
|
29
|
+
call('round', strategy === 'nearest' ? rest : [ident(strategy), ...rest]);
|
|
36
30
|
|
|
37
31
|
// B omitted: defaults to 1 when A is <number>; else opaque.
|
|
38
32
|
const argsForFold = argsForRoundFold(rest);
|
|
@@ -49,12 +43,12 @@ function simplifyRound(args) {
|
|
|
49
43
|
// case folds to ±0 carrying A's sign. Infinite-A / finite-B falls through
|
|
50
44
|
// to applyRound, where floor*b===ceil*b===±∞ collapses back to A
|
|
51
45
|
// (§10.3.1 "result is the same infinity").
|
|
52
|
-
if (isNaN(b)) {
|
|
53
|
-
return num(NaN);
|
|
46
|
+
if (Number.isNaN(b)) {
|
|
47
|
+
return num(Number.NaN);
|
|
54
48
|
}
|
|
55
|
-
if (!isFinite(b)) {
|
|
56
|
-
if (!isFinite(a)) {
|
|
57
|
-
return num(NaN);
|
|
49
|
+
if (!Number.isFinite(b)) {
|
|
50
|
+
if (!Number.isFinite(a)) {
|
|
51
|
+
return num(Number.NaN);
|
|
58
52
|
}
|
|
59
53
|
let result;
|
|
60
54
|
if (strategy === 'up' && a > 0) {
|
|
@@ -68,8 +62,8 @@ function simplifyRound(args) {
|
|
|
68
62
|
}
|
|
69
63
|
|
|
70
64
|
const result = applyRound(strategy, a, b);
|
|
71
|
-
if (isNaN(result)) {
|
|
72
|
-
return num(NaN);
|
|
65
|
+
if (Number.isNaN(result)) {
|
|
66
|
+
return num(Number.NaN);
|
|
73
67
|
}
|
|
74
68
|
return fold.unit === '' ? num(result) : dim(result, fold.unit);
|
|
75
69
|
}
|
|
@@ -96,7 +90,7 @@ function argsForRoundFold(args) {
|
|
|
96
90
|
*/
|
|
97
91
|
function applyRound(strategy, a, b) {
|
|
98
92
|
if (b === 0) {
|
|
99
|
-
return NaN;
|
|
93
|
+
return Number.NaN;
|
|
100
94
|
}
|
|
101
95
|
const q = a / b;
|
|
102
96
|
const c1 = Math.floor(q) * b;
|
package/src/lib/simplify/sign.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { num } from '../node.js';
|
|
1
|
+
import { num, call } from '../node.js';
|
|
2
2
|
|
|
3
3
|
/** @typedef {import('../node.js').Node} Node */
|
|
4
4
|
|
|
@@ -8,7 +8,7 @@ import { num } from '../node.js';
|
|
|
8
8
|
*/
|
|
9
9
|
function simplifySign(args) {
|
|
10
10
|
if (args.length !== 1) {
|
|
11
|
-
return
|
|
11
|
+
return call('sign', args);
|
|
12
12
|
}
|
|
13
13
|
const a = args[0];
|
|
14
14
|
if (a.type === 'Num') {
|
|
@@ -18,7 +18,7 @@ function simplifySign(args) {
|
|
|
18
18
|
if (a.type === 'Dim' && a.unit !== '%') {
|
|
19
19
|
return num(Math.sign(a.value));
|
|
20
20
|
}
|
|
21
|
-
return
|
|
21
|
+
return call('sign', [a]);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export { simplifySign };
|
package/src/lib/simplify/sqrt.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { num } from '../node.js';
|
|
1
|
+
import { num, call } from '../node.js';
|
|
2
2
|
|
|
3
3
|
/** @typedef {import('../node.js').Node} Node */
|
|
4
4
|
/**
|
|
@@ -7,7 +7,7 @@ import { num } from '../node.js';
|
|
|
7
7
|
*/
|
|
8
8
|
function simplifySqrt(args) {
|
|
9
9
|
if (args.length !== 1 || args[0].type !== 'Num') {
|
|
10
|
-
return
|
|
10
|
+
return call('sqrt', args);
|
|
11
11
|
}
|
|
12
12
|
return num(Math.sqrt(args[0].value));
|
|
13
13
|
}
|
package/src/lib/simplify/trig.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// §10.4 — sin/cos/tan. <number> is radians; <angle> dim is converted.
|
|
2
2
|
|
|
3
|
-
import { num } from '../node.js';
|
|
3
|
+
import { num, call } from '../node.js';
|
|
4
4
|
import { baseOf, convert } from '../convertUnits.js';
|
|
5
5
|
|
|
6
6
|
/** @typedef {import('../node.js').Node} Node */
|
|
@@ -18,7 +18,7 @@ const TRIG_OPS = /** @type {const} */ ({
|
|
|
18
18
|
*/
|
|
19
19
|
function simplifyTrig(name, args) {
|
|
20
20
|
if (args.length !== 1) {
|
|
21
|
-
return
|
|
21
|
+
return call(name, args);
|
|
22
22
|
}
|
|
23
23
|
const a = args[0];
|
|
24
24
|
/** @type {number | null} */ let radians = null;
|
|
@@ -36,7 +36,7 @@ function simplifyTrig(name, args) {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
if (radians === null) {
|
|
39
|
-
return
|
|
39
|
+
return call(name, args);
|
|
40
40
|
}
|
|
41
41
|
return num(TRIG_OPS[name](radians));
|
|
42
42
|
}
|
package/types/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
export type
|
|
1
|
+
export type PostCssCalcOptions = {
|
|
2
2
|
precision?: number | false;
|
|
3
|
-
preserve?: boolean;
|
|
4
3
|
warnWhenCannotResolve?: boolean;
|
|
5
4
|
mediaQueries?: boolean;
|
|
6
5
|
selectors?: boolean;
|
|
@@ -9,15 +8,21 @@ export type PluginOptions = {
|
|
|
9
8
|
*/
|
|
10
9
|
onParseError?: (error: Error, input: string) => void;
|
|
11
10
|
};
|
|
12
|
-
export type ResolvedOptions = Required<Omit<
|
|
11
|
+
export type ResolvedOptions = Required<Omit<PostCssCalcOptions, 'onParseError'>> & Pick<PostCssCalcOptions, 'onParseError'>;
|
|
12
|
+
export type TransformContext = {
|
|
13
|
+
options: ResolvedOptions;
|
|
14
|
+
result: import('postcss').Result;
|
|
15
|
+
item: import('postcss').ChildNode;
|
|
16
|
+
value: string;
|
|
17
|
+
};
|
|
13
18
|
/**
|
|
14
|
-
* @param {
|
|
19
|
+
* @param {PostCssCalcOptions} [opts]
|
|
15
20
|
* @return {import('postcss').Plugin}
|
|
16
21
|
*/
|
|
17
|
-
declare function pluginCreator(opts?:
|
|
22
|
+
declare function pluginCreator(opts?: PostCssCalcOptions): import('postcss').Plugin;
|
|
18
23
|
declare namespace pluginCreator {
|
|
19
|
-
var postcss:
|
|
24
|
+
var postcss: true;
|
|
20
25
|
}
|
|
21
|
-
declare const _default: import('postcss').PluginCreator<
|
|
26
|
+
declare const _default: import('postcss').PluginCreator<PostCssCalcOptions>;
|
|
22
27
|
export default _default;
|
|
23
28
|
export { pluginCreator as 'module.exports' };
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
export type Node = import('../node.js').Node;
|
|
2
2
|
export type SimplifyFn = import('../simplify.js').SimplifyFn;
|
|
3
|
-
|
|
4
|
-
/**
|
|
3
|
+
export type MathSimplifier = (name: string, args: Node[]) => Node;
|
|
4
|
+
/**
|
|
5
|
+
* Whether a bare CSS math function has an implemented simplifier.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} name
|
|
8
|
+
* @return {boolean}
|
|
9
|
+
*/
|
|
10
|
+
declare function isSupportedMathFunction(name: string): boolean;
|
|
5
11
|
/**
|
|
6
12
|
* @param {Extract<Node, { type: 'Call' }>} node
|
|
7
13
|
* @param {SimplifyFn} simplify
|
|
@@ -10,4 +16,4 @@ export type SimplifyFn = import('../simplify.js').SimplifyFn;
|
|
|
10
16
|
declare function simplifyCall(node: Extract<Node, {
|
|
11
17
|
type: 'Call';
|
|
12
18
|
}>, simplify: SimplifyFn): Node;
|
|
13
|
-
export { simplifyCall };
|
|
19
|
+
export { isSupportedMathFunction, simplifyCall };
|