Skip to content

Commit

Permalink
Fixes #1421 - re-parses variable-interpolated elements to selectors (…
Browse files Browse the repository at this point in the history
…no.2) (#3227)

* Fix element to selector list conversion, passing all tests!
* Add passing test from #3098
* Added passing test example from #1817
* Allow lists to be re-evaluated as selectors (Fixes #1694)
  • Loading branch information
matthew-dean authored Jun 25, 2018
1 parent 7a12d2f commit b8140d4
Show file tree
Hide file tree
Showing 10 changed files with 192 additions and 38 deletions.
51 changes: 28 additions & 23 deletions lib/less/parser/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -814,7 +814,7 @@ var Parser = function Parser(context, imports, fileInfo) {
if (!e) {
break;
}
elem = new(tree.Element)(c, e, elemIndex, fileInfo);
elem = new(tree.Element)(c, e, false, elemIndex, fileInfo);
if (elements) {
elements.push(elem);
} else {
Expand Down Expand Up @@ -1101,7 +1101,7 @@ var Parser = function Parser(context, imports, fileInfo) {
}
}

if (e) { return new(tree.Element)(c, e, index, fileInfo); }
if (e) { return new(tree.Element)(c, e, e instanceof tree.Variable, index, fileInfo); }
},

//
Expand Down Expand Up @@ -1181,6 +1181,30 @@ var Parser = function Parser(context, imports, fileInfo) {
if (elements) { return new(tree.Selector)(elements, allExtends, condition, index, fileInfo); }
if (allExtends) { error('Extend must be used to extend a selector, it cannot be used on its own'); }
},
selectors: function () {
var s, selectors;
while (true) {
s = this.selector();
if (!s) {
break;
}
if (selectors) {
selectors.push(s);
} else {
selectors = [ s ];
}
parserInput.commentStore.length = 0;
if (s.condition && selectors.length > 1) {
error("Guards are only currently allowed on a single selector.");
}
if (!parserInput.$char(',')) { break; }
if (s.condition) {
error("Guards are only currently allowed on a single selector.");
}
parserInput.commentStore.length = 0;
}
return selectors;
},
attribute: function () {
if (!parserInput.$char('[')) { return; }

Expand Down Expand Up @@ -1232,34 +1256,15 @@ var Parser = function Parser(context, imports, fileInfo) {
// div, .class, body > p {...}
//
ruleset: function () {
var selectors, s, rules, debugInfo;
var selectors, rules, debugInfo;

parserInput.save();

if (context.dumpLineNumbers) {
debugInfo = getDebugInfo(parserInput.i);
}

while (true) {
s = this.selector();
if (!s) {
break;
}
if (selectors) {
selectors.push(s);
} else {
selectors = [ s ];
}
parserInput.commentStore.length = 0;
if (s.condition && selectors.length > 1) {
error('Guards are only currently allowed on a single selector.');
}
if (!parserInput.$char(',')) { break; }
if (s.condition) {
error('Guards are only currently allowed on a single selector.');
}
parserInput.commentStore.length = 0;
}
selectors = this.selectors();

if (selectors && (rules = this.block())) {
parserInput.forget();
Expand Down
5 changes: 4 additions & 1 deletion lib/less/tree/element.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ var Node = require('./node'),
Paren = require('./paren'),
Combinator = require('./combinator');

var Element = function (combinator, value, index, currentFileInfo, visibilityInfo) {
var Element = function (combinator, value, isVariable, index, currentFileInfo, visibilityInfo) {
this.combinator = combinator instanceof Combinator ?
combinator : new Combinator(combinator);

Expand All @@ -13,6 +13,7 @@ var Element = function (combinator, value, index, currentFileInfo, visibilityInf
} else {
this.value = '';
}
this.isVariable = isVariable;
this._index = index;
this._fileInfo = currentFileInfo;
this.copyVisibilityInfo(visibilityInfo);
Expand All @@ -30,12 +31,14 @@ Element.prototype.accept = function (visitor) {
Element.prototype.eval = function (context) {
return new Element(this.combinator,
this.value.eval ? this.value.eval(context) : this.value,
this.isVariable,
this.getIndex(),
this.fileInfo(), this.visibilityInfo());
};
Element.prototype.clone = function () {
return new Element(this.combinator,
this.value,
this.isVariable,
this.getIndex(),
this.fileInfo(), this.visibilityInfo());
};
Expand Down
2 changes: 1 addition & 1 deletion lib/less/tree/mixin-definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ var Selector = require('./selector'),

var Definition = function (name, params, rules, condition, variadic, frames, visibilityInfo) {
this.name = name;
this.selectors = [new Selector([new Element(null, name, this._index, this._fileInfo)])];
this.selectors = [new Selector([new Element(null, name, false, this._index, this._fileInfo)])];
this.params = params;
this.condition = condition;
this.variadic = variadic;
Expand Down
58 changes: 48 additions & 10 deletions lib/less/tree/ruleset.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,22 +41,47 @@ Ruleset.prototype.accept = function (visitor) {
}
};
Ruleset.prototype.eval = function (context) {
var thisSelectors = this.selectors, selectors,
selCnt, selector, i, hasOnePassingSelector = false;
var that = this, selectors, selCnt, selector, i, hasVariable, hasOnePassingSelector = false;

if (thisSelectors && (selCnt = thisSelectors.length)) {
if (this.selectors && (selCnt = this.selectors.length)) {
selectors = new Array(selCnt);
defaultFunc.error({
type: 'Syntax',
message: 'it is currently only allowed in parametric mixin guards,'
});

for (i = 0; i < selCnt; i++) {
selector = thisSelectors[i].eval(context);
selector = this.selectors[i].eval(context);
for (var j = 0; j < selector.elements.length; j++) {
if (selector.elements[j].isVariable) {
hasVariable = true;
break;
}
}
selectors[i] = selector;
if (selector.evaldCondition) {
hasOnePassingSelector = true;
}
}

if (hasVariable) {
var toParseSelectors = new Array(selCnt);
for (i = 0; i < selCnt; i++) {
selector = selectors[i];
toParseSelectors[i] = selector.toCSS(context);
}
this.parse.parseNode(
toParseSelectors.join(','),
["selectors"],
selectors[0].getIndex(),
selectors[0].fileInfo(),
function(err, result) {
if (result) {
selectors = utils.flattenArray(result);
}
});
}

defaultFunc.reset();
} else {
hasOnePassingSelector = true;
Expand Down Expand Up @@ -162,7 +187,7 @@ Ruleset.prototype.eval = function (context) {
// for rulesets, check if it is a css guard and can be removed
if (rule instanceof Ruleset && rule.selectors && rule.selectors.length === 1) {
// check if it can be folded in (e.g. & where)
if (rule.selectors[0].isJustParentSelector()) {
if (rule.selectors[0] && rule.selectors[0].isJustParentSelector()) {
rsRules.splice(i--, 1);

for (var j = 0; (subRule = rule.rules[j]); j++) {
Expand Down Expand Up @@ -511,7 +536,13 @@ Ruleset.prototype.joinSelector = function (paths, context, selector) {
} else {
var insideParent = new Array(elementsToPak.length);
for (j = 0; j < elementsToPak.length; j++) {
insideParent[j] = new Element(null, elementsToPak[j], originalElement._index, originalElement._fileInfo);
insideParent[j] = new Element(
null,
elementsToPak[j],
originalElement.isVariable,
originalElement._index,
originalElement._fileInfo
);
}
replacementParen = new Paren(new Selector(insideParent));
}
Expand All @@ -520,7 +551,7 @@ Ruleset.prototype.joinSelector = function (paths, context, selector) {

function createSelector(containedElement, originalElement) {
var element, selector;
element = new Element(null, containedElement, originalElement._index, originalElement._fileInfo);
element = new Element(null, containedElement, originalElement.isVariable, originalElement._index, originalElement._fileInfo);
selector = new Selector([element]);
return selector;
}
Expand All @@ -545,7 +576,8 @@ Ruleset.prototype.joinSelector = function (paths, context, selector) {
}

if (addPath.length > 0) {
// /deep/ is a combinator that is valid without anything in front of it
// /deep/ is a CSS4 selector - (removed, so should deprecate)
// 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
Expand All @@ -554,7 +586,13 @@ Ruleset.prototype.joinSelector = function (paths, context, selector) {
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._fileInfo));
newJoinedSelector.elements.push(new Element(
combinator,
parentEl.value,
replacedElement.isVariable,
replacedElement._index,
replacedElement._fileInfo
));
newJoinedSelector.elements = newJoinedSelector.elements.concat(addPath[0].elements.slice(1));
}

Expand Down Expand Up @@ -688,7 +726,7 @@ Ruleset.prototype.joinSelector = function (paths, context, selector) {
// 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._fileInfo));
sel[0].elements.push(new Element(el.combinator, '', el.isVariable, el._index, el._fileInfo));
}
selectorsMultiplied.push(sel);
}
Expand Down
2 changes: 1 addition & 1 deletion lib/less/tree/selector.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Selector.prototype.getElements = function(els) {
return els;
};
Selector.prototype.createEmptySelectors = function() {
var el = new Element('', '&', this._index, this._fileInfo),
var el = new Element('', '&', false, this._index, this._fileInfo),
sels = [new Selector([el], null, null, this._index, this._fileInfo)];
sels[0].mediaEmpty = true;
return sels;
Expand Down
18 changes: 17 additions & 1 deletion lib/less/utils.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* jshint proto: true */
module.exports = {
var utils = {
getLocation: function(index, inputStream) {
var n = index + 1,
line = null,
Expand Down Expand Up @@ -65,5 +65,21 @@ module.exports = {
}
}
return obj1;
},
flattenArray: function(arr, result) {
result = result || [];
for (var i = 0, length = arr.length; i < length; i++) {
var value = arr[i];
if (Array.isArray(value)) {
utils.flattenArray(value, result);
} else {
if (value !== undefined) {
result.push(value);
}
}
}
return result;
}
};

module.exports = utils;
1 change: 1 addition & 0 deletions lib/less/visitors/extend-visitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,7 @@ ProcessExtendsVisitor.prototype = {
firstElement = new tree.Element(
match.initialCombinator,
replacementSelector.elements[0].value,
replacementSelector.elements[0].isVariable,
replacementSelector.elements[0].getIndex(),
replacementSelector.elements[0].fileInfo()
);
Expand Down
36 changes: 36 additions & 0 deletions test/css/parse-interpolation.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
input[type=text]:focus,
input[type=email]:focus,
input[type=password]:focus,
textarea:focus {
foo: bar;
}
.a + .z,
.b + .z,
.c + .z {
color: blue;
}
.bar .d.a,
.bar .b,
.c.bar:hover,
.bar baz {
color: blue;
}
.a + .e,
.b.c + .e,
.d + .e {
foo: bar;
}
input[class="text"],
input.text {
background: red;
}
.master-page-1 .selector-1,
.master-page-1 .selector-2 {
background-color: red;
}
.fruit-apple,
.fruit-satsuma,
.fruit-banana,
.fruit-pear {
content: "Just a test.";
}
4 changes: 3 additions & 1 deletion test/css/permissive-parse.css
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
@-moz-whatever (foo: "(" bam ")") {
bar: foo;
}
#selector, .bar, foo[attr="blah"] {
#selector,
.bar,
foo[attr="blah"] {
bar: value;
}
@media (min-width: 640px) {
Expand Down
53 changes: 53 additions & 0 deletions test/less/parse-interpolation.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
@inputs: input[type=text], input[type=email], input[type=password], textarea;

@{inputs} {
&:focus {
foo: bar;
}
}

@classes: .a, .b, .c;

@{classes} {
+ .z {
color: blue;
}
}

.bar {
.d@{classes}&:hover, baz {
color: blue;
}
}

@c: ~'.a, .b';
@d: ~'.c, .d';
@e: ~' + .e';

@{c}@{d} {
@{e} {
foo: bar;
}
}

@textClasses: ~'&[class="text"], &.text';

input {
@{textClasses} {
background: red;
}
}

@my-selector: ~'.selector-1, .selector-2';
.master-page-1 {
@{my-selector} {
background-color: red;
}
}

@list: apple, satsuma, banana, pear;
@{list} {
.fruit-& {
content: "Just a test.";
}
}

0 comments on commit b8140d4

Please sign in to comment.