mirror of
https://github.com/less/less.js.git
synced 2026-05-01 03:00:22 -04:00
Also remove dep-injection from visitors, none of which use environment. make functions used as classes TitleCase
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user