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 #2270 - Adds each() function to Less functions #3263

Merged
merged 10 commits into from
Jul 11, 2018
1 change: 1 addition & 0 deletions lib/less/functions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module.exports = function(environment) {
require('./color');
require('./color-blending');
require('./data-uri')(environment);
require('./list');
require('./math');
require('./number');
require('./string');
Expand Down
101 changes: 101 additions & 0 deletions lib/less/functions/list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
var Dimension = require('../tree/dimension'),
Declaration = require('../tree/declaration'),
Ruleset = require('../tree/ruleset'),
Selector = require('../tree/selector'),
Element = require('../tree/element'),
functionRegistry = require('./function-registry');

var getItemsFromNode = function(node) {
// handle non-array values as an array of length 1
// return 'undefined' if index is invalid
var items = Array.isArray(node.value) ?
node.value : Array(node);

return items;
};

functionRegistry.addMultiple({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love the movement of the functions into the file where they're most relevant!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

_SELF: function(n) {
return n;
},
extract: function(values, index) {
index = index.value - 1; // (1-based index)

return getItemsFromNode(values)[index];
},
length: function(values) {
return new Dimension(getItemsFromNode(values).length);
},
each: function(list, rs) {
var i = 0, rules = [], newRules, iterator;

if (list.value) {
if (Array.isArray(list.value)) {
iterator = list.value;
} else {
iterator = [list.value];
}
} else if (list.ruleset) {
iterator = list.ruleset.rules;
} else if (Array.isArray(list)) {
iterator = list;
} else {
iterator = [list];
}

var valueName = '@value',
keyName = '@key',
indexName = '@index';

if (rs.params) {
valueName = rs.params[0] && rs.params[0].name;
keyName = rs.params[1] && rs.params[1].name;
indexName = rs.params[2] && rs.params[2].name;
rs = rs.rules;
} else {
rs = rs.ruleset;
}

iterator.forEach(function(item) {
i = i + 1;
var key, value;
if (item instanceof Declaration) {
key = typeof item.name === 'string' ? item.name : item.name[0].value;
value = item.value;
} else {
key = new Dimension(i);
value = item;
}

newRules = rs.rules.slice(0);
if (valueName) {
newRules.push(new Declaration(valueName,
value,
false, false, this.index, this.currentFileInfo));
}
if (indexName) {
newRules.push(new Declaration(indexName,
new Dimension(i),
false, false, this.index, this.currentFileInfo));
}
if (keyName) {
newRules.push(new Declaration(keyName,
key,
false, false, this.index, this.currentFileInfo));
}

rules.push(new Ruleset([ new(Selector)([ new Element("", '&') ]) ],
newRules,
rs.strictImports,
rs.visibilityInfo()
));
});

return new Ruleset([ new(Selector)([ new Element("", '&') ]) ],
rules,
rs.strictImports,
rs.visibilityInfo()
).eval(this.context);

}
});
20 changes: 1 addition & 19 deletions lib/less/functions/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,8 @@ var isa = function (n, Type) {
throw { type: 'Argument', message: 'Second argument to isunit should be a unit or a string.' };
}
return (n instanceof Dimension) && n.unit.is(unit) ? Keyword.True : Keyword.False;
},
getItemsFromNode = function(node) {
// handle non-array values as an array of length 1
// return 'undefined' if index is invalid
var items = Array.isArray(node.value) ?
node.value : Array(node);

return items;
};

functionRegistry.addMultiple({
isruleset: function (n) {
return isa(n, DetachedRuleset);
Expand Down Expand Up @@ -77,16 +70,5 @@ functionRegistry.addMultiple({
},
'get-unit': function (n) {
return new Anonymous(n.unit);
},
extract: function(values, index) {
index = index.value - 1; // (1-based index)

return getItemsFromNode(values)[index];
},
length: function(values) {
return new Dimension(getItemsFromNode(values).length);
},
_SELF: function(n) {
return n;
}
});
23 changes: 23 additions & 0 deletions lib/less/parser/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -1375,10 +1375,33 @@ var Parser = function Parser(context, imports, fileInfo) {
},

detachedRuleset: function() {
var argInfo, params, variadic;

parserInput.save();
if (parserInput.$re(/^[.#]\(/)) {
/**
* DR args currently only implemented for each() function, and not
* yet settable as `@dr: #(@arg) {}`
* This should be done when DRs are merged with mixins.
* See: https://github.com/less/less-meta/issues/16
*/
argInfo = this.mixin.args(false);
params = argInfo.args;
variadic = argInfo.variadic;
if (!parserInput.$char(')')) {
parserInput.restore();
return;
}
}
var blockRuleset = this.blockRuleset();
if (blockRuleset) {
parserInput.forget();
if (params) {
return new tree.mixin.Definition(null, params, blockRuleset, null, variadic);
}
return new tree.DetachedRuleset(blockRuleset);
}
parserInput.restore();
},

//
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'),
utils = require('../utils');

var Definition = function (name, params, rules, condition, variadic, frames, visibilityInfo) {
this.name = name;
this.name = name || 'anonymous mixin';
this.selectors = [new Selector([new Element(null, name, false, this._index, this._fileInfo)])];
this.params = params;
this.condition = condition;
Expand Down
42 changes: 42 additions & 0 deletions test/css/functions-each.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
.sel-blue {
a: b;
}
.sel-green {
a: b;
}
.sel-red {
a: b;
}
.each {
index: 1, 2, 3, 4;
item1: a;
item2: b;
item3: c;
item4: d;
nest-1-1: 10px 1;
nest-2-1: 15px 2;
nest-1-2: 20px 1;
nest-2-2: 25px 2;
padding: 10px 20px 30px 40px;
}

This comment was marked as outdated.

.set {
one: blue;
two: green;
three: red;
}
.set-2 {
one-1: blue;
two-2: green;
three-3: red;
}
.single {
val: true;
val2: 2;
val3: 4;
}
.box {
-less-log: a;
-less-log: b;
-less-log: c;
-less-log: d;
}
72 changes: 72 additions & 0 deletions test/less/functions-each.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
@selectors: blue, green, red;
@list: a b c d;

each(@selectors, {
.sel-@{value} {
a: b;
}
});

.each {
each(@list, {
index+: @index;
item@{index}: @value;
});

// nested each
each(10px 15px, 20px 25px; {
// demonstrates nesting of each()
each(@value; #(@v, @k, @i) {
nest-@{i}-@{index}: @v @k;
});
});

This comment was marked as outdated.

// vector math
each(1 2 3 4, {
padding+_: (@value * 10px);
});
}

@set: {
one: blue;
two: green;
three: red;
}
.set {
each(@set, {
@{key}: @value;
});
}
.set-2() {
one: blue;
two: green;
three: red;
}
.set-2 {
each(.set-2(), .(@v, @k, @i) {
@{k}-@{i}: @v;
});
}

.pick(@a) when (@a = 4) {
val3: @a;
}
.single {
each(true, {
val: @value;
});
@exp: 1 + 1;
each(@exp, {
val2: @value;
});
each(1 2 3 4, {
.pick(@value);
});
}

@list: a b c d;
.box {
each(@list, {
-less-log: extract(@list, @index);
})
}