Как получить имена / значения параметров функции динамически? - PullRequest
275 голосов
/ 17 июня 2009

Есть ли способ получить имена параметров функции динамически?

Допустим, моя функция выглядит следующим образом:

function doSomething(param1, param2, .... paramN){
   // fill an array with the parameter name and value
   // some other code 
}

Теперь, как мне получить список имен параметров и их значений в массив из функции?

Ответы [ 31 ]

301 голосов
/ 29 марта 2012

Следующая функция вернет массив имен параметров любой переданной функции.

var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
var ARGUMENT_NAMES = /([^\s,]+)/g;
function getParamNames(func) {
  var fnStr = func.toString().replace(STRIP_COMMENTS, '');
  var result = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')')).match(ARGUMENT_NAMES);
  if(result === null)
     result = [];
  return result;
}

Пример использования:

getParamNames(getParamNames) // returns ['func']
getParamNames(function (a,b,c,d){}) // returns ['a','b','c','d']
getParamNames(function (a,/*b,c,*/d){}) // returns ['a','d']
getParamNames(function (){}) // returns []

Редактировать

С изобретением ES6 эту функцию можно отключить по умолчанию. Вот быстрый взлом, который должен работать в большинстве случаев:

var STRIP_COMMENTS = /(\/\/.*$)|(\/\*[\s\S]*?\*\/)|(\s*=[^,\)]*(('(?:\\'|[^'\r\n])*')|("(?:\\"|[^"\r\n])*"))|(\s*=[^,\)]*))/mg;

Я говорю, что большинство случаев, потому что есть некоторые вещи, которые запутают его

function (a=4*(5/3), b) {} // returns ['a']

Редактировать : Я также отмечаю, что vikasde также хочет значения параметров в массиве. Это уже предусмотрено в локальной переменной с именем arguments.

выдержка из https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments:

Объект arguments не является массивом. Он похож на массив, но не имеет никаких свойств массива, кроме длины. Например, у него нет метода pop. Однако его можно преобразовать в настоящий массив:

var args = Array.prototype.slice.call(arguments);

Если доступны универсальные массивы, можно использовать следующее:

var args = Array.slice(arguments);
118 голосов
/ 24 августа 2012

Ниже приведен код, взятый из AngularJS, который использует технику для механизма внедрения зависимостей.

А вот объяснение этого взято из http://docs.angularjs.org/tutorial/step_05

Инжектор зависимостей Angular предоставляет сервисы вашему контроллеру когда контроллер строится. Инжектор зависимости также заботится о создании любых переходных зависимостей, которые служба может есть (услуги часто зависят от других услуг).

Обратите внимание, что имена аргументов значимы, потому что инжектор использует их для поиска зависимостей.

/**
 * @ngdoc overview
 * @name AUTO
 * @description
 *
 * Implicit module which gets automatically added to each {@link AUTO.$injector $injector}.
 */

var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /^\s*(_?)(.+?)\1\s*$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
function annotate(fn) {
  var $inject,
      fnText,
      argDecl,
      last;

  if (typeof fn == 'function') {
    if (!($inject = fn.$inject)) {
      $inject = [];
      fnText = fn.toString().replace(STRIP_COMMENTS, '');
      argDecl = fnText.match(FN_ARGS);
      forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){
        arg.replace(FN_ARG, function(all, underscore, name){
          $inject.push(name);
        });
      });
      fn.$inject = $inject;
    }
  } else if (isArray(fn)) {
    last = fn.length - 1;
    assertArgFn(fn[last], 'fn')
    $inject = fn.slice(0, last);
  } else {
    assertArgFn(fn, 'fn', true);
  }
  return $inject;
}
34 голосов
/ 03 июля 2015

Вот обновленное решение, которое пытается компактно решить все крайние случаи, упомянутые выше:

