js-confuser 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +215 -170
  3. package/dist/constants.js +6 -2
  4. package/dist/obfuscator.js +0 -6
  5. package/dist/options.js +4 -4
  6. package/dist/presets.js +6 -7
  7. package/dist/templates/crash.js +2 -2
  8. package/dist/templates/functionLength.js +16 -0
  9. package/dist/transforms/dispatcher.js +4 -1
  10. package/dist/transforms/extraction/duplicateLiteralsRemoval.js +89 -58
  11. package/dist/transforms/flatten.js +224 -147
  12. package/dist/transforms/identifier/movedDeclarations.js +38 -85
  13. package/dist/transforms/identifier/renameVariables.js +94 -41
  14. package/dist/transforms/lock/lock.js +0 -37
  15. package/dist/transforms/minify.js +2 -2
  16. package/dist/transforms/rgf.js +139 -246
  17. package/dist/transforms/stack.js +42 -1
  18. package/dist/transforms/transform.js +1 -1
  19. package/dist/util/gen.js +2 -1
  20. package/dist/util/identifiers.js +37 -3
  21. package/dist/util/insert.js +24 -3
  22. package/docs/ControlFlowFlattening.md +595 -0
  23. package/{Countermeasures.md → docs/Countermeasures.md} +1 -15
  24. package/{Integrity.md → docs/Integrity.md} +2 -2
  25. package/docs/RGF.md +419 -0
  26. package/package.json +1 -1
  27. package/src/constants.ts +3 -0
  28. package/src/obfuscator.ts +0 -4
  29. package/src/options.ts +9 -86
  30. package/src/presets.ts +6 -7
  31. package/src/templates/crash.ts +10 -10
  32. package/src/templates/functionLength.ts +14 -0
  33. package/src/transforms/dispatcher.ts +5 -1
  34. package/src/transforms/extraction/duplicateLiteralsRemoval.ts +130 -129
  35. package/src/transforms/flatten.ts +357 -290
  36. package/src/transforms/identifier/movedDeclarations.ts +50 -96
  37. package/src/transforms/identifier/renameVariables.ts +120 -56
  38. package/src/transforms/lock/lock.ts +1 -42
  39. package/src/transforms/minify.ts +11 -2
  40. package/src/transforms/rgf.ts +214 -404
  41. package/src/transforms/stack.ts +62 -0
  42. package/src/transforms/transform.ts +6 -2
  43. package/src/util/gen.ts +7 -2
  44. package/src/util/identifiers.ts +43 -2
  45. package/src/util/insert.ts +26 -2
  46. package/test/code/ES6.src.js +24 -0
  47. package/test/transforms/flatten.test.ts +352 -88
  48. package/test/transforms/identifier/movedDeclarations.test.ts +37 -9
  49. package/test/transforms/identifier/renameVariables.test.ts +37 -0
  50. package/test/transforms/lock/lock.test.ts +1 -48
  51. package/test/transforms/minify.test.ts +19 -0
  52. package/test/transforms/rgf.test.ts +262 -353
  53. package/test/transforms/stack.test.ts +52 -0
  54. package/test/util/identifiers.test.ts +113 -1
  55. package/test/util/insert.test.ts +57 -3
  56. package/src/transforms/eval.ts +0 -89
  57. package/src/transforms/identifier/nameRecycling.ts +0 -280
  58. package/test/transforms/eval.test.ts +0 -131
  59. package/test/transforms/identifier/nameRecycling.test.ts +0 -205
@@ -3,36 +3,23 @@ import { reservedIdentifiers } from "../constants";
3
3
  import Obfuscator from "../obfuscator";
4
4
  import { ObfuscateOrder } from "../order";
5
5
  import { ComputeProbabilityMap } from "../probability";
6
- import Template from "../templates/template";
7
- import traverse, { walk } from "../traverse";
6
+ import { walk } from "../traverse";
8
7
  import {
9
8
  ArrayExpression,
10
- AssignmentExpression,
9
+ BlockStatement,
11
10
  CallExpression,
12
- ConditionalExpression,
13
- ExpressionStatement,
14
- FunctionExpression,
15
11
  Identifier,
16
12
  Literal,
17
- Location,
18
13
  MemberExpression,
19
14
  NewExpression,
20
15
  Node,
21
16
  ReturnStatement,
22
- SpreadElement,
17
+ ThisExpression,
23
18
  VariableDeclaration,
24
19
  VariableDeclarator,
25
20
  } from "../util/gen";
