forked from babel/babel
/
index.ts
242 lines (207 loc) 路 6.25 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
import getFunctionArity from "@babel/helper-get-function-arity";
import template from "@babel/template";
import {
NOT_LOCAL_BINDING,
cloneNode,
identifier,
isAssignmentExpression,
isFunction,
isIdentifier,
isLiteral,
isNullLiteral,
isObjectMethod,
isObjectProperty,
isRegExpLiteral,
isTemplateLiteral,
isVariableDeclarator,
toBindingIdentifierName,
} from "@babel/types";
import type * as t from "@babel/types";
const buildPropertyMethodAssignmentWrapper = template(`
(function (FUNCTION_KEY) {
function FUNCTION_ID() {
return FUNCTION_KEY.apply(this, arguments);
}
FUNCTION_ID.toString = function () {
return FUNCTION_KEY.toString();
}
return FUNCTION_ID;
})(FUNCTION)
`);
const buildGeneratorPropertyMethodAssignmentWrapper = template(`
(function (FUNCTION_KEY) {
function* FUNCTION_ID() {
return yield* FUNCTION_KEY.apply(this, arguments);
}
FUNCTION_ID.toString = function () {
return FUNCTION_KEY.toString();
};
return FUNCTION_ID;
})(FUNCTION)
`);
const visitor = {
"ReferencedIdentifier|BindingIdentifier"(path, state) {
// check if this node matches our function id
if (path.node.name !== state.name) return;
// check that we don't have a local variable declared as that removes the need
// for the wrapper
const localDeclar = path.scope.getBindingIdentifier(state.name);
if (localDeclar !== state.outerDeclar) return;
state.selfReference = true;
path.stop();
},
};
function getNameFromLiteralId(id) {
if (isNullLiteral(id)) {
return "null";
}
if (isRegExpLiteral(id)) {
return `_${id.pattern}_${id.flags}`;
}
if (isTemplateLiteral(id)) {
return id.quasis.map(quasi => quasi.value.raw).join("");
}
if (id.value !== undefined) {
return id.value + "";
}
return "";
}
function wrap(state, method, id, scope) {
if (state.selfReference) {
if (scope.hasBinding(id.name) && !scope.hasGlobal(id.name)) {
// we can just munge the local binding
scope.rename(id.name);
} else {
// we don't currently support wrapping class expressions
if (!isFunction(method)) return;
// need to add a wrapper since we can't change the references
let build = buildPropertyMethodAssignmentWrapper;
if (method.generator) {
build = buildGeneratorPropertyMethodAssignmentWrapper;
}
const template = (
build({
FUNCTION: method,
FUNCTION_ID: id,
FUNCTION_KEY: scope.generateUidIdentifier(id.name),
}) as t.ExpressionStatement
).expression as t.CallExpression;
// shim in dummy params to retain function arity, if you try to read the
// source then you'll get the original since it's proxied so it's all good
const params = (
(template.callee as t.FunctionExpression).body
.body[0] as any as t.FunctionExpression
).params;
for (let i = 0, len = getFunctionArity(method); i < len; i++) {
params.push(scope.generateUidIdentifier("x"));
}
return template;
}
}
method.id = id;
scope.getProgramParent().references[id.name] = true;
}
function visit(node, name, scope) {
const state = {
selfAssignment: false,
selfReference: false,
outerDeclar: scope.getBindingIdentifier(name),
references: [],
name: name,
};
// check to see if we have a local binding of the id we're setting inside of
// the function, this is important as there are caveats associated
const binding = scope.getOwnBinding(name);
if (binding) {
if (binding.kind === "param") {
// safari will blow up in strict mode with code like:
//
// let t = function t(t) {};
//
// with the error:
//
// Cannot declare a parameter named 't' as it shadows the name of a
// strict mode function.
//
// this isn't to the spec and they've invented this behaviour which is
// **extremely** annoying so we avoid setting the name if it has a param
// with the same id
state.selfReference = true;
} else {
// otherwise it's defined somewhere in scope like:
//
// let t = function () {
// let t = 2;
// };
//
// so we can safely just set the id and move along as it shadows the
// bound function id
}
} else if (state.outerDeclar || scope.hasGlobal(name)) {
scope.traverse(node, visitor, state);
}
return state;
}
/**
* @param {NodePath} param0
* @param {Boolean} localBinding whether a name could shadow a self-reference (e.g. converting arrow function)
*/
export default function (
{
node,
parent,
scope,
id,
}: { node: any; parent?: any; scope: any; id?: any },
localBinding = false,
) {
// has an `id` so we don't need to infer one
if (node.id) return;
if (
(isObjectProperty(parent) || isObjectMethod(parent, { kind: "method" })) &&
(!parent.computed || isLiteral(parent.key))
) {
// { foo() {} };
id = parent.key;
} else if (isVariableDeclarator(parent)) {
// let foo = function () {};
id = parent.id;
// but not "let foo = () => {};" being converted to function expression
if (isIdentifier(id) && !localBinding) {
const binding = scope.parent.getBinding(id.name);
if (
binding &&
binding.constant &&
scope.getBinding(id.name) === binding
) {
// always going to reference this method
node.id = cloneNode(id);
node.id[NOT_LOCAL_BINDING] = true;
return;
}
}
} else if (isAssignmentExpression(parent, { operator: "=" })) {
// foo = function () {};
id = parent.left;
} else if (!id) {
return;
}
let name;
if (id && isLiteral(id)) {
name = getNameFromLiteralId(id);
} else if (id && isIdentifier(id)) {
name = id.name;
}
if (isFunction(node) && /[\u{10000}-\u{10ffff}]/u.test(name)) return;
if (name === undefined) {
return;
}
name = toBindingIdentifierName(name);
id = identifier(name);
// The id shouldn't be considered a local binding to the function because
// we are simply trying to set the function name and not actually create
// a local binding.
id[NOT_LOCAL_BINDING] = true;
const state = visit(node, name, scope);
return wrap(state, node, id, scope) || node;
}