🚀 Big News: Socket Acquires Coana to Bring Reachability Analysis to Every Appsec Team.Learn more
Socket
Sign inDemoInstall
Socket

postcss-css-variables

Package Overview
Dependencies
Maintainers
1
Versions
28
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

postcss-css-variables - npm Package Compare versions

Comparing version

to
0.3.5

lib/clone-splice-parent-onto-node-when.js

11

CHANGELOG.md
# v0.3.5 - 2015-5-12
- Big refactor of code to reduce cyclomatic complexity. Still needs work though.
- Fix variable referencing another variable resolution when being changed by at-rule in non-root rule
# v0.3.4 - 2015-5-12
- Fix variable referencing another variable resolution when being changed by at-rule
# v0.3.3 - 2015-5-11

@@ -7,3 +16,2 @@

# v0.3.1 - 2015-5-5

@@ -18,3 +26,2 @@

# v0.2.2 - 2015-5-1

@@ -21,0 +28,0 @@

543

index.js
// PostCSS CSS Variables (postcss-css-variables)
// v0.3.3
// v0.3.5
//

@@ -11,4 +11,11 @@ // https://github.com/MadLittleMods/postcss-css-variables

var extend = require('extend');
var escapeStringRegexp = require('escape-string-regexp');
var cloneSpliceParentOntoNodeWhen = require('./lib/clone-splice-parent-onto-node-when');
var findNodeAncestorWithSelector = require('./lib/find-node-ancestor-with-selector');
var resolveValue = require('./lib/resolve-value');
var isNodeUnderScope = require('./lib/is-node-under-scope');
var generateScopeList = require('./lib/generate-scope-list');
var gatherVariableDependencies = require('./lib/gather-variable-dependencies');
// A custom property is any property whose name starts with two dashes (U+002D HYPHEN-MINUS)

@@ -18,301 +25,60 @@ // `--foo`

