Skip to content

Commit 4c96c79

Browse files
authored
feat: add types (#155)
* feat: add types * feat: introduce plugin options type * fix: check that firstZeroItem is not undefined
1 parent 133260a commit 4c96c79

14 files changed

+286
-35
lines changed

package.json

+5-2
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,16 @@
1010
"calc"
1111
],
1212
"main": "dist/index.js",
13+
"types": "types/index.d.ts",
1314
"files": [
1415
"dist",
16+
"types",
1517
"LICENSE"
1618
],
1719
"scripts": {
18-
"prepare": "pnpm run build",
20+
"prepare": "pnpm run build && tsc",
1921
"build": "rimraf dist && babel src --out-dir dist --ignore src/__tests__/**/*.js && jison src/parser.jison -o dist/parser.js",
20-
"lint": "eslint src",
22+
"lint": "eslint src && tsc",
2123
"pretest": "pnpm run build",
2224
"test": "uvu -r @babel/register src/__tests__"
2325
},
@@ -44,6 +46,7 @@
4446
"jison-gho": "^0.6.1-216",
4547
"postcss": "^8.2.2",
4648
"rimraf": "^3.0.2",
49+
"typescript": "^4.5.4",
4750
"uvu": "^0.5.2"
4851
},
4952
"dependencies": {

pnpm-lock.yaml

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/index.js

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
import transform from './lib/transform';
22

3+
/**
4+
* @typedef {{precision?: number | false,
5+
* preserve?: boolean,
6+
* warnWhenCannotResolve?: boolean,
7+
* mediaQueries?: boolean,
8+
* selectors?: boolean}} PostCssCalcOptions
9+
*
10+
* @param {PostCssCalcOptions} opts
11+
*/
312
function pluginCreator(opts) {
413
const options = Object.assign({
514
precision: 5,
@@ -11,10 +20,13 @@ function pluginCreator(opts) {
1120

1221
return {
1322
postcssPlugin: 'postcss-calc',
23+
/**
24+
* @param {import('postcss').Root} css
25+
* @param {{result: import('postcss').Result}} helpers
26+
*/
1427
OnceExit(css, { result }) {
1528
css.walk(node => {
1629
const { type } = node;
17-
1830
if (type === 'decl') {
1931
transform(node, "value", options, result);
2032
}

src/lib/convertUnit.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
/**
2+
* @type {{[key:string]: {[key:string]: number}}}
3+
*/
14
const conversions = {
25
// Absolute length units
36
'px': {
@@ -123,7 +126,12 @@ const conversions = {
123126
'dppx': 1
124127
}
125128
};
126-
129+
/**
130+
* @param {number} value
131+
* @param {string} sourceUnit
132+
* @param {string} targetUnit
133+
* @param {number|false} precision
134+
*/
127135
function convertUnit(value, sourceUnit, targetUnit, precision) {
128136
const sourceUnitNormalized = sourceUnit.toLowerCase();
129137
const targetUnitNormalized = targetUnit.toLowerCase();
@@ -139,7 +147,7 @@ function convertUnit(value, sourceUnit, targetUnit, precision) {
139147
const converted = conversions[targetUnitNormalized][sourceUnitNormalized] * value;
140148

141149
if (precision !== false) {
142-
precision = Math.pow(10, parseInt(precision) || 5);
150+
precision = Math.pow(10, Math.ceil(precision) || 5);
143151

144152
return Math.round(converted * precision) / precision;
145153
}

src/lib/reducer.js

+72-22
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import convertUnit from "./convertUnit";
22

3-
function isValueType(type) {
4-
switch (type) {
3+
/**
4+
* @param {import('../parser').CalcNode} node
5+
* @return {node is import('../parser').ValueExpression}
6+
*/
7+
function isValueType(node) {
8+
switch (node.type) {
59
case 'LengthValue':
610
case 'AngleValue':
711
case 'TimeValue':
@@ -22,22 +26,38 @@ function isValueType(type) {
2226
return false;
2327
}
2428

29+
/** @param {'-'|'+'} operator */
2530
function flip(operator) {
2631
return operator === '+' ? '-' : '+';
2732
}
2833

34+
/**
35+
* @param {string} operator
36+
* @returns {operator is '+'|'-'}
37+
*/
2938
function isAddSubOperator(operator) {
3039
return operator === '+' || operator === '-';
3140
}
3241

42+
/**
43+
* @typedef {{preOperator: '+'|'-', node: import('../parser').CalcNode}} Collectible
44+
*/
45+
46+
/**
47+
* @param {'+'|'-'} preOperator
48+
* @param {import('../parser').CalcNode} node
49+
* @param {Collectible[]} collected
50+
* @param {number} precision
51+
*/
3352
function collectAddSubItems(preOperator, node, collected, precision) {
3453
if (!isAddSubOperator(preOperator)) { throw new Error(`invalid operator ${preOperator}`); }
35-
const type = node.type;
36-
if (isValueType(type)) {
37-
const itemIndex = collected.findIndex(x => x.node.type === type);
54+
if (isValueType(node)) {
55+
const itemIndex = collected.findIndex(x => x.node.type === node.type);
3856
if (itemIndex >= 0) {
3957
if (node.value === 0) { return; }
40-
const {left: reducedNode, right: current} = convertNodesUnits(collected[itemIndex].node, node, precision)
58+
// can cast because of the criterion used to find itemIndex
59+
const otherValueNode = /** @type import('../parser').ValueExpression*/(collected[itemIndex].node);
60+
const {left: reducedNode, right: current} = convertNodesUnits(otherValueNode, node, precision)
4161

4262
if (collected[itemIndex].preOperator === '-') {
4363
collected[itemIndex].preOperator = '+';
@@ -64,7 +84,7 @@ function collectAddSubItems(preOperator, node, collected, precision) {
6484
collected.push({node, preOperator: flip(preOperator)});
6585
}
6686
}
67-
} else if (type === "MathExpression") {
87+
} else if (node.type === "MathExpression") {
6888
if (isAddSubOperator(node.operator)) {
6989
collectAddSubItems(preOperator, node.left, collected, precision);
7090
const collectRightOperator = preOperator === '-' ? flip(node.operator) : node.operator;
@@ -85,27 +105,33 @@ function collectAddSubItems(preOperator, node, collected, precision) {
85105
}
86106
}
87107

88-
108+
/**
109+
* @param {import('../parser').CalcNode} node
110+
* @param {number} precision
111+
*/
89112
function reduceAddSubExpression(node, precision) {
113+
/** @type Collectible[] */
90114
const collected = [];
91115
collectAddSubItems('+', node, collected, precision);
92116

93-
const withoutZeroItem = collected.filter((item) => !(isValueType(item.node.type) && item.node.value === 0));
117+
const withoutZeroItem = collected.filter((item) => !(isValueType(item.node) && item.node.value === 0));
94118
const firstNonZeroItem = withoutZeroItem[0]; // could be undefined
95119

96120
// prevent producing "calc(-var(--a))" or "calc()"
97121
// which is invalid css
98122
if (!firstNonZeroItem ||
99123
firstNonZeroItem.preOperator === '-' &&
100-
!isValueType(firstNonZeroItem.node.type)) {
124+
!isValueType(firstNonZeroItem.node)) {
101125
const firstZeroItem = collected.find((item) =>
102-
isValueType(item.node.type) && item.node.value === 0);
103-
withoutZeroItem.unshift(firstZeroItem)
126+
isValueType(item.node) && item.node.value === 0);
127+
if (firstZeroItem) {
128+
withoutZeroItem.unshift(firstZeroItem)
129+
}
104130
}
105131

106132
// make sure the preOperator of the first item is +
107133
if (withoutZeroItem[0].preOperator === '-' &&
108-
isValueType(withoutZeroItem[0].node.type)) {
134+
isValueType(withoutZeroItem[0].node)) {
109135
withoutZeroItem[0].node.value *= -1;
110136
withoutZeroItem[0].preOperator = '+';
111137
}
@@ -122,9 +148,11 @@ function reduceAddSubExpression(node, precision) {
122148

123149
return root;
124150
}
125-
151+
/**
152+
* @param {import('../parser').MathExpression} node
153+
*/
126154
function reduceDivisionExpression(node) {
127-
if (!isValueType(node.right.type)) {
155+
if (!isValueType(node.right)) {
128156
return node;
129157
}
130158

@@ -135,12 +163,18 @@ function reduceDivisionExpression(node) {
135163
return applyNumberDivision(node.left, node.right.value)
136164
}
137165

138-
// apply (expr) / number
166+
/**
167+
* apply (expr) / number
168+
*
169+
* @param {import('../parser').CalcNode} node
170+
* @param {number} divisor
171+
* @return {import('../parser').CalcNode}
172+
*/
139173
function applyNumberDivision(node, divisor) {
140174
if (divisor === 0) {
141175
throw new Error('Cannot divide by zero');
142176
}
143-
if (isValueType(node.type)) {
177+
if (isValueType(node)) {
144178
node.value /= divisor;
145179
return node;
146180
}
@@ -169,7 +203,9 @@ function applyNumberDivision(node, divisor) {
169203
}
170204
}
171205
}
172-
206+
/**
207+
* @param {import('../parser').MathExpression} node
208+
*/
173209
function reduceMultiplicationExpression(node) {
174210
// (expr) * number
175211
if (node.right.type === 'Number') {
@@ -182,9 +218,14 @@ function reduceMultiplicationExpression(node) {
182218
return node;
183219
}
184220

185-
// apply (expr) / number
221+
/**
222+
* apply (expr) * number
223+
* @param {number} multiplier
224+
* @param {import('../parser').CalcNode} node
225+
* @return {import('../parser').CalcNode}
226+
*/
186227
function applyNumberMultiplication(node, multiplier) {
187-
if (isValueType(node.type)) {
228+
if (isValueType(node)) {
188229
node.value *= multiplier;
189230
return node;
190231
}
@@ -214,6 +255,11 @@ function applyNumberMultiplication(node, multiplier) {
214255
}
215256
}
216257

258+
/**
259+
* @param {import('../parser').ValueExpression} left
260+
* @param {import('../parser').ValueExpression} right
261+
* @param {number} precision
262+
*/
217263
function convertNodesUnits(left, right, precision) {
218264
switch (left.type) {
219265
case 'LengthValue':
@@ -237,6 +283,10 @@ function convertNodesUnits(left, right, precision) {
237283
}
238284
}
239285

286+
/**
287+
* @param {import('../parser').CalcNode} node
288+
* @param {number} precision
289+
*/
240290
function reduce(node, precision) {
241291
if (node.type === "MathExpression") {
242292
if (isAddSubOperator(node.operator)) {
@@ -247,9 +297,9 @@ function reduce(node, precision) {
247297
node.right = reduce(node.right, precision);
248298
switch (node.operator) {
249299
case "/":
250-
return reduceDivisionExpression(node, precision);
300+
return reduceDivisionExpression(node);
251301
case "*":
252-
return reduceMultiplicationExpression(node, precision);
302+
return reduceMultiplicationExpression(node);
253303
}
254304

255305
return node;

src/lib/stringifier.js

+20-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ const order = {
55
"-": 1,
66
};
77

8+
/**
9+
* @param {number} value
10+
* @param {number | false} prec
11+
*/
812
function round(value, prec) {
913
if (prec !== false) {
1014
const precision = Math.pow(10, prec);
@@ -13,12 +17,15 @@ function round(value, prec) {
1317
return value;
1418
}
1519

20+
/**
21+
* @param {number | false} prec
22+
* @param {import('../parser').CalcNode} node
23+
*/
1624
function stringify(node, prec) {
1725
switch (node.type) {
1826
case "MathExpression": {
1927
const {left, right, operator: op} = node;
2028
let str = "";
21-
2229
if (left.type === 'MathExpression' && order[op] < order[left.operator]) {
2330
str += `(${stringify(left, prec)})`;
2431
} else {
@@ -36,14 +43,24 @@ function stringify(node, prec) {
3643
return str;
3744
}
3845
case 'Number':
39-
return round(node.value, prec);
46+
return round(node.value, prec).toString();
4047
case 'Function':
41-
return node.value;
48+
return node.value.toString();
4249
default:
4350
return round(node.value, prec) + node.unit;
4451
}
4552
}
4653

54+
/**
55+
* @param {string} calc
56+
* @param {import('../parser').CalcNode} node
57+
* @param {string} originalValue
58+
* @param {{precision: number | false, warnWhenCannotResolve: boolean}} options
59+
* @param {import("postcss").Result} result
60+
* @param {import("postcss").ChildNode} item
61+
*
62+
* @returns {string}
63+
*/
4764
export default function (
4865
calc,
4966
node,

0 commit comments

Comments
 (0)