diff --git a/lib/less/parser.js b/lib/less/parser.js index afb6fa348..910a01e38 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -783,7 +783,7 @@ less.Parser = function Parser(env) { // selector for now. // call: function () { - var elements = [], e, c, args = [], arg, index = i, s = input.charAt(i), name, value, important = false; + var elements = [], e, c, argsSemiColon = [], argsComma = [], args, delim, arg, nameLoop, expressions, isSemiColonSeperated, expressionContainsNamed, index = i, s = input.charAt(i), name, value, important = false; if (s !== '.' && s !== '#') { return } @@ -794,31 +794,60 @@ less.Parser = function Parser(env) { c = $('>'); } if ($('(')) { + expressions = []; while (arg = $(this.expression)) { + nameLoop = null; value = arg; - name = null; // Variable if (arg.value.length == 1) { var val = arg.value[0]; if (val instanceof tree.Variable) { if ($(':')) { - if (value = $(this.expression)) { - name = val.name; - } else { - throw new(Error)("Expected value"); + if (expressions.length > 0) { + if (isSemiColonSeperated) { + error("Cannot mix ; and , as delimiter types"); + } + expressionContainsNamed = true; } + value = expect(this.expression); + nameLoop = (name = val.name); } } } - - args.push({ name: name, value: value }); - - if (! $(',')) { break } + + expressions.push(value); + + argsComma.push({ name: nameLoop, value: value }); + + if ($(',')) { + continue; + } + + if ($(';') || isSemiColonSeperated) { + + if (expressionContainsNamed) { + error("Cannot mix ; and , as delimiter types"); + } + + isSemiColonSeperated = true; + + if (expressions.length > 1) { + value = new(tree.Value)(expressions); + } + argsSemiColon.push({ name: name, value: value }); + + name = null; + expressions = []; + expressionContainsNamed = false; + } } - if (! $(')')) throw new(Error)("Expected )"); + + expect(')'); } + args = isSemiColonSeperated ? argsSemiColon : argsComma; + if ($(this.important)) { important = true; } @@ -852,7 +881,7 @@ less.Parser = function Parser(env) { definition: function () { var name, params = [], match, ruleset, param, value, cond, variadic = false; if ((input.charAt(i) !== '.' && input.charAt(i) !== '#') || - peek(/^[^{]*(;|})/)) return; + peek(/^[^{]*\}/)) return; save(); @@ -862,6 +891,7 @@ less.Parser = function Parser(env) { do { if (input.charAt(i) === '.' && $(/^\.{3}/)) { variadic = true; + params.push({ variadic: true }); break; } else if (param = $(this.entities.variable) || $(this.entities.literal) || $(this.entities.keyword)) { @@ -883,7 +913,7 @@ less.Parser = function Parser(env) { } else { break; } - } while ($(',')) + } while ($(',') || $(';')) // .mixincall("@{a}"); // looks a bit like a mixin definition.. so we have to be nice and restore diff --git a/lib/less/tree/mixin.js b/lib/less/tree/mixin.js index b441bf3b2..1de625ea7 100644 --- a/lib/less/tree/mixin.js +++ b/lib/less/tree/mixin.js @@ -70,42 +70,69 @@ tree.mixin.Definition.prototype = { find: function () { return this.parent.find.apply(this, arguments) }, rulesets: function () { return this.parent.rulesets.apply(this) }, - evalParams: function (env, args) { - var frame = new(tree.Ruleset)(null, []), varargs, arg; + evalParams: function (env, args, evaldArguments) { + var frame = new(tree.Ruleset)(null, []), varargs, arg, params = this.params.slice(0), i, j, val, name, isNamedFound; + + if (args) { + args = args.slice(0); - for (var i = 0, val, name; i < this.params.length; i++) { - arg = args && args[i] - - if (arg && arg.name) { - frame.rules.unshift(new(tree.Rule)(arg.name, arg.value.eval(env))); - args.splice(i, 1); - i--; - continue; + for(i = 0; i < args.length; i++) { + arg = args[i]; + if (name = (arg && arg.name)) { + isNamedFound = false; + for(j = 0; j < params.length; j++) { + if (!evaldArguments[j] && name === params[j].name) { + evaldArguments[j] = arg.value.eval(env); + frame.rules.unshift(new(tree.Rule)(name, arg.value.eval(env))); + isNamedFound = true; + break; + } + } + if (isNamedFound) { + args.splice(i, 1); + i--; + continue; + } else { + throw { type: 'Runtime', message: "Named argument for " + this.name + + ' ' + args[i].name + ' not found' }; + } + } } + } + + for (i = 0; i < params.length; i++) { + if (evaldArguments[i]) continue; + + arg = args && args[i] - if (name = this.params[i].name) { - if (this.params[i].variadic && args) { + if (name = params[i].name) { + if (params[i].variadic && args) { varargs = []; - for (var j = i; j < args.length; j++) { + for (j = i; j < args.length; j++) { varargs.push(args[j].value.eval(env)); } frame.rules.unshift(new(tree.Rule)(name, new(tree.Expression)(varargs).eval(env))); - } else if (val = (arg && arg.value) || this.params[i].value) { + } else if (val = (arg && arg.value) || params[i].value) { frame.rules.unshift(new(tree.Rule)(name, val.eval(env))); + evaldArguments[i] = val.eval(env) } else { throw { type: 'Runtime', message: "wrong number of arguments for " + this.name + ' (' + args.length + ' for ' + this.arity + ')' }; } } + + if (params[i].variadic && args) { + for (j = i; j < args.length; j++) { + evaldArguments[j] = args[j].value.eval(env); + } + } } + return frame; }, eval: function (env, args, important) { - var frame = this.evalParams(env, args), context, _arguments = [], rules, start; + var _arguments = [], frame = this.evalParams(env, args, _arguments), context, rules, start; - for (var i = 0; i < Math.max(this.params.length, args && args.length); i++) { - _arguments.push((args[i] && args[i].value) || this.params[i].value); - } frame.rules.unshift(new(tree.Rule)('@arguments', new(tree.Expression)(_arguments).eval(env))); rules = important ? @@ -127,13 +154,13 @@ tree.mixin.Definition.prototype = { } if (this.condition && !this.condition.eval({ - frames: [this.evalParams(env, args)].concat(env.frames) + frames: [this.evalParams(env, args, [])].concat(env.frames) })) { return false } len = Math.min(argsLength, this.arity); for (var i = 0; i < len; i++) { - if (!this.params[i].name) { + if (!this.params[i].name && !this.params[i].variadic) { if (args[i].value.eval(env).toCSS() != this.params[i].value.eval(env).toCSS()) { return false; } diff --git a/test/css/mixins-args.css b/test/css/mixins-args.css index d5b67f304..365d5fc95 100644 --- a/test/css/mixins-args.css +++ b/test/css/mixins-args.css @@ -72,3 +72,20 @@ body { border: "{"; width: "{"; } +.comma-vs-semi-colon { + one: a; + two: b, c; + one: d, e; + two: f; + one: g; + one: h; + one: i; + one: j; + one: k; + two: l; + one: m, n; + one: o, p; + two: q; + one: r, s; + two: t; +} diff --git a/test/css/mixins-named-args.css b/test/css/mixins-named-args.css index 1c1afaf8f..e460aa104 100644 --- a/test/css/mixins-named-args.css +++ b/test/css/mixins-named-args.css @@ -2,11 +2,18 @@ color: blue; width: 5px; height: 99%; + args: 1px 100%; text-align: center; } .class { width: 5px; height: 19%; + args: 1px 20%; +} +.all-args-wrong-args { + width: 10px; + height: 9%; + args: 2px 10%; } .named-args2 { width: 15px; diff --git a/test/less/errors/mixed-mixin-definition-args-1.less b/test/less/errors/mixed-mixin-definition-args-1.less new file mode 100644 index 000000000..9b0e23afa --- /dev/null +++ b/test/less/errors/mixed-mixin-definition-args-1.less @@ -0,0 +1,6 @@ +.mixin(@a : 4, @b : 3, @c: 2) { + will: fail; +} +.mixin-test { + .mixin(@a: 5; @b: 6, @c: 7); +} \ No newline at end of file diff --git a/test/less/errors/mixed-mixin-definition-args-1.txt b/test/less/errors/mixed-mixin-definition-args-1.txt new file mode 100644 index 000000000..6ceda7d8a --- /dev/null +++ b/test/less/errors/mixed-mixin-definition-args-1.txt @@ -0,0 +1,4 @@ +SyntaxError: Cannot mix ; and , as delimiter types in {path}mixed-mixin-definition-args-1.less:5:29 +4 .mixin-test { +5 .mixin(@a: 5; @b: 6, @c: 7); +6 } diff --git a/test/less/errors/mixed-mixin-definition-args-2.less b/test/less/errors/mixed-mixin-definition-args-2.less new file mode 100644 index 000000000..c9709427a --- /dev/null +++ b/test/less/errors/mixed-mixin-definition-args-2.less @@ -0,0 +1,6 @@ +.mixin(@a : 4, @b : 3, @c: 2) { + will: fail; +} +.mixin-test { + .mixin(@a: 5, @b: 6; @c: 7); +} diff --git a/test/less/errors/mixed-mixin-definition-args-2.txt b/test/less/errors/mixed-mixin-definition-args-2.txt new file mode 100644 index 000000000..ebb566650 --- /dev/null +++ b/test/less/errors/mixed-mixin-definition-args-2.txt @@ -0,0 +1,4 @@ +SyntaxError: Cannot mix ; and , as delimiter types in {path}mixed-mixin-definition-args-2.less:5:25 +4 .mixin-test { +5 .mixin(@a: 5, @b: 6; @c: 7); +6 } diff --git a/test/less/mixins-args.less b/test/less/mixins-args.less index ea43a0a53..76ba26234 100644 --- a/test/less/mixins-args.less +++ b/test/less/mixins-args.less @@ -128,3 +128,27 @@ body { .edge-case { .mixin-arguments("{"); } + +// semi-colon vs comma for delimiting + +.mixin-takes-one(@a) { + one: @a; +} + +.mixin-takes-two(@a; @b) { + one: @a; + two: @b; +} + +.comma-vs-semi-colon { + .mixin-takes-two(@a : a; @b : b, c); + .mixin-takes-two(@a : d, e; @b : f); + .mixin-takes-one(@a: g); + .mixin-takes-one(@a : h;); + .mixin-takes-one(i); + .mixin-takes-one(j;); + .mixin-takes-two(k, l); + .mixin-takes-one(m, n;); + .mixin-takes-two(o, p; q); + .mixin-takes-two(r, s; t;); +} diff --git a/test/less/mixins-named-args.less b/test/less/mixins-named-args.less index 57b07d010..afe9ad6b9 100644 --- a/test/less/mixins-named-args.less +++ b/test/less/mixins-named-args.less @@ -1,6 +1,7 @@ .mixin (@a: 1px, @b: 50%) { width: @a * 5; height: @b - 1%; + args: @arguments; } .mixin (@a: 1px, @b: 50%) when (@b > 75%){ text-align: center; @@ -16,6 +17,10 @@ .mixin(@b: @var); } +.all-args-wrong-args { + .mixin(@b: 10%, @a: 2px); +} + .mixin2 (@a: 1px, @b: 50%, @c: 50) { width: @a * 5; height: @b - 1%;