Skip to content

Commit f3b8ea5

Browse files
Code improvements: changed regex match library to balanced-match and more tests added for css nested functions.
1 parent cd5c5dc commit f3b8ea5

File tree

4 files changed

+68
-67
lines changed

4 files changed

+68
-67
lines changed

lib/resolve-value.js

Lines changed: 57 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
var matchRecursive = require('match-recursive');
1+
var balanced = require('balanced-match');
22

33
var generateScopeList = require('./generate-scope-list');
44
var isNodeUnderScope = require('./is-node-under-scope');
@@ -43,74 +43,67 @@ var resolveValue = function(decl, map, /*optional*/ignorePseudoScope, /*internal
4343
// var() = var( <custom-property-name> [, <any-value> ]? )
4444
// matches `name[, fallback]`, captures "name" and "fallback"
4545
// See: http://dev.w3.org/csswg/css-variables/#funcdef-var
46-
var matches = null;
47-
// iterate over possible recursive matches
48-
while ((matches = matchRecursive(resultantValue, 'var(...)')).length) {
49-
// var originalDeclaration = matches.shift(); // get what was matched
50-
51-
// iterate over found var() occurences (some declaration may have multiple var() nested)
52-
matches.forEach(function (match) {
53-
match = match.split(',');
54-
match = [ match.shift(), match.join(',') ].filter(item => item) // clear empty items
55-
56-
var [ variableName, fallback ] = match.map(item => item.trim())
57-
58-
var matchingVarDeclMapItem;
59-
(map[variableName] || []).forEach(function(varDeclMapItem) {
60-
// Make sure the variable declaration came from the right spot
61-
// And if the current matching variable is already important, a new one to replace it has to be important
62-
var isRoot = varDeclMapItem.parent.type === 'root' || varDeclMapItem.parent.selectors[0] === ':root';
63-
64-
var underScope = isNodeUnderScope(decl.parent, varDeclMapItem.parent);
65-
var underScsopeIgnorePseudo = isNodeUnderScope(decl.parent, varDeclMapItem.parent, ignorePseudoScope);
66-
67-
//console.log(debugIndent, 'isNodeUnderScope', underScope, underScsopeIgnorePseudo, generateScopeList(varDeclMapItem.parent, true), varDeclMapItem.decl.value);
68-
69-
if(
70-
underScsopeIgnorePseudo &&
71-
// And if the currently matched declaration is `!important`, it will take another `!important` to override it
72-
(!(matchingVarDeclMapItem || {}).isImportant || varDeclMapItem.isImportant)
73-
) {
74-
matchingVarDeclMapItem = varDeclMapItem;
75-
}
76-
});
77-
78-
// Default to the calculatedInPlaceValue which might be a previous fallback, then try this declarations fallback
79-
var replaceValue = (matchingVarDeclMapItem || {}).calculatedInPlaceValue || (function() {
80-
// Resolve `var` values in fallback
81-
var fallbackValue = fallback;
82-
if(fallback) {
83-
var fallbackDecl = decl.clone({ parent: decl.parent, value: fallback });
84-
fallbackValue = resolveValue(fallbackDecl, map, false, /*internal*/true).value;
85-
}
86-
87-
return fallbackValue;
88-
})();
89-
// Otherwise if the dependency health is good(no circular or self references), dive deeper and resolve
90-
if(matchingVarDeclMapItem !== undefined && !gatherVariableDependencies(variablesUsedInValue, map).hasCircularOrSelfReference) {
91-
// Splice the declaration parent onto the matching entry
92-
93-
var varDeclScopeList = generateScopeList(decl.parent.parent, true);
94-
var innerMostAtRuleSelector = varDeclScopeList[0].slice(-1)[0];
95-
var nodeToSpliceParentOnto = findNodeAncestorWithSelector(innerMostAtRuleSelector, matchingVarDeclMapItem.decl.parent);
96-
// See: `test/fixtures/cascade-with-calc-expression-on-nested-rules`
97-
var matchingMimicDecl = cloneSpliceParentOntoNodeWhen(matchingVarDeclMapItem.decl, decl.parent.parent, function(ancestor) {
98-
return ancestor === nodeToSpliceParentOnto;
99-
});
100-
101-
replaceValue = resolveValue(matchingMimicDecl, map, false, /*internal*/true).value;
46+
var match;
47+
while (match = balanced('var(', ')', resultantValue)) {
48+
var matchingVarDeclMapItem = undefined;
49+
50+
// split comma to find variable name and fallback value
51+
match.body = match.body.split(',');
52+
// get variable name and fallback, filtering empty items
53+
var [ variableName, fallback ] = [ match.body.shift(), match.body.join(',') ].map(item => item.trim()).filter(item => !!item);
54+
55+
(map[variableName] || []).forEach(function(varDeclMapItem) {
56+
// Make sure the variable declaration came from the right spot
57+
// And if the current matching variable is already important, a new one to replace it has to be important
58+
var isRoot = varDeclMapItem.parent.type === 'root' || varDeclMapItem.parent.selectors[0] === ':root';
59+
60+
var underScope = isNodeUnderScope(decl.parent, varDeclMapItem.parent);
61+
var underScsopeIgnorePseudo = isNodeUnderScope(decl.parent, varDeclMapItem.parent, ignorePseudoScope);
62+
63+
//console.log(debugIndent, 'isNodeUnderScope', underScope, underScsopeIgnorePseudo, generateScopeList(varDeclMapItem.parent, true), varDeclMapItem.decl.value);
64+
65+
if(
66+
underScsopeIgnorePseudo &&
67+
// And if the currently matched declaration is `!important`, it will take another `!important` to override it
68+
(!(matchingVarDeclMapItem || {}).isImportant || varDeclMapItem.isImportant)
69+
) {
70+
matchingVarDeclMapItem = varDeclMapItem;
10271
}
72+
});
10373

104-
isResultantValueUndefined = replaceValue === undefined;
105-
if(isResultantValueUndefined) {
106-
warnings.push(['variable ' + variableName + ' is undefined and used without a fallback', { node: decl }]);
74+
// Default to the calculatedInPlaceValue which might be a previous fallback, then try this declarations fallback
75+
var replaceValue = (matchingVarDeclMapItem || {}).calculatedInPlaceValue || (function() {
76+
// Resolve `var` values in fallback
77+
var fallbackValue = fallback;
78+
if(fallback) {
79+
var fallbackDecl = decl.clone({ parent: decl.parent, value: fallback });
80+
fallbackValue = resolveValue(fallbackDecl, map, false, /*internal*/true).value;
10781
}
10882

109-
// replace original declaration
110-
resultantValue = resultantValue.replace(`var(${match.join(',')})`, replaceValue)
83+
return fallbackValue;
84+
})();
85+
// Otherwise if the dependency health is good(no circular or self references), dive deeper and resolve
86+
if(matchingVarDeclMapItem !== undefined && !gatherVariableDependencies(variablesUsedInValue, map).hasCircularOrSelfReference) {
87+
// Splice the declaration parent onto the matching entry
88+
89+
var varDeclScopeList = generateScopeList(decl.parent.parent, true);
90+
var innerMostAtRuleSelector = varDeclScopeList[0].slice(-1)[0];
91+
var nodeToSpliceParentOnto = findNodeAncestorWithSelector(innerMostAtRuleSelector, matchingVarDeclMapItem.decl.parent);
92+
// See: `test/fixtures/cascade-with-calc-expression-on-nested-rules`
93+
var matchingMimicDecl = cloneSpliceParentOntoNodeWhen(matchingVarDeclMapItem.decl, decl.parent.parent, function(ancestor) {
94+
return ancestor === nodeToSpliceParentOnto;
95+
});
11196

112-
//console.log(debugIndent, 'replaceValue', replaceValue);
113-
});
97+
replaceValue = resolveValue(matchingMimicDecl, map, false, /*internal*/true).value;
98+
}
99+
100+
isResultantValueUndefined = replaceValue === undefined;
101+
if(isResultantValueUndefined) {
102+
warnings.push(['variable ' + variableName + ' is undefined and used without a fallback', { node: decl }]);
103+
}
104+
105+
// replace original declaration
106+
resultantValue = `${match.pre || ''}${replaceValue}${match.post || ''}`;
114107
}
115108

