js-confuser 1.7.2 → 2.0.0-alpha.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 (263) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +6 -4
  2. package/.github/workflows/node.js.yml +1 -1
  3. package/CHANGELOG.md +105 -0
  4. package/Migration.md +57 -0
  5. package/README.md +23 -913
  6. package/dist/constants.js +69 -13
  7. package/dist/index.js +108 -152
  8. package/dist/obfuscator.js +316 -118
  9. package/dist/options.js +1 -109
  10. package/dist/order.js +30 -30
  11. package/dist/presets.js +47 -45
  12. package/dist/probability.js +25 -32
  13. package/dist/templates/bufferToStringTemplate.js +9 -0
  14. package/dist/templates/deadCodeTemplates.js +9 -0
  15. package/dist/templates/getGlobalTemplate.js +19 -0
  16. package/dist/templates/integrityTemplate.js +30 -0
  17. package/dist/templates/setFunctionLengthTemplate.js +9 -0
  18. package/dist/templates/stringCompressionTemplate.js +10 -0
  19. package/dist/templates/tamperProtectionTemplates.js +21 -0
  20. package/dist/templates/template.js +213 -93
  21. package/dist/transforms/astScrambler.js +100 -0
  22. package/dist/transforms/calculator.js +70 -127
  23. package/dist/transforms/controlFlowFlattening.js +1182 -0
  24. package/dist/transforms/deadCode.js +62 -577
  25. package/dist/transforms/dispatcher.js +300 -309
  26. package/dist/transforms/extraction/duplicateLiteralsRemoval.js +88 -189
  27. package/dist/transforms/extraction/objectExtraction.js +131 -215
  28. package/dist/transforms/finalizer.js +56 -59
  29. package/dist/transforms/flatten.js +275 -276
  30. package/dist/transforms/functionOutlining.js +230 -0
  31. package/dist/transforms/identifier/globalConcealing.js +217 -103
  32. package/dist/transforms/identifier/movedDeclarations.js +167 -91
  33. package/dist/transforms/identifier/renameVariables.js +240 -187
  34. package/dist/transforms/lock/integrity.js +61 -184
  35. package/dist/transforms/lock/lock.js +263 -303
  36. package/dist/transforms/minify.js +431 -436
  37. package/dist/transforms/opaquePredicates.js +65 -118
  38. package/dist/transforms/pack.js +160 -0
  39. package/dist/transforms/plugin.js +179 -0
  40. package/dist/transforms/preparation.js +263 -163
  41. package/dist/transforms/renameLabels.js +132 -56
  42. package/dist/transforms/rgf.js +142 -240
  43. package/dist/transforms/shuffle.js +52 -145
  44. package/dist/transforms/string/encoding.js +45 -173
  45. package/dist/transforms/string/stringCompression.js +81 -126
  46. package/dist/transforms/string/stringConcealing.js +189 -224
  47. package/dist/transforms/string/stringEncoding.js +32 -40
  48. package/dist/transforms/string/stringSplitting.js +54 -55
  49. package/dist/transforms/variableMasking.js +232 -0
  50. package/dist/utils/ControlObject.js +125 -0
  51. package/dist/utils/IntGen.js +46 -0
  52. package/dist/utils/NameGen.js +106 -0
  53. package/dist/utils/ast-utils.js +560 -0
  54. package/dist/utils/function-utils.js +56 -0
  55. package/dist/utils/gen-utils.js +48 -0
  56. package/dist/utils/node.js +77 -0
  57. package/dist/utils/object-utils.js +21 -0
  58. package/dist/utils/random-utils.js +91 -0
  59. package/dist/utils/static-utils.js +64 -0
  60. package/dist/validateOptions.js +122 -0
  61. package/index.d.ts +1 -17
  62. package/package.json +27 -22
  63. package/src/constants.ts +139 -77
  64. package/src/index.ts +70 -163
  65. package/src/obfuscationResult.ts +43 -0
  66. package/src/obfuscator.ts +328 -135
  67. package/src/options.ts +154 -623
  68. package/src/order.ts +14 -14
  69. package/src/presets.ts +39 -34
  70. package/src/probability.ts +21 -36
  71. package/src/templates/{bufferToString.ts → bufferToStringTemplate.ts} +5 -54
  72. package/src/templates/deadCodeTemplates.ts +1185 -0
  73. package/src/templates/getGlobalTemplate.ts +72 -0
  74. package/src/templates/integrityTemplate.ts +69 -0
  75. package/src/templates/setFunctionLengthTemplate.ts +11 -0
  76. package/src/templates/stringCompressionTemplate.ts +42 -0
  77. package/src/templates/tamperProtectionTemplates.ts +116 -0
  78. package/src/templates/template.ts +183 -92
  79. package/src/transforms/astScrambler.ts +99 -0
  80. package/src/transforms/calculator.ts +96 -224
  81. package/src/transforms/controlFlowFlattening.ts +1594 -0
  82. package/src/transforms/deadCode.ts +85 -628
  83. package/src/transforms/dispatcher.ts +431 -636
  84. package/src/transforms/extraction/duplicateLiteralsRemoval.ts +147 -299
  85. package/src/transforms/extraction/objectExtraction.ts +160 -333
  86. package/src/transforms/finalizer.ts +63 -64
  87. package/src/transforms/flatten.ts +439 -557
  88. package/src/transforms/functionOutlining.ts +225 -0
  89. package/src/transforms/identifier/globalConcealing.ts +261 -189
  90. package/src/transforms/identifier/movedDeclarations.ts +228 -142
  91. package/src/transforms/identifier/renameVariables.ts +252 -258
  92. package/src/transforms/lock/integrity.ts +84 -260
  93. package/src/transforms/lock/lock.ts +342 -491
  94. package/src/transforms/minify.ts +523 -663
  95. package/src/transforms/opaquePredicates.ts +90 -229
  96. package/src/transforms/pack.ts +195 -0
  97. package/src/transforms/plugin.ts +185 -0
  98. package/src/transforms/preparation.ts +337 -215
  99. package/src/transforms/renameLabels.ts +176 -77
  100. package/src/transforms/rgf.ts +293 -386
  101. package/src/transforms/shuffle.ts +80 -254
  102. package/src/transforms/string/encoding.ts +26 -129
  103. package/src/transforms/string/stringCompression.ts +118 -236
  104. package/src/transforms/string/stringConcealing.ts +255 -339
  105. package/src/transforms/string/stringEncoding.ts +28 -47
  106. package/src/transforms/string/stringSplitting.ts +61 -75
  107. package/src/transforms/variableMasking.ts +257 -0
  108. package/src/utils/ControlObject.ts +141 -0
  109. package/src/utils/IntGen.ts +33 -0
  110. package/src/utils/NameGen.ts +106 -0
  111. package/src/utils/ast-utils.ts +667 -0
  112. package/src/utils/function-utils.ts +50 -0
  113. package/src/utils/gen-utils.ts +48 -0
  114. package/src/utils/node.ts +78 -0
  115. package/src/utils/object-utils.ts +21 -0
  116. package/src/utils/random-utils.ts +79 -0
  117. package/src/utils/static-utils.ts +66 -0
  118. package/src/validateOptions.ts +256 -0
  119. package/tsconfig.json +13 -8
  120. package/babel.config.js +0 -12
  121. package/dev.js +0 -8
  122. package/dist/compiler.js +0 -34
  123. package/dist/parser.js +0 -59
  124. package/dist/precedence.js +0 -66
  125. package/dist/templates/bufferToString.js +0 -108
  126. package/dist/templates/crash.js +0 -59
  127. package/dist/templates/es5.js +0 -137
  128. package/dist/templates/functionLength.js +0 -34
  129. package/dist/templates/globals.js +0 -9
  130. package/dist/transforms/antiTooling.js +0 -88
  131. package/dist/transforms/controlFlowFlattening/controlFlowFlattening.js +0 -1281
  132. package/dist/transforms/controlFlowFlattening/expressionObfuscation.js +0 -131
  133. package/dist/transforms/es5/antiClass.js +0 -164
  134. package/dist/transforms/es5/antiDestructuring.js +0 -193
  135. package/dist/transforms/es5/antiES6Object.js +0 -185
  136. package/dist/transforms/es5/antiSpreadOperator.js +0 -35
  137. package/dist/transforms/es5/antiTemplate.js +0 -66
  138. package/dist/transforms/es5/es5.js +0 -123
  139. package/dist/transforms/extraction/classExtraction.js +0 -83
  140. package/dist/transforms/identifier/globalAnalysis.js +0 -70
  141. package/dist/transforms/identifier/variableAnalysis.js +0 -104
  142. package/dist/transforms/lock/antiDebug.js +0 -76
  143. package/dist/transforms/stack.js +0 -343
  144. package/dist/transforms/transform.js +0 -350
  145. package/dist/traverse.js +0 -110
  146. package/dist/util/compare.js +0 -145
  147. package/dist/util/gen.js +0 -564
  148. package/dist/util/guard.js +0 -9
  149. package/dist/util/identifiers.js +0 -355
  150. package/dist/util/insert.js +0 -362
  151. package/dist/util/math.js +0 -19
  152. package/dist/util/object.js +0 -40
  153. package/dist/util/random.js +0 -130
  154. package/dist/util/scope.js +0 -20
  155. package/docs/ControlFlowFlattening.md +0 -595
  156. package/docs/Countermeasures.md +0 -63
  157. package/docs/ES5.md +0 -197
  158. package/docs/Integrity.md +0 -75
  159. package/docs/RGF.md +0 -419
  160. package/samples/example.js +0 -15
  161. package/samples/high.js +0 -1
  162. package/samples/input.js +0 -3
  163. package/samples/javascriptobfuscator.com.js +0 -8
  164. package/samples/jscrambler_advanced.js +0 -1894
  165. package/samples/jscrambler_light.js +0 -1134
  166. package/samples/low.js +0 -1
  167. package/samples/medium.js +0 -1
  168. package/samples/obfuscator.io.js +0 -1686
  169. package/samples/preemptive.com.js +0 -16
  170. package/src/compiler.ts +0 -35
  171. package/src/parser.ts +0 -49
  172. package/src/precedence.ts +0 -61
  173. package/src/templates/crash.ts +0 -55
  174. package/src/templates/es5.ts +0 -131
  175. package/src/templates/functionLength.ts +0 -32
  176. package/src/templates/globals.ts +0 -3
  177. package/src/transforms/antiTooling.ts +0 -102
  178. package/src/transforms/controlFlowFlattening/controlFlowFlattening.ts +0 -2146
  179. package/src/transforms/controlFlowFlattening/expressionObfuscation.ts +0 -179
  180. package/src/transforms/es5/antiClass.ts +0 -272
  181. package/src/transforms/es5/antiDestructuring.ts +0 -294
  182. package/src/transforms/es5/antiES6Object.ts +0 -267
  183. package/src/transforms/es5/antiSpreadOperator.ts +0 -56
  184. package/src/transforms/es5/antiTemplate.ts +0 -98
  185. package/src/transforms/es5/es5.ts +0 -149
  186. package/src/transforms/extraction/classExtraction.ts +0 -168
  187. package/src/transforms/identifier/globalAnalysis.ts +0 -85
  188. package/src/transforms/identifier/variableAnalysis.ts +0 -118
  189. package/src/transforms/lock/antiDebug.ts +0 -112
  190. package/src/transforms/stack.ts +0 -551
  191. package/src/transforms/transform.ts +0 -453
  192. package/src/traverse.ts +0 -120
  193. package/src/types.ts +0 -131
  194. package/src/util/compare.ts +0 -181
  195. package/src/util/gen.ts +0 -651
  196. package/src/util/guard.ts +0 -7
  197. package/src/util/identifiers.ts +0 -494
  198. package/src/util/insert.ts +0 -419
  199. package/src/util/math.ts +0 -15
  200. package/src/util/object.ts +0 -39
  201. package/src/util/random.ts +0 -141
  202. package/src/util/scope.ts +0 -21
  203. package/test/code/Cash.src.js +0 -1011
  204. package/test/code/Cash.test.ts +0 -49
  205. package/test/code/Dynamic.src.js +0 -118
  206. package/test/code/Dynamic.test.ts +0 -49
  207. package/test/code/ES6.src.js +0 -235
  208. package/test/code/ES6.test.ts +0 -42
  209. package/test/code/NewFeatures.test.ts +0 -19
  210. package/test/code/StrictMode.src.js +0 -65
  211. package/test/code/StrictMode.test.js +0 -37
  212. package/test/compare.test.ts +0 -104
  213. package/test/index.test.ts +0 -249
  214. package/test/options.test.ts +0 -132
  215. package/test/presets.test.ts +0 -22
  216. package/test/probability.test.ts +0 -44
  217. package/test/templates/template.test.ts +0 -14
  218. package/test/transforms/antiTooling.test.ts +0 -52
  219. package/test/transforms/calculator.test.ts +0 -78
  220. package/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts +0 -1274
  221. package/test/transforms/controlFlowFlattening/expressionObfuscation.test.ts +0 -192
  222. package/test/transforms/deadCode.test.ts +0 -85
  223. package/test/transforms/dispatcher.test.ts +0 -457
  224. package/test/transforms/es5/antiClass.test.ts +0 -427
  225. package/test/transforms/es5/antiDestructuring.test.ts +0 -157
  226. package/test/transforms/es5/antiES6Object.test.ts +0 -245
  227. package/test/transforms/es5/antiTemplate.test.ts +0 -116
  228. package/test/transforms/es5/es5.test.ts +0 -110
  229. package/test/transforms/extraction/classExtraction.test.ts +0 -86
  230. package/test/transforms/extraction/duplicateLiteralsRemoval.test.ts +0 -200
  231. package/test/transforms/extraction/objectExtraction.test.ts +0 -491
  232. package/test/transforms/flatten.test.ts +0 -721
  233. package/test/transforms/hexadecimalNumbers.test.ts +0 -62
  234. package/test/transforms/identifier/globalConcealing.test.ts +0 -72
  235. package/test/transforms/identifier/movedDeclarations.test.ts +0 -275
  236. package/test/transforms/identifier/renameVariables.test.ts +0 -621
  237. package/test/transforms/lock/antiDebug.test.ts +0 -66
  238. package/test/transforms/lock/browserLock.test.ts +0 -129
  239. package/test/transforms/lock/countermeasures.test.ts +0 -100
  240. package/test/transforms/lock/integrity.test.ts +0 -161
  241. package/test/transforms/lock/lock.test.ts +0 -204
  242. package/test/transforms/lock/osLock.test.ts +0 -312
  243. package/test/transforms/lock/selfDefending.test.ts +0 -68
  244. package/test/transforms/minify.test.ts +0 -575
  245. package/test/transforms/opaquePredicates.test.ts +0 -43
  246. package/test/transforms/preparation.test.ts +0 -157
  247. package/test/transforms/renameLabels.test.ts +0 -95
  248. package/test/transforms/rgf.test.ts +0 -378
  249. package/test/transforms/shuffle.test.ts +0 -135
  250. package/test/transforms/stack.test.ts +0 -573
  251. package/test/transforms/string/stringCompression.test.ts +0 -120
  252. package/test/transforms/string/stringConcealing.test.ts +0 -299
  253. package/test/transforms/string/stringEncoding.test.ts +0 -95
  254. package/test/transforms/string/stringSplitting.test.ts +0 -135
  255. package/test/transforms/transform.test.ts +0 -66
  256. package/test/traverse.test.ts +0 -139
  257. package/test/util/compare.test.ts +0 -34
  258. package/test/util/gen.test.ts +0 -121
  259. package/test/util/identifiers.test.ts +0 -253
  260. package/test/util/insert.test.ts +0 -142
  261. package/test/util/math.test.ts +0 -5
  262. package/test/util/random.test.ts +0 -71
  263. /package/dist/{types.js → obfuscationResult.js} +0 -0
