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'));