Skip to content

Commit 096a697

Browse files
committed
support media queries in extend chaining. Also tidied up. Fixes less#1213
1 parent 2ff9ae5 commit 096a697

File tree

3 files changed

+90
-7
lines changed

3 files changed

+90
-7
lines changed

lib/less/extend-visitor.js

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
extend = extendList[j];
4646
extend.findSelfSelectors(selectorPath);
4747
extend.ruleset = rulesetNode;
48+
if (j === 0) { extend.firstExtendOnThisSelectorPath = true; }
4849
this.allExtendsStack[this.allExtendsStack.length-1].push(extend);
4950
}
5051
}
@@ -86,37 +87,75 @@
8687
return this._visitor.visit(root);
8788
},
8889
doExtendChaining: function (extendsList, extendsListTarget, iterationCount) {
89-
var extendIndex, targetExtendIndex, matches, extendsToAdd = [], newSelector, extendVisitor = this, selectorPath;
90+
//
91+
// chaining is different from normal extension.. if we extend an extend then we are not just copying, altering and pasting
92+
// the selector we would do normally, but we are also adding an extend with the same target selector
93+
// this means this new extend can then go and alter other extends
94+
//
95+
// this method deals with all the chaining work - without it, extend is flat and doesn't work on other extend selectors
96+
// this is also the most expensive.. and a match on one selector can cause an extension of a selector we had already processed if
97+
// we look at each selector at a time, as is done in visitRuleset
98+
99+
var extendIndex, targetExtendIndex, matches, extendsToAdd = [], newSelector, extendVisitor = this, selectorPath, extend, targetExtend;
90100

91101
iterationCount = iterationCount || 0;
92102

103+
//loop through comparing every extend with every target extend.
104+
// a target extend is the one on the ruleset we are looking at copy/edit/pasting in place
105+
// e.g. .a:extend(.b) {} and .b:extend(.c) {} then the first extend extends the second one
106+
// and the second is the target.
107+
// the seperation into two lists allows us to process a subset of chains with a bigger set, as is the
108+
// case when processing media queries
93109
for(extendIndex = 0; extendIndex < extendsList.length; extendIndex++){
94110
for(targetExtendIndex = 0; targetExtendIndex < extendsListTarget.length; targetExtendIndex++){
95111

96-
var extend = extendsList[extendIndex];
97-
var targetExtend = extendsListTarget[targetExtendIndex];
112+
extend = extendsList[extendIndex];
113+
targetExtend = extendsListTarget[targetExtendIndex];
114+
115+
// look for circular references
98116
if (this.inInheritanceChain(targetExtend, extend)) { continue; }
99117

118+
// find a match in the target extends self selector (the bit before :extend)
100119
selectorPath = [targetExtend.selfSelectors[0]];
101120
matches = extendVisitor.findMatch(extend, selectorPath);
102121

103122
if (matches.length) {
104123

124+
// we found a match, so for each self selector..
105125
extend.selfSelectors.forEach(function(selfSelector) {
126+
127+
// process the extend as usual
106128
newSelector = extendVisitor.extendSelector(matches, selectorPath, selfSelector);
129+
130+
// but now we create a new extend from it
107131
newExtend = new(tree.Extend)(targetExtend.selector, targetExtend.option, 0);
108132
newExtend.selfSelectors = newSelector;
133+
134+
// add the extend onto the list of extends for that selector
109135
newSelector[newSelector.length-1].extendList = [newExtend];
136+
137+
// record that we need to add it.
110138
extendsToAdd.push(newExtend);
111139
newExtend.ruleset = targetExtend.ruleset;
140+
141+
//remember its parents for circular references
112142
newExtend.parents = [targetExtend, extend];
113-
targetExtend.ruleset.paths.push(newSelector);
143+
144+
// only process the selector once.. if we have :extend(.a,.b) then multiple
145+
// extends will look at the same selector path, so when extending
146+
// we know that any others will be duplicates in terms of what is added to the css
147+
if (targetExtend.firstExtendOnThisSelectorPath) {
148+
newExtend.firstExtendOnThisSelectorPath = true;
149+
targetExtend.ruleset.paths.push(newSelector);
150+
}
114151
});
115152
}
116153
}
117154
}
118155

119156
if (extendsToAdd.length) {
157+
// try to detect circular references to stop a stack overflow.
158+
// may no longer be needed.
120159
this.extendChainCount++;
121160
if (iterationCount > 100) {
122161
var selectorOne = "{unable to calculate}";
@@ -129,6 +168,8 @@
129168
catch(e) {}
130169
throw {message: "extend circular reference detected. One of the circular extends is currently:"+selectorOne+":extend(" + selectorTwo+")"};
131170
}
171+
172+
// now process the new extends on the existing rules so that we can handle a extending b extending c ectending d extending e...
132173
return extendsToAdd.concat(extendVisitor.doExtendChaining(extendsToAdd, extendsListTarget, iterationCount+1));
133174
} else {
134175
return extendsToAdd;
@@ -308,13 +349,17 @@
308349
visitRulesetOut: function (rulesetNode) {
309350
},
310351
visitMedia: function (mediaNode, visitArgs) {
311-
this.allExtendsStack.push(mediaNode.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]));
352+
var newAllExtends = mediaNode.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]);
353+
newAllExtends = newAllExtends.concat(this.doExtendChaining(newAllExtends, mediaNode.allExtends));
354+
this.allExtendsStack.push(newAllExtends);
312355
},
313356
visitMediaOut: function (mediaNode) {
314357
this.allExtendsStack.length = this.allExtendsStack.length - 1;
315358
},
316359
visitDirective: function (directiveNode, visitArgs) {
317-
this.allExtendsStack.push(directiveNode.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]));
360+
var newAllExtends = directiveNode.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]);
361+
newAllExtends = newAllExtends.concat(this.doExtendChaining(newAllExtends, directiveNode.allExtends));
362+
this.allExtendsStack.push(newAllExtends);
318363
},
319364
visitDirectiveOut: function (directiveNode) {
320365
this.allExtendsStack.length = this.allExtendsStack.length - 1;

test/css/extend-chaining.css

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,22 @@
5151
.y {
5252
color: z;
5353
}
54+
@media tv {
55+
.ma,
56+
.mb,
57+
.mc {
58+
color: black;
59+
}
60+
.md,
61+
.ma,
62+
.mb,
63+
.mc {
64+
color: white;
65+
}
66+
}
67+
@media tv and plasma {
68+
.me,
69+
.mf {
70+
background: red;
71+
}
72+
}

test/less/extend-chaining.less

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,23 @@
5757
}
5858
.z:extend(.y) {
5959
color: z;
60-
}
60+
}
61+
62+
// media queries - dont extend outside, do extend inside
63+
64+
@media tv {
65+
.ma:extend(.a,.b,.c,.d,.e,.f,.g,.h,.i,.j,.k,.l,.m,.n,.o,.p,.q,.r,.s,.t,.u,.v,.w,.x,.y,.z,.md) {
66+
color: black;
67+
}
68+
.md {
69+
color: white;
70+
}
71+
@media plasma {
72+
.me, .mf {
73+
&:extend(.mb,.md);
74+
background: red;
75+
}
76+
}
77+
}
78+
.mb:extend(.ma) {};
79+
.mc:extend(.mb) {};

0 commit comments

Comments
 (0)