From a2b1ef808f4230878c959bc9f9c7c34f32a044fd Mon Sep 17 00:00:00 2001 From: Donald Harvey Date: Sat, 4 Feb 2012 15:09:51 +0000 Subject: [PATCH 1/2] Added named argument support. --- lib/less/parser.js | 28 +++++++++++++++---- lib/less/tree/mixin.js | 52 ++++++++++++++++++++++++----------- test/css/mixins-args.css | 6 ++++ test/less/mixins-args.less | 10 ++++++- test/less/mixins-pattern.less | 2 +- 5 files changed, 75 insertions(+), 23 deletions(-) diff --git a/lib/less/parser.js b/lib/less/parser.js index 26bc4978a..272aebf74 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -744,7 +744,7 @@ less.Parser = function Parser(env) { // A Mixin call, with an optional argument list // // #mixins > .square(#fff); - // .rounded(4px, black); + // .rounded(4px, black, @shadow: 5px); // .button; // // The `while` loop is there because mixins can be @@ -752,7 +752,7 @@ less.Parser = function Parser(env) { // selector for now. // call: function () { - var elements = [], e, c, args, index = i, s = input.charAt(i), important = false; + var elements = [], e, c, args = [], kwargs = [], inkwargs, index = i, s = input.charAt(i), important = false; if (s !== '.' && s !== '#') { return } @@ -760,21 +760,39 @@ less.Parser = function Parser(env) { elements.push(new(tree.Element)(c, e, i)); c = $('>'); } - $('(') && (args = $(this.entities.arguments)) && $(')'); + if ($('(')) { + inkwargs = false; + while(arg = $(this.variable) || $(this.expression)) { + if(typeof arg == 'string') { + value = expect(this.expression, 'expected expression'); + inkwargs = true; + kwargs.push({ name: arg, value: value }); + if(! $(',')) { break; } + } + else { + if (inkwargs) { + error('named arguments must come after positional arguments'); + } + args.push(arg); + if(! $(',')) { break; } + } + } + expect(')'); + } if ($(this.important)) { important = true; } if (elements.length > 0 && ($(';') || peek('}'))) { - return new(tree.mixin.Call)(elements, args || [], index, env.filename, important); + return new(tree.mixin.Call)(elements, args || [], kwargs, index, env.filename, important); } }, // // A Mixin definition, with a list of parameters // - // .rounded (@radius: 2px, @color) { + // .rounded (@radius: 2px, @color, @shadow: 0px) { // ... // } // diff --git a/lib/less/tree/mixin.js b/lib/less/tree/mixin.js index 4464bc6cd..004e62048 100644 --- a/lib/less/tree/mixin.js +++ b/lib/less/tree/mixin.js @@ -1,25 +1,36 @@ (function (tree) { tree.mixin = {}; -tree.mixin.Call = function (elements, args, index, filename, important) { +tree.mixin.Call = function (elements, args, kwargs, index, filename, important) { this.selector = new(tree.Selector)(elements); this.arguments = args; + this.kwargs = kwargs; this.index = index; this.filename = filename; this.important = important; }; tree.mixin.Call.prototype = { eval: function (env) { - var mixins, args, rules = [], match = false; + var mixins, args, kwargs = {}, kwargsEval = {}, rules = [], match = false, name; for (var i = 0; i < env.frames.length; i++) { if ((mixins = env.frames[i].find(this.selector)).length > 0) { args = this.arguments && this.arguments.map(function (a) { return a.eval(env) }); + for(var k = 0; k < this.kwargs.length; k++) { + name = this.kwargs[k].name; + if (kwargs[name] !== undefined) { + throw { type: 'Runtime', + message: 'Multiple values given for keyword argument `' + name + '`', + index: this.index, filename: this.filename } + } + kwargs[name] = this.kwargs[k].value; + kwargsEval[name] = this.kwargs[k].value.eval(env); + } for (var m = 0; m < mixins.length; m++) { - if (mixins[m].match(args, env)) { + if (mixins[m].match(args, kwargsEval, env)) { try { Array.prototype.push.apply( - rules, mixins[m].eval(env, this.arguments, this.important).rules); + rules, mixins[m].eval(env, this.arguments, kwargs, this.important).rules); match = true; } catch (e) { throw { message: e.message, index: this.index, filename: this.filename, stack: e.stack }; @@ -68,32 +79,41 @@ 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; - + evalParams: function (env, args, kwargs) { + var frame = new(tree.Ruleset)(null, []), paramnames = {}, varargs; + // Make sure all kwargs are also in this.params. + this.params.forEach(function(param) { paramnames[param.name] = 1 }) + for(key in kwargs) { + if (!paramnames[key]) { + throw { type: 'Runtime', message: this.name + "() got unexpected keyword argument " + key }; + } + } for (var i = 0, val, name; i < this.params.length; i++) { if (name = this.params[i].name) { + if (args && args[i] && kwargs && kwargs[name]) { + throw { type: 'Runtime', message: this.name + "() got multiple values for parameter " + this.params[i].name } + } if (this.params[i].variadic && args) { varargs = []; for (var j = i; j < args.length; j++) { varargs.push(args[j].eval(env)); } frame.rules.unshift(new(tree.Rule)(name, new(tree.Expression)(varargs).eval(env))); - } else if (val = (args && args[i]) || this.params[i].value) { + } else if (val = (args && args[i]) || (kwargs && kwargs[name]) || this.params[i].value) { frame.rules.unshift(new(tree.Rule)(name, val.eval(env))); } else { throw { type: 'Runtime', message: "wrong number of arguments for " + this.name + - ' (' + args.length + ' for ' + this.arity + ')' }; + ' (' + (args.length + Object.keys(kwargs).length) + ' for ' + this.arity + ')' }; } } } return frame; }, - eval: function (env, args, important) { - var frame = this.evalParams(env, args), context, _arguments = [], rules, start; + eval: function (env, args, kwargs, important) { + var frame = this.evalParams(env, args, kwargs), context, _arguments = [], rules, start; for (var i = 0; i < Math.max(this.params.length, args && args.length); i++) { - _arguments.push(args[i] || this.params[i].value); + _arguments.push(args[i] || kwargs[this.params[i].name] || this.params[i].value); } frame.rules.unshift(new(tree.Rule)('@arguments', new(tree.Expression)(_arguments).eval(env))); @@ -106,8 +126,8 @@ tree.mixin.Definition.prototype = { frames: [this, frame].concat(this.frames, env.frames) }); }, - match: function (args, env) { - var argsLength = (args && args.length) || 0, len, frame; + match: function (args, kwargs, env) { + var argsLength = ((args && args.length) + (kwargs && Object.keys(kwargs).length)) || 0, len, frame; if (! this.variadic) { if (argsLength < this.required) { return false } @@ -116,10 +136,10 @@ tree.mixin.Definition.prototype = { } if (this.condition && !this.condition.eval({ - frames: [this.evalParams(env, args)].concat(env.frames) + frames: [this.evalParams(env, args, kwargs)].concat(env.frames) })) { return false } - len = Math.min(argsLength, this.arity); + len = Math.min(args.length, this.arity); for (var i = 0; i < len; i++) { if (!this.params[i].name) { diff --git a/test/css/mixins-args.css b/test/css/mixins-args.css index d5b67f304..680198476 100644 --- a/test/css/mixins-args.css +++ b/test/css/mixins-args.css @@ -22,6 +22,12 @@ width: 5px; height: 49%; } +.kwargs { + border: 90% bold #0000ff; +} +.args-and-kwargs { + border: 10px dotted #ffffff; +} .var-args { width: 45; height: 17%; diff --git a/test/less/mixins-args.less b/test/less/mixins-args.less index ea43a0a53..15118262c 100644 --- a/test/less/mixins-args.less +++ b/test/less/mixins-args.less @@ -43,9 +43,17 @@ .mixin(); } +.kwargs { + .mixina(@color: blue, @style: bold, @width: 10% * 9); +} + +.args-and-kwargs { + .mixina(dotted, @width: 10px, @color: white) +} + .var-args { @var: 9; - .mixin(@var, @var * 2); + .mixin(@var, @b: @var * 2); } .multi-mix { diff --git a/test/less/mixins-pattern.less b/test/less/mixins-pattern.less index b2121e956..e9991e76a 100644 --- a/test/less/mixins-pattern.less +++ b/test/less/mixins-pattern.less @@ -72,7 +72,7 @@ .border(right, 4px); } .border-left { - .border(left, 4px); + .border(left, @width: 4px); } // From b33a0a816b1a87ce3583a724a44ae78c36ca6ffb Mon Sep 17 00:00:00 2001 From: Donald Harvey Date: Sat, 4 Feb 2012 15:11:52 +0000 Subject: [PATCH 2/2] Added filename to test parser env for better debugging. --- test/less-test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/less-test.js b/test/less-test.js index 46412e011..20f123f24 100644 --- a/test/less-test.js +++ b/test/less-test.js @@ -42,7 +42,8 @@ function toCSS(path, callback) { new(less.Parser)({ paths: [require('path').dirname(path)], - optimization: 0 + optimization: 0, + filename: require('path').basename(path) }).parse(str, function (err, tree) { if (err) { callback(err);