Skip to content

Commit 046839f

Browse files
Merge pull request #97 from Konnng/master
Fix regex in resolve-value.js to allow nested CSS functions
2 parents 5695b77 + eb8adbe commit 046839f

13 files changed

+170
-22
lines changed

lib/resolve-value.js

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
1+
var balanced = require('balanced-match');
2+
13
var generateScopeList = require('./generate-scope-list');
24
var isNodeUnderScope = require('./is-node-under-scope');
35
var gatherVariableDependencies = require('./gather-variable-dependencies');
46

57
var findNodeAncestorWithSelector = require('./find-node-ancestor-with-selector');
68
var cloneSpliceParentOntoNodeWhen = require('./clone-splice-parent-onto-node-when');
79

8-
9-
10-
// var() = var( <custom-property-name> [, <any-value> ]? )
11-
// matches `name[, fallback]`, captures "name" and "fallback"
12-
// See: http://dev.w3.org/csswg/css-variables/#funcdef-var
13-
var RE_VAR_FUNC = (/var\(\s*(--[^,\s]+?)(?:\s*,\s*(.+))?\s*\)/);
10+
// Regexp to capture variable names
11+
var RE_VAR_FUNC = (/var\(\s*(--[^,\s)]+)/);
1412

1513
function toString(value) {
1614
return String(value);
@@ -27,26 +25,53 @@ function toString(value) {
2725
var resolveValue = function(decl, map, /*optional*/ignorePseudoScope, /*internal debugging*/_debugIsInternal) {
2826
var debugIndent = _debugIsInternal ? '\t' : '';
2927

28+
var matchingVarDecl = undefined;
3029
var resultantValue = toString(decl.value);
3130
var warnings = [];
3231

33-
var variablesUsedInValueMap = {};
34-
// Use `replace` as a loop to go over all occurrences with the `g` flag
35-
resultantValue.replace(new RegExp(RE_VAR_FUNC.source, 'g'), function(match, variableName, fallback) {
32+
// Match all variables first so we can later on if there are circular dependencies
33+
var variablesUsedInValueMap = {}
34+
// Create a temporary variable, storing resultantValue variable value
35+
var remainingVariableValue = resultantValue;
36+
// Use balanced lib to find var() declarations and store variable names
37+
while ((matchingVarDecl = balanced('var(', ')', remainingVariableValue))) {
38+
// Split at the comma to find variable name and fallback value
39+
// There may be other commas in the values so this isn't necessarily just 2 pieces
40+
var variableFallbackSplitPieces = matchingVarDecl.body.split(',');
41+
42+
// Get variable name and fallback, filtering empty items
43+
var variableName = variableFallbackSplitPieces[0].trim();
44+
45+
// add variable found in the object
3646
variablesUsedInValueMap[variableName] = true;
37-
});
47+
48+
// Replace variable name (first occurence only) from result, to avoid circular loop
49+
remainingVariableValue = (matchingVarDecl.pre || '') + matchingVarDecl.body.replace(variableName, '') + (matchingVarDecl.post || '');
50+
}
51+
// clear temporary variable
52+
remainingVariableValue = undefined;
53+
3854
var variablesUsedInValue = Object.keys(variablesUsedInValueMap);
3955

4056
//console.log(debugIndent, (_debugIsInternal ? '' : 'Try resolving'), generateScopeList(decl.parent, true), `ignorePseudoScope=${ignorePseudoScope}`, '------------------------');
4157

4258
// Resolve any var(...) substitutons
4359
var isResultantValueUndefined = false;
44-
resultantValue = resultantValue.replace(new RegExp(RE_VAR_FUNC.source, 'g'), function(match, variableName, fallback) {
45-
// Loop through the list of declarations for that value and find the one that best matches
46-
// By best match, we mean, the variable actually applies. Criteria:
47-
// - is under the same scope
48-
// - The latest defined `!important` if any
49-
var matchingVarDeclMapItem;
60+
61+
// var() = var( <custom-property-name> [, <any-value> ]? )
62+
// matches `name[, fallback]`, captures "name" and "fallback"
63+
// See: http://dev.w3.org/csswg/css-variables/#funcdef-var
64+
while ((matchingVarDecl = balanced('var(', ')', resultantValue))) {
65+
var matchingVarDeclMapItem = undefined;
66+
67+
// Split at the comma to find variable name and fallback value
68+
// There may be other commas in the values so this isn't necessarily just 2 pieces
69+
var variableFallbackSplitPieces = matchingVarDecl.body.split(',');
70+
71+
// Get variable name and fallback, filtering empty items
72+
var variableName = variableFallbackSplitPieces[0].trim();
73+
var fallback = variableFallbackSplitPieces.length > 1 ? variableFallbackSplitPieces.slice(1).join(',').trim() : undefined;
74+
5075
(map[variableName] || []).forEach(function(varDeclMapItem) {
5176
// Make sure the variable declaration came from the right spot
5277
// And if the current matching variable is already important, a new one to replace it has to be important
@@ -97,10 +122,9 @@ var resolveValue = function(decl, map, /*optional*/ignorePseudoScope, /*internal
97122
warnings.push(['variable ' + variableName + ' is undefined and used without a fallback', { node: decl }]);
98123
}
99124

100-
//console.log(debugIndent, 'replaceValue', replaceValue);
101-
102-
return replaceValue;
103-
});
125+
// Replace original declaration with found value
126+
resultantValue = (matchingVarDecl.pre || '') + replaceValue + (matchingVarDecl.post || '')
127+
}
104128

105129
return {
106130
// The resolved value

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"url": "https://github.com/MadLittleMods/postcss-css-variables.git"
1515
},
1616
"dependencies": {
17+
"balanced-match": "^1.0.0",
1718
"escape-string-regexp": "^1.0.3",
1819
"extend": "^3.0.1",
1920
"postcss": "^6.0.8"
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
:root {
2+
--some-width: 150px;
3+
--some-other-width: 50px;
4+
}
5+
6+
.box-foo {
7+
width: calc(58.3333333333% - var(--missing, var(--some-width, 100px)));
8+
}
9+
10+
.box-foo {
11+
width: calc(58.3333333333% - var(--missing, var(--missing2, 100px)));
12+
}
13+
14+
.box-foo {
15+
width: calc(58.3333333333% - var(--missing, var(--missing2, var(--some-other-width))));
16+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.box-foo {
2+
width: calc(58.3333333333% - 150px);
3+
}
4+
5+
.box-foo {
6+
width: calc(58.3333333333% - 100px);
7+
}
8+
9+
.box-foo {
10+
width: calc(58.3333333333% - 50px);
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
:root {
2+
--some-width: 150px;
3+
}
4+
5+
.box-foo {
6+
width: calc(58.3333333333% - var(--some-width, 100px));
7+
}
8+
9+
.box-foo {
10+
width: calc(58.3333333333% - var(--missing, 100px));
11+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.box-foo {
2+
width: calc(58.3333333333% - 150px);
3+
}
4+
5+
.box-foo {
6+
width: calc(58.3333333333% - 100px);
7+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
:root {
2+
--some-width: 150px;
3+
--some-other-width: 50px;
4+
}
5+
6+
.box-foo {
7+
width: calc(1000% - var(--some-width));
8+
}
9+
10+
.box-foo {
11+
width: calc(1000% - var(--missing-width));
12+
}
13+
14+
.box-foo {
15+
width: calc(var(--some-width) - var(--some-other-width));
16+
}
17+
18+
.box-foo {
19+
--widthA: 100px;
20+
--widthB: calc(var(--widthA) / 2);
21+
--widthC: calc(var(--widthB) / 2);
22+
width: var(--widthC);
23+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.box-foo {
2+
width: calc(1000% - 150px);
3+
}
4+
5+
.box-foo {
6+
width: undefined;
7+
}
8+
9+
.box-foo {
10+
width: calc(150px - 50px);
11+
}
12+
13+
.box-foo {
14+
width: calc(calc(100px / 2) / 2);
15+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
:root {
2+
--some-color: red;
3+
}
4+
.box-foo {
5+
background-color: color(var(--missing, white));
6+
}
7+
.box-foo {
8+
background-color: color(var(--some-color, white));
9+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.box-foo {
2+
background-color: color(white);
3+
}
4+
.box-foo {
5+
background-color: color(red);
6+
}
Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
:root {
2+
--some-color: red;
3+
--some-opacity: 0.3;
4+
}
5+
6+
.box-foo {
7+
background-color: color(var(--some-color));
8+
}
9+
110
.box-foo {
2-
background-color: color(var(--some-color, white));
11+
background-color: rgba(255, 0, 0, var(--some-opacity));
312
}
13+
14+
.box-foo {
15+
background-color: hsla(120,100%,50%, var(--missing-opacity));
16+
}
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
11
.box-foo {
2-
background-color: color(white);
2+
background-color: color(red);
33
}
4+
5+
.box-foo {
6+
background-color: rgba(255, 0, 0, 0.3);
7+
}
8+
9+
.box-foo {
10+
background-color: undefined;
11+
}

test/test.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,10 @@ describe('postcss-css-variables', function() {
259259
test('should use fallback variable if provided with missing variables calc', 'missing-variable-should-fallback-calc');
260260
test('should use fallback variable if provided with missing variables nested', 'missing-variable-should-fallback-nested');
261261
test('should not mangle outer function parentheses', 'nested-inside-other-func');
262+
test('should not mangle outer function parentheses - with fallback', 'nested-inside-other-func-with-fallback');
263+
test('should not mangle outer function parentheses - calc', 'nested-inside-calc-func');
264+
test('should not mangle outer function parentheses - calc with fallback', 'nested-inside-calc-func-with-fallback');
265+
test('should not mangle outer function parentheses - calc with fallback var()', 'nested-inside-calc-func-with-fallback-var');
262266
});
263267

264268
test('should accept whitespace in var() declarations', 'whitespace-in-var-declaration' )

0 commit comments

Comments
 (0)