Skip to content

Commit ee1a9b9

Browse files
authored
Infer function names
Infer function name when declared in a `var`/`let` statement, handles also function in object properties, methods, getter, setters This does _not_ handle computed properties, because that needs to be done at runtime and we are only handling static names here.
1 parent 312afad commit ee1a9b9

File tree

8 files changed

+225
-39
lines changed

8 files changed

+225
-39
lines changed

rhino/src/main/java/org/mozilla/javascript/ArrowFunction.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,14 @@ public int getLength() {
7474
return 0;
7575
}
7676

77+
@Override
78+
public String getFunctionName() {
79+
if (targetFunction instanceof BaseFunction) {
80+
return ((BaseFunction) targetFunction).getFunctionName();
81+
}
82+
return super.getFunctionName();
83+
}
84+
7785
@Override
7886
public int getArity() {
7987
return getLength();

rhino/src/main/java/org/mozilla/javascript/IRFactory.java

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.util.ArrayDeque;
1010
import java.util.ArrayList;
1111
import java.util.List;
12+
import java.util.Objects;
1213
import java.util.Queue;
1314
import org.mozilla.javascript.ast.ArrayComprehension;
1415
import org.mozilla.javascript.ast.ArrayComprehensionLoop;
@@ -448,19 +449,27 @@ private Node transformArrayLiteral(ArrayLiteral node) {
448449

449450
private Node transformAssignment(Assignment node) {
450451
AstNode right = node.getRight();
451-
AstNode left = parser.removeParens(node.getLeft());
452+
AstNode originalLeft = node.getLeft();
453+
AstNode left = parser.removeParens(originalLeft);
454+
boolean shouldTryToInferName =
455+
(originalLeft == left); // If we removed parens, we won't try to infer name
452456
left = transformAssignmentLeft(node, left, right);
453457

454458
Node target = null;
455459
if (isDestructuring(left)) {
456460
target = left;
461+
shouldTryToInferName = false;
457462
} else {
458463
target = transform(left);
459464
}
460465

461466
astNodePos.push(left);
462467
try {
463-
return createAssignment(node.getType(), target, transform(right));
468+
Node transformedRight = transform(right);
469+
if (shouldTryToInferName) {
470+
inferNameIfMissing(node.getLeft(), transformedRight, null);
471+
}
472+
return createAssignment(node.getType(), target, transformedRight);
464473
} finally {
465474
astNodePos.pop();
466475
}
@@ -961,14 +970,26 @@ private Node transformObjectLiteral(ObjectLiteral node) {
961970
properties = new Object[size];
962971
for (ObjectProperty prop : elems) {
963972
Object propKey = Parser.getPropKey(prop.getLeft());
973+
Node inferrableName = null;
964974
if (propKey == null) {
965975
Node theId = transform(prop.getLeft());
966976
properties[i++] = theId;
967977
} else {
968978
properties[i++] = propKey;
979+
assert propKey instanceof String || propKey instanceof Integer;
980+
inferrableName = parser.createName(Objects.toString(propKey));
981+
inferrableName.setLineColumnNumber(
982+
prop.getLeft().getLineno(), prop.getLeft().getColumn());
969983
}
970984

971985
Node right = transform(prop.getRight());
986+
if (inferrableName != null) {
987+
inferNameIfMissing(
988+
inferrableName,
989+
right,
990+
prop.isGetterMethod() ? "get " : prop.isSetterMethod() ? "set " : null);
991+
}
992+
972993
if (prop.isGetterMethod()) {
973994
right = createUnary(Token.GET, right);
974995
} else if (prop.isSetterMethod()) {
@@ -1238,6 +1259,7 @@ private Node transformVariableInitializers(VariableDeclaration node) {
12381259
}
12391260
}
12401261
} else {
1262+
inferNameIfMissing(left, right, null);
12411263
if (right != null) {
12421264
left.addChildToBack(right);
12431265
}
@@ -2332,6 +2354,31 @@ private Node createAssignment(int assignType, Node left, Node right) {
23322354
throw Kit.codeBug();
23332355
}
23342356

2357+
/** Infer function name is missing on rhs. In the future, should also handle class names. */
2358+
private void inferNameIfMissing(Object left, Node right, String prefix) {
2359+
if (parser.compilerEnv.getLanguageVersion() < Context.VERSION_ES6) {
2360+
return;
2361+
}
2362+
2363+
if (left instanceof Name && right != null && right.type == Token.FUNCTION) {
2364+
Name name = (Name) left;
2365+
if (name.getIdentifier().equals(NativeObject.PROTO_PROPERTY)) {
2366+
// Ignore weird edge case
2367+
return;
2368+
}
2369+
2370+
var fnIndex = right.getExistingIntProp(Node.FUNCTION_PROP);
2371+
FunctionNode functionNode = parser.currentScriptOrFn.getFunctionNode(fnIndex);
2372+
if (functionNode.getType() != 0 && functionNode.getFunctionName() == null) {
2373+
if (prefix != null) {
2374+
functionNode.setFunctionName(name.withPrefix(prefix));
2375+
} else {
2376+
functionNode.setFunctionName(name);
2377+
}
2378+
}
2379+
}
2380+
}
2381+
23352382
private Node propagateSuperFromLhs(Node result, Node left) {
23362383
if (left.getIntProp(Node.SUPER_PROPERTY_ACCESS, 0) == 1) {
23372384
result.putIntProp(Node.SUPER_PROPERTY_ACCESS, 1);

rhino/src/main/java/org/mozilla/javascript/ast/Name.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,12 @@ public String toSource(int depth) {
137137
return makeIndent(depth) + (identifier == null ? "<null>" : identifier);
138138
}
139139

140+
public Name withPrefix(String prefix) {
141+
Name clone = new Name(this.getPosition(), this.getLength(), prefix + this.identifier);
142+
clone.setLineColumnNumber(this.getLineno(), this.getColumn());
143+
return clone;
144+
}
145+
140146
/** Visits this node. There are no children to visit. */
141147
@Override
142148
public void visit(NodeVisitor v) {

rhino/src/test/java/org/mozilla/javascript/FunctionPrototypeSymbolHasInstanceTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ public void testThrowTypeErrorOnNonObjectIncludingSymbol() {
145145
+ "f.prototype = Symbol(); \n"
146146
+ "f[Symbol.hasInstance]({})";
147147
Utils.assertEcmaErrorES6(
148-
"TypeError: 'prototype' property of '' is not an object. (test#3)", script);
148+
"TypeError: 'prototype' property of 'f' is not an object. (test#3)", script);
149149
}
150150

151151
@Test

rhino/src/test/java/org/mozilla/javascript/tests/GeneratedMethodNameTest.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,19 @@ public void constructor() throws Exception {
6464
@Test
6565
public void anonymousFunction() throws Exception {
6666
final String scriptCode =
67-
"var myFunc = function() {\n"
67+
"(function() {\n"
6868
+ " var m = javaNameGetter.readCurrentFunctionJavaName();\n"
6969
+ " if (m != 'anonymous') throw 'got ' + m;"
70+
+ "})();";
71+
doTest(scriptCode);
72+
}
73+
74+
@Test
75+
public void anonymousFunctionAssignedToVariable() throws Exception {
76+
final String scriptCode =
77+
"var myFunc = function() {\n"
78+
+ " var m = javaNameGetter.readCurrentFunctionJavaName();\n"
79+
+ " if (m != 'myFunc') throw 'got ' + m;"
7080
+ "}\n"
7181
+ "myFunc();";
7282
doTest(scriptCode);
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
package org.mozilla.javascript.tests.es6;
2+
3+
import org.junit.jupiter.api.Test;
4+
import org.mozilla.javascript.testutils.Utils;
5+
6+
class FunctionNameTest {
7+
@Test
8+
void varEqualsFunction() {
9+
Utils.assertWithAllModes_ES6("f", "var f = function() {}; f.name");
10+
}
11+
12+
@Test
13+
void varEqualsArrowFunction() {
14+
Utils.assertWithAllModes_ES6("f", "var f = () => {}; f.name");
15+
}
16+
17+
@Test
18+
void letEqualsFunction() {
19+
Utils.assertWithAllModes_ES6("f", "let f = function() {}; f.name");
20+
}
21+
22+
@Test
23+
void letEqualsArrowFunction() {
24+
Utils.assertWithAllModes_ES6("f", "let f = () => {}; f.name");
25+
}
26+
27+
@Test
28+
void constEqualsFunction() {
29+
Utils.assertWithAllModes_ES6("f", "const f = function() {}; f.name");
30+
}
31+
32+
@Test
33+
void constEqualsArrowFunction() {
34+
Utils.assertWithAllModes_ES6("f", "const f = () => {}; f.name");
35+
}
36+
37+
@Test
38+
void nonAnonymousFunctionsDontGetOverriddenInDeclaration() {
39+
Utils.assertWithAllModes_ES6("g", "var f = function g() {}; f.name");
40+
}
41+
42+
@Test
43+
void assignmentVarFunction() {
44+
Utils.assertWithAllModes_ES6("f", "var f; f = function(){}; f.name");
45+
}
46+
47+
@Test
48+
void assignmentVarArrow() {
49+
Utils.assertWithAllModes_ES6("f", "var f; f = () => {}; f.name");
50+
}
51+
52+
@Test
53+
void assignmentLetFunction() {
54+
Utils.assertWithAllModes_ES6("f", "let f; f = function(){}; f.name");
55+
}
56+
57+
@Test
58+
void assignmentLetArrow() {
59+
Utils.assertWithAllModes_ES6("f", "let f; f = () => {}; f.name");
60+
}
61+
62+
@Test
63+
void assignmentShouldNotInferIfParenthesized() {
64+
Utils.assertWithAllModes_ES6("", "var f; (f) = function(){}; f.name");
65+
}
66+
67+
@Test
68+
void nonAnonymousFunctionsDontGetOverriddenInAssignment() {
69+
Utils.assertWithAllModes_ES6("g", "var f; f = function g() {}; f.name");
70+
}
71+
72+
@Test
73+
void reassignDoesntOverride() {
74+
Utils.assertWithAllModes_ES6("f", "var f; f = function() {}; g = f; g.name");
75+
}
76+
77+
@Test
78+
void declarationMixedWithAssignment() {
79+
Utils.assertWithAllModes_ES6("g", "var f = g = () => {}; f.name");
80+
}
81+
82+
@Test
83+
void propertyFunction() {
84+
Utils.assertWithAllModes_ES6("x", "o = { x: function(){} }; o.x.name");
85+
}
86+
87+
@Test
88+
void propertyArrow() {
89+
Utils.assertWithAllModes_ES6("x", "o = { x: () => {} }; o.x.name");
90+
}
91+
92+
@Test
93+
void method() {
94+
Utils.assertWithAllModes_ES6("x", "o = { x() {} }; o.x.name");
95+
}
96+
97+
@Test
98+
void methodNumericLiteral() {
99+
Utils.assertWithAllModes_ES6("1", "o = { 1() {} }; o['1'].name");
100+
}
101+
102+
@Test
103+
void methodBooleanLiteral() {
104+
Utils.assertWithAllModes_ES6("false", "o = { false() {} }; o['false'].name");
105+
}
106+
107+
@Test
108+
void methodComputedProperty() {
109+
// TODO: this is not working at the moment, because it cannot be done statically
110+
// but needs to be done at runtime
111+
Utils.assertWithAllModes_ES6("", "o = { [1 + 2]() {} }; o['3'].name");
112+
}
113+
114+
@Test
115+
void methodGenerators() {
116+
Utils.assertWithAllModes_ES6("x", "o = { *x() {} }; o.x.name");
117+
}
118+
119+
@Test
120+
void getter() {
121+
Utils.assertWithAllModes_ES6(
122+
"get x",
123+
"var o = { get x(){} }; var desc = Object.getOwnPropertyDescriptor(o, \"x\"); desc.get.name");
124+
}
125+
126+
@Test
127+
void setter() {
128+
Utils.assertWithAllModes_ES6(
129+
"set x",
130+
"var o = { set x(v){} }; var desc = Object.getOwnPropertyDescriptor(o, \"x\"); desc.set.name");
131+
}
132+
133+
@Test
134+
void protoIsASpecialName() {
135+
Utils.assertWithAllModes_ES6("", "var o = { __proto__() {} }; o.__proto__.name");
136+
}
137+
138+
@Test
139+
void inferenceIsNotUsedInEs5() {
140+
Utils.assertWithAllModes_1_8("", "var f = function() {}; f.name");
141+
}
142+
}

tests/src/test/java/org/mozilla/javascript/tests/es6/ProtoProperty2Test.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1220,10 +1220,8 @@ public void protoInitOverwriteSetter() {
12201220
"false / false / undefined / undefined / # truetrue / new", script);
12211221
Utils.assertWithAllModes_1_8(
12221222
"false / false / undefined / undefined / # truetrue / new", script);
1223-
// Utils.assertWithAllModes_ES6("true / false / undefined / get __proto__ / set # truetrue /
1224-
// new", script);
12251223
Utils.assertWithAllModes_ES6(
1226-
"true / false / undefined / get __proto__ / # truetrue / new", script);
1224+
"true / false / undefined / get __proto__ / set # truetrue / new", script);
12271225
}
12281226

12291227
@Test

0 commit comments

Comments
 (0)