Skip to content

Commit a4646ae

Browse files
committed
Use reduce-function-call
Also - support more complex formulas - better error message
1 parent fd8dea0 commit a4646ae

File tree

4 files changed

+48
-67
lines changed

4 files changed

+48
-67
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
# 1.1.0 - 2014-08-06
2+
3+
* support more complex formulas
4+
* use `reduce-function-call`
5+
* better error message
6+
7+
18
# 1.0.0 - 2014-08-04
29

310
First release

index.js

Lines changed: 28 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,23 @@
1-
/*
1+
/**
22
* Module dependencies
33
*/
44
var balanced = require("balanced-match")
5+
var reduceFunctionCall = require("reduce-function-call")
56

67
/**
7-
* Constantes
8-
*/
9-
var CALC_FUNC_IDENTIFIER = "calc"
10-
var EXPRESSION_OPT_VENDOR_PREFIX = "(\\-[a-z]+\\-)?"
11-
var EXPRESSION_METHOD_REGEXP = EXPRESSION_OPT_VENDOR_PREFIX + CALC_FUNC_IDENTIFIER
12-
var EXPRESSION_REGEXP = "\\b" + EXPRESSION_METHOD_REGEXP + "\\("
13-
14-
module.exports = resolveValue
15-
16-
/**
17-
* Walkthrough all expressions, evaluate them and insert them into the declaration
8+
* Expose reduceCssCalc plugin
189
*
19-
* @param {Array} expressions
20-
* @param {Object} declaration
10+
* @type {Function}
2111
*/
22-
23-
function resolveValue(value) {
24-
getExpressions(value).forEach(function(expression) {
25-
var result = evaluateExpression(expression.body)
26-
27-
value = value.replace(
28-
expression.fn + "(" + expression.body + ")",
29-
result.resolved ?
30-
result.value :
31-
expression.fn + "(" + result.value + ")"
32-
)
33-
})
34-
35-
return value
36-
}
12+
module.exports = reduceCssCalc
3713

