Also remove dep-injection from visitors, none of which use environment. make functions used as classes TitleCase

This commit is contained in:
Luke Page
2014-08-24 18:47:25 +01:00
parent fe5e2bf7cb
commit e325aec74d
8 changed files with 909 additions and 912 deletions

View File

@@ -4,7 +4,7 @@ var less = {
};
less.tree = require('./tree/index.js');
less.visitor = require('./visitor/index.js')(less, less.tree);
less.visitor = require('./visitor/index.js');
less.Parser = (require('./parser'))(less, less.tree, less.visitor);
less.functions = require('./functions/index.js')(less);
less.contexts = require("./contexts.js");

View File

@@ -485,9 +485,9 @@ var Parser = function Parser(env) {
try {
var preEvalVisitors = [],
visitors = [
new(visitor.joinSelectorVisitor)(),
new(visitor.extendVisitor)(),
new(visitor.toCSSVisitor)({compress: Boolean(options.compress)})
new(visitor.JoinSelectorVisitor)(),
new(visitor.ExtendVisitor)(),
new(visitor.ToCSSVisitor)({compress: Boolean(options.compress)})
], i, root = this;
if (options.plugins) {
@@ -623,7 +623,7 @@ var Parser = function Parser(env) {
};
if (env.processImports !== false) {
new visitor.importVisitor(this.imports, finish)
new visitor.ImportVisitor(this.imports, finish)
.run(root);
} else {
return finish();

View File

@@ -1,417 +1,418 @@
module.exports = function (visitor, tree) {
/*jshint loopfunc:true */
var tree = require("../tree/index.js"),
Visitor = require("./visitor.js");
var extendFinderVisitor = function() {
this._visitor = new visitor(this);
this.contexts = [];
this.allExtendsStack = [[]];
};
/*jshint loopfunc:true */
extendFinderVisitor.prototype = {
run: function (root) {
root = this._visitor.visit(root);
root.allExtends = this.allExtendsStack[0];
return root;
},
visitRule: function (ruleNode, visitArgs) {
visitArgs.visitDeeper = false;
},
visitMixinDefinition: function (mixinDefinitionNode, visitArgs) {
visitArgs.visitDeeper = false;
},
visitRuleset: function (rulesetNode, visitArgs) {
if (rulesetNode.root) {
return;
var ExtendFinderVisitor = function() {
this._visitor = new Visitor(this);
this.contexts = [];
this.allExtendsStack = [[]];
};
ExtendFinderVisitor.prototype = {
run: function (root) {
root = this._visitor.visit(root);
root.allExtends = this.allExtendsStack[0];
return root;
},
visitRule: function (ruleNode, visitArgs) {
visitArgs.visitDeeper = false;
},
visitMixinDefinition: function (mixinDefinitionNode, visitArgs) {
visitArgs.visitDeeper = false;
},
visitRuleset: function (rulesetNode, visitArgs) {
if (rulesetNode.root) {
return;
}
var i, j, extend, allSelectorsExtendList = [], extendList;
// get &:extend(.a); rules which apply to all selectors in this ruleset
var rules = rulesetNode.rules, ruleCnt = rules ? rules.length : 0;
for(i = 0; i < ruleCnt; i++) {
if (rulesetNode.rules[i] instanceof tree.Extend) {
allSelectorsExtendList.push(rules[i]);
rulesetNode.extendOnEveryPath = true;
}
}
// now find every selector and apply the extends that apply to all extends
// and the ones which apply to an individual extend
var paths = rulesetNode.paths;
for(i = 0; i < paths.length; i++) {
var selectorPath = paths[i],
selector = selectorPath[selectorPath.length - 1],
selExtendList = selector.extendList;
extendList = selExtendList ? selExtendList.slice(0).concat(allSelectorsExtendList)
: allSelectorsExtendList;
if (extendList) {
extendList = extendList.map(function(allSelectorsExtend) {
return allSelectorsExtend.clone();
});
}
var i, j, extend, allSelectorsExtendList = [], extendList;
// get &:extend(.a); rules which apply to all selectors in this ruleset
var rules = rulesetNode.rules, ruleCnt = rules ? rules.length : 0;
for(i = 0; i < ruleCnt; i++) {
if (rulesetNode.rules[i] instanceof tree.Extend) {
allSelectorsExtendList.push(rules[i]);
rulesetNode.extendOnEveryPath = true;
}
for(j = 0; j < extendList.length; j++) {
this.foundExtends = true;
extend = extendList[j];
extend.findSelfSelectors(selectorPath);
extend.ruleset = rulesetNode;
if (j === 0) { extend.firstExtendOnThisSelectorPath = true; }
this.allExtendsStack[this.allExtendsStack.length-1].push(extend);
}
}
// now find every selector and apply the extends that apply to all extends
// and the ones which apply to an individual extend
var paths = rulesetNode.paths;
for(i = 0; i < paths.length; i++) {
var selectorPath = paths[i],
selector = selectorPath[selectorPath.length - 1],
selExtendList = selector.extendList;
this.contexts.push(rulesetNode.selectors);
},
visitRulesetOut: function (rulesetNode) {
if (!rulesetNode.root) {
this.contexts.length = this.contexts.length - 1;
}
},
visitMedia: function (mediaNode, visitArgs) {
mediaNode.allExtends = [];
this.allExtendsStack.push(mediaNode.allExtends);
},
visitMediaOut: function (mediaNode) {
this.allExtendsStack.length = this.allExtendsStack.length - 1;
},
visitDirective: function (directiveNode, visitArgs) {
directiveNode.allExtends = [];
this.allExtendsStack.push(directiveNode.allExtends);
},
visitDirectiveOut: function (directiveNode) {
this.allExtendsStack.length = this.allExtendsStack.length - 1;
}
};
extendList = selExtendList ? selExtendList.slice(0).concat(allSelectorsExtendList)
: allSelectorsExtendList;
var ProcessExtendsVisitor = function() {
this._visitor = new Visitor(this);
};
if (extendList) {
extendList = extendList.map(function(allSelectorsExtend) {
return allSelectorsExtend.clone();
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) {
//
// chaining is different from normal extension.. if we extend an extend then we are not just copying, altering and pasting
// the selector we would do normally, but we are also adding an extend with the same target selector
// this means this new extend can then go and alter other extends
//
// this method deals with all the chaining work - without it, extend is flat and doesn't work on other extend selectors
// this is also the most expensive.. and a match on one selector can cause an extension of a selector we had already processed if
// we look at each selector at a time, as is done in visitRuleset
var extendIndex, targetExtendIndex, matches, extendsToAdd = [], newSelector, extendVisitor = this, selectorPath, extend, targetExtend, newExtend;
iterationCount = iterationCount || 0;
//loop through comparing every extend with every target extend.
// a target extend is the one on the ruleset we are looking at copy/edit/pasting in place
// e.g. .a:extend(.b) {} and .b:extend(.c) {} then the first extend extends the second one
// and the second is the target.
// the seperation into two lists allows us to process a subset of chains with a bigger set, as is the
// case when processing media queries
for(extendIndex = 0; extendIndex < extendsList.length; extendIndex++){
for(targetExtendIndex = 0; targetExtendIndex < extendsListTarget.length; targetExtendIndex++){
extend = extendsList[extendIndex];
targetExtend = extendsListTarget[targetExtendIndex];
// look for circular references
if( extend.parent_ids.indexOf( targetExtend.object_id ) >= 0 ){ continue; }
// find a match in the target extends self selector (the bit before :extend)
selectorPath = [targetExtend.selfSelectors[0]];
matches = extendVisitor.findMatch(extend, selectorPath);
if (matches.length) {
// we found a match, so for each self selector..
extend.selfSelectors.forEach(function(selfSelector) {
// process the extend as usual
newSelector = extendVisitor.extendSelector(matches, selectorPath, selfSelector);
// but now we create a new extend from it
newExtend = new(tree.Extend)(targetExtend.selector, targetExtend.option, 0);
newExtend.selfSelectors = newSelector;
// add the extend onto the list of extends for that selector
newSelector[newSelector.length-1].extendList = [newExtend];
// record that we need to add it.
extendsToAdd.push(newExtend);
newExtend.ruleset = targetExtend.ruleset;
//remember its parents for circular references
newExtend.parent_ids = newExtend.parent_ids.concat(targetExtend.parent_ids, extend.parent_ids);
// only process the selector once.. if we have :extend(.a,.b) then multiple
// extends will look at the same selector path, so when extending
// we know that any others will be duplicates in terms of what is added to the css
if (targetExtend.firstExtendOnThisSelectorPath) {
newExtend.firstExtendOnThisSelectorPath = true;
targetExtend.ruleset.paths.push(newSelector);
}
});
}
for(j = 0; j < extendList.length; j++) {
this.foundExtends = true;
extend = extendList[j];
extend.findSelfSelectors(selectorPath);
extend.ruleset = rulesetNode;
if (j === 0) { extend.firstExtendOnThisSelectorPath = true; }
this.allExtendsStack[this.allExtendsStack.length-1].push(extend);
}
}
this.contexts.push(rulesetNode.selectors);
},
visitRulesetOut: function (rulesetNode) {
if (!rulesetNode.root) {
this.contexts.length = this.contexts.length - 1;
}
},
visitMedia: function (mediaNode, visitArgs) {
mediaNode.allExtends = [];
this.allExtendsStack.push(mediaNode.allExtends);
},
visitMediaOut: function (mediaNode) {
this.allExtendsStack.length = this.allExtendsStack.length - 1;
},
visitDirective: function (directiveNode, visitArgs) {
directiveNode.allExtends = [];
this.allExtendsStack.push(directiveNode.allExtends);
},
visitDirectiveOut: function (directiveNode) {
this.allExtendsStack.length = this.allExtendsStack.length - 1;
}
};
var processExtendsVisitor = function() {
this._visitor = new visitor(this);
};
if (extendsToAdd.length) {
// try to detect circular references to stop a stack overflow.
// may no longer be needed.
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+")"};
}
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) {
//
// chaining is different from normal extension.. if we extend an extend then we are not just copying, altering and pasting
// the selector we would do normally, but we are also adding an extend with the same target selector
// this means this new extend can then go and alter other extends
//
// this method deals with all the chaining work - without it, extend is flat and doesn't work on other extend selectors
// this is also the most expensive.. and a match on one selector can cause an extension of a selector we had already processed if
// we look at each selector at a time, as is done in visitRuleset
// now process the new extends on the existing rules so that we can handle a extending b extending c ectending d extending e...
return extendsToAdd.concat(extendVisitor.doExtendChaining(extendsToAdd, extendsListTarget, iterationCount+1));
} else {
return extendsToAdd;
}
},
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;
var extendIndex, targetExtendIndex, matches, extendsToAdd = [], newSelector, extendVisitor = this, selectorPath, extend, targetExtend, newExtend;
// look at each selector path in the ruleset, find any extend matches and then copy, find and replace
iterationCount = iterationCount || 0;
for(extendIndex = 0; extendIndex < allExtends.length; extendIndex++) {
for(pathIndex = 0; pathIndex < rulesetNode.paths.length; pathIndex++) {
selectorPath = rulesetNode.paths[pathIndex];
//loop through comparing every extend with every target extend.
// a target extend is the one on the ruleset we are looking at copy/edit/pasting in place
// e.g. .a:extend(.b) {} and .b:extend(.c) {} then the first extend extends the second one
// and the second is the target.
// the seperation into two lists allows us to process a subset of chains with a bigger set, as is the
// case when processing media queries
for(extendIndex = 0; extendIndex < extendsList.length; extendIndex++){
for(targetExtendIndex = 0; targetExtendIndex < extendsListTarget.length; targetExtendIndex++){
// extending extends happens initially, before the main pass
if (rulesetNode.extendOnEveryPath) { continue; }
var extendList = selectorPath[selectorPath.length-1].extendList;
if (extendList && extendList.length) { continue; }
extend = extendsList[extendIndex];
targetExtend = extendsListTarget[targetExtendIndex];
matches = this.findMatch(allExtends[extendIndex], selectorPath);
// look for circular references
if( extend.parent_ids.indexOf( targetExtend.object_id ) >= 0 ){ continue; }
if (matches.length) {
// find a match in the target extends self selector (the bit before :extend)
selectorPath = [targetExtend.selfSelectors[0]];
matches = extendVisitor.findMatch(extend, selectorPath);
if (matches.length) {
// we found a match, so for each self selector..
extend.selfSelectors.forEach(function(selfSelector) {
// process the extend as usual
newSelector = extendVisitor.extendSelector(matches, selectorPath, selfSelector);
// but now we create a new extend from it
newExtend = new(tree.Extend)(targetExtend.selector, targetExtend.option, 0);
newExtend.selfSelectors = newSelector;
// add the extend onto the list of extends for that selector
newSelector[newSelector.length-1].extendList = [newExtend];
// record that we need to add it.
extendsToAdd.push(newExtend);
newExtend.ruleset = targetExtend.ruleset;
//remember its parents for circular references
newExtend.parent_ids = newExtend.parent_ids.concat(targetExtend.parent_ids, extend.parent_ids);
// only process the selector once.. if we have :extend(.a,.b) then multiple
// extends will look at the same selector path, so when extending
// we know that any others will be duplicates in terms of what is added to the css
if (targetExtend.firstExtendOnThisSelectorPath) {
newExtend.firstExtendOnThisSelectorPath = true;
targetExtend.ruleset.paths.push(newSelector);
}
});
}
allExtends[extendIndex].selfSelectors.forEach(function(selfSelector) {
selectorsToAdd.push(extendVisitor.extendSelector(matches, selectorPath, selfSelector));
});
}
}
}
rulesetNode.paths = rulesetNode.paths.concat(selectorsToAdd);
},
findMatch: function (extend, haystackSelectorPath) {
//
// look through the haystack selector path to try and find the needle - extend.selector
// returns an array of selector matches that can then be replaced
//
var haystackSelectorIndex, hackstackSelector, hackstackElementIndex, haystackElement,
targetCombinator, i,
extendVisitor = this,
needleElements = extend.selector.elements,
potentialMatches = [], potentialMatch, matches = [];
if (extendsToAdd.length) {
// try to detect circular references to stop a stack overflow.
// may no longer be needed.
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+")"};
// loop through the haystack elements
for(haystackSelectorIndex = 0; haystackSelectorIndex < haystackSelectorPath.length; haystackSelectorIndex++) {
hackstackSelector = haystackSelectorPath[haystackSelectorIndex];
for(hackstackElementIndex = 0; hackstackElementIndex < hackstackSelector.elements.length; hackstackElementIndex++) {
haystackElement = hackstackSelector.elements[hackstackElementIndex];
// if we allow elements before our match we can add a potential match every time. otherwise only at the first element.
if (extend.allowBefore || (haystackSelectorIndex === 0 && hackstackElementIndex === 0)) {
potentialMatches.push({pathIndex: haystackSelectorIndex, index: hackstackElementIndex, matched: 0, initialCombinator: haystackElement.combinator});
}
// now process the new extends on the existing rules so that we can handle a extending b extending c ectending d extending e...
return extendsToAdd.concat(extendVisitor.doExtendChaining(extendsToAdd, extendsListTarget, iterationCount+1));
} else {
return extendsToAdd;
}
},
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(i = 0; i < potentialMatches.length; i++) {
potentialMatch = potentialMatches[i];
// look at each selector path in the ruleset, find any extend matches and then copy, find and replace
for(extendIndex = 0; extendIndex < allExtends.length; extendIndex++) {
for(pathIndex = 0; pathIndex < rulesetNode.paths.length; pathIndex++) {
selectorPath = rulesetNode.paths[pathIndex];
// extending extends happens initially, before the main pass
if (rulesetNode.extendOnEveryPath) { continue; }
var extendList = selectorPath[selectorPath.length-1].extendList;
if (extendList && 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) {
//
// look through the haystack selector path to try and find the needle - extend.selector
// returns an array of selector matches that can then be replaced
//
var haystackSelectorIndex, hackstackSelector, hackstackElementIndex, haystackElement,
targetCombinator, i,
extendVisitor = this,
needleElements = extend.selector.elements,
potentialMatches = [], potentialMatch, matches = [];
// loop through the haystack elements
for(haystackSelectorIndex = 0; haystackSelectorIndex < haystackSelectorPath.length; haystackSelectorIndex++) {
hackstackSelector = haystackSelectorPath[haystackSelectorIndex];
for(hackstackElementIndex = 0; hackstackElementIndex < hackstackSelector.elements.length; hackstackElementIndex++) {
haystackElement = hackstackSelector.elements[hackstackElementIndex];
// if we allow elements before our match we can add a potential match every time. otherwise only at the first element.
if (extend.allowBefore || (haystackSelectorIndex === 0 && hackstackElementIndex === 0)) {
potentialMatches.push({pathIndex: haystackSelectorIndex, index: hackstackElementIndex, matched: 0, initialCombinator: haystackElement.combinator});
// selectors add " " onto the first element. When we use & it joins the selectors together, but if we don't
// then each selector in haystackSelectorPath has a space before it added in the toCSS phase. so we need to work out
// what the resulting combinator will be
targetCombinator = haystackElement.combinator.value;
if (targetCombinator === '' && hackstackElementIndex === 0) {
targetCombinator = ' ';
}
for(i = 0; i < potentialMatches.length; i++) {
potentialMatch = potentialMatches[i];
// if we don't match, null our match to indicate failure
if (!extendVisitor.isElementValuesEqual(needleElements[potentialMatch.matched].value, haystackElement.value) ||
(potentialMatch.matched > 0 && needleElements[potentialMatch.matched].combinator.value !== targetCombinator)) {
potentialMatch = null;
} else {
potentialMatch.matched++;
}
// selectors add " " onto the first element. When we use & it joins the selectors together, but if we don't
// then each selector in haystackSelectorPath has a space before it added in the toCSS phase. so we need to work out
// what the resulting combinator will be
targetCombinator = haystackElement.combinator.value;
if (targetCombinator === '' && hackstackElementIndex === 0) {
targetCombinator = ' ';
}
// if we don't match, null our match to indicate failure
if (!extendVisitor.isElementValuesEqual(needleElements[potentialMatch.matched].value, haystackElement.value) ||
(potentialMatch.matched > 0 && needleElements[potentialMatch.matched].combinator.value !== targetCombinator)) {
// if we are still valid and have finished, test whether we have elements after and whether these are allowed
if (potentialMatch) {
potentialMatch.finished = potentialMatch.matched === needleElements.length;
if (potentialMatch.finished &&
(!extend.allowAfter && (hackstackElementIndex+1 < hackstackSelector.elements.length || haystackSelectorIndex+1 < haystackSelectorPath.length))) {
potentialMatch = null;
} else {
potentialMatch.matched++;
}
// if we are still valid and have finished, test whether we have elements after and whether these are allowed
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 null we remove, if not, we are still valid, so either push as a valid match or continue
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--;
}
// if null we remove, if not, we are still valid, so either push as a valid match or continue
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;
}
return matches;
},
isElementValuesEqual: function(elementValue1, elementValue2) {
if (typeof elementValue1 === "string" || typeof elementValue2 === "string") {
return elementValue1 === elementValue2;
}
if (elementValue1 instanceof tree.Attribute) {
if (elementValue1.op !== elementValue2.op || elementValue1.key !== elementValue2.key) {
return false;
}
if (elementValue1 instanceof tree.Attribute) {
if (elementValue1.op !== elementValue2.op || elementValue1.key !== elementValue2.key) {
if (!elementValue1.value || !elementValue2.value) {
if (elementValue1.value || elementValue2.value) {
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;
}
elementValue1 = elementValue1.value;
elementValue2 = elementValue2.value;
if (elementValue1 instanceof tree.Selector) {
if (!(elementValue2 instanceof tree.Selector) || elementValue1.elements.length !== elementValue2.elements.length) {
return false;
}
for(var i = 0; i <elementValue1.elements.length; i++) {
if (elementValue1.elements[i].combinator.value !== elementValue2.elements[i].combinator.value) {
if (i !== 0 || (elementValue1.elements[i].combinator.value || ' ') !== (elementValue2.elements[i].combinator.value || ' ')) {
return false;
}
}
if (!this.isElementValuesEqual(elementValue1.elements[i].value, elementValue2.elements[i].value)) {
return false;
}
}
return true;
}
return false;
},
extendSelector:function (matches, selectorPath, replacementSelector) {
//for a set of matches, replace each match with the replacement selector
var currentSelectorPathIndex = 0,
currentSelectorPathElementIndex = 0,
path = [],
matchIndex,
selector,
firstElement,
match,
newElements;
for (matchIndex = 0; matchIndex < matches.length; matchIndex++) {
match = matches[matchIndex];
selector = selectorPath[match.pathIndex];
firstElement = new tree.Element(
match.initialCombinator,
replacementSelector.elements[0].value,
replacementSelector.elements[0].index,
replacementSelector.elements[0].currentFileInfo
);
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++;
elementValue1 = elementValue1.value.value || elementValue1.value;
elementValue2 = elementValue2.value.value || elementValue2.value;
return elementValue1 === elementValue2;
}
elementValue1 = elementValue1.value;
elementValue2 = elementValue2.value;
if (elementValue1 instanceof tree.Selector) {
if (!(elementValue2 instanceof tree.Selector) || elementValue1.elements.length !== elementValue2.elements.length) {
return false;
}
for(var i = 0; i <elementValue1.elements.length; i++) {
if (elementValue1.elements[i].combinator.value !== elementValue2.elements[i].combinator.value) {
if (i !== 0 || (elementValue1.elements[i].combinator.value || ' ') !== (elementValue2.elements[i].combinator.value || ' ')) {
return false;
}
}
newElements = selector.elements
.slice(currentSelectorPathElementIndex, match.index)
.concat([firstElement])
.concat(replacementSelector.elements.slice(1));
if (currentSelectorPathIndex === match.pathIndex && matchIndex > 0) {
path[path.length - 1].elements =
path[path.length - 1].elements.concat(newElements);
} else {
path = path.concat(selectorPath.slice(currentSelectorPathIndex, match.pathIndex));
path.push(new tree.Selector(
newElements
));
}
currentSelectorPathIndex = match.endPathIndex;
currentSelectorPathElementIndex = match.endPathElementIndex;
if (currentSelectorPathElementIndex >= selectorPath[currentSelectorPathIndex].elements.length) {
currentSelectorPathElementIndex = 0;
currentSelectorPathIndex++;
if (!this.isElementValuesEqual(elementValue1.elements[i].value, elementValue2.elements[i].value)) {
return false;
}
}
return true;
}
return false;
},
extendSelector:function (matches, selectorPath, replacementSelector) {
if (currentSelectorPathIndex < selectorPath.length && currentSelectorPathElementIndex > 0) {
//for a set of matches, replace each match with the replacement selector
var currentSelectorPathIndex = 0,
currentSelectorPathElementIndex = 0,
path = [],
matchIndex,
selector,
firstElement,
match,
newElements;
for (matchIndex = 0; matchIndex < matches.length; matchIndex++) {
match = matches[matchIndex];
selector = selectorPath[match.pathIndex];
firstElement = new tree.Element(
match.initialCombinator,
replacementSelector.elements[0].value,
replacementSelector.elements[0].index,
replacementSelector.elements[0].currentFileInfo
);
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, selectorPath.length));
newElements = selector.elements
.slice(currentSelectorPathElementIndex, match.index)
.concat([firstElement])
.concat(replacementSelector.elements.slice(1));
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;
if (currentSelectorPathIndex === match.pathIndex && matchIndex > 0) {
path[path.length - 1].elements =
path[path.length - 1].elements.concat(newElements);
} else {
path = path.concat(selectorPath.slice(currentSelectorPathIndex, match.pathIndex));
path.push(new tree.Selector(
newElements
));
}
currentSelectorPathIndex = match.endPathIndex;
currentSelectorPathElementIndex = match.endPathElementIndex;
if (currentSelectorPathElementIndex >= selectorPath[currentSelectorPathIndex].elements.length) {
currentSelectorPathElementIndex = 0;
currentSelectorPathIndex++;
}
}
};
return processExtendsVisitor;
if (currentSelectorPathIndex < selectorPath.length && currentSelectorPathElementIndex > 0) {
path[path.length - 1].elements = path[path.length - 1].elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex));
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;

View File

@@ -1,145 +1,145 @@
var contexts = require("../contexts.js");
module.exports = function (visitor, tree) {
var ImportVisitor = function(importer, finish, evalEnv, onceFileDetectionMap, recursionDetector) {
this._visitor = new visitor(this);
this._importer = importer;
this._finish = finish;
this.env = evalEnv || new contexts.evalEnv();
this.importCount = 0;
this.onceFileDetectionMap = onceFileDetectionMap || {};
this.recursionDetector = {};
if (recursionDetector) {
for(var fullFilename in recursionDetector) {
if (recursionDetector.hasOwnProperty(fullFilename)) {
this.recursionDetector[fullFilename] = true;
}
var contexts = require("../contexts.js"),
Visitor = require("./visitor.js");
var ImportVisitor = function(importer, finish, evalEnv, onceFileDetectionMap, recursionDetector) {
this._visitor = new Visitor(this);
this._importer = importer;
this._finish = finish;
this.env = evalEnv || new contexts.evalEnv();
this.importCount = 0;
this.onceFileDetectionMap = onceFileDetectionMap || {};
this.recursionDetector = {};
if (recursionDetector) {
for(var fullFilename in recursionDetector) {
if (recursionDetector.hasOwnProperty(fullFilename)) {
this.recursionDetector[fullFilename] = true;
}
}
};
}
};
ImportVisitor.prototype = {
isReplacing: true,
run: function (root) {
var error;
try {
// process the contents
this._visitor.visit(root);
}
catch(e) {
error = e;
}
this.isFinished = true;
if (this.importCount === 0) {
this._finish(error);
}
},
visitImport: function (importNode, visitArgs) {
var importVisitor = this,
evaldImportNode,
inlineCSS = importNode.options.inline;
if (!importNode.css || inlineCSS) {
ImportVisitor.prototype = {
isReplacing: true,
run: function (root) {
var error;
try {
// process the contents
this._visitor.visit(root);
}
catch(e) {
error = e;
evaldImportNode = importNode.evalForImport(this.env);
} catch(e){
if (!e.filename) { e.index = importNode.index; e.filename = importNode.currentFileInfo.filename; }
// attempt to eval properly and treat as css
importNode.css = true;
// if that fails, this error will be thrown
importNode.error = e;
}
this.isFinished = true;
if (evaldImportNode && (!evaldImportNode.css || inlineCSS)) {
importNode = evaldImportNode;
this.importCount++;
var env = new contexts.evalEnv(this.env, this.env.frames.slice(0));
if (this.importCount === 0) {
this._finish(error);
}
},
visitImport: function (importNode, visitArgs) {
var importVisitor = this,
evaldImportNode,
inlineCSS = importNode.options.inline;
if (!importNode.css || inlineCSS) {
try {
evaldImportNode = importNode.evalForImport(this.env);
} catch(e){
if (!e.filename) { e.index = importNode.index; e.filename = importNode.currentFileInfo.filename; }
// attempt to eval properly and treat as css
importNode.css = true;
// if that fails, this error will be thrown
importNode.error = e;
if (importNode.options.multiple) {
env.importMultiple = true;
}
if (evaldImportNode && (!evaldImportNode.css || inlineCSS)) {
importNode = evaldImportNode;
this.importCount++;
var env = new contexts.evalEnv(this.env, this.env.frames.slice(0));
if (importNode.options.multiple) {
env.importMultiple = true;
this._importer.push(importNode.getPath(), importNode.currentFileInfo, importNode.options, function (e, root, importedAtRoot, fullPath) {
if (e && !e.filename) {
e.index = importNode.index; e.filename = importNode.currentFileInfo.filename;
}
this._importer.push(importNode.getPath(), importNode.currentFileInfo, importNode.options, function (e, root, importedAtRoot, fullPath) {
if (e && !e.filename) {
e.index = importNode.index; e.filename = importNode.currentFileInfo.filename;
var duplicateImport = importedAtRoot || fullPath in importVisitor.recursionDetector;
if (!env.importMultiple) {
if (duplicateImport) {
importNode.skip = true;
} else {
importNode.skip = function() {
if (fullPath in importVisitor.onceFileDetectionMap) {
return true;
}
importVisitor.onceFileDetectionMap[fullPath] = true;
return false;
};
}
}
var duplicateImport = importedAtRoot || fullPath in importVisitor.recursionDetector;
if (!env.importMultiple) {
if (duplicateImport) {
importNode.skip = true;
} else {
importNode.skip = function() {
if (fullPath in importVisitor.onceFileDetectionMap) {
return true;
}
importVisitor.onceFileDetectionMap[fullPath] = true;
return false;
};
}
var subFinish = function(e) {
importVisitor.importCount--;
if (importVisitor.importCount === 0 && importVisitor.isFinished) {
importVisitor._finish(e);
}
};
var subFinish = function(e) {
importVisitor.importCount--;
if (root) {
importNode.root = root;
importNode.importedFilename = fullPath;
if (importVisitor.importCount === 0 && importVisitor.isFinished) {
importVisitor._finish(e);
}
};
if (root) {
importNode.root = root;
importNode.importedFilename = fullPath;
if (!inlineCSS && (env.importMultiple || !duplicateImport)) {
importVisitor.recursionDetector[fullPath] = true;
new(ImportVisitor)(importVisitor._importer, subFinish, env, importVisitor.onceFileDetectionMap, importVisitor.recursionDetector)
.run(root);
return;
}
if (!inlineCSS && (env.importMultiple || !duplicateImport)) {
importVisitor.recursionDetector[fullPath] = true;
new(ImportVisitor)(importVisitor._importer, subFinish, env, importVisitor.onceFileDetectionMap, importVisitor.recursionDetector)
.run(root);
return;
}
}
subFinish();
});
}
subFinish();
});
}
visitArgs.visitDeeper = false;
return importNode;
},
visitRule: function (ruleNode, visitArgs) {
visitArgs.visitDeeper = false;
return ruleNode;
},
visitDirective: function (directiveNode, visitArgs) {
this.env.frames.unshift(directiveNode);
return directiveNode;
},
visitDirectiveOut: function (directiveNode) {
this.env.frames.shift();
},
visitMixinDefinition: function (mixinDefinitionNode, visitArgs) {
this.env.frames.unshift(mixinDefinitionNode);
return mixinDefinitionNode;
},
visitMixinDefinitionOut: function (mixinDefinitionNode) {
this.env.frames.shift();
},
visitRuleset: function (rulesetNode, visitArgs) {
this.env.frames.unshift(rulesetNode);
return rulesetNode;
},
visitRulesetOut: function (rulesetNode) {
this.env.frames.shift();
},
visitMedia: function (mediaNode, visitArgs) {
this.env.frames.unshift(mediaNode.rules[0]);
return mediaNode;
},
visitMediaOut: function (mediaNode) {
this.env.frames.shift();
}
};
return ImportVisitor;
visitArgs.visitDeeper = false;
return importNode;
},
visitRule: function (ruleNode, visitArgs) {
visitArgs.visitDeeper = false;
return ruleNode;
},
visitDirective: function (directiveNode, visitArgs) {
this.env.frames.unshift(directiveNode);
return directiveNode;
},
visitDirectiveOut: function (directiveNode) {
this.env.frames.shift();
},
visitMixinDefinition: function (mixinDefinitionNode, visitArgs) {
this.env.frames.unshift(mixinDefinitionNode);
return mixinDefinitionNode;
},
visitMixinDefinitionOut: function (mixinDefinitionNode) {
this.env.frames.shift();
},
visitRuleset: function (rulesetNode, visitArgs) {
this.env.frames.unshift(rulesetNode);
return rulesetNode;
},
visitRulesetOut: function (rulesetNode) {
this.env.frames.shift();
},
visitMedia: function (mediaNode, visitArgs) {
this.env.frames.unshift(mediaNode.rules[0]);
return mediaNode;
},
visitMediaOut: function (mediaNode) {
this.env.frames.shift();
}
};
module.exports = ImportVisitor;

View File

@@ -1,13 +1,9 @@
module.exports = function(less, tree) {
var visitor = require("./visitor")(tree),
visitors = {
visitor: visitor
};
visitors.importVisitor = require('./import-visitor.js')(visitor, tree);
visitors.extendVisitor = require('./extend-visitor.js')(visitor, tree);
visitors.joinSelectorVisitor = require('./join-selector-visitor.js')(visitor);
visitors.toCSSVisitor = require('./to-css-visitor.js')(visitor, tree);
return visitors;
var visitors = {
Visitor: require("./visitor"),
ImportVisitor: require('./import-visitor.js'),
ExtendVisitor: require('./extend-visitor.js'),
JoinSelectorVisitor: require('./join-selector-visitor.js'),
ToCSSVisitor: require('./to-css-visitor.js')
};
};
module.exports = visitors;

View File

@@ -1,45 +1,45 @@
module.exports = function (visitor) {
var joinSelectorVisitor = function() {
this.contexts = [[]];
this._visitor = new visitor(this);
};
var Visitor = require("./visitor.js");
joinSelectorVisitor.prototype = {
run: function (root) {
return this._visitor.visit(root);
},
visitRule: function (ruleNode, visitArgs) {
visitArgs.visitDeeper = false;
},
visitMixinDefinition: function (mixinDefinitionNode, visitArgs) {
visitArgs.visitDeeper = false;
},
var JoinSelectorVisitor = function() {
this.contexts = [[]];
this._visitor = new Visitor(this);
};
visitRuleset: function (rulesetNode, visitArgs) {
var context = this.contexts[this.contexts.length - 1],
paths = [], selectors;
JoinSelectorVisitor.prototype = {
run: function (root) {
return this._visitor.visit(root);
},
visitRule: function (ruleNode, visitArgs) {
visitArgs.visitDeeper = false;
},
visitMixinDefinition: function (mixinDefinitionNode, visitArgs) {
visitArgs.visitDeeper = false;
},
this.contexts.push(paths);
visitRuleset: function (rulesetNode, visitArgs) {
var context = this.contexts[this.contexts.length - 1],
paths = [], selectors;
if (! rulesetNode.root) {
selectors = rulesetNode.selectors;
if (selectors) {
selectors = selectors.filter(function(selector) { return selector.getIsOutput(); });
rulesetNode.selectors = selectors.length ? selectors : (selectors = null);
if (selectors) { rulesetNode.joinSelectors(paths, context, selectors); }
}
if (!selectors) { rulesetNode.rules = null; }
rulesetNode.paths = paths;
this.contexts.push(paths);
if (! rulesetNode.root) {
selectors = rulesetNode.selectors;
if (selectors) {
selectors = selectors.filter(function(selector) { return selector.getIsOutput(); });
rulesetNode.selectors = selectors.length ? selectors : (selectors = null);
if (selectors) { rulesetNode.joinSelectors(paths, context, selectors); }
}
},
visitRulesetOut: function (rulesetNode) {
this.contexts.length = this.contexts.length - 1;
},
visitMedia: function (mediaNode, visitArgs) {
var context = this.contexts[this.contexts.length - 1];
mediaNode.rules[0].root = (context.length === 0 || context[0].multiMedia);
if (!selectors) { rulesetNode.rules = null; }
rulesetNode.paths = paths;
}
};
return joinSelectorVisitor;
};
},
visitRulesetOut: function (rulesetNode) {
this.contexts.length = this.contexts.length - 1;
},
visitMedia: function (mediaNode, visitArgs) {
var context = this.contexts[this.contexts.length - 1];
mediaNode.rules[0].root = (context.length === 0 || context[0].multiMedia);
}
};
module.exports = JoinSelectorVisitor;

View File

@@ -1,244 +1,245 @@
module.exports = function (visitor, tree) {
var toCSSVisitor = function(env) {
this._visitor = new visitor(this);
this._env = env;
};
var tree = require("../tree/index.js"),
Visitor = require("./visitor.js");
toCSSVisitor.prototype = {
isReplacing: true,
run: function (root) {
return this._visitor.visit(root);
},
var ToCSSVisitor = function(env) {
this._visitor = new Visitor(this);
this._env = env;
};
visitRule: function (ruleNode, visitArgs) {
if (ruleNode.variable) {
ToCSSVisitor.prototype = {
isReplacing: true,
run: function (root) {
return this._visitor.visit(root);
},
visitRule: function (ruleNode, visitArgs) {
if (ruleNode.variable) {
return [];
}
return ruleNode;
},
visitMixinDefinition: function (mixinNode, visitArgs) {
// mixin definitions do not get eval'd - this means they keep state
// so we have to clear that state here so it isn't used if toCSS is called twice
mixinNode.frames = [];
return [];
},
visitExtend: function (extendNode, visitArgs) {
return [];
},
visitComment: function (commentNode, visitArgs) {
if (commentNode.isSilent(this._env)) {
return [];
}
return commentNode;
},
visitMedia: function(mediaNode, visitArgs) {
mediaNode.accept(this._visitor);
visitArgs.visitDeeper = false;
if (!mediaNode.rules.length) {
return [];
}
return mediaNode;
},
visitDirective: function(directiveNode, visitArgs) {
if (directiveNode.currentFileInfo.reference && !directiveNode.isReferenced) {
return [];
}
if (directiveNode.name === "@charset") {
// Only output the debug info together with subsequent @charset definitions
// a comment (or @media statement) before the actual @charset directive would
// be considered illegal css as it has to be on the first line
if (this.charset) {
if (directiveNode.debugInfo) {
var comment = new tree.Comment("/* " + directiveNode.toCSS(this._env).replace(/\n/g, "")+" */\n");
comment.debugInfo = directiveNode.debugInfo;
return this._visitor.visit(comment);
}
return [];
}
return ruleNode;
},
this.charset = true;
}
if (directiveNode.rules && directiveNode.rules.rules) {
this._mergeRules(directiveNode.rules.rules);
}
return directiveNode;
},
visitMixinDefinition: function (mixinNode, visitArgs) {
// mixin definitions do not get eval'd - this means they keep state
// so we have to clear that state here so it isn't used if toCSS is called twice
mixinNode.frames = [];
return [];
},
visitExtend: function (extendNode, visitArgs) {
return [];
},
visitComment: function (commentNode, visitArgs) {
if (commentNode.isSilent(this._env)) {
return [];
checkPropertiesInRoot: function(rules) {
var ruleNode;
for(var i = 0; i < rules.length; i++) {
ruleNode = rules[i];
if (ruleNode instanceof tree.Rule && !ruleNode.variable) {
throw { message: "properties must be inside selector blocks, they cannot be in the root.",
index: ruleNode.index, filename: ruleNode.currentFileInfo ? ruleNode.currentFileInfo.filename : null};
}
return commentNode;
},
}
},
visitMedia: function(mediaNode, visitArgs) {
mediaNode.accept(this._visitor);
visitRuleset: function (rulesetNode, visitArgs) {
var rule, rulesets = [];
if (rulesetNode.firstRoot) {
this.checkPropertiesInRoot(rulesetNode.rules);
}
if (! rulesetNode.root) {
if (rulesetNode.paths) {
rulesetNode.paths = rulesetNode.paths
.filter(function(p) {
var i;
if (p[0].elements[0].combinator.value === ' ') {
p[0].elements[0].combinator = new(tree.Combinator)('');
}
for(i = 0; i < p.length; i++) {
if (p[i].getIsReferenced() && p[i].getIsOutput()) {
return true;
}
}
return false;
});
}
// Compile rules and rulesets
var nodeRules = rulesetNode.rules, nodeRuleCnt = nodeRules ? nodeRules.length : 0;
for (var i = 0; i < nodeRuleCnt; ) {
rule = nodeRules[i];
if (rule && rule.rules) {
// visit because we are moving them out from being a child
rulesets.push(this._visitor.visit(rule));
nodeRules.splice(i, 1);
nodeRuleCnt--;
continue;
}
i++;
}
// accept the visitor to remove rules and refactor itself
// then we can decide now whether we want it or not
if (nodeRuleCnt > 0) {
rulesetNode.accept(this._visitor);
} else {
rulesetNode.rules = null;
}
visitArgs.visitDeeper = false;
if (!mediaNode.rules.length) {
return [];
}
return mediaNode;
},
visitDirective: function(directiveNode, visitArgs) {
if (directiveNode.currentFileInfo.reference && !directiveNode.isReferenced) {
return [];
}
if (directiveNode.name === "@charset") {
// Only output the debug info together with subsequent @charset definitions
// a comment (or @media statement) before the actual @charset directive would
// be considered illegal css as it has to be on the first line
if (this.charset) {
if (directiveNode.debugInfo) {
var comment = new tree.Comment("/* " + directiveNode.toCSS(this._env).replace(/\n/g, "")+" */\n");
comment.debugInfo = directiveNode.debugInfo;
return this._visitor.visit(comment);
}
return [];
}
this.charset = true;
}
if (directiveNode.rules && directiveNode.rules.rules) {
this._mergeRules(directiveNode.rules.rules);
}
return directiveNode;
},
checkPropertiesInRoot: function(rules) {
var ruleNode;
for(var i = 0; i < rules.length; i++) {
ruleNode = rules[i];
if (ruleNode instanceof tree.Rule && !ruleNode.variable) {
throw { message: "properties must be inside selector blocks, they cannot be in the root.",
index: ruleNode.index, filename: ruleNode.currentFileInfo ? ruleNode.currentFileInfo.filename : null};
}
}
},
visitRuleset: function (rulesetNode, visitArgs) {
var rule, rulesets = [];
if (rulesetNode.firstRoot) {
this.checkPropertiesInRoot(rulesetNode.rules);
}
if (! rulesetNode.root) {
if (rulesetNode.paths) {
rulesetNode.paths = rulesetNode.paths
.filter(function(p) {
var i;
if (p[0].elements[0].combinator.value === ' ') {
p[0].elements[0].combinator = new(tree.Combinator)('');
}
for(i = 0; i < p.length; i++) {
if (p[i].getIsReferenced() && p[i].getIsOutput()) {
return true;
}
}
return false;
});
}
// Compile rules and rulesets
var nodeRules = rulesetNode.rules, nodeRuleCnt = nodeRules ? nodeRules.length : 0;
for (var i = 0; i < nodeRuleCnt; ) {
rule = nodeRules[i];
if (rule && rule.rules) {
// visit because we are moving them out from being a child
rulesets.push(this._visitor.visit(rule));
nodeRules.splice(i, 1);
nodeRuleCnt--;
continue;
}
i++;
}
// accept the visitor to remove rules and refactor itself
// then we can decide now whether we want it or not
if (nodeRuleCnt > 0) {
rulesetNode.accept(this._visitor);
} else {
rulesetNode.rules = null;
}
visitArgs.visitDeeper = false;
nodeRules = rulesetNode.rules;
if (nodeRules) {
this._mergeRules(nodeRules);
nodeRules = rulesetNode.rules;
if (nodeRules) {
this._mergeRules(nodeRules);
nodeRules = rulesetNode.rules;
}
if (nodeRules) {
this._removeDuplicateRules(nodeRules);
nodeRules = rulesetNode.rules;
}
// now decide whether we keep the ruleset
if (nodeRules && nodeRules.length > 0 && rulesetNode.paths.length > 0) {
rulesets.splice(0, 0, rulesetNode);
}
} else {
rulesetNode.accept(this._visitor);
visitArgs.visitDeeper = false;
if (rulesetNode.firstRoot || (rulesetNode.rules && rulesetNode.rules.length > 0)) {
rulesets.splice(0, 0, rulesetNode);
}
}
if (rulesets.length === 1) {
return rulesets[0];
}
return rulesets;
},
_removeDuplicateRules: function(rules) {
if (!rules) { return; }
// remove duplicates
var ruleCache = {},
ruleList, rule, i;
for(i = rules.length - 1; i >= 0 ; i--) {
rule = rules[i];
if (rule instanceof tree.Rule) {
if (!ruleCache[rule.name]) {
ruleCache[rule.name] = rule;
} else {
ruleList = ruleCache[rule.name];
if (ruleList instanceof tree.Rule) {
ruleList = ruleCache[rule.name] = [ruleCache[rule.name].toCSS(this._env)];
}
var ruleCSS = rule.toCSS(this._env);
if (ruleList.indexOf(ruleCSS) !== -1) {
rules.splice(i, 1);
} else {
ruleList.push(ruleCSS);
}
}
}
}
},
_mergeRules: function (rules) {
if (!rules) { return; }
var groups = {},
parts,
rule,
key;
for (var i = 0; i < rules.length; i++) {
rule = rules[i];
if ((rule instanceof tree.Rule) && rule.merge) {
key = [rule.name,
rule.important ? "!" : ""].join(",");
if (!groups[key]) {
groups[key] = [];
} else {
rules.splice(i--, 1);
}
groups[key].push(rule);
}
if (nodeRules) {
this._removeDuplicateRules(nodeRules);
nodeRules = rulesetNode.rules;
}
Object.keys(groups).map(function (k) {
function toExpression(values) {
return new (tree.Expression)(values.map(function (p) {
return p.value;
}));
}
function toValue(values) {
return new (tree.Value)(values.map(function (p) {
return p;
}));
}
parts = groups[k];
if (parts.length > 1) {
rule = parts[0];
var spacedGroups = [];
var lastSpacedGroup = [];
parts.map(function (p) {
if (p.merge==="+") {
if (lastSpacedGroup.length > 0) {
spacedGroups.push(toExpression(lastSpacedGroup));
}
lastSpacedGroup = [];
}
lastSpacedGroup.push(p);
});
spacedGroups.push(toExpression(lastSpacedGroup));
rule.value = toValue(spacedGroups);
}
});
// now decide whether we keep the ruleset
if (nodeRules && nodeRules.length > 0 && rulesetNode.paths.length > 0) {
rulesets.splice(0, 0, rulesetNode);
}
} else {
rulesetNode.accept(this._visitor);
visitArgs.visitDeeper = false;
if (rulesetNode.firstRoot || (rulesetNode.rules && rulesetNode.rules.length > 0)) {
rulesets.splice(0, 0, rulesetNode);
}
}
};
return toCSSVisitor;
if (rulesets.length === 1) {
return rulesets[0];
}
return rulesets;
},
_removeDuplicateRules: function(rules) {
if (!rules) { return; }
// remove duplicates
var ruleCache = {},
ruleList, rule, i;
for(i = rules.length - 1; i >= 0 ; i--) {
rule = rules[i];
if (rule instanceof tree.Rule) {
if (!ruleCache[rule.name]) {
ruleCache[rule.name] = rule;
} else {
ruleList = ruleCache[rule.name];
if (ruleList instanceof tree.Rule) {
ruleList = ruleCache[rule.name] = [ruleCache[rule.name].toCSS(this._env)];
}
var ruleCSS = rule.toCSS(this._env);
if (ruleList.indexOf(ruleCSS) !== -1) {
rules.splice(i, 1);
} else {
ruleList.push(ruleCSS);
}
}
}
}
},
_mergeRules: function (rules) {
if (!rules) { return; }
var groups = {},
parts,
rule,
key;
for (var i = 0; i < rules.length; i++) {
rule = rules[i];
if ((rule instanceof tree.Rule) && rule.merge) {
key = [rule.name,
rule.important ? "!" : ""].join(",");
if (!groups[key]) {
groups[key] = [];
} else {
rules.splice(i--, 1);
}
groups[key].push(rule);
}
}
Object.keys(groups).map(function (k) {
function toExpression(values) {
return new (tree.Expression)(values.map(function (p) {
return p.value;
}));
}
function toValue(values) {
return new (tree.Value)(values.map(function (p) {
return p;
}));
}
parts = groups[k];
if (parts.length > 1) {
rule = parts[0];
var spacedGroups = [];
var lastSpacedGroup = [];
parts.map(function (p) {
if (p.merge==="+") {
if (lastSpacedGroup.length > 0) {
spacedGroups.push(toExpression(lastSpacedGroup));
}
lastSpacedGroup = [];
}
lastSpacedGroup.push(p);
});
spacedGroups.push(toExpression(lastSpacedGroup));
rule.value = toValue(spacedGroups);
}
});
}
};
module.exports = ToCSSVisitor;

View File

@@ -1,146 +1,145 @@
module.exports = function (tree) {
var tree = require("../tree/index.js");
var _visitArgs = { visitDeeper: true },
_hasIndexed = false;
var _visitArgs = { visitDeeper: true },
_hasIndexed = false;
function _noop(node) {
return node;
}
function _noop(node) {
return node;
}
function indexNodeTypes(parent, ticker) {
// add .typeIndex to tree node types for lookup table
var key, child;
for (key in parent) {
if (parent.hasOwnProperty(key)) {
child = parent[key];
switch (typeof child) {
case "function":
// ignore bound functions directly on tree which do not have a prototype
// or aren't nodes
if (child.prototype && child.prototype.type) {
child.prototype.typeIndex = ticker++;
}
break;
case "object":
ticker = indexNodeTypes(child, ticker);
break;
}
}
}
return ticker;
}
var visitor = function(implementation) {
this._implementation = implementation;
this._visitFnCache = [];
if (!_hasIndexed) {
indexNodeTypes(tree, 1);
_hasIndexed = true;
}
};
visitor.prototype = {
visit: function(node) {
if (!node) {
return node;
}
var nodeTypeIndex = node.typeIndex;
if (!nodeTypeIndex) {
return node;
}
var visitFnCache = this._visitFnCache,
impl = this._implementation,
aryIndx = nodeTypeIndex << 1,
outAryIndex = aryIndx | 1,
func = visitFnCache[aryIndx],
funcOut = visitFnCache[outAryIndex],
visitArgs = _visitArgs,
fnName;
visitArgs.visitDeeper = true;
if (!func) {
fnName = "visit" + node.type;
func = impl[fnName] || _noop;
funcOut = impl[fnName + "Out"] || _noop;
visitFnCache[aryIndx] = func;
visitFnCache[outAryIndex] = funcOut;
}
if (func !== _noop) {
var newNode = func.call(impl, node, visitArgs);
if (impl.isReplacing) {
node = newNode;
}
}
if (visitArgs.visitDeeper && node && node.accept) {
node.accept(this);
}
if (funcOut != _noop) {
funcOut.call(impl, node);
}
return node;
},
visitArray: function(nodes, nonReplacing) {
if (!nodes) {
return nodes;
}
var cnt = nodes.length, i;
// Non-replacing
if (nonReplacing || !this._implementation.isReplacing) {
for (i = 0; i < cnt; i++) {
this.visit(nodes[i]);
}
return nodes;
}
// Replacing
var out = [];
for (i = 0; i < cnt; i++) {
var evald = this.visit(nodes[i]);
if (!evald.splice) {
out.push(evald);
} else if (evald.length) {
this.flatten(evald, out);
}
}
return out;
},
flatten: function(arr, out) {
if (!out) {
out = [];
}
var cnt, i, item,
nestedCnt, j, nestedItem;
for (i = 0, cnt = arr.length; i < cnt; i++) {
item = arr[i];
if (!item.splice) {
out.push(item);
continue;
}
for (j = 0, nestedCnt = item.length; j < nestedCnt; j++) {
nestedItem = item[j];
if (!nestedItem.splice) {
out.push(nestedItem);
} else if (nestedItem.length) {
this.flatten(nestedItem, out);
function indexNodeTypes(parent, ticker) {
// add .typeIndex to tree node types for lookup table
var key, child;
for (key in parent) {
if (parent.hasOwnProperty(key)) {
child = parent[key];
switch (typeof child) {
case "function":
// ignore bound functions directly on tree which do not have a prototype
// or aren't nodes
if (child.prototype && child.prototype.type) {
child.prototype.typeIndex = ticker++;
}
}
break;
case "object":
ticker = indexNodeTypes(child, ticker);
break;
}
}
}
return ticker;
}
var Visitor = function(implementation) {
this._implementation = implementation;
this._visitFnCache = [];
if (!_hasIndexed) {
indexNodeTypes(tree, 1);
_hasIndexed = true;
}
};
Visitor.prototype = {
visit: function(node) {
if (!node) {
return node;
}
var nodeTypeIndex = node.typeIndex;
if (!nodeTypeIndex) {
return node;
}
var visitFnCache = this._visitFnCache,
impl = this._implementation,
aryIndx = nodeTypeIndex << 1,
outAryIndex = aryIndx | 1,
func = visitFnCache[aryIndx],
funcOut = visitFnCache[outAryIndex],
visitArgs = _visitArgs,
fnName;
visitArgs.visitDeeper = true;
if (!func) {
fnName = "visit" + node.type;
func = impl[fnName] || _noop;
funcOut = impl[fnName + "Out"] || _noop;
visitFnCache[aryIndx] = func;
visitFnCache[outAryIndex] = funcOut;
}
if (func !== _noop) {
var newNode = func.call(impl, node, visitArgs);
if (impl.isReplacing) {
node = newNode;
}
}
if (visitArgs.visitDeeper && node && node.accept) {
node.accept(this);
}
if (funcOut != _noop) {
funcOut.call(impl, node);
}
return node;
},
visitArray: function(nodes, nonReplacing) {
if (!nodes) {
return nodes;
}
var cnt = nodes.length, i;
// Non-replacing
if (nonReplacing || !this._implementation.isReplacing) {
for (i = 0; i < cnt; i++) {
this.visit(nodes[i]);
}
return nodes;
}
// Replacing
var out = [];
for (i = 0; i < cnt; i++) {
var evald = this.visit(nodes[i]);
if (!evald.splice) {
out.push(evald);
} else if (evald.length) {
this.flatten(evald, out);
}
}
return out;
},
flatten: function(arr, out) {
if (!out) {
out = [];
}
var cnt, i, item,
nestedCnt, j, nestedItem;
for (i = 0, cnt = arr.length; i < cnt; i++) {
item = arr[i];
if (!item.splice) {
out.push(item);
continue;
}
return out;
for (j = 0, nestedCnt = item.length; j < nestedCnt; j++) {
nestedItem = item[j];
if (!nestedItem.splice) {
out.push(nestedItem);
} else if (nestedItem.length) {
this.flatten(nestedItem, out);
}
}
}
};
return visitor;
return out;
}
};
module.exports = Visitor;