From f3d882de259963149ef3a002e29ed662843cc56f Mon Sep 17 00:00:00 2001 From: Luke Page Date: Sun, 9 Sep 2012 00:48:57 +0100 Subject: [PATCH 1/3] Fix problem with name arguments with arguments variable and if you've specified all the arguments --- lib/less/parser.js | 1 + lib/less/tree/mixin.js | 67 ++++++++++++++++++++++---------- test/css/mixins-named-args.css | 7 ++++ test/less/mixins-named-args.less | 5 +++ 4 files changed, 60 insertions(+), 20 deletions(-) diff --git a/lib/less/parser.js b/lib/less/parser.js index afb6fa348..51d0f7e43 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -862,6 +862,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)) { 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-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/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%; From bc25dbb531dec3d46c7f4a712948914036044e2e Mon Sep 17 00:00:00 2001 From: Luke Page Date: Sun, 9 Sep 2012 00:52:47 +0100 Subject: [PATCH 2/3] Add support for ';' as a delimiter --- lib/less/parser.js | 43 ++++++++++++++----- test/css/mixins-args.css | 17 ++++++++ .../errors/mixed-mixin-definition-args.less | 3 ++ .../errors/mixed-mixin-definition-args.txt | 3 ++ test/less/mixins-args.less | 24 +++++++++++ 5 files changed, 80 insertions(+), 10 deletions(-) create mode 100644 test/less/errors/mixed-mixin-definition-args.less create mode 100644 test/less/errors/mixed-mixin-definition-args.txt diff --git a/lib/less/parser.js b/lib/less/parser.js index 51d0f7e43..39bcc47de 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, index = i, s = input.charAt(i), name, value, important = false; if (s !== '.' && s !== '#') { return } @@ -794,31 +794,54 @@ less.Parser = function Parser(env) { c = $('>'); } if ($('(')) { + //todo remove + name = null; + isSemiColonSeperated = false; + 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 (isSemiColonSeperated && expressions.length > 1) { + error("Cannot mix ; and , as delimiter types"); } + 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) { + isSemiColonSeperated = true; + + if (expressions.length > 1) { + value = new(tree.Value)(expressions); + } + argsSemiColon.push({ name: name, value: value }); + + name = null; + expressions = []; + } } + if (! $(')')) throw new(Error)("Expected )"); } + args = isSemiColonSeperated ? argsSemiColon : argsComma; + if ($(this.important)) { important = true; } 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/less/errors/mixed-mixin-definition-args.less b/test/less/errors/mixed-mixin-definition-args.less new file mode 100644 index 000000000..c08c89dc3 --- /dev/null +++ b/test/less/errors/mixed-mixin-definition-args.less @@ -0,0 +1,3 @@ +.mixin(@a : 4; @b : 4, @c: 3) { + will: fail; +} \ No newline at end of file diff --git a/test/less/errors/mixed-mixin-definition-args.txt b/test/less/errors/mixed-mixin-definition-args.txt new file mode 100644 index 000000000..b66a5d533 --- /dev/null +++ b/test/less/errors/mixed-mixin-definition-args.txt @@ -0,0 +1,3 @@ +ParseError: Syntax Error on line 1 in {path}mixed-mixin-definition-args.less:1:10 +1 .mixin(@a : 4; @b : 4, @c: 3) { +2 will: fail; diff --git a/test/less/mixins-args.less b/test/less/mixins-args.less index ea43a0a53..6c479512c 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;); +} From b1e9cf4ba114a378b7dc5557057eefd6b81d06b3 Mon Sep 17 00:00:00 2001 From: Luke Page Date: Sun, 9 Sep 2012 10:17:05 +0100 Subject: [PATCH 3/3] basic support in definition, clean up and tests --- lib/less/parser.js | 24 ++++++++++++------- .../errors/mixed-mixin-definition-args-1.less | 6 +++++ .../errors/mixed-mixin-definition-args-1.txt | 4 ++++ .../errors/mixed-mixin-definition-args-2.less | 6 +++++ .../errors/mixed-mixin-definition-args-2.txt | 4 ++++ .../errors/mixed-mixin-definition-args.less | 3 --- .../errors/mixed-mixin-definition-args.txt | 3 --- test/less/mixins-args.less | 2 +- 8 files changed, 36 insertions(+), 16 deletions(-) create mode 100644 test/less/errors/mixed-mixin-definition-args-1.less create mode 100644 test/less/errors/mixed-mixin-definition-args-1.txt create mode 100644 test/less/errors/mixed-mixin-definition-args-2.less create mode 100644 test/less/errors/mixed-mixin-definition-args-2.txt delete mode 100644 test/less/errors/mixed-mixin-definition-args.less delete mode 100644 test/less/errors/mixed-mixin-definition-args.txt diff --git a/lib/less/parser.js b/lib/less/parser.js index 39bcc47de..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, argsSemiColon = [], argsComma = [], args, delim, arg, nameLoop, expressions, isSemiColonSeperated, 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,9 +794,6 @@ less.Parser = function Parser(env) { c = $('>'); } if ($('(')) { - //todo remove - name = null; - isSemiColonSeperated = false; expressions = []; while (arg = $(this.expression)) { nameLoop = null; @@ -807,8 +804,11 @@ less.Parser = function Parser(env) { var val = arg.value[0]; if (val instanceof tree.Variable) { if ($(':')) { - if (isSemiColonSeperated && expressions.length > 1) { - error("Cannot mix ; and , as delimiter types"); + if (expressions.length > 0) { + if (isSemiColonSeperated) { + error("Cannot mix ; and , as delimiter types"); + } + expressionContainsNamed = true; } value = expect(this.expression); nameLoop = (name = val.name); @@ -825,6 +825,11 @@ less.Parser = function Parser(env) { } if ($(';') || isSemiColonSeperated) { + + if (expressionContainsNamed) { + error("Cannot mix ; and , as delimiter types"); + } + isSemiColonSeperated = true; if (expressions.length > 1) { @@ -834,10 +839,11 @@ less.Parser = function Parser(env) { name = null; expressions = []; + expressionContainsNamed = false; } } - if (! $(')')) throw new(Error)("Expected )"); + expect(')'); } args = isSemiColonSeperated ? argsSemiColon : argsComma; @@ -875,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(); @@ -907,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/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/errors/mixed-mixin-definition-args.less b/test/less/errors/mixed-mixin-definition-args.less deleted file mode 100644 index c08c89dc3..000000000 --- a/test/less/errors/mixed-mixin-definition-args.less +++ /dev/null @@ -1,3 +0,0 @@ -.mixin(@a : 4; @b : 4, @c: 3) { - will: fail; -} \ No newline at end of file diff --git a/test/less/errors/mixed-mixin-definition-args.txt b/test/less/errors/mixed-mixin-definition-args.txt deleted file mode 100644 index b66a5d533..000000000 --- a/test/less/errors/mixed-mixin-definition-args.txt +++ /dev/null @@ -1,3 +0,0 @@ -ParseError: Syntax Error on line 1 in {path}mixed-mixin-definition-args.less:1:10 -1 .mixin(@a : 4; @b : 4, @c: 3) { -2 will: fail; diff --git a/test/less/mixins-args.less b/test/less/mixins-args.less index 6c479512c..76ba26234 100644 --- a/test/less/mixins-args.less +++ b/test/less/mixins-args.less @@ -135,7 +135,7 @@ body { one: @a; } -.mixin-takes-two(@a, @b) { +.mixin-takes-two(@a; @b) { one: @a; two: @b; }