116109
return {

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
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",
19-
"match-recursive": "^0.1.1",
2020
"postcss": "^6.0.8"
2121
},
2222
"devDependencies": {

test/fixtures/nested-inside-other-func.css

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
--some-color: red;
33
--some-width: 150px;
44
--some-other-width: 50px;
5-
--some-margin: 20px;
5+
--some-margin: 25px;
66
--some-padding: 20px;
77
}
88

@@ -22,12 +22,16 @@
2222
width: calc(58.3333333333% - var(--missing, 100px));
2323
}
2424

25+
.box-foo {
26+
width: calc(80vw - var(--missing, var(--some-other-width)));
27+
}
28+
2529
.box-foo {
2630
width: calc(58.3333333333% - var(--missing, var(--some-width, 100px)));
2731
}
2832

2933
.box-foo {
30-
width: calc(58.3333333333% - var(--missing));
34+
width: calc(100vw - var(--missing));
3135
}
3236

3337
.box-foo {

test/fixtures/nested-inside-other-func.expected.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
width: calc(58.3333333333% - 100px);
1515
}
1616

17+
.box-foo {
18+
width: calc(80vw - 50px);
19+
}
20+
1721
.box-foo {
1822
width: calc(58.3333333333% - 150px);
1923
}

0 commit comments

Comments
 (0)