Skip to content

Commit cd5c5dc

Browse files
Code improviments to match var() declarations in nested CSS functions, using match-recursive libr
.editorconfig removed
1 parent bf638e1 commit cd5c5dc

File tree

5 files changed

+76
-40
lines changed

5 files changed

+76
-40
lines changed

.editorconfig

Lines changed: 0 additions & 8 deletions
This file was deleted.

lib/resolve-value.js

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
1+
var matchRecursive = require('match-recursive');
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+
// regex to capture variable names
11+
var RE_VAR_FUNC = (/var\(\s*(--[^,\s)]+)/);
1412

1513
function toString(value) {
1614
return String(value);
@@ -32,37 +30,42 @@ var resolveValue = function(decl, map, /*optional*/ignorePseudoScope, /*internal
3230

3331
var variablesUsedInValueMap = {};
3432
// Use `replace` as a loop to go over all occurrences with the `g` flag
35-
var resultantValueCopy = resultantValue;
36-
while (resultantValueCopy.match(new RegExp(RE_VAR_FUNC.source, 'g'))) {
37-
resultantValueCopy = resultantValueCopy.replace(new RegExp(RE_VAR_FUNC.source, 'g'), function(match, variableName, fallback) {
38-
variablesUsedInValueMap[variableName] = true;
39-
});
40-
}
41-
33+
resultantValue.replace(new RegExp(RE_VAR_FUNC.source, 'g'), function(match, variableName, fallback) {
34+
variablesUsedInValueMap[variableName] = true;
35+
});
4236
var variablesUsedInValue = Object.keys(variablesUsedInValueMap);
4337

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

4640
// Resolve any var(...) substitutons
4741
var isResultantValueUndefined = false;
42+
43+
// var() = var( <custom-property-name> [, <any-value> ]? )
44+
// matches `name[, fallback]`, captures "name" and "fallback"
45+
// 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())
4857

49-
while (resultantValue.match(new RegExp(RE_VAR_FUNC.source, 'g'))) {
50-
resultantValue = resultantValue.replace(new RegExp(RE_VAR_FUNC.source, 'g'), function(match, variableName, fallback) {
51-
// Loop through the list of declarations for that value and find the one that best matches
52-
// By best match, we mean, the variable actually applies. Criteria:
53-
// - is under the same scope
54-
// - The latest defined `!important` if any
5558
var matchingVarDeclMapItem;
5659
(map[variableName] || []).forEach(function(varDeclMapItem) {
5760
// Make sure the variable declaration came from the right spot
5861
// And if the current matching variable is already important, a new one to replace it has to be important
5962
var isRoot = varDeclMapItem.parent.type === 'root' || varDeclMapItem.parent.selectors[0] === ':root';
60-
63+
6164
var underScope = isNodeUnderScope(decl.parent, varDeclMapItem.parent);
6265
var underScsopeIgnorePseudo = isNodeUnderScope(decl.parent, varDeclMapItem.parent, ignorePseudoScope);
63-
66+
6467
//console.log(debugIndent, 'isNodeUnderScope', underScope, underScsopeIgnorePseudo, generateScopeList(varDeclMapItem.parent, true), varDeclMapItem.decl.value);
65-
68+
6669
if(
6770
underScsopeIgnorePseudo &&
6871
// And if the currently matched declaration is `!important`, it will take another `!important` to override it
@@ -71,7 +74,7 @@ var resolveValue = function(decl, map, /*optional*/ignorePseudoScope, /*internal
7174
matchingVarDeclMapItem = varDeclMapItem;
7275
}
7376
});
74-
77+
7578
// Default to the calculatedInPlaceValue which might be a previous fallback, then try this declarations fallback
7679
var replaceValue = (matchingVarDeclMapItem || {}).calculatedInPlaceValue || (function() {
7780
// Resolve `var` values in fallback
@@ -80,32 +83,33 @@ var resolveValue = function(decl, map, /*optional*/ignorePseudoScope, /*internal
8083
var fallbackDecl = decl.clone({ parent: decl.parent, value: fallback });
8184
fallbackValue = resolveValue(fallbackDecl, map, false, /*internal*/true).value;
8285
}
83-
86+
8487
return fallbackValue;
8588
})();
8689
// Otherwise if the dependency health is good(no circular or self references), dive deeper and resolve
8790
if(matchingVarDeclMapItem !== undefined && !gatherVariableDependencies(variablesUsedInValue, map).hasCircularOrSelfReference) {
8891
// Splice the declaration parent onto the matching entry
89-
92+
9093
var varDeclScopeList = generateScopeList(decl.parent.parent, true);
9194
var innerMostAtRuleSelector = varDeclScopeList[0].slice(-1)[0];
9295
var nodeToSpliceParentOnto = findNodeAncestorWithSelector(innerMostAtRuleSelector, matchingVarDeclMapItem.decl.parent);
9396
// See: `test/fixtures/cascade-with-calc-expression-on-nested-rules`
9497
var matchingMimicDecl = cloneSpliceParentOntoNodeWhen(matchingVarDeclMapItem.decl, decl.parent.parent, function(ancestor) {
9598
return ancestor === nodeToSpliceParentOnto;
9699
});
97-
100+
98101
replaceValue = resolveValue(matchingMimicDecl, map, false, /*internal*/true).value;
99102
}
100-
103+
101104
isResultantValueUndefined = replaceValue === undefined;
102105
if(isResultantValueUndefined) {
103106
warnings.push(['variable ' + variableName + ' is undefined and used without a fallback', { node: decl }]);
104107
}
105-
108+
109+
// replace original declaration
110+
resultantValue = resultantValue.replace(`var(${match.join(',')})`, replaceValue)
111+
106112
//console.log(debugIndent, 'replaceValue', replaceValue);
107-
108-
return replaceValue;
109113
});
110114
}
111115

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"dependencies": {
1717
"escape-string-regexp": "^1.0.3",
1818
"extend": "^3.0.1",
19+
"match-recursive": "^0.1.1",
1920
"postcss": "^6.0.8"
2021
},
2122
"devDependencies": {

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

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
:root {
2+
--some-color: red;
23
--some-width: 150px;
34
--some-other-width: 50px;
5+
--some-margin: 20px;
6+
--some-padding: 20px;
47
}
58

69
.box-foo {
7-
background-color: color(var(--some-color, white));
10+
background-color: color(var(--some-color));
11+
}
12+
13+
.box-foo {
14+
background-color: color(var(--missing, white));
815
}
916

1017
.box-foo {
@@ -25,4 +32,19 @@
2532

2633
.box-foo {
2734
width: calc(var(--some-width) - var(--some-other-width));
35+
}
36+
37+
.box-foo {
38+
--widthA: 100px;
39+
--widthB: calc(var(--widthA) / 2);
40+
--widthC: calc(var(--widthB) / 2);
41+
width: var(--widthC);
42+
}
43+
44+
.box-foo {
45+
margin: calc(var(--some-margin) - 2px) calc(1rem - var(--missing));
46+
}
47+
48+
.box-foo {
49+
padding: calc(var(--some-padding) - 2px) calc(100px - var(--some-padding)) calc(100px - calc(var(--missing, 20px) - 10px));
2850
}

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
.box-foo {
2+
background-color: color(red);
3+
}
4+
15
.box-foo {
26
background-color: color(white);
37
}
@@ -20,4 +24,17 @@
2024

2125
.box-foo {
2226
width: calc(150px - 50px);
23-
}
27+
}
28+
29+
.box-foo {
30+
width: calc(calc(100px / 2) / 2);
31+
}
32+
33+
.box-foo {
34+
margin: undefined
35+
}
36+
37+
.box-foo {
38+
padding: calc(20px - 2px) calc(100px - 20px) calc(100px - calc(20px - 10px));
39+
}
40+

0 commit comments

Comments
 (0)