Skip to content

Each() function #2786

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

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/less/functions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module.exports = function(environment) {
require("./color");
require("./color-blending");
require("./data-uri")(environment);
require("./list");
require("./math");
require("./number");
require("./string");
Expand Down
55 changes: 55 additions & 0 deletions lib/less/functions/list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
var Ruleset = require("../tree/ruleset"),
Rule = require("../tree/rule"),
Selector = require("../tree/selector"),
Element = require("../tree/element"),
Dimension = require("../tree/dimension"),
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({
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, vars, ruleset) {
var i = 0, rules = [], rs, newRules;

rs = ruleset ? ruleset.ruleset : vars.ruleset;

list.value.forEach(function(item) {
i = i + 1;
newRules = rs.rules.slice(0);
newRules.push(new Rule(ruleset && vars.value[1] ? '@' + vars.value[1].value : '@item',
item,
false, false, this.index, this.currentFileInfo));
newRules.push(new Rule(ruleset && vars.value[0] ? '@' + vars.value[0].value : '@index',
new Dimension(i),
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);

}

});
17 changes: 1 addition & 16 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,13 +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);
}
});
46 changes: 39 additions & 7 deletions lib/less/parser/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ var Parser = function Parser(context, imports, fileInfo) {
}

node = mixin.definition() || this.rule() || this.ruleset() ||
mixin.call() || this.rulesetCall() || this.directive();
mixin.call() || this.rulesetCall() || this.entities.call() || this.directive();
if (node) {
root.push(node);
} else {
Expand Down Expand Up @@ -390,19 +390,51 @@ var Parser = function Parser(context, imports, fileInfo) {
return new(tree.Call)(name, args, index, fileInfo);
},
arguments: function () {
var args = [], arg;
var argsSemiColon = [], argsComma = [],
expressions = [],
isSemiColonSeparated, value, arg;

parserInput.save();

while (true) {
Copy link
Member

Choose a reason for hiding this comment

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

@matthew-dean By the way. If we allow all these "mixin-like" arguments for functions, will there be any difference between mixin.arguments() and function.arguments() beside the "named arguments" thing? Obviously I'm just considering if this code can be reused somehow. I understand that because of the "named args" they need to return different structures, but are there any other differences?

Copy link
Member Author

Choose a reason for hiding this comment

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

There were various bits I modified from mixin args to functions args. One is that there are legacy IE "function-like" calls that need to be parsed but that mixins don't parse. (Which I'm not sure are still needed, but it's possible that legacy CSS code continues to persist in currently-used mixins.) It's possible that may be the only real variation, at least for calls. The code for mixin args is re-used between both mixin calls and mixin definitions, so it's possible we could collapse args for "function calls" and "mixin calls", and break out mixin definitions? Or maybe could just collapse all 3 with appropriate if statements.

Copy link
Member

Choose a reason for hiding this comment

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

Thanks... Yes, it seems to need more investigations on possible unifications.
I was just about to experiment with some @var(args...); stuff, and realized that having three distinct functions to handle pretty much the same syntax would be somewhat an overkill. Additionally I recall that for #1857, I was also thinking of allowing @arg... stuff for functions too, but quickly bailed out because of all these differences :(

arg = this.assignment() || parsers.expression();

arg = parsers.detachedRuleset() || this.assignment() || parsers.expression();

if (!arg) {
break;
}
args.push(arg);
if (! parserInput.$char(',')) {
break;

value = arg;

if (arg.value && arg.value.length == 1) {
value = arg.value[0];
}

if (value) {
expressions.push(value);
}

argsComma.push(value);

if (parserInput.$char(',')) {
continue;
}

if (parserInput.$char(';') || isSemiColonSeparated) {

isSemiColonSeparated = true;

if (expressions.length > 1) {
value = new(tree.Value)(expressions);
}
argsSemiColon.push(value);

expressions = [];
}
}
return args;

parserInput.forget();
return isSemiColonSeparated ? argsSemiColon : argsComma;
},
literal: function () {
return this.dimension() ||
Expand Down
2 changes: 1 addition & 1 deletion lib/less/tree/call.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Call.prototype.eval = function (context) {
var args = this.args.map(function (a) { return a.eval(context); }),
result, funcCaller = new FunctionCaller(this.name, context, this.index, this.currentFileInfo);

if (funcCaller.isValid()) { // 1.
if (funcCaller.isValid()) {
try {
result = funcCaller.call(args);
if (result != null) {
Expand Down
24 changes: 18 additions & 6 deletions lib/less/visitors/to-css-visitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,12 +199,24 @@ ToCSSVisitor.prototype = {
return directiveNode;
},

checkPropertiesInRoot: function(rules) {
checkValidNodes: function(rules, isRoot) {
if (!rules || !rules.length) {
return;
}

var ruleNode;
for (var i = 0; i < rules.length; i++) {
ruleNode = rules[i];
if (ruleNode instanceof tree.Rule && !ruleNode.variable) {
throw { message: "properties must be inside selector blocks, they cannot be in the root.",
if (isRoot && ruleNode instanceof tree.Rule && !ruleNode.variable) {
throw { message: "Properties must be inside selector blocks. They cannot be in the root.",
index: ruleNode.index, filename: ruleNode.currentFileInfo ? ruleNode.currentFileInfo.filename : null};
}
if (ruleNode instanceof tree.Call) {
throw { message: "Custom function '" + ruleNode.name + "' is not registered.",
index: ruleNode.index, filename: ruleNode.currentFileInfo ? ruleNode.currentFileInfo.filename : null};
}
if (ruleNode instanceof tree.Color) {
throw { message: "Invalid value in a ruleset or root node.",
index: ruleNode.index, filename: ruleNode.currentFileInfo ? ruleNode.currentFileInfo.filename : null};
}
}
Expand All @@ -213,9 +225,9 @@ ToCSSVisitor.prototype = {
visitRuleset: function (rulesetNode, visitArgs) {
//at this point rulesets are nested into each other
var rule, rulesets = [];
if (rulesetNode.firstRoot) {
this.checkPropertiesInRoot(rulesetNode.rules);
}

this.checkValidNodes(rulesetNode.rules, rulesetNode.firstRoot);

if (! rulesetNode.root) {
//remove invisible paths
this._compileRulesetPaths(rulesetNode);
Expand Down
8 changes: 8 additions & 0 deletions test/css/functions.css
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,14 @@
two-length: 2;
two-one: b;
two-two: 2;
index: 1, 2;
item1: a 1;
item2: b 2;
nest-a-1: 10px 1;
nest-b-1: 15px 2;
nest-c-2: 20px 1;
nest-d-2: 25px 2;
padding: 10px 20px 30px 40px;
}
/* comment1 */
html {
Expand Down
4 changes: 4 additions & 0 deletions test/css/plugin.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@charset "utf-8";
.other {
trans: transitive;
}
Expand Down Expand Up @@ -42,3 +43,6 @@
result: local;
}
}
.root {
prop: value;
}
2 changes: 1 addition & 1 deletion test/less/errors/detached-ruleset-3.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
SyntaxError: properties must be inside selector blocks, they cannot be in the root. in {path}detached-ruleset-3.less on line 2, column 3:
SyntaxError: Properties must be inside selector blocks. They cannot be in the root. in {path}detached-ruleset-3.less on line 2, column 3:
1 @a: {
2 b: 1;
3 };
1 change: 1 addition & 0 deletions test/less/errors/plugin-undefined-1.less
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
func();
2 changes: 2 additions & 0 deletions test/less/errors/plugin-undefined-1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SyntaxError: Custom function 'func' is not registered. in {path}plugin-undefined-1.less on line 1, column 1:
1 func();
2 changes: 2 additions & 0 deletions test/less/errors/plugin-undefined-2.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@plugin "../plugin/plugin-root.js";
test-undefined();
3 changes: 3 additions & 0 deletions test/less/errors/plugin-undefined-2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
SyntaxError: Custom function 'test-undefined' is not registered. in {path}plugin-undefined-2.less on line 2, column 1:
1 @plugin "../plugin/plugin-root.js";
2 test-undefined();
2 changes: 1 addition & 1 deletion test/less/errors/property-in-root.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
SyntaxError: properties must be inside selector blocks, they cannot be in the root. in {path}property-in-root.less on line 2, column 3:
SyntaxError: Properties must be inside selector blocks. They cannot be in the root. in {path}property-in-root.less on line 2, column 3:
1 .a() {
2 prop:1;
3 }
2 changes: 1 addition & 1 deletion test/less/errors/property-in-root2.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
SyntaxError: properties must be inside selector blocks, they cannot be in the root. in {path}property-in-root.less on line 2, column 3:
SyntaxError: Properties must be inside selector blocks. They cannot be in the root. in {path}property-in-root.less on line 2, column 3:
1 .a() {
2 prop:1;
3 }
2 changes: 1 addition & 1 deletion test/less/errors/property-in-root3.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
SyntaxError: properties must be inside selector blocks, they cannot be in the root. in {path}property-in-root3.less on line 1, column 1:
SyntaxError: Properties must be inside selector blocks. They cannot be in the root. in {path}property-in-root3.less on line 1, column 1:
1 prop:1;
2 .a {
22 changes: 22 additions & 0 deletions test/less/functions.less
Original file line number Diff line number Diff line change
Expand Up @@ -223,10 +223,32 @@
two-length: length(@two);
two-one: extract(@two, 1);
two-two: extract(@two, 2);

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

// nested each
each(10px 15px, 20px 25px; {
@alpha: a b c d;
@i: (@index - 1);

each(@item; key, val; {
@a: extract(@alpha, (@i * 2 + @key));
nest-@{a}-@{index}: @val @key;
});
});

// vector math
each(1 2 3 4, {
padding+_: (@item * 10px);
});
}
@color1: #FFF;/* comment1 */
@color2: #FFF/* comment2 */;
html {
color: mix(blue, @color1, 50%);
color: mix(blue, @color2, 50%);
}

6 changes: 6 additions & 0 deletions test/less/plugin.less
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,11 @@
}
}

@plugin "./plugin/plugin-root";
@ruleset2: test-detached-ruleset();
.root {
@ruleset2();
}

test-directive("@charset"; "utf-8");

11 changes: 11 additions & 0 deletions test/less/plugin/plugin-root.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
functions.addMultiple({
"test-detached-ruleset" : function() {
var rule = new tree.Rule('prop', new tree.Anonymous('value'));
return new tree.DetachedRuleset(new tree.Ruleset("", [ rule ]));
},
"test-directive": function(arg1, arg2) {
return new tree.Directive(arg1.value, new tree.Anonymous('"' + arg2.value + '"'));
},
// Functions must return something. Must 'return true' if they produce no output.
"test-undefined": function() { }
});