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,347 +1,349 @@
1
- import { NodePath } from "@babel/traverse";
2
- import { PluginArg, PluginObject } from "./plugin";
3
- import * as t from "@babel/types";
4
- import { Order } from "../order";
5
- import {
6
- NodeSymbol,
7
- PREDICTABLE,
8
- UNSAFE,
9
- variableFunctionName,
10
- } from "../constants";
11
- import { ok } from "assert";
12
- import {
13
- getParentFunctionOrProgram,
14
- getPatternIdentifierNames,
15
- isVariableIdentifier,
16
- } from "../utils/ast-utils";
17
- import { isVariableFunctionIdentifier } from "../utils/function-utils";
18
- import Template from "../templates/template";
19
-
20
- /**
21
- * Preparation arranges the user's code into an AST the obfuscator can easily transform.
22
- *
23
- * ExplicitIdentifiers
24
- * - `object.IDENTIFIER` -> `object['IDENTIFIER']` // Now String Concealing can apply on it
25
- * - `{ IDENTIFIER: ... }` -> `{ "IDENTIFIER": ... }`
26
- *
27
- * ExplicitDeclarations
28
- * - `var a,b,c` -> `var a; var b; var c;` // Now Stack can apply on it
29
- *
30
- * Block
31
- * - `x => x * 2` -> `x => { return x * 2 }` // Change into Block Statements
32
- * - `if(true) return` -> `if (true) { return }`
33
- * - `while(a) a--;` -> `while(a) { a-- }`
34
- */
35
- export default ({ Plugin }: PluginArg): PluginObject => {
36
- const me = Plugin(Order.Preparation);
37
-
38
- const markFunctionUnsafe = (path: NodePath<t.Node>) => {
39
- const functionPath = path.findParent(
40
- (path) => path.isFunction() || path.isProgram()
41
- );
42
- if (!functionPath) return;
43
-
44
- const functionNode = functionPath.node;
45
-
46
- (functionNode as NodeSymbol)[UNSAFE] = true;
47
- };
48
-
49
- return {
50
- visitor: {
51
- "ThisExpression|Super": {
52
- exit(path) {
53
- markFunctionUnsafe(path);
54
- },
55
- },
56
-
57
- // @js-confuser-var "myVar" -> __JS_CONFUSER_VAR__(myVar)
58
- StringLiteral: {
59
- exit(path) {
60
- // Check for @js-confuser-var comment
61
- if (
62
- path.node.leadingComments?.find((comment) =>
63
- comment.value.includes("@js-confuser-var")
64
- )
65
- ) {
66
- var identifierName = path.node.value;
67
- ok(
68
- t.isValidIdentifier(identifierName),
69
- "Invalid identifier name: " + identifierName
70
- );
71
-
72
- // Create a new __JS_CONFUSER_VAR__ call with the identifier
73
- var newExpression = new Template(
74
- `__JS_CONFUSER_VAR__({identifier})`
75
- ).expression({
76
- identifier: t.identifier(identifierName),
77
- });
78
-
79
- path.replaceWith(newExpression);
80
-
81
- // Remove comment and skip further processing
82
- path.node.leadingComments = [];
83
- path.skip();
84
- }
85
- },
86
- },
87
-
88
- // `Hello ${username}` -> "Hello " + username
89
- TemplateLiteral: {
90
- exit(path) {
91
- // Check if this is a tagged template literal, if yes, skip it
92
- if (t.isTaggedTemplateExpression(path.parent)) {
93
- return;
94
- }
95
-
96
- const { quasis, expressions } = path.node;
97
-
98
- // Start with the first quasi (template string part)
99
- let binaryExpression: t.Expression = t.stringLiteral(
100
- quasis[0].value.cooked
101
- );
102
-
103
- // Loop over the remaining quasis and expressions, concatenating them
104
- for (let i = 0; i < expressions.length; i++) {
105
- // Add the expression as part of the binary concatenation
106
- binaryExpression = t.binaryExpression(
107
- "+",
108
- binaryExpression,
109
- expressions[i] as t.Expression
110
- );
111
-
112
- // Add the next quasi (template string part)
113
- if (quasis[i + 1].value.cooked !== "") {
114
- binaryExpression = t.binaryExpression(
115
- "+",
116
- binaryExpression,
117
- t.stringLiteral(quasis[i + 1].value.cooked)
118
- );
119
- }
120
- }
121
-
122
- // Replace the template literal with the constructed binary expression
123
- path.replaceWith(binaryExpression);
124
- },
125
- },
126
-
127
- // /Hello World/g -> new RegExp("Hello World", "g")
128
- RegExpLiteral: {
129
- exit(path) {
130
- const { pattern, flags } = path.node;
131
-
132
- // Create a new RegExp() expression using the pattern and flags
133
- const newRegExpCall = t.newExpression(
134
- t.identifier("RegExp"), // Identifier for RegExp constructor
135
- [
136
- t.stringLiteral(pattern), // First argument: the pattern (no extra escaping needed)
137
- flags ? t.stringLiteral(flags) : t.stringLiteral(""), // Second argument: the flags (if any)
138
- ]
139
- );
140
-
141
- // Replace the literal regex with the new RegExp() call
142
- path.replaceWith(newRegExpCall);
143
- },
144
- },
145
-
146
- ReferencedIdentifier: {
147
- exit(path) {
148
- const { name } = path.node;
149
- if (["arguments", "eval"].includes(name)) {
150
- markFunctionUnsafe(path);
151
- }
152
-
153
- // When Rename Variables is disabled, __JS_CONFUSER_VAR__ must still be removed
154
- if (
155
- !me.obfuscator.hasPlugin(Order.RenameVariables) &&
156
- isVariableFunctionIdentifier(path)
157
- ) {
158
- ok(
159
- path.parentPath.isCallExpression(),
160
- variableFunctionName + " must be directly called"
161
- );
162
-
163
- var argument = path.parentPath.node.arguments[0];
164
- t.assertIdentifier(argument);
165
-
166
- // Remove the variableFunctionName call
167
- path.parentPath.replaceWith(t.stringLiteral(argument.name));
168
- }
169
- },
170
- },
171
-
172
- FunctionDeclaration: {
173
- exit(path) {
174
- // A function is 'predictable' if the parameter lengths are guaranteed to be known
175
- // a(true) -> predictable
176
- // (a || b)(true) -> unpredictable (Must be directly in a Call Expression)
177
- // a(...args) -> unpredictable (Cannot use SpreadElement)
178
-
179
- const { name } = path.node.id;
180
-
181
- var binding = path.scope.getBinding(name);
182
- var predictable = true;
183
- var maxArgLength = 0;
184
-
185
- for (var referencePath of binding.referencePaths) {
186
- if (!referencePath.parentPath.isCallExpression()) {
187
- predictable = false;
188
- break;
189
- }
190
-
191
- var argsPath = referencePath.parentPath.get("arguments");
192
- for (var arg of argsPath) {
193
- if (arg.isSpreadElement()) {
194
- predictable = false;
195
- break;
196
- }
197
- }
198
-
199
- if (argsPath.length > maxArgLength) {
200
- maxArgLength = argsPath.length;
201
- }
202
- }
203
-
204
- var definedArgLength = path.get("params").length;
205
- if (predictable && definedArgLength >= maxArgLength) {
206
- (path.node as NodeSymbol)[PREDICTABLE] = true;
207
- }
208
- },
209
- },
210
-
211
- // console.log() -> console["log"]();
212
- MemberExpression: {
213
- exit(path) {
214
- if (!path.node.computed && path.node.property.type === "Identifier") {
215
- path.node.property = t.stringLiteral(path.node.property.name);
216
- path.node.computed = true;
217
- }
218
- },
219
- },
220
-
221
- // { key: true } -> { "key": true }
222
- "Property|Method": {
223
- exit(_path) {
224
- let path = _path as NodePath<t.Property | t.Method>;
225
-
226
- if (t.isClassPrivateProperty(path.node)) return;
227
-
228
- if (!path.node.computed && path.node.key.type === "Identifier") {
229
- // Don't change constructor key
230
- if (t.isClassMethod(path.node) && path.node.kind === "constructor")
231
- return;
232
-
233
- path.node.key = t.stringLiteral(path.node.key.name);
234
- path.node.computed = true;
235
- }
236
- },
237
- },
238
-
239
- // var a,b,c -> var a; var b; var c;
240
- VariableDeclaration: {
241
- exit(path) {
242
- if (path.node.declarations.length > 1) {
243
- // E.g. for (var i = 0, j = 1;;)
244
- if (path.key === "init" && path.parentPath.isForStatement()) {
245
- if (
246
- !path.parentPath.node.test &&
247
- !path.parentPath.node.update &&
248
- path.node.kind === "var"
249
- ) {
250
- path.parentPath.insertBefore(
251
- path.node.declarations.map((declaration) =>
252
- t.variableDeclaration(path.node.kind, [declaration])
253
- )
254
- );
255
- path.remove();
256
- }
257
- } else {
258
- if (path.parentPath.isExportNamedDeclaration()) {
259
- path.parentPath.replaceWithMultiple(
260
- path.node.declarations.map((declaration) =>
261
- t.exportNamedDeclaration(
262
- t.variableDeclaration(path.node.kind, [declaration])
263
- )
264
- )
265
- );
266
- } else {
267
- path
268
- .replaceWithMultiple(
269
- path.node.declarations.map((declaration, i) => {
270
- var names = Array.from(
271
- getPatternIdentifierNames(path.get("declarations")[i])
272
- );
273
- names.forEach((name) => {
274
- path.scope.removeBinding(name);
275
- });
276
-
277
- var newNode = t.variableDeclaration(path.node.kind, [
278
- declaration,
279
- ]);
280
- return newNode;
281
- })
282
- )
283
- .forEach((newPath) => {
284
- if (newPath.node.kind === "var") {
285
- var functionOrProgram =
286
- getParentFunctionOrProgram(newPath);
287
- functionOrProgram.scope.registerDeclaration(newPath);
288
- }
289
- newPath.scope.registerDeclaration(newPath);
290
- });
291
- }
292
- }
293
- }
294
- },
295
- },
296
-
297
- // () => a() -> () => { return a(); }
298
- ArrowFunctionExpression: {
299
- exit(path: NodePath<t.ArrowFunctionExpression>) {
300
- if (path.node.body.type !== "BlockStatement") {
301
- path.node.expression = false;
302
- path.node.body = t.blockStatement([
303
- t.returnStatement(path.node.body),
304
- ]);
305
- }
306
- },
307
- },
308
-
309
- // if (a) b() -> if (a) { b(); }
310
- // if (a) {b()} else c() -> if (a) { b(); } else { c(); }
311
- IfStatement: {
312
- exit(path) {
313
- if (path.node.consequent.type !== "BlockStatement") {
314
- path.node.consequent = t.blockStatement([path.node.consequent]);
315
- }
316
-
317
- if (
318
- path.node.alternate &&
319
- path.node.alternate.type !== "BlockStatement"
320
- ) {
321
- path.node.alternate = t.blockStatement([path.node.alternate]);
322
- }
323
- },
324
- },
325
-
326
- // for() d() -> for() { d(); }
327
- // while(a) b() -> while(a) { b(); }
328
- // with(a) b() -> with(a) { b(); }
329
- "ForStatement|ForInStatement|ForOfStatement|WhileStatement|WithStatement":
330
- {
331
- exit(_path) {
332
- var path = _path as NodePath<
333
- | t.ForStatement
334
- | t.ForInStatement
335
- | t.ForOfStatement
336
- | t.WhileStatement
337
- | t.WithStatement
338
- >;
339
-
340
- if (path.node.body.type !== "BlockStatement") {
341
- path.node.body = t.blockStatement([path.node.body]);
342
- }
343
- },
344
- },
345
- },
346
- };
347
- };
1
+ import { NodePath } from "@babel/traverse";
2
+ import { PluginArg, PluginObject } from "./plugin";
3
+ import * as t from "@babel/types";
4
+ import { Order } from "../order";
5
+ import {
6
+ NodeSymbol,
7
+ PREDICTABLE,
8
+ UNSAFE,
9
+ variableFunctionName,
10
+ } from "../constants";
11
+ import { ok } from "assert";
12
+ import {
13
+ getParentFunctionOrProgram,
14
+ getPatternIdentifierNames,
15
+ isVariableIdentifier,
16
+ } from "../utils/ast-utils";
17
+ import { isVariableFunctionIdentifier } from "../utils/function-utils";
18
+ import Template from "../templates/template";
19
+
20
+ /**
21
+ * Preparation arranges the user's code into an AST the obfuscator can easily transform.
22
+ *
23
+ * ExplicitIdentifiers
24
+ * - `object.IDENTIFIER` -> `object['IDENTIFIER']` // Now String Concealing can apply on it
25
+ * - `{ IDENTIFIER: ... }` -> `{ "IDENTIFIER": ... }`
26
+ *
27
+ * ExplicitDeclarations
28
+ * - `var a,b,c` -> `var a; var b; var c;` // Now Stack can apply on it
29
+ *
30
+ * Block
31
+ * - `x => x * 2` -> `x => { return x * 2 }` // Change into Block Statements
32
+ * - `if(true) return` -> `if (true) { return }`
33
+ * - `while(a) a--;` -> `while(a) { a-- }`
34
+ */
35
+ export default ({ Plugin }: PluginArg): PluginObject => {
36
+ const me = Plugin(Order.Preparation);
37
+
38
+ const markFunctionUnsafe = (path: NodePath<t.Node>) => {
39
+ const functionPath = path.findParent(
40
+ (path) => path.isFunction() || path.isProgram()
41
+ );
42
+ if (!functionPath) return;
43
+
44
+ const functionNode = functionPath.node;
45
+
46
+ (functionNode as NodeSymbol)[UNSAFE] = true;
47
+ };
48
+
49
+ return {
50
+ visitor: {
51
+ "ThisExpression|Super": {
52
+ exit(path) {
53
+ markFunctionUnsafe(path);
54
+ },
55
+ },
56
+
57
+ // @js-confuser-var "myVar" -> __JS_CONFUSER_VAR__(myVar)
58
+ StringLiteral: {
59
+ exit(path) {
60
+ // Check for @js-confuser-var comment
61
+ if (
62
+ path.node.leadingComments?.find((comment) =>
63
+ comment.value.includes("@js-confuser-var")
64
+ )
65
+ ) {
66
+ var identifierName = path.node.value;
67
+ ok(
68
+ t.isValidIdentifier(identifierName),
69
+ "Invalid identifier name: " + identifierName
70
+ );
71
+
72
+ // Create a new __JS_CONFUSER_VAR__ call with the identifier
73
+ var newExpression = new Template(
74
+ `__JS_CONFUSER_VAR__({identifier})`
75
+ ).expression({
76
+ identifier: t.identifier(identifierName),
77
+ });
78
+
79
+ path.replaceWith(newExpression);
80
+
81
+ // Remove comment and skip further processing
82
+ path.node.leadingComments = [];
83
+ path.skip();
84
+ }
85
+ },
86
+ },
87
+
88
+ // `Hello ${username}` -> "Hello " + username
89
+ TemplateLiteral: {
90
+ exit(path) {
91
+ // Check if this is a tagged template literal, if yes, skip it
92
+ if (t.isTaggedTemplateExpression(path.parent)) {
93
+ return;
94
+ }
95
+
96
+ const { quasis, expressions } = path.node;
97
+
98
+ // Start with the first quasi (template string part)
99
+ let binaryExpression: t.Expression = t.stringLiteral(
100
+ quasis[0].value.cooked
101
+ );
102
+
103
+ // Loop over the remaining quasis and expressions, concatenating them
104
+ for (let i = 0; i < expressions.length; i++) {
105
+ // Add the expression as part of the binary concatenation
106
+ binaryExpression = t.binaryExpression(
107
+ "+",
108
+ binaryExpression,
109
+ expressions[i] as t.Expression
110
+ );
111
+
112
+ // Add the next quasi (template string part)
113
+ if (quasis[i + 1].value.cooked !== "") {
114
+ binaryExpression = t.binaryExpression(
115
+ "+",
116
+ binaryExpression,
117
+ t.stringLiteral(quasis[i + 1].value.cooked)
118
+ );
119
+ }
120
+ }
121
+
122
+ // Replace the template literal with the constructed binary expression
123
+ path.replaceWith(binaryExpression);
124
+ },
125
+ },
126
+
127
+ // /Hello World/g -> new RegExp("Hello World", "g")
128
+ RegExpLiteral: {
129
+ exit(path) {
130
+ const { pattern, flags } = path.node;
131
+
132
+ // Create a new RegExp() expression using the pattern and flags
133
+ const newRegExpCall = t.newExpression(
134
+ t.identifier("RegExp"), // Identifier for RegExp constructor
135
+ [
136
+ t.stringLiteral(pattern), // First argument: the pattern (no extra escaping needed)
137
+ flags ? t.stringLiteral(flags) : t.stringLiteral(""), // Second argument: the flags (if any)
138
+ ]
139
+ );
140
+
141
+ // Replace the literal regex with the new RegExp() call
142
+ path.replaceWith(newRegExpCall);
143
+ },
144
+ },
145
+
146
+ ReferencedIdentifier: {
147
+ exit(path) {
148
+ const { name } = path.node;
149
+ if (["arguments", "eval"].includes(name)) {
150
+ markFunctionUnsafe(path);
151
+ }
152
+
153
+ // When Rename Variables is disabled, __JS_CONFUSER_VAR__ must still be removed
154
+ if (
155
+ !me.obfuscator.hasPlugin(Order.RenameVariables) &&
156
+ isVariableFunctionIdentifier(path)
157
+ ) {
158
+ ok(
159
+ path.parentPath.isCallExpression(),
160
+ variableFunctionName + " must be directly called"
161
+ );
162
+
163
+ var argument = path.parentPath.node.arguments[0];
164
+ t.assertIdentifier(argument);
165
+
166
+ // Remove the variableFunctionName call
167
+ path.parentPath.replaceWith(t.stringLiteral(argument.name));
168
+ }
169
+ },
170
+ },
171
+
172
+ FunctionDeclaration: {
173
+ exit(path) {
174
+ // A function is 'predictable' if the parameter lengths are guaranteed to be known
175
+ // a(true) -> predictable
176
+ // (a || b)(true) -> unpredictable (Must be directly in a Call Expression)
177
+ // a(...args) -> unpredictable (Cannot use SpreadElement)
178
+
179
+ const { name } = path.node.id;
180
+
181
+ var binding = path.scope.getBinding(name);
182
+ var predictable = true;
183
+ var maxArgLength = 0;
184
+
185
+ if (!binding) return;
186
+
187
+ for (var referencePath of binding.referencePaths) {
188
+ if (!referencePath.parentPath.isCallExpression()) {
189
+ predictable = false;
190
+ break;
191
+ }
192
+
193
+ var argsPath = referencePath.parentPath.get("arguments");
194
+ for (var arg of argsPath) {
195
+ if (arg.isSpreadElement()) {
196
+ predictable = false;
197
+ break;
198
+ }
199
+ }
200
+
201
+ if (argsPath.length > maxArgLength) {
202
+ maxArgLength = argsPath.length;
203
+ }
204
+ }
205
+
206
+ var definedArgLength = path.get("params").length;
207
+ if (predictable && definedArgLength >= maxArgLength) {
208
+ (path.node as NodeSymbol)[PREDICTABLE] = true;
209
+ }
210
+ },
211
+ },
212
+
213
+ // console.log() -> console["log"]();
214
+ MemberExpression: {
215
+ exit(path) {
216
+ if (!path.node.computed && path.node.property.type === "Identifier") {
217
+ path.node.property = t.stringLiteral(path.node.property.name);
218
+ path.node.computed = true;
219
+ }
220
+ },
221
+ },
222
+
223
+ // { key: true } -> { "key": true }
224
+ "Property|Method": {
225
+ exit(_path) {
226
+ let path = _path as NodePath<t.Property | t.Method>;
227
+
228
+ if (t.isClassPrivateProperty(path.node)) return;
229
+
230
+ if (!path.node.computed && path.node.key.type === "Identifier") {
231
+ // Don't change constructor key
232
+ if (t.isClassMethod(path.node) && path.node.kind === "constructor")
233
+ return;
234
+
235
+ path.node.key = t.stringLiteral(path.node.key.name);
236
+ path.node.computed = true;
237
+ }
238
+ },
239
+ },
240
+
241
+ // var a,b,c -> var a; var b; var c;
242
+ VariableDeclaration: {
243
+ exit(path) {
244
+ if (path.node.declarations.length > 1) {
245
+ // E.g. for (var i = 0, j = 1;;)
246
+ if (path.key === "init" && path.parentPath.isForStatement()) {
247
+ if (
248
+ !path.parentPath.node.test &&
249
+ !path.parentPath.node.update &&
250
+ path.node.kind === "var"
251
+ ) {
252
+ path.parentPath.insertBefore(
253
+ path.node.declarations.map((declaration) =>
254
+ t.variableDeclaration(path.node.kind, [declaration])
255
+ )
256
+ );
257
+ path.remove();
258
+ }
259
+ } else {
260
+ if (path.parentPath.isExportNamedDeclaration()) {
261
+ path.parentPath.replaceWithMultiple(
262
+ path.node.declarations.map((declaration) =>
263
+ t.exportNamedDeclaration(
264
+ t.variableDeclaration(path.node.kind, [declaration])
265
+ )
266
+ )
267
+ );
268
+ } else {
269
+ path
270
+ .replaceWithMultiple(
271
+ path.node.declarations.map((declaration, i) => {
272
+ var names = Array.from(
273
+ getPatternIdentifierNames(path.get("declarations")[i])
274
+ );
275
+ names.forEach((name) => {
276
+ path.scope.removeBinding(name);
277
+ });
278
+
279
+ var newNode = t.variableDeclaration(path.node.kind, [
280
+ declaration,
281
+ ]);
282
+ return newNode;
283
+ })
284
+ )
285
+ .forEach((newPath) => {
286
+ if (newPath.node.kind === "var") {
287
+ var functionOrProgram =
288
+ getParentFunctionOrProgram(newPath);
289
+ functionOrProgram.scope.registerDeclaration(newPath);
290
+ }
291
+ newPath.scope.registerDeclaration(newPath);
292
+ });
293
+ }
294
+ }
295
+ }
296
+ },
297
+ },
298
+
299
+ // () => a() -> () => { return a(); }
300
+ ArrowFunctionExpression: {
301
+ exit(path: NodePath<t.ArrowFunctionExpression>) {
302
+ if (path.node.body.type !== "BlockStatement") {
303
+ path.node.expression = false;
304
+ path.node.body = t.blockStatement([
305
+ t.returnStatement(path.node.body),
306
+ ]);
307
+ }
308
+ },
309
+ },
310
+
311
+ // if (a) b() -> if (a) { b(); }
312
+ // if (a) {b()} else c() -> if (a) { b(); } else { c(); }
313
+ IfStatement: {
314
+ exit(path) {
315
+ if (path.node.consequent.type !== "BlockStatement") {
316
+ path.node.consequent = t.blockStatement([path.node.consequent]);
317
+ }
318
+
319
+ if (
320
+ path.node.alternate &&
321
+ path.node.alternate.type !== "BlockStatement"
322
+ ) {
323
+ path.node.alternate = t.blockStatement([path.node.alternate]);
324
+ }
325
+ },
326
+ },
327
+
328
+ // for() d() -> for() { d(); }
329
+ // while(a) b() -> while(a) { b(); }
330
+ // with(a) b() -> with(a) { b(); }
331
+ "ForStatement|ForInStatement|ForOfStatement|WhileStatement|WithStatement":
332
+ {
333
+ exit(_path) {
334
+ var path = _path as NodePath<
335
+ | t.ForStatement
336
+ | t.ForInStatement
337
+ | t.ForOfStatement
338
+ | t.WhileStatement
339
+ | t.WithStatement
340
+ >;
341
+
342
+ if (path.node.body.type !== "BlockStatement") {
343
+ path.node.body = t.blockStatement([path.node.body]);
344
+ }
345
+ },
346
+ },
347
+ },
348
+ };
349
+ };