Over at https://github.com/Swatinem/js-source-scopes I have been experimenting with extracting scopes, and their names from minified source; and re-mapping the individual name components using a sourcemaps names
, with surprisingly great success.
We should make up our minds how we expect this inference to work.
This inference works differently in the rendered stack traces of various browsers, which is the primary use-case we are after.
Discussion
Function instance name
The Function instance name
is defined like this:
The value of the "name" property is a String that is descriptive of the function. The name has no semantic significance but is typically a variable or property name that is used to refer to the function at its point of definition in ECMAScript code.
For named functions, this is trivial, but for anonymous functions, the spec defines a whole section on how the name should be inferred:
In particular the runtime semantics for NamedEvaluation
is being "called" with the name as defined using various ambient syntax.
For example const a = () => {};
is clearly defined by the spec as the following:
LexicalBinding : BindingIdentifier Initializer
1. Let bindingId be StringValue of BindingIdentifier.
2. ...
3. If IsAnonymousFunctionDefinition(Initializer) is true, then
a. Let value be ? NamedEvaluation of Initializer with argument bindingId.
The final .name
property of functions is evaluated at runtime and does support dynamic constructs like so:
$ a = "abc", b = {[a]: () => {}}, b.abc.name
> "abc"
Names for property Assignments
The spec only infers name
from relatively simple constructs. Neither Firefox nor Chrome infer a name for this snippet:
$ a = {}, a.abc = () => {}, a.abc.name
> ""
Interesting though that at least the firefox devtools internally do infer the name, and the stacktrace even has the complete object path to the function:
My prototype infers the name from the complete expression the function was assigned to.
Class/Prototype/Constructors
Another interesting case is classes, constructors, and methods either on the class directly or on the prototype.
class X {
static y() {}
z() {}
}
X.prototype.w = () => {};
As before, the .name
property for all of these is their name, except w
for which it is empty.
However, when throwing an Error, the stack trace will say X.w
in chrome/node, and X.prototype.w
in Firefox for the method defined on the prototype, but just z
in Firefox for the class method.
In stack traces, Chrome/Node will prefix this with the class name of that specific instance, not the name of the class defining the method if you use inheritance!
My personal intuition says that I would like to have the names X.y
for the static function, and X.prototype.z
for the instance function; as that is how you would refer to them via code.
Anonymous Callbacks
The most complicated case would be "completely" anonymous functions used as callbacks.
const myCallback = useCallback(() => {}, []);
Per spec, this function has no name itself. In stack traces, Chrome/Node do not infer a name at all. Firefox however lists all of the named scopes in which the callback is defined.
Options that we have in this case are:
- Anonymous callback to
useCallback
.
- Anonymous callback which results in
myCallback
.
The second option is very much specific to the use-case in React, as the framework returns a wrapper that is then being called. We can’t infer from pure syntax if that is the case, or if the callback is immediately invoked, such as with Array.prototype.map
, in which case the first option would make more sense.