Skip to content

Commit aa65cda

Browse files
committed
Add support for child combinator. Fix MadLittleMods#7 - v0.3.7
1 parent 9cdc113 commit aa65cda

15 files changed

+258
-49
lines changed

CHANGELOG.md

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

2+
# v0.3.7 - 2015-5-27
3+
4+
- Fix #7: Support for child combinator
5+
- Added tests for child combinator coverage
6+
27
# v0.3.6 - 2015-5-21
38

49
- Fix #6. Variable usage in comma separated selector to use proper scope

index.js

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

@@ -65,7 +65,7 @@ module.exports = postcss.plugin('postcss-css-variables', function(options) {
6565
return function (css, result) {
6666
// Transform CSS AST here
6767

68-
/* * /
68+
/* */
6969
try {
7070
/* */
7171

@@ -243,7 +243,7 @@ module.exports = postcss.plugin('postcss-css-variables', function(options) {
243243

244244
//console.log('map', map);
245245

246-
/* * /
246+
/* */
247247
}
248248
catch(e) {
249249
//console.log('e', e.message);

lib/generate-descendant-pieces-from-selector.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
// Unit Tests: https://regex101.com/r/oP0fM9/13
1+
// Unit Tests: https://regex101.com/r/oP0fM9/15
22
//
33
// It is a shame the regex has to be this long. Maybe a CSS selector parser would be better.
44
// We could almost use `/\b\s(?![><+~][\s]+?)/` to split the selector but this doesn't work with attribute selectors
5-
var RE_SELECTOR_DESCENDANT_SPLIT = (/(.*?(?:(?:\[[^\]]+\]|(?![><+~\s]).)+)(?:(?:(?:\s(?!>>))|(?:\t(?!>>))|(?:\s?>>\s?))(?!\s+))(?![><+~][\s]+?))/);
5+
var RE_SELECTOR_DESCENDANT_SPLIT = (/(.*?(?:(?:\([^\)]+\)|\[[^\]]+\]|(?![><+~\s]).)+)(?:(?:(?:\s(?!>>))|(?:\t(?!>>))|(?:\s?>>\s?))(?!\s+))(?![><+~][\s]+?))/);
66

77

88
var generateDescendantPiecesFromSelector = function(selector) {
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Unit Tests: https://regex101.com/r/oS4zJ8/3
2+
3+
var RE_SELECTOR_DIRECT_DESCENDANT_SPLIT = (/(.*?(?:(?:\([^\)]+\)|\[[^\]]+\]|(?!>>|<|\+|~|\s).)+)(?:(?:(?:>(?!>))|(?:\s?>(?!>)\s?))(?!\s+))(?!(?:>>|<|\+|~)[\s]+?))/);
4+
5+
6+
var generateDirectDescendantPiecesFromSelector = function(selector) {
7+
return selector.split(RE_SELECTOR_DIRECT_DESCENDANT_SPLIT)
8+
.filter(function(piece) {
9+
if(piece.length > 0) {
10+
return true;
11+
}
12+
return false;
13+
})
14+
.map(function(piece) {
15+
// Trim whitespace which would be a normal descendant selector
16+
// and trim off the CSS4 descendant `>>` into a normal descendant selector
17+
return piece.trim().replace(/\s*?>\s*?/, function(match) {
18+
return '';
19+
});
20+
});
21+
};
22+
23+
module.exports = generateDirectDescendantPiecesFromSelector;

lib/is-under-scope.js

Lines changed: 98 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,123 @@
11
var escapeStringRegexp = require('escape-string-regexp');
22

3-
var isPieceIsAlwaysAncestorSelector = require('./is-piece-always-ancestor-selector');
3+
var isPieceAlwaysAncestorSelector = require('./is-piece-always-ancestor-selector');
4+
var generateDirectDescendantPiecesFromSelector = require('./generate-direct-descendant-pieces-from-selector');
45

5-
// Given the nodes scope, and the target scope,
6-
// Is the node in the same or under the target scope (cascade wise)
7-
//
8-
// Another way to think about it: Can the target cascade properties to the node?
9-
//
10-
// For scope-lists see: `generateScopeList`
11-
var isUnderScope = function(nodeScopeList, scopeNodeScopeList) {
12-
var matchesScope = scopeNodeScopeList.some(function(scopeNodeScopePieces) {
6+
7+
8+
function asdfqwer(nodeScopeList, scopeNodeScopeList) {
9+
var currentPieceOffset;
10+
var scopePieceIndex;
11+
12+
// Check each comma separated piece of the complex selector
13+
var doesMatchScope = scopeNodeScopeList.some(function(scopeNodeScopePieces) {
1314
return nodeScopeList.some(function(nodeScopePieces) {
14-
var currentPieceOffset;
15-
var wasEveryPieceFound = scopeNodeScopePieces.every(function(scopePiece) {
15+
16+
17+
currentPieceOffset = null;
18+
var wasEveryPieceFound = true;
19+
// scopeNodeScopePieces.every(function(scopePiece) {
20+
for(scopePieceIndex = 0; scopePieceIndex < scopeNodeScopePieces.length; scopePieceIndex++) {
21+
var scopePiece = scopeNodeScopePieces[scopePieceIndex];
1622
var pieceOffset = currentPieceOffset || 0;
1723

1824
var foundIndex = -1;
19-
var firstAlwaysAncestorPieceIndex = -1;
2025
// Look through the remaining pieces(start from the offset)
2126
var piecesWeCanMatch = nodeScopePieces.slice(pieceOffset);
22-
piecesWeCanMatch.some(function(nodeScopePiece, index) {
23-
var overallIndex = pieceOffset + index;
24-
25-
if(firstAlwaysAncestorPieceIndex < 0 && isPieceIsAlwaysAncestorSelector(nodeScopePiece)) {
26-
firstAlwaysAncestorPieceIndex = overallIndex;
27-
}
27+
//piecesWeCanMatch.some(function(nodeScopePiece, index) {
28+
for(var nodeScopePieceIndex = 0; nodeScopePieceIndex < piecesWeCanMatch.length; nodeScopePieceIndex++) {
29+
var nodeScopePiece = piecesWeCanMatch[nodeScopePieceIndex];
30+
var overallIndex = pieceOffset + nodeScopePieceIndex;
2831

2932
// Find the scope piece at the end of the node selector
3033
// Last-occurence
31-
if(new RegExp(escapeStringRegexp(scopePiece) + '$').test(nodeScopePiece)) {
34+
if(
35+
// If the part on the end of the piece itself matches:
36+
// scopePiece `.bar` matches node `.bar`
37+
// scopePiece `.bar` matches node `.foo + .bar`
38+
new RegExp(escapeStringRegexp(scopePiece) + '$').test(nodeScopePiece)
39+
) {
3240
foundIndex = overallIndex;
33-
// Escape
34-
return true;
41+
break;
42+
}
43+
44+
45+
// If the scope piece is a always-ancestor, then it is valid no matter what
46+
//
47+
// Or the node scope piece could be an always-ancestor selector itself
48+
// And we only want the first occurence so we can keep matching future scope pieces
49+
if(isPieceAlwaysAncestorSelector(scopePiece) || isPieceAlwaysAncestorSelector(nodeScopePiece)) {
50+
foundIndex = overallIndex;
51+
52+
break;
53+
}
54+
55+
56+
// Handle any direct descendant operators in each piece
57+
var directDescendantPieces = generateDirectDescendantPiecesFromSelector(nodeScopePiece);
58+
if(directDescendantPieces.length > 1) {
59+
60+
var ddNodeScopeList = [].concat([directDescendantPieces]);
61+
var ddScopeList = [].concat([
62+
scopeNodeScopePieces
63+
.slice(scopePieceIndex)
64+
.reduce(function(prevScopePieces, scopePiece) {
65+
return prevScopePieces.concat(generateDirectDescendantPiecesFromSelector(scopePiece));
66+
}, [])
67+
]);
68+
var result = asdfqwer(ddNodeScopeList, ddScopeList);
69+
70+
// If it matches completely
71+
// or there are still more pieces to match in the future
72+
if(result.doesMatchScope || scopePieceIndex+1 < scopeNodeScopePieces.length) {
73+
foundIndex = overallIndex;
74+
// -1 because the fo loop increments at the top
75+
scopePieceIndex += result.scopePieceIndex-1;
76+
}
77+
78+
break;
79+
}
80+
81+
if(directDescendantPieces.length > 1) {
82+
var asdf = scopeNodeScopePieces.slice(scopePieceIndex);
83+
3584
}
36-
return false;
37-
});
38-
// If the scope piece is a always-ancestor, then it is valid no matter what
39-
if(foundIndex < 0 && isPieceIsAlwaysAncestorSelector(scopePiece)) {
40-
foundIndex = pieceOffset + 1;
41-
}
42-
// The piece could be a always-ancestor selector itself
43-
// And we only want the first occurence so we can keep matching future scope pieces
44-
else if(foundIndex < 0 && firstAlwaysAncestorPieceIndex > 0) {
45-
foundIndex = firstAlwaysAncestorPieceIndex;
4685
}
86+
4787

48-
var isFurther = foundIndex > pieceOffset || (foundIndex >= 0 && currentPieceOffset === undefined);
88+
var isFurther = foundIndex >= pieceOffset;
4989

50-
currentPieceOffset = foundIndex;
51-
return isFurther;
52-
});
90+
currentPieceOffset = foundIndex+1;
91+
92+
wasEveryPieceFound = wasEveryPieceFound && isFurther;
93+
if(!wasEveryPieceFound) {
94+
break;
95+
}
96+
}
5397

5498
return wasEveryPieceFound;
5599
});
56100
});
57101

58-
return matchesScope;
102+
return {
103+
doesMatchScope: doesMatchScope,
104+
nodeScopePieceIndex: currentPieceOffset-1,
105+
scopePieceIndex: scopePieceIndex
106+
};
107+
}
108+
109+
110+
111+
112+
113+
// Given the nodes scope, and the target scope,
114+
// Is the node in the same or under the target scope (cascade wise)
115+
//
116+
// Another way to think about it: Can the target scope cascade properties to the node?
117+
//
118+
// For scope-lists see: `generateScopeList`
119+
var isUnderScope = function isUnderScope(nodeScopeList, scopeNodeScopeList) {
120+
return asdfqwer(nodeScopeList, scopeNodeScopeList).doesMatchScope;
59121
};
60122

61123
module.exports = isUnderScope;

lib/resolve-decl.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ function resolveDecl(decl, map, /*optional*/logResolveValueResult) {
1717
return valueResults;
1818
};
1919

20+
21+
2022
// Grab the balue for this declarations
2123
var valueResults = _logResolveValueResult(resolveValue(decl, map));
2224

lib/resolve-value.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ var RE_VAR_FUNC = (/var\((--[^,\s]+?)(?:\s*,\s*(.+))?\)/);
1818
// Note: We do not modify the declaration
1919
// Note: Resolving a declaration value without any `var(...)` does not harm the final value.
2020
// This means, feel free to run everything through this function
21-
var resolveValue = function(decl, map, _debugIsInternal) {
21+
var resolveValue = function(decl, map, /*internal debugging*/_debugIsInternal) {
2222

2323
var resultantValue = decl.value;
2424
var warnings = [];
@@ -31,7 +31,6 @@ var resolveValue = function(decl, map, _debugIsInternal) {
3131
var variablesUsedInValue = Object.keys(variablesUsedInValueMap);
3232

3333

34-
3534
// Resolve any var(...) substitutons
3635
var isResultantValueUndefined = false;
3736
resultantValue = resultantValue.replace(new RegExp(RE_VAR_FUNC.source, 'g'), function(match, variableName, fallback) {
@@ -50,7 +49,7 @@ var resolveValue = function(decl, map, _debugIsInternal) {
5049
//console.log(debugIndent, generateScopeList(decl.parent, true));
5150
//console.log(debugIndent, generateScopeList(varDeclMapItem.parent, true));
5251
//console.log(debugIndent, 'isNodeUnderScope', isNodeUnderScope(decl.parent, varDeclMapItem.parent), varDeclMapItem.decl.value);
53-
52+
5453
if(
5554
isNodeUnderScope(decl.parent, varDeclMapItem.parent) &&
5655
// And if the currently matched declaration is `!important`, it will take another `!important` to override it

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "postcss-css-variables",
3-
"version": "0.3.6",
3+
"version": "0.3.7",
44
"description": "PostCSS plugin to transform CSS Custom Properties(CSS variables) syntax into a static representation",
55
"keywords": [
66
"postcss",
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
2+
.baz {
3+
--baz-width: 180px;
4+
}
5+
6+
.baz .qux {
7+
--baz-width: 300px;
8+
}
9+
10+
.baz > .qux > .wan {
11+
width: var(--baz-width);
12+
}
13+
14+
.some-parent > .baz > .qux > .wan {
15+
width: var(--baz-width);
16+
}
17+
18+
.some-parent > .baz > .wan > .qux {
19+
width: var(--baz-width);
20+
}
21+
22+
23+
24+
25+
.baz .qux .further-child {
26+
--baz-width: 320px;
27+
}
28+
29+
.some-parent > .baz > .qux > .wan .further-child .ger {
30+
width: var(--baz-width);
31+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
.baz > .qux > .wan {
2+
width: 300px;
3+
}
4+
5+
.some-parent > .baz > .qux > .wan {
6+
width: 300px;
7+
}
8+
9+
.some-parent > .baz > .wan > .qux {
10+
width: 300px;
11+
}
12+
13+
14+
15+
.some-parent > .baz > .qux > .wan .further-child .ger {
16+
width: 320px;
17+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
.foo > .bar {
2+
--foo-bar-width: 400px;
3+
}
4+
5+
.foo > .bar > .baz {
6+
width: var(--foo-bar-width);
7+
}
8+
9+
.qux + .foo > .bar > .baz {
10+
width: var(--foo-bar-width);
11+
}
12+
13+
.foo > .bar + .baz {
14+
/* undefined */
15+
width: var(--foo-bar-width);
16+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.foo > .bar > .baz {
2+
width: 400px;
3+
}
4+
5+
.qux + .foo > .bar > .baz {
6+
width: 400px;
7+
}
8+
9+
.foo > .bar + .baz {
10+
/* undefined */
11+
width: undefined;
12+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.foo {
2+
--foo-width: 150px;
3+
}
4+
5+
.foo > .bar {
6+
width: var(--foo-width);
7+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.foo > .bar {
2+
width: 150px;
3+
}

0 commit comments

Comments
 (0)