@@ -1,722 +1,582 @@
1
- import Transform from "./transform";
2
- import { ObfuscateOrder } from "../order";
1
+ import { NodePath } from "@babel/core";
2
+ import { PluginArg, PluginObject } from "./plugin";
3
+ import * as t from "@babel/types";
4
+ import { Order } from "../order";
3
5
  import {
4
- Node,
5
- VariableDeclaration,
6
- BinaryExpression,
7
- ExpressionStatement,
8
- SequenceExpression,
9
- Literal,
10
- UnaryExpression,
11
- ConditionalExpression,
12
- BlockStatement,
13
- ReturnStatement,
14
- AssignmentExpression,
15
- VariableDeclarator,
16
- Identifier,
17
- CallExpression,
18
- } from "../util/gen";
19
- import {
20
- getBlockBody,
21
- clone,
22
- isForInitialize,
23
- append,
24
- isVarContext,
25
- computeFunctionLength,
26
- } from "../util/insert";
27
- import { isValidIdentifier, isEquivalent } from "../util/compare";
28
- import { walk, isBlock } from "../traverse";
29
- import { ok } from "assert";
30
- import { isLexicalScope } from "../util/scope";
31
- import Template from "../templates/template";
32
- import { ObjectDefineProperty } from "../templates/globals";
33
- import { getIdentifierInfo } from "../util/identifiers";
6
+ ensureComputedExpression,
7
+ getParentFunctionOrProgram,
8
+ isUndefined,
9
+ } from "../utils/ast-utils";
10
+ import { Binding, Scope } from "@babel/traverse";
11
+ import { NodeSymbol, placeholderVariablePrefix, UNSAFE } from "../constants";
12
+
13
+ const identifierMap = new Map<string, () => t.Expression>();
14
+ identifierMap.set("undefined", () =>
15
+ t.unaryExpression("void", t.numericLiteral(0))
16
+ );
17
+ identifierMap.set("Infinity", () =>
18
+ t.binaryExpression("/", t.numericLiteral(1), t.numericLiteral(0))
19
+ );
34
20
 
