diff --git a/README.md b/README.md index f3a2c37..7966dbb 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,9 @@ Copyright [Damien Simonin Feugas][feugy] and other contributors, licensed under ## Changelog +### 1.2.1 +- fix issue related to parameter name extraction when using arrow functions + ### 1.2.0 - use proxy to delay remotely exposed Apis retrieval to the first effective usage - activate Travis CI and coveralls reports @@ -238,7 +241,7 @@ Copyright [Damien Simonin Feugas][feugy] and other contributors, licensed under [david-url]: https://david-dm.org/feugy/mini-service [npm-image]: https://img.shields.io/npm/v/mini-service.svg [npm-url]: https://npmjs.org/package/mini-service -[travis-image]: https://img.shields.io/travis/expressjs/express/master.svg?label=linux -[travis-url]: https://travis-ci.org/expressjs/express +[travis-image]: https://travis-ci.org/feugy/mini-service/master.svg +[travis-url]: https://travis-ci.org/feugy/mini-service [coveralls-image]: https://img.shields.io/coveralls/feugy/mini-service/master.svg [coveralls-url]: https://coveralls.io/r/feugy/mini-service?branch=master \ No newline at end of file diff --git a/lib/utils/index.js b/lib/utils/index.js index 10f9c41..2a9cf40 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -23,10 +23,35 @@ exports.getLogger = () => { /** * Extract declared parameter names from funciton signature + * Rest parameters are not supported + * * @param {Function} fn - analyzed function * @returns {Array} names - array of declared parameters (might be empty) + * @throws {Error} if the passed argument isn't a function, or is unsupported */ -exports.getParamNames = fn => fn.toString().match(/^[^(]*\(((?:\S+, )*\S*)\)/)[1].split(', ').filter(p => p) +exports.getParamNames = fn => { + const declaration = (fn || '').toString() + if (/^[^(]*\(/.test(declaration)) { + // covers the following cases: "function ()", "() =>", "name () {", "function name () {" {}, + const params = declaration.match(/^[^(]*\(((?:[^,]+, )*[^\)]*)\)/)[1].split(', ') + // remove empty false-positives + .filter(p => p) + // remove default values + .map(p => p.replace(/\s*=.+/, '')) + // guard against rest parameters + for (const p of params) { + if (p.startsWith('...')) { + throw new Error(`unsupported function ${fn}: rest parameter ${p}`) + } + } + return params + } + if (/^\s*\S+\s*=>/.test(declaration)) { + // covers the following cases: "name =>", + return [declaration.match(/^\s*(\S+)\s*=>/)[1]] + } + throw new Error(`unsupported function ${fn}`) +} /** * Transform an array to an object, assigning each array item to a given property. diff --git a/package.json b/package.json index 03ca4f1..2e294b1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mini-service", - "version": "1.2.0", + "version": "1.2.1", "description": "Micro services done simply. Choose to run them locally or remotely", "repository": { "type": "git", diff --git a/test/utils/common.js b/test/utils/common.js new file mode 100644 index 0000000..72efc28 --- /dev/null +++ b/test/utils/common.js @@ -0,0 +1,142 @@ +const Lab = require('lab') +const assert = require('power-assert') +const utils = require('../../lib/utils') + +const lab = exports.lab = Lab.script() +const {describe, it} = lab + +describe('Utilities', () => { + + describe('getParamNames', () => { + + it('should fails on null', done => { + assert.throws(() => utils.getParamNames(null), /unsupported function null/) + done() + }) + + it('should fails on undefined', done => { + assert.throws(() => utils.getParamNames(), /unsupported function undefined/) + done() + }) + + it('should fails on object', done => { + assert.throws(() => utils.getParamNames({}), /unsupported function \[object Object\]/) + done() + }) + + it('should fails on rest parameter', done => { + assert.throws(() => utils.getParamNames((...args) => {}), /unsupported function \(\.\.\.args\)/) + done() + }) + + it('should handle typical function', done => { + const obj = { + ping() { + return Promise.resolve({time: new Date()}) + } + } + assert.deepEqual(utils.getParamNames(obj.ping), []) + done() + }) + + it('should handle empty function declaration', done => { + /* eslint prefer-arrow-callback: 0, no-empty-function: 0 */ + function declared() {} + assert.deepEqual(utils.getParamNames(declared), []) + done() + }) + + it('should handle empty anonymous function', done => { + /* eslint prefer-arrow-callback: 0, no-empty-function: 0 */ + assert.deepEqual(utils.getParamNames(function() {}), []) + done() + }) + + it('should handle named function', done => { + /* eslint prefer-arrow-callback: 0, no-empty-function: 0 */ + assert.deepEqual(utils.getParamNames(function named() {}), []) + done() + }) + + it('should handle empty arrow function', done => { + /* eslint no-empty-function: 0 */ + assert.deepEqual(utils.getParamNames(() => {}), []) + done() + }) + + it('should handle function declaration with single parameter', done => { + /* eslint prefer-arrow-callback: 0, no-empty-function: 0 */ + function declared(a) {} + assert.deepEqual(utils.getParamNames(declared), ['a']) + done() + }) + + it('should handle anonymous function with single parameter', done => { + /* eslint prefer-arrow-callback: 0, no-empty-function: 0, no-unused-vars:0 */ + assert.deepEqual(utils.getParamNames(function(a) {}), ['a']) + done() + }) + + it('should handle named function with single parameter', done => { + /* eslint prefer-arrow-callback: 0, no-empty-function: 0, no-unused-vars:0 */ + assert.deepEqual(utils.getParamNames(function named(a) {}), ['a']) + done() + }) + + it('should handle empty arrow function with single parameter', done => { + /* eslint no-empty-function: 0, no-unused-vars:0 */ + assert.deepEqual(utils.getParamNames(a => {}), ['a']) + done() + }) + + it('should handle function declaration with multiple parameter', done => { + /* eslint prefer-arrow-callback: 0, no-empty-function: 0 */ + function declared(a, b, c) {} + assert.deepEqual(utils.getParamNames(declared), ['a', 'b', 'c']) + done() + }) + + it('should handle anonymous function with multiple parameters', done => { + /* eslint prefer-arrow-callback: 0, no-empty-function: 0, no-unused-vars:0 */ + assert.deepEqual(utils.getParamNames(function(a, b, c) {}), ['a', 'b', 'c']) + done() + }) + + it('should handle named function with multiple parameters', done => { + /* eslint prefer-arrow-callback: 0, no-empty-function: 0, no-unused-vars:0 */ + assert.deepEqual(utils.getParamNames(function named(a, b, c) {}), ['a', 'b', 'c']) + done() + }) + + it('should handle empty arrow function with multiple parameters', done => { + /* eslint no-empty-function: 0, no-unused-vars:0 */ + assert.deepEqual(utils.getParamNames((a, b, c) => {}), ['a', 'b', 'c']) + done() + }) + + it('should handle function declaration with default values', done => { + /* eslint prefer-arrow-callback: 0, no-empty-function: 0 */ + function declared(a, b, c = false) {} + assert.deepEqual(utils.getParamNames(declared), ['a', 'b', 'c']) + done() + }) + + it('should handle anonymous function with default values', done => { + /* eslint prefer-arrow-callback: 0, no-empty-function: 0, no-unused-vars:0 */ + assert.deepEqual(utils.getParamNames(function(a, b, c = 10) {}), ['a', 'b', 'c']) + done() + }) + + it('should handle named function with default values', done => { + /* eslint prefer-arrow-callback: 0, no-empty-function: 0, no-unused-vars:0 */ + assert.deepEqual(utils.getParamNames(function named(a, b, c = null) {}), ['a', 'b', 'c']) + done() + }) + + it('should handle empty arrow function with default values', done => { + /* eslint no-empty-function: 0, no-unused-vars:0 */ + assert.deepEqual(utils.getParamNames((a, b, c = []) => {}), ['a', 'b', 'c']) + done() + }) + }) +})