lib/less/js/lib/less/tree/ruleset.js in less-2.3.0 vs lib/less/js/lib/less/tree/ruleset.js in less-2.3.1
- old
+ new
@@ -8,40 +8,45 @@
};
tree.Ruleset.prototype = {
eval: function (env) {
var selectors = this.selectors && this.selectors.map(function (s) { return s.eval(env) });
var ruleset = new(tree.Ruleset)(selectors, this.rules.slice(0), this.strictImports);
-
+ var rules;
+
+ ruleset.originalRuleset = this;
ruleset.root = this.root;
ruleset.allowImports = this.allowImports;
+ if(this.debugInfo) {
+ ruleset.debugInfo = this.debugInfo;
+ }
+
// push the current ruleset to the frames stack
env.frames.unshift(ruleset);
// Evaluate imports
if (ruleset.root || ruleset.allowImports || !ruleset.strictImports) {
- for (var i = 0; i < ruleset.rules.length; i++) {
- if (ruleset.rules[i] instanceof tree.Import) {
- Array.prototype.splice
- .apply(ruleset.rules, [i, 1].concat(ruleset.rules[i].eval(env)));
- }
- }
+ ruleset.evalImports(env);
}
// Store the frames around mixin definitions,
// so they can be evaluated like closures when the time comes.
for (var i = 0; i < ruleset.rules.length; i++) {
if (ruleset.rules[i] instanceof tree.mixin.Definition) {
ruleset.rules[i].frames = env.frames.slice(0);
}
}
+
+ var mediaBlockCount = (env.mediaBlocks && env.mediaBlocks.length) || 0;
// Evaluate mixin calls.
for (var i = 0; i < ruleset.rules.length; i++) {
if (ruleset.rules[i] instanceof tree.mixin.Call) {
- Array.prototype.splice
- .apply(ruleset.rules, [i, 1].concat(ruleset.rules[i].eval(env)));
+ rules = ruleset.rules[i].eval(env);
+ ruleset.rules.splice.apply(ruleset.rules, [i, 1].concat(rules));
+ i += rules.length-1;
+ ruleset.resetCache();
}
}
// Evaluate everything else
for (var i = 0, rule; i < ruleset.rules.length; i++) {
@@ -52,16 +57,51 @@
}
}
// Pop the stack
env.frames.shift();
+
+ if (env.mediaBlocks) {
+ for(var i = mediaBlockCount; i < env.mediaBlocks.length; i++) {
+ env.mediaBlocks[i].bubbleSelectors(selectors);
+ }
+ }
return ruleset;
},
- match: function (args) {
+ evalImports: function(env) {
+ var i, rules;
+ for (i = 0; i < this.rules.length; i++) {
+ if (this.rules[i] instanceof tree.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 tree.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 tree.Rule && r.variable === true) {
@@ -112,29 +152,45 @@
// `context` holds an array of arrays.
//
toCSS: function (context, env) {
var css = [], // The CSS output
rules = [], // node.Rule instances
+ _rules = [], //
rulesets = [], // node.Ruleset instances
paths = [], // Current selectors
selector, // The fully rendered selector
+ debugInfo, // Line number debugging
rule;
if (! this.root) {
- if (context.length === 0) {
- paths = this.selectors.map(function (s) { return [s] });
- } else {
- this.joinSelectors(paths, context, this.selectors);
- }
+ this.joinSelectors(paths, context, this.selectors);
}
// Compile rules and rulesets
for (var i = 0; i < this.rules.length; i++) {
rule = this.rules[i];
- if (rule.rules || (rule instanceof tree.Directive) || (rule instanceof tree.Media)) {
+ if (rule.rules || (rule instanceof tree.Media)) {
rulesets.push(rule.toCSS(paths, env));
+ } else if (rule instanceof tree.Directive) {
+ var cssValue = rule.toCSS(paths, env);
+ // Output only the first @charset definition as such - convert the others
+ // to comments in case debug is enabled
+ if (rule.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 (env.charset) {
+ if (rule.debugInfo) {
+ rulesets.push(tree.debugInfo(env, rule));
+ rulesets.push(new tree.Comment("/* "+cssValue.replace(/\n/g, "")+" */\n").toCSS(env));
+ }
+ continue;
+ }
+ env.charset = true;
+ }
+ rulesets.push(cssValue);
} else if (rule instanceof tree.Comment) {
if (!rule.silent) {
if (this.root) {
rulesets.push(rule.toCSS(env));
} else {
@@ -157,60 +213,202 @@
// Otherwise, only output if this ruleset has rules.
if (this.root) {
css.push(rules.join(env.compress ? '' : '\n'));
} else {
if (rules.length > 0) {
+ debugInfo = tree.debugInfo(env, this);
selector = paths.map(function (p) {
return p.map(function (s) {
return s.toCSS(env);
}).join('').trim();
- }).join( env.compress ? ',' : ',\n');
+ }).join(env.compress ? ',' : ',\n');
- css.push(selector,
+ // Remove duplicates
+ for (var i = rules.length - 1; i >= 0; i--) {
+ if (_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' : '');
+ 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 before = [], after = [], beforeElements = [],
- afterElements = [], hasParentSelector = false, el;
- for (var i = 0; i < selector.elements.length; i++) {
+ 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.combinator.value.charAt(0) === '&') {
+ if (el.value === '&') {
hasParentSelector = true;
}
- if (hasParentSelector) afterElements.push(el);
- else beforeElements.push(el);
}
+
+ if (!hasParentSelector) {
+ if (context.length > 0) {
+ for(i = 0; i < context.length; i++) {
+ paths.push(context[i].concat(selector));
+ }
+ }
+ else {
+ paths.push([selector]);
+ }
+ return;
+ }
- if (! hasParentSelector) {
- afterElements = beforeElements;
- beforeElements = [];
+ // The paths are [[Selector]]
+ // The first list is a list of comma seperated selectors
+ // The inner list is a list of inheritance seperated selectors
+ // e.g.
+ // .a, .b {
+ // .c {
+ // }
+ // }
+ // == [[.a] [.c]] [[.b] [.c]]
+ //
+
+ // the elements from the current selector so far
+ currentElements = [];
+ // the current list of new selectors to add to the path.
+ // We will build it up. We initiate it with one empty selector as we "multiply" the new selectors
+ // by the parents
+ newSelectors = [[]];
+
+ for (i = 0; i < selector.elements.length; i++) {
+ el = selector.elements[i];
+ // non parent reference elements just get added
+ if (el.value !== "&") {
+ currentElements.push(el);
+ } else {
+ // the new list of selectors to add
+ selectorsMultiplied = [];
+
+ // merge the current list of non parent selector elements
+ // on to the current list of selectors to add
+ if (currentElements.length > 0) {
+ this.mergeElementsOnToSelectors(currentElements, newSelectors);
+ }
+
+ // loop through our current selectors
+ for(j = 0; j < newSelectors.length; j++) {
+ sel = newSelectors[j];
+ // if we don't have any parent paths, the & might be in a mixin so that it can be used
+ // whether there are parents or not
+ if (context.length == 0) {
+ // the combinator used on el should now be applied to the next element instead so that
+ // it is not lost
+ if (sel.length > 0) {
+ sel[0].elements = sel[0].elements.slice(0);
+ sel[0].elements.push(new(tree.Element)(el.combinator, '', 0)); //new Element(el.Combinator, ""));
+ }
+ selectorsMultiplied.push(sel);
+ }
+ else {
+ // and the parent selectors
+ for(k = 0; k < context.length; k++) {
+ parentSel = context[k];
+ // We need to put the current selectors
+ // then join the last selector's elements on to the parents selectors
+
+ // our new selector path
+ newSelectorPath = [];
+ // selectors from the parent after the join
+ afterParentJoin = [];
+ newJoinedSelectorEmpty = true;
+
+ //construct the joined selector - if & is the first thing this will be empty,
+ // if not newJoinedSelector will be the last set of elements in the selector
+ if (sel.length > 0) {
+ newSelectorPath = sel.slice(0);
+ lastSelector = newSelectorPath.pop();
+ newJoinedSelector = new(tree.Selector)(lastSelector.elements.slice(0));
+ newJoinedSelectorEmpty = false;
+ }
+ else {
+ newJoinedSelector = new(tree.Selector)([]);
+ }
+
+ //put together the parent selectors after the join
+ if (parentSel.length > 1) {
+ afterParentJoin = afterParentJoin.concat(parentSel.slice(1));
+ }
+
+ if (parentSel.length > 0) {
+ newJoinedSelectorEmpty = false;
+
+ // join the elements so far with the first part of the parent
+ newJoinedSelector.elements.push(new(tree.Element)(el.combinator, parentSel[0].elements[0].value, 0));
+ newJoinedSelector.elements = newJoinedSelector.elements.concat(parentSel[0].elements.slice(1));
+ }
+
+ if (!newJoinedSelectorEmpty) {
+ // now add the joined selector
+ newSelectorPath.push(newJoinedSelector);
+ }
+
+ // and the rest of the parent
+ newSelectorPath = newSelectorPath.concat(afterParentJoin);
+
+ // add that to our new set of selectors
+ selectorsMultiplied.push(newSelectorPath);
+ }
+ }
+ }
+
+ // our new selectors has been multiplied, so reset the state
+ newSelectors = selectorsMultiplied;
+ currentElements = [];
+ }
}
- if (beforeElements.length > 0) {
- before.push(new(tree.Selector)(beforeElements));
+ // if we have any elements left over (e.g. .a& .b == .b)
+ // add them on to all the current selectors
+ if (currentElements.length > 0) {
+ this.mergeElementsOnToSelectors(currentElements, newSelectors);
}
- if (afterElements.length > 0) {
- after.push(new(tree.Selector)(afterElements));
+ for(i = 0; i < newSelectors.length; i++) {
+ paths.push(newSelectors[i]);
}
+ },
+
+ mergeElementsOnToSelectors: function(elements, selectors) {
+ var i, sel;
- for (var c = 0; c < context.length; c++) {
- paths.push(before.concat(context[c]).concat(after));
+ if (selectors.length == 0) {
+ selectors.push([ new(tree.Selector)(elements) ]);
+ return;
+ }
+
+ for(i = 0; i < selectors.length; i++) {
+ sel = selectors[i];
+
+ // if the previous thing in sel is a parent this needs to join on to it
+ if (sel.length > 0) {
+ sel[sel.length - 1] = new(tree.Selector)(sel[sel.length - 1].elements.concat(elements));
+ }
+ else {
+ sel.push(new(tree.Selector)(elements));
+ }
}
}
};
})(require('../tree'));