diff --git a/lib/less/parser/parser.js b/lib/less/parser/parser.js index f1cf119b..61a1d4fa 100644 --- a/lib/less/parser/parser.js +++ b/lib/less/parser/parser.js @@ -858,9 +858,11 @@ var Parser = function Parser(context, imports, fileInfo) { c = this.combinator(); e = parserInput.$re(/^(?:\d+\.\d+|\d+)%/) || parserInput.$re(/^(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/) || - parserInput.$char('*') || parserInput.$char('&') || this.attribute() || parserInput.$re(/^\([^()@]+\)/) || parserInput.$re(/^[\.#](?=@)/) || + parserInput.$char('*') || parserInput.$char('&') || this.attribute() || parserInput.$re(/^\([^&()@]+\)/) || parserInput.$re(/^[\.#](?=@)/) || + //parserInput.$char('*') || parserInput.$char('&') || this.attribute() || parserInput.$re(/^\([^()@]+\)/) || parserInput.$re(/^[\.#](?=@)/) || this.entities.variableCurly(); + if (! e) { parserInput.save(); if (parserInput.$char('(')) { diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index aa86214d..b087723a 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -2,6 +2,7 @@ var Node = require("./node"), Rule = require("./rule"), Selector = require("./selector"), Element = require("./element"), + Paren = require("./paren"), contexts = require("../contexts"), defaultFunc = require("../functions/default"), getDebugInfo = require("./debug-info"); @@ -426,162 +427,229 @@ Ruleset.prototype.joinSelectors = function (paths, context, selectors) { this.joinSelector(paths, context, selectors[s]); } }; -Ruleset.prototype.joinSelector = function (paths, context, selector) { +function replaceNextPartMeri(attachToPrefix, replacement, appender, originalSelector) { + var newSelectorPath, afterParentJoin, lastSelector, newJoinedSelectorEmpty, newJoinedSelector; + // our new selector path + newSelectorPath = []; + // selectors from the parent after the join + afterParentJoin = []; + newJoinedSelectorEmpty = true; - var i, j, k, - hasParentSelector, newSelectors, el, sel, parentSel, - newSelectorPath, afterParentJoin, newJoinedSelector, - newJoinedSelectorEmpty, lastSelector, currentElements, - selectorsMultiplied; + //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 (attachToPrefix.length > 0) { + newSelectorPath = attachToPrefix.slice(0); + lastSelector = newSelectorPath.pop(); + newJoinedSelector = originalSelector.createDerived(lastSelector.elements.slice(0)); + newJoinedSelectorEmpty = false; + } + else { + newJoinedSelector = originalSelector.createDerived([]); + } - for (i = 0; i < selector.elements.length; i++) { - el = selector.elements[i]; - if (el.value === '&') { - hasParentSelector = true; - } + //put together the parent selectors after the join + if (replacement.length > 1) { + afterParentJoin = afterParentJoin.concat(replacement.slice(1)); + } + + if (replacement.length > 0) { + newJoinedSelectorEmpty = false; + + // /deep/ is a combinator that is valid without anything in front of it + // so if the & does not have a combinator that is "" or " " then + // and there is a combinator on the parent, then grab that. + // this also allows + a { & .b { .a & { ... though not sure why you would want to do that + var combinator = appender.combinator, + parentEl = replacement[0].elements[0]; + if (combinator.emptyOrWhitespace && !parentEl.combinator.emptyOrWhitespace) { + combinator = parentEl.combinator; } + // join the elements so far with the first part of the parent + newJoinedSelector.elements.push(new Element(combinator, parentEl.value, appender.index, appender.currentFileInfo)); + newJoinedSelector.elements = newJoinedSelector.elements.concat(replacement[0].elements.slice(1)); + } - 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 (!newJoinedSelectorEmpty) { + // now add the joined selector + newSelectorPath.push(newJoinedSelector); + } - // The paths are [[Selector]] - // The first list is a list of comma separated selectors - // The inner list is a list of inheritance separated selectors - // e.g. - // .a, .b { - // .c { - // } - // } - // == [[.a] [.c]] [[.b] [.c]] - // + // and the rest of the parent + newSelectorPath = newSelectorPath.concat(afterParentJoin); + return newSelectorPath; +} +function replaceInnerParentSelector(paths, context, selector) { + var i, j, k, currentElements, newSelectors, selectorsMultiplied, parentSel, sel, el, encounteredParentSelector = false; + function findNestedSelector(element) { + var maybeSelector; + if (element.value.type !== 'Paren') + return null; - // 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 = [[]]; + maybeSelector = element.value.value; + if (maybeSelector.type !== 'Selector') + return null; - 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 = []; + return maybeSelector; + } +// 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 = [ + [] + ]; - // 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); - } + for (i = 0; i < selector.elements.length; i++) { + el = selector.elements[i]; + // non parent reference elements just get added + if (el.value !== "&") { + var nestedSelector = findNestedSelector(el); + if (nestedSelector != null) { + selectorsMultiplied = []; + // merge the current list of non parent selector elements + // on to the current list of selectors to add + this.mergeElementsOnToSelectors(currentElements, newSelectors); - // loop through our current selectors + var nestedPathsWtf = [], replaced; + replaced = replaceInnerParentSelector.call(this, nestedPathsWtf, context, nestedSelector); + encounteredParentSelector = encounteredParentSelector || replaced; + for (k = 0; k < nestedPathsWtf.length; k++) { + + var replacementParen = new Paren(nestedPathsWtf[0][0]); + var replacementElement = new Element(null, replacementParen, el.index, el.currentFileInfo); + var replacementSelector = new Selector([replacementElement]); + var replacements = [replacementSelector]; + //replaceNextPartMeri(attachToPrefix, replacement, appender, originalSelector) 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 Element(el.combinator, '', el.index, el.currentFileInfo)); - } - 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 = selector.createDerived(lastSelector.elements.slice(0)); - newJoinedSelectorEmpty = false; - } - else { - newJoinedSelector = selector.createDerived([]); - } - - //put together the parent selectors after the join - if (parentSel.length > 1) { - afterParentJoin = afterParentJoin.concat(parentSel.slice(1)); - } - - if (parentSel.length > 0) { - newJoinedSelectorEmpty = false; - - // /deep/ is a combinator that is valid without anything in front of it - // so if the & does not have a combinator that is "" or " " then - // and there is a combinator on the parent, then grab that. - // this also allows + a { & .b { .a & { ... though not sure why you would want to do that - var combinator = el.combinator, - parentEl = parentSel[0].elements[0]; - if (combinator.emptyOrWhitespace && !parentEl.combinator.emptyOrWhitespace) { - combinator = parentEl.combinator; - } - // join the elements so far with the first part of the parent - newJoinedSelector.elements.push(new Element(combinator, parentEl.value, el.index, el.currentFileInfo)); - 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); - } - } + sel = newSelectors[j]; + var entirelyNewSelectorPath = replaceNextPartMeri(sel, replacements, el, selector); + selectorsMultiplied.push(entirelyNewSelectorPath); // this causes cucle } - - // our new selectors has been multiplied, so reset the state newSelectors = selectorsMultiplied; currentElements = []; - } - } + } - // 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); - } + } else { + currentElements.push(el); + } - for (i = 0; i < newSelectors.length; i++) { - if (newSelectors[i].length > 0) { - paths.push(newSelectors[i]); + } else { + encounteredParentSelector = true; + // 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 + 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 Element(el.combinator, '', el.index, el.currentFileInfo)); + } + 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 + var newSelectorPath = replaceNextPartMeri(sel, parentSel, el, selector); + // 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 we have any elements left over (e.g. .a& .b == .b) + // add them on to all the current selectors + this.mergeElementsOnToSelectors(currentElements, newSelectors); + + for (i = 0; i < newSelectors.length; i++) { + if (newSelectors[i].length > 0) { + paths.push(newSelectors[i]); + } + } + + return encounteredParentSelector; +} +Ruleset.prototype.joinSelector = function (paths, context, selector) { + + var i, hasParentSelector, el; + + for (i = 0; i < selector.elements.length; i++) { + el = selector.elements[i]; + if (el.value === '&') { + hasParentSelector = true; + } + } + + if (false) { + if (context.length > 0) { + console.log('context: full'); + for (i = 0; i < context.length; i++) { + paths.push(context[i].concat(selector)); + } + } + else { + console.log('context: empty'); + paths.push([selector]); + } + return; + } + + // The paths are [[Selector]] + // The first list is a list of comma separated selectors + // The inner list is a list of inheritance separated selectors + // e.g. + // .a, .b { + // .c { + // } + // } + // == [[.a] [.c]] [[.b] [.c]] + // + var newPaths = []; + var encounteredParentSelector = replaceInnerParentSelector.call(this, newPaths, context, selector); + + if (true && !encounteredParentSelector) { + newPaths = []; + if (context.length > 0) { + console.log('context: full'); + for (i = 0; i < context.length; i++) { + newPaths.push(context[i].concat(selector)); + } + } + else { + console.log('context: empty'); + newPaths.push([selector]); + } + } + for (i = 0; i < newPaths.length; i++) { + paths.push(newPaths[i]); + } + }; Ruleset.prototype.mergeElementsOnToSelectors = function(elements, selectors) { var i, sel; + if (elements.length === 0) { + return ; + } if (selectors.length === 0) { selectors.push([ new Selector(elements) ]); return; diff --git a/test/less/selectors.less b/test/less/selectors.less index 42aa29e7..ccc57da2 100644 --- a/test/less/selectors.less +++ b/test/less/selectors.less @@ -126,13 +126,13 @@ a { selector: interpolated; } .test { - &:nth-child(@{num}) { - selector: interpolated; - } - &:nth-child(odd):not(:nth-child(3)) { - color: #ff0000; - } -} + &:nth-child(@{num}) { + selector: interpolated; + } + &:nth-child(odd):not(:nth-child(3)) { + color: #ff0000; + } + } [prop], [prop=10%], [prop="value@{num}"],