26
- import { getDefiningIdentifier, getIdentifierInfo } from "../util/identifiers";
27
- import {
28
- getVarContext,
29
- isVarContext,
30
- isFunction,
31
- prepend,
32
- getDefiningContext,
33
- clone,
34
- } from "../util/insert";
35
- import { getRandomString } from "../util/random";
21
+ import { getIdentifierInfo } from "../util/identifiers";
22
+ import { prepend, getDefiningContext } from "../util/insert";
36
23
  import Transform from "./transform";
37
24
 
38
25
  /**
@@ -43,436 +30,259 @@ import Transform from "./transform";
43
30
  * Rigorous checks are in place to only include pure functions.
44
31
  *
45
32
  * `flatten` can attempt to make function reference-less. Recommended to have flatten enabled with RGF.
46
- *
47
- * | Mode | Description |
48
- * | --- | --- |
49
- * | `"all"` | Applies to all scopes |
50
- * | `true` | Applies to the top level only |
51
- * | `false` | Feature disabled |
52
33
  */
53
34
  export default class RGF extends Transform {
35
+ // Array of all the `new Function` calls
36
+ arrayExpressionElements: Node[];
37
+ // The name of the array holding all the `new Function` expressions
38
+ arrayExpressionName: string;
39
+
54
40
  constructor(o) {
55
41
  super(o, ObfuscateOrder.RGF);
42
+
43
+ this.arrayExpressionName = this.getPlaceholder() + "_rgf";
44
+ this.arrayExpressionElements = [];
45
+ }
46
+
47
+ apply(tree: Node): void {
48
+ super.apply(tree);
49
+
50
+ // Only add the array if there were converted functions
51
+ if (this.arrayExpressionElements.length > 0) {
52
+ prepend(
53
+ tree,
54
+ VariableDeclaration(
55
+ VariableDeclarator(
56
+ Identifier(this.arrayExpressionName),
57
+ ArrayExpression(this.arrayExpressionElements)
58
+ )
59
+ )
60
+ );
61
+ }
56
62
  }
57
63
 
58
64
  match(object, parents) {
59
- return isVarContext(object) && object.type !== "ArrowFunctionExpression";
65
+ return (
66
+ (object.type === "FunctionDeclaration" ||
67
+ object.type === "FunctionExpression") && // Does not apply to Arrow functions
68
+ !object.async && // Does not apply to async/generator functions
69
+ !object.generator
70
+ );
60
71
  }
61
72
 
62
- transform(contextObject, contextParents) {
63
- return () => {
64
- var isGlobal = contextObject.type == "Program";
73
+ transform(object: Node, parents: Node[]) {
74
+ // Discard getter/setter methods
75
+ if (parents[0].type === "Property" && parents[0].value === object) {
76
+ if (
77
+ parents[0].method ||
78
+ parents[0].kind === "get" ||
79
+ parents[0].kind === "set"
80
+ ) {
81
+ return;
82
+ }
83
+ }
84
+
85
+ // Discard class methods
86
+ if (parents[0].type === "MethodDefinition" && parents[0].value === object) {
87
+ return;
88
+ }
89
+
90
+ // Avoid applying to the countermeasures function
91
+ if (typeof this.options.lock?.countermeasures === "string") {
92
+ // function countermeasures(){...}
93
+ if (
94
+ object.type === "FunctionDeclaration" &&
95
+ object.id.type === "Identifier" &&
96
+ object.id.name === this.options.lock.countermeasures
97
+ ) {
98
+ return;
99
+ }
65
100
 
66
- var value = ComputeProbabilityMap(this.options.rgf, (x) => x, isGlobal);
67
- if (value !== "all" && !isGlobal) {
101
+ // var countermeasures = function(){...}
102
+ if (
103
+ parents[0].type === "VariableDeclarator" &&
104
+ parents[0].init === object &&
105
+ parents[0].id.type === "Identifier" &&
106
+ parents[0].id.name === this.options.lock.countermeasures
107
+ ) {
68
108
  return;
69
109
  }
110
+ }
111
+
112
+ // Check user option
113
+ if (!ComputeProbabilityMap(this.options.rgf, (x) => x, object?.id?.name))
114
+ return;
115
+
116
+ // Discard functions that use 'eval' function
117
+ if (object.$requiresEval) return;
118
+
119
+ // Check for 'this', 'arguments' (not allowed!)
120
+ var isIllegal = false;
121
+ walk(object, parents, (o, p) => {
122
+ if (
123
+ o.type === "ThisExpression" ||
124
+ o.type === "Super" ||
125
+ (o.type === "Identifier" && o.name === "arguments")
126
+ ) {
127
+ isIllegal = true;
128
+ return "EXIT";
129
+ }
130
+ });
70
131
 
71
- var collect: {
72
- location: Location;
73
- references: Set<string>;
74
- name?: string;
75
- }[] = [];
76
- var queue: Location[] = [];
77
- var names = new Map<string, number>();
78
- var referenceSignatures: { [name: string]: string } = {};
132
+ if (isIllegal) return;
79
133
 
80
- var definingNodes = new Map<string, Node>();
134
+ return () => {
135
+ // Make sure function is 'reference-less'
136
+ var definedMap = new Map<Node, Set<string>>();
137
+ var isReferenceLess = true;
138
+ var identifierPreventingTransformation: string;
81
139
 
82
- walk(contextObject, contextParents, (object, parents) => {
140
+ walk(object, parents, (o, p) => {
83
141
  if (
84
- object !== contextObject &&
85
- isFunction(object) &&
86
- !object.$requiresEval &&
87
- !object.async &&
88
- !object.generator &&
89
- getVarContext(parents[0], parents.slice(1)) === contextObject
142
+ o.type === "Identifier" &&
143
+ !reservedIdentifiers.has(o.name) &&
144
+ !this.options.globalVariables.has(o.name)
90
145
  ) {
91
- // Discard getter/setter methods
92
- if (parents[0].type === "Property" && parents[0].value === object) {
93
- if (
94
- parents[0].method ||
95
- parents[0].kind === "get" ||
96
- parents[0].kind === "set"
97
- ) {
98
- return;
99
- }
100
- }
101
-
102
- // Discard class methods
103
- if (
104
- parents[0].type === "MethodDefinition" &&
105
- parents[0].value === object
106
- ) {
146
+ var info = getIdentifierInfo(o, p);
147
+ if (!info.spec.isReferenced) {
107
148
  return;
108
149
  }
109
150
 
110
- // Avoid applying to the countermeasures function
111
- if (typeof this.options.lock?.countermeasures === "string") {
112
- // function countermeasures(){...}
113
- if (
114
- object.type === "FunctionDeclaration" &&
115
- object.id.type === "Identifier" &&
116
- object.id.name === this.options.lock.countermeasures
117
- ) {
118
- return;
119
- }
151
+ if (info.spec.isDefined) {
152
+ // Add to defined map
153
+ var definingContext = getDefiningContext(o, p);
120
154
 
121
- // var countermeasures = function(){...}
122
- if (
123
- parents[0].type === "VariableDeclarator" &&
124
- parents[0].init === object &&
125
- parents[0].id.type === "Identifier" &&
126
- parents[0].id.name === this.options.lock.countermeasures
127
- ) {
128
- return;
155
+ if (!definedMap.has(definingContext)) {
156
+ definedMap.set(definingContext, new Set([o.name]));
157
+ } else {
158
+ definedMap.get(definingContext).add(o.name);
129
159
  }
130
- }
131
-
132
- var defined = new Set<string>(),
133
- referenced = new Set<string>();
134
-
135
- var isBound = false;
136
-
137
- /**
138
- * The fnTraverses serves two important purposes
139
- *
140
- * - Identify all the variables referenced and defined here
141
- * - Identify is the 'this' keyword is used anywhere
142
- *
143
- * @param o
144
- * @param p
145
- * @returns
146
- */
147
- const fnTraverser = (o, p) => {
148
- if (
149
- o.type == "Identifier" &&
150
- !reservedIdentifiers.has(o.name) &&
151
- !this.options.globalVariables.has(o.name)
152
- ) {
153
- var info = getIdentifierInfo(o, p);
154
- if (!info.spec.isReferenced) {
155
- return;
156
- }
157
- if (info.spec.isDefined && getDefiningContext(o, p) === object) {
158
- defined.add(o.name);
159
- } else {
160
- referenced.add(o.name);
160
+ } else {
161
+ // This approach is dirty and does not account for hoisted FunctionDeclarations
162
+ var isDefinedAbove = false;
163
+ for (var pNode of p) {
164
+ if (definedMap.has(pNode)) {
165
+ if (definedMap.get(pNode).has(o.name)) {
166
+ isDefinedAbove = true;
167
+ break;
168
+ }
161
169
  }
162
170
  }
163
171
 
164
- if (o.type == "ThisExpression" || o.type == "Super") {
165
- isBound = true;
166
- }
167
- };
168
-
169
- walk(object.params, [object, ...parents], fnTraverser);
170
- walk(object.body, [object, ...parents], fnTraverser);
171
-
172
- if (!isBound) {
173
- defined.forEach((identifier) => {
174
- referenced.delete(identifier);
175
- });
176
-
177
- object.params.forEach((param) => {
178
- referenced.delete(param.name);
179
- });
180
-
181
- collect.push({
182
- location: [object, parents],
183
- references: referenced,
184
- name: object.id?.name,
185
- });
186
- }
187
- }
188
- });
189
-
190
- if (!collect.length) {
191
- return;
192
- }
193
-
194
- var miss = 0;
195
- var start = collect.length * 2;
196
-
197
- while (true) {
198
- var hit = false;
199
-
200
- collect.forEach(
201
- ({ name, references: references1, location: location1 }) => {
202
- if (!references1.size && name) {
203
- collect.forEach((o) => {
204
- if (
205
- o.location[0] !== location1[0] &&
206
- o.references.size &&
207
- o.references.delete(name)
208
- ) {
209
- // console.log(collect);
172
+ if (!isDefinedAbove) {
173
+ isReferenceLess = false;
174
+ identifierPreventingTransformation = o.name;
210
175
 
211
- hit = true;
212
- }
213
- });
176
+ return "EXIT";
214
177
  }
215
178
  }
216
- );
217
- if (hit) {
218
- miss = 0;
219
- } else {
220
- miss++;
221
179
  }
180
+ });
222
181
 
223
- if (miss > start) {
224
- break;
182
+ // This function is not 'reference-less', cannot be RGF'd
183
+ if (!isReferenceLess) {
184
+ if (object.id) {
185
+ this.log(
186
+ `${object?.id?.name}() cannot be transformed because of ${identifierPreventingTransformation}`
187
+ );
225
188
  }
189
+ return;
226
190
  }
227
191
 
228
- queue = [];
229
- collect.forEach((o) => {
230
- if (!o.references.size) {
231
- var [object, parents] = o.location;
232
-
233
- queue.push([object, parents]);
234
- if (
235
- object.type == "FunctionDeclaration" &&
236
- typeof object.id.name === "string"
237
- ) {
238
- var index = names.size;
239
-
240
- names.set(object.id.name, index);
241
- referenceSignatures[index] = getRandomString(10);
242
-
243
- definingNodes.set(object.id.name, object.id);
244
- }
245
- }
192
+ // Since `new Function` is completely isolated, create an entire new obfuscator and run remaining transformations.
193
+ // RGF runs early and needs completed code before converting to a string.
194
+ // (^ the variables haven't been renamed yet)
195
+ var obfuscator = new Obfuscator({
196
+ ...this.options,
197
+ stringEncoding: false,
198
+ compact: true,
246
199
  });
247
200
 
248
- if (!queue.length) {
249
- return;
201
+ if (obfuscator.options.lock) {
202
+ delete obfuscator.options.lock.countermeasures;
250
203
  }
251
204
 
252
- // An array containing all the function declarations
253
- var referenceArray = "_" + getRandomString(10);
254
-
255
- walk(contextObject, contextParents, (o, p) => {
256
- if (o.type == "Identifier" && !reservedIdentifiers.has(o.name)) {
257
- var index = names.get(o.name);
258
- if (typeof index === "number") {
259
- var info = getIdentifierInfo(o, p);
260
- if (info.spec.isReferenced && !info.spec.isDefined) {
261
- var location = getDefiningIdentifier(o, p);
262
- if (location) {
263
- var pointingTo = location[0];
264
- var shouldBe = definingNodes.get(o.name);
265
-
266
- // console.log(pointingTo, shouldBe);
267
-
268
- if (pointingTo == shouldBe) {
269
- this.log(o.name, "->", `${referenceArray}[${index}]`);
270
-
271
- var memberExpression = MemberExpression(
272
- Identifier(referenceArray),
273
- Literal(index),
274
- true
275
- );
276
-
277
- // Allow re-assignment to the RGF function
278
- if (
279
- p[0] &&
280
- p[0].type === "AssignmentExpression" &&
281
- p[0].left === o
282
- ) {
283
- // fn = ...
284
-
285
- this.replace(o, memberExpression);
286
- } else {
287
- // fn()
288
- // fn
289
-
290
- // In most cases the identifier is being used like this (call expression, or referenced to be called later)
291
- // Replace it with a simple wrapper function that will pass on the reference array
292
-
293
- var conditionalExpression = ConditionalExpression(
294
- Template(
295
- `typeof ${referenceArray}[${index}] === "function" && ${referenceArray}[${index}]["${
296
- referenceSignatures[index] || "_"
297
- }"]`
298
- ).single().expression,
299
- FunctionExpression(
300
- [],
301
- [
302
- ReturnStatement(
303
- // clone() is required!
304
- CallExpression(clone(memberExpression), [
305
- Identifier(referenceArray),
306
- SpreadElement(Identifier("arguments")),
307
- ])
308
- ),
309
- ]
310
- ),
311
- clone(memberExpression)
312
- );
313
-
314
- this.replace(o, conditionalExpression);
315
- }
316
- }
317
- }
318
- }
319
- }
320
- }
205
+ var transforms = obfuscator.array.filter(
206
+ (x) => x.priority > this.priority
207
+ );
208
+
209
+ var embeddedFunctionName = this.getPlaceholder();
210
+
211
+ var embeddedFunction = {
212
+ type: "FunctionDeclaration",
213
+ id: Identifier(embeddedFunctionName),
214
+ body: BlockStatement([...object.body.body]),
215
+ params: object.params,
216
+ async: false,
217
+ generator: false,
218
+ };
219
+
220
+ var tree = {
221
+ type: "Program",
222
+ body: [
223
+ embeddedFunction,
224
+ ReturnStatement(
225
+ CallExpression(
226
+ MemberExpression(
227
+ Identifier(embeddedFunctionName),
228
+ Literal("apply"),
229
+ true
230
+ ),
231
+ [ThisExpression(), Identifier("arguments")]
232
+ )
233
+ ),
234
+ ],
235
+ };
236
+
237
+ transforms.forEach((transform) => {
238
+ transform.apply(tree);
321
239
  });
322
240
 
323
- var arrayExpression = ArrayExpression([]);
324
- var variableDeclaration = VariableDeclaration([
325
- VariableDeclarator(Identifier(referenceArray), arrayExpression),
326
- ]);
327
-
328
- prepend(contextObject, variableDeclaration);
329
-
330
- queue.forEach(([object, parents]) => {
331
- var name = object?.id?.name;
332
- var signature = referenceSignatures[names.get(name)];
333
-
334
- var embeddedName = name || this.getPlaceholder();
335
-
336
- // Since `new Function` is completely isolated, create an entire new obfuscator and run remaining transformations.
337
- // RGF runs early and needs completed code before converting to a string.
338
- // (^ the variables haven't been renamed yet)
339
- var obfuscator = new Obfuscator({
340
- ...this.options,
341
- rgf: false,
342
- globalVariables: new Set([
343
- ...this.options.globalVariables,
344
- referenceArray,
345
- ]),
346
- lock: {
347
- integrity: false,
348
- },
349
- eval: false,
350
- stringEncoding: false,
351
- });
352
- var transforms = obfuscator.array.filter(
353
- (x) => x.priority > this.priority
354
- );
355
-
356
- var embeddedFunction = {
357
- ...object,
358
- type: "FunctionDeclaration",
359
- id: Identifier(embeddedName),
360
- };
361
-
362
- var tree = {
363
- type: "Program",
364
- body: [
365
- embeddedFunction,
366
- ReturnStatement(
367
- CallExpression(
368
- MemberExpression(
369
- Identifier(embeddedName),
370
- Literal("call"),
371
- true
372
- ),
373
- [
374
- Identifier("undefined"),
375
- SpreadElement(
376
- Template(
377
- `Array.prototype.slice.call(arguments, 1)`
378
- ).single().expression
379
- ),
380
- ]
381
- )
382
- ),
383
- ],
384
- };
385
-
386
- (tree as any).__hiddenDeclarations = VariableDeclaration(
387
- VariableDeclarator(referenceArray)
388
- );
389
- (tree as any).__hiddenDeclarations.hidden = true;
390
- (tree as any).__hiddenDeclarations.declarations[0].id.hidden = true;
391
-
392
- transforms.forEach((transform) => {
393
- transform.apply(tree);
394
- });
395
-
396
- // Find eval callbacks
397
- traverse(tree, (o, p) => {
398
- if (o.$eval) {
399
- return () => {
400
- o.$eval(o, p);
401
- };
402
- }
403
- });
241
+ var toString = compileJsSync(tree, obfuscator.options);
404
242
 
405
- var toString = compileJsSync(tree, this.options);
243
+ // new Function(code)
244
+ var newFunctionExpression = NewExpression(Identifier("Function"), [
245
+ Literal(toString),
246
+ ]);
406
247
 
407
- var newFunction = NewExpression(Identifier("Function"), [
408
- Literal(referenceArray),
409
- Literal(toString),
248
+ // The index where this function is placed in the array
249
+ var newFunctionExpressionIndex = this.arrayExpressionElements.length;
250
+
251
+ // Add it to the array
252
+ this.arrayExpressionElements.push(newFunctionExpression);
253
+
254
+ // The member expression to retrieve this function
255
+ var memberExpression = MemberExpression(
256
+ Identifier(this.arrayExpressionName),
257
+ Literal(newFunctionExpressionIndex),
258
+ true
259
+ );
260
+
261
+ // Replace based on type
262
+
263
+ // (1) Function Declaration:
264
+ // - Replace body with call to new function
265
+ if (object.type === "FunctionDeclaration") {
266
+ object.body = BlockStatement([
267
+ ReturnStatement(
268
+ CallExpression(
269
+ MemberExpression(memberExpression, Literal("apply"), true),
270
+ [ThisExpression(), Identifier("arguments")]
271
+ )
272
+ ),
410
273
  ]);
411
274
 
412
- function applySignature(fn) {
413
- if (!signature) {
414
- return fn;
415
- }
416
-
417
- // This code marks the function object with a unique property
418
- return CallExpression(
419
- FunctionExpression(
420
- [],
421
- [
422
- VariableDeclaration(VariableDeclarator("fn", fn)),
423
- ExpressionStatement(
424
- AssignmentExpression(
425
- "=",
426
- MemberExpression(
427
- Identifier("fn"),
428
- Literal(signature),
429
- true
430
- ),
431
- Literal(true)
432
- )
433
- ),
434
- ReturnStatement(Identifier("fn")),
435
- ]
436
- ),
437
- []
438
- );
439
- }
440
-
441
- if (object.type === "FunctionDeclaration") {
442
- arrayExpression.elements[names.get(name)] =
443
- applySignature(newFunction);
444
-
445
- if (Array.isArray(parents[0])) {
446
- parents[0].splice(parents[0].indexOf(object), 1);
447
- } else {
448
- this.error(
449
- new Error(
450
- "Error deleting function declaration: " +
451
- parents.map((x) => x.type).join(",")
452
- )
453
- );
454
- }
455
- } else {
456
- // The wrapper function passes the reference array around
457
- var wrapperFunction = FunctionExpression(
458
- [],
459
- [
460
- ReturnStatement(
461
- CallExpression(
462
- MemberExpression(newFunction, Literal("call"), true),
463
- [
464
- Identifier("undefined"),
465
- Identifier(referenceArray),
466
- SpreadElement(Identifier("arguments")),
467
- ]
468
- )
469
- ),
470
- ]
471
- );
275
+ // The parameters are no longer needed ('arguments' is used to capture them)
276
+ object.params = [];
277
+ return;
278
+ }
472
279
 
473
- this.replace(object, applySignature(wrapperFunction));
474
- }
475
- });
280
+ // (2) Function Expression:
281
+ // - Replace expression with member expression pointing to new function
282
+ if (object.type === "FunctionExpression") {
283
+ this.replace(object, memberExpression);
284
+ return;
285
+ }
476
286
  };
477
287
  }
478
288
  }