postcss-calc 11.0.1 → 11.0.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 +2 -0
- package/package.json +5 -5
- package/src/index.js +99 -57
- package/src/lib/parser.js +63 -64
- package/src/lib/tokenizer.js +18 -4
- package/types/index.d.ts +9 -0
- package/types/lib/parser.d.ts +0 -4
- package/types/lib/tokenizer.d.ts +11 -1
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "postcss-calc",
|
|
3
|
-
"version": "11.0.
|
|
3
|
+
"version": "11.0.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "PostCSS plugin to reduce calc()",
|
|
6
6
|
"keywords": [
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"homepage": "https://github.com/postcss/postcss-calc",
|
|
14
14
|
"repository": {
|
|
15
15
|
"type": "git",
|
|
16
|
-
"url": "https://github.com/postcss/postcss-calc.git"
|
|
16
|
+
"url": "git+https://github.com/postcss/postcss-calc.git"
|
|
17
17
|
},
|
|
18
18
|
"exports": {
|
|
19
19
|
".": {
|
|
@@ -34,13 +34,13 @@
|
|
|
34
34
|
"devEngines": {
|
|
35
35
|
"packageManager": {
|
|
36
36
|
"name": "pnpm",
|
|
37
|
-
"version": "11.
|
|
37
|
+
"version": "11.25.0"
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@csstools/css-calc": "^3.3.0",
|
|
42
42
|
"@rmenke/css-tokenizer-tests": "^1.2.0",
|
|
43
|
-
"@types/node": "^26.
|
|
43
|
+
"@types/node": "^26.4.0",
|
|
44
44
|
"fast-check": "^4.9.0",
|
|
45
45
|
"oxfmt": "^0.65.0",
|
|
46
46
|
"oxlint": "^1.80.0",
|
|
@@ -48,7 +48,6 @@
|
|
|
48
48
|
"typescript": "~7.0.2"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@csstools/css-parser-algorithms": "^4.0.0",
|
|
52
51
|
"@csstools/css-tokenizer": "^4.0.0"
|
|
53
52
|
},
|
|
54
53
|
"peerDependencies": {
|
|
@@ -57,6 +56,7 @@
|
|
|
57
56
|
"scripts": {
|
|
58
57
|
"lint": "oxlint . && tsc && oxfmt --check",
|
|
59
58
|
"fmt": "oxfmt",
|
|
59
|
+
"benchmark:arithmetic-chains": "node scripts/benchmark-arithmetic-chains.mjs",
|
|
60
60
|
"test": "node --test --test-reporter=dot 'test/**/*.test.mjs' test/index.cjs test/convertUnit.cjs",
|
|
61
61
|
"test:mutation:corpus": "node test/mutation/corpus-selection.mjs",
|
|
62
62
|
"test:corpus:full": "POSTCSS_CALC_FULL_CORPUS=1 node --test test/conformance/corpus.test.mjs"
|
package/src/index.js
CHANGED
|
@@ -1,27 +1,24 @@
|
|
|
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 { tokenize as cssTokenize } from '@csstools/css-tokenizer';
|
|
5
4
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from '
|
|
10
|
-
import { tokenize } from './lib/tokenizer.js';
|
|
5
|
+
tokenize as cssTokenize,
|
|
6
|
+
TokenType as CssType,
|
|
7
|
+
} from '@csstools/css-tokenizer';
|
|
8
|
+
import { tokenizeTokens } from './lib/tokenizer.js';
|
|
11
9
|
import { parse } from './lib/parser.js';
|
|
12
10
|
import { simplify } from './lib/simplify.js';
|
|
13
11
|
import { isSupportedMathFunction } from './lib/simplify/call.js';
|
|
14
12
|
import { serialize } from './lib/serialize.js';
|
|
15
13
|
|
|
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: () => {} };
|
|
22
|
-
|
|
23
14
|
const MATCH_CALC = /^(?:-(?:moz|webkit)-)?calc$/i;
|
|
24
15
|
|
|
16
|
+
const BLOCK_CLOSE = new Map([
|
|
17
|
+
[CssType.OpenParen, CssType.CloseParen],
|
|
18
|
+
[CssType.OpenSquare, CssType.CloseSquare],
|
|
19
|
+
[CssType.OpenCurly, CssType.CloseCurly],
|
|
20
|
+
]);
|
|
21
|
+
|
|
25
22
|
/**
|
|
26
23
|
* @typedef {object} PostCssCalcOptions
|
|
27
24
|
* @property {number | false} [precision]
|
|
@@ -34,7 +31,7 @@ const MATCH_CALC = /^(?:-(?:moz|webkit)-)?calc$/i;
|
|
|
34
31
|
/** @typedef {Required<Omit<PostCssCalcOptions, 'onParseError'>> & Pick<PostCssCalcOptions, 'onParseError'>} ResolvedOptions */
|
|
35
32
|
|
|
36
33
|
/**
|
|
37
|
-
* Fields threaded unchanged through the
|
|
34
|
+
* Fields threaded unchanged through the token-range walk.
|
|
38
35
|
* `value` is the original full property text, used only for the
|
|
39
36
|
* warnWhenCannotResolve message.
|
|
40
37
|
*
|
|
@@ -43,59 +40,76 @@ const MATCH_CALC = /^(?:-(?:moz|webkit)-)?calc$/i;
|
|
|
43
40
|
* @property {import('postcss').Result} result
|
|
44
41
|
* @property {import('postcss').ChildNode} item
|
|
45
42
|
* @property {string} value
|
|
43
|
+
* @property {import('@csstools/css-tokenizer').CSSToken[]} tokens
|
|
44
|
+
* @property {Replacement[]} replacements
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @typedef {object} Replacement
|
|
49
|
+
* @property {number} start
|
|
50
|
+
* @property {number} end
|
|
51
|
+
* @property {import('./lib/node.js').Node} node
|
|
52
|
+
* @property {string} calcName
|
|
53
|
+
* @property {string} matchedName
|
|
46
54
|
*/
|
|
47
55
|
|
|
48
56
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
57
|
+
* Walk one component-value level. Unsupported functions and simple blocks are
|
|
58
|
+
* traversed, while a supported function is treated as one opaque calculation
|
|
59
|
+
* even when parsing it fails. A missing closer consumes through EOF, matching
|
|
60
|
+
* CSS component-value parsing's error recovery.
|
|
53
61
|
*
|
|
54
|
-
* @param {
|
|
62
|
+
* @param {number} start
|
|
63
|
+
* @param {import('@csstools/css-tokenizer').TokenType | undefined} expectedClose
|
|
55
64
|
* @param {TransformContext} ctx
|
|
56
|
-
* @
|
|
65
|
+
* @param {boolean} transform
|
|
66
|
+
* @return {number} Index of the matching closer, or the EOF token.
|
|
57
67
|
*/
|
|
58
|
-
function
|
|
59
|
-
for (let i =
|
|
60
|
-
const
|
|
61
|
-
if (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
function walkTokens(start, expectedClose, ctx, transform) {
|
|
69
|
+
for (let i = start; i < ctx.tokens.length; i++) {
|
|
70
|
+
const token = ctx.tokens[i];
|
|
71
|
+
if (token[0] === CssType.EOF || token[0] === expectedClose) {
|
|
72
|
+
return i;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const blockClose = BLOCK_CLOSE.get(token[0]);
|
|
76
|
+
if (blockClose) {
|
|
77
|
+
i = walkTokens(i + 1, blockClose, ctx, transform);
|
|
65
78
|
continue;
|
|
66
79
|
}
|
|
67
80
|
|
|
68
|
-
|
|
81
|
+
if (token[0] !== CssType.Function) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const name = token[4].value;
|
|
69
86
|
const isCalc = MATCH_CALC.test(name);
|
|
70
87
|
const isMath = !isCalc && isSupportedMathFunction(name);
|
|
71
|
-
if (!isCalc && !isMath) {
|
|
72
|
-
|
|
88
|
+
if (!transform || (!isCalc && !isMath)) {
|
|
89
|
+
i = walkTokens(i + 1, CssType.CloseParen, ctx, transform);
|
|
73
90
|
continue;
|
|
74
91
|
}
|
|
75
92
|
|
|
76
|
-
//
|
|
77
|
-
const
|
|
78
|
-
const
|
|
93
|
+
// Locate the complete outer function without transforming its children.
|
|
94
|
+
const close = walkTokens(i + 1, CssType.CloseParen, ctx, false);
|
|
95
|
+
const closed = ctx.tokens[close][0] === CssType.CloseParen;
|
|
96
|
+
const end = closed ? ctx.tokens[close][3] + 1 : ctx.value.length;
|
|
97
|
+
const sliceStart = isCalc ? i + 1 : i;
|
|
98
|
+
const sliceEnd = closed ? close + (isCalc ? 0 : 1) : close;
|
|
99
|
+
const inputStart = isCalc ? token[3] + 1 : token[2];
|
|
100
|
+
const inputEnd = closed && isCalc ? ctx.tokens[close][2] : end;
|
|
101
|
+
const contents = ctx.value.slice(inputStart, inputEnd);
|
|
79
102
|
try {
|
|
80
|
-
const
|
|
81
|
-
|
|
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
|
|
103
|
+
const node = simplify(
|
|
104
|
+
parse(tokenizeTokens(ctx.tokens.slice(sliceStart, sliceEnd), end))
|
|
96
105
|
);
|
|
97
|
-
|
|
98
|
-
|
|
106
|
+
ctx.replacements.push({
|
|
107
|
+
start: token[2],
|
|
108
|
+
end,
|
|
109
|
+
node,
|
|
110
|
+
calcName: isCalc ? name : 'calc',
|
|
111
|
+
matchedName: name,
|
|
112
|
+
});
|
|
99
113
|
} catch (error) {
|
|
100
114
|
const err = error instanceof Error ? error : new Error('Error');
|
|
101
115
|
if (ctx.options.onParseError) {
|
|
@@ -104,7 +118,10 @@ function transformList(list, ctx) {
|
|
|
104
118
|
ctx.result.warn(err.message, { node: ctx.item });
|
|
105
119
|
}
|
|
106
120
|
}
|
|
121
|
+
i = close;
|
|
107
122
|
}
|
|
123
|
+
|
|
124
|
+
return ctx.tokens.length - 1;
|
|
108
125
|
}
|
|
109
126
|
|
|
110
127
|
/**
|
|
@@ -115,14 +132,39 @@ function transformList(list, ctx) {
|
|
|
115
132
|
* @return {string}
|
|
116
133
|
*/
|
|
117
134
|
function transformValue(value, options, result, item) {
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
135
|
+
const tokens = cssTokenize({ css: value });
|
|
136
|
+
/** @type {Replacement[]} */
|
|
137
|
+
const replacements = [];
|
|
138
|
+
const ctx = { options, result, item, value, tokens, replacements };
|
|
139
|
+
walkTokens(0, undefined, ctx, true);
|
|
122
140
|
|
|
123
|
-
|
|
141
|
+
/** @type {(Replacement & {text: string})[]} */
|
|
142
|
+
const serialized = replacements.map((replacement) => {
|
|
143
|
+
const text = serialize(replacement.node, {
|
|
144
|
+
precision: options.precision,
|
|
145
|
+
calcName: replacement.calcName,
|
|
146
|
+
});
|
|
147
|
+
if (
|
|
148
|
+
options.warnWhenCannotResolve &&
|
|
149
|
+
text.startsWith(`${replacement.matchedName}(`)
|
|
150
|
+
) {
|
|
151
|
+
result.warn('Could not reduce expression: ' + value, {
|
|
152
|
+
plugin: 'postcss-calc',
|
|
153
|
+
node: item,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
return { ...replacement, text };
|
|
157
|
+
});
|
|
124
158
|
|
|
125
|
-
|
|
159
|
+
let output = value;
|
|
160
|
+
for (let i = serialized.length - 1; i >= 0; i--) {
|
|
161
|
+
const replacement = serialized[i];
|
|
162
|
+
output =
|
|
163
|
+
output.slice(0, replacement.start) +
|
|
164
|
+
replacement.text +
|
|
165
|
+
output.slice(replacement.end);
|
|
166
|
+
}
|
|
167
|
+
return output;
|
|
126
168
|
}
|
|
127
169
|
|
|
128
170
|
/**
|
package/src/lib/parser.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
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, ident, call } from './node.js';
|
|
4
|
+
import { mkSum, mkProduct, negate, num, dim, ident, call } from './node.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @typedef {import('./tokenizer.js').Token} Token
|
|
8
8
|
* @typedef {import('./tokenizer.js').TokenType} TokenType
|
|
9
9
|
* @typedef {import('./node.js').Node} Node
|
|
10
10
|
* @typedef {(p: Parser, token: Token) => Node} PrefixParselet
|
|
11
|
-
* @typedef {{lbp: number, parse: (p: Parser, left: Node, token: Token) => Node}} InfixParselet
|
|
12
11
|
*/
|
|
13
12
|
|
|
14
13
|
/**
|
|
@@ -30,17 +29,17 @@ function foldCalcKeyword(name) {
|
|
|
30
29
|
// form arrives as a single ident because CSS Syntax tokenizes leading
|
|
31
30
|
// `-` + ident-start as one ident-token.
|
|
32
31
|
if (name === 'NaN' || name === '-NaN') {
|
|
33
|
-
return
|
|
32
|
+
return num(Number.NaN);
|
|
34
33
|
}
|
|
35
34
|
switch (name.toLowerCase()) {
|
|
36
35
|
case 'pi':
|
|
37
|
-
return
|
|
36
|
+
return num(Math.PI);
|
|
38
37
|
case 'e':
|
|
39
|
-
return
|
|
38
|
+
return num(Math.E);
|
|
40
39
|
case 'infinity':
|
|
41
|
-
return
|
|
40
|
+
return num(Infinity);
|
|
42
41
|
case '-infinity':
|
|
43
|
-
return
|
|
42
|
+
return num(-Infinity);
|
|
44
43
|
}
|
|
45
44
|
return null;
|
|
46
45
|
}
|
|
@@ -102,40 +101,58 @@ class Parser {
|
|
|
102
101
|
if (!rule || rule.lbp < minBp) {
|
|
103
102
|
break;
|
|
104
103
|
}
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
if (infixKey === '+' || infixKey === '-') {
|
|
105
|
+
/** @type {import('./node.js').SumTerm[]} */
|
|
106
|
+
const terms = [{ sign: /** @type {1} */ (1), node: left }];
|
|
107
|
+
do {
|
|
108
|
+
const token = this.next();
|
|
109
|
+
requireSurroundingWs(this, token);
|
|
110
|
+
terms.push({
|
|
111
|
+
sign: /** @type {1 | -1} */ (token.value === '+' ? 1 : -1),
|
|
112
|
+
node: this.parseExpr(ADD_BP + 1),
|
|
113
|
+
});
|
|
114
|
+
const next = this.peek();
|
|
115
|
+
if (
|
|
116
|
+
next.type !== 'punct' ||
|
|
117
|
+
(next.value !== '+' && next.value !== '-')
|
|
118
|
+
) {
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
} while (ADD_BP >= minBp);
|
|
122
|
+
left = mkSum(terms);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (infixKey === '*' || infixKey === '/') {
|
|
127
|
+
/** @type {import('./node.js').ProductFactor[]} */
|
|
128
|
+
const factors = [{ exponent: /** @type {1} */ (1), node: left }];
|
|
129
|
+
do {
|
|
130
|
+
const token = this.next();
|
|
131
|
+
factors.push({
|
|
132
|
+
exponent: /** @type {1 | -1} */ (token.value === '*' ? 1 : -1),
|
|
133
|
+
node: this.parseExpr(MUL_BP + 1),
|
|
134
|
+
});
|
|
135
|
+
const next = this.peek();
|
|
136
|
+
if (
|
|
137
|
+
next.type !== 'punct' ||
|
|
138
|
+
(next.value !== '*' && next.value !== '/')
|
|
139
|
+
) {
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
} while (MUL_BP >= minBp);
|
|
143
|
+
left = mkProduct(factors);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Every infix operator is handled above. Keep this defensive exit in
|
|
148
|
+
// case a future parselet is added without a chain implementation.
|
|
149
|
+
break;
|
|
107
150
|
}
|
|
108
151
|
|
|
109
152
|
return left;
|
|
110
153
|
}
|
|
111
154
|
}
|
|
112
155
|
|
|
113
|
-
/**
|
|
114
|
-
* @param {Node} left
|
|
115
|
-
* @param {Node} right
|
|
116
|
-
* @param {1 | -1} rightSign
|
|
117
|
-
* @return {Node}
|
|
118
|
-
*/
|
|
119
|
-
function addTerm(left, right, rightSign) {
|
|
120
|
-
return mkSum([
|
|
121
|
-
{ sign: 1, node: left },
|
|
122
|
-
{ sign: rightSign, node: right },
|
|
123
|
-
]);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* @param {Node} left
|
|
128
|
-
* @param {Node} right
|
|
129
|
-
* @param {1 | -1} rightExp
|
|
130
|
-
* @return {Node}
|
|
131
|
-
*/
|
|
132
|
-
function mulFactor(left, right, rightExp) {
|
|
133
|
-
return mkProduct([
|
|
134
|
-
{ exponent: 1, node: left },
|
|
135
|
-
{ exponent: rightExp, node: right },
|
|
136
|
-
]);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
156
|
const ADD_BP = 1;
|
|
140
157
|
const MUL_BP = 3;
|
|
141
158
|
const UNARY_BP = 7;
|
|
@@ -228,14 +245,14 @@ function requireSurroundingWs(p, token) {
|
|
|
228
245
|
|
|
229
246
|
/** @type {Record<string, PrefixParselet>} */
|
|
230
247
|
const PREFIX = {
|
|
231
|
-
number: (_p, t) => (
|
|
248
|
+
number: (_p, t) => num(Number.parseFloat(t.value)),
|
|
232
249
|
|
|
233
250
|
// Unit case normalization per §10.12: `1PX` serializes as `1px`.
|
|
234
|
-
dimension: (_p, t) =>
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
251
|
+
dimension: (_p, t) =>
|
|
252
|
+
dim(
|
|
253
|
+
Number.parseFloat(t.value),
|
|
254
|
+
t.unit === '%' ? '%' : /** @type {string} */ (t.unit).toLowerCase()
|
|
255
|
+
),
|
|
239
256
|
|
|
240
257
|
ident: (p, t) => {
|
|
241
258
|
const nxt = p.peek();
|
|
@@ -273,30 +290,12 @@ const PREFIX = {
|
|
|
273
290
|
'+': (p) => p.parseExpr(UNARY_BP),
|
|
274
291
|
};
|
|
275
292
|
|
|
276
|
-
/** @type {Record<string,
|
|
293
|
+
/** @type {Record<string, {lbp: number}>} */
|
|
277
294
|
const INFIX = {
|
|
278
|
-
'+': {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
return addTerm(left, p.parseExpr(ADD_BP + 1), 1);
|
|
283
|
-
},
|
|
284
|
-
},
|
|
285
|
-
'-': {
|
|
286
|
-
lbp: ADD_BP,
|
|
287
|
-
parse: (p, left, token) => {
|
|
288
|
-
requireSurroundingWs(p, token);
|
|
289
|
-
return addTerm(left, p.parseExpr(ADD_BP + 1), -1);
|
|
290
|
-
},
|
|
291
|
-
},
|
|
292
|
-
'*': {
|
|
293
|
-
lbp: MUL_BP,
|
|
294
|
-
parse: (p, left) => mulFactor(left, p.parseExpr(MUL_BP + 1), 1),
|
|
295
|
-
},
|
|
296
|
-
'/': {
|
|
297
|
-
lbp: MUL_BP,
|
|
298
|
-
parse: (p, left) => mulFactor(left, p.parseExpr(MUL_BP + 1), -1),
|
|
299
|
-
},
|
|
295
|
+
'+': { lbp: ADD_BP },
|
|
296
|
+
'-': { lbp: ADD_BP },
|
|
297
|
+
'*': { lbp: MUL_BP },
|
|
298
|
+
'/': { lbp: MUL_BP },
|
|
300
299
|
};
|
|
301
300
|
|
|
302
301
|
/**
|
package/src/lib/tokenizer.js
CHANGED
|
@@ -23,6 +23,19 @@ const NUMERIC_RAW = /^[+-]?(?:\d+(?:\.\d+)?|\.\d+)(?:[eE][+-]?\d+)?/;
|
|
|
23
23
|
* @return {Token[]}
|
|
24
24
|
*/
|
|
25
25
|
function tokenize(input) {
|
|
26
|
+
return tokenizeTokens(tokenizeCss({ css: input }), input.length);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Convert a slice of an existing CSS token stream into the token subset used
|
|
31
|
+
* by the calculation parser. Token positions remain relative to the original
|
|
32
|
+
* source text, which keeps parse errors useful to adapter callers.
|
|
33
|
+
*
|
|
34
|
+
* @param {import('@csstools/css-tokenizer').CSSToken[]} cssTokens
|
|
35
|
+
* @param {number} eofPosition
|
|
36
|
+
* @return {Token[]}
|
|
37
|
+
*/
|
|
38
|
+
function tokenizeTokens(cssTokens, eofPosition) {
|
|
26
39
|
/** @type {Token[]} */
|
|
27
40
|
const tokens = [];
|
|
28
41
|
let ws = true;
|
|
@@ -52,7 +65,7 @@ function tokenize(input) {
|
|
|
52
65
|
ws = false;
|
|
53
66
|
}
|
|
54
67
|
|
|
55
|
-
for (const t of
|
|
68
|
+
for (const t of cssTokens) {
|
|
56
69
|
switch (t[0]) {
|
|
57
70
|
case CssType.Whitespace:
|
|
58
71
|
case CssType.Comment:
|
|
@@ -97,8 +110,7 @@ function tokenize(input) {
|
|
|
97
110
|
tokens.push({ type: 'punct', value: t[4].value, pos: t[2], ws });
|
|
98
111
|
break;
|
|
99
112
|
case CssType.EOF:
|
|
100
|
-
|
|
101
|
-
break;
|
|
113
|
+
continue;
|
|
102
114
|
default:
|
|
103
115
|
throw new Error(
|
|
104
116
|
`Unexpected character "${t[1][0] ?? ''}" at position ${t[2]}`
|
|
@@ -107,7 +119,9 @@ function tokenize(input) {
|
|
|
107
119
|
ws = false;
|
|
108
120
|
}
|
|
109
121
|
|
|
122
|
+
tokens.push({ type: 'eof', value: '', pos: eofPosition, ws });
|
|
123
|
+
|
|
110
124
|
return tokens;
|
|
111
125
|
}
|
|
112
126
|
|
|
113
|
-
export { tokenize };
|
|
127
|
+
export { tokenize, tokenizeTokens };
|
package/types/index.d.ts
CHANGED
|
@@ -14,6 +14,15 @@ export type TransformContext = {
|
|
|
14
14
|
result: import('postcss').Result;
|
|
15
15
|
item: import('postcss').ChildNode;
|
|
16
16
|
value: string;
|
|
17
|
+
tokens: import('@csstools/css-tokenizer').CSSToken[];
|
|
18
|
+
replacements: Replacement[];
|
|
19
|
+
};
|
|
20
|
+
export type Replacement = {
|
|
21
|
+
start: number;
|
|
22
|
+
end: number;
|
|
23
|
+
node: import('./lib/node.js').Node;
|
|
24
|
+
calcName: string;
|
|
25
|
+
matchedName: string;
|
|
17
26
|
};
|
|
18
27
|
/**
|
|
19
28
|
* @param {PostCssCalcOptions} [opts]
|
package/types/lib/parser.d.ts
CHANGED
|
@@ -2,10 +2,6 @@ export type Token = import('./tokenizer.js').Token;
|
|
|
2
2
|
export type TokenType = import('./tokenizer.js').TokenType;
|
|
3
3
|
export type Node = import('./node.js').Node;
|
|
4
4
|
export type PrefixParselet = (p: Parser, token: Token) => Node;
|
|
5
|
-
export type InfixParselet = {
|
|
6
|
-
lbp: number;
|
|
7
|
-
parse: (p: Parser, left: Node, token: Token) => Node;
|
|
8
|
-
};
|
|
9
5
|
declare class Parser {
|
|
10
6
|
/** @private */
|
|
11
7
|
i;
|
package/types/lib/tokenizer.d.ts
CHANGED
|
@@ -17,4 +17,14 @@ export type Token = {
|
|
|
17
17
|
* @return {Token[]}
|
|
18
18
|
*/
|
|
19
19
|
declare function tokenize(input: string): Token[];
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Convert a slice of an existing CSS token stream into the token subset used
|
|
22
|
+
* by the calculation parser. Token positions remain relative to the original
|
|
23
|
+
* source text, which keeps parse errors useful to adapter callers.
|
|
24
|
+
*
|
|
25
|
+
* @param {import('@csstools/css-tokenizer').CSSToken[]} cssTokens
|
|
26
|
+
* @param {number} eofPosition
|
|
27
|
+
* @return {Token[]}
|
|
28
|
+
*/
|
|
29
|
+
declare function tokenizeTokens(cssTokens: import('@csstools/css-tokenizer').CSSToken[], eofPosition: number): Token[];
|
|
30
|
+
export { tokenize, tokenizeTokens };
|