Skip to content

Commit 3a0bd6f

Browse files
committed
Alpha update
1 parent 096a697 commit 3a0bd6f

File tree

2 files changed

+140
-16
lines changed

2 files changed

+140
-16
lines changed

dist/less-1.4.0-alpha.js

Lines changed: 136 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1334,29 +1334,38 @@ less.Parser = function Parser(env) {
13341334
restore();
13351335
}
13361336
},
1337-
rule: function () {
1337+
rule: function (tryAnonymous) {
13381338
var name, value, c = input.charAt(i), important, match;
13391339
save();
13401340

13411341
if (c === '.' || c === '#' || c === '&') { return }
13421342

13431343
if (name = $(this.variable) || $(this.property)) {
1344-
if (!env.compress && (name.charAt(0) != '@') && (match = /^([^@+\/'"*`(;{}-]*);/.exec(chunks[j]))) {
1345-
i += match[0].length - 1;
1346-
value = new(tree.Anonymous)(match[1]);
1347-
} else {
1348-
value = $(this.value);
1349-
}
1344+
// prefer to try to parse first if its a variable or we are compressing
1345+
// but always fallback on the other one
1346+
value = !tryAnonymous && (env.compress || (name.charAt(0) === '@')) ?
1347+
($(this.value) || $(this.anonymousValue)) :
1348+
($(this.anonymousValue) || $(this.value));
1349+
13501350
important = $(this.important);
13511351

13521352
if (value && $(this.end)) {
13531353
return new(tree.Rule)(name, value, important, memo, env.currentFileInfo);
13541354
} else {
13551355
furthest = i;
13561356
restore();
1357+
if (value && !tryAnonymous) {
1358+
return this.rule(true);
1359+
}
13571360
}
13581361
}
13591362
},
1363+
anonymousValue: function () {
1364+
if (match = /^([^@+\/'"*`(;{}-]*);/.exec(chunks[j])) {
1365+
i += match[0].length - 1;
1366+
return new(tree.Anonymous)(match[1]);
1367+
}
1368+
},
13601369

13611370
//
13621371
// An @import directive
@@ -4982,8 +4991,11 @@ tree.jsify = function (obj) {
49824991
return allSelectorsExtend.clone();
49834992
});
49844993
for(j = 0; j < extendList.length; j++) {
4994+
this.foundExtends = true;
49854995
extend = extendList[j];
49864996
extend.findSelfSelectors(selectorPath);
4997+
extend.ruleset = rulesetNode;
4998+
if (j === 0) { extend.firstExtendOnThisSelectorPath = true; }
49874999
this.allExtendsStack[this.allExtendsStack.length-1].push(extend);
49885000
}
49895001
}
@@ -5013,16 +5025,120 @@ tree.jsify = function (obj) {
50135025

50145026
tree.processExtendsVisitor = function() {
50155027
this._visitor = new tree.visitor(this);
5016-
this._searches
50175028
};
50185029

50195030
tree.processExtendsVisitor.prototype = {
50205031
run: function(root) {
50215032
var extendFinder = new tree.extendFinderVisitor();
50225033
extendFinder.run(root);
5034+
if (!extendFinder.foundExtends) { return root; }
5035+
root.allExtends = root.allExtends.concat(this.doExtendChaining(root.allExtends, root.allExtends));
50235036
this.allExtendsStack = [root.allExtends];
50245037
return this._visitor.visit(root);
50255038
},
5039+
doExtendChaining: function (extendsList, extendsListTarget, iterationCount) {
5040+
//
5041+
// chaining is different from normal extension.. if we extend an extend then we are not just copying, altering and pasting
5042+
// the selector we would do normally, but we are also adding an extend with the same target selector
5043+
// this means this new extend can then go and alter other extends
5044+
//
5045+
// this method deals with all the chaining work - without it, extend is flat and doesn't work on other extend selectors
5046+
// this is also the most expensive.. and a match on one selector can cause an extension of a selector we had already processed if
5047+
// we look at each selector at a time, as is done in visitRuleset
5048+
5049+
var extendIndex, targetExtendIndex, matches, extendsToAdd = [], newSelector, extendVisitor = this, selectorPath, extend, targetExtend;
5050+
5051+
iterationCount = iterationCount || 0;
5052+
5053+
//loop through comparing every extend with every target extend.
5054+
// a target extend is the one on the ruleset we are looking at copy/edit/pasting in place
5055+
// e.g. .a:extend(.b) {} and .b:extend(.c) {} then the first extend extends the second one
5056+
// and the second is the target.
5057+
// the seperation into two lists allows us to process a subset of chains with a bigger set, as is the
5058+
// case when processing media queries
5059+
for(extendIndex = 0; extendIndex < extendsList.length; extendIndex++){
5060+
for(targetExtendIndex = 0; targetExtendIndex < extendsListTarget.length; targetExtendIndex++){
5061+
5062+
extend = extendsList[extendIndex];
5063+
targetExtend = extendsListTarget[targetExtendIndex];
5064+
5065+
// look for circular references
5066+
if (this.inInheritanceChain(targetExtend, extend)) { continue; }
5067+
5068+
// find a match in the target extends self selector (the bit before :extend)
5069+
selectorPath = [targetExtend.selfSelectors[0]];
5070+
matches = extendVisitor.findMatch(extend, selectorPath);
5071+
5072+
if (matches.length) {
5073+
5074+
// we found a match, so for each self selector..
5075+
extend.selfSelectors.forEach(function(selfSelector) {
5076+
5077+
// process the extend as usual
5078+
newSelector = extendVisitor.extendSelector(matches, selectorPath, selfSelector);
5079+
5080+
// but now we create a new extend from it
5081+
newExtend = new(tree.Extend)(targetExtend.selector, targetExtend.option, 0);
5082+
newExtend.selfSelectors = newSelector;
5083+
5084+
// add the extend onto the list of extends for that selector
5085+
newSelector[newSelector.length-1].extendList = [newExtend];
5086+
5087+
// record that we need to add it.
5088+
extendsToAdd.push(newExtend);
5089+
newExtend.ruleset = targetExtend.ruleset;
5090+
5091+
//remember its parents for circular references
5092+
newExtend.parents = [targetExtend, extend];
5093+
5094+
// only process the selector once.. if we have :extend(.a,.b) then multiple
5095+
// extends will look at the same selector path, so when extending
5096+
// we know that any others will be duplicates in terms of what is added to the css
5097+
if (targetExtend.firstExtendOnThisSelectorPath) {
5098+
newExtend.firstExtendOnThisSelectorPath = true;
5099+
targetExtend.ruleset.paths.push(newSelector);
5100+
}
5101+
});
5102+
}
5103+
}
5104+
}
5105+
5106+
if (extendsToAdd.length) {
5107+
// try to detect circular references to stop a stack overflow.
5108+
// may no longer be needed.
5109+
this.extendChainCount++;
5110+
if (iterationCount > 100) {
5111+
var selectorOne = "{unable to calculate}";
5112+
var selectorTwo = "{unable to calculate}";
5113+
try
5114+
{
5115+
selectorOne = extendsToAdd[0].selfSelectors[0].toCSS();
5116+
selectorTwo = extendsToAdd[0].selector.toCSS();
5117+
}
5118+
catch(e) {}
5119+
throw {message: "extend circular reference detected. One of the circular extends is currently:"+selectorOne+":extend(" + selectorTwo+")"};
5120+
}
5121+
5122+
// now process the new extends on the existing rules so that we can handle a extending b extending c ectending d extending e...
5123+
return extendsToAdd.concat(extendVisitor.doExtendChaining(extendsToAdd, extendsListTarget, iterationCount+1));
5124+
} else {
5125+
return extendsToAdd;
5126+
}
5127+
},
5128+
inInheritanceChain: function (possibleParent, possibleChild) {
5129+
if (possibleParent === possibleChild) {
5130+
return true;
5131+
}
5132+
if (possibleChild.parents) {
5133+
if (this.inInheritanceChain(possibleParent, possibleChild.parents[0])) {
5134+
return true;
5135+
}
5136+
if (this.inInheritanceChain(possibleParent, possibleChild.parents[1])) {
5137+
return true;
5138+
}
5139+
}
5140+
return false;
5141+
},
50265142
visitRule: function (ruleNode, visitArgs) {
50275143
visitArgs.visitDeeper = false;
50285144
},
@@ -5044,12 +5160,16 @@ tree.jsify = function (obj) {
50445160
for(pathIndex = 0; pathIndex < rulesetNode.paths.length; pathIndex++) {
50455161

50465162
selectorPath = rulesetNode.paths[pathIndex];
5163+
5164+
// extending extends happens initially, before the main pass
5165+
if (selectorPath[selectorPath.length-1].extendList.length) { continue; }
5166+
50475167
matches = this.findMatch(allExtends[extendIndex], selectorPath);
50485168

50495169
if (matches.length) {
50505170

50515171
allExtends[extendIndex].selfSelectors.forEach(function(selfSelector) {
5052-
selectorsToAdd.push(extendVisitor.extendSelector(matches, selfSelector));
5172+
selectorsToAdd.push(extendVisitor.extendSelector(matches, selectorPath, selfSelector));
50535173
});
50545174
}
50555175
}
@@ -5124,7 +5244,7 @@ tree.jsify = function (obj) {
51245244
}
51255245
return matches;
51265246
},
5127-
extendSelector:function (matches, replacementSelector) {
5247+
extendSelector:function (matches, selectorPath, replacementSelector) {
51285248

51295249
//for a set of matches, replace each match with the replacement selector
51305250

@@ -5179,13 +5299,17 @@ tree.jsify = function (obj) {
51795299
visitRulesetOut: function (rulesetNode) {
51805300
},
51815301
visitMedia: function (mediaNode, visitArgs) {
5182-
this.allExtendsStack.push(mediaNode.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]));
5302+
var newAllExtends = mediaNode.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]);
5303+
newAllExtends = newAllExtends.concat(this.doExtendChaining(newAllExtends, mediaNode.allExtends));
5304+
this.allExtendsStack.push(newAllExtends);
51835305
},
51845306
visitMediaOut: function (mediaNode) {
51855307
this.allExtendsStack.length = this.allExtendsStack.length - 1;
51865308
},
51875309
visitDirective: function (directiveNode, visitArgs) {
5188-
this.allExtendsStack.push(directiveNode.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]));
5310+
var newAllExtends = directiveNode.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]);
5311+
newAllExtends = newAllExtends.concat(this.doExtendChaining(newAllExtends, directiveNode.allExtends));
5312+
this.allExtendsStack.push(newAllExtends);
51895313
},
51905314
visitDirectiveOut: function (directiveNode) {
51915315
this.allExtendsStack.length = this.allExtendsStack.length - 1;

0 commit comments

Comments
 (0)