From b5711dbb1364e7d70e83fd8078036cebb5641bb3 Mon Sep 17 00:00:00 2001 From: jurcovicovam Date: Sun, 18 Jan 2015 11:47:23 +0100 Subject: [PATCH 1/3] Working but extremly ugly version. Desperately needs clean up. --- lib/less/parser/parser.js | 4 +- lib/less/tree/ruleset.js | 350 +++++++++++++++++++++++--------------- test/less/selectors.less | 14 +- 3 files changed, 219 insertions(+), 149 deletions(-) diff --git a/lib/less/parser/parser.js b/lib/less/parser/parser.js index f1cf119b4..61a1d4fa1 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 aa86214d2..b087723a1 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; + + //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([]); + } + + //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 (!newJoinedSelectorEmpty) { + // now add the joined selector + newSelectorPath.push(newJoinedSelector); + } + + // 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; + + maybeSelector = element.value.value; + if (maybeSelector.type !== 'Selector') + return null; + + 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 = [ + [] + ]; + + 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); + + 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]; + var entirelyNewSelectorPath = replaceNextPartMeri(sel, replacements, el, selector); + selectorsMultiplied.push(entirelyNewSelectorPath); // this causes cucle + } + newSelectors = selectorsMultiplied; + currentElements = []; + } - var i, j, k, - hasParentSelector, newSelectors, el, sel, parentSel, - newSelectorPath, afterParentJoin, newJoinedSelector, - newJoinedSelectorEmpty, lastSelector, currentElements, - selectorsMultiplied; + } else { + currentElements.push(el); + } - 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 { + 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 { - paths.push([selector]); + // 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); + } } - 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]] - // - - // 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 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); + // our new selectors has been multiplied, so reset the state + newSelectors = selectorsMultiplied; + currentElements = []; + } + } - // add that to our new set of selectors - selectorsMultiplied.push(newSelectorPath); - } - } - } + // if we have any elements left over (e.g. .a& .b == .b) + // add them on to all the current selectors + this.mergeElementsOnToSelectors(currentElements, newSelectors); - // our new selectors has been multiplied, so reset the state - newSelectors = selectorsMultiplied; - currentElements = []; - } + for (i = 0; i < newSelectors.length; i++) { + if (newSelectors[i].length > 0) { + paths.push(newSelectors[i]); } + } - // 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); - } + 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]); + } - for (i = 0; i < newSelectors.length; i++) { - if (newSelectors[i].length > 0) { - paths.push(newSelectors[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 42aa29e72..ccc57da25 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}"], From 20e555bf4749abd43a9a89337278ab3c54465d1d Mon Sep 17 00:00:00 2001 From: jurcovicovam Date: Tue, 20 Jan 2015 13:40:56 +0100 Subject: [PATCH 2/3] Cleaned up parent selector replacement inside nested selectors :not() and added unit test. --- lib/less/tree/ruleset.js | 416 +++++++++++++++++++-------------------- test/css/selectors.css | 9 + test/less/selectors.less | 15 +- 3 files changed, 221 insertions(+), 219 deletions(-) diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index b087723a1..0d465938f 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -427,244 +427,224 @@ Ruleset.prototype.joinSelectors = function (paths, context, selectors) { this.joinSelector(paths, context, selectors[s]); } }; -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; - - //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([]); - } - - //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 (!newJoinedSelectorEmpty) { - // now add the joined selector - newSelectorPath.push(newJoinedSelector); - } - - // 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; - - maybeSelector = element.value.value; - if (maybeSelector.type !== 'Selector') - return null; - - 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 = [ - [] - ]; - - 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); - - 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]; - var entirelyNewSelectorPath = replaceNextPartMeri(sel, replacements, el, selector); - selectorsMultiplied.push(entirelyNewSelectorPath); // this causes cucle - } - newSelectors = selectorsMultiplied; - currentElements = []; - } - } else { - currentElements.push(el); - } +Ruleset.prototype.joinSelector = function (paths, context, selector) { + // replace all parent selectors inside `inSelector` by content of `context` array + // resulting selectors are returned inside `paths` array + // returns true if `inSelector` contained at least one parent selector + function replaceParentSelector(paths, context, inSelector) { + // 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 i, j, k, currentElements, newSelectors, selectorsMultiplied, sel, el, hadParentSelector = false; + function findNestedSelector(element) { + var maybeSelector; + if (element.value.type !== 'Paren') + return null; + + maybeSelector = element.value.value; + if (maybeSelector.type !== 'Selector') + return null; + + return maybeSelector; + } - } 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); + // 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 < inSelector.elements.length; i++) { + el = inSelector.elements[i]; + // non parent reference elements just get added + if (el.value !== "&") { + var nestedSelector = findNestedSelector(el); + if (nestedSelector != null) { + // merge the current list of non parent selector elements + // on to the current list of selectors to add + mergeElementsOnToSelectors(currentElements, newSelectors); + + var nestedPaths = [], replaced; + replaced = replaceParentSelector(nestedPaths, context, nestedSelector); + hadParentSelector = hadParentSelector || replaced; + for (k = 0; k < nestedPaths.length; k++) { + var replacementParen = new Paren(nestedPaths[0][0]); + var replacementElement = new Element(null, replacementParen, el.index, el.currentFileInfo); + var replacementSelector = new Selector([replacementElement]); + newSelectors = addAllReplacementsIntoPath(newSelectors, [replacementSelector], el, inSelector); + } + currentElements = []; + + } else { + currentElements.push(el); + } + + } else { + hadParentSelector = 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 + 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.push(new Element(el.combinator, '', el.index, el.currentFileInfo)); + } + selectorsMultiplied.push(sel); + } + else { + // and the parent selectors + for (k = 0; k < context.length; k++) { + // We need to put the current selectors + // then join the last selector's elements on to the parents selectors + var newSelectorPath = addReplacementIntoPath(sel, context[k], el, inSelector); + // add that to our new set of selectors + selectorsMultiplied.push(newSelectorPath); + } + } + } + + // our new selectors has been multiplied, so reset the state + newSelectors = selectorsMultiplied; + currentElements = []; + } } - 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); - } + + // if we have any elements left over (e.g. .a& .b == .b) + // add them on to all the current selectors + mergeElementsOnToSelectors(currentElements, newSelectors); + + for (i = 0; i < newSelectors.length; i++) { + if (newSelectors[i].length > 0) { + paths.push(newSelectors[i]); + } } - } - // our new selectors has been multiplied, so reset the state - newSelectors = selectorsMultiplied; - currentElements = []; + return hadParentSelector; } - } - // if we have any elements left over (e.g. .a& .b == .b) - // add them on to all the current selectors - this.mergeElementsOnToSelectors(currentElements, newSelectors); + // joins selector path from `beginningPath` with selector path in `addPath` + // `replacedElement` contains element that is being replaced by `addPath` + // returns concatenated path + function addReplacementIntoPath(beginningPath, addPath, replacedElement, originalSelector) { + var newSelectorPath, lastSelector, newJoinedSelector; + // our new selector path + newSelectorPath = []; - for (i = 0; i < newSelectors.length; i++) { - if (newSelectors[i].length > 0) { - paths.push(newSelectors[i]); - } - } + //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 (beginningPath.length > 0) { + newSelectorPath = beginningPath.slice(0); + lastSelector = newSelectorPath.pop(); + newJoinedSelector = originalSelector.createDerived(lastSelector.elements.slice(0)); + } + else { + newJoinedSelector = originalSelector.createDerived([]); + } - return encounteredParentSelector; -} -Ruleset.prototype.joinSelector = function (paths, context, selector) { + if (addPath.length > 0) { + // /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 = replacedElement.combinator, parentEl = addPath[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, replacedElement.index, replacedElement.currentFileInfo)); + newJoinedSelector.elements = newJoinedSelector.elements.concat(addPath[0].elements.slice(1)); + } - 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)); - } + // now add the joined selector - but only if it is not empty + if (newJoinedSelector.elements.length !== 0) { + newSelectorPath.push(newJoinedSelector); + } + + //put together the parent selectors after the join (e.g. the rest of the parent) + if (addPath.length > 1) { + newSelectorPath = newSelectorPath.concat(addPath.slice(1)); + } + return newSelectorPath; } - else { - console.log('context: empty'); - newPaths.push([selector]); + + // joins selector path from `beginningPath` with every selector path in `addPaths` array + // `replacedElement` contains element that is being replaced by `addPath` + // returns array with all concatenated paths + function addAllReplacementsIntoPath( beginningPath, addPaths, replacedElement, originalSelector) { + var j, result = []; + for (j = 0; j < beginningPath.length; j++) { + var newSelectorPath = addReplacementIntoPath(beginningPath[j], addPaths, replacedElement, originalSelector); + result.push(newSelectorPath); + } + return result; } - } - for (i = 0; i < newPaths.length; i++) { - paths.push(newPaths[i]); - } -}; -Ruleset.prototype.mergeElementsOnToSelectors = function(elements, selectors) { - var i, sel; + function mergeElementsOnToSelectors(elements, selectors) { + var i, sel; - if (elements.length === 0) { - return ; - } - if (selectors.length === 0) { - selectors.push([ new Selector(elements) ]); - return; + if (elements.length === 0) { + return ; + } + if (selectors.length === 0) { + selectors.push([ new 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] = sel[sel.length - 1].createDerived(sel[sel.length - 1].elements.concat(elements)); + } + else { + sel.push(new Selector(elements)); + } + } } - for (i = 0; i < selectors.length; i++) { - sel = selectors[i]; + // joinSelector code follows + var i, newPaths, hadParentSelector; - // if the previous thing in sel is a parent this needs to join on to it - if (sel.length > 0) { - sel[sel.length - 1] = sel[sel.length - 1].createDerived(sel[sel.length - 1].elements.concat(elements)); + newPaths = []; + hadParentSelector = replaceParentSelector(newPaths, context, selector); + + if (!hadParentSelector) { + if (context.length > 0) { + newPaths = []; + for (i = 0; i < context.length; i++) { + newPaths.push(context[i].concat(selector)); + } } else { - sel.push(new Selector(elements)); + newPaths = [[selector]]; } } + + for (i = 0; i < newPaths.length; i++) { + paths.push(newPaths[i]); + } + }; module.exports = Ruleset; diff --git a/test/css/selectors.css b/test/css/selectors.css index 85f95b56e..b114b62ca 100644 --- a/test/css/selectors.css +++ b/test/css/selectors.css @@ -154,3 +154,12 @@ blank blank blank blank blank blank blank blank blank blank blank blank blank bl .blood { color: red; } +.foo:not(.tst.only-nested:hover) { + test: only-nested; +} +.foo.nestend-and-non-nested:not(.tst.nestend-and-non-nested:hover) { + test: nestend-and-non-nested; +} +.selector:not(:hover) { + test: global scope; +} diff --git a/test/less/selectors.less b/test/less/selectors.less index ccc57da25..b98fca221 100644 --- a/test/less/selectors.less +++ b/test/less/selectors.less @@ -156,4 +156,17 @@ blank blank blank blank blank blank blank blank blank blank blank blank blank bl */ @{selector} { color: red; -} \ No newline at end of file +} +.only-nested { + .foo:not(.tst&:hover) { + test: only-nested; + } +} +.nestend-and-non-nested { + .foo&:not(.tst&:hover) { + test: nestend-and-non-nested; + } +} +.selector:not(&:hover) { + test: global scope; +} From 964b991423478e6b85bce25a3aeec084b3986836 Mon Sep 17 00:00:00 2001 From: jurcovicovam Date: Wed, 28 Jan 2015 09:50:44 +0100 Subject: [PATCH 3/3] Reviews changes: spacing and commented code. Bug fixed: fixed handling of multi level nesting. --- lib/less/parser/parser.js | 1 - lib/less/tree/ruleset.js | 38 ++++++++++++++++++++++++++++++-------- test/css/selectors.css | 2 +- test/less/selectors.less | 20 +++++++++++--------- 4 files changed, 42 insertions(+), 19 deletions(-) diff --git a/lib/less/parser/parser.js b/lib/less/parser/parser.js index 61a1d4fa1..686397918 100644 --- a/lib/less/parser/parser.js +++ b/lib/less/parser/parser.js @@ -859,7 +859,6 @@ var Parser = function Parser(context, imports, fileInfo) { 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(/^[\.#](?=@)/) || this.entities.variableCurly(); diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index 0d465938f..49b3d783a 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -429,6 +429,28 @@ Ruleset.prototype.joinSelectors = function (paths, context, selectors) { }; Ruleset.prototype.joinSelector = function (paths, context, selector) { + + function createParenthesis(elementsToPak, originalElement) { + var replacementParen, j; + if (elementsToPak.length === 0) { + replacementParen = new Paren(elementsToPak[0]); + } else { + var insideParent = []; + for (j = 0; j < elementsToPak.length; j++) { + insideParent.push(new Element(null, elementsToPak[j], originalElement.index, originalElement.currentFileInfo)); + } + replacementParen = new Paren(new Selector(insideParent)); + } + return replacementParen; + } + + function createSelector(containedElement, originalElement) { + var element, selector; + element = new Element(null, containedElement, originalElement.index, originalElement.currentFileInfo); + selector = new Selector([element]); + return selector; + } + // replace all parent selectors inside `inSelector` by content of `context` array // resulting selectors are returned inside `paths` array // returns true if `inSelector` contained at least one parent selector @@ -475,15 +497,15 @@ Ruleset.prototype.joinSelector = function (paths, context, selector) { // on to the current list of selectors to add mergeElementsOnToSelectors(currentElements, newSelectors); - var nestedPaths = [], replaced; + var nestedPaths = [], replaced, replacedNewSelectors = []; replaced = replaceParentSelector(nestedPaths, context, nestedSelector); hadParentSelector = hadParentSelector || replaced; + //the nestedPaths array should have only one member - replaceParentSelector does not multiply selectors for (k = 0; k < nestedPaths.length; k++) { - var replacementParen = new Paren(nestedPaths[0][0]); - var replacementElement = new Element(null, replacementParen, el.index, el.currentFileInfo); - var replacementSelector = new Selector([replacementElement]); - newSelectors = addAllReplacementsIntoPath(newSelectors, [replacementSelector], el, inSelector); + var replacementSelector = createSelector(createParenthesis(nestedPaths[k], el), el); + addAllReplacementsIntoPath(newSelectors, [replacementSelector], el, inSelector, replacedNewSelectors); } + newSelectors = replacedNewSelectors; currentElements = []; } else { @@ -591,8 +613,8 @@ Ruleset.prototype.joinSelector = function (paths, context, selector) { // joins selector path from `beginningPath` with every selector path in `addPaths` array // `replacedElement` contains element that is being replaced by `addPath` // returns array with all concatenated paths - function addAllReplacementsIntoPath( beginningPath, addPaths, replacedElement, originalSelector) { - var j, result = []; + function addAllReplacementsIntoPath( beginningPath, addPaths, replacedElement, originalSelector, result) { + var j; for (j = 0; j < beginningPath.length; j++) { var newSelectorPath = addReplacementIntoPath(beginningPath[j], addPaths, replacedElement, originalSelector); result.push(newSelectorPath); @@ -647,4 +669,4 @@ Ruleset.prototype.joinSelector = function (paths, context, selector) { } }; -module.exports = Ruleset; +module.exports = Ruleset; \ No newline at end of file diff --git a/test/css/selectors.css b/test/css/selectors.css index b114b62ca..46acb407a 100644 --- a/test/css/selectors.css +++ b/test/css/selectors.css @@ -154,7 +154,7 @@ blank blank blank blank blank blank blank blank blank blank blank blank blank bl .blood { color: red; } -.foo:not(.tst.only-nested:hover) { +.foo:not(.tst.only-nested .level2:hover) { test: only-nested; } .foo.nestend-and-non-nested:not(.tst.nestend-and-non-nested:hover) { diff --git a/test/less/selectors.less b/test/less/selectors.less index b98fca221..51e3d0845 100644 --- a/test/less/selectors.less +++ b/test/less/selectors.less @@ -123,15 +123,15 @@ a { } @num: 3; :nth-child(@{num}) { - selector: interpolated; + 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%], @@ -158,8 +158,10 @@ blank blank blank blank blank blank blank blank blank blank blank blank blank bl color: red; } .only-nested { - .foo:not(.tst&:hover) { - test: only-nested; + .level2 { + .foo:not(.tst&:hover) { + test: only-nested; + } } } .nestend-and-non-nested {