node-less
Version:
Less Compiler For Node
350 lines (336 loc) • 15 kB
JavaScript
(function (module) {
var DebugInfo = require('./debugInfo.js'),
Rule = require('./rule.js'),
Media = require('./media.js'),
Directive = require('./directive.js'),
Selector = require('./selector.js'),
Import = require('./import.js'),
Comment = require('./comment.js'),
Element = require('./element.js');
var Ruleset = function (selectors, rules, strictImports) {
this.selectors = selectors;
this.rules = rules;
this._lookups = {};
this.strictImports = strictImports;
};
Ruleset.prototype = {
type: "Ruleset",
accept: function (visitor) {
this.selectors = visitor.visit(this.selectors);
this.rules = visitor.visit(this.rules);
},
eval: function (env) {
var selectors = this.selectors && this.selectors.map(function (s) { return s.eval(env) });
var ruleset = new Ruleset(selectors, this.rules.slice(0), this.strictImports);
var rules;
var mixin = require('./mixin.js');
ruleset.originalRuleset = this;
ruleset.root = this.root;
ruleset.firstRoot = this.firstRoot;
ruleset.allowImports = this.allowImports;
if(this.debugInfo) {
ruleset.debugInfo = this.debugInfo;
}
env.frames.unshift(ruleset);
if (!env.selectors) {
env.selectors = [];
}
env.selectors.unshift(this.selectors);
if (ruleset.root || ruleset.allowImports || !ruleset.strictImports) {
ruleset.evalImports(env);
}
for (var i = 0; i < ruleset.rules.length; i++) {
if (ruleset.rules[i] instanceof mixin.Definition) {
ruleset.rules[i].frames = env.frames.slice(0);
}
}
var mediaBlockCount = (env.mediaBlocks && env.mediaBlocks.length) || 0;
for (var i = 0; i < ruleset.rules.length; i++) {
if (ruleset.rules[i] instanceof mixin.Call) {
rules = ruleset.rules[i].eval(env).filter(function(r) {
if ((r instanceof Rule) && r.variable) {
return !(ruleset.variable(r.name));
}
return true;
});
ruleset.rules.splice.apply(ruleset.rules, [i, 1].concat(rules));
i += rules.length-1;
ruleset.resetCache();
}
}
for (var i = 0, rule; i < ruleset.rules.length; i++) {
rule = ruleset.rules[i];
if (! (rule instanceof mixin.Definition)) {
ruleset.rules[i] = rule.eval ? rule.eval(env) : rule;
}
}
env.frames.shift();
env.selectors.shift();
if (env.mediaBlocks) {
for(var i = mediaBlockCount; i < env.mediaBlocks.length; i++) {
env.mediaBlocks[i].bubbleSelectors(selectors);
}
}
return ruleset;
},
evalImports: function(env) {
var i, rules;
for (i = 0; i < this.rules.length; i++) {
if (this.rules[i] instanceof Import) {
rules = this.rules[i].eval(env);
if (typeof rules.length === "number") {
this.rules.splice.apply(this.rules, [i, 1].concat(rules));
i+= rules.length-1;
} else {
this.rules.splice(i, 1, rules);
}
this.resetCache();
}
}
},
makeImportant: function() {
return new Ruleset(this.selectors, this.rules.map(function (r) {
if (r.makeImportant) {
return r.makeImportant();
} else {
return r;
}
}), this.strictImports);
},
matchArgs: function (args) {
return !args || args.length === 0;
},
resetCache: function () {
this._rulesets = null;
this._variables = null;
this._lookups = {};
},
variables: function () {
if (this._variables) { return this._variables }
else {
return this._variables = this.rules.reduce(function (hash, r) {
if (r instanceof Rule && r.variable === true) {
hash[r.name] = r;
}
return hash;
}, {});
}
},
variable: function (name) {
return this.variables()[name];
},
rulesets: function () {
return this.rules.filter(function (r) {
return (r instanceof Ruleset) || (r instanceof mixin.Definition);
});
},
find: function (selector, self) {
self = self || this;
var rules = [], rule, match,
key = selector.toCSS();
if (key in this._lookups) { return this._lookups[key] }
this.rulesets().forEach(function (rule) {
if (rule !== self) {
for (var j = 0; j < rule.selectors.length; j++) {
if (match = selector.match(rule.selectors[j])) {
if (selector.elements.length > rule.selectors[j].elements.length) {
Array.prototype.push.apply(rules, rule.find(
new Selector(selector.elements.slice(1)), self));
} else {
rules.push(rule);
}
break;
}
}
}
});
return this._lookups[key] = rules;
},
toCSS: function (env) {
var css = [], // The CSS output
rules = [], // node.Rule instances
_rules = [], //
rulesets = [], // node.Ruleset instances
selector, // The fully rendered selector
debugInfo, // Line number debugging
rule;
for (var i = 0; i < this.rules.length; i++) {
rule = this.rules[i];
if (rule.rules || (rule instanceof Media)) {
rulesets.push(rule.toCSS(env));
} else if (rule instanceof Directive) {
var cssValue = rule.toCSS(env);
if (rule.name === "@charset") {
if (env.charset) {
if (rule.debugInfo) {
rulesets.push(DebugInfo(env, rule));
rulesets.push(new Comment("/*"+cssValue.replace(/\n/g, "")+" */\n").toCSS(env));
}
continue;
}
env.charset = true;
}
rulesets.push(cssValue);
} else if (rule instanceof Comment) {
if (!rule.silent) {
if (this.root) {
rulesets.push(rule.toCSS(env));
} else {
rules.push(rule.toCSS(env));
}
}
} else {
if (rule.toCSS && !rule.variable) {
if (this.firstRoot && rule instanceof Rule) {
throw { message: "properties must be inside selector blocks, they cannot be in the root.",
index: rule.index, filename: rule.currentFileInfo ? rule.currentFileInfo.filename : null};
}
rules.push(rule.toCSS(env));
} else if (rule.value && !rule.variable) {
rules.push(rule.value.toString());
}
}
}
if (env.compress && rules.length) {
rule = rules[rules.length - 1];
if (rule.charAt(rule.length - 1) === ';') {
rules[rules.length - 1] = rule.substring(0, rule.length - 1);
}
}
rulesets = rulesets.join('');
if (this.root) {
css.push(rules.join(env.compress ? '' : '\n'));
} else {
if (rules.length > 0) {
debugInfo = DebugInfo(env, this);
selector = this.paths.map(function (p) {
return p.map(function (s) {
return s.toCSS(env);
}).join('').trim();
}).join(env.compress ? ',' : ',\n');
for (var i = rules.length - 1; i >= 0; i--) {
if (rules[i].slice(0, 2) === "/*" || _rules.indexOf(rules[i]) === -1) {
_rules.unshift(rules[i]);
}
}
rules = _rules;
css.push(debugInfo + selector +
(env.compress ? '{' : ' {\n ') +
rules.join(env.compress ? '' : '\n ') +
(env.compress ? '}' : '\n}\n'));
}
}
css.push(rulesets);
return css.join('') + (env.compress ? '\n' : '');
},
joinSelectors: function (paths, context, selectors) {
for (var s = 0; s < selectors.length; s++) {
this.joinSelector(paths, context, selectors[s]);
}
},
joinSelector: function (paths, context, selector) {
var i, j, k,
hasParentSelector, newSelectors, el, sel, parentSel,
newSelectorPath, afterParentJoin, newJoinedSelector,
newJoinedSelectorEmpty, lastSelector, currentElements,
selectorsMultiplied;
for (i = 0; i < selector.elements.length; i++) {
el = selector.elements[i];
if (el.value === '&') {
hasParentSelector = true;
}
}
if (!hasParentSelector) {
if (context.length > 0) {
for(i = 0; i < context.length; i++) {
paths.push(context[i].concat(selector));
}
}
else {
paths.push([selector]);
}
return;
}
currentElements = [];
newSelectors = [[]];
for (i = 0; i < selector.elements.length; i++) {
el = selector.elements[i];
if (el.value !== "&") {
currentElements.push(el);
} else {
selectorsMultiplied = [];
if (currentElements.length > 0) {
this.mergeElementsOnToSelectors(currentElements, newSelectors);
}
for(j = 0; j < newSelectors.length; j++) {
sel = newSelectors[j];
if (context.length == 0) {
if (sel.length > 0) {
sel[0].elements = sel[0].elements.slice(0);
sel[0].elements.push(new Element(el.combinator, '', 0)); //new Element(el.Combinator, ""));
}
selectorsMultiplied.push(sel);
}
else {
for(k = 0; k < context.length; k++) {
parentSel = context[k];
newSelectorPath = [];
afterParentJoin = [];
newJoinedSelectorEmpty = true;
if (sel.length > 0) {
newSelectorPath = sel.slice(0);
lastSelector = newSelectorPath.pop();
newJoinedSelector = new Selector(lastSelector.elements.slice(0), selector.extendList);
newJoinedSelectorEmpty = false;
}
else {
newJoinedSelector = new Selector([], selector.extendList);
}
if (parentSel.length > 1) {
afterParentJoin = afterParentJoin.concat(parentSel.slice(1));
}
if (parentSel.length > 0) {
newJoinedSelectorEmpty = false;
newJoinedSelector.elements.push(new Element(el.combinator, parentSel[0].elements[0].value, 0));
newJoinedSelector.elements = newJoinedSelector.elements.concat(parentSel[0].elements.slice(1));
}
if (!newJoinedSelectorEmpty) {
newSelectorPath.push(newJoinedSelector);
}
newSelectorPath = newSelectorPath.concat(afterParentJoin);
selectorsMultiplied.push(newSelectorPath);
}
}
}
newSelectors = selectorsMultiplied;
currentElements = [];
}
}
if (currentElements.length > 0) {
this.mergeElementsOnToSelectors(currentElements, newSelectors);
}
for(i = 0; i < newSelectors.length; i++) {
if (newSelectors[i].length > 0) {
paths.push(newSelectors[i]);
}
}
},
mergeElementsOnToSelectors: function(elements, selectors) {
var i, sel, extendList;
if (selectors.length == 0) {
selectors.push([ new Selector(elements) ]);
return;
}
for(i = 0; i < selectors.length; i++) {
sel = selectors[i];
if (sel.length > 0) {
sel[sel.length - 1] = new Selector(sel[sel.length - 1].elements.concat(elements), sel[sel.length - 1].extendList);
}
else {
sel.push(new Selector(elements));
}
}
}
};
module.exports = Ruleset;
})(module);