js-confuser 2.0.0-alpha.5 → 2.0.1

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 (113) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +43 -43
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +20 -20
  3. package/.github/workflows/node.js.yml +28 -28
  4. package/.prettierrc +4 -4
  5. package/CHANGELOG.md +1015 -989
  6. package/CODE_OF_CONDUCT.md +131 -131
  7. package/CONTRIBUTING.md +52 -52
  8. package/LICENSE +21 -21
  9. package/Migration.md +72 -71
  10. package/README.md +86 -78
  11. package/dist/constants.js +43 -43
  12. package/dist/index.js +14 -23
  13. package/dist/obfuscator.js +31 -25
  14. package/dist/order.js +4 -4
  15. package/dist/presets.js +31 -31
  16. package/dist/templates/integrityTemplate.js +4 -4
  17. package/dist/templates/template.js +1 -2
  18. package/dist/transforms/astScrambler.js +1 -2
  19. package/dist/transforms/calculator.js +1 -2
  20. package/dist/transforms/controlFlowFlattening.js +93 -63
  21. package/dist/transforms/deadCode.js +1 -2
  22. package/dist/transforms/dispatcher.js +4 -5
  23. package/dist/transforms/extraction/duplicateLiteralsRemoval.js +1 -2
  24. package/dist/transforms/extraction/objectExtraction.js +1 -2
  25. package/dist/transforms/finalizer.js +1 -2
  26. package/dist/transforms/flatten.js +1 -2
  27. package/dist/transforms/identifier/globalConcealing.js +15 -2
  28. package/dist/transforms/identifier/movedDeclarations.js +8 -7
  29. package/dist/transforms/identifier/renameVariables.js +7 -7
  30. package/dist/transforms/lock/integrity.js +11 -10
  31. package/dist/transforms/lock/lock.js +2 -3
  32. package/dist/transforms/minify.js +11 -29
  33. package/dist/transforms/opaquePredicates.js +1 -2
  34. package/dist/transforms/pack.js +5 -2
  35. package/dist/transforms/plugin.js +18 -19
  36. package/dist/transforms/preparation.js +16 -16
  37. package/dist/transforms/renameLabels.js +1 -2
  38. package/dist/transforms/rgf.js +8 -9
  39. package/dist/transforms/shuffle.js +1 -2
  40. package/dist/transforms/string/encoding.js +1 -2
  41. package/dist/transforms/string/stringCompression.js +3 -4
  42. package/dist/transforms/string/stringConcealing.js +8 -3
  43. package/dist/transforms/string/stringEncoding.js +1 -2
  44. package/dist/transforms/variableMasking.js +1 -2
  45. package/dist/utils/NameGen.js +2 -2
  46. package/dist/utils/PredicateGen.js +1 -2
  47. package/dist/utils/ast-utils.js +87 -88
  48. package/dist/utils/function-utils.js +8 -8
  49. package/dist/utils/node.js +5 -6
  50. package/dist/utils/object-utils.js +4 -4
  51. package/dist/utils/random-utils.js +20 -20
  52. package/dist/utils/static-utils.js +1 -2
  53. package/dist/validateOptions.js +4 -7
  54. package/index.d.ts +17 -17
  55. package/package.json +61 -59
  56. package/src/constants.ts +168 -168
  57. package/src/index.ts +118 -118
  58. package/src/obfuscationResult.ts +49 -49
  59. package/src/obfuscator.ts +501 -497
  60. package/src/options.ts +407 -407
  61. package/src/order.ts +54 -54
  62. package/src/presets.ts +125 -125
  63. package/src/templates/bufferToStringTemplate.ts +57 -57
  64. package/src/templates/deadCodeTemplates.ts +1185 -1185
  65. package/src/templates/getGlobalTemplate.ts +76 -76
  66. package/src/templates/integrityTemplate.ts +64 -64
  67. package/src/templates/setFunctionLengthTemplate.ts +11 -11
  68. package/src/templates/stringCompressionTemplate.ts +20 -20
  69. package/src/templates/tamperProtectionTemplates.ts +120 -120
  70. package/src/templates/template.ts +224 -224
  71. package/src/transforms/astScrambler.ts +99 -99
  72. package/src/transforms/calculator.ts +99 -99
  73. package/src/transforms/controlFlowFlattening.ts +1716 -1664
  74. package/src/transforms/deadCode.ts +82 -82
  75. package/src/transforms/dispatcher.ts +450 -450
  76. package/src/transforms/extraction/duplicateLiteralsRemoval.ts +156 -158
  77. package/src/transforms/extraction/objectExtraction.ts +186 -186
  78. package/src/transforms/finalizer.ts +74 -74
  79. package/src/transforms/flatten.ts +421 -420
  80. package/src/transforms/identifier/globalConcealing.ts +315 -295
  81. package/src/transforms/identifier/movedDeclarations.ts +252 -251
  82. package/src/transforms/identifier/renameVariables.ts +328 -321
  83. package/src/transforms/lock/integrity.ts +117 -114
  84. package/src/transforms/lock/lock.ts +418 -425
  85. package/src/transforms/minify.ts +615 -629
  86. package/src/transforms/opaquePredicates.ts +100 -100
  87. package/src/transforms/pack.ts +239 -231
  88. package/src/transforms/plugin.ts +173 -173
  89. package/src/transforms/preparation.ts +349 -347
  90. package/src/transforms/renameLabels.ts +175 -175
  91. package/src/transforms/rgf.ts +322 -322
  92. package/src/transforms/shuffle.ts +82 -82
  93. package/src/transforms/string/encoding.ts +144 -144
  94. package/src/transforms/string/stringCompression.ts +128 -128
  95. package/src/transforms/string/stringConcealing.ts +312 -298
  96. package/src/transforms/string/stringEncoding.ts +80 -80
  97. package/src/transforms/string/stringSplitting.ts +77 -77
  98. package/src/transforms/variableMasking.ts +257 -257
  99. package/src/utils/IntGen.ts +33 -33
  100. package/src/utils/NameGen.ts +116 -116
  101. package/src/utils/PredicateGen.ts +61 -61
  102. package/src/utils/ast-utils.ts +663 -663
  103. package/src/utils/function-utils.ts +50 -50
  104. package/src/utils/gen-utils.ts +48 -48
  105. package/src/utils/node.ts +78 -78
  106. package/src/utils/object-utils.ts +21 -21
  107. package/src/utils/random-utils.ts +93 -93
  108. package/src/utils/static-utils.ts +66 -66
  109. package/src/validateOptions.ts +256 -259
  110. package/tsconfig.json +13 -14
  111. package/dist/probability.js +0 -1
  112. package/dist/transforms/functionOutlining.js +0 -230
  113. package/dist/utils/ControlObject.js +0 -125
