Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Code improvements: changed regex match library to balanced-match and …
…more tests added for css nested functions.
  • Loading branch information
juliovedovatto committed Apr 29, 2019
commit f3b8ea5c43edf39687701a12f4b17141bc101251
121 changes: 57 additions & 64 deletions lib/resolve-value.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
var matchRecursive = require('match-recursive');
var balanced = require('balanced-match');

var generateScopeList = require('./generate-scope-list');
var isNodeUnderScope = require('./is-node-under-scope');
Expand Down Expand Up @@ -43,74 +43,67 @@ var resolveValue = function(decl, map, /*optional*/ignorePseudoScope, /*internal
// var() = var( <custom-property-name> [, <any-value> ]? )
// matches `name[, fallback]`, captures "name" and "fallback"
// See: http://dev.w3.org/csswg/css-variables/#funcdef-var
var matches = null;
// iterate over possible recursive matches
while ((matches = matchRecursive(resultantValue, 'var(...)')).length) {
// var originalDeclaration = matches.shift(); // get what was matched

// iterate over found var() occurences (some declaration may have multiple var() nested)
matches.forEach(function (match) {
match = match.split(',');
match = [ match.shift(), match.join(',') ].filter(item => item) // clear empty items

var [ variableName, fallback ] = match.map(item => item.trim())

var matchingVarDeclMapItem;
(map[variableName] || []).forEach(function(varDeclMapItem) {
// Make sure the variable declaration came from the right spot
// And if the current matching variable is already important, a new one to replace it has to be important
var isRoot = varDeclMapItem.parent.type === 'root' || varDeclMapItem.parent.selectors[0] === ':root';

var underScope = isNodeUnderScope(decl.parent, varDeclMapItem.parent);
var underScsopeIgnorePseudo = isNodeUnderScope(decl.parent, varDeclMapItem.parent, ignorePseudoScope);

//console.log(debugIndent, 'isNodeUnderScope', underScope, underScsopeIgnorePseudo, generateScopeList(varDeclMapItem.parent, true), varDeclMapItem.decl.value);

if(
underScsopeIgnorePseudo &&
// And if the currently matched declaration is `!important`, it will take another `!important` to override it
(!(matchingVarDeclMapItem || {}).isImportant || varDeclMapItem.isImportant)
) {
matchingVarDeclMapItem = varDeclMapItem;
}
});

// Default to the calculatedInPlaceValue which might be a previous fallback, then try this declarations fallback
var replaceValue = (matchingVarDeclMapItem || {}).calculatedInPlaceValue || (function() {
// Resolve `var` values in fallback
var fallbackValue = fallback;
if(fallback) {
var fallbackDecl = decl.clone({ parent: decl.parent, value: fallback });
fallbackValue = resolveValue(fallbackDecl, map, false, /*internal*/true).value;
}

