183
transformedFn = fn;
184
break;
185
}
186
+ default: {
187
+ assertExhaustive(
188
+ originalFn.node,
189
+ `Creating unhandled function: ${originalFn.node}`,
190
+ );
191
+ }
192
}
187
-
193
// Avoid visiting the new transformed version
194
ALREADY_COMPILED.add(transformedFn);
195
return transformedFn;
196
}
197
198
+function insertNewFunctionNode(
199
+ originalFn: BabelFn,
200
+ compiledFn: CodegenFunction,
201
+): NodePath<t.Function> {
202
+ switch (originalFn.type) {
203
+ case 'FunctionDeclaration': {
204
+ return originalFn.insertAfter(
205
+ createNewFunctionNode(originalFn, compiledFn),
206
+ )[0]!;
207
+ }
208
+ /**
209
+ * This is pretty gross but we can't just append an (Arrow)FunctionExpression as a sibling of
210
+ * the original function. If the original function was itself an (Arrow)FunctionExpression,
211
+ * this would cause its parent to become a SequenceExpression instead which breaks a bunch of
212
+ * assumptions elsewhere in the plugin.
213
+ *
214
+ * To get around this, we synthesize a new VariableDeclaration holding the compiled function
215
+ * expression and insert it as a true sibling (ie within the Program's block statements).
216
+ */
217
+ case 'ArrowFunctionExpression':
218
+ case 'FunctionExpression': {
219
+ const funcExpr = createNewFunctionNode(originalFn, compiledFn);
220
+ CompilerError.invariant(
221
+ t.isArrowFunctionExpression(funcExpr) ||
222
+ t.isFunctionExpression(funcExpr),
223
+ {
224
+ reason: 'Expected an (arrow) function expression to be created',
225
+ description: `Got: ${funcExpr.type}`,
226
+ loc: funcExpr.loc ?? null,
227
+ },
228
+ );
229
+ CompilerError.invariant(compiledFn.id != null, {
230
+ reason: 'Expected compiled function to have an identifier',
231
+ loc: compiledFn.loc,
232
+ });
233
+ CompilerError.invariant(
234
+ originalFn.parentPath.isVariableDeclarator() &&
235
+ originalFn.parentPath.parentPath.isVariableDeclaration(),
236
+ {
237
+ reason: 'Expected a variable declarator parent',
238
+ loc: originalFn.node.loc ?? null,
239
+ },
240
+ );
241
+ const varDecl = originalFn.parentPath.parentPath;
242
+ varDecl.insertAfter(
243
+ t.variableDeclaration('const', [
244
+ t.variableDeclarator(compiledFn.id, funcExpr),
245
+ ]),
246
+ );
247
+ const insertedFuncExpr = varDecl.get('declarations')[0]!.get('init')!;
248
+ CompilerError.invariant(
249
+ insertedFuncExpr.isArrowFunctionExpression() ||
250
+ insertedFuncExpr.isFunctionExpression(),
251
+ {
252
+ reason: 'Expected inserted (arrow) function expression',
253
+ description: `Got: ${insertedFuncExpr}`,
254
+ loc: insertedFuncExpr.node?.loc ?? null,
255
+ },
256
+ );
257
+ return insertedFuncExpr;
258
+ }
259
+ default: {
260
+ assertExhaustive(
261
+ originalFn,
262
+ `Inserting unhandled function: ${originalFn}`,
263
+ );
264
+ }
265
+ }
266
+}
267
+
268
/*
269
* This is a hack to work around what seems to be a Babel bug. Babel doesn't
270
* consistently respect the `skip()` function to avoid revisiting a node within
482
reason: 'Unexpected nested outlined functions',
483
loc: outlined.fn.loc,
484
});
410
- const fn = current.fn.insertAfter(
411
- createNewFunctionNode(current.fn, outlined.fn),
412
- )[0]!;
485
+ const fn = insertNewFunctionNode(current.fn, outlined.fn);
486
fn.skip();
487
ALREADY_COMPILED.add(fn.node);
488
if (outlined.type !== null) {