Use a runtime SetFunctionName bytecode operation when object literal property keys are not known until evaluation. This lets anonymous function and class expressions, methods, and accessors receive names from numeric, computed, and Symbol property keys. Store inferred ECMAScript function names on each function object instead of mutating shared function data. That keeps repeated evaluations with different computed keys from leaking names across closures, while still using the per-instance name for stack metadata. Add regression coverage for computed object property names, repeated computed-key evaluations, and preserving unnamed functions that are only referenced by a computed property value.
127 lines
3.6 KiB
JavaScript
127 lines
3.6 KiB
JavaScript
test("basic functionality", () => {
|
|
expect(function () {}.name).toBe("");
|
|
|
|
function bar() {}
|
|
expect(bar.name).toBe("bar");
|
|
expect((bar.name = "baz")).toBe("baz");
|
|
expect(bar.name).toBe("bar");
|
|
});
|
|
|
|
test("function assigned to variable", () => {
|
|
let foo = function () {};
|
|
expect(foo.name).toBe("foo");
|
|
expect((foo.name = "bar")).toBe("bar");
|
|
expect(foo.name).toBe("foo");
|
|
|
|
let a, b;
|
|
a = b = function () {};
|
|
expect(a.name).toBe("b");
|
|
expect(b.name).toBe("b");
|
|
});
|
|
|
|
test("functions in array assigned to variable", () => {
|
|
const arr = [function () {}, function () {}, function () {}];
|
|
expect(arr[0].name).toBe("");
|
|
expect(arr[1].name).toBe("");
|
|
expect(arr[2].name).toBe("");
|
|
});
|
|
|
|
test("functions in objects", () => {
|
|
let f;
|
|
let o = { a: function () {} };
|
|
|
|
expect(o.a.name).toBe("a");
|
|
f = o.a;
|
|
expect(f.name).toBe("a");
|
|
expect(o.a.name).toBe("a");
|
|
|
|
o = { ...o, b: f };
|
|
expect(o.a.name).toBe("a");
|
|
expect(o.b.name).toBe("a");
|
|
|
|
// Member expressions do not get named.
|
|
o.c = function () {};
|
|
expect(o.c.name).toBe("");
|
|
});
|
|
|
|
test("computed property names infer anonymous function names", () => {
|
|
const namedSymbol = Symbol("named");
|
|
const emptySymbol = Symbol("");
|
|
const unnamedSymbol = Symbol();
|
|
const getterSymbol = Symbol("getter");
|
|
const setterSymbol = Symbol("");
|
|
|
|
const object = {
|
|
[1]: function () {},
|
|
[2]: class {},
|
|
[namedSymbol]: () => {},
|
|
[emptySymbol]: function* () {},
|
|
[unnamedSymbol]: async function () {},
|
|
[/a/]: function () {},
|
|
get [getterSymbol]() {},
|
|
set [setterSymbol](value) {},
|
|
};
|
|
|
|
expect(object[1].name).toBe("1");
|
|
expect(object[2].name).toBe("2");
|
|
expect(object[namedSymbol].name).toBe("[named]");
|
|
expect(object[emptySymbol].name).toBe("[]");
|
|
expect(object[unnamedSymbol].name).toBe("");
|
|
expect(object[/a/].name).toBe("/a/");
|
|
expect(Object.getOwnPropertyDescriptor(object, getterSymbol).get.name).toBe("get [getter]");
|
|
expect(Object.getOwnPropertyDescriptor(object, setterSymbol).set.name).toBe("set []");
|
|
});
|
|
|
|
test("computed property name inference does not leak between evaluations", () => {
|
|
function objectName(key) {
|
|
return { [key]: function () {} }[key].name;
|
|
}
|
|
|
|
function classMethodName(key) {
|
|
return new (class {
|
|
[key]() {}
|
|
})()[key].name;
|
|
}
|
|
|
|
expect(objectName("first")).toBe("first");
|
|
expect(objectName("second")).toBe("second");
|
|
expect(classMethodName("first")).toBe("first");
|
|
expect(classMethodName("second")).toBe("second");
|
|
});
|
|
|
|
test("computed property name inference only applies to anonymous definitions", () => {
|
|
const key = "computed";
|
|
const value = [function () {}][0];
|
|
const object = { [key]: value };
|
|
|
|
expect(object[key].name).toBe("");
|
|
});
|
|
|
|
test("names of native functions", () => {
|
|
expect(console.debug.name).toBe("debug");
|
|
expect((console.debug.name = "warn")).toBe("warn");
|
|
expect(console.debug.name).toBe("debug");
|
|
});
|
|
|
|
describe("some anonymous functions get renamed", () => {
|
|
test("direct assignment does name new function expression", () => {
|
|
// prettier-ignore
|
|
let f1 = (function () {});
|
|
expect(f1.name).toBe("f1");
|
|
|
|
let f2 = false;
|
|
f2 ||= function () {};
|
|
expect(f2.name).toBe("f2");
|
|
});
|
|
|
|
test("assignment from variable does not name", () => {
|
|
const f1 = function () {};
|
|
let f3 = f1;
|
|
expect(f3.name).toBe("f1");
|
|
});
|
|
|
|
test("assignment via expression does not name", () => {
|
|
let f4 = false || function () {};
|
|
expect(f4.name).toBe("");
|
|
});
|
|
});
|