node-less
Version:
Less Compiler For Node
244 lines (239 loc) • 13.4 kB
JavaScript
(function(module) {
var visitor = require('./visitor.js'),
Extend = require('./extend.js'),
Selector = require('./selector.js'),
Element = require('./element.js'),
extendFinderVisitor = require('./extendFinderVisitor.js'),
Attribute = require('./attribute.js');
var processExtendsVisitor = function() {
this._visitor = new visitor(this);
};
processExtendsVisitor.prototype = {
run: function(root) {
var extendFinder = new extendFinderVisitor();
extendFinder.run(root);
if (!extendFinder.foundExtends) { return root; }
root.allExtends = root.allExtends.concat(this.doExtendChaining(root.allExtends, root.allExtends));
this.allExtendsStack = [root.allExtends];
return this._visitor.visit(root);
},
doExtendChaining: function (extendsList, extendsListTarget, iterationCount) {
var extendIndex, targetExtendIndex, matches, extendsToAdd = [], newSelector, extendVisitor = this, selectorPath, extend, targetExtend, newExtend;
iterationCount = iterationCount || 0;
for(extendIndex = 0; extendIndex < extendsList.length; extendIndex++){
for(targetExtendIndex = 0; targetExtendIndex < extendsListTarget.length; targetExtendIndex++){
extend = extendsList[extendIndex];
targetExtend = extendsListTarget[targetExtendIndex];
if (this.inInheritanceChain(targetExtend, extend)) { continue; }
selectorPath = [targetExtend.selfSelectors[0]];
matches = extendVisitor.findMatch(extend, selectorPath);
if (matches.length) {
extend.selfSelectors.forEach(function(selfSelector) {
newSelector = extendVisitor.extendSelector(matches, selectorPath, selfSelector);
newExtend = new Extend(targetExtend.selector, targetExtend.option, 0);
newExtend.selfSelectors = newSelector;
newSelector[newSelector.length-1].extendList = [newExtend];
extendsToAdd.push(newExtend);
newExtend.ruleset = targetExtend.ruleset;
newExtend.parents = [targetExtend, extend];
if (targetExtend.firstExtendOnThisSelectorPath) {
newExtend.firstExtendOnThisSelectorPath = true;
targetExtend.ruleset.paths.push(newSelector);
}
});
}
}
}
if (extendsToAdd.length) {
this.extendChainCount++;
if (iterationCount > 100) {
var selectorOne = "{unable to calculate}";
var selectorTwo = "{unable to calculate}";
try
{
selectorOne = extendsToAdd[0].selfSelectors[0].toCSS();
selectorTwo = extendsToAdd[0].selector.toCSS();
}
catch(e) {}
throw {message: "extend circular reference detected. One of the circular extends is currently:"+selectorOne+":extend(" + selectorTwo+")"};
}
return extendsToAdd.concat(extendVisitor.doExtendChaining(extendsToAdd, extendsListTarget, iterationCount+1));
} else {
return extendsToAdd;
}
},
inInheritanceChain: function (possibleParent, possibleChild) {
if (possibleParent === possibleChild) {
return true;
}
if (possibleChild.parents) {
if (this.inInheritanceChain(possibleParent, possibleChild.parents[0])) {
return true;
}
if (this.inInheritanceChain(possibleParent, possibleChild.parents[1])) {
return true;
}
}
return false;
},
visitRule: function (ruleNode, visitArgs) {
visitArgs.visitDeeper = false;
},
visitMixinDefinition: function (mixinDefinitionNode, visitArgs) {
visitArgs.visitDeeper = false;
},
visitSelector: function (selectorNode, visitArgs) {
visitArgs.visitDeeper = false;
},
visitRuleset: function (rulesetNode, visitArgs) {
if (rulesetNode.root) {
return;
}
var matches, pathIndex, extendIndex, allExtends = this.allExtendsStack[this.allExtendsStack.length-1], selectorsToAdd = [], extendVisitor = this, selectorPath;
for(extendIndex = 0; extendIndex < allExtends.length; extendIndex++) {
for(pathIndex = 0; pathIndex < rulesetNode.paths.length; pathIndex++) {
selectorPath = rulesetNode.paths[pathIndex];
if (selectorPath[selectorPath.length-1].extendList.length) { continue; }
matches = this.findMatch(allExtends[extendIndex], selectorPath);
if (matches.length) {
allExtends[extendIndex].selfSelectors.forEach(function(selfSelector) {
selectorsToAdd.push(extendVisitor.extendSelector(matches, selectorPath, selfSelector));
});
}
}
}
rulesetNode.paths = rulesetNode.paths.concat(selectorsToAdd);
},
findMatch: function (extend, haystackSelectorPath) {
var haystackSelectorIndex, hackstackSelector, hackstackElementIndex, haystackElement,
targetCombinator, i,
extendVisitor = this,
needleElements = extend.selector.elements,
potentialMatches = [], potentialMatch, matches = [];
for(haystackSelectorIndex = 0; haystackSelectorIndex < haystackSelectorPath.length; haystackSelectorIndex++) {
hackstackSelector = haystackSelectorPath[haystackSelectorIndex];
for(hackstackElementIndex = 0; hackstackElementIndex < hackstackSelector.elements.length; hackstackElementIndex++) {
haystackElement = hackstackSelector.elements[hackstackElementIndex];
if (extend.allowBefore || (haystackSelectorIndex == 0 && hackstackElementIndex == 0)) {
potentialMatches.push({pathIndex: haystackSelectorIndex, index: hackstackElementIndex, matched: 0, initialCombinator: haystackElement.combinator});
}
for(i = 0; i < potentialMatches.length; i++) {
potentialMatch = potentialMatches[i];
targetCombinator = haystackElement.combinator.value;
if (targetCombinator == '' && hackstackElementIndex === 0) {
targetCombinator = ' ';
}
if (!extendVisitor.isElementValuesEqual(needleElements[potentialMatch.matched].value, haystackElement.value) ||
(potentialMatch.matched > 0 && needleElements[potentialMatch.matched].combinator.value !== targetCombinator)) {
potentialMatch = null;
} else {
potentialMatch.matched++;
}
if (potentialMatch) {
potentialMatch.finished = potentialMatch.matched === needleElements.length;
if (potentialMatch.finished &&
(!extend.allowAfter && (hackstackElementIndex+1 < hackstackSelector.elements.length || haystackSelectorIndex+1 < haystackSelectorPath.length))) {
potentialMatch = null;
}
}
if (potentialMatch) {
if (potentialMatch.finished) {
potentialMatch.length = needleElements.length;
potentialMatch.endPathIndex = haystackSelectorIndex;
potentialMatch.endPathElementIndex = hackstackElementIndex + 1; // index after end of match
potentialMatches.length = 0; // we don't allow matches to overlap, so start matching again
matches.push(potentialMatch);
}
} else {
potentialMatches.splice(i, 1);
i--;
}
}
}
}
return matches;
},
isElementValuesEqual: function(elementValue1, elementValue2) {
if (typeof elementValue1 === "string" || typeof elementValue2 === "string") {
return elementValue1 === elementValue2;
}
if (elementValue1 instanceof Attribute) {
if (elementValue1.op !== elementValue2.op || elementValue1.key !== elementValue2.key) {
return false;
}
if (!elementValue1.value || !elementValue2.value) {
if (elementValue1.value || elementValue2.value) {
return false;
}
return true;
}
elementValue1 = elementValue1.value.value || elementValue1.value;
elementValue2 = elementValue2.value.value || elementValue2.value;
return elementValue1 === elementValue2;
}
return false;
},
extendSelector:function (matches, selectorPath, replacementSelector) {
var currentSelectorPathIndex = 0,
currentSelectorPathElementIndex = 0,
path = [],
matchIndex,
selector,
firstElement,
match;
for (matchIndex = 0; matchIndex < matches.length; matchIndex++) {
match = matches[matchIndex];
selector = selectorPath[match.pathIndex];
firstElement = new Element(
match.initialCombinator,
replacementSelector.elements[0].value,
replacementSelector.elements[0].index
);
if (match.pathIndex > currentSelectorPathIndex && currentSelectorPathElementIndex > 0) {
path[path.length - 1].elements = path[path.length - 1].elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex));
currentSelectorPathElementIndex = 0;
currentSelectorPathIndex++;
}
path = path.concat(selectorPath.slice(currentSelectorPathIndex, match.pathIndex));
path.push(new Selector(
selector.elements
.slice(currentSelectorPathElementIndex, match.index)
.concat([firstElement])
.concat(replacementSelector.elements.slice(1))
));
currentSelectorPathIndex = match.endPathIndex;
currentSelectorPathElementIndex = match.endPathElementIndex;
if (currentSelectorPathElementIndex >= selector.elements.length) {
currentSelectorPathElementIndex = 0;
currentSelectorPathIndex++;
}
}
if (currentSelectorPathIndex < selectorPath.length && currentSelectorPathElementIndex > 0) {
path[path.length - 1].elements = path[path.length - 1].elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex));
currentSelectorPathElementIndex = 0;
currentSelectorPathIndex++;
}
path = path.concat(selectorPath.slice(currentSelectorPathIndex, selectorPath.length));
return path;
},
visitRulesetOut: function (rulesetNode) {
},
visitMedia: function (mediaNode, visitArgs) {
var newAllExtends = mediaNode.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]);
newAllExtends = newAllExtends.concat(this.doExtendChaining(newAllExtends, mediaNode.allExtends));
this.allExtendsStack.push(newAllExtends);
},
visitMediaOut: function (mediaNode) {
this.allExtendsStack.length = this.allExtendsStack.length - 1;
},
visitDirective: function (directiveNode, visitArgs) {
var newAllExtends = directiveNode.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]);
newAllExtends = newAllExtends.concat(this.doExtendChaining(newAllExtends, directiveNode.allExtends));
this.allExtendsStack.push(newAllExtends);
},
visitDirectiveOut: function (directiveNode) {
this.allExtendsStack.length = this.allExtendsStack.length - 1;
}
};
module.exports = processExtendsVisitor;
})(module);