-1

Very similar to this Python question but for JavaScript.

I'm using a library that relies on nodejs-depd which uses new Function() to dynamically wrap functions in deprecation messages:

function wrapfunction(fn, message) {
  if (typeof fn !== "function") {
    throw new TypeError("argument fn must be a function");
  }

  var args = createArgumentsString(fn.length);
  var stack = getStack();
  var site = callSiteLocation(stack[1]);

  site.name = fn.name;

  var deprecatedFn = new Function(
    "fn",
    "log",
    "deprecate",
    "message",
    "site",
    '"use strict"\n' +
      "return function (" +
      args +
      ") {" +
      "log.call(deprecate, message, site)\n" +
      "return fn.apply(this, arguments)\n" +
      "}"
  )(fn, log, this, message, site);

  return deprecatedFn;
}

This produces various legitimate concerns about security. But it also produces functions that match the original - if args are arg0, arg1, the new function will be

function (arg0, arg1) {
  log.call(deprecate, message, site)
  return fn.apply(this, arguments)
}

BTW, 'no, this isn't possible' is a fine answer. I just want to find out if this is possible.

mikemaccana
  • 110,530
  • 99
  • 389
  • 494
  • 2
    Other than the methods you specified, I don't think there is a way to create a function of specific arity. You can, however, define a function use the known array-like `arguments` object (if it's not part of a [ES6] _module_ or is in a [strict mode](http://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode)), or have it accept _rest_ parameters, e.g. `function f(...args) { ... }`. Both are a bit different than having a declaration with a particular number of arguments, but applicability of chosen method would depend on why you're asking. – Armen Michaeli Mar 21 '23 at 11:59
  • 2
    Curious -- why do you want to do this? Are you concerned about overloading of functions? I ask because there's nothing in Javascript stopping you from calling a function with a different number or type of arguments than those from which it's defined. Unless you're using a static type checker like TS or Flow. – Matt Morgan Mar 21 '23 at 11:59
  • @MattMorgan I mentioned nodejs-depd in the question, you can read the docs at https://github.com/dougwilson/nodejs-depd. nodejs-depd produces wrapped functions that are very close to the original, including the named arguments, with additional logging and deprecation warnings. – mikemaccana Mar 21 '23 at 13:43
  • Thanks @ArmenMichaeli you're welcome to add that as an answer. – mikemaccana Mar 21 '23 at 13:44

1 Answers1

2

Dynamically create a function with a specific parameter declaration

It is not possible to dynamically create a function with a specific parameter declaration without eval/new Function, but it also not advisable to depend on that - the code (fn.toString()) is considered an implementation detail.

Dynamically create a function with a specific arity

Yes, it is possible to dynamically create a function with a specific arity, if all you care about in the "signature" is the arity of the function.

The arity (.length) of a function is a non-writable property, but it's still configurable:

function wrapfunction(fn, message) {
  if (typeof fn !== "function") {
    throw new TypeError("argument fn must be a function");
  }

  var stack = getStack();
  var site = callSiteLocation(stack[1]);

  site.name = fn.name;
  var deprecate = this;

  var deprecatedFnOld = function() {
     log.call(deprecate, message, site);
     return fn.apply(this, arguments);
  };
  Object.defineProperty(deprecatedFnOld, 'length', {value: fn.length});
  Object.defineProperty(deprecatedFnOld, 'name', {value: 'deprecated '+fn.name});

  return deprecatedFnOld;
}
mikemaccana
  • 110,530
  • 99
  • 389
  • 494
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • @mikemaccana The arity is determined by the `.length` (or by the documentation). The code of the function is insubstantial, an implementation detail - this holds for the parameter list just as it does for the body. Notice that this is also what `.bind()` does. – Bergi Mar 21 '23 at 14:23
  • Well it does. "*If all you care about is the `.length` of the function, and not the code that `.toString()` would display*", as I've written in my answer. If you care about something else, please spell it out explicitly, best with the code that depends on this "function signature". – Bergi Mar 21 '23 at 14:41
  • The code of the function (i.e. `function (arg0, arg1) {log.call(deprecate, message, site) return fn.apply(this, arguments) }`) not considered to be the function signature. I argue, and most people would agree, that also the parameter names (i.e. the `arg0, arg1` bit) are not part of the signature. JavaScript is not a statically typed language, so there is no definition of what a "signature" means precisely, but usually - and also in TypeScript - it contains the types, number and order (but not names) of positional parameters. The closest you get to that at runtime is the arity (ie. `.length`). – Bergi Mar 21 '23 at 15:41
  • 1
    And that's also what the question is asking: "*specific amount of arguments*", which I equate to the `.length` of the function. So I'd refrain from answering "no" since I think it is possible. – Bergi Mar 21 '23 at 15:44