Muchas de las respuestas aquí usan expresiones regulares, esto está bien, pero no maneja las nuevas adiciones al lenguaje demasiado bien (como las funciones de flecha y las clases). También es de destacar que si usa alguna de estas funciones en el código minimizado, irá 🔥. Utilizará cualquier nombre minificado. Angular evita esto al permitirle pasar una serie ordenada de cadenas que coincida con el orden de los argumentos al registrarlos en el contenedor DI. Entonces con la solución:
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');
};
Esto maneja el problema de análisis original y algunos tipos de funciones más (por ejemplo, funciones de flecha). Aquí hay una idea de lo que puede y no puede manejar como es:
// 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);
});
});
Dependiendo de lo que quieras usar para ES6 Proxies y la desestructuración puede ser tu mejor opción. Por ejemplo, si desea utilizarlo para la inyección de dependencia (utilizando los nombres de los parámetros), puede hacerlo de la siguiente manera:
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);
}
}
}
No es el solucionador más avanzado que existe, pero da una idea de cómo puede usar un Proxy para manejarlo si desea usar el analizador args para una DI simple. Sin embargo, hay una leve advertencia en este enfoque. Necesitamos usar tareas de desestructuración en lugar de parámetros normales. Cuando pasamos el proxy del inyector, la desestructuración es lo mismo que llamar al captador del objeto.
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));
Esto genera lo siguiente:
{
"tweeter": {
"api": {
"client": {}
}
},
"timeline": {
"api": {
"client": {}
}
}
}
Está conectado toda la aplicación. Lo mejor es que la aplicación es fácil de probar (puede crear una instancia de cada clase y aprobar simulacros / trozos / etc.). Además, si necesita intercambiar implementaciones, puede hacerlo desde un solo lugar. Todo esto es posible gracias a los objetos JS Proxy.
Nota: Hay mucho trabajo por hacer antes de que esté listo para su uso en producción, pero da una idea de cómo se vería.
Es un poco tarde en la respuesta, pero puede ayudar a otros que están pensando en lo mismo. 👍