main
ts 179 lines 5.89 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8 import {
9 FunctionExpression,
10 getHookKind,
11 HIRFunction,
12 IdentifierId,
13 } from '../HIR';
14
15 export function nameAnonymousFunctions(fn: HIRFunction): void {
16 if (fn.id == null) {
17 return;
18 }
19 const parentName = fn.id;
20 const functions = nameAnonymousFunctionsImpl(fn);
21 function visit(node: Node, prefix: string): void {
22 if (node.generatedName != null && node.fn.nameHint == null) {
23 /**
24 * Note that we don't generate a name for functions that already had one,
25 * so we'll only add the prefix to anonymous functions regardless of
26 * nesting depth.
27 */
28 const name = `${prefix}${node.generatedName}]`;
29 node.fn.nameHint = name;
30 node.fn.loweredFunc.func.nameHint = name;
31 }
32 /**
33 * Whether or not we generated a name for the function at this node,
34 * traverse into its nested functions to assign them names
35 */
36 const nextPrefix = `${prefix}${node.generatedName ?? node.fn.name ?? '<anonymous>'} > `;
37 for (const inner of node.inner) {
38 visit(inner, nextPrefix);
39 }
40 }
41 for (const node of functions) {
42 visit(node, `${parentName}[`);
43 }
44 }
45
46 type Node = {
47 fn: FunctionExpression;
48 generatedName: string | null;
49 inner: Array<Node>;
50 };
51
52 function nameAnonymousFunctionsImpl(fn: HIRFunction): Array<Node> {
53 // Functions that we track to generate names for
54 const functions: Map<IdentifierId, Node> = new Map();
55 // Tracks temporaries that read from variables/globals/properties
56 const names: Map<IdentifierId, string> = new Map();
57 // Tracks all function nodes to bubble up for later renaming
58 const nodes: Array<Node> = [];
59 for (const block of fn.body.blocks.values()) {
60 for (const instr of block.instructions) {
61 const {lvalue, value} = instr;
62 switch (value.kind) {
63 case 'LoadGlobal': {
64 names.set(lvalue.identifier.id, value.binding.name);
65 break;
66 }
67 case 'LoadContext':
68 case 'LoadLocal': {
69 const name = value.place.identifier.name;
70 if (name != null && name.kind === 'named') {
71 names.set(lvalue.identifier.id, name.value);
72 }
73 const func = functions.get(value.place.identifier.id);
74 if (func != null) {
75 functions.set(lvalue.identifier.id, func);
76 }
77 break;
78 }
79 case 'PropertyLoad': {
80 const objectName = names.get(value.object.identifier.id);
81 if (objectName != null) {
82 names.set(
83 lvalue.identifier.id,
84 `${objectName}.${String(value.property)}`,
85 );
86 }
87 break;
88 }
89 case 'FunctionExpression': {
90 const inner = nameAnonymousFunctionsImpl(value.loweredFunc.func);
91 const node: Node = {
92 fn: value,
93 generatedName: null,
94 inner,
95 };
96 /**
97 * Bubble-up all functions, even if they're named, so that we can
98 * later generate names for any inner anonymous functions
99 */
100 nodes.push(node);
101 if (value.name == null) {
102 // but only generate names for anonymous functions
103 functions.set(lvalue.identifier.id, node);
104 }
105 break;
106 }
107 case 'StoreContext':
108 case 'StoreLocal': {
109 const node = functions.get(value.value.identifier.id);
110 const variableName = value.lvalue.place.identifier.name;
111 if (
112 node != null &&
113 node.generatedName == null &&
114 variableName != null &&
115 variableName.kind === 'named'
116 ) {
117 node.generatedName = variableName.value;
118 functions.delete(value.value.identifier.id);
119 }
120 break;
121 }
122 case 'CallExpression':
123 case 'MethodCall': {
124 const callee =
125 value.kind === 'MethodCall' ? value.property : value.callee;
126 const hookKind = getHookKind(fn.env, callee.identifier);
127 let calleeName: string | null = null;
128 if (hookKind != null && hookKind !== 'Custom') {
129 calleeName = hookKind;
130 } else {
131 calleeName = names.get(callee.identifier.id) ?? '(anonymous)';
132 }
133 let fnArgCount = 0;
134 for (const arg of value.args) {
135 if (arg.kind === 'Identifier' && functions.has(arg.identifier.id)) {
136 fnArgCount++;
137 }
138 }
139 for (let i = 0; i < value.args.length; i++) {
140 const arg = value.args[i]!;
141 if (arg.kind === 'Spread') {
142 continue;
143 }
144 const node = functions.get(arg.identifier.id);
145 if (node != null && node.generatedName == null) {
146 const generatedName =
147 fnArgCount > 1 ? `${calleeName}(arg${i})` : `${calleeName}()`;
148 node.generatedName = generatedName;
149 functions.delete(arg.identifier.id);
150 }
151 }
152 break;
153 }
154 case 'JsxExpression': {
155 for (const attr of value.props) {
156 if (attr.kind === 'JsxSpreadAttribute') {
157 continue;
158 }
159 const node = functions.get(attr.place.identifier.id);
160 if (node != null && node.generatedName == null) {
161 const elementName =
162 value.tag.kind === 'BuiltinTag'
163 ? value.tag.name
164 : (names.get(value.tag.identifier.id) ?? null);
165 const propName =
166 elementName == null
167 ? attr.name
168 : `<${elementName}>.${attr.name}`;
169 node.generatedName = `${propName}`;
170 functions.delete(attr.place.identifier.id);
171 }
172 }
173 break;
174 }
175 }
176 }
177 }
178 return nodes;
179 }