postcss-calc 11.2.1 → 11.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +0 -0
- package/README.md +44 -16
- package/package.json +13 -13
- package/src/index.js +2 -1
- package/src/lib/analyze.js +0 -6
- package/src/lib/compile.js +1 -1
- package/src/lib/node.js +61 -4
- package/src/lib/parser/opaque.js +151 -0
- package/src/lib/parser/tokens.js +242 -0
- package/src/lib/parser.js +32 -377
- package/src/lib/serialize/expression.js +376 -0
- package/src/lib/serialize/precision.js +244 -0
- package/src/lib/serialize.js +24 -561
- package/src/lib/simplify/bucket.js +22 -5
- package/src/lib/simplify/cancel.js +35 -11
- package/src/lib/simplify/clamp.js +2 -2
- package/src/lib/simplify/exact.js +18 -0
- package/src/lib/simplify/fold.js +15 -1
- package/src/lib/simplify/min-max.js +2 -2
- package/src/lib/simplify/product.js +263 -48
- package/src/lib/simplify/sum.js +6 -2
- package/src/lib/simplify.js +22 -5
- package/src/reduce.js +2 -1
- package/types/lib/node.d.ts +17 -2
- package/types/lib/parser/opaque.d.ts +12 -0
- package/types/lib/parser/tokens.d.ts +83 -0
- package/types/lib/parser.d.ts +4 -44
- package/types/lib/serialize/expression.d.ts +95 -0
- package/types/lib/serialize/precision.d.ts +101 -0
- package/types/lib/simplify/bucket.d.ts +4 -1
- package/types/lib/simplify/cancel.d.ts +11 -7
- package/types/lib/simplify/exact.d.ts +9 -0
- package/types/lib/simplify/fold.d.ts +14 -1
- package/types/lib/simplify/product.d.ts +3 -7
- package/types/lib/simplify/sum.d.ts +2 -1
- package/types/lib/simplify.d.ts +3 -8
package/LICENSE
CHANGED
|
File without changes
|
package/README.md
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
[![Support Chat][git-img]][git-url]
|
|
5
5
|
|
|
6
6
|
[PostCSS Calc] lets you reduce `calc()` references whenever it's possible.
|
|
7
|
-
When
|
|
8
|
-
|
|
7
|
+
When an expression mixes units that cannot be combined exactly (such as `px`
|
|
8
|
+
and `em`), or contains values only known at runtime (such as `var()`), the
|
|
9
|
+
unresolved part is left for the browser's [W3C calc() implementation].
|
|
9
10
|
|
|
10
11
|
## Installation
|
|
11
12
|
|
|
@@ -62,7 +63,7 @@ leaving all other text untouched.
|
|
|
62
63
|
import reduceCalc from 'postcss-calc/reduce';
|
|
63
64
|
|
|
64
65
|
reduceCalc('calc(1in + 10px)');
|
|
65
|
-
// => 'calc(
|
|
66
|
+
// => 'calc(106px)'
|
|
66
67
|
|
|
67
68
|
reduceCalc('min(50px, calc(2 * 40px))');
|
|
68
69
|
// => 'calc(50px)'
|
|
@@ -123,6 +124,28 @@ Allows you to define the precision for decimal numbers. Set it to `false` to
|
|
|
123
124
|
disable rounding and preserve full IEEE-754 floating-point precision (emitting
|
|
124
125
|
the shortest round-tripping decimal representation).
|
|
125
126
|
|
|
127
|
+
Values below 1 keep `precision` significant digits (`.0123456px` becomes
|
|
128
|
+
`.012346px`), and larger values keep `precision` decimals. Divisions and unit
|
|
129
|
+
conversions are folded only when the result is exact at the precision;
|
|
130
|
+
otherwise they stay symbolic and everything around them is still simplified:
|
|
131
|
+
|
|
132
|
+
```css
|
|
133
|
+
.a {
|
|
134
|
+
width: calc(100% / 4);
|
|
135
|
+
} /* calc(25%) */
|
|
136
|
+
.b {
|
|
137
|
+
width: calc(100% / 3);
|
|
138
|
+
} /* calc(100% / 3), not 33.33333% */
|
|
139
|
+
.c {
|
|
140
|
+
width: calc(1cm + 1px);
|
|
141
|
+
} /* calc(1cm + 1px) */
|
|
142
|
+
.d {
|
|
143
|
+
width: calc(1px + 1pt);
|
|
144
|
+
} /* calc(1.75pt) */
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
With `precision: false` nothing is rounded, so every division is folded.
|
|
148
|
+
|
|
126
149
|
```js
|
|
127
150
|
var out = postcss()
|
|
128
151
|
.use(calc({ precision: 10 }))
|
|
@@ -235,15 +258,10 @@ canonical-form decisions:
|
|
|
235
258
|
requires the zero term because it carries the length-percentage type.
|
|
236
259
|
- **Constant folding.** `calc(43 + pi)` now folds to `46.14159` (§10.7.1).
|
|
237
260
|
Previously `pi` / `e` stayed symbolic.
|
|
238
|
-
- **Reciprocal conversion.** `calc(var(--x) / 2)` becomes
|
|
239
|
-
`calc(var(--x) * 0.5)`. The two are mathematically equivalent;
|
|
240
|
-
previously the division shape was kept.
|
|
241
261
|
- **Distributive multiplication.** `calc(0.5 * (100vw - 10px))` becomes
|
|
242
262
|
`calc(50vw - 5px)`.
|
|
243
263
|
- **Unit case normalization.** `2PX` becomes `2px` (CSS units are case-
|
|
244
264
|
insensitive; lowercase is conventional).
|
|
245
|
-
- **Calc unwrap (§10.6).** `calc(var(--foo))` becomes `var(--foo)` — a
|
|
246
|
-
`calc()` containing a single value is replaced by that value.
|
|
247
265
|
- **Spec-style spaced operators.** `2px*var(--x)` is serialized as
|
|
248
266
|
`2px * var(--x)`. The tokenizer is unaffected; only output spacing
|
|
249
267
|
differs.
|
|
@@ -265,13 +283,15 @@ To replace the value of CSS custom properties at build time, try [PostCSS Custom
|
|
|
265
283
|
## Contributing
|
|
266
284
|
|
|
267
285
|
Work on a branch, install dev-dependencies, respect coding style & run tests
|
|
268
|
-
before submitting a bug fix or a feature.
|
|
286
|
+
before submitting a bug fix or a feature. See [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
287
|
+
for the full guidelines. The project uses [pnpm](https://pnpm.io/).
|
|
269
288
|
|
|
270
289
|
```bash
|
|
271
290
|
git clone git@github.com:postcss/postcss-calc.git
|
|
272
291
|
git checkout -b patch-1
|
|
273
|
-
|
|
274
|
-
|
|
292
|
+
pnpm install
|
|
293
|
+
pnpm test
|
|
294
|
+
pnpm lint
|
|
275
295
|
```
|
|
276
296
|
|
|
277
297
|
The normal test run uses a deterministic structural sample of the harvested
|
|
@@ -282,11 +302,19 @@ when changing parsing/simplification behavior:
|
|
|
282
302
|
pnpm test:corpus:full
|
|
283
303
|
```
|
|
284
304
|
|
|
285
|
-
|
|
286
|
-
`pnpm benchmark:
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
305
|
+
Performance changes to the parser, analyzer, simplifier, or serializer should be
|
|
306
|
+
checked with the benchmarks. `pnpm benchmark:arithmetic-chains` and
|
|
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.
|
|
290
318
|
|
|
291
319
|
The PostCSS benchmark awaits `postcss().process(...)`, and that await already
|
|
292
320
|
triggers result stringification. It therefore does not add a redundant
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "postcss-calc",
|
|
3
|
-
"version": "11.2.
|
|
3
|
+
"version": "11.2.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "PostCSS plugin to reduce calc()",
|
|
6
6
|
"keywords": [
|
|
@@ -38,20 +38,20 @@
|
|
|
38
38
|
"devEngines": {
|
|
39
39
|
"packageManager": {
|
|
40
40
|
"name": "pnpm",
|
|
41
|
-
"version": "12.
|
|
41
|
+
"version": "12.8.1"
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@csstools/css-calc": "^3.4.
|
|
46
|
-
"@types/node": "^26.6.
|
|
47
|
-
"fast-check": "^4.10.
|
|
48
|
-
"oxfmt": "^0.
|
|
49
|
-
"oxlint": "^1.
|
|
45
|
+
"@csstools/css-calc": "^3.4.1",
|
|
46
|
+
"@types/node": "^26.6.3",
|
|
47
|
+
"fast-check": "^4.10.2",
|
|
48
|
+
"oxfmt": "^0.71.0",
|
|
49
|
+
"oxlint": "^1.86.0",
|
|
50
50
|
"postcss": "^8.5.28",
|
|
51
51
|
"typescript": "~7.0.2"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@csstools/css-tokenizer": "^4.0.
|
|
54
|
+
"@csstools/css-tokenizer": "^4.0.2"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
57
|
"postcss": "^8.5.28"
|
|
@@ -59,13 +59,13 @@
|
|
|
59
59
|
"scripts": {
|
|
60
60
|
"lint": "oxlint . && tsc && oxfmt --check",
|
|
61
61
|
"fmt": "oxfmt",
|
|
62
|
-
"benchmark:arithmetic-chains": "node scripts/benchmark-arithmetic-chains
|
|
63
|
-
"benchmark:nested-fallbacks": "node scripts/benchmark-nested-fallbacks
|
|
64
|
-
"benchmark:corpus": "node scripts/benchmark.js",
|
|
65
|
-
"benchmark:serialization": "node scripts/benchmark-serialization.js",
|
|
62
|
+
"benchmark:arithmetic-chains": "node scripts/benchmark/benchmark-parser.js arithmetic-chains",
|
|
63
|
+
"benchmark:nested-fallbacks": "node scripts/benchmark/benchmark-parser.js nested-fallbacks",
|
|
64
|
+
"benchmark:corpus": "node scripts/benchmark/benchmark-corpus.js",
|
|
65
|
+
"benchmark:serialization": "node scripts/benchmark/benchmark-serialization.js",
|
|
66
66
|
"test:benchmark": "node --test 'test/unit/benchmark-*.test.js' test/unit/compare-parser-benchmarks.test.js test/unit/corpus-benchmark.test.js",
|
|
67
67
|
"test:benchmark:simulation": "node test/benchmark/statistical-simulation.js",
|
|
68
|
-
"benchmark:reanalyze": "node scripts/compare-parser-benchmarks.js",
|
|
68
|
+
"benchmark:reanalyze": "node scripts/benchmark/compare-parser-benchmarks.js",
|
|
69
69
|
"test": "node --test --test-reporter=dot 'test/**/*.test.js' 'test/**/*.test.cjs'",
|
|
70
70
|
"test:mutation:corpus": "node test/mutation/corpus-selection.js",
|
|
71
71
|
"test:corpus:full": "POSTCSS_CALC_FULL_CORPUS=1 node --test test/conformance/corpus.test.js"
|
package/src/index.js
CHANGED
|
@@ -65,12 +65,13 @@ function applyTransform(
|
|
|
65
65
|
function pluginCreator(opts) {
|
|
66
66
|
/** @type {ResolvedOptions} */
|
|
67
67
|
const options = {
|
|
68
|
-
precision: 5,
|
|
69
68
|
warnWhenCannotResolve: false,
|
|
70
69
|
mediaQueries: false,
|
|
71
70
|
selectors: false,
|
|
72
71
|
unwrapSingleValue: false,
|
|
73
72
|
...opts,
|
|
73
|
+
// An explicit `precision: undefined` keeps the default.
|
|
74
|
+
precision: opts?.precision ?? 5,
|
|
74
75
|
};
|
|
75
76
|
|
|
76
77
|
return {
|
package/src/lib/analyze.js
CHANGED
|
@@ -50,9 +50,6 @@ function analyzeType(node, depth = 0) {
|
|
|
50
50
|
case 'Num':
|
|
51
51
|
return resolved(numberType);
|
|
52
52
|
case 'Dim':
|
|
53
|
-
// Percentages are contextual; their percent-ness is tracked so a
|
|
54
|
-
// `% / %` product cancels to a number. Unknown units are opaque, while
|
|
55
|
-
// known families can still reject px + seconds.
|
|
56
53
|
return node.unit === '%'
|
|
57
54
|
? finish(percentageType, true, true)
|
|
58
55
|
: resolved({ kind: 'dimension', base: baseOf(node.unit) });
|
|
@@ -83,9 +80,6 @@ function analyzeSum(node, depth) {
|
|
|
83
80
|
if (isFailure(child.type)) {
|
|
84
81
|
type = failureType;
|
|
85
82
|
} else if (child.type.kind === 'unknown') {
|
|
86
|
-
// A pure percentage sum stays percentage-typed so a surrounding
|
|
87
|
-
// product can cancel `% / %`; any other opaque term must widen the
|
|
88
|
-
// sum back to unknown.
|
|
89
83
|
if (isPercentage(child.type)) hasPercentage = true;
|
|
90
84
|
else hasUnknown = true;
|
|
91
85
|
} else if (type === null) {
|
package/src/lib/compile.js
CHANGED
|
@@ -34,7 +34,7 @@ function compileCandidate(candidate, ctx) {
|
|
|
34
34
|
if (!analysis.valid) {
|
|
35
35
|
throw new Error('Invalid CSS calculation type');
|
|
36
36
|
}
|
|
37
|
-
const tree = simplify(parsed);
|
|
37
|
+
const tree = simplify(parsed, ctx.options.precision);
|
|
38
38
|
const original =
|
|
39
39
|
analysis.unresolved && !candidate.calculation
|
|
40
40
|
? ctx.value.slice(candidate.start, candidate.end)
|
package/src/lib/node.js
CHANGED
|
@@ -8,7 +8,11 @@
|
|
|
8
8
|
// - No ungrouped Sum directly contains another Sum (flattened on
|
|
9
9
|
// construction). A grouped Sum is retained so a later negative sign
|
|
10
10
|
// cannot be distributed across opaque terms.
|
|
11
|
-
// - No Product directly contains another Product (flattened).
|
|
11
|
+
// - No Product directly contains another ungrouped Product (flattened). A
|
|
12
|
+
// grouped Product is retained: it is a parenthesized group around a
|
|
13
|
+
// substitution function (`var()`, `env()`, ...), whose tokens must not
|
|
14
|
+
// merge with the surrounding factors. A grouped Product may hold a single
|
|
15
|
+
// factor.
|
|
12
16
|
// - A Sum/Product with one positive element collapses to that element.
|
|
13
17
|
// - A Sum/Product with no elements collapses to Num(0) / Num(1).
|
|
14
18
|
// - Positive zero-valued Nums are dropped from all-number sums. They are
|
|
@@ -28,7 +32,7 @@
|
|
|
28
32
|
* @typedef {{sign: 1 | -1, node: Node}} SumTerm Sign is always +1 when node is Num or Dim.
|
|
29
33
|
* @typedef {{type: 'Sum', terms: SumTerm[], grouped?: boolean}} Sum
|
|
30
34
|
* @typedef {{exponent: 1 | -1, node: Node}} ProductFactor exponent +1 = numerator, -1 = denominator.
|
|
31
|
-
* @typedef {{type: 'Product', factors: ProductFactor[]}} Product
|
|
35
|
+
* @typedef {{type: 'Product', factors: ProductFactor[], grouped?: boolean}} Product
|
|
32
36
|
* @typedef {Num | Dim | Ident | Call | OpaqueCall | Sum | Product} Node
|
|
33
37
|
*/
|
|
34
38
|
|
|
@@ -185,7 +189,7 @@ function mkProduct(rawFactors) {
|
|
|
185
189
|
*/
|
|
186
190
|
function pushProductFactor(out, f) {
|
|
187
191
|
const n = f.node;
|
|
188
|
-
if (n.type === 'Product') {
|
|
192
|
+
if (n.type === 'Product' && !n.grouped) {
|
|
189
193
|
for (const inner of n.factors) {
|
|
190
194
|
out.push({
|
|
191
195
|
exponent: /** @type {1 | -1} */ (f.exponent * inner.exponent),
|
|
@@ -201,6 +205,48 @@ function pushProductFactor(out, f) {
|
|
|
201
205
|
out.push(f);
|
|
202
206
|
}
|
|
203
207
|
|
|
208
|
+
/**
|
|
209
|
+
* Mark a node as a source parenthesized group. A Sum or Product keeps its
|
|
210
|
+
* structure; any other node becomes a single-factor grouped Product.
|
|
211
|
+
* @param {Node} node
|
|
212
|
+
* @return {Node}
|
|
213
|
+
*/
|
|
214
|
+
function mkGroup(node) {
|
|
215
|
+
if (node.type === 'Sum' || node.type === 'Product') {
|
|
216
|
+
return { ...node, grouped: true };
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
type: 'Product',
|
|
220
|
+
factors: [{ exponent: 1, node }],
|
|
221
|
+
grouped: true,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Whether a node is a substitution function call (`var()`, `env()`, ...), or
|
|
227
|
+
* an ungrouped Product that directly contains one. Nested groups are not
|
|
228
|
+
* inspected: they already bound their own tokens.
|
|
229
|
+
* @param {Node} node
|
|
230
|
+
* @return {boolean}
|
|
231
|
+
*/
|
|
232
|
+
function isSubstitutionNode(node) {
|
|
233
|
+
if (node.type === 'OpaqueCall') return true;
|
|
234
|
+
if (node.type === 'Product' && !node.grouped) {
|
|
235
|
+
return node.factors.some((factor) => isSubstitutionNode(factor.node));
|
|
236
|
+
}
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Group a parenthesized expression when its tokens must stay bound: a sum, or
|
|
242
|
+
* a product around a substitution function.
|
|
243
|
+
* @param {Node} node
|
|
244
|
+
* @return {Node}
|
|
245
|
+
*/
|
|
246
|
+
function groupSubstitution(node) {
|
|
247
|
+
return node.type === 'Sum' || isSubstitutionNode(node) ? mkGroup(node) : node;
|
|
248
|
+
}
|
|
249
|
+
|
|
204
250
|
/**
|
|
205
251
|
* Negate any node, preserving canonical form.
|
|
206
252
|
* @param {Node} node
|
|
@@ -233,4 +279,15 @@ function negate(node) {
|
|
|
233
279
|
return mkSum([{ sign: -1, node }]);
|
|
234
280
|
}
|
|
235
281
|
|
|
236
|
-
export {
|
|
282
|
+
export {
|
|
283
|
+
num,
|
|
284
|
+
dim,
|
|
285
|
+
ident,
|
|
286
|
+
call,
|
|
287
|
+
opaqueCall,
|
|
288
|
+
mkSum,
|
|
289
|
+
mkProduct,
|
|
290
|
+
mkGroup,
|
|
291
|
+
groupSubstitution,
|
|
292
|
+
negate,
|
|
293
|
+
};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// Opaque function parsing: `var()`, unknown functions and their component trees.
|
|
2
|
+
import { TokenType as CssType } from '@csstools/css-tokenizer';
|
|
3
|
+
import { ident, opaqueCall } from '../node.js';
|
|
4
|
+
import {
|
|
5
|
+
isCalculationFunction,
|
|
6
|
+
isSupportedMathFunction,
|
|
7
|
+
} from '../functions.js';
|
|
8
|
+
import { assertDepth } from '../limits.js';
|
|
9
|
+
import { eofPositionAt, sourceSpelling } from './tokens.js';
|
|
10
|
+
|
|
11
|
+
/** @typedef {import('@csstools/css-tokenizer').CSSToken} CSSToken */
|
|
12
|
+
/** @typedef {import('../node.js').Node} Node */
|
|
13
|
+
/** @typedef {import('../node.js').OpaqueComponent} OpaqueComponent */
|
|
14
|
+
/** @typedef {import('./tokens.js').ParseInput} ParseInput */
|
|
15
|
+
/** @typedef {import('./tokens.js').Cursor} Cursor */
|
|
16
|
+
/** @typedef {import('./tokens.js').FunctionToken} FunctionToken */
|
|
17
|
+
/** @typedef {(input: ParseInput, start: number, end: number) => Node} ParseRange */
|
|
18
|
+
|
|
19
|
+
/** @param {OpaqueComponent[]} target @param {OpaqueComponent} part */
|
|
20
|
+
function pushComponent(target, part) {
|
|
21
|
+
if (typeof part === 'string' && typeof target.at(-1) === 'string')
|
|
22
|
+
target[target.length - 1] += part;
|
|
23
|
+
else target.push(part);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** @param {ParseInput} input @param {Cursor} cursor @param {FunctionToken} token @param {string} name @return {number} */
|
|
27
|
+
function closeOfCall(input, cursor, token, name) {
|
|
28
|
+
const close = input.index.closeOf(token.index, input.end);
|
|
29
|
+
if (close === -1)
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Unclosed ${name}( at position ${eofPositionAt(input, cursor.index)}`
|
|
32
|
+
);
|
|
33
|
+
return close;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** @param {ParseInput} input @param {Cursor} cursor @param {FunctionToken} token @param {string} name @param {string} rawName @param {ParseRange} parseRange @return {Node} */
|
|
37
|
+
function parseOpaqueCall(input, cursor, token, name, rawName, parseRange) {
|
|
38
|
+
const close = closeOfCall(input, cursor, token, name);
|
|
39
|
+
cursor.skipTo(close + 1);
|
|
40
|
+
return opaqueCall(
|
|
41
|
+
name,
|
|
42
|
+
componentTree(input, token.index + 1, close, parseRange, []),
|
|
43
|
+
sourceSpelling(rawName, name)
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** @param {CSSToken[]} tokens @param {number} start @param {number} end */
|
|
48
|
+
function rawTokens(tokens, start, end) {
|
|
49
|
+
let raw = '';
|
|
50
|
+
for (let i = start; i < end; i++) raw += tokens[i][1];
|
|
51
|
+
return raw;
|
|
52
|
+
}
|
|
53
|
+
/** @param {CSSToken[]} tokens @param {number} start @param {number} end */
|
|
54
|
+
function customProperty(tokens, start, end) {
|
|
55
|
+
/** @type {CSSToken | null} */
|
|
56
|
+
let found = null;
|
|
57
|
+
let foundIndex = -1;
|
|
58
|
+
for (let i = start; i < end; i++) {
|
|
59
|
+
const type = tokens[i][0];
|
|
60
|
+
if (type === CssType.Whitespace || type === CssType.Comment) continue;
|
|
61
|
+
if (found !== null) return null;
|
|
62
|
+
found = tokens[i];
|
|
63
|
+
foundIndex = i;
|
|
64
|
+
}
|
|
65
|
+
if (!found || found[0] !== CssType.Ident) return null;
|
|
66
|
+
const decoded = found[4].value;
|
|
67
|
+
return decoded.startsWith('--') && decoded !== '--'
|
|
68
|
+
? { decoded, raw: found[1], index: foundIndex }
|
|
69
|
+
: null;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Append the component tree of `[start, end)` to `root` and return it.
|
|
73
|
+
* Callers seed `root` so leading components need no copy or spread.
|
|
74
|
+
* @param {ParseInput} input @param {number} start @param {number} end @param {ParseRange} parseRange @param {OpaqueComponent[]} root @return {OpaqueComponent[]}
|
|
75
|
+
*/
|
|
76
|
+
function componentTree(input, start, end, parseRange, root) {
|
|
77
|
+
/** @type {OpaqueComponent[]} */ let tree = root;
|
|
78
|
+
/** @type {{parent: OpaqueComponent[], tree: OpaqueComponent[], close: number, end: number}[]} */
|
|
79
|
+
const frames = [];
|
|
80
|
+
const { tokens } = input;
|
|
81
|
+
let i = start;
|
|
82
|
+
while (true) {
|
|
83
|
+
if (i >= end) {
|
|
84
|
+
if (frames.length === 0) break;
|
|
85
|
+
const frame =
|
|
86
|
+
/** @type {{parent: OpaqueComponent[], tree: OpaqueComponent[], close: number, end: number}} */ (
|
|
87
|
+
frames.pop()
|
|
88
|
+
);
|
|
89
|
+
tree = frame.parent;
|
|
90
|
+
pushComponent(tree, frame.tree);
|
|
91
|
+
pushComponent(tree, tokens[frame.close][1]);
|
|
92
|
+
end = frame.end;
|
|
93
|
+
i = frame.close + 1;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const token = tokens[i];
|
|
98
|
+
const close = input.index.closeOf(i, end);
|
|
99
|
+
if (close === -1) {
|
|
100
|
+
pushComponent(tree, token[1]);
|
|
101
|
+
i++;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const isMathFunction =
|
|
106
|
+
token[0] === CssType.Function &&
|
|
107
|
+
(isCalculationFunction(token[4].value) ||
|
|
108
|
+
isSupportedMathFunction(token[4].value));
|
|
109
|
+
if (isMathFunction) {
|
|
110
|
+
try {
|
|
111
|
+
pushComponent(tree, parseRange(input, i, close + 1));
|
|
112
|
+
} catch {
|
|
113
|
+
pushComponent(tree, rawTokens(tokens, i, close + 1));
|
|
114
|
+
}
|
|
115
|
+
i = close + 1;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
pushComponent(tree, token[1]);
|
|
120
|
+
assertDepth(frames.length + 1);
|
|
121
|
+
/** @type {OpaqueComponent[]} */
|
|
122
|
+
const child = [];
|
|
123
|
+
frames.push({ parent: tree, tree: child, close, end });
|
|
124
|
+
tree = child;
|
|
125
|
+
end = close;
|
|
126
|
+
i++;
|
|
127
|
+
}
|
|
128
|
+
return root;
|
|
129
|
+
}
|
|
130
|
+
/** @param {ParseInput} input @param {Cursor} cursor @param {FunctionToken} token @param {string} name @param {string} rawName @param {ParseRange} parseRange @return {Node} */
|
|
131
|
+
function parseVar(input, cursor, token, name, rawName, parseRange) {
|
|
132
|
+
const { tokens } = input;
|
|
133
|
+
const start = token.index + 1;
|
|
134
|
+
const close = closeOfCall(input, cursor, token, name);
|
|
135
|
+
const comma = input.index.firstTopLevelComma(start, close);
|
|
136
|
+
const property = customProperty(tokens, start, comma === -1 ? close : comma);
|
|
137
|
+
if (!property)
|
|
138
|
+
throw new Error(
|
|
139
|
+
`Invalid custom property in ${name}() at position ${tokens[start]?.[2] ?? eofPositionAt(input, cursor.index)}`
|
|
140
|
+
);
|
|
141
|
+
cursor.skipTo(close + 1);
|
|
142
|
+
/** @type {OpaqueComponent[]} */
|
|
143
|
+
const components = [
|
|
144
|
+
ident(property.decoded, sourceSpelling(property.raw, property.decoded)),
|
|
145
|
+
];
|
|
146
|
+
if (comma !== -1)
|
|
147
|
+
componentTree(input, property.index + 1, close, parseRange, components);
|
|
148
|
+
return opaqueCall(name, components, sourceSpelling(rawName, name));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export { parseOpaqueCall, parseVar };
|