Skip to content

Commit b6cd010

Browse files
committed
Fix MadLittleMods#6 - var. use in comma separated selector 0.3.6
1 parent 869a91a commit b6cd010

20 files changed

+97660
-136
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11

2+
# v0.3.6 - 2015-5-21
3+
4+
- Fix #6. Variable usage in comma separated selector to use proper scope
5+
26
# v0.3.5 - 2015-5-12
37

48
- Big refactor of code to reduce cyclomatic complexity. Still needs work though.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ CSS variables or CSS Custom Properties limited subset polyfill/shim.
88

99
We strive for the most complete transformation but we/no plugin can achieve true complete parity according to the [specification](http://dev.w3.org/csswg/css-variables/) because of the DOM cascade unknowns.
1010

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

1414
### Install

index.js

Lines changed: 56 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// PostCSS CSS Variables (postcss-css-variables)
2-
// v0.3.5
2+
// v0.3.6
33
//
44
// https://github.com/MadLittleMods/postcss-css-variables
55

@@ -9,12 +9,8 @@
99
var postcss = require('postcss');
1010
var extend = require('extend');
1111

12-
var cloneSpliceParentOntoNodeWhen = require('./lib/clone-splice-parent-onto-node-when');
13-
var findNodeAncestorWithSelector = require('./lib/find-node-ancestor-with-selector');
1412
var resolveValue = require('./lib/resolve-value');
15-
var isNodeUnderScope = require('./lib/is-node-under-scope');
16-
var generateScopeList = require('./lib/generate-scope-list');
17-
var gatherVariableDependencies = require('./lib/gather-variable-dependencies');
13+
var resolveDecl = require('./lib/resolve-decl');
1814

1915

2016
// A custom property is any property whose name starts with two dashes (U+002D HYPHEN-MINUS)
@@ -36,49 +32,22 @@ function eachCssVariableDeclaration(css, cb) {
3632
}
3733

3834

39-
function eachMapItemUnderAtRuleUsedByVariable(variablesUsedList, map, decl, cb) {
40-
// Now find any at-rule declarations that pertains to each rule
41-
// Loop through the variables used
42-
variablesUsedList.forEach(function(variableUsedName) {
43-
44-
// Find anything in the map that corresponds to that variable
45-
gatherVariableDependencies(variablesUsedList, map).deps.forEach(function(mapItem) {
46-
if(mapItem.isUnderAtRule) {
47-
48-
// Get the inner-most selector of the at-rule scope variable declaration we are matching
49-
// 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
50-
var varDeclScopeList = generateScopeList(mapItem.parent, true);
51-
var innerMostAtRuleSelector = varDeclScopeList[0].slice(-1)[0];
52-
var nodeToSpliceParentOnto = findNodeAncestorWithSelector(innerMostAtRuleSelector, decl.parent);
53-
54-
// Splice on where the selector starts matching the selector inside at-rule
55-
// See: `test/fixtures/cascade-on-nested-rules.css`
56-
var varDeclAtRule = mapItem.parent.parent;
57-
var mimicDecl = cloneSpliceParentOntoNodeWhen(decl, varDeclAtRule, function(ancestor) {
58-
return ancestor === nodeToSpliceParentOnto;
59-
});
60-
6135

62-
//console.log('amd og', generateScopeList(decl.parent, true));
63-
//console.log('amd', generateScopeList(mimicDecl.parent, true));
64-
//console.log(generateScopeList(mapItem.parent, true));
65-
//console.log('amd isNodeUnderScope', isNodeUnderScope(mimicDecl.parent, mapItem.parent), mapItem.decl.value);
36+
function cleanUpNode(currentNodeToRemove) {
37+
// If we removed all of the declarations in the rule(making it empty), then just remove it
38+
var currentNodeToPossiblyCleanUp = currentNodeToRemove;
39+
while(currentNodeToPossiblyCleanUp && currentNodeToPossiblyCleanUp.nodes.length <= 0) {
40+
var nodeToRemove = currentNodeToPossiblyCleanUp;
41+
// Get a reference to it before we remove and lose reference to the child after removing it
42+
currentNodeToPossiblyCleanUp = currentNodeToPossiblyCleanUp.parent;
6643

67-
// If it is under the proper scope,
68-
// we need to check because we are iterating over all map entries that are `isUnderAtRule`
69-
// Then lets create the new rules
70-
if(isNodeUnderScope(mimicDecl.parent, mapItem.parent)) {
71-
cb(mimicDecl, mapItem);
72-
}
73-
}
74-
});
75-
});
44+
nodeToRemove.removeSelf();
45+
}
7646
}
7747

7848

7949

8050

81-
8251
module.exports = postcss.plugin('postcss-css-variables', function(options) {
8352
var defaults = {
8453
// Allows you to preserve custom properties & var() usage in output.
@@ -100,9 +69,6 @@ module.exports = postcss.plugin('postcss-css-variables', function(options) {
10069
try {
10170
/* */
10271

103-
// List of nodes to add at the end
104-
// We use this so we don't add to the tree as we are processing it (infinite loop)
105-
var createNodeCallbackList = [];
10672
// List of nodes that if empty, will be removed
10773
// We use this because we don't want to modify the AST when we still need to reference these later on
10874
var nodesToRemoveAtEnd = [];
@@ -203,102 +169,76 @@ module.exports = postcss.plugin('postcss-css-variables', function(options) {
203169
//else {}
204170

205171
// We add to the clean up list if we removed some variable declarations to make it become an empty rule
172+
// We clean up later on because we don't want to modify the AST when we still need to reference these later on
206173
if(declParentRule.nodes.length <= 0) {
207174
nodesToRemoveAtEnd.push(declParentRule);
208175
}
209176
});
210177

211178

212179

180+
181+
213182
// Resolve variables everywhere
214183
// ---------------------------------------------------------
215184
// ---------------------------------------------------------
216-
css.eachDecl(function(decl) {
217-
// Ignore any variable declarations that we may be preserving from earlier
218-
// Don't worry, they are already processed
219-
// If not a variable decalaraton... then resolve
220-
if(!RE_VAR_PROP.test(decl.prop)) {
221-
222-
223-
// Grab the balue for this declarations
224-
var valueResults = logResolveValueResult(resolveValue(decl, map));
225-
226185

227-
// Resolve the cascade
228-
// Now find any at-rule declarations that need to be added below each rule
229-
eachMapItemUnderAtRuleUsedByVariable(valueResults.variablesUsed, map, decl, function(mimicDecl, mapItem) {
230-
// Create the clean atRule for which we place the declaration under
231-
var atRuleNode = mapItem.parent.parent.clone().removeAll();
232-
233-
var ruleClone = decl.parent.clone().removeAll();
234-
var declClone = decl.clone();
235-
declClone.value = logResolveValueResult(resolveValue(mimicDecl, map)).value;
186+
// Collect all the rules that have declarations that use variables
187+
var rulesThatHaveDeclarationsWithVariablesList = [];
188+
css.eachRule(function(rule) {
189+
var doesRuleUseVariables = rule.nodes.some(function(node) {
190+
if(node.type == 'decl') {
191+
var decl = node;
192+
// If it uses variables
193+
// and is not a variable declarations that we may be preserving from earlier
194+
if(resolveValue.RE_VAR_FUNC.test(decl.value) && !RE_VAR_PROP.test(decl.prop)) {
195+
return true;
196+
}
197+
}
236198

237-
// Add the declaration to our new rule
238-
ruleClone.append(declClone);
239-
// Add the rule to the atRule
240-
atRuleNode.append(ruleClone);
199+
return false;
200+
});
241201

202+
if(doesRuleUseVariables) {
203+
rulesThatHaveDeclarationsWithVariablesList.push(rule);
204+
}
205+
});
242206

243-
// Since that atRuleNode can be nested in other atRules, we need to make the appropriate structure
244-
var parentAtRuleNode = atRuleNode;
245-
var currentAtRuleNode = mapItem.parent.parent;
246-
while(currentAtRuleNode.parent.type === 'atrule') {
247-
// Create a new clean clone of that at rule to nest under
248-
var newParentAtRuleNode = currentAtRuleNode.parent.clone().removeAll();
207+
rulesThatHaveDeclarationsWithVariablesList.forEach(function(rule) {
208+
var rulesToWorkOn = [].concat(rule);
209+
// Split out the rule into each comma separated selector piece
210+
// We only need to split if is actually comma separted(selectors > 1)
211+
if(rule.selectors.length > 1) {
212+
// Reverse the selectors so that we can cloneAfter in the same comma separated order
213+
rulesToWorkOn = rule.selectors.reverse().map(function(selector) {
214+
var ruleClone = rule.cloneAfter();
215+
ruleClone.selector = selector;
216+
217+
return ruleClone;
218+
});
249219

250-
// Append the old parent
251-
newParentAtRuleNode.append(parentAtRuleNode);
252-
// Then set the new one as the current for next iteration
253-
parentAtRuleNode = newParentAtRuleNode;
220+
rule.removeSelf();
221+
}
254222

255-
currentAtRuleNode = currentAtRuleNode.parent;
223+
// Resolve the declarations
224+
rulesToWorkOn.forEach(function(ruleToWorkOn) {
225+
ruleToWorkOn.nodes.slice(0).forEach(function(node) {
226+
if(node.type == 'decl') {
227+
var decl = node;
228+
resolveDecl(decl, map);
256229
}
257-
258-
createNodeCallbackList.push(function() {
259-
// Put the atRuleStructure after the declaration's rule
260-
decl.parent.parent.insertAfter(decl.parent, parentAtRuleNode);
261-
});
262230
});
231+
});
263232

264-
265-
// If we are preserving var(...) usage and the value changed meaning it had some
266-
if(opts.preserve === true && decl.value !== valueResults.value) {
267-
createNodeCallbackList.push(function() {
268-
decl.cloneAfter();
269-
270-
// Set the new value after we are done dealing with at-rule stuff
271-
decl.value = valueResults.value;
272-
});
273-
}
274-
else {
275-
// Set the new value after we are done dealing with at-rule stuff
276-
decl.value = valueResults.value;
277-
}
278-
279-
}
280233
});
281234

235+
282236

283237

284-
// Add some nodes that we need to add
285-
// We use this so we don't add to the tree as we are processing it (infinite loop)
286-
createNodeCallbackList.forEach(function(cb) {
287-
cb();
288-
});
289238

290239
// Clean up any nodes we don't want anymore
291-
nodesToRemoveAtEnd.forEach(function(currentChildToRemove) {
292-
// If we removed all of the declarations in the rule(making it empty), then just remove it
293-
var currentNodeToPossiblyCleanUp = currentChildToRemove;
294-
while(currentNodeToPossiblyCleanUp && currentNodeToPossiblyCleanUp.nodes.length <= 0) {
295-
var nodeToRemove = currentNodeToPossiblyCleanUp;
296-
// Get a reference to it before we remove and lose reference to the child after removing it
297-
currentNodeToPossiblyCleanUp = currentNodeToPossiblyCleanUp.parent;
298-
299-
nodeToRemove.removeSelf();
300-
}
301-
});
240+
// We clean up at the end because we don't want to modify the AST when we still need to reference these later on
241+
nodesToRemoveAtEnd.forEach(cleanUpNode);
302242

303243

304244
//console.log('map', map);

lib/resolve-decl.js

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
var resolveValue = require('./resolve-value');
2+
var generateScopeList = require('./generate-scope-list');
3+
var gatherVariableDependencies = require('./gather-variable-dependencies');
4+
var isNodeUnderScope = require('./is-node-under-scope');
5+
6+
var findNodeAncestorWithSelector = require('./find-node-ancestor-with-selector');
7+
var cloneSpliceParentOntoNodeWhen = require('./clone-splice-parent-onto-node-when');
8+
9+
// Resolve the decl with the computed value
10+
// Also add in any media queries that change the value as necessary
11+
function resolveDecl(decl, map, /*optional*/logResolveValueResult) {
12+
var _logResolveValueResult = function(valueResults) {
13+
if(logResolveValueResult) {
14+
logResolveValueResult(valueResults);
15+
}
16+
17+
return valueResults;
18+
};
19+
20+
// Grab the balue for this declarations
21+
var valueResults = _logResolveValueResult(resolveValue(decl, map));
22+
23+
24+
25+
// Resolve the cascade
26+
// Now find any at-rule declarations that need to be added below each rule
27+
eachMapItemUnderAtRuleUsedByVariable(valueResults.variablesUsed, map, decl, function(mimicDecl, mapItem) {
28+
// Create the clean atRule for which we place the declaration under
29+
var atRuleNode = mapItem.parent.parent.clone().removeAll();
30+
31+
var ruleClone = decl.parent.clone().removeAll();
32+
var declClone = decl.clone();
33+
declClone.value = _logResolveValueResult(resolveValue(mimicDecl, map)).value;
34+
35+
// Add the declaration to our new rule
36+
ruleClone.append(declClone);
37+
// Add the rule to the atRule
38+
atRuleNode.append(ruleClone);
39+
40+
41+
// Since that atRuleNode can be nested in other atRules, we need to make the appropriate structure
42+
var parentAtRuleNode = atRuleNode;
43+
var currentAtRuleNode = mapItem.parent.parent;
44+
while(currentAtRuleNode.parent.type === 'atrule') {
45+
// Create a new clean clone of that at rule to nest under
46+
var newParentAtRuleNode = currentAtRuleNode.parent.clone().removeAll();
47+
48+
// Append the old parent
49+
newParentAtRuleNode.append(parentAtRuleNode);
50+
// Then set the new one as the current for next iteration
51+
parentAtRuleNode = newParentAtRuleNode;
52+
53+
currentAtRuleNode = currentAtRuleNode.parent;
54+
}
55+
56+
// Put the atRuleStructure after the declaration's rule
57+
decl.parent.parent.insertAfter(decl.parent, parentAtRuleNode);
58+
59+
});
60+
61+
62+
// If we are preserving var(...) usage and the value changed meaning it had some
63+
if(opts.preserve === true && decl.value !== valueResults.value) {
64+
decl.cloneAfter();
65+
}
66+
67+
// Set the new value after we are done dealing with at-rule stuff
68+
decl.value = valueResults.value;
69+
70+
}
71+
72+
73+
function eachMapItemUnderAtRuleUsedByVariable(variablesUsedList, map, decl, cb) {
74+
// Now find any at-rule declarations that pertains to each rule
75+
// Loop through the variables used
76+
variablesUsedList.forEach(function(variableUsedName) {
77+
78+
// Find anything in the map that corresponds to that variable
79+
gatherVariableDependencies(variablesUsedList, map).deps.forEach(function(mapItem) {
80+
if(mapItem.isUnderAtRule) {
81+
82+
// Get the inner-most selector of the at-rule scope variable declaration we are matching
83+
// 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
84+
var varDeclScopeList = generateScopeList(mapItem.parent, true);
85+
var innerMostAtRuleSelector = varDeclScopeList[0].slice(-1)[0];
86+
var nodeToSpliceParentOnto = findNodeAncestorWithSelector(innerMostAtRuleSelector, decl.parent);
87+
88+
// Splice on where the selector starts matching the selector inside at-rule
89+
// See: `test/fixtures/cascade-on-nested-rules.css`
90+
var varDeclAtRule = mapItem.parent.parent;
91+
var mimicDecl = cloneSpliceParentOntoNodeWhen(decl, varDeclAtRule, function(ancestor) {
92+
return ancestor === nodeToSpliceParentOnto;
93+
});
94+
95+
96+
//console.log('amd og', generateScopeList(decl.parent, true));
97+
//console.log('amd', generateScopeList(mimicDecl.parent, true));
98+
//console.log(generateScopeList(mapItem.parent, true));
99+
//console.log('amd isNodeUnderScope', isNodeUnderScope(mimicDecl.parent, mapItem.parent), mapItem.decl.value);
100+
101+
// If it is under the proper scope,
102+
// we need to check because we are iterating over all map entries that are `isUnderAtRule`
103+
// Then lets create the new rules
104+
if(isNodeUnderScope(mimicDecl.parent, mapItem.parent)) {
105+
cb(mimicDecl, mapItem);
106+
}
107+
}
108+
});
109+
});
110+
}
111+
112+
113+
114+
115+
module.exports = resolveDecl;

lib/resolve-value.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ var cloneSpliceParentOntoNodeWhen = require('./clone-splice-parent-onto-node-whe
77

88

99

10-
// matches `name[, fallback]`, captures "name" and "fallback"
1110
// var() = var( <custom-property-name> [, <any-value> ]? )
11+
// matches `name[, fallback]`, captures "name" and "fallback"
1212
// See: http://dev.w3.org/csswg/css-variables/#funcdef-var
1313
var RE_VAR_FUNC = (/var\((--[^,\s]+?)(?:\s*,\s*(.+))?\)/);
1414

@@ -95,5 +95,7 @@ var resolveValue = function(decl, map, _debugIsInternal) {
9595
};
9696
};
9797

98+
resolveValue.RE_VAR_FUNC = RE_VAR_FUNC;
99+
98100

99101
module.exports = resolveValue;

0 commit comments

Comments
 (0)