postcss-calc 11.0.2 → 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/package.json +1 -2
- package/src/index.js +99 -57
- package/src/lib/tokenizer.js +18 -4
- package/types/index.d.ts +9 -0
- package/types/lib/tokenizer.d.ts +11 -1
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": [
|
|
@@ -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": {
|
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/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/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 };
|