From 2773092f465f47c1ba0c4c7464ec7d1ae94d4ff4 Mon Sep 17 00:00:00 2001 From: Martijn Cuppens Date: Fri, 26 Oct 2018 22:16:25 +0200 Subject: [PATCH] Allow rounding option --- README.md | 13 ++++++++++ src/index.js | 1 + src/lib/convert.js | 8 +++---- src/lib/reducer.js | 54 +++++++++++++++++++++--------------------- src/lib/stringifier.js | 22 ++++++++--------- src/lib/transform.js | 16 ++++++++++--- 6 files changed, 69 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 9cd9252..08202f1 100755 --- a/README.md +++ b/README.md @@ -116,6 +116,19 @@ var out = postcss() .css ``` +#### `allowRounding` (default: `true`) + +With this variable set `true`, `calc(100% / 3)` will output `33.33333%` (with `precision: 5`). If it is set `false` it will remain `calc(100% / 3)`. + +Another example with `allowRounding: false`: `calc(900% / 16)` will output `56.25%` with `precision: 5` (because `calc(900% / 16)` == `56.25%`), but `calc(900% / 16)` with `precision: 0` (because `calc(900% / 16)` != `56%`). + +```js +var out = postcss() + .use(calc({allowRounding: false})) + .process(css) + .css +``` + #### `warnWhenCannotResolve` (default: `false`) Adds warnings when calc() are not reduced to a single value. diff --git a/src/index.js b/src/index.js index c44ad0f..2d960a0 100644 --- a/src/index.js +++ b/src/index.js @@ -5,6 +5,7 @@ import transform from './lib/transform'; export default plugin('postcss-calc', (opts) => { const options = Object.assign({ precision: 5, + allowRounding: true, preserve: false, warnWhenCannotResolve: false, mediaQueries: false, diff --git a/src/lib/convert.js b/src/lib/convert.js index 144f34b..17e48a9 100644 --- a/src/lib/convert.js +++ b/src/lib/convert.js @@ -1,23 +1,23 @@ import convertUnits from 'css-unit-converter'; -function convertNodes(left, right, precision) { +function convertNodes(left, right, options) { switch (left.type) { case 'LengthValue': case 'AngleValue': case 'TimeValue': case 'FrequencyValue': case 'ResolutionValue': - return convertAbsoluteLength(left, right, precision); + return convertAbsoluteLength(left, right, options); default: return { left, right }; } } -function convertAbsoluteLength(left, right, precision) { +function convertAbsoluteLength(left, right, options) { if (right.type === left.type) { right = { type: left.type, - value: convertUnits(right.value, right.unit, left.unit, precision), + value: convertUnits(right.value, right.unit, left.unit, options), unit: left.unit, }; } diff --git a/src/lib/reducer.js b/src/lib/reducer.js index 0dc0914..8f63e46 100644 --- a/src/lib/reducer.js +++ b/src/lib/reducer.js @@ -1,8 +1,8 @@ import convert from './convert'; -function reduce(node, precision) { - if (node.type === "MathExpression") - return reduceMathExpression(node, precision); +function reduce(node, options) { + if (node.type === "MathExpression") + return reduceMathExpression(node, options); return node; } @@ -33,10 +33,10 @@ function isValueType(type) { return false; } -function convertMathExpression(node, precision) { - let nodes = convert(node.left, node.right, precision); - let left = reduce(nodes.left, precision); - let right = reduce(nodes.right, precision); +function convertMathExpression(node, options) { + let nodes = convert(node.left, node.right, options); + let left = reduce(nodes.left, options); + let right = reduce(nodes.right, options); if (left.type === "MathExpression" && right.type === "MathExpression") { @@ -46,13 +46,13 @@ function convertMathExpression(node, precision) { (left.operator === '+' && right.operator === '-'))) { if (isEqual(left.right, right.right)) - nodes = convert(left.left, right.left, precision); + nodes = convert(left.left, right.left, options); else if (isEqual(left.right, right.left)) - nodes = convert(left.left, right.right, precision); + nodes = convert(left.left, right.right, options); - left = reduce(nodes.left, precision); - right = reduce(nodes.right, precision); + left = reduce(nodes.left, options); + right = reduce(nodes.right, options); } } @@ -76,7 +76,7 @@ function flipValue(node) { return node; } -function reduceAddSubExpression(node, precision) { +function reduceAddSubExpression(node, options) { const {left, right, operator: op} = node; if (left.type === 'Function' || right.type === 'Function') @@ -122,10 +122,10 @@ function reduceAddSubExpression(node, precision) { operator: op, left: left, right: right.left - }, precision); + }, options); node.right = right.right; node.operator = op === '-' ? flip(right.operator) : right.operator; - return reduce(node, precision); + return reduce(node, options); } // value + (something + value) => (value + value) + something // value + (something - value) => (value - value) + something @@ -138,15 +138,15 @@ function reduceAddSubExpression(node, precision) { operator: op === '-' ? flip(right.operator) : right.operator, left: left, right: right.right - }, precision); + }, options); node.right = right.left; - return reduce(node, precision); + return reduce(node, options); } // value - (something + something) => value - something - something else if (op === '-' && right.operator === '+') { node = Object.assign({ }, node); node.right.operator = '-'; - return reduce(node, precision); + return reduce(node, options); } } @@ -167,8 +167,8 @@ function reduceAddSubExpression(node, precision) { operator: op, left: left.left, right: right - }, precision); - return reduce(node, precision); + }, options); + return reduce(node, options); } // (something + value) + value => something + (value + value) // (something - value1) + value2 => something - (value2 - value1) @@ -182,7 +182,7 @@ function reduceAddSubExpression(node, precision) { operator: flip(op), left: left.right, right: right - }, precision); + }, options); if (node.right.value && node.right.value < 0) { node.right.value = Math.abs(node.right.value); node.operator = '+'; @@ -196,16 +196,16 @@ function reduceAddSubExpression(node, precision) { operator: op, left: left.right, right: right - }, precision); + }, options); } if (node.right.value < 0) { node.right.value *= -1; node.operator = node.operator === '-' ? '+' : '-'; } - return reduce(node, precision); + return reduce(node, options); } } - + if ( left.type === 'MathExpression' && right.type === 'MathExpression' && op === '-' && right.operator === '-' @@ -269,15 +269,15 @@ function reduceMultiplicationExpression(node) { return node; } -function reduceMathExpression(node, precision) { - node = convertMathExpression(node, precision); +function reduceMathExpression(node, options) { + node = convertMathExpression(node, options); switch (node.operator) { case "+": case "-": - return reduceAddSubExpression(node, precision); + return reduceAddSubExpression(node, options); case "/": - return reduceDivisionExpression(node, precision); + return reduceDivisionExpression(node, options); case "*": return reduceMultiplicationExpression(node); } diff --git a/src/lib/stringifier.js b/src/lib/stringifier.js index 740c99f..7eb7c78 100644 --- a/src/lib/stringifier.js +++ b/src/lib/stringifier.js @@ -5,40 +5,40 @@ const order = { "-": 1, }; -function round(value, prec) { - if (prec !== false) { - const precision = Math.pow(10, prec); +function round(value, options) { + if (options.precision !== false && options.allowRounding) { + const precision = Math.pow(10, options.precision); return Math.round(value * precision) / precision; } return value; } -function stringify(node, prec) { +function stringify(node, options) { 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)})`; + str += `(${stringify(left, options)})`; else - str += stringify(left, prec); + str += stringify(left, options); str += order[op] ? ` ${node.operator} ` : node.operator; if (right.type === 'MathExpression' && order[op] < order[right.operator]) - str += `(${stringify(right, prec)})`; + str += `(${stringify(right, options)})`; else - str += stringify(right, prec); + str += stringify(right, options); return str; } case "Value": - return round(node.value, prec); + return round(node.value, options); case 'Function': return node.value; default: - return round(node.value, prec) + node.unit; + return round(node.value, options) + node.unit; } } @@ -50,7 +50,7 @@ export default function ( result, item ) { - let str = stringify(node, options.precision); + let str = stringify(node, options); if (node.type === "MathExpression") { // if calc expression couldn't be resolved to a single value, re-wrap it as diff --git a/src/lib/transform.js b/src/lib/transform.js index 25e72d3..b4e7779 100644 --- a/src/lib/transform.js +++ b/src/lib/transform.js @@ -1,5 +1,5 @@ import selectorParser from 'postcss-selector-parser'; -import valueParser from 'postcss-value-parser'; +import valueParser from 'postcss-value-parser'; // eslint-disable-next-line import/no-unresolved import { parser } from '../parser'; @@ -18,10 +18,20 @@ function transformValue(value, options, result, item) { // stringify calc expression and produce an AST const contents = valueParser.stringify(node.nodes); const ast = parser.parse(contents); - + // reduce AST to its simplest form, that is, either to a single value // or a simplified calc expression - const reducedAst = reducer(ast, options.precision); + const reducedAst = reducer(ast, options); + + // prevent rounding if not allowed + if (!options.allowRounding && reducedAst.type !== "MathExpression") { + const precision = Math.pow(10, options.precision); + + // Check if the result is rounded + if (Math.round(reducedAst.value * precision) / precision !== reducedAst.value){ + return node; + } + } // stringify AST and write it back node.type = 'word';