return fallbackValue;
})();
// Otherwise if the dependency health is good(no circular or self references), dive deeper and resolve
if(matchingVarDeclMapItem !== undefined && !gatherVariableDependencies(variablesUsedInValue, map).hasCircularOrSelfReference) {
// Splice the declaration parent onto the matching entry

var varDeclScopeList = generateScopeList(decl.parent.parent, true);
var innerMostAtRuleSelector = varDeclScopeList[0].slice(-1)[0];
var nodeToSpliceParentOnto = findNodeAncestorWithSelector(innerMostAtRuleSelector, matchingVarDeclMapItem.decl.parent);
// See: `test/fixtures/cascade-with-calc-expression-on-nested-rules`
var matchingMimicDecl = cloneSpliceParentOntoNodeWhen(matchingVarDeclMapItem.decl, decl.parent.parent, function(ancestor) {
return ancestor === nodeToSpliceParentOnto;
});

replaceValue = resolveValue(matchingMimicDecl, map, false, /*internal*/true).value;
var match;
while (match = balanced('var(', ')', resultantValue)) {
var matchingVarDeclMapItem = undefined;

// split comma to find variable name and fallback value
match.body = match.body.split(',');
// get variable name and fallback, filtering empty items
var [ variableName, fallback ] = [ match.body.shift(), match.body.join(',') ].map(item => item.trim()).filter(item => !!item);

(map[variableName] || []).forEach(function(varDeclMapItem) {
// Make sure the variable declaration came from the right spot
// And if the current matching variable is already important, a new one to replace it has to be important
var isRoot = varDeclMapItem.parent.type === 'root' || varDeclMapItem.parent.selectors[0] === ':root';

var underScope = isNodeUnderScope(decl.parent, varDeclMapItem.parent);
var underScsopeIgnorePseudo = isNodeUnderScope(decl.parent, varDeclMapItem.parent, ignorePseudoScope);

//console.log(debugIndent, 'isNodeUnderScope', underScope, underScsopeIgnorePseudo, generateScopeList(varDeclMapItem.parent, true), varDeclMapItem.decl.value);

if(
underScsopeIgnorePseudo &&
// And if the currently matched declaration is `!important`, it will take another `!important` to override it
(!(matchingVarDeclMapItem || {}).isImportant || varDeclMapItem.isImportant)
) {
matchingVarDeclMapItem = varDeclMapItem;
}
});

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

// replace original declaration
resultantValue = resultantValue.replace(`var(${match.join(',')})`, replaceValue)
return fallbackValue;
})();
// Otherwise if the dependency health is good(no circular or self references), dive deeper and resolve
if(matchingVarDeclMapItem !== undefined && !gatherVariableDependencies(variablesUsedInValue, map).hasCircularOrSelfReference) {
// Splice the declaration parent onto the matching entry

var varDeclScopeList = generateScopeList(decl.parent.parent, true);
var innerMostAtRuleSelector = varDeclScopeList[0].slice(-1)[0];
var nodeToSpliceParentOnto = findNodeAncestorWithSelector(innerMostAtRuleSelector, matchingVarDeclMapItem.decl.parent);
// See: `test/fixtures/cascade-with-calc-expression-on-nested-rules`
var matchingMimicDecl = cloneSpliceParentOntoNodeWhen(matchingVarDeclMapItem.decl, decl.parent.parent, function(ancestor) {
return ancestor === nodeToSpliceParentOnto;
});

//console.log(debugIndent, 'replaceValue', replaceValue);
});
replaceValue = resolveValue(matchingMimicDecl, map, false, /*internal*/true).value;
}

isResultantValueUndefined = replaceValue === undefined;
if(isResultantValueUndefined) {
warnings.push(['variable ' + variableName + ' is undefined and used without a fallback', { node: decl }]);
}

// replace original declaration
resultantValue = `${match.pre || ''}${replaceValue}${match.post || ''}`;
}

return {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
"url": "https://github.com/MadLittleMods/postcss-css-variables.git"
},
"dependencies": {
"balanced-match": "^1.0.0",
"escape-string-regexp": "^1.0.3",
"extend": "^3.0.1",
"match-recursive": "^0.1.1",
"postcss": "^6.0.8"
},
"devDependencies": {
Expand Down
8 changes: 6 additions & 2 deletions test/fixtures/nested-inside-other-func.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
--some-color: red;
--some-width: 150px;
--some-other-width: 50px;
--some-margin: 20px;
--some-margin: 25px;
--some-padding: 20px;
}

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

.box-foo {
width: calc(80vw - var(--missing, var(--some-other-width)));
}

.box-foo {
width: calc(58.3333333333% - var(--missing, var(--some-width, 100px)));
}

.box-foo {
width: calc(58.3333333333% - var(--missing));
width: calc(100vw - var(--missing));
}

.box-foo {
Expand Down
4 changes: 4 additions & 0 deletions test/fixtures/nested-inside-other-func.expected.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
width: calc(58.3333333333% - 100px);
}

.box-foo {
width: calc(80vw - 50px);
}

.box-foo {
width: calc(58.3333333333% - 150px);
}
Expand Down