var RE_VAR_PROP = (/(--(.+))/);
// matches `name[, fallback]`, captures "name" and "fallback"
// var() = var( <custom-property-name> [, <any-value> ]? )
// See: http://dev.w3.org/csswg/css-variables/#funcdef-var
var RE_VAR_FUNC = (/var\((--[^,\s]+?)(?:\s*,\s*(.+))?\)/);
// Unit Tests: https://regex101.com/r/oP0fM9/13
//
// It is a shame the regex has to be this long. Maybe a CSS selector parser would be better.
// We could almost use `/\b\s(?![><+~][\s]+?)/` to split the selector but this doesn't work with attribute selectors
var RE_SELECTOR_DESCENDANT_SPLIT = (/(.*?(?:(?:\[[^\]]+\]|(?![><+~\s]).)+)(?:(?:(?:\s(?!>>))|(?:\t(?!>>))|(?:\s?>>\s?))(?!\s+))(?![><+~][\s]+?))/);
// Splice on a parent scope onto a node
// And return a detached clone
function cloneSpliceParentOntoNodeWhen(node, parent, /*optional*/whenCb) {
whenCb = whenCb || function() {
return true;
};
var cloneList = [];
// Gather node ancestors and clone along the way
var current = node;
var isWhenNow = false;
while(current && !isWhenNow) {
if(current.type === 'decl') {
cloneList.push(current.clone());
function eachCssVariableDeclaration(css, cb) {
// Loop through all of the declarations and grab the variables and put them in the map
css.eachDecl(function(decl, index) {
// If declaration is a variable
if(RE_VAR_PROP.test(decl.prop)) {
cb(decl);
}
else {
cloneList.push(current.clone().removeAll());
}
isWhenNow = whenCb(current);
current = current.parent;
}
// Gather parent ancestors all the way up and clone along the way
var cloneParentList = [];
var currentParent = parent;
while(currentParent) {
cloneParentList.push(currentParent.clone().removeAll());
currentParent = currentParent.parent;
}
// Assign parents to our parent clones
cloneParentList.forEach(function(parentClone, index, cloneParentList) {
// Keep assigning parents detached until the very end
if(index+1 < cloneParentList.length) {
parentClone.parent = cloneParentList[index+1];
}
});
// Assign parents to our node clones
cloneList.forEach(function(clone, index, cloneList) {
// Keep assigning parents detached until the very end
if(index+1 < cloneList.length) {
clone.parent = cloneList[index+1];
// Then splice on the new parent scope
} else {
// Set the highest parent ancestor to back to where we should splice in
cloneParentList.slice(-1)[0].parent = current;
// Set the node clone to the lowest parent ancestor
clone.parent = cloneParentList[0];
}
});
return cloneList[0];
}
// Find a node starting from the given node that matches
function findNodeAncestorWithSelector(selector, node) {
var matchingNode;
function eachMapItemUnderAtRuleUsedByVariable(variablesUsedList, map, decl, cb) {
// Now find any at-rule declarations that pertains to each rule
// Loop through the variables used
variablesUsedList.forEach(function(variableUsedName) {
var currentNode = node;
var stillFindingNode = true;
// Keep going until we run out of parents to search
// or we found the node
while(currentNode.parent && !matchingNode) {
// A trick to get the selector split up. Generate a scope list on a clone(clean parent)
var currentNodeScopeList = generateScopeList(currentNode.clone(), true);
currentNodeScopeList.some(function(scopePieces) {
return scopePieces.some(function(scopePiece) {
if(scopePiece === selector) {
matchingNode = currentNode;
return true;
}
return false;
});
});
currentNode = currentNode.parent;
}
return matchingNode;
}
function generateDescendantPieces(selector) {
return selector.split(RE_SELECTOR_DESCENDANT_SPLIT)
.filter(function(piece) {
if(piece.length > 0) {
return true;
}
return false;
})
.map(function(piece) {
// Trim whitespace which would be a normal descendant selector
// and trim off the CSS4 descendant `>>` into a normal descendant selector
return piece.trim().replace(/\s*?>>\s*?/, function(match) {
return '';
});
});
}
function generateScopeList(node, /*optional*/includeSelf) {
includeSelf = includeSelf || false;
var selectorScopeList = [
// Start off with one branch
[]
];
var currentNodeParent = includeSelf ? node : node.parent;
while(currentNodeParent) {
// `currentNodeParent.selectors` is a list of each comma separated piece of the selector
var scopePieces = (currentNodeParent.selectors || []).map(function(selectorPiece) {
return {
value: selectorPiece,
type: 'selector'
};
});
// If it is a at-rule, then we need to construct the proper piece
if(currentNodeParent.type === 'atrule') {
scopePieces = [].concat(currentNodeParent.params).map(function(param, index) {
return {
value: '@' + currentNodeParent.name + ' ' + param,
type: 'atrule'
};
});
}
// Branch each current scope for each comma separated selector
// Otherwise just keep the [1] branch going
var branches = (scopePieces.length > 0 ? scopePieces : [1]).map(function() {
return selectorScopeList.map(function(scopePieces) {
return scopePieces.slice(0);
});
});
scopePieces.forEach(function(scopeObject, index) {
// Update each selector string with the new piece
branches[index] = branches[index].map(function(scopeStringPieces) {
var descendantPieces = [scopeObject.value];
// Split at any descendant combinators to properly make the scope list
if(scopeObject.type === 'selector') {
descendantPieces = generateDescendantPieces(scopeObject.value);
}
// Add to the front of the array
scopeStringPieces.unshift.apply(scopeStringPieces, descendantPieces);
// Find anything in the map that corresponds to that variable
gatherVariableDependencies(variablesUsedList, map).deps.forEach(function(mapItem) {
if(mapItem.isUnderAtRule) {
return scopeStringPieces;
});
});
// Get the inner-most selector of the at-rule scope variable declaration we are matching
// Because the inner-most selector will be the same for each branch, we can look at the first one [0] or any of the others
var varDeclScopeList = generateScopeList(mapItem.parent, true);
var innerMostAtRuleSelector = varDeclScopeList[0].slice(-1)[0];
var nodeToSpliceParentOnto = findNodeAncestorWithSelector(innerMostAtRuleSelector, decl.parent);
// Start from a new list so we can
// Flatten out the branches a bit and and merge back into the list
selectorScopeList = [];
branches.forEach(function(branch) {
selectorScopeList = selectorScopeList.concat(branch);
});
// Splice on where the selector starts matching the selector inside at-rule
// See: `test/fixtures/cascade-on-nested-rules.css`
var varDeclAtRule = mapItem.parent.parent;
var mimicDecl = cloneSpliceParentOntoNodeWhen(decl, varDeclAtRule, function(ancestor) {
return ancestor === nodeToSpliceParentOnto;
});
currentNodeParent = currentNodeParent.parent;
}
return selectorScopeList;
}
//console.log('amd og', generateScopeList(decl.parent, true));
//console.log('amd', generateScopeList(mimicDecl.parent, true));
//console.log(generateScopeList(mapItem.parent, true));
//console.log('amd isNodeUnderScope', isNodeUnderScope(mimicDecl.parent, mapItem.parent), mapItem.decl.value);
function isUnderScope(nodeScopeList, scopeNodeScopeList) {
var matchesScope = scopeNodeScopeList.some(function(scopeNodeScopePieces) {
return nodeScopeList.some(function(nodeScopePieces) {
var currentPieceOffset;
var wasEveryPieceFound = scopeNodeScopePieces.every(function(scopePiece) {
var pieceOffset = currentPieceOffset || 0;
// Start from the previous index and make sure we can find it
//var foundIndex = nodeScopePieces.indexOf(scopePiece, pieceOffset);
var foundIndex = -1;
var piecesWeCanMatch = nodeScopePieces.slice(pieceOffset);
piecesWeCanMatch.some(function(nodeScopePiece, index) {
// Find the scope piece at the end of the node selector
// Last-occurence
if(new RegExp(escapeStringRegexp(scopePiece) + '$').test(nodeScopePiece)) {
foundIndex = pieceOffset + index;
// Escape
return true;
}
return false;
});
// If it is a star or root, then it is valid no matter what
// We might consider adding `html` and `body` to this list as well
if(foundIndex < 0 && (scopePiece === '*' || scopePiece === ':root')) {
foundIndex = pieceOffset + 1;
// If it is under the proper scope,
// we need to check because we are iterating over all map entries that are `isUnderAtRule`
// Then lets create the new rules
if(isNodeUnderScope(mimicDecl.parent, mapItem.parent)) {
cb(mimicDecl, mapItem);
}
var isFurther = foundIndex > pieceOffset || (foundIndex >= 0 && currentPieceOffset === undefined);
currentPieceOffset = foundIndex;
return isFurther;
});
return wasEveryPieceFound;
}
});
});
return matchesScope;
}
function isNodeUnderNode(node, scopeNode) {
var nodeScopeList = generateScopeList(node, true);
var scopeNodeScopeList = generateScopeList(scopeNode, true);
return isUnderScope(nodeScopeList, scopeNodeScopeList);
}
// Pass in a value string to parse/resolve and a map of available values
// and we can figure out the final value
//
// Note: We do not modify the declaration
// Note: Resolving a declaration value without any `var(...)` does not harm the final value.
// This means, feel free to run everything through this function
var resolveValue = function(decl, map) {
var resultantValue = decl.value;
var variablesUsedInValue = [];
var warnings = [];
// Resolve any var(...) substitutons
var isResultantValueUndefined = false;
resultantValue = resultantValue.replace(new RegExp(RE_VAR_FUNC.source, 'g'), function(match, variableName, fallback) {
variablesUsedInValue.push(variableName);
// Loop through the list of declarations for that value and find the one that best matches
// By best match, we mean, the variable actually applies. Criteria:
// - At the root
// - Defined in the same rule
// - The latest defined `!important` if any
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';
//console.log(generateScopeList(decl.parent, true));
//console.log(generateScopeList(varDeclMapItem.parent, true));
//console.log('isNodeUnderNode', isNodeUnderNode(decl.parent, varDeclMapItem.parent), varDeclMapItem.value);
if(
isNodeUnderNode(decl.parent, varDeclMapItem.parent) &&
// And if the currently matched declaration is `!important`, it will take another `!important` to override it
(!(matchingVarDeclMapItem || {}).isImportant || varDeclMapItem.isImportant)
) {
matchingVarDeclMapItem = varDeclMapItem;
}
});
var replaceValue = (matchingVarDeclMapItem || {}).value || fallback;
isResultantValueUndefined = replaceValue === undefined;
if(isResultantValueUndefined) {
warnings.push(["variable '" + variableName + "' is undefined and used without a fallback", { node: decl }]);
}
return replaceValue;
});
return {
// The resolved value
value: !isResultantValueUndefined ? resultantValue : undefined,
// Array of variable names used in resolving this value
variablesUsed: variablesUsedInValue,
// Any warnings generated from parsing this value
warnings: warnings
};
};
module.exports = postcss.plugin('postcss-css-variables', function(options) {

@@ -352,32 +118,38 @@ var defaults = {

map,
Object.keys(opts.variables)
.reduce(function(prevVariableMap, variableName) {
var variableValue = opts.variables[variableName];
// Automatically prefix any variable with `--` (CSS custom property syntax) if it doesn't have it already
variableName = variableName.slice(0, 2) === '--' ? variableName : '--' + variableName;
Object.keys(opts.variables).reduce(function(prevVariableMap, variableName) {
var variableEntry = opts.variables[variableName];
// Automatically prefix any variable with `--` (CSS custom property syntax) if it doesn't have it already
variableName = variableName.slice(0, 2) === '--' ? variableName : '--' + variableName;
var variableValue = (variableEntry || {}).value || variableEntry;
var isImportant = (variableEntry || {}).isImportant || false;
// If they didn't pass a object, lets construct one
if(typeof variableValue !== 'object') {
variableValue = {
value: variableValue,
isImportant: false,
parent: css.root(),
isUnderAtRule: false
};
}
// Add a root node to the AST
var variableRootRule = postcss.rule({ selector: ':root' });
css.root().prepend(variableRootRule);
// Add the variable decl to the root node
var varDecl = postcss.decl({
prop: variableName,
value: variableValue
});
varDecl.moveTo(variableRootRule);
prevVariableMap[variableName] = (prevVariableMap[variableName] || []).concat(extend({
value: undefined,
isImportant: false,
parent: css.root(),
isUnderAtRule: false
}, variableValue));
// Add the entry to the map
prevVariableMap[variableName] = (prevVariableMap[variableName] || []).concat({
decl: varDecl,
prop: variableName,
calculatedInPlaceValue: variableValue,
isImportant: isImportant,
variablesUsed: [],
parent: variableRootRule,
isUnderAtRule: false
});
return prevVariableMap;
}, {})
return prevVariableMap;
}, {})
);
// Chainable helper function to log any messages (warnings)
function logResolveValueResult(valueResult) {
var logResolveValueResult = function(valueResult) {
// Log any warnings that might of popped up

@@ -392,3 +164,3 @@ var warningList = [].concat(valueResult.warnings);

return valueResult;
}
};

@@ -399,57 +171,44 @@

// ---------------------------------------------------------
var addedRules = [];
css.eachRule(function(rule) {
// We don't want infinite recursion possibly, so don't iterate over the rules we add inside
var shouldNotIterateOverThisRule = addedRules.some(function(addedRule) {
return addedRule === rule;
});
if(shouldNotIterateOverThisRule) {
return;
}
eachCssVariableDeclaration(css, function(decl) {
var declParentRule = decl.parent;
var valueResults = logResolveValueResult(resolveValue(decl, map));
// Split out each selector piece into its own declaration for easier logic down the road
decl.parent.selectors.forEach(function(selector) {
// Create a detached clone
var splitOutRule = decl.parent.clone().removeAll();
splitOutRule.selector = selector;
splitOutRule.parent = decl.parent.parent;
var numberOfStartingChildren = rule.nodes.length;
var declClone = decl.clone();
declClone.moveTo(splitOutRule);
// Loop through all of the declarations and grab the variables and put them in the map
rule.eachDecl(function(decl, index) {
var prop = decl.prop;
// If declaration is a variable
if(RE_VAR_PROP.test(prop)) {
var resolvedValue = logResolveValueResult(resolveValue(decl, map)).value;
if(resolvedValue !== undefined) {
// Split out each selector piece into its own declaration for easier logic down the road
decl.parent.selectors.forEach(function(selector, index) {
// Create a detached clone
var splitOutRule = rule.clone();
rule.selector = selector;
splitOutRule.parent = rule.parent;
map[prop] = (map[prop] || []).concat({
prop: prop,
value: resolvedValue,
isImportant: decl.important || false,
// variables inside root or at-rules (eg. @media, @support)
parent: splitOutRule,
isUnderAtRule: splitOutRule.parent.type === 'atrule'
});
});
}
// Remove the variable declaration because they are pretty much useless after we resolve them
if(!opts.preserve) {
decl.removeSelf();
}
// Or we can also just show the computed value used for that variable
else if(opts.preserve === 'computed') {
decl.value = resolvedValue;
}
// Otherwise just keep them as var declarations
}
map[prop] = (map[prop] || []).concat({
decl: declClone,
prop: prop,
calculatedInPlaceValue: valueResults.value,
isImportant: decl.important || false,
variablesUsed: valueResults.variablesUsed,
parent: splitOutRule,
// variables inside root or at-rules (eg. @media, @support)
isUnderAtRule: splitOutRule.parent.type === 'atrule'
});
});
// We don't want to mess up their code if they wrote a empty rule
// We add to the clean up list if we removed some variable declarations to make it become empty
if(numberOfStartingChildren > 0 && rule.nodes.length <= 0) {
nodesToRemoveAtEnd.push(rule);
// Remove the variable declaration because they are pretty much useless after we resolve them
if(!opts.preserve) {
decl.removeSelf();
}
// Or we can also just show the computed value used for that variable
else if(opts.preserve === 'computed') {
decl.value = valueResults.value;
}
// Otherwise just keep them as var declarations
//else {}
// We add to the clean up list if we removed some variable declarations to make it become an empty rule
if(declParentRule.nodes.length <= 0) {
nodesToRemoveAtEnd.push(declParentRule);
}
});

@@ -459,3 +218,2 @@

// Resolve variables everywhere

@@ -474,69 +232,37 @@ // ---------------------------------------------------------

// Resolve the cascade
// Now find any at-rule declarations that need to be added below each rule
// Loop through the variables used
valueResults.variablesUsed.forEach(function(variableUsedName) {
// Find anything in the map that corresponds to that variable
(map[variableUsedName] || []).forEach(function(varDeclMapItem) {
if(varDeclMapItem.isUnderAtRule) {
eachMapItemUnderAtRuleUsedByVariable(valueResults.variablesUsed, map, decl, function(mimicDecl, mapItem) {
// Create the clean atRule for which we place the declaration under
var atRuleNode = mapItem.parent.parent.clone().removeAll();
// Get the inner-most selector of the at-rule scope variable declaration we are matching
// Because the inner-most selector will be the same for each branch, we can look in any of them
var varDeclScopeList = generateScopeList(varDeclMapItem.parent, true);
var selector = varDeclScopeList[0].slice(-1)[0];
var ruleClone = decl.parent.clone().removeAll();
var declClone = decl.clone();
declClone.value = logResolveValueResult(resolveValue(mimicDecl, map)).value;
var currentNodeToSpliceParentOnto = findNodeAncestorWithSelector(selector, decl.parent);
// Add the declaration to our new rule
ruleClone.append(declClone);
// Add the rule to the atRule
atRuleNode.append(ruleClone);
var varDeclAtRule = varDeclMapItem.parent.parent;
var mimicDecl = cloneSpliceParentOntoNodeWhen(decl, varDeclAtRule, function(ancestor) {
return ancestor === currentNodeToSpliceParentOnto;
});
// Since that atRuleNode can be nested in other atRules, we need to make the appropriate structure
var parentAtRuleNode = atRuleNode;
var currentAtRuleNode = mapItem.parent.parent;
while(currentAtRuleNode.parent.type === 'atrule') {
// Create a new clean clone of that at rule to nest under
var newParentAtRuleNode = currentAtRuleNode.parent.clone().removeAll();
// Append the old parent
newParentAtRuleNode.append(parentAtRuleNode);
// Then set the new one as the current for next iteration
parentAtRuleNode = newParentAtRuleNode;
//console.log('amd og', generateScopeList(decl.parent, true));
//console.log('amd', generateScopeList(mimicDecl.parent, true));
//console.log(generateScopeList(varDeclMapItem.parent, true));
//console.log('amd isNodeUnderNode', isNodeUnderNode(mimicDecl.parent, varDeclMapItem.parent), varDeclMapItem.value);
currentAtRuleNode = currentAtRuleNode.parent;
}
// If it is under the proper scope
// Then lets create the new rules
if(isNodeUnderNode(mimicDecl.parent, varDeclMapItem.parent)) {
// Create the clean atRule for which we place the declaration under
var atRuleNode = varDeclMapItem.parent.parent.clone().removeAll();
var ruleClone = decl.parent.clone().removeAll();
var declClone = decl.clone();
declClone.value = logResolveValueResult(resolveValue(mimicDecl, map)).value;
// Add the declaration to our new rule
ruleClone.append(declClone);
// Add the rule to the atRule
atRuleNode.append(ruleClone);
// Since that atRuleNode can be nested in other atRules, we need to make the appropriate structure
var parentAtRuleNode = atRuleNode;
var currentAtRuleNode = varDeclMapItem.parent.parent;
while(currentAtRuleNode.parent.type === 'atrule') {
// Create a new clean clone of that at rule to nest under
var newParentAtRuleNode = currentAtRuleNode.parent.clone().removeAll();
// Append the old parent
newParentAtRuleNode.append(parentAtRuleNode);
// Then set the new one as the current for next iteration
parentAtRuleNode = newParentAtRuleNode;
currentAtRuleNode = currentAtRuleNode.parent;
}
createNodeCallbackList.push(function() {
// Put the atRuleStructure after the declaration's rule
decl.parent.parent.insertAfter(decl.parent, parentAtRuleNode);
});
}
}
createNodeCallbackList.push(function() {
// Put the atRuleStructure after the declaration's rule
decl.parent.parent.insertAfter(decl.parent, parentAtRuleNode);
});

@@ -590,2 +316,3 @@ });

catch(e) {
//console.log('e', e.message);
console.log('e', e.message, e.stack);

@@ -592,0 +319,0 @@ }

{
"name": "postcss-css-variables",
"version": "0.3.3",
"version": "0.3.5",
"description": "PostCSS plugin to transform CSS Custom Properties(CSS variables) syntax into a static representation",

@@ -5,0 +5,0 @@ "keywords": [

@@ -11,3 +11,3 @@ [![npm version](https://badge.fury.io/js/postcss-css-variables.svg)](http://badge.fury.io/js/postcss-css-variables) [![Build Status](https://travis-ci.org/MadLittleMods/postcss-css-variables.svg)](https://travis-ci.org/MadLittleMods/postcss-css-variables)

## Latest Version: v0.3.3
## Latest Version: v0.3.5
### [Changelog](https://github.com/MadLittleMods/postcss-css-variables/blob/master/CHANGELOG.md)

@@ -14,0 +14,0 @@