Skip to content

Commit

Permalink
feat: add types
Browse files Browse the repository at this point in the history
  • Loading branch information
ludofischer committed Jan 4, 2022
1 parent 133260a commit 30ab635
Show file tree
Hide file tree
Showing 14 changed files with 279 additions and 35 deletions.
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@
"calc"
],
"main": "dist/index.js",
"types": "types/index.d.ts",
"files": [
"dist",
"types",
"LICENSE"
],
"scripts": {
"prepare": "pnpm run build",
"prepare": "pnpm run build && tsc",
"build": "rimraf dist && babel src --out-dir dist --ignore src/__tests__/**/*.js && jison src/parser.jison -o dist/parser.js",
"lint": "eslint src",
"lint": "eslint src && tsc",
"pretest": "pnpm run build",
"test": "uvu -r @babel/register src/__tests__"
},
Expand All @@ -44,6 +46,7 @@
"jison-gho": "^0.6.1-216",
"postcss": "^8.2.2",
"rimraf": "^3.0.2",
"typescript": "^4.5.4",
"uvu": "^0.5.2"
},
"dependencies": {
Expand Down
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 11 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import transform from './lib/transform';

/**
* @param {{precision?: number | false,
* preserve?: boolean,
* warnWhenCannotResolve?: boolean,
* mediaQueries?: boolean,
* selectors?: boolean}} opts
*/
function pluginCreator(opts) {
const options = Object.assign({
precision: 5,
Expand All @@ -11,10 +18,13 @@ function pluginCreator(opts) {

return {
postcssPlugin: 'postcss-calc',
/**
* @param {import('postcss').Root} css
* @param {{result: import('postcss').Result}} helpers
*/
OnceExit(css, { result }) {
css.walk(node => {
const { type } = node;

if (type === 'decl') {
transform(node, "value", options, result);
}
Expand Down
12 changes: 10 additions & 2 deletions src/lib/convertUnit.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/**
* @type {{[key:string]: {[key:string]: number}}}
*/
const conversions = {
// Absolute length units
'px': {
Expand Down Expand Up @@ -123,7 +126,12 @@ const conversions = {
'dppx': 1
}
};

/**
* @param {number} value
* @param {string} sourceUnit
* @param {string} targetUnit
* @param {number|false} precision
*/
function convertUnit(value, sourceUnit, targetUnit, precision) {
const sourceUnitNormalized = sourceUnit.toLowerCase();
const targetUnitNormalized = targetUnit.toLowerCase();
Expand All @@ -139,7 +147,7 @@ function convertUnit(value, sourceUnit, targetUnit, precision) {
const converted = conversions[targetUnitNormalized][sourceUnitNormalized] * value;

if (precision !== false) {
precision = Math.pow(10, parseInt(precision) || 5);
precision = Math.pow(10, Math.ceil(precision) || 5);

return Math.round(converted * precision) / precision;
}
Expand Down
92 changes: 70 additions & 22 deletions src/lib/reducer.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import convertUnit from "./convertUnit";

function isValueType(type) {
switch (type) {
/**
* @param {import('../parser').CalcNode} node
* @return {node is import('../parser').ValueExpression}
*/
function isValueType(node) {
switch (node.type) {
case 'LengthValue':
case 'AngleValue':
case 'TimeValue':
Expand All @@ -22,22 +26,38 @@ function isValueType(type) {
return false;
}

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

/**
* @param {string} operator
* @returns {operator is '+'|'-'}
*/
function isAddSubOperator(operator) {
return operator === '+' || operator === '-';
}

/**
* @typedef {{preOperator: '+'|'-', node: import('../parser').CalcNode}} Collectible
*/

/**
* @param {'+'|'-'} preOperator
* @param {import('../parser').CalcNode} node
* @param {Collectible[]} collected
* @param {number} precision
*/
function collectAddSubItems(preOperator, node, collected, precision) {
if (!isAddSubOperator(preOperator)) { throw new Error(`invalid operator ${preOperator}`); }
const type = node.type;
if (isValueType(type)) {
const itemIndex = collected.findIndex(x => x.node.type === type);
if (isValueType(node)) {
const itemIndex = collected.findIndex(x => x.node.type === node.type);
if (itemIndex >= 0) {
if (node.value === 0) { return; }
const {left: reducedNode, right: current} = convertNodesUnits(collected[itemIndex].node, node, precision)
// can cast because of the criterion used to find itemIndex
const otherValueNode = /** @type import('../parser').ValueExpression*/(collected[itemIndex].node);
const {left: reducedNode, right: current} = convertNodesUnits(otherValueNode, node, precision)

if (collected[itemIndex].preOperator === '-') {
collected[itemIndex].preOperator = '+';
Expand All @@ -64,7 +84,7 @@ function collectAddSubItems(preOperator, node, collected, precision) {
collected.push({node, preOperator: flip(preOperator)});
}
}
} else if (type === "MathExpression") {
} else if (node.type === "MathExpression") {
if (isAddSubOperator(node.operator)) {
collectAddSubItems(preOperator, node.left, collected, precision);
const collectRightOperator = preOperator === '-' ? flip(node.operator) : node.operator;
Expand All @@ -85,27 +105,31 @@ function collectAddSubItems(preOperator, node, collected, precision) {
}
}


/**
* @param {import('../parser').CalcNode} node
* @param {number} precision
*/
function reduceAddSubExpression(node, precision) {
/** @type Collectible[] */
const collected = [];
collectAddSubItems('+', node, collected, precision);

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

// prevent producing "calc(-var(--a))" or "calc()"
// which is invalid css
if (!firstNonZeroItem ||
firstNonZeroItem.preOperator === '-' &&
!isValueType(firstNonZeroItem.node.type)) {
!isValueType(firstNonZeroItem.node)) {
const firstZeroItem = collected.find((item) =>
isValueType(item.node.type) && item.node.value === 0);
withoutZeroItem.unshift(firstZeroItem)
isValueType(item.node) && item.node.value === 0);
withoutZeroItem.unshift(/** @type Collectible*/(firstZeroItem))
}

// make sure the preOperator of the first item is +
if (withoutZeroItem[0].preOperator === '-' &&
isValueType(withoutZeroItem[0].node.type)) {
isValueType(withoutZeroItem[0].node)) {
withoutZeroItem[0].node.value *= -1;
withoutZeroItem[0].preOperator = '+';
}
Expand All @@ -122,9 +146,11 @@ function reduceAddSubExpression(node, precision) {

return root;
}

/**
* @param {import('../parser').MathExpression} node
*/
function reduceDivisionExpression(node) {
if (!isValueType(node.right.type)) {
if (!isValueType(node.right)) {
return node;
}

Expand All @@ -135,12 +161,18 @@ function reduceDivisionExpression(node) {
return applyNumberDivision(node.left, node.right.value)
}

// apply (expr) / number
/**
* apply (expr) / number
*
* @param {import('../parser').CalcNode} node
* @param {number} divisor
* @return {import('../parser').CalcNode}
*/
function applyNumberDivision(node, divisor) {
if (divisor === 0) {
throw new Error('Cannot divide by zero');
}
if (isValueType(node.type)) {
if (isValueType(node)) {
node.value /= divisor;
return node;
}
Expand Down Expand Up @@ -169,7 +201,9 @@ function applyNumberDivision(node, divisor) {
}
}
}

/**
* @param {import('../parser').MathExpression} node
*/
function reduceMultiplicationExpression(node) {
// (expr) * number
if (node.right.type === 'Number') {
Expand All @@ -182,9 +216,14 @@ function reduceMultiplicationExpression(node) {
return node;
}

// apply (expr) / number
/**
* apply (expr) * number
* @param {number} multiplier
* @param {import('../parser').CalcNode} node
* @return {import('../parser').CalcNode}
*/
function applyNumberMultiplication(node, multiplier) {
if (isValueType(node.type)) {
if (isValueType(node)) {
node.value *= multiplier;
return node;
}
Expand Down Expand Up @@ -214,6 +253,11 @@ function applyNumberMultiplication(node, multiplier) {
}
}

/**
* @param {import('../parser').ValueExpression} left
* @param {import('../parser').ValueExpression} right
* @param {number} precision
*/
function convertNodesUnits(left, right, precision) {
switch (left.type) {
case 'LengthValue':
Expand All @@ -237,6 +281,10 @@ function convertNodesUnits(left, right, precision) {
}
}

/**
* @param {import('../parser').CalcNode} node
* @param {number} precision
*/
function reduce(node, precision) {
if (node.type === "MathExpression") {
if (isAddSubOperator(node.operator)) {
Expand All @@ -247,9 +295,9 @@ function reduce(node, precision) {
node.right = reduce(node.right, precision);
switch (node.operator) {
case "/":
return reduceDivisionExpression(node, precision);
return reduceDivisionExpression(node);
case "*":
return reduceMultiplicationExpression(node, precision);
return reduceMultiplicationExpression(node);
}

return node;
Expand Down
23 changes: 20 additions & 3 deletions src/lib/stringifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ const order = {
"-": 1,
};

/**
* @param {number} value
* @param {number | false} prec
*/
function round(value, prec) {
if (prec !== false) {
const precision = Math.pow(10, prec);
Expand All @@ -13,12 +17,15 @@ function round(value, prec) {
return value;
}

/**
* @param {number | false} prec
* @param {import('../parser').CalcNode} node
*/
function stringify(node, prec) {
switch (node.type) {
case "MathExpression": {
const {left, right, operator: op} = node;
let str = "";

if (left.type === 'MathExpression' && order[op] < order[left.operator]) {
str += `(${stringify(left, prec)})`;
} else {
Expand All @@ -36,14 +43,24 @@ function stringify(node, prec) {
return str;
}
case 'Number':
return round(node.value, prec);
return round(node.value, prec).toString();
case 'Function':
return node.value;
return node.value.toString();
default:
return round(node.value, prec) + node.unit;
}
}

/**
* @param {string} calc
* @param {import('../parser').CalcNode} node
* @param {string} originalValue
* @param {{precision: number | false, warnWhenCannotResolve: boolean}} options
* @param {import("postcss").Result} result
* @param {import("postcss").ChildNode} item
*
* @returns {string}
*/
export default function (
calc,
node,
Expand Down
Loading

0 comments on commit 30ab635

Please sign in to comment.