3814
/**
39-
* Parses expressions in a value
15+
* Reduce CSS calc() in a string, whenever it's possible
4016
*
41-
* @param {String} value
42-
* @returns {Array}
43-
* @api private
17+
* @param {String} value css input
4418
*/
45-
46-
function getExpressions(value) {
47-
var expressions = []
48-
var fnRE = new RegExp(EXPRESSION_METHOD_REGEXP)
49-
do {
50-
var searchMatch = fnRE.exec(value)
51-
var fn = searchMatch[0]
52-
var calcStartIndex = searchMatch.index
53-
var calcRef = balanced("(", ")", value.substring(calcStartIndex))
54-
55-
if (!calcRef) {
56-
throw new SyntaxError("calc(): missing closing ')' in the value '" + value + "'")
57-
}
58-
if (calcRef.body === "") {
59-
throw new Error("calc(): calc() must contain a non-whitespace string")
60-
}
61-
62-
expressions.push({fn: fn, body: calcRef.body})
63-
value = calcRef.post
64-
}
65-
while (fnRE.test(value))
66-
67-
return expressions
19+
function reduceCssCalc(value) {
20+
return reduceFunctionCall(value, /((?:\-[a-z]+\-)?calc)\(/, evaluateExpression)
6821
}
6922

7023
/**
@@ -75,24 +28,34 @@ function getExpressions(value) {
7528
* @api private
7629
*/
7730

78-
function evaluateExpression (expression) {
79-
// Remove method names for possible nested expressions:
80-
expression = expression.replace(new RegExp(EXPRESSION_REGEXP, "g"), "(")
31+
function evaluateExpression (expression, functionIdentifier, call) {
32+
if (expression === "") {
33+
throw new Error(functionIdentifier + "(): '" + call + "' must contain a non-whitespace string")
34+
}
8135

8236
var balancedExpr = balanced("(", ")", expression)
83-
if (balancedExpr) {
37+
while (balancedExpr) {
8438
if (balancedExpr.body === "") {
85-
throw new Error("calc(): () must contain a non-whitespace string")
39+
throw new Error(functionIdentifier + "(): '" + expression + "' must contain a non-whitespace string")
8640
}
8741

88-
expression = balancedExpr.pre + evaluateExpression(balancedExpr.body).value + balancedExpr.post
42+
var evaluated = evaluateExpression(balancedExpr.body, functionIdentifier, call)
43+
44+
// if result didn't change since the last try, we consider it won't change anymore
45+
if (evaluated === balancedExpr.body) {
46+
balancedExpr = false
47+
}
48+
else {
49+
expression = balancedExpr.pre + evaluated + balancedExpr.post
50+
balancedExpr = balanced("(", ")", expression)
51+
}
8952
}
9053

9154
var units = getUnitsInExpression(expression)
9255

9356
// If multiple units let the expression be (i.e. browser calc())
9457
if (units.length > 1) {
95-
return {resolved: false, value: expression}
58+
return functionIdentifier + "(" + expression + ")"
9659
}
9760

9861
var unit = units[0] || ""
@@ -112,7 +75,7 @@ function evaluateExpression (expression) {
11275
result = eval(toEvaluate)
11376
}
11477
catch (e) {
115-
return {resolved: false, value: expression}
78+
return functionIdentifier + "(" + expression + ")"
11679
}
11780

11881
// Transform back to a percentage result:
@@ -125,7 +88,7 @@ function evaluateExpression (expression) {
12588
result += unit
12689
}
12790

128-
return {resolved: true, value: result}
91+
return result
12992
}
13093

13194
/**

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
"index.js"
2121
],
2222
"dependencies": {
23-
"balanced-match": "~0.1.0"
23+
"balanced-match": "~0.1.0",
24+
"reduce-function-call": "^1.0.0"
2425
},
2526
"devDependencies": {
2627
"jscs": "^1.5.9",

test/index.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,12 @@ test("ignore value around css calc() functions ", function(t) {
5151
test("reduce complexe css calc()", function(t) {
5252
t.equal(reduceCssCalc("calc(calc(100 + 10) + 1)"), "111", "integer")
5353
t.equal(reduceCssCalc("calc(calc(calc(1rem * 0.75) * 1.5) - 1rem)"), "0.125rem", "with a single unit")
54-
t.equal(reduceCssCalc("calc(calc(calc(1rem * 0.75) * 1.5) - 1px)"), "calc(1.125rem - 1px)", "multiple units")
54+
t.equal(reduceCssCalc("calc(calc(calc(1rem * 0.75) * 1.5) - 1px)"), "calc(1.125rem - 1px)", "multiple units with explicit calc")
55+
t.equal(reduceCssCalc("calc(((1rem * 0.75) * 1.5) - 1px)"), "calc(1.125rem - 1px)", "multiple units with implicit calc")
56+
t.equal(reduceCssCalc("calc(-1px + (1.5 * (1rem * 0.75)))"), "calc(-1px + 1.125rem)", "multiple units with implicit calc, reverse order")
57+
t.equal(reduceCssCalc("calc(2rem * (2 * (2 + 3)) + 4 + (5/2))"), "26.5rem", "complex math formula works correctly")
58+
59+
t.equal(reduceCssCalc("calc((4 * 2) + 4.2 + 1 + (2rem * .4) + (2px * .4))"), "calc(8 + 4.2 + 1 + 0.8rem + 0.8px)", "handle long formula")
5560
t.end()
5661
})
5762

@@ -63,3 +68,8 @@ test("reduce prefixed css calc()", function(t) {
6368
t.equal(reduceCssCalc("-moz-calc(50% - 2em)"), "-moz-calc(50% - 2em)","-moz, multiple unit")
6469
t.end()
6570
})
71+
72+
test("ignore unrecognized values", function(t) {
73+
t.equal(reduceCssCalc("calc((4px * 2) + 4.2 + a1 + (2rem * .4))"), "calc(8px + 4.2 + a1 + 0.8rem)", "ignore when eval fail")
74+
t.end()
75+
})

0 commit comments

Comments
 (0)