function $args(func) {  
    return (func + '')
      .replace(/[/][/].*$/mg,'') // strip single-line comments
      .replace(/\s+/g, '') // strip white space
      .replace(/[/][*][^/*]*[*][/]/g, '') // strip multi-line comments  
      .split('){', 1)[0].replace(/^[^(]*[(]/, '') // extract the parameters  
      .replace(/=[^,]+/g, '') // strip any ES6 defaults  
      .split(',').filter(Boolean); // split & filter [""]
}  

Сокращенный результат теста (полные тестовые примеры прилагаются ниже):

'function (a,b,c)...' // returns ["a","b","c"]
'function ()...' // returns []
'function named(a, b, c) ...' // returns ["a","b","c"]
'function (a /* = 1 */, b /* = true */) ...' // returns ["a","b"]
'function fprintf(handle, fmt /*, ...*/) ...' // returns ["handle","fmt"]
'function( a, b = 1, c )...' // returns ["a","b","c"]
'function (a=4*(5/3), b) ...' // returns ["a","b"]
'function (a, // single-line comment xjunk) ...' // returns ["a","b"]
'function (a /* fooled you...' // returns ["a","b"]
'function (a /* function() yes */, \n /* no, */b)/* omg! */...' // returns ["a","b"]
'function ( A, b \n,c ,d \n ) \n ...' // returns ["A","b","c","d"]
'function (a,b)...' // returns ["a","b"]
'function $args(func) ...' // returns ["func"]
'null...' // returns ["null"]
'function Object() ...' // returns []

function $args(func) {  
    return (func + '')
      .replace(/[/][/].*$/mg,'') // strip single-line comments
      .replace(/\s+/g, '') // strip white space
      .replace(/[/][*][^/*]*[*][/]/g, '') // strip multi-line comments  
      .split('){', 1)[0].replace(/^[^(]*[(]/, '') // extract the parameters  
      .replace(/=[^,]+/g, '') // strip any ES6 defaults  
      .split(',').filter(Boolean); // split & filter [""]
}  

// test cases  
document.getElementById('console_info').innerHTML = (
[  
  // formatting -- typical  
  function(a,b,c){},  
  function(){},  
  function named(a, b,  c) {  
/* multiline body */  
  },  
    
  // default values -- conventional  
  function(a /* = 1 */, b /* = true */) { a = a||1; b=b||true; },  
  function fprintf(handle, fmt /*, ...*/) { },  
  
  // default values -- ES6  
  "function( a, b = 1, c ){}",  
  "function (a=4*(5/3), b) {}",  
  
  // embedded comments -- sardonic  
  function(a, // single-line comment xjunk) {}
    b //,c,d
  ) // single-line comment
  {},  
  function(a /* fooled you{*/,b){},  
  function /* are you kidding me? (){} */(a /* function() yes */,  
   /* no, */b)/* omg! */{/*}}*/},  
  
  // formatting -- sardonic  
  function  (  A,  b  
,c  ,d  
  )  
  {  
  },  
  
  // by reference  
  this.jQuery || function (a,b){return new e.fn.init(a,b,h)},
  $args,  
  
  // inadvertent non-function values  
  null,  
  Object  
].map(function(f) {
    var abbr = (f + '').replace(/\n/g, '\\n').replace(/\s+|[{]+$/g, ' ').split("{", 1)[0] + "...";
    return "    '" + abbr + "' // returns " + JSON.stringify($args(f));
  }).join("\n") + "\n"); // output for copy and paste as a markdown snippet
21 голосов
/ 02 февраля 2013

Решение, которое менее подвержено ошибкам с пробелами и комментариями, будет:

var fn = function(/* whoa) */ hi, you){};

fn.toString()
  .replace(/((\/\/.*$)|(\/\*[\s\S]*?\*\/)|(\s))/mg,'')
  .match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1]
  .split(/,/)

["hi", "you"]
16 голосов
/ 07 января 2017

Многие ответы здесь используют регулярные выражения, это хорошо, но он не слишком хорошо обрабатывает новые дополнения к языку (например, функции стрелок и классы). Также следует отметить, что если вы используете какую-либо из этих функций в минимизированном коде, она пойдет ? Он будет использовать любое минимизированное имя. Angular обходит это, позволяя вам передавать упорядоченный массив строк, который соответствует порядку аргументов при регистрации их в контейнере DI. Итак, с решением:

var esprima = require('esprima');
var _ = require('lodash');

const parseFunctionArguments = (func) => {
    // allows us to access properties that may or may not exist without throwing 
    // TypeError: Cannot set property 'x' of undefined
    const maybe = (x) => (x || {});

    // handle conversion to string and then to JSON AST
    const functionAsString = func.toString();
    const tree = esprima.parse(functionAsString);
    console.log(JSON.stringify(tree, null, 4))
    // We need to figure out where the main params are. Stupid arrow functions ?
    const isArrowExpression = (maybe(_.first(tree.body)).type == 'ExpressionStatement');
    const params = isArrowExpression ? maybe(maybe(_.first(tree.body)).expression).params 
                                     : maybe(_.first(tree.body)).params;

    // extract out the param names from the JSON AST
    return _.map(params, 'name');
};

Это обрабатывает исходную проблему разбора и еще несколько типов функций (например, функции стрелок). Вот идея того, что он может и не может обрабатывать как есть:

// I usually use mocha as the test runner and chai as the assertion library
describe('Extracts argument names from function signature. ?', () => {
    const test = (func) => {
        const expectation = ['it', 'parses', 'me'];
        const result = parseFunctionArguments(toBeParsed);
        result.should.equal(expectation);
    } 

    it('Parses a function declaration.', () => {
        function toBeParsed(it, parses, me){};
        test(toBeParsed);
    });

    it('Parses a functional expression.', () => {
        const toBeParsed = function(it, parses, me){};
        test(toBeParsed);
    });

    it('Parses an arrow function', () => {
        const toBeParsed = (it, parses, me) => {};
        test(toBeParsed);
    });

    // ================= cases not currently handled ========================

    // It blows up on this type of messing. TBH if you do this it deserves to 
    // fail ? On a tech note the params are pulled down in the function similar 
    // to how destructuring is handled by the ast.
    it('Parses complex default params', () => {
        function toBeParsed(it=4*(5/3), parses, me) {}
        test(toBeParsed);
    });

    // This passes back ['_ref'] as the params of the function. The _ref is a 
    // pointer to an VariableDeclarator where the ✨? happens.
    it('Parses object destructuring param definitions.' () => {
        function toBeParsed ({it, parses, me}){}
        test(toBeParsed);
    });

    it('Parses object destructuring param definitions.' () => {
        function toBeParsed ([it, parses, me]){}
        test(toBeParsed);
    });

    // Classes while similar from an end result point of view to function
    // declarations are handled completely differently in the JS AST. 
    it('Parses a class constructor when passed through', () => {
        class ToBeParsed {
            constructor(it, parses, me) {}
        }
        test(ToBeParsed);
    });
});

В зависимости от того, что вы хотите использовать для прокси-серверов ES6 и деструктуризация, может быть вашим лучшим выбором. Например, если вы хотите использовать его для внедрения зависимостей (используя имена параметров), вы можете сделать это следующим образом:

class GuiceJs {
    constructor() {
        this.modules = {}
    }
    resolve(name) {
        return this.getInjector()(this.modules[name]);
    }
    addModule(name, module) {
        this.modules[name] = module;
    }
    getInjector() {
        var container = this;

        return (klass) => {
            console.log(klass);
            var paramParser = new Proxy({}, {
                // The `get` handler is invoked whenever a get-call for
                // `injector.*` is made. We make a call to an external service
                // to actually hand back in the configured service. The proxy
                // allows us to bypass parsing the function params using
                // taditional regex or even the newer parser.
                get: (target, name) => container.resolve(name),

                // You shouldn't be able to set values on the injector.
                set: (target, name, value) => {
                    throw new Error(`Don't try to set ${name}! ?`);
                }
            })
            return new klass(paramParser);
        }
    }
}

Это не самый продвинутый распознаватель, но он дает представление о том, как вы можете использовать прокси для его обработки, если вы хотите использовать анализатор args для простого DI. Однако в этом подходе есть одна небольшая оговорка. Нам нужно использовать деструктурирующие назначения вместо обычных параметров. Когда мы передаем прокси инжектора, деструктурирование аналогично вызову геттера объекта.

class App {
   constructor({tweeter, timeline}) {
        this.tweeter = tweeter;
        this.timeline = timeline;
    }
}

class HttpClient {}

class TwitterApi {
    constructor({client}) {
        this.client = client;
    }
}

class Timeline {
    constructor({api}) {
        this.api = api;
    }
}

class Tweeter {
    constructor({api}) {
        this.api = api;
    }
}

// Ok so now for the business end of the injector!
const di = new GuiceJs();

di.addModule('client', HttpClient);
di.addModule('api', TwitterApi);
di.addModule('tweeter', Tweeter);
di.addModule('timeline', Timeline);
di.addModule('app', App);

var app = di.resolve('app');
console.log(JSON.stringify(app, null, 4));

Это выводит следующее:

{
    "tweeter": {
        "api": {
            "client": {}
        }
    },
    "timeline": {
        "api": {
            "client": {}
        }
    }
}

Это связано все приложение. Лучше всего то, что приложение легко тестировать (вы можете просто создать экземпляр каждого класса и передать в mocks / stubs / etc). Также, если вам нужно поменять местами реализации, вы можете сделать это из одного места. Все это возможно благодаря объектам JS Proxy.

Примечание: для этого нужно проделать большую работу, прежде чем он будет готов к использованию, но он дает представление о том, как он будет выглядеть.

Уже немного поздно в ответе, но это может помочь другим, кто думает о том же. ?

12 голосов
/ 20 августа 2015

Я знаю, что это старый вопрос, но новички копировали его так, как будто это хорошая практика в любом коде.В большинстве случаев необходимость разбора строкового представления функции для использования имен ее параметров просто скрывает недостаток в логике кода.

Параметры функции фактически сохраняются в объекте, похожем на массив и называемом arguments, где первый аргумент arguments[0], второй arguments[1] и т. Д.Запись имен параметров в скобках можно рассматривать как сокращенный синтаксис.Это:

function doSomething(foo, bar) {
    console.log("does something");
}

... - это то же самое, что и

function doSomething() {
    var foo = arguments[0];
    var bar = arguments[1];

    console.log("does something");
}

Сами переменные хранятся в области действия функции, а не в качестве свойств объекта.Невозможно извлечь имя параметра через код, поскольку это просто символ, представляющий переменную на человеческом языке.

Я всегда рассматривал строковое представление функции как инструмент для целей отладки, особенно из-заэтот arguments массивоподобный объект.Вы не обязаны давать имена аргументам в первую очередь.Если вы попытаетесь выполнить разбор строковой функции, она на самом деле не скажет вам о дополнительных безымянных параметрах, которые она может принять.

Вот еще более частая и распространенная ситуация.Если функция имеет более 3 или 4 аргументов, может быть логично передать ей объект, с которым проще работать.

function saySomething(obj) {
  if(obj.message) console.log((obj.sender || "Anon") + ": " + obj.message);
}

saySomething({sender: "user123", message: "Hello world"});

В этом случае сама функция сможет читатьчерез объект, который он получает, ищет его свойства и получает их имена и значения, но попытка разобрать строковое представление функции даст только «obj» для параметров, что совершенно бесполезно.

8 голосов
/ 29 марта 2018

Поскольку JavaScript является языком сценариев, я считаю, что его самоанализ должен поддерживать получение имен параметров функций. Использование этой функциональности является нарушением первых принципов, поэтому я решил изучить эту проблему подробнее.

Это привело меня к этому вопросу , но без встроенных решений. Что привело меня к этому ответу , который объясняет, что arguments устарела только вне функции, поэтому мы больше не можем использовать myFunction.arguments или получаем:

TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them

Время засучить рукава и приступить к работе:

⭐ Для извлечения параметров функции требуется синтаксический анализатор, поскольку сложные выражения, такие как 4*(5/3), могут использоваться в качестве значений по умолчанию. Так что ответ Гаафара или ответ Джеймса Дрю пока являются лучшими подходами.

Я попробовал парсеры babylon и esprima , но, к сожалению, они не могут анализировать автономные анонимные функции, как указано в ответе Матеуша Чаритонюка . Я нашел другой обходной путь, заключив код в круглые скобки, чтобы не менять логику:

const ast = parser.parse("(\n" + func.toString() + "\n)")

Новые строки предотвращают проблемы с // (однострочные комментарии).

⭐ Если синтаксический анализатор недоступен, следующий лучший вариант - использовать проверенную технику, например регулярные выражения инжектора зависимостей Angular.js. Я объединил функциональную версию ответа Ламбдера с ответом humbletim и добавил необязательный логический ARROW для контроля, разрешены ли функции жирной стрелки ES6 регулярными выражениями.

<Ч />

Вот два решения, которые я собрал. Обратите внимание, что в них нет логики для определения правильности синтаксиса функции, они только извлекают аргументы. Обычно это нормально, так как мы обычно передаем проанализированные функции в getArguments(), поэтому их синтаксис уже действителен.

Я постараюсь курировать эти решения как можно лучше, но без усилий со стороны сопровождающих JavaScript это останется открытой проблемой.

Версия Node.js (не запускается, пока StackOverflow не поддерживает Node.js):

const parserName = 'babylon';
// const parserName = 'esprima';
const parser = require(parserName);

function getArguments(func) {
    const maybe = function (x) {
        return x || {}; // optionals support
    }

    try {
        const ast = parser.parse("(\n" + func.toString() + "\n)");
        const program = parserName == 'babylon' ? ast.program : ast;

        return program
            .body[0]
            .expression
            .params
            .map(function(node) {
                return node.name || maybe(node.left).name || '...' + maybe(node.argument).name;
            });
    } catch (e) {
        return []; // could also return null
    }
};

////////// TESTS //////////

function logArgs(func) {
	let object = {};

	object[func] = getArguments(func);

	console.log(object);
// 	console.log(/*JSON.stringify(*/getArguments(func)/*)*/);
}

console.log('');
console.log('////////// MISC //////////');

logArgs((a, b) => {});
logArgs((a, b = 1) => {});
logArgs((a, b, ...args) => {});
logArgs(function(a, b, ...args) {});
logArgs(function(a, b = 1, c = 4 * (5 / 3), d = 2) {});
logArgs(async function(a, b, ...args) {});
logArgs(function async(a, b, ...args) {});

console.log('');
console.log('////////// FUNCTIONS //////////');

logArgs(function(a, b, c) {});
logArgs(function() {});
logArgs(function named(a, b, c) {});
logArgs(function(a /* = 1 */, b /* = true */) {});
logArgs(function fprintf(handle, fmt /*, ...*/) {});
logArgs(function(a, b = 1, c) {});
logArgs(function(a = 4 * (5 / 3), b) {});
// logArgs(function (a, // single-line comment xjunk) {});
// logArgs(function (a /* fooled you {});
// logArgs(function (a /* function() yes */, \n /* no, */b)/* omg! */ {});
// logArgs(function ( A, b \n,c ,d \n ) \n {});
logArgs(function(a, b) {});
logArgs(function $args(func) {});
logArgs(null);
logArgs(function Object() {});

console.log('');
console.log('////////// STRINGS //////////');

logArgs('function (a,b,c) {}');
logArgs('function () {}');
logArgs('function named(a, b, c) {}');
logArgs('function (a /* = 1 */, b /* = true */) {}');
logArgs('function fprintf(handle, fmt /*, ...*/) {}');
logArgs('function( a, b = 1, c ) {}');
logArgs('function (a=4*(5/3), b) {}');
logArgs('function (a, // single-line comment xjunk) {}');
logArgs('function (a /* fooled you {}');
logArgs('function (a /* function() yes */, \n /* no, */b)/* omg! */ {}');
logArgs('function ( A, b \n,c ,d \n ) \n {}');
logArgs('function (a,b) {}');
logArgs('function $args(func) {}');
logArgs('null');
logArgs('function Object() {}');

Полный рабочий пример:

https://repl.it/repls/SandybrownPhonyAngles

Версия браузера (обратите внимание, что он останавливается на первом комплексном значении по умолчанию):

function getArguments(func) {
    const ARROW = true;
    const FUNC_ARGS = ARROW ? /^(function)?\s*[^\(]*\(\s*([^\)]*)\)/m : /^(function)\s*[^\(]*\(\s*([^\)]*)\)/m;
    const FUNC_ARG_SPLIT = /,/;
    const FUNC_ARG = /^\s*(_?)(.+?)\1\s*$/;
    const STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;

    return ((func || '').toString().replace(STRIP_COMMENTS, '').match(FUNC_ARGS) || ['', '', ''])[2]
        .split(FUNC_ARG_SPLIT)
        .map(function(arg) {
            return arg.replace(FUNC_ARG, function(all, underscore, name) {
                return name.split('=')[0].trim();
            });
        })
        .filter(String);
}

////////// TESTS //////////

function logArgs(func) {
	let object = {};

	object[func] = getArguments(func);

	console.log(object);
// 	console.log(/*JSON.stringify(*/getArguments(func)/*)*/);
}

console.log('');
console.log('////////// MISC //////////');

logArgs((a, b) => {});
logArgs((a, b = 1) => {});
logArgs((a, b, ...args) => {});
logArgs(function(a, b, ...args) {});
logArgs(function(a, b = 1, c = 4 * (5 / 3), d = 2) {});
logArgs(async function(a, b, ...args) {});
logArgs(function async(a, b, ...args) {});

console.log('');
console.log('////////// FUNCTIONS //////////');

logArgs(function(a, b, c) {});
logArgs(function() {});
logArgs(function named(a, b, c) {});
logArgs(function(a /* = 1 */, b /* = true */) {});
logArgs(function fprintf(handle, fmt /*, ...*/) {});
logArgs(function(a, b = 1, c) {});
logArgs(function(a = 4 * (5 / 3), b) {});
// logArgs(function (a, // single-line comment xjunk) {});
// logArgs(function (a /* fooled you {});
// logArgs(function (a /* function() yes */, \n /* no, */b)/* omg! */ {});
// logArgs(function ( A, b \n,c ,d \n ) \n {});
logArgs(function(a, b) {});
logArgs(function $args(func) {});
logArgs(null);
logArgs(function Object() {});

console.log('');
console.log('////////// STRINGS //////////');

logArgs('function (a,b,c) {}');
logArgs('function () {}');
logArgs('function named(a, b, c) {}');
logArgs('function (a /* = 1 */, b /* = true */) {}');
logArgs('function fprintf(handle, fmt /*, ...*/) {}');
logArgs('function( a, b = 1, c ) {}');
logArgs('function (a=4*(5/3), b) {}');
logArgs('function (a, // single-line comment xjunk) {}');
logArgs('function (a /* fooled you {}');
logArgs('function (a /* function() yes */, \n /* no, */b)/* omg! */ {}');
logArgs('function ( A, b \n,c ,d \n ) \n {}');
logArgs('function (a,b) {}');
logArgs('function $args(func) {}');
logArgs('null');
logArgs('function Object() {}');

Полный рабочий пример:

https://repl.it/repls/StupendousShowyOffices

7 голосов
/ 02 августа 2013
(function(a,b,c){}).toString().replace(/.*\(|\).*/ig,"").split(',')

=> ["a", "b", "c"]

6 голосов
/ 17 июня 2009

Я пытался сделать это раньше, но так и не нашел практического способа сделать это. В итоге я вместо этого передал объект, а затем прошел через него.

//define like
function test(args) {
    for(var item in args) {
        alert(item);
        alert(args[item]);
    }
}

//then used like
test({
    name:"Joe",
    age:40,
    admin:bool
});
6 голосов
/ 26 февраля 2014

Вы также можете использовать парсер esprima, чтобы избежать многих проблем с комментариями, пробелами и другими вещами в списке параметров.

function getParameters(yourFunction) {
    var i,
        // safetyValve is necessary, because sole "function () {...}"
        // is not a valid syntax
        parsed = esprima.parse("safetyValve = " + yourFunction.toString()),
        params = parsed.body[0].expression.right.params,
        ret = [];

    for (i = 0; i < params.length; i += 1) {
        // Handle default params. Exe: function defaults(a = 0,b = 2,c = 3){}
        if (params[i].type == 'AssignmentPattern') {
            ret.push(params[i].left.name)
        } else {
            ret.push(params[i].name);
        }
    }

    return ret;
}

Работает даже с таким кодом:

getParameters(function (hello /*, foo ),* /bar* { */,world) {}); // ["hello", "world"]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...