35
21
  /**
36
- * Basic transformations to reduce code size.
22
+ * Minify removes unnecessary code and shortens the length for file size.
37
23
  *
38
- * Examples:
39
- * - `if(a) { b() }` **->** `a && b()`
40
- * - `if(a){b()}else{c()}` **->** `a?b():c()`
41
- * - `x['y']` **->** `x.y`
24
+ * - Dead code elimination
25
+ * - Variable grouping
26
+ * - Constant folding
27
+ * - Shorten literals: True to !0, False to !1, Infinity to 1/0, Undefined to void 0
28
+ * - Remove unused variables, functions
42
29
  */
43
- export default class Minify extends Transform {
44
- /**
45
- * A helper function that is introduced preserve function semantics
46
- */
47
- arrowFunctionName: string;
48
-
49
- constructor(o) {
50
- super(o, ObfuscateOrder.Minify);
51
- }
52
-
53
- match(object: Node, parents: Node[]) {
54
- return object.hasOwnProperty("type");
55
- }
56
-
57
- transform(object: Node, parents: Node[]) {
58
- if (isLexicalScope(object)) {
59
- return () => {
60
- var body =
61
- object.type == "SwitchCase"
62
- ? object.consequent
63
- : getBlockBody(object);
64
- var earlyReturn = body.length;
65
- var fnDecs: [Node, number][] = [];
66
-
67
- body.forEach((stmt, i) => {
30
+ export default ({ Plugin }: PluginArg): PluginObject => {
31
+ const me = Plugin(Order.Minify);
32
+ return {
33
+ visitor: {
34
+ Program(path) {
35
+ path.scope.crawl();
36
+ },
37
+ // var a; var b; -> var a,b;
38
+ VariableDeclaration: {
39
+ exit(path) {
40
+ if (typeof path.key !== "number") return;
41
+ const kind = path.node.kind;
42
+
43
+ // get declaration after this
44
+ const nextDeclaration = path.getSibling(path.key + 1);
68
45
  if (
69
- stmt.type === "ReturnStatement" ||
70
- stmt.type === "BreakStatement" ||
71
- stmt.type === "ContinueStatement" ||
72
- stmt.type === "ThrowStatement"
46
+ nextDeclaration.isVariableDeclaration({
47
+ kind: kind,
48
+ })
73
49
  ) {
74
- if (earlyReturn > i + 1) {
75
- earlyReturn = i + 1;
50
+ const declarations = path.get("declarations");
51
+
52
+ // Preserve bindings!
53
+ // This is important for dead code elimination
54
+ const bindings: { [name: string]: Binding } = Object.create(null);
55
+ for (var declaration of declarations) {
56
+ for (var idPath of Object.values(
57
+ declaration.getBindingIdentifierPaths()
58
+ )) {
59
+ bindings[idPath.node.name] = idPath.scope.getBinding(
60
+ idPath.node.name
61
+ );
62
+ }
76
63
  }
77
- }
78
-
79
- if (stmt.type == "FunctionDeclaration") {
80
- fnDecs.push([stmt, i]);
81
- }
82
- });
83
-
84
- if (earlyReturn < body.length) {
85
- body.length = earlyReturn;
86
- body.push(
87
- ...fnDecs.filter((x) => x[1] >= earlyReturn).map((x) => x[0])
88
- );
89
- }
90
-
91
- // Now combine ExpressionStatements
92
64
 
93
- if (body.length > 1) {
94
- var exprs = [];
95
- var startIndex = -1;
96
-
97
- var sequences: { index: number; exprs: Node[] }[] = [];
65
+ nextDeclaration.node.declarations.unshift(
66
+ ...declarations.map((x) => x.node)
67
+ );
98
68
 
99
- body.forEach((stmt, i) => {
100
- if (stmt.type == "ExpressionStatement" && !stmt.directive) {
101
- exprs.push(stmt.expression);
102
- if (startIndex == -1) {
103
- startIndex = i;
104
- }
105
- } else {
106
- if (exprs.length) {
107
- sequences.push({ exprs: exprs, index: startIndex });
69
+ const newBindingIdentifierPaths =
70
+ nextDeclaration.getBindingIdentifierPaths();
71
+
72
+ // path.remove() unfortunately removes the bindings
73
+ // We must perverse the entire binding object (referencePaths, constantViolations, etc)
74
+ // and re-add them to the new scope
75
+ path.remove();
76
+
77
+ // Add bindings back
78
+ function addBindingsToScope(scope: Scope) {
79
+ for (var name in bindings) {
80
+ const binding = bindings[name];
81
+ if (binding) {
82
+ binding.path = newBindingIdentifierPaths[name];
83
+ scope.bindings[name] = binding;
84
+ }
108
85
  }
109
- exprs = [];
110
- startIndex = -1;
111
86
  }
112
- });
113
87
 
114
- if (exprs.length) {
115
- sequences.push({ exprs: exprs, index: startIndex });
88
+ if (kind === "var") {
89
+ addBindingsToScope(getParentFunctionOrProgram(path).scope);
90
+ }
91
+ addBindingsToScope(path.scope);
116
92
  }
117
-
118
- sequences.reverse().forEach((seq) => {
119
- ok(seq.index != -1);
120
- body.splice(
121
- seq.index,
122
- seq.exprs.length,
123
- ExpressionStatement(
124
- seq.exprs.length == 1
125
- ? seq.exprs[0]
126
- : SequenceExpression(seq.exprs)
127
- )
93
+ },
94
+ },
95
+ // true -> !0, false -> !1
96
+ BooleanLiteral: {
97
+ exit(path) {
98
+ if (path.node.value) {
99
+ path.replaceWith(t.unaryExpression("!", t.numericLiteral(0)));
100
+ } else {
101
+ path.replaceWith(t.unaryExpression("!", t.numericLiteral(1)));
102
+ }
103
+ },
104
+ },
105
+ // !"" -> !1
106
+ UnaryExpression: {
107
+ exit(path) {
108
+ if (path.node.operator === "!") {
109
+ var argument = path.get("argument");
110
+ if (argument.isNumericLiteral()) return;
111
+ const value = argument.evaluateTruthy();
112
+ const parent = getParentFunctionOrProgram(path);
113
+ if (parent && (parent.node as NodeSymbol)[UNSAFE]) return;
114
+
115
+ if (value === undefined) return;
116
+
117
+ path.replaceWith(
118
+ t.unaryExpression("!", t.numericLiteral(value ? 1 : 0))
128
119
  );
129
- });
130
- }
131
-
132
- // Unnecessary return
133
- if (
134
- parents[0] &&
135
- isVarContext(parents[0]) &&
136
- body.length &&
137
- body[body.length - 1]
138
- ) {
139
- var last = body[body.length - 1];
140
- if (last.type == "ReturnStatement") {
141
- var isUndefined = last.argument == null;
142
-
143
- if (isUndefined) {
144
- body.pop();
120
+ }
121
+ },
122
+ },
123
+ // a["key"] -> a.key
124
+ MemberExpression: {
125
+ exit(path) {
126
+ if (!path.node.computed) return;
127
+
128
+ const property = path.get("property");
129
+ if (!property.isStringLiteral()) return;
130
+
131
+ const key = property.node.value;
132
+ if (!t.isValidIdentifier(key)) return;
133
+
134
+ path.node.computed = false;
135
+ path.node.property = t.identifier(key);
136
+ },
137
+ },
138
+ // {["key"]: 1} -> {key: 1}
139
+ // {"key": 1} -> {key: 1}
140
+ ObjectProperty: {
141
+ exit(path) {
142
+ var key = path.get("key");
143
+ if (path.node.computed && key.isStringLiteral()) {
144
+ path.node.computed = false;
145
+ }
146
+
147
+ if (
148
+ !path.node.computed &&
149
+ key.isStringLiteral() &&
150
+ t.isValidIdentifier(key.node.value)
151
+ ) {
152
+ if (identifierMap.has(key.node.value)) {
153
+ path.node.computed = true;
154
+ key.replaceWith(identifierMap.get(key.node.value)!());
155
+ } else {
156
+ key.replaceWith(t.identifier(key.node.value));
145
157
  }
146
158
  }
147
- }
148
-
149
- // Variable declaration grouping
150
- // var a = 1;
151
- // var b = 1;
152
- // var c = 1;
153
- //
154
- // var a=1,b=1,c=1;
155
- var lastDec = null;
156
-
157
- var remove = [];
158
- body.forEach((x, i) => {
159
- if (x.type === "VariableDeclaration") {
159
+ },
160
+ },
161
+ // (a); -> a;
162
+ SequenceExpression: {
163
+ exit(path) {
164
+ if (path.node.expressions.length === 1) {
165
+ path.replaceWith(path.node.expressions[0]);
166
+ }
167
+ },
168
+ },
169
+ // ; -> ()
170
+ EmptyStatement: {
171
+ exit(path) {
172
+ path.remove();
173
+ },
174
+ },
175
+ // console; -> ();
176
+ ExpressionStatement: {
177
+ exit(path) {
178
+ if (path.get("expression").isIdentifier()) {
179
+ // Preserve last expression of program for RGF
160
180
  if (
161
- !lastDec ||
162
- lastDec.kind !== x.kind ||
163
- !lastDec.declarations.length
164
- ) {
165
- lastDec = x;
166
- } else {
167
- lastDec.declarations.push(...clone(x.declarations));
168
- remove.unshift(i);
181
+ path.parentPath?.isProgram() &&
182
+ path.parentPath?.get("body").at(-1) === path
183
+ )
184
+ return;
185
+ path.remove();
186
+ }
187
+ },
188
+ },
189
+ // undefined -> void 0
190
+ // Infinity -> 1/0
191
+ Identifier: {
192
+ exit(path) {
193
+ if (path.isReferencedIdentifier()) {
194
+ if (identifierMap.has(path.node.name)) {
195
+ ensureComputedExpression(path);
196
+ path.replaceWith(identifierMap.get(path.node.name)!());
169
197
  }
170
- } else {
171
- lastDec = null;
172
198
  }
173
- });
174
-
175
- remove.forEach((x) => {
176
- body.splice(x, 1);
177
- });
178
- };
179
- }
180
-
181
- /**
182
- * ES6 and higher only
183
- * - `function(){}` -> `()=>{}`
184
- * - `function abc(){}` -> `var abc = ()=>{}`
185
- */
186
- if (
187
- !this.options.es5 &&
188
- (object.type == "FunctionExpression" ||
189
- object.type == "FunctionDeclaration")
190
- ) {
191
- return () => {
192
- // Don't touch `{get key(){...}}`
193
- var propIndex = parents.findIndex(
194
- (x) => x.type == "Property" || x.type == "MethodDefinition"
195
- );
196
- if (propIndex !== -1) {
197
- if (parents[propIndex].value === (parents[propIndex - 1] || object)) {
199
+ },
200
+ },
201
+ // true ? a : b -> a
202
+ ConditionalExpression: {
203
+ exit(path) {
204
+ const testValue = path.get("test").evaluateTruthy();
205
+ if (testValue === undefined) return;
206
+
207
+ path.replaceWith(
208
+ testValue ? path.node.consequent : path.node.alternate
209
+ );
210
+ },
211
+ },
212
+ // Remove unused functions
213
+ FunctionDeclaration: {
214
+ exit(path) {
215
+ const id = path.get("id");
216
+ if (
217
+ id.isIdentifier() &&
218
+ !id.node.name.startsWith(placeholderVariablePrefix)
219
+ ) {
220
+ const binding = path.scope.getBinding(id.node.name);
198
221
  if (
199
- parents[propIndex].kind !== "init" ||
200
- parents[propIndex].method
222
+ binding &&
223
+ binding.constantViolations.length === 0 &&
224
+ binding.referencePaths.length === 0 &&
225
+ !binding.referenced
201
226
  ) {
202
- return;
227
+ path.remove();
203
228
  }
204
229
  }
205
- }
230
+ },
231
+ },
232
+ // var x=undefined -> var x
233
+ // Remove unused variables
234
+ // Simple destructuring
235
+ VariableDeclarator: {
236
+ exit(path) {
237
+ if (isUndefined(path.get("init"))) {
238
+ path.node.init = null;
239
+ }
206
240
 
207
- if (object.type === "FunctionDeclaration") {
208
- var body = parents[0];
209
- if (!Array.isArray(body)) {
210
- return;
241
+ const id = path.get("id");
242
+ const init = path.get("init");
243
+
244
+ // Simple array/object destructuring
245
+ if (id.isArrayPattern() && init.isArrayExpression()) {
246
+ const elements = id.get("elements");
247
+ const initElements = init.get("elements");
248
+
249
+ if (elements.length === 1 && initElements.length === 1) {
250
+ id.replaceWith(elements[0]);
251
+ init.replaceWith(initElements[0]);
252
+ }
211
253
  }
212
254
 
213
- var index = body.indexOf(object);
214
- if (index == -1) {
215
- return;
255
+ if (id.isObjectPattern() && init.isObjectExpression()) {
256
+ const properties = id.get("properties");
257
+ const initProperties = init.get("properties");
258
+
259
+ if (properties.length === 1 && initProperties.length === 1) {
260
+ const firstProperty = properties[0];
261
+ const firstInitProperty = initProperties[0];
262
+
263
+ if (
264
+ firstProperty.isObjectProperty() &&
265
+ firstInitProperty.isObjectProperty()
266
+ ) {
267
+ const firstKey = firstProperty.get("key");
268
+ const firstInitKey = firstInitProperty.get("key");
269
+ if (
270
+ firstKey.isIdentifier() &&
271
+ firstInitKey.isIdentifier() &&
272
+ firstKey.node.name === firstInitKey.node.name
273
+ ) {
274
+ id.replaceWith(firstProperty.node.value);
275
+ init.replaceWith(firstInitProperty.node.value);
276
+ }
277
+ }
278
+ }
216
279
  }
217
280
 
218
- var before = body.slice(0, index);
219
- ok(!before.includes(object));
281
+ // Remove unused variables
282
+ // Can only remove if it's pure
283
+ if (id.isIdentifier()) {
284
+ // Do not remove variables in unsafe functions
285
+ const fn = getParentFunctionOrProgram(path);
286
+ if ((fn.node as NodeSymbol)[UNSAFE]) return;
220
287
 
221
- var beforeTypes = new Set(before.map((x) => x.type));
222
- beforeTypes.delete("FunctionDeclaration");
288
+ const binding = path.scope.getBinding(id.node.name);
223
289
 
224
- if (beforeTypes.size > 0) {
225
- return;
290
+ if (
291
+ binding &&
292
+ binding.constantViolations.length === 0 &&
293
+ binding.referencePaths.length === 0
294
+ ) {
295
+ if (!init.node || init.isPure()) {
296
+ path.remove();
297
+ } else if (
298
+ path.parentPath.isVariableDeclaration() &&
299
+ path.parentPath.node.declarations.length === 1
300
+ ) {
301
+ path.parentPath.replaceWith(t.expressionStatement(init.node));
302
+ }
303
+ }
304
+ }
305
+ },
306
+ },
307
+ // return undefined->return
308
+ ReturnStatement: {
309
+ exit(path) {
310
+ if (isUndefined(path.get("argument"))) {
311
+ path.node.argument = null;
312
+ }
313
+ },
314
+ },
315
+ // while(true) {a();} -> while(true) a();
316
+ // for(;;) {a();} -> for(;;) a();
317
+ // with(a) {a();} -> with(a) a();
318
+ "While|For|WithStatement": {
319
+ exit(_path) {
320
+ var path = _path as NodePath<t.While | t.For | t.WithStatement>;
321
+ var body = path.get("body");
322
+
323
+ if (body.isBlock() && body.node.body.length === 1) {
324
+ body.replaceWith(body.node.body[0]);
226
325
  }
326
+ },
327
+ },
328
+ // if(a) a(); -> a && a();
329
+ // if(a) { return b; } -> if(a) return b;
330
+ // if(a) { a(); } else { b(); } -> a ? a() : b();
331
+ // if(a) { return b; } else { return c; } -> return a ? b : c;
332
+ IfStatement: {
333
+ exit(path) {
334
+ // BlockStatement to single statement
335
+ const consequent = path.get("consequent");
336
+ const alternate = path.get("alternate");
337
+
338
+ const isMoveable = (node: t.Statement) => {
339
+ if (t.isDeclaration(node)) return false;
340
+
341
+ return true;
342
+ };
227
343
 
228
- // Test Variant #25: Don't break redefined function declaration
229
- if (
230
- object.id &&
231
- body.find(
232
- (x) =>
233
- x.type === "FunctionDeclaration" &&
234
- x !== object &&
235
- x.id &&
236
- x.id.name === object.id.name
237
- )
238
- ) {
239
- return;
344
+ let testValue = path.get("test").evaluateTruthy();
345
+
346
+ const parent = getParentFunctionOrProgram(path);
347
+ if (parent && (parent.node as NodeSymbol)[UNSAFE]) {
348
+ testValue = undefined;
240
349
  }
241
- }
242
-
243
- var canTransform = true;
244
- walk(object.body, [], ($object, $parents) => {
245
- if ($object.type == "ThisExpression") {
246
- canTransform = false;
247
- } else if ($object.type == "Identifier") {
248
- if ($object.name == "arguments") {
249
- canTransform = false;
350
+
351
+ if (typeof testValue !== "undefined") {
352
+ if (
353
+ !alternate.node &&
354
+ consequent.isBlock() &&
355
+ consequent.node.body.length === 1 &&
356
+ isMoveable(consequent.node.body[0])
357
+ ) {
358
+ consequent.replaceWith(consequent.node.body[0]);
250
359
  }
251
- if ($object.name == "this") {
252
- this.error(new Error("Use ThisExpression instead"));
360
+
361
+ if (
362
+ alternate.node &&
363
+ alternate.isBlock() &&
364
+ alternate.node.body.length === 1 &&
365
+ isMoveable(alternate.node.body[0])
366
+ ) {
367
+ alternate.replaceWith(alternate.node.body[0]);
253
368
  }
254
369
  }
255
- });
256
-
257
- if (canTransform) {
258
- if (!this.arrowFunctionName) {
259
- this.arrowFunctionName = this.getPlaceholder();
260
-
261
- append(
262
- parents[parents.length - 1] || object,
263
- Template(`
264
- function ${this.arrowFunctionName}(arrowFn, functionLength = 0){
265
- var functionObject = function(){ return arrowFn(...arguments) };
266
-
267
- ${
268
- this.options.preserveFunctionLength
269
- ? `return {ObjectDefineProperty}(functionObject, "length", {
270
- "value": functionLength,
271
- "configurable": true
272
- });`
273
- : `return functionObject`
274
- }
275
-
370
+
371
+ if (testValue === false) {
372
+ // if(false){} -> ()
373
+ if (!alternate.node) {
374
+ path.remove();
375
+ return;
376
+
377
+ // if(false){a()}else{b()} -> b()
378
+ } else {
379
+ path.replaceWith(alternate.node);
380
+ return;
276
381
  }
277
- `).single({
278
- ObjectDefineProperty: this.options.preserveFunctionLength
279
- ? this.createInitVariable(ObjectDefineProperty, parents)
280
- : undefined,
281
- })
282
- );
382
+
383
+ // if(true){a()} -> {a()}
384
+ } else if (testValue === true) {
385
+ path.replaceWith(consequent.node);
386
+ return;
283
387
  }
284
388
 
285
- const wrap = (object: Node) => {
286
- var args: Node[] = [clone(object)];
287
- var fnLength = computeFunctionLength(object.params);
288
- if (this.options.preserveFunctionLength && fnLength != 0) {
289
- args.push(Literal(fnLength));
290
- }
291
- return CallExpression(Identifier(this.arrowFunctionName), args);
292
- };
389
+ function getResult(path: NodePath): {
390
+ returnPath: NodePath<t.ReturnStatement> | null;
391
+ expressions: t.Expression[];
392
+ } {
393
+ if (!path.node) return null;
293
394
 
294
- if (object.type == "FunctionExpression") {
295
- object.type = "ArrowFunctionExpression";
395
+ if (path.isReturnStatement()) {
396
+ return { returnPath: path, expressions: [] };
397
+ }
398
+ if (path.isExpressionStatement()) {
399
+ return {
400
+ returnPath: null,
401
+ expressions: [path.get("expression").node],
402
+ };
403
+ }
296
404
 
297
- this.replace(object, wrap(clone(object)));
298
- } else {
299
- var arrow = { ...clone(object), type: "ArrowFunctionExpression" };
300
- this.replace(
301
- object,
302
- VariableDeclaration(
303
- VariableDeclarator(object.id.name, wrap(arrow))
304
- )
305
- );
405
+ if (path.isBlockStatement()) {
406
+ var expressions = [];
407
+ for (var statement of path.get("body")) {
408
+ if (statement.isReturnStatement()) {
409
+ return { returnPath: statement, expressions: expressions };
410
+ } else if (statement.isExpressionStatement()) {
411
+ expressions.push(statement.get("expression").node);
412
+ } else {
413
+ return null;
414
+ }
415
+ }
306
416
 
307
- var x = this.transform(arrow, []);
308
- if (typeof x === "function") {
309
- x();
417
+ return { returnPath: null, expressions: expressions };
310
418
  }
419
+
420
+ return null;
311
421
  }
312
- }
313
- };
314
- }
315
-
316
- /**
317
- * ()=>{ expr } -> ()=>expr
318
- */
319
- if (
320
- object.type == "ArrowFunctionExpression" &&
321
- object.body.type == "BlockStatement"
322
- ) {
323
- return () => {
324
- var body = getBlockBody(object.body);
325
- var stmt1 = body[0];
326
-
327
- if (body.length == 1 && stmt1.type == "ReturnStatement") {
328
- // x=>{a: 1} // Invalid syntax
329
- if (stmt1.argument && stmt1.argument.type != "ObjectExpression") {
330
- object.body = stmt1.argument;
331
- object.expression = true;
332
- }
333
- } else {
334
- // ()=>{exprStmt;exprStmt;} -> ()=>(expr, expr, expr, undefined)
335
- var exprs = body.filter((x) => x.type == "ExpressionStatement");
336
- if (exprs.length == body.length) {
337
- var array: Node[] = [];
338
- function flatten(expr) {
339
- if (expr.type == "SequenceExpression") {
340
- expr.expressions.forEach(flatten);
341
- } else if (expr.type == "ExpressionStatement") {
342
- flatten(expr.expression);
343
- } else {
344
- array.push(expr);
422
+
423
+ var consequentReturn = getResult(consequent);
424
+ var alternateReturn = getResult(alternate);
425
+
426
+ if (consequentReturn && alternateReturn) {
427
+ if (consequentReturn.returnPath && alternateReturn.returnPath) {
428
+ function createReturnArgument(
429
+ resultInfo: ReturnType<typeof getResult>
430
+ ) {
431
+ return t.sequenceExpression([
432
+ ...resultInfo.expressions,
433
+ resultInfo.returnPath.node.argument ||
434
+ t.identifier("undefined"),
435
+ ]);
345
436
  }
346
- }
347
437
 
348
- body.forEach(flatten);
438
+ path.replaceWith(
439
+ t.returnStatement(
440
+ t.conditionalExpression(
441
+ path.node.test,
442
+ createReturnArgument(consequentReturn),
443
+ createReturnArgument(alternateReturn)
444
+ )
445
+ )
446
+ );
447
+ } else if (
448
+ !consequentReturn.returnPath &&
449
+ !alternateReturn.returnPath
450
+ ) {
451
+ function joinExpressions(expressions: t.Expression[]) {
452
+ // condition?():() is invalid syntax
453
+ // Just use 0 as a placeholder
454
+ if (expressions.length === 0) return t.numericLiteral(0);
349
455
 
350
- object.body = SequenceExpression([
351
- ...clone(array),
352
- UnaryExpression("void", Literal(0)),
353
- ]);
354
- }
355
- }
356
- };
357
- }
358
-
359
- // (a()) -> a()
360
- if (object.type == "SequenceExpression") {
361
- return () => {
362
- if (object.expressions.length == 1) {
363
- this.replace(object, clone(object.expressions[0]));
364
- }
365
- };
366
- }
367
-
368
- // a += -1 -> a -= 1
369
- if (object.type == "AssignmentExpression") {
370
- return () => {
371
- if (
372
- object.operator == "+=" &&
373
- object.right.type == "UnaryExpression" &&
374
- object.right.operator == "-"
375
- ) {
376
- object.operator = "-=";
377
- object.right = object.right.argument;
378
- } else if (
379
- // a -= -1 -> a += 1
380
- object.operator == "-=" &&
381
- object.right.type == "UnaryExpression" &&
382
- object.right.operator == "-"
383
- ) {
384
- object.operator = "+=";
385
- object.right = object.right.argument;
386
- }
387
- };
388
- }
389
-
390
- // a + -b -> a - b
391
- if (object.type == "BinaryExpression") {
392
- return () => {
393
- if (
394
- object.operator == "+" &&
395
- object.right.type == "UnaryExpression" &&
396
- object.right.operator == "-"
397
- ) {
398
- object.operator = "-";
399
- object.right = object.right.argument;
400
- } else if (
401
- // a - -1 -> a + 1
402
- object.operator == "-" &&
403
- object.right.type == "UnaryExpression" &&
404
- object.right.operator == "-"
405
- ) {
406
- object.operator = "+";
407
- object.right = object.right.argument;
408
- }
409
- };
410
- }
411
-
412
- if (
413
- object.type == "ForStatement" ||
414
- object.type == "ForInStatement" ||
415
- object.type == "ForOfStatement" ||
416
- object.type == "WhileStatement"
417
- ) {
418
- if (object.body.type == "BlockStatement") {
419
- return () => {
420
- if (object.body.body.length === 1) {
421
- object.body = object.body.body[0];
422
- }
423
- };
424
- }
425
- }
426
-
427
- // Last switch case does not need break
428
- if (object.type == "SwitchStatement") {
429
- var last = object.cases[object.cases.length - 1];
430
- if (last) {
431
- var lastStatement = last.consequent[last.consequent.length - 1];
432
- if (
433
- lastStatement &&
434
- lastStatement.type == "BreakStatement" &&
435
- lastStatement.label == null
436
- ) {
437
- last.consequent.pop();
438
- }
439
- } else {
440
- if (
441
- object.cases.length == 0 &&
442
- (object.discriminant.type == "Literal" ||
443
- object.discriminant.type == "Identifier")
444
- ) {
445
- if (
446
- parents[0].type == "LabeledStatement" &&
447
- Array.isArray(parents[1])
448
- ) {
449
- return () => {
450
- parents[1].splice(parents[1].indexOf(parents[0]), 1);
451
- };
452
- } else if (Array.isArray(parents[0])) {
453
- return () => {
454
- parents[0].splice(parents[0].indexOf(object), 1);
455
- };
456
- }
457
- }
458
- }
459
- }
460
-
461
- // if ( x ) { y() } -> x && y()
462
- // Todo Make this shit readable
463
- if (object.type == "IfStatement") {
464
- if (object.consequent.type != "BlockStatement") {
465
- this.replace(
466
- object.consequent,
467
- BlockStatement([clone(object.consequent)])
468
- );
469
- }
470
- if (object.alternate && object.alternate.type != "BlockStatement") {
471
- this.replace(
472
- object.alternate,
473
- BlockStatement([clone(object.alternate)])
474
- );
475
- }
476
- var body = getBlockBody(object.consequent);
477
-
478
- // Check for hard-coded if statements
479
- if (object.test.type == "Literal") {
480
- if (object.test.value || object.test.regex) {
481
- // Why would anyone test just a regex literal
482
- object.alternate = null;
483
- } else {
484
- object.consequent = BlockStatement([]);
485
- }
486
- }
487
-
488
- return () => {
489
- // if ( a ) { } else {b()} -> if ( !a ) b();
490
- if (body.length == 0 && object.alternate) {
491
- object.test = UnaryExpression("!", clone(object.test));
492
- if (
493
- object.alternate.type == "BlockStatement" &&
494
- object.alternate.body.length == 1
495
- ) {
496
- object.alternate = clone(object.alternate.body[0]);
497
- }
498
- object.consequent = object.alternate;
499
- object.alternate = null;
500
- }
501
-
502
- if (
503
- object.consequent &&
504
- object.consequent.body &&
505
- object.consequent.body.length == 1 &&
506
- object.alternate &&
507
- object.alternate.body.length == 1
508
- ) {
509
- var stmt1 = clone(object.consequent.body[0]);
510
- var stmt2 = clone(object.alternate.body[0]);
511
-
512
- // if (a) {return b;} else {return c;} -> return a ? b : c;
513
- if (
514
- stmt1.type == "ReturnStatement" &&
515
- stmt2.type == "ReturnStatement"
516
- ) {
517
- this.replace(
518
- object,
519
- ReturnStatement(
520
- ConditionalExpression(
521
- clone(object.test),
522
- stmt1.argument || Identifier("undefined"),
523
- stmt2.argument || Identifier("undefined")
456
+ // No need for sequence expression if there's only one expression
457
+ if (expressions.length === 1) return expressions[0];
458
+
459
+ return t.sequenceExpression(expressions);
460
+ }
461
+
462
+ path.replaceWith(
463
+ t.conditionalExpression(
464
+ path.node.test,
465
+ joinExpressions(consequentReturn.expressions),
466
+ joinExpressions(alternateReturn.expressions)
524
467
  )
525
- )
526
- );
468
+ );
469
+ }
527
470
  }
471
+ },
472
+ },
473
+ // Remove unreachable code
474
+ // Code after a return/throw/break/continue is unreachable
475
+ // Remove implied returns
476
+ // Remove code after if all branches are unreachable
477
+ "Block|SwitchCase": {
478
+ enter(path) {
479
+ if (path.isProgram()) {
480
+ path.scope.crawl();
481
+ }
482
+ },
483
+ exit(path) {
484
+ var statementList = path.isBlock()
485
+ ? (path.get("body") as NodePath<t.Statement>[])
486
+ : (path.get("consequent") as NodePath<t.Statement>[]);
528
487
 
529
- // if (a) {b = 0} else {b = 1} -> b = a ? 0 : 1;
530
- if (
531
- stmt1.type == "ExpressionStatement" &&
532
- stmt2.type == "ExpressionStatement"
488
+ var impliedReturn: NodePath<t.ReturnStatement>;
489
+
490
+ function isUnreachable(
491
+ statementList: NodePath<t.Statement>[],
492
+ topLevel = false
533
493
  ) {
534
- var e1 = stmt1.expression;
535
- var e2 = stmt2.expression;
494
+ var unreachableState = false;
495
+
496
+ for (var statement of statementList) {
497
+ if (unreachableState) {
498
+ statement.remove();
499
+ continue;
500
+ }
501
+
502
+ if (statement.isIfStatement()) {
503
+ const consequent = statement.get("consequent");
504
+ const alternate = statement.get("alternate");
505
+
506
+ if (
507
+ [consequent, alternate].every(
508
+ (x) =>
509
+ x.node &&
510
+ x.isBlockStatement() &&
511
+ isUnreachable(x.get("body"))
512
+ )
513
+ ) {
514
+ unreachableState = true;
515
+ if (!topLevel) {
516
+ return true;
517
+ } else {
518
+ continue;
519
+ }
520
+ }
521
+ }
522
+
523
+ if (statement.isSwitchStatement()) {
524
+ // Can only remove switch statements if all cases are unreachable
525
+ // And all paths are exhausted
526
+ const cases = statement.get("cases");
527
+ const hasDefaultCase = cases.some((x) => !x.node.test);
528
+ if (
529
+ hasDefaultCase &&
530
+ cases.every((x) => isUnreachable(x.get("consequent")))
531
+ ) {
532
+ unreachableState = true;
533
+ if (!topLevel) {
534
+ return true;
535
+ } else {
536
+ continue;
537
+ }
538
+ }
539
+ }
536
540
 
537
- if (
538
- e1.type == "AssignmentExpression" &&
539
- e2.type == "AssignmentExpression"
540
- ) {
541
541
  if (
542
- e1.operator === e2.operator &&
543
- isEquivalent(e1.left, e2.left)
542
+ statement.isReturnStatement() ||
543
+ statement.isThrowStatement() ||
544
+ statement.isBreakStatement() ||
545
+ statement.isContinueStatement()
544
546
  ) {
545
- this.replace(
546
- object,
547
- ExpressionStatement(
548
- AssignmentExpression(
549
- e1.operator,
550
- e1.left,
551
- ConditionalExpression(
552
- clone(object.test),
553
- e1.right,
554
- e2.right
555
- )
556
- )
557
- )
558
- );
547
+ unreachableState = true;
548
+ if (!topLevel) {
549
+ return true;
550
+ }
551
+ }
552
+
553
+ if (topLevel) {
554
+ if (
555
+ statement == statementList.at(-1) &&
556
+ statement.isReturnStatement() &&
557
+ !statement.node.argument
558
+ ) {
559
+ impliedReturn = statement;
560
+ }
559
561
  }
560
562
  }
563
+ return false;
561
564
  }
562
- }
563
- };
564
- }
565
-
566
- // x["abc"] -> x.abc
567
- if (object.type == "MemberExpression") {
568
- var { object: obj, property } = object;
569
-
570
- if (property.type == "Literal" && isValidIdentifier(property.value)) {
571
- object.computed = false;
572
- object.property.type = "Identifier";
573
- object.property.name = clone(object.property.value);
574
-
575
- // obj.name &&
576
- // this.log(
577
- // obj.name +
578
- // "['" +
579
- // object.property.name +
580
- // "'] -> " +
581
- // obj.name +
582
- // "." +
583
- // object.property.name
584
- // );
585
- }
586
- }
587
-
588
- if (object.type == "CallExpression") {
589
- if (object.callee.type == "MemberExpression") {
590
- var key = object.callee.computed
591
- ? object.callee.property.value
592
- : object.callee.property.name;
593
- if (key == "toString" && object.arguments.length == 0) {
594
- this.replace(
595
- object,
596
- BinaryExpression("+", Literal(""), clone(object.callee.object))
597
- );
598
- }
599
- }
600
- }
601
-
602
- // { "x": 1 } -> {x: 1}
603
- if (object.type === "Property" || object.type === "MethodDefinition") {
604
- if (
605
- object.key.type == "SequenceExpression" &&
606
- object.key.expressions.length == 1
607
- ) {
608
- object.key = object.key.expressions[0];
609
- object.computed = true;
610
- }
611
-
612
- if (
613
- object.key.type == "Literal" &&
614
- typeof object.key.value === "string" &&
615
- isValidIdentifier(object.key.value)
616
- ) {
617
- object.key.type = "Identifier";
618
- object.key.name = object.key.value;
619
- object.computed = false;
620
- }
621
- }
622
-
623
- if (object.type == "VariableDeclarator") {
624
- // undefined is not necessary
625
- if (object.init && object.init.type == "Identifier") {
626
- if (object.init.name == "undefined") {
627
- object.init = null;
628
- }
629
- }
630
-
631
- if (
632
- object.id.type == "ObjectPattern" &&
633
- object.init &&
634
- object.init.type == "ObjectExpression"
635
- ) {
636
- if (
637
- object.id.properties.length === 1 &&
638
- object.init.properties.length === 1
639
- ) {
640
- var key1 = object.id.properties[0].computed
641
- ? object.id.properties[0].key.value
642
- : object.id.properties[0].key.name;
643
- var key2 = object.init.properties[0].computed
644
- ? object.init.properties[0].key.value
645
- : object.init.properties[0].key.name;
646
-
647
- // console.log(key1, key2);
648
-
649
- if (key1 && key2 && key1 === key2) {
650
- object.id = object.id.properties[0].value;
651
- object.init = object.init.properties[0].value;
565
+
566
+ isUnreachable(statementList, true);
567
+
568
+ if (impliedReturn) {
569
+ var functionParent = path.getFunctionParent();
570
+ if (
571
+ functionParent &&
572
+ t.isBlockStatement(functionParent.node.body) &&
573
+ functionParent.node.body === path.node
574
+ ) {
575
+ impliedReturn.remove();
576
+ }
652
577
  }
653
- }
654
- }
655
-
656
- // check for redundant patterns
657
- if (
658
- object.id.type == "ArrayPattern" &&
659
- object.init &&
660
- typeof object.init === "object" &&
661
- object.init.type == "ArrayExpression"
662
- ) {
663
- if (
664
- object.id.elements.length == 1 &&
665
- object.init.elements.length == 1
666
- ) {
667
- object.id = object.id.elements[0];
668
- object.init = object.init.elements[0];
669
- }
670
- }
671
- }
672
-
673
- if (object.type == "Literal") {
674
- return () => {
675
- switch (typeof object.value) {
676
- case "boolean":
677
- this.replaceIdentifierOrLiteral(
678
- object,
679
- UnaryExpression("!", Literal(object.value ? 0 : 1)),
680
- parents
681
- );
682
- break;
683
- }
684
- };
685
- }
686
- if (object.type == "Identifier") {
687
- return () => {
688
- var info = getIdentifierInfo(object, parents);
689
- if (info.spec.isDefined || info.spec.isModified) return;
690
-
691
- if (object.name == "undefined" && !isForInitialize(object, parents)) {
692
- this.replaceIdentifierOrLiteral(
693
- object,
694
- UnaryExpression("void", Literal(0)),
695
- parents
696
- );
697
- } else if (object.name == "Infinity") {
698
- this.replaceIdentifierOrLiteral(
699
- object,
700
- BinaryExpression("/", Literal(1), Literal(0)),
701
- parents
702
- );
703
- }
704
- };
705
- }
706
-
707
- if (object.type == "UnaryExpression" && object.operator == "!") {
708
- if (object.argument.type == "Literal" && !object.argument.regex) {
709
- this.replace(object, Literal(!object.argument.value));
710
- }
711
- }
712
-
713
- if (object.type == "ConditionalExpression") {
714
- if (object.test.type == "Literal" && !object.test.regex) {
715
- this.replace(
716
- object,
717
- object.test.value ? object.consequent : object.alternate
718
- );
719
- }
720
- }
721
- }
722
- }
578
+ },
579
+ },
580
+ },
581
+ };
582
+ };