From 39735df82fb7a34203f0db4922da04416f2a7a10 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Tue, 9 Feb 2016 13:07:42 +0100 Subject: [PATCH] fp docs - Inject default values and cap args. --- lib/doc/apply-fp-mapping.js | 336 +++++++++++++++++++++++++++++++----- lib/doc/test.js | 249 +++++++++++++++++++++++--- lodash.js | 6 +- package.json | 1 + 4 files changed, 526 insertions(+), 66 deletions(-) diff --git a/lib/doc/apply-fp-mapping.js b/lib/doc/apply-fp-mapping.js index bf443773e..fa6792379 100644 --- a/lib/doc/apply-fp-mapping.js +++ b/lib/doc/apply-fp-mapping.js @@ -1,5 +1,6 @@ var _ = require('lodash'), j = require('jscodeshift'), + recast = require('recast'), Entry = require('docdown/lib/entry'); var baseGetParams = Entry.prototype.getParams; @@ -11,14 +12,25 @@ function getMultilineValue(string, tagName) { result = _.result(RegExp(prelude + '([\\s\\S]*?)' + postlude, 'gm').exec(string), 1, ''); return _.trim(result.replace(RegExp('(?:^|\\n)[\\t ]*\\*[\\t ]' + (tagName == 'example' ? '?' : '*'), 'g'), '\n')); + +} + +// Function copied from docdown/lib/entry that is not exported. +function hasTag(string, tagName) { + tagName = tagName == '*' ? '\\w+' : _.escapeRegExp(tagName); + return RegExp('^ *\\*[\\t ]*@' + tagName + '\\b', 'm').test(string); +} + +function isWrapped(entry) { + return !hasTag(entry, 'static'); } /** - * Extracts the entry's `name` data. + * Extract the entry's `name` data. * Sub-part of Entry.prototype.getCall() that fetches the name. Using `Entry.prototype.getCall()` * makes a call to getParams(), which itself call getBaseName --> infinite recursion. * - * @param {Object} entry Entry whose name to extract. + * @param {object} entry Entry whose name to extract. * @returns {string} The entry's `name` data. */ function getBaseName(entry) { @@ -33,32 +45,38 @@ function getBaseName(entry) { } /** - * Returns the new ary of a given function. + * Return the new ary of a given function. * - * @param {Object} mapping Mapping object that defines the arity of all functions. - * @param {String} name Name of the function associated to the call/function definition. + * @param {object} mapping Mapping object that defines the arity of all functions. + * @param {String} name Name of the function associated to the call/function definition. + * @param {boolean} wrapped Flag indicating whether method is wrapped. Will decrement ary if true. * @return {number} Ary of the function as an integer */ -function getMethodAry(mapping, name) { - return _.find(mapping.caps, function(cap) { +function getMethodAry(mapping, name, wrapped) { + var ary = _.find(mapping.caps, function(cap) { return _.includes(mapping.aryMethod[cap], name) && cap; }); + if (_.isNumber(ary) && wrapped) { + return ary - 1; + } + return ary; } /** - * Reorders `params` for a given function definition/call. + * Reorder `params` for a given function definition/call. * - * @param {Object} mapping Mapping object that defines if and how the `params` will be reordered. - * @param {String} name Name of the function associated to the call/function definition. - * @param {*[]} params Parameters/arguments to reorder. + * @param {object} mapping Mapping object that defines if and how the `params` will be reordered. + * @param {String} name Name of the function associated to the call/function definition. + * @param {*[]} params Parameters/arguments to reorder. + * @param {boolean} wrapped Flag indicating whether method is wrapped. Will decrement ary if true. * @returns {*[]} Reordered parameters/arguments. */ -function reorderParams(mapping, name, params) { +function reorderParams(mapping, name, params, wrapped) { // Check if reordering is needed. if (!mapping || mapping.skipRearg[name]) { return params; } - var reargOrder = mapping.methodRearg[name] || mapping.aryRearg[getMethodAry(mapping, name)]; + var reargOrder = mapping.methodRearg[name] || mapping.aryRearg[getMethodAry(mapping, name, wrapped)]; if (!reargOrder) { return params; } @@ -72,28 +90,41 @@ function reorderParams(mapping, name, params) { var dotsRegex = /^\.\.\./; var parensRegex = /^\((.*)\)$/; +var squareBracketsRegex = /^\[(.*)\]$/; var arrayRegex = /\[\]$/; +/** + * Return `types` as '(X|Y|...)' if `types` contains multiple values, `types[0]` otherwise. + * + * @param {string[]} types Possible types of the parameter. + * @return {string} `types` as a string. + */ function wrapInParensIfMultiple(types) { if (types.length > 1) { return '(' + types.join('|') + ')'; } - return types; + return types[0]; } -function singleItemOrArrayOf(types) { - return types + '|' + types + '[]'; +/** + * Transform parameter type from 'X' to 'X|X[]'. + * + * @param {string[]} param Array whose first item is a description of the parameter type. + * @return {string[]} `param` with the updated type. + */ +function singleItemOrArrayOf(type) { + return type + '|' + type + '[]'; } /** * Replace parameter type from something like `...number` to `number|number[]`. * - * @param {string[]} param Array whose first item is a description of the parameter type. + * @param {string[]} param Array whose first item is a description of the parameter type. * @return {string[]} `param` with the updated type. */ -function removeDotsFromType(param) { +function removeDotsFromTypeAndAllowMultiple(param) { var type = param[0]; - if (!type.startsWith('...')) { + if (!dotsRegex.test(type)) { return param; } @@ -112,15 +143,89 @@ function removeDotsFromType(param) { return [newType].concat(_.tail(param)); } -function updateParamsDescription(mapping, entry, params) { - var paramsWithoutDots = params.map(removeDotsFromType); - return reorderParams(mapping, getBaseName(entry), paramsWithoutDots); +/** + * Replace parameter type from something like `...number` to `number|number[]`. + * + * @param {string[]} param Array whose first item is a description of the parameter type. + * @return {string[]} `param` with the updated type. + */ +function removeDotsFromType(param) { + var type = param[0]; + if (!dotsRegex.test(type)) { + return param; + } + + var newType = type + .replace(dotsRegex, '') + .replace(parensRegex, '$1'); + + return [newType].concat(_.tail(param)); } /** - * Returns a function that extracts the entry's `param` data, reordered according to `mapping`. + * Find and duplicate the parameter with a type of the form '...x'. * - * @param {Object} mapping Mapping object that defines if and how the `params` will be reordered. + * @param {string} name Name of the method. + * @param {string[]} params Description of the parameters of the method. + * @return {string[]} Updated parameters. + */ +function duplicateRestArrays(name, params) { + var indexOfRestParam = _.findIndex(params, function(param) { + return dotsRegex.test(param[0]); + }); + if (indexOfRestParam === -1) { + console.log('WARNING: method `' + name + '`', + 'is capped to more arguments than its declared number of parameters,', + 'but does not have a parameter like `...x`'); + } + // duplicates param[indexOfRestParam] at its position + return params.slice(0, indexOfRestParam + 1) + .concat(params.slice(indexOfRestParam)); +} + +/** + * Remove the optional default value and brackets around the name of the method. + * + * @param {string[]} param Array whose second item is the name of the param of the form + * 'name', '[name]' or [name=defaultValue]. + * @return {string[]} `param` with the updated name. + */ +function removeDefaultValue(param) { + var paramName = param[1] + .replace(squareBracketsRegex, '$1') + .split('=') + [0]; + + return [param[0], paramName, param[2]]; +} + +function updateParamsDescription(mapping, entry, params) { + var tmpParams; + var name = getBaseName(entry); + var ary = getMethodAry(mapping, name); + + var wrapped = isWrapped(entry); + if (wrapped) { + // Needs one less argument when wrapped + ary = ary - 1; + params.shift(); + } + + if (ary > params.length) { + tmpParams = duplicateRestArrays(name, params) + .map(removeDotsFromType); + } else { + tmpParams = params + .map(removeDotsFromTypeAndAllowMultiple); + } + tmpParams = tmpParams.map(removeDefaultValue); + return reorderParams(mapping, name, tmpParams, wrapped); +} + +/** + * Return a function that extracts the entry's `param` data, reordered according to `mapping`. + * + * @param {object} mapping Mapping object that defines if and how the `params` will be reordered. * @returns {Function} Function that returns the entry's `param` data. */ function getReorderedParams(mapping) { @@ -135,25 +240,160 @@ function getReorderedParams(mapping) { }; } +function getDefaultValue(paramDescription) { + var paramName = paramDescription[1]; + if (paramName[0] !== '[') { + return null; + } + return paramName + .slice(1, paramName.length - 1) + .split('=') + [1] || null; +} + +/** + * Return an AST node representation of `str`. + * + * @param {object} j JSCodeShift object. + * @param {string} str String to convert. + * @return {ASTObject} AST node. + */ +function stringToASTNode(j, str) { + return j(str).find(j.Expression).paths()[0].value; +} + +/** + * Return the name of a parameter from its description. + * @param {string[]} paramDescription Parameter description. + * @return {string} name of the parameter. + */ +function paramName(paramDescription) { + var paramName = paramDescription[1]; + if (paramName[0] !== '[') { + return paramName; + } + return paramName + .slice(1, paramName.length - 1) + .split('=') + [0]; +} + +/** + * Return a AST node representation of `object.property`. + * If `object.property` can be evaluated (ex: [].length --> 0), the node will be simplified. + * If `defaultValue` references another argument, it will be replaced by the value of that argument. + * + * @param {object} j JSCodeShift object. + * @param {ASTObject} object Object of the member expression. + * @param {string} property Property of the member expression. + * @return {ASTObject} AST node. + */ +function memberExpressiontoASTNode(j, object, property) { + var node = j.memberExpression(object, j.identifier(property)); + try { + // Attempt to evaluate the value of the node to have simpler calls + // [1, 2, 3, 4].length --> 4 + var evaluatedNode = eval(recast.print(node).code); + return stringToASTNode(j, JSON.stringify(evaluatedNode)); + } catch (e) { + return node; + } +} + +/** + * Return a AST node representation of `defaultValue`. + * If `defaultValue` references another argument, it will be replaced by the value of that argument. + * + * @param {object} j JSCodeShift object. + * @param {string} defaultValue Value to convert. + * @param {ASTObject[]} args Arguments given to the function. + * @param {string[]} paramNames Name of the expected parameters. + * @return {ASTObject} AST node representation of `defaultValue`. + */ +function defaultValueToASTNode(j, defaultValue, args, paramNames) { + // var endValue = replaceValueByArgValue(j, defaultValue, args, paramNames); + var splitDefaultValue = defaultValue.split('.'); + var indexOfReferencedParam = paramNames.indexOf(splitDefaultValue[0]); + if (indexOfReferencedParam !== -1) { + if (splitDefaultValue.length > 1) { + // defaultValue is probably of the type 'someArg.length' + // Other more complicated cases could be handled but none exist as of this writing. + return memberExpressiontoASTNode(j, args[indexOfReferencedParam], splitDefaultValue[1]); + } + return args[indexOfReferencedParam]; + } + return stringToASTNode(j, defaultValue); +} + +function mapRight(array, fn) { + var res = []; + var index = array.length; + while (index--) { + res = [fn(array[index], index)].concat(res); + } + return res; +} + +/** + * Return the list of arguments, augmented by the default value of the arguments that were ommitted. + * The augmentation only happens when the method call is made without some of the optional arguments, + * and when the arguments these optional arguments have become compulsory. + * For a `function fn(a, b, c=0, d=b.length) { ... }` with an arity of 4, + * when called with `args` [a, ['b']], returns [a, ['b'], 0, ['b'].length]. + * If possible, the value will be evaluated such that ̀`['b'].length` becomes `1`. + * + * @param {object} j JSCodeShift object. + * @param {object} mapping Mapping object that defines if and how the arguments will be reordered. + * @param {String} name Name of the function associated to the call/function definition. + * @param {ASTObject[]} args Arguments to concatenate. + * @param {string[][]} paramsDescription Description of the expected params. + * @return {ASTObject[]} Args along with missing arguments. + */ +function addMissingArguments(j, mapping, name, args, paramsDescription) { + var ary = getMethodAry(mapping, name); + + if (ary === undefined) { + console.log('WARNING: method `' + name + '` is not capped'); + } + + ary = ary || 1; + if (ary <= args.length) { + return args; + } + var paramNames = paramsDescription.map(paramName); + var tmpArgs = _.clone(args); + var newArgs = mapRight(_.take(paramsDescription, ary), function(paramDescription, index) { + if (index === tmpArgs.length - 1) { + return tmpArgs.pop(); + } + var defaultValue = getDefaultValue(paramDescription); + if (defaultValue !== null) { + return defaultValueToASTNode(j, defaultValue, args, paramNames); + } + return tmpArgs.pop(); + }); + return newArgs; +} + /** * Concatenate arguments into an array of arguments. * For a `function fn(a, b, ...args) { ... }` with an arity of 3, * when called with `args` [a, b, c, d, e, f], returns [a, b, [c, d, e, f]]. * * @param {object} j JSCodeShift object. - * @param {Object} mapping Mapping object that defines if and how the arguments will be reordered. + * @param {object} mapping Mapping object that defines if and how the arguments will be reordered. * @param {String} name Name of the function associated to the call/function definition. - * @param {ASTobjects[]} Arguments to concatenate. - * @return {ASTobjects[]} Concatenated arguments + * @param {ASTObject[]} args Arguments to concatenate. + * @return {ASTObject[]} Concatenated arguments */ function concatExtraArgs(j, mapping, name, args) { var ary = getMethodAry(mapping, name); - if (ary === args.length) { + if (args.length <= ary) { return args; } - return _.take(args, ary - 1).concat( - j.arrayExpression(_.takeRight(args, args.length - ary + 1)) - ); + + var concatenatedArgs = j.arrayExpression(_.takeRight(args, args.length - ary + 1)); + return _.take(args, ary - 1).concat(concatenatedArgs); } /** @@ -162,15 +402,16 @@ function concatExtraArgs(j, mapping, name, args) { * * @param {object} j JSCodeShift object. * @param {ASTObject} root AST representation of the example - * @param {Object} mapping Mapping object that defines if and how the arguments will be reordered. + * @param {object} mapping Mapping object that defines if and how the arguments will be reordered. * @return {ASTObject} AST object where the arguments are reordered/merged */ -function reorderMethodArgs(j, root, mapping) { +function reorderMethodArgs(j, root, mapping, paramsDescription) { root.find(j.CallExpression, { callee: { object: {name: '_' }}}) .replaceWith(function(callExpr, i) { var value = callExpr.value; var name = value.callee.property.name; - var args = concatExtraArgs(j, mapping, name, value.arguments); + var argsIncludingMissingOnes = addMissingArguments(j, mapping, name, value.arguments, paramsDescription) + var args = concatExtraArgs(j, mapping, name, argsIncludingMissingOnes); return j.callExpression( value.callee, reorderParams(mapping, name, args) @@ -190,21 +431,36 @@ function removeConsoleLogs(codeSample) { /** * Updates a code sample so that the arguments in the call are reordered according to `mapping`. * - * @param {Object} mapping Mapping object that defines if and how the arguments will be reordered. + * @param {object} mapping Mapping object that defines if and how the arguments will be reordered. * @param {string} codeSample Code sample to update. * @returns {string} Updated code sample. */ -function reorderParamsInExample(mapping, codeSample) { +function reorderParamsInExample(mapping, codeSample, paramsDescription) { var root = j(removeConsoleLogs(codeSample)); - reorderMethodArgs(j, root, mapping); + try { + reorderMethodArgs(j, root, mapping, paramsDescription); + } catch (error) { + console.error(codeSample); + console.error(error.stack); + process.exit(1); + } return root.toSource(); } +function getOriginalParams() { + var prev = this._params; + this._params = undefined; + baseGetParams.call(this); + var result = this._params; + this._params = prev; + return result; +} + /** * Returns a function that extracts the entry's `example` data, * where function call arguments are reordered according to `mapping`. * - * @param {Object} mapping Mapping object that defines if and how the `params` will be reordered. + * @param {object} mapping Mapping object that defines if and how the `params` will be reordered. * @returns {Function} Function that returns the entry's `example` data. */ function getReorderedExample(mapping) { @@ -213,7 +469,9 @@ function getReorderedExample(mapping) { if (!result) { return result; } - var resultReordered = reorderParamsInExample(mapping, result); + + var paramsDescription = getOriginalParams.call(this); + var resultReordered = reorderParamsInExample(mapping, result, paramsDescription); return '```' + this.lang + '\n' + resultReordered + '\n```'; }; } diff --git a/lib/doc/test.js b/lib/doc/test.js index 2525e1141..53c0c8ef5 100644 --- a/lib/doc/test.js +++ b/lib/doc/test.js @@ -5,11 +5,12 @@ var Entry = require('docdown/lib/entry'); var applyFPMapping = require('./apply-fp-mapping'); var mapping = require('../../fp/_mapping'); -function toExample(name, lines) { +function toSource(name, paramLines, exampleLines, attachedToPrototype) { var start = [ "/**", " * ", - " * @example" + " * Foo", + " * " ]; var end = [ " */", @@ -17,16 +18,23 @@ function toExample(name, lines) { "", "}" ]; - var example = lines.map(function(line) { + var staticLine = attachedToPrototype ? [] : [' * @static']; + var params = paramLines.map(function(line) { + return ' * @param ' + line; + }); + var example = (exampleLines || []).map(function(line) { return ' * ' + line; }); - return [].concat(start, example, end).join('\n'); + + return [].concat(start, staticLine, params, [' * @example'], example, end).join('\n'); } -function toParams(name, lines) { +function toParams(name, lines, wrapped) { var start = [ "/**", " * ", + " * Foo", + " * " ]; var end = [ " * @returns Foo bar", @@ -35,12 +43,32 @@ function toParams(name, lines) { "", "}" ]; - var example = lines.map(function(line) { + var staticLine = wrapped ? [] : [' * @static']; + var params = lines.map(function(line) { return ' * @param ' + line; }); - return [].concat(start, example, end).join('\n'); + return [].concat(start, staticLine, params, end).join('\n'); } +var differenceBySource = toSource('differenceBy', [ + '{Array} array The array to inspect.', + '{...Array} [values] The values to exclude.', + '{Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.' +], [ + "_.differenceBy([3.1, 2.2, 1.3], [4.4, 2.5], Math.floor);", + "// → [3.1, 1.3]", + "", + "// The `_.property` iteratee shorthand.", + "_.differenceBy([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], 'x');", + "// → [{ 'x': 2 }]" +]); + +var setParams = [ + '{Object} object The object to modify.', + '{Array|string} path The path of the property to set.', + '{*} value The value to set.' +]; + describe('Docs FP mapping', function() { var oldgetParams; var oldgetExample; @@ -48,6 +76,7 @@ describe('Docs FP mapping', function() { before(function() { oldgetParams = Entry.prototype.getParams; oldgetExample = Entry.prototype.getExample; + mapping.aryMethod[2].push('customFun'); applyFPMapping(mapping); }); @@ -58,15 +87,7 @@ describe('Docs FP mapping', function() { describe('getExample', function() { it('should reorder parameters', function() { - var example = toExample('differenceBy', [ - "_.differenceBy([3.1, 2.2, 1.3], [4.4, 2.5], Math.floor);", - "// → [3.1, 1.3]", - "", - "// The `_.property` iteratee shorthand.", - "_.differenceBy([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], 'x');", - "// → [{ 'x': 2 }]" - ]); - var entry = new Entry(example, example); + var entry = new Entry(differenceBySource, differenceBySource); var actual = entry.getExample(); @@ -83,7 +104,7 @@ describe('Docs FP mapping', function() { }); it('should reorder parameters that have a special order', function() { - var example = toExample('set', [ + var example = toSource('set', setParams, [ "var object = { 'a': [{ 'b': { 'c': 3 } }] };", "_.set(object, 'a[0].b.c', 4);", "_.set(object, 'x[0].y.z', 5);", @@ -102,7 +123,7 @@ describe('Docs FP mapping', function() { }); it('should preserve comments', function() { - var example = toExample('set', [ + var example = toSource('set', setParams, [ "var object = { 'a': [{ 'b': { 'c': 3 } }] };", "_.set(object, 'a[0].b.c', 4);", "// => 4", @@ -123,7 +144,7 @@ describe('Docs FP mapping', function() { }); it('should remove console.logs from example', function() { - var example = toExample('set', [ + var example = toSource('set', setParams, [ "var object = { 'a': [{ 'b': { 'c': 3 } }] };", "", "_.set(object, 'a[0].b.c', 4);", @@ -152,7 +173,11 @@ describe('Docs FP mapping', function() { }); it('should merge extra arguments into an array', function() { - var example = toExample('pullAt', [ + var example = toSource('pullAt', [ + '{Array} array The array to modify.', + '{...(number|number[])} [indexes] The indexes of elements to remove,\n' + + ' * specified individually or in arrays.' + ], [ "var array = [5, 10, 15, 20];", "var evens = _.pullAt(array, 1, 3);", "", @@ -177,10 +202,139 @@ describe('Docs FP mapping', function() { "```" ].join('\n')); }); + + it('should inject default values into optional arguments that became compulsory', function() { + var example = toSource('sampleSize', [ + '{Array|Object} collection The collection to sample.', + '{number} [n=0] The number of elements to sample.' + ], [ + "_.sampleSize([1, 2, 3]);", + "// => [3, 1]", + "", + "_.sampleSize([1, 2, 3], 4);", + "// => [2, 3, 1]" + ]); + var entry = new Entry(example, example); + + var actual = entry.getExample(); + + assert.equal(actual, [ + "```js", + "_.sampleSize(0, [1, 2, 3]);", + "// => [3, 1]", + "", + "_.sampleSize(4, [1, 2, 3]);", + "// => [2, 3, 1]", + "```" + ].join('\n')); + }); + + it('should inject referenced values into optional arguments that became compulsory, ' + + 'if a parameter\'s default value references parameter (direct reference)', + function() { + var example = toSource('customFun', [ + '{Array} array Array', + '{number} [foo=array] Foo' + ], [ + "_.customFun([1, 2, 3]);", + ]); + var entry = new Entry(example, example); + + var actual = entry.getExample(); + + assert.equal(actual, [ + "```js", + "_.customFun([1, 2, 3], [1, 2, 3]);", + "```" + ].join('\n')); + }); + + it('should inject referenced values into optional arguments that became compulsory, ' + + 'if a parameter\'s default value references parameter (member expression)', + function() { + var example = toSource('fill', [ + '{Array} array The array to fill.', + '{*} value The value to fill `array` with.', + '{number} [start=0] The start position.', + '{number} [end=array.length] The end position.' + ], [ + "var array = [1, 2, 3];", + "", + "_.fill(array, 'a');", + "console.log(array);", + "// => ['a', 'a', 'a']", + "", + "_.fill(Array(3), 2, 1);", + "// => [undefined, 2, 2]", + "", + "_.fill([4, 6, 8, 10], '*');", + "// => [*, '*', '*', *]" + ]); + var entry = new Entry(example, example); + + var actual = entry.getExample(); + + assert.equal(actual, [ + "```js", + "var array = [1, 2, 3];", + "", + "_.fill(0, array.length, 'a', array);", + "// => ['a', 'a', 'a']", + "", + "_.fill(1, 3, 2, Array(3));", + "// => [undefined, 2, 2]", + "", + "_.fill(0, 4, '*', [4, 6, 8, 10]);", + "// => [*, '*', '*', *]", + "```" + ].join('\n')); + }); + + it('should inject default values in the middle of the arguments', function() { + var example = toSource('inRange', [ + '{number} number The number to check.', + '{number} [start=0] The start of the range.', + '{number} end The end of the range.' + ], [ + "_.inRange(4, 8);", + "// => true" + ]); + var entry = new Entry(example, example); + + var actual = entry.getExample(); + + assert.equal(actual, [ + "```js", + "_.inRange(8, 0, 4);", + "// => true", + "```" + ].join('\n')); + }); + + it('should not use ignored params as default values', function() { + var example = toSource('drop', [ + '{Array} array The array to query.', + '{number} [n=1] The number of elements to drop.', + '{Object} [guard] Enables use as an iteratee for functions like `_.map`.' + ], [ + "_.drop([1, 2, 3]);", + "// => [2, 3]" + ]); + var entry = new Entry(example, example); + + var actual = entry.getExample(); + + assert.equal(actual, [ + "```js", + "_.drop(1, [1, 2, 3]);", + "// => [2, 3]", + "```" + ].join('\n')); + }); }); describe('getParams', function() { - it('should reorder arguments', function() { + it('should reorder arguments and remove default values', function() { var example = toParams('differenceBy', [ '{Array} array The array to inspect.', '{...Array} [values] The values to exclude.', @@ -191,8 +345,8 @@ describe('Docs FP mapping', function() { var actual = entry.getParams(); assert.deepEqual(actual, [ - ['Function|Object|string', '[iteratee=_.identity]', 'The iteratee invoked per element. '], - ['Array|Array[]', '[values]', 'The values to exclude. '], + ['Function|Object|string', 'iteratee', 'The iteratee invoked per element. '], + ['Array|Array[]', 'values', 'The values to exclude. '], ['Array', 'array', 'The array to inspect. '] ]); }); @@ -228,10 +382,57 @@ describe('Docs FP mapping', function() { // TODO Remove this line in favor of the commented one. // Is linked to a docdown (https://github.com/jdalton/docdown/pull/37) // that does not handle parens in the arguments well - ['((number|number)|((number|number)[]', '[indexes]', 'The indexes of elements to remove, specified individually or in arrays. '], + ['((number|number)|((number|number)[]', 'indexes', 'The indexes of elements to remove, specified individually or in arrays. '], // ['number|number[]', '[indexes]', 'The indexes of elements to remove, specified individually or in arrays. '], ['Array', 'array', 'The array to modify. '], ]); }); }); + + it('should duplicate and de-restify "rest" parameters if there are less parameters than cap', function() { + var example = toParams('intersectionWith', [ + '{...Array} [arrays] The arrays to inspect.', + '{Function} [comparator] The comparator invoked per element.' + ]); + var entry = new Entry(example, example); + + var actual = entry.getParams(); + + assert.deepEqual(actual, [ + ['Function', 'comparator', 'The comparator invoked per element. '], + ['Array', 'arrays', 'The arrays to inspect. '], + ['Array', 'arrays', 'The arrays to inspect. '] + ]); + }); + + it('should consider method to have an ary of `ary - 1` when capped and wrapped', function() { + var wrapped = true; + var example = toParams('flatMap', [ + '{Array} array The array to iterate over.', + '{Function|Object|string} [iteratee=_.identity] The function invoked per iteration.' + ], wrapped); + var entry = new Entry(example, example); + + var actual = entry.getParams(); + + assert.deepEqual(actual, [ + ['Function|Object|string', 'iteratee', 'The function invoked per iteration. '] + ]); + }); + + it('should remove arguments ignored because of capping', function() { + var example = toParams('includes', [ + '{Array|Object|string} collection The collection to search.', + '{*} value The value to search for.', + '{number} [fromIndex=0] The index to search from.' + ]); + var entry = new Entry(example, example); + + var actual = entry.getParams(); + + assert.deepEqual(actual, [ + ['*', 'value', 'The value to search for. '], + ['Array|Object|string', 'collection', 'The collection to search. '] + ]); + }); }); diff --git a/lodash.js b/lodash.js index 954f4404d..c824f8bbf 100644 --- a/lodash.js +++ b/lodash.js @@ -12364,7 +12364,7 @@ * @memberOf _ * @category String * @param {string} string The string to convert. - * @param {number} [radix] The radix to interpret `value` by. + * @param {number} [radix=10] The radix to interpret `value` by. * @param- {Object} [guard] Enables use as an iteratee for functions like `_.map`. * @returns {number} Returns the converted integer. * @example @@ -12910,7 +12910,7 @@ * @memberOf _ * @category String * @param {string} [string=''] The string to truncate. - * @param {Object} [options] The options object. + * @param {Object} [options=({})] The options object. * @param {number} [options.length=30] The maximum string length. * @param {string} [options.omission='...'] The string to indicate text is omitted. * @param {RegExp|string} [options.separator] The separator pattern to truncate to. @@ -13829,7 +13829,7 @@ * @static * @memberOf _ * @category Util - * @param {string} [prefix] The value to prefix the ID with. + * @param {string} [prefix=''] The value to prefix the ID with. * @returns {string} Returns the unique ID. * @example * diff --git a/package.json b/package.json index 40433ada8..6066accfc 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "platform": "^1.3.1", "qunit-extras": "^1.4.5", "qunitjs": "~1.21.0", + "recast": "^0.11.0", "request": "^2.69.0", "requirejs": "^2.1.22", "sauce-tunnel": "^2.4.0",