@@ -1,420 +1,421 @@
1
- import * as t from "@babel/types";
2
- import { NodePath } from "@babel/traverse";
3
- import {
4
- ensureComputedExpression,
5
- getFunctionName,
6
- isDefiningIdentifier,
7
- isModifiedIdentifier,
8
- isStrictMode,
9
- isVariableIdentifier,
10
- prepend,
11
- prependProgram,
12
- } from "../utils/ast-utils";
13
- import { PluginArg, PluginObject } from "./plugin";
14
- import { Order } from "../order";
15
- import { NodeSymbol, PREDICTABLE, UNSAFE } from "../constants";
16
- import {
17
- computeFunctionLength,
18
- isVariableFunctionIdentifier,
19
- } from "../utils/function-utils";
20
- import { ok } from "assert";
21
- import { Scope } from "@babel/traverse";
22
- import { NameGen } from "../utils/NameGen";
23
-
24
- export default ({ Plugin }: PluginArg): PluginObject => {
25
- const me = Plugin(Order.Flatten, {
26
- changeData: {
27
- functions: 0,
28
- },
29
- });
30
- const isDebug = false;
31
-
32
- function flattenFunction(fnPath: NodePath<t.Function>) {
33
- // Skip if already processed
34
- if (me.isSkipped(fnPath)) return;
35
-
36
- // Don't apply to generator functions
37
- if (fnPath.node.generator) return;
38
-
39
- // Skip getter/setter methods
40
- if (fnPath.isObjectMethod() || fnPath.isClassMethod()) {
41
- if (fnPath.node.kind !== "method") return;
42
- }
43
-
44
- // Do not apply to arrow functions
45
- if (t.isArrowFunctionExpression(fnPath.node)) return;
46
- if (!t.isBlockStatement(fnPath.node.body)) return;
47
-
48
- // Skip if marked as unsafe
49
- if ((fnPath.node as NodeSymbol)[UNSAFE]) return;
50
-
51
- var program = fnPath.findParent((p) =>
52
- p.isProgram()
53
- ) as NodePath<t.Program>;
54
-
55
- let functionName = getFunctionName(fnPath);
56
- if (!t.isValidIdentifier(functionName, true)) {
57
- functionName = "anonymous";
58
- }
59
-
60
- if (!me.computeProbabilityMap(me.options.flatten, functionName)) {
61
- return;
62
- }
63
-
64
- const strictMode = fnPath.find((path) => isStrictMode(path));
65
- if (strictMode === fnPath) return;
66
-
67
- me.log("Transforming", functionName);
68
-
69
- const flatObjectName = `${me.getPlaceholder()}_flat_object`;
70
- const newFnName = `${me.getPlaceholder()}_flat_${functionName}`;
71
-
72
- const nameGen = new NameGen(me.options.identifierGenerator);
73
-
74
- function generateProp(originalName: string, type: string) {
75
- var newPropertyName: string;
76
- do {
77
- newPropertyName = isDebug
78
- ? type + "_" + originalName
79
- : nameGen.generate();
80
- } while (allPropertyNames.has(newPropertyName));
81
-
82
- allPropertyNames.add(newPropertyName);
83
-
84
- return newPropertyName;
85
- }
86
-
87
- const standardProps = new Map<string, string>();
88
- const setterPropsNeeded = new Set<string>();
89
- const typeofProps = new Map<string, string>();
90
- const functionCallProps = new Map<string, string>();
91
- const allPropertyNames = new Set();
92
-
93
- const identifierPaths: NodePath<t.Identifier>[] = [];
94
-
95
- // Traverse function to identify variables to be replaced with flat object properties
96
- fnPath.traverse({
97
- Identifier: {
98
- exit(identifierPath) {
99
- if (!isVariableIdentifier(identifierPath)) return;
100
-
101
- if (
102
- identifierPath.isBindingIdentifier() &&
103
- isDefiningIdentifier(identifierPath)
104
- )
105
- return;
106
-
107
- if (isVariableFunctionIdentifier(identifierPath)) return;
108
-
109
- if ((identifierPath.node as NodeSymbol)[UNSAFE]) return;
110
- const identifierName = identifierPath.node.name;
111
-
112
- if (identifierName === "arguments") return;
113
-
114
- var binding = identifierPath.scope.getBinding(identifierName);
115
- if (!binding) {
116
- return;
117
- }
118
-
119
- var isOutsideVariable =
120
- fnPath.scope.parent.getBinding(identifierName) === binding;
121
-
122
- if (!isOutsideVariable) {
123
- return;
124
- }
125
-
126
- identifierPaths.push(identifierPath);
127
- },
128
- },
129
- });
130
-
131
- me.log(
132
- `Function ${functionName}`,
133
- "requires",
134
- Array.from(new Set(identifierPaths.map((x) => x.node.name)))
135
- );
136
-
137
- for (var identifierPath of identifierPaths) {
138
- const identifierName = identifierPath.node.name;
139
- if (typeof identifierName !== "string") continue;
140
-
141
- const isTypeof = identifierPath.parentPath.isUnaryExpression({
142
- operator: "typeof",
143
- });
144
- const isFunctionCall =
145
- identifierPath.parentPath.isCallExpression() &&
146
- identifierPath.parentPath.node.callee === identifierPath.node;
147
-
148
- if (isTypeof) {
149
- var typeofProp = typeofProps.get(identifierName);
150
- if (!typeofProp) {
151
- typeofProp = generateProp(identifierName, "typeof");
152
- typeofProps.set(identifierName, typeofProp);
153
- }
154
-
155
- ensureComputedExpression(identifierPath.parentPath);
156
-
157
- identifierPath.parentPath
158
- .replaceWith(
159
- t.memberExpression(
160
- t.identifier(flatObjectName),
161
- t.stringLiteral(typeofProp),
162
- true
163
- )
164
- )[0]
165
- .skip();
166
- } else if (isFunctionCall) {
167
- let functionCallProp = functionCallProps.get(identifierName);
168
- if (!functionCallProp) {
169
- functionCallProp = generateProp(identifierName, "call");
170
- functionCallProps.set(identifierName, functionCallProp);
171
- }
172
-
173
- ensureComputedExpression(identifierPath);
174
-
175
- // Replace identifier with a reference to the flat object property
176
- identifierPath
177
- .replaceWith(
178
- t.memberExpression(
179
- t.identifier(flatObjectName),
180
- t.stringLiteral(functionCallProp),
181
- true
182
- )
183
- )[0]
184
- .skip();
185
- } else {
186
- let standardProp = standardProps.get(identifierName);
187
- if (!standardProp) {
188
- standardProp = generateProp(identifierName, "standard");
189
- standardProps.set(identifierName, standardProp);
190
- }
191
-
192
- if (!setterPropsNeeded.has(identifierName)) {
193
- // Only provide 'set' method if the variable is modified
194
- var isModification = isModifiedIdentifier(identifierPath);
195
-
196
- if (isModification) {
197
- setterPropsNeeded.add(identifierName);
198
- }
199
- }
200
-
201
- ensureComputedExpression(identifierPath);
202
-
203
- // Replace identifier with a reference to the flat object property
204
- identifierPath
205
- .replaceWith(
206
- t.memberExpression(
207
- t.identifier(flatObjectName),
208
- t.stringLiteral(standardProp),
209
- true
210
- )
211
- )[0]
212
- .skip();
213
- }
214
- }
215
-
216
- // for (const prop of [...typeofProps.keys(), ...functionCallProps.keys()]) {
217
- // if (!standardProps.has(prop)) {
218
- // standardProps.set(prop, generateProp());
219
- // }
220
- // }
221
-
222
- const flatObjectProperties: t.ObjectMember[] = [];
223
-
224
- for (var entry of standardProps) {
225
- const [identifierName, objectProp] = entry;
226
-
227
- flatObjectProperties.push(
228
- me.skip(
229
- t.objectMethod(
230
- "get",
231
- t.stringLiteral(objectProp),
232
- [],
233
- t.blockStatement([t.returnStatement(t.identifier(identifierName))]),
234
- false,
235
- false,
236
- false
237
- )
238
- )
239
- );
240
-
241
- // Not all properties need a setter
242
- if (setterPropsNeeded.has(identifierName)) {
243
- var valueArgName = me.getPlaceholder() + "_value";
244
- flatObjectProperties.push(
245
- me.skip(
246
- t.objectMethod(
247
- "set",
248
- t.stringLiteral(objectProp),
249
- [t.identifier(valueArgName)],
250
- t.blockStatement([
251
- t.expressionStatement(
252
- t.assignmentExpression(
253
- "=",
254
- t.identifier(identifierName),
255
- t.identifier(valueArgName)
256
- )
257
- ),
258
- ]),
259
- false,
260
- false,
261
- false
262
- )
263
- )
264
- );
265
- }
266
- }
267
-
268
- for (const entry of typeofProps) {
269
- const [identifierName, objectProp] = entry;
270
-
271
- flatObjectProperties.push(
272
- me.skip(
273
- t.objectMethod(
274
- "get",
275
- t.stringLiteral(objectProp),
276
- [],
277
- t.blockStatement([
278
- t.returnStatement(
279
- t.unaryExpression("typeof", t.identifier(identifierName))
280
- ),
281
- ]),
282
- false,
283
- false,
284
- false
285
- )
286
- )
287
- );
288
- }
289
-
290
- for (const entry of functionCallProps) {
291
- const [identifierName, objectProp] = entry;
292
-
293
- flatObjectProperties.push(
294
- me.skip(
295
- t.objectMethod(
296
- "method",
297
- t.stringLiteral(objectProp),
298
- [t.restElement(t.identifier("args"))],
299
- t.blockStatement([
300
- t.returnStatement(
301
- t.callExpression(t.identifier(identifierName), [
302
- t.spreadElement(t.identifier("args")),
303
- ])
304
- ),
305
- ]),
306
- false,
307
- false,
308
- false
309
- )
310
- )
311
- );
312
- }
313
-
314
- // Create the new flattened function
315
- const flattenedFunctionDeclaration = t.functionDeclaration(
316
- t.identifier(newFnName),
317
- [t.arrayPattern([...fnPath.node.params]), t.identifier(flatObjectName)],
318
- t.blockStatement([...[...fnPath.node.body.body]]),
319
- false,
320
- fnPath.node.async
321
- );
322
-
323
- // Create the flat object variable declaration
324
- const flatObjectDeclaration = t.variableDeclaration("var", [
325
- t.variableDeclarator(
326
- t.identifier(flatObjectName),
327
- t.objectExpression(flatObjectProperties)
328
- ),
329
- ]);
330
-
331
- var argName = me.getPlaceholder() + "_args";
332
-
333
- // Replace original function body with a call to the flattened function
334
- fnPath.node.body = t.blockStatement([
335
- flatObjectDeclaration,
336
- t.returnStatement(
337
- t.callExpression(t.identifier(newFnName), [
338
- t.identifier(argName),
339
- t.identifier(flatObjectName),
340
- ])
341
- ),
342
- ]);
343
-
344
- const originalLength = computeFunctionLength(fnPath);
345
- fnPath.node.params = [t.restElement(t.identifier(argName))];
346
-
347
- // Ensure updated parameter gets registered in the function scope
348
- fnPath.scope.crawl();
349
- fnPath.skip();
350
-
351
- // Add the new flattened function at the top level
352
- var newPath = prependProgram(
353
- program,
354
- flattenedFunctionDeclaration
355
- )[0] as NodePath<t.FunctionDeclaration>;
356
-
357
- me.skip(newPath);
358
-
359
- // Copy over all properties except the predictable flag
360
- for (var symbol of Object.getOwnPropertySymbols(fnPath.node)) {
361
- if (symbol !== PREDICTABLE) {
362
- newPath.node[symbol] = fnPath.node[symbol];
363
- }
364
- }
365
-
366
- // Old function is no longer predictable (rest element parameter)
367
- (fnPath.node as NodeSymbol)[PREDICTABLE] = false;
368
- // Old function is unsafe (uses arguments, this)
369
- (fnPath.node as NodeSymbol)[UNSAFE] = true;
370
-
371
- newPath.node[PREDICTABLE] = true;
372
-
373
- // Carry over 'use strict' directive if not already present
374
- if (strictMode) {
375
- newPath.node.body.directives.push(
376
- t.directive(t.directiveLiteral("use strict"))
377
- );
378
-
379
- // Non-simple parameter list conversion
380
- prepend(
381
- newPath,
382
- t.variableDeclaration("var", [
383
- t.variableDeclarator(
384
- t.arrayPattern(newPath.node.params),
385
- t.identifier("arguments")
386
- ),
387
- ])
388
- );
389
- newPath.node.params = [];
390
- // Using 'arguments' is unsafe
391
- (newPath.node as NodeSymbol)[UNSAFE] = true;
392
- // Params changed and using 'arguments'
393
- (newPath.node as NodeSymbol)[PREDICTABLE] = false;
394
- }
395
-
396
- // Ensure parameters are registered in the new function scope
397
- newPath.scope.crawl();
398
-
399
- newPath.skip();
400
- me.skip(newPath);
401
-
402
- // Set function length
403
- me.setFunctionLength(fnPath, originalLength);
404
-
405
- me.changeData.functions++;
406
- }
407
-
408
- return {
409
- visitor: {
410
- Function: {
411
- exit(path: NodePath<t.Function>) {
412
- flattenFunction(path);
413
- },
414
- },
415
- Program(path) {
416
- path.scope.crawl();
417
- },
418
- },
419
- };
420
- };
1
+ import * as t from "@babel/types";
2
+ import { NodePath } from "@babel/traverse";
3
+ import {
4
+ ensureComputedExpression,
5
+ getFunctionName,
6
+ isDefiningIdentifier,
7
+ isModifiedIdentifier,
8
+ isStrictMode,
9
+ isVariableIdentifier,
10
+ prepend,
11
+ prependProgram,
12
+ } from "../utils/ast-utils";
13
+ import { PluginArg, PluginObject } from "./plugin";
14
+ import { Order } from "../order";
15
+ import { NodeSymbol, PREDICTABLE, UNSAFE } from "../constants";
16
+ import {
17
+ computeFunctionLength,
18
+ isVariableFunctionIdentifier,
19
+ } from "../utils/function-utils";
20
+ import { NameGen } from "../utils/NameGen";
21
+
22
+ export default ({ Plugin }: PluginArg): PluginObject => {
23
+ const me = Plugin(Order.Flatten, {
24
+ changeData: {
25
+ functions: 0,
26
+ },
27
+ });
28
+ const isDebug = false;
29
+
30
+ function flattenFunction(fnPath: NodePath<t.Function>) {
31
+ // Skip if already processed
32
+ if (me.isSkipped(fnPath)) return;
33
+
34
+ // Don't apply to generator functions
35
+ if (fnPath.node.generator) return;
36
+
37
+ // Skip getter/setter methods
38
+ if (fnPath.isObjectMethod() || fnPath.isClassMethod()) {
39
+ if (fnPath.node.kind !== "method") return;
40
+ }
41
+
42
+ // Do not apply to arrow functions
43
+ if (t.isArrowFunctionExpression(fnPath.node)) return;
44
+ if (!t.isBlockStatement(fnPath.node.body)) return;
45
+
46
+ // Skip if marked as unsafe
47
+ if ((fnPath.node as NodeSymbol)[UNSAFE]) return;
48
+
49
+ var program = fnPath.findParent((p) =>
50
+ p.isProgram()
51
+ ) as NodePath<t.Program>;
52
+
53
+ let functionName = getFunctionName(fnPath);
54
+ if (!t.isValidIdentifier(functionName, true)) {
55
+ functionName = "anonymous";
56
+ }
57
+
58
+ if (!me.computeProbabilityMap(me.options.flatten, functionName)) {
59
+ return;
60
+ }
61
+
62
+ const strictMode = fnPath.find((path) => isStrictMode(path));
63
+ if (strictMode === fnPath) return;
64
+
65
+ me.log("Transforming", functionName);
66
+
67
+ const flatObjectName = `${me.getPlaceholder()}_flat_object`;
68
+ const newFnName = `${me.getPlaceholder()}_flat_${functionName}`;
69
+
70
+ const nameGen = new NameGen(me.options.identifierGenerator);
71
+
72
+ function generateProp(originalName: string, type: string) {
73
+ var newPropertyName: string;
74
+ do {
75
+ newPropertyName = isDebug
76
+ ? type + "_" + originalName
77
+ : nameGen.generate();
78
+ } while (allPropertyNames.has(newPropertyName));
79
+
80
+ allPropertyNames.add(newPropertyName);
81
+
82
+ return newPropertyName;
83
+ }
84
+
85
+ const standardProps = new Map<string, string>();
86
+ const setterPropsNeeded = new Set<string>();
87
+ const typeofProps = new Map<string, string>();
88
+ const functionCallProps = new Map<string, string>();
89
+ const allPropertyNames = new Set();
90
+
91
+ const identifierPaths: NodePath<t.Identifier>[] = [];
92
+
93
+ // Traverse function to identify variables to be replaced with flat object properties
94
+ fnPath.traverse({
95
+ Identifier: {
96
+ exit(identifierPath) {
97
+ if (!isVariableIdentifier(identifierPath)) return;
98
+
99
+ if (
100
+ identifierPath.isBindingIdentifier() &&
101
+ isDefiningIdentifier(identifierPath)
102
+ )
103
+ return;
104
+
105
+ if (isVariableFunctionIdentifier(identifierPath)) return;
106
+
107
+ if ((identifierPath.node as NodeSymbol)[UNSAFE]) return;
108
+ const identifierName = identifierPath.node.name;
109
+
110
+ if (identifierName === "arguments") return;
111
+
112
+ var binding = identifierPath.scope.getBinding(identifierName);
113
+ if (!binding) {
114
+ return;
115
+ }
116
+
117
+ var isOutsideVariable =
118
+ fnPath.scope.parent.getBinding(identifierName) === binding;
119
+
120
+ if (!isOutsideVariable) {
121
+ return;
122
+ }
123
+
124
+ identifierPaths.push(identifierPath);
125
+ },
126
+ },
127
+ });
128
+
129
+ me.log(
130
+ `Function ${functionName}`,
131
+ "requires",
132
+ Array.from(new Set(identifierPaths.map((x) => x.node.name)))
133
+ );
134
+
135
+ for (var identifierPath of identifierPaths) {
136
+ const identifierName = identifierPath.node.name;
137
+ if (typeof identifierName !== "string") continue;
138
+
139
+ const isTypeof = identifierPath.parentPath.isUnaryExpression({
140
+ operator: "typeof",
141
+ });
142
+ const isFunctionCall =
143
+ identifierPath.parentPath.isCallExpression() &&
144
+ identifierPath.parentPath.node.callee === identifierPath.node;
145
+
146
+ if (isTypeof) {
147
+ var typeofProp = typeofProps.get(identifierName);
148
+ if (!typeofProp) {
149
+ typeofProp = generateProp(identifierName, "typeof");
150
+ typeofProps.set(identifierName, typeofProp);
151
+ }
152
+
153
+ ensureComputedExpression(identifierPath.parentPath);
154
+
155
+ identifierPath.parentPath
156
+ .replaceWith(
157
+ t.memberExpression(
158
+ t.identifier(flatObjectName),
159
+ t.stringLiteral(typeofProp),
160
+ true
161
+ )
162
+ )[0]
163
+ .skip();
164
+ } else if (isFunctionCall) {
165
+ let functionCallProp = functionCallProps.get(identifierName);
166
+ if (!functionCallProp) {
167
+ functionCallProp = generateProp(identifierName, "call");
168
+ functionCallProps.set(identifierName, functionCallProp);
169
+ }
170
+
171
+ ensureComputedExpression(identifierPath);
172
+
173
+ // Replace identifier with a reference to the flat object property
174
+ identifierPath
175
+ .replaceWith(
176
+ t.memberExpression(
177
+ t.identifier(flatObjectName),
178
+ t.stringLiteral(functionCallProp),
179
+ true
180
+ )
181
+ )[0]
182
+ .skip();
183
+ } else {
184
+ let standardProp = standardProps.get(identifierName);
185
+ if (!standardProp) {
186
+ standardProp = generateProp(identifierName, "standard");
187
+ standardProps.set(identifierName, standardProp);
188
+ }
189
+
190
+ if (!setterPropsNeeded.has(identifierName)) {
191
+ // Only provide 'set' method if the variable is modified
192
+ var isModification = isModifiedIdentifier(identifierPath);
193
+
194
+ if (isModification) {
195
+ setterPropsNeeded.add(identifierName);
196
+ }
197
+ }
198
+
199
+ ensureComputedExpression(identifierPath);
200
+
201
+ // Replace identifier with a reference to the flat object property
202
+ identifierPath
203
+ .replaceWith(
204
+ t.memberExpression(
205
+ t.identifier(flatObjectName),
206
+ t.stringLiteral(standardProp),
207
+ true
208
+ )
209
+ )[0]
210
+ .skip();
211
+ }
212
+ }
213
+
214
+ // for (const prop of [...typeofProps.keys(), ...functionCallProps.keys()]) {
215
+ // if (!standardProps.has(prop)) {
216
+ // standardProps.set(prop, generateProp());
217
+ // }
218
+ // }
219
+
220
+ const flatObjectProperties: t.ObjectMember[] = [];
221
+
222
+ for (var entry of standardProps) {
223
+ const [identifierName, objectProp] = entry;
224
+
225
+ flatObjectProperties.push(
226
+ me.skip(
227
+ t.objectMethod(
228
+ "get",
229
+ t.stringLiteral(objectProp),
230
+ [],
231
+ t.blockStatement([t.returnStatement(t.identifier(identifierName))]),
232
+ false,
233
+ false,
234
+ false
235
+ )
236
+ )
237
+ );
238
+
239
+ // Not all properties need a setter
240
+ if (setterPropsNeeded.has(identifierName)) {
241
+ var valueArgName = me.getPlaceholder() + "_value";
242
+ flatObjectProperties.push(
243
+ me.skip(
244
+ t.objectMethod(
245
+ "set",
246
+ t.stringLiteral(objectProp),
247
+ [t.identifier(valueArgName)],
248
+ t.blockStatement([
249
+ t.expressionStatement(
250
+ t.assignmentExpression(
251
+ "=",
252
+ t.identifier(identifierName),
253
+ t.identifier(valueArgName)
254
+ )
255
+ ),
256
+ ]),
257
+ false,
258
+ false,
259
+ false
260
+ )
261
+ )
262
+ );
263
+ }
264
+ }
265
+
266
+ for (const entry of typeofProps) {
267
+ const [identifierName, objectProp] = entry;
268
+
269
+ flatObjectProperties.push(
270
+ me.skip(
271
+ t.objectMethod(
272
+ "get",
273
+ t.stringLiteral(objectProp),
274
+ [],
275
+ t.blockStatement([
276
+ t.returnStatement(
277
+ t.unaryExpression("typeof", t.identifier(identifierName))
278
+ ),
279
+ ]),
280
+ false,
281
+ false,
282
+ false
283
+ )
284
+ )
285
+ );
286
+ }
287
+
288
+ for (const entry of functionCallProps) {
289
+ const [identifierName, objectProp] = entry;
290
+
291
+ flatObjectProperties.push(
292
+ me.skip(
293
+ t.objectMethod(
294
+ "method",
295
+ t.stringLiteral(objectProp),
296
+ [t.restElement(t.identifier("args"))],
297
+ t.blockStatement([
298
+ t.returnStatement(
299
+ t.callExpression(t.identifier(identifierName), [
300
+ t.spreadElement(t.identifier("args")),
301
+ ])
302
+ ),
303
+ ]),
304
+ false,
305
+ false,
306
+ false
307
+ )
308
+ )
309
+ );
310
+ }
311
+
312
+ // Create the new flattened function
313
+ const flattenedFunctionDeclaration = t.functionDeclaration(
314
+ t.identifier(newFnName),
315
+ [
316
+ t.arrayPattern([...(fnPath.node.params as t.FunctionParameter[])]),
317
+ t.identifier(flatObjectName),
318
+ ],
319
+ t.blockStatement([...[...fnPath.node.body.body]]),
320
+ false,
321
+ fnPath.node.async
322
+ );
323
+
324
+ // Create the flat object variable declaration
325
+ const flatObjectDeclaration = t.variableDeclaration("var", [
326
+ t.variableDeclarator(
327
+ t.identifier(flatObjectName),
328
+ t.objectExpression(flatObjectProperties)
329
+ ),
330
+ ]);
331
+
332
+ var argName = me.getPlaceholder() + "_args";
333
+
334
+ // Replace original function body with a call to the flattened function
335
+ fnPath.node.body = t.blockStatement([
336
+ flatObjectDeclaration,
337
+ t.returnStatement(
338
+ t.callExpression(t.identifier(newFnName), [
339
+ t.identifier(argName),
340
+ t.identifier(flatObjectName),
341
+ ])
342
+ ),
343
+ ]);
344
+
345
+ const originalLength = computeFunctionLength(fnPath);
346
+ fnPath.node.params = [t.restElement(t.identifier(argName))];
347
+
348
+ // Ensure updated parameter gets registered in the function scope
349
+ fnPath.scope.crawl();
350
+ fnPath.skip();
351
+
352
+ // Add the new flattened function at the top level
353
+ var newPath = prependProgram(
354
+ program,
355
+ flattenedFunctionDeclaration
356
+ )[0] as NodePath<t.FunctionDeclaration>;
357
+
358
+ me.skip(newPath);
359
+
360
+ // Copy over all properties except the predictable flag
361
+ for (var symbol of Object.getOwnPropertySymbols(fnPath.node)) {
362
+ if (symbol !== PREDICTABLE) {
363
+ newPath.node[symbol] = fnPath.node[symbol];
364
+ }
365
+ }
366
+
367
+ // Old function is no longer predictable (rest element parameter)
368
+ (fnPath.node as NodeSymbol)[PREDICTABLE] = false;
369
+ // Old function is unsafe (uses arguments, this)
370
+ (fnPath.node as NodeSymbol)[UNSAFE] = true;
371
+
372
+ newPath.node[PREDICTABLE] = true;
373
+
374
+ // Carry over 'use strict' directive if not already present
375
+ if (strictMode) {
376
+ newPath.node.body.directives.push(
377
+ t.directive(t.directiveLiteral("use strict"))
378
+ );
379
+
380
+ // Non-simple parameter list conversion
381
+ prepend(
382
+ newPath,
383
+ t.variableDeclaration("var", [
384
+ t.variableDeclarator(
385
+ t.arrayPattern(newPath.node.params),
386
+ t.identifier("arguments")
387
+ ),
388
+ ])
389
+ );
390
+ newPath.node.params = [];
391
+ // Using 'arguments' is unsafe
392
+ (newPath.node as NodeSymbol)[UNSAFE] = true;
393
+ // Params changed and using 'arguments'
394
+ (newPath.node as NodeSymbol)[PREDICTABLE] = false;
395
+ }
396
+
397
+ // Ensure parameters are registered in the new function scope
398
+ newPath.scope.crawl();
399
+
400
+ newPath.skip();
401
+ me.skip(newPath);
402
+
403
+ // Set function length
404
+ me.setFunctionLength(fnPath, originalLength);
405
+
406
+ me.changeData.functions++;
407
+ }
408
+
409
+ return {
410
+ visitor: {
411
+ Function: {
412
+ exit(path: NodePath<t.Function>) {
413
+ flattenFunction(path);
414
+ },
415
+ },
416
+ Program(path) {
417
+ path.scope.crawl();
418
+ },
419
+ },
420
+ };
421
+ };