Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #1421 - re-parses variable-interpolated elements to selectors (no.2) #3227

Merged
merged 9 commits into from
Jun 25, 2018
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.";
}
}