webfuscator 0.9.4

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 (289) hide show
  1. package/DISCLOSURE +9 -0
  2. package/LICENSE +21 -0
  3. package/README.md +83 -0
  4. package/dist/analysis/constant.d.ts +11 -0
  5. package/dist/analysis/constant.d.ts.map +1 -0
  6. package/dist/analysis/constant.js +261 -0
  7. package/dist/analysis/constant.js.map +1 -0
  8. package/dist/analysis/document-order.d.ts +5 -0
  9. package/dist/analysis/document-order.d.ts.map +1 -0
  10. package/dist/analysis/document-order.js +124 -0
  11. package/dist/analysis/document-order.js.map +1 -0
  12. package/dist/analysis/inlineability.d.ts +13 -0
  13. package/dist/analysis/inlineability.d.ts.map +1 -0
  14. package/dist/analysis/inlineability.js +270 -0
  15. package/dist/analysis/inlineability.js.map +1 -0
  16. package/dist/analysis/purity.d.ts +5 -0
  17. package/dist/analysis/purity.d.ts.map +1 -0
  18. package/dist/analysis/purity.js +281 -0
  19. package/dist/analysis/purity.js.map +1 -0
  20. package/dist/index.d.ts +3 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +2 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/obfuscator.d.ts +4 -0
  25. package/dist/obfuscator.d.ts.map +1 -0
  26. package/dist/obfuscator.js +222 -0
  27. package/dist/obfuscator.js.map +1 -0
  28. package/dist/options.d.ts +73 -0
  29. package/dist/options.d.ts.map +1 -0
  30. package/dist/options.js +2 -0
  31. package/dist/options.js.map +1 -0
  32. package/dist/preparation/dot-to-bracket.d.ts +16 -0
  33. package/dist/preparation/dot-to-bracket.d.ts.map +1 -0
  34. package/dist/preparation/dot-to-bracket.js +36 -0
  35. package/dist/preparation/dot-to-bracket.js.map +1 -0
  36. package/dist/preparation/expand-destructuring.d.ts +26 -0
  37. package/dist/preparation/expand-destructuring.d.ts.map +1 -0
  38. package/dist/preparation/expand-destructuring.js +154 -0
  39. package/dist/preparation/expand-destructuring.js.map +1 -0
  40. package/dist/preparation/expand-template-literals.d.ts +23 -0
  41. package/dist/preparation/expand-template-literals.d.ts.map +1 -0
  42. package/dist/preparation/expand-template-literals.js +78 -0
  43. package/dist/preparation/expand-template-literals.js.map +1 -0
  44. package/dist/preparation/rewrite-class-fields.d.ts +43 -0
  45. package/dist/preparation/rewrite-class-fields.d.ts.map +1 -0
  46. package/dist/preparation/rewrite-class-fields.js +270 -0
  47. package/dist/preparation/rewrite-class-fields.js.map +1 -0
  48. package/dist/preparation/split-sequence-expressions.d.ts +15 -0
  49. package/dist/preparation/split-sequence-expressions.d.ts.map +1 -0
  50. package/dist/preparation/split-sequence-expressions.js +36 -0
  51. package/dist/preparation/split-sequence-expressions.js.map +1 -0
  52. package/dist/preparation/split-variable-declarations.d.ts +20 -0
  53. package/dist/preparation/split-variable-declarations.d.ts.map +1 -0
  54. package/dist/preparation/split-variable-declarations.js +36 -0
  55. package/dist/preparation/split-variable-declarations.js.map +1 -0
  56. package/dist/preparation/stringify-object-keys.d.ts +21 -0
  57. package/dist/preparation/stringify-object-keys.d.ts.map +1 -0
  58. package/dist/preparation/stringify-object-keys.js +96 -0
  59. package/dist/preparation/stringify-object-keys.js.map +1 -0
  60. package/dist/preparation/wrap-single-statements.d.ts +24 -0
  61. package/dist/preparation/wrap-single-statements.d.ts.map +1 -0
  62. package/dist/preparation/wrap-single-statements.js +53 -0
  63. package/dist/preparation/wrap-single-statements.js.map +1 -0
  64. package/dist/transforms/arguments-to-parameters.d.ts +20 -0
  65. package/dist/transforms/arguments-to-parameters.d.ts.map +1 -0
  66. package/dist/transforms/arguments-to-parameters.js +162 -0
  67. package/dist/transforms/arguments-to-parameters.js.map +1 -0
  68. package/dist/transforms/arrow-to-function.d.ts +46 -0
  69. package/dist/transforms/arrow-to-function.d.ts.map +1 -0
  70. package/dist/transforms/arrow-to-function.js +203 -0
  71. package/dist/transforms/arrow-to-function.js.map +1 -0
  72. package/dist/transforms/collapse-single-use-temps.d.ts +16 -0
  73. package/dist/transforms/collapse-single-use-temps.d.ts.map +1 -0
  74. package/dist/transforms/collapse-single-use-temps.js +805 -0
  75. package/dist/transforms/collapse-single-use-temps.js.map +1 -0
  76. package/dist/transforms/const-let-to-var.d.ts +21 -0
  77. package/dist/transforms/const-let-to-var.d.ts.map +1 -0
  78. package/dist/transforms/const-let-to-var.js +217 -0
  79. package/dist/transforms/const-let-to-var.js.map +1 -0
  80. package/dist/transforms/const-to-let.d.ts +17 -0
  81. package/dist/transforms/const-to-let.d.ts.map +1 -0
  82. package/dist/transforms/const-to-let.js +33 -0
  83. package/dist/transforms/const-to-let.js.map +1 -0
  84. package/dist/transforms/do-while-to-while.d.ts +26 -0
  85. package/dist/transforms/do-while-to-while.d.ts.map +1 -0
  86. package/dist/transforms/do-while-to-while.js +54 -0
  87. package/dist/transforms/do-while-to-while.js.map +1 -0
  88. package/dist/transforms/drop-console.d.ts +27 -0
  89. package/dist/transforms/drop-console.d.ts.map +1 -0
  90. package/dist/transforms/drop-console.js +107 -0
  91. package/dist/transforms/drop-console.js.map +1 -0
  92. package/dist/transforms/drop-debugger.d.ts +18 -0
  93. package/dist/transforms/drop-debugger.d.ts.map +1 -0
  94. package/dist/transforms/drop-debugger.js +34 -0
  95. package/dist/transforms/drop-debugger.js.map +1 -0
  96. package/dist/transforms/expand-binary-assignment.d.ts +26 -0
  97. package/dist/transforms/expand-binary-assignment.d.ts.map +1 -0
  98. package/dist/transforms/expand-binary-assignment.js +78 -0
  99. package/dist/transforms/expand-binary-assignment.js.map +1 -0
  100. package/dist/transforms/expand-logical-assignment.d.ts +22 -0
  101. package/dist/transforms/expand-logical-assignment.d.ts.map +1 -0
  102. package/dist/transforms/expand-logical-assignment.js +73 -0
  103. package/dist/transforms/expand-logical-assignment.js.map +1 -0
  104. package/dist/transforms/extract-object-properties.d.ts +21 -0
  105. package/dist/transforms/extract-object-properties.d.ts.map +1 -0
  106. package/dist/transforms/extract-object-properties.js +340 -0
  107. package/dist/transforms/extract-object-properties.js.map +1 -0
  108. package/dist/transforms/fold-builtin-methods.d.ts +23 -0
  109. package/dist/transforms/fold-builtin-methods.d.ts.map +1 -0
  110. package/dist/transforms/fold-builtin-methods.js +839 -0
  111. package/dist/transforms/fold-builtin-methods.js.map +1 -0
  112. package/dist/transforms/fold-constants.d.ts +16 -0
  113. package/dist/transforms/fold-constants.d.ts.map +1 -0
  114. package/dist/transforms/fold-constants.js +421 -0
  115. package/dist/transforms/fold-constants.js.map +1 -0
  116. package/dist/transforms/for-to-while.d.ts +29 -0
  117. package/dist/transforms/for-to-while.d.ts.map +1 -0
  118. package/dist/transforms/for-to-while.js +91 -0
  119. package/dist/transforms/for-to-while.js.map +1 -0
  120. package/dist/transforms/function-declaration-to-expression.d.ts +28 -0
  121. package/dist/transforms/function-declaration-to-expression.d.ts.map +1 -0
  122. package/dist/transforms/function-declaration-to-expression.js +95 -0
  123. package/dist/transforms/function-declaration-to-expression.js.map +1 -0
  124. package/dist/transforms/inline-functions.d.ts +16 -0
  125. package/dist/transforms/inline-functions.d.ts.map +1 -0
  126. package/dist/transforms/inline-functions.js +896 -0
  127. package/dist/transforms/inline-functions.js.map +1 -0
  128. package/dist/transforms/logical-to-ternary.d.ts +20 -0
  129. package/dist/transforms/logical-to-ternary.d.ts.map +1 -0
  130. package/dist/transforms/logical-to-ternary.js +78 -0
  131. package/dist/transforms/logical-to-ternary.js.map +1 -0
  132. package/dist/transforms/mangle-properties.d.ts +27 -0
  133. package/dist/transforms/mangle-properties.d.ts.map +1 -0
  134. package/dist/transforms/mangle-properties.js +703 -0
  135. package/dist/transforms/mangle-properties.js.map +1 -0
  136. package/dist/transforms/nullish-coalescing-to-ternary.d.ts +18 -0
  137. package/dist/transforms/nullish-coalescing-to-ternary.d.ts.map +1 -0
  138. package/dist/transforms/nullish-coalescing-to-ternary.js +61 -0
  139. package/dist/transforms/nullish-coalescing-to-ternary.js.map +1 -0
  140. package/dist/transforms/numbers-to-strings.d.ts +19 -0
  141. package/dist/transforms/numbers-to-strings.d.ts.map +1 -0
  142. package/dist/transforms/numbers-to-strings.js +51 -0
  143. package/dist/transforms/numbers-to-strings.js.map +1 -0
  144. package/dist/transforms/object-method-to-property.d.ts +34 -0
  145. package/dist/transforms/object-method-to-property.d.ts.map +1 -0
  146. package/dist/transforms/object-method-to-property.js +78 -0
  147. package/dist/transforms/object-method-to-property.js.map +1 -0
  148. package/dist/transforms/optional-chaining-to-ternary.d.ts +19 -0
  149. package/dist/transforms/optional-chaining-to-ternary.d.ts.map +1 -0
  150. package/dist/transforms/optional-chaining-to-ternary.js +206 -0
  151. package/dist/transforms/optional-chaining-to-ternary.js.map +1 -0
  152. package/dist/transforms/pack-declarations-into-parameters.d.ts +22 -0
  153. package/dist/transforms/pack-declarations-into-parameters.d.ts.map +1 -0
  154. package/dist/transforms/pack-declarations-into-parameters.js +208 -0
  155. package/dist/transforms/pack-declarations-into-parameters.js.map +1 -0
  156. package/dist/transforms/remove-anonymous-function-names.d.ts +17 -0
  157. package/dist/transforms/remove-anonymous-function-names.d.ts.map +1 -0
  158. package/dist/transforms/remove-anonymous-function-names.js +72 -0
  159. package/dist/transforms/remove-anonymous-function-names.js.map +1 -0
  160. package/dist/transforms/remove-unreachable-code.d.ts +21 -0
  161. package/dist/transforms/remove-unreachable-code.d.ts.map +1 -0
  162. package/dist/transforms/remove-unreachable-code.js +103 -0
  163. package/dist/transforms/remove-unreachable-code.js.map +1 -0
  164. package/dist/transforms/remove-unused-code.d.ts +18 -0
  165. package/dist/transforms/remove-unused-code.d.ts.map +1 -0
  166. package/dist/transforms/remove-unused-code.js +249 -0
  167. package/dist/transforms/remove-unused-code.js.map +1 -0
  168. package/dist/transforms/rename-identifiers.d.ts +24 -0
  169. package/dist/transforms/rename-identifiers.d.ts.map +1 -0
  170. package/dist/transforms/rename-identifiers.js +264 -0
  171. package/dist/transforms/rename-identifiers.js.map +1 -0
  172. package/dist/transforms/specials-to-strings.d.ts +30 -0
  173. package/dist/transforms/specials-to-strings.d.ts.map +1 -0
  174. package/dist/transforms/specials-to-strings.js +101 -0
  175. package/dist/transforms/specials-to-strings.js.map +1 -0
  176. package/dist/transforms/switch-to-if.d.ts +28 -0
  177. package/dist/transforms/switch-to-if.d.ts.map +1 -0
  178. package/dist/transforms/switch-to-if.js +279 -0
  179. package/dist/transforms/switch-to-if.js.map +1 -0
  180. package/dist/transforms/ternary-to-if.d.ts +20 -0
  181. package/dist/transforms/ternary-to-if.d.ts.map +1 -0
  182. package/dist/transforms/ternary-to-if.js +315 -0
  183. package/dist/transforms/ternary-to-if.js.map +1 -0
  184. package/dist/transforms/update-to-assignment.d.ts +26 -0
  185. package/dist/transforms/update-to-assignment.d.ts.map +1 -0
  186. package/dist/transforms/update-to-assignment.js +120 -0
  187. package/dist/transforms/update-to-assignment.js.map +1 -0
  188. package/dist/transforms/yodify-conditions.d.ts +22 -0
  189. package/dist/transforms/yodify-conditions.d.ts.map +1 -0
  190. package/dist/transforms/yodify-conditions.js +91 -0
  191. package/dist/transforms/yodify-conditions.js.map +1 -0
  192. package/dist/utils/ast.d.ts +10 -0
  193. package/dist/utils/ast.d.ts.map +1 -0
  194. package/dist/utils/ast.js +112 -0
  195. package/dist/utils/ast.js.map +1 -0
  196. package/dist/utils/change-tracking.d.ts +8 -0
  197. package/dist/utils/change-tracking.d.ts.map +1 -0
  198. package/dist/utils/change-tracking.js +8 -0
  199. package/dist/utils/change-tracking.js.map +1 -0
  200. package/dist/utils/dom-properties.d.ts +2 -0
  201. package/dist/utils/dom-properties.d.ts.map +1 -0
  202. package/dist/utils/dom-properties.js +9252 -0
  203. package/dist/utils/dom-properties.js.map +1 -0
  204. package/dist/utils/evaluation-order.d.ts +4 -0
  205. package/dist/utils/evaluation-order.d.ts.map +1 -0
  206. package/dist/utils/evaluation-order.js +152 -0
  207. package/dist/utils/evaluation-order.js.map +1 -0
  208. package/dist/utils/literal.d.ts +5 -0
  209. package/dist/utils/literal.d.ts.map +1 -0
  210. package/dist/utils/literal.js +90 -0
  211. package/dist/utils/literal.js.map +1 -0
  212. package/dist/utils/logical-lowering.d.ts +4 -0
  213. package/dist/utils/logical-lowering.d.ts.map +1 -0
  214. package/dist/utils/logical-lowering.js +104 -0
  215. package/dist/utils/logical-lowering.js.map +1 -0
  216. package/dist/utils/loop-lowering.d.ts +23 -0
  217. package/dist/utils/loop-lowering.d.ts.map +1 -0
  218. package/dist/utils/loop-lowering.js +88 -0
  219. package/dist/utils/loop-lowering.js.map +1 -0
  220. package/dist/utils/paths.d.ts +9 -0
  221. package/dist/utils/paths.d.ts.map +1 -0
  222. package/dist/utils/paths.js +90 -0
  223. package/dist/utils/paths.js.map +1 -0
  224. package/dist/utils/random.d.ts +3 -0
  225. package/dist/utils/random.d.ts.map +1 -0
  226. package/dist/utils/random.js +22 -0
  227. package/dist/utils/random.js.map +1 -0
  228. package/dist/utils/string-generator.d.ts +37 -0
  229. package/dist/utils/string-generator.d.ts.map +1 -0
  230. package/dist/utils/string-generator.js +175 -0
  231. package/dist/utils/string-generator.js.map +1 -0
  232. package/package.json +76 -0
  233. package/src/analysis/constant.ts +310 -0
  234. package/src/analysis/document-order.ts +141 -0
  235. package/src/analysis/inlineability.ts +336 -0
  236. package/src/analysis/purity.ts +315 -0
  237. package/src/index.ts +2 -0
  238. package/src/obfuscator.ts +271 -0
  239. package/src/options.ts +114 -0
  240. package/src/preparation/dot-to-bracket.ts +41 -0
  241. package/src/preparation/expand-destructuring.ts +211 -0
  242. package/src/preparation/expand-template-literals.ts +92 -0
  243. package/src/preparation/rewrite-class-fields.ts +320 -0
  244. package/src/preparation/split-sequence-expressions.ts +40 -0
  245. package/src/preparation/split-variable-declarations.ts +39 -0
  246. package/src/preparation/stringify-object-keys.ts +107 -0
  247. package/src/preparation/wrap-single-statements.ts +63 -0
  248. package/src/transforms/arguments-to-parameters.ts +187 -0
  249. package/src/transforms/arrow-to-function.ts +225 -0
  250. package/src/transforms/collapse-single-use-temps.ts +957 -0
  251. package/src/transforms/const-let-to-var.ts +246 -0
  252. package/src/transforms/const-to-let.ts +38 -0
  253. package/src/transforms/do-while-to-while.ts +69 -0
  254. package/src/transforms/drop-console.ts +119 -0
  255. package/src/transforms/drop-debugger.ts +38 -0
  256. package/src/transforms/expand-binary-assignment.ts +94 -0
  257. package/src/transforms/expand-logical-assignment.ts +84 -0
  258. package/src/transforms/extract-object-properties.ts +404 -0
  259. package/src/transforms/fold-builtin-methods.ts +949 -0
  260. package/src/transforms/fold-constants.ts +462 -0
  261. package/src/transforms/for-to-while.ts +109 -0
  262. package/src/transforms/function-declaration-to-expression.ts +108 -0
  263. package/src/transforms/inline-functions.ts +1074 -0
  264. package/src/transforms/logical-to-ternary.ts +92 -0
  265. package/src/transforms/mangle-properties.ts +819 -0
  266. package/src/transforms/nullish-coalescing-to-ternary.ts +77 -0
  267. package/src/transforms/numbers-to-strings.ts +61 -0
  268. package/src/transforms/object-method-to-property.ts +86 -0
  269. package/src/transforms/optional-chaining-to-ternary.ts +225 -0
  270. package/src/transforms/pack-declarations-into-parameters.ts +233 -0
  271. package/src/transforms/remove-anonymous-function-names.ts +84 -0
  272. package/src/transforms/remove-unreachable-code.ts +115 -0
  273. package/src/transforms/remove-unused-code.ts +296 -0
  274. package/src/transforms/rename-identifiers.ts +289 -0
  275. package/src/transforms/specials-to-strings.ts +116 -0
  276. package/src/transforms/switch-to-if.ts +339 -0
  277. package/src/transforms/ternary-to-if.ts +387 -0
  278. package/src/transforms/update-to-assignment.ts +132 -0
  279. package/src/transforms/yodify-conditions.ts +103 -0
  280. package/src/utils/ast.ts +129 -0
  281. package/src/utils/change-tracking.ts +15 -0
  282. package/src/utils/dom-properties.ts +9252 -0
  283. package/src/utils/evaluation-order.ts +178 -0
  284. package/src/utils/literal.ts +96 -0
  285. package/src/utils/logical-lowering.ts +132 -0
  286. package/src/utils/loop-lowering.ts +109 -0
  287. package/src/utils/paths.ts +103 -0
  288. package/src/utils/random.ts +22 -0
  289. package/src/utils/string-generator.ts +206 -0
@@ -0,0 +1,84 @@
1
+ import type { NodePath, Visitor } from '@babel/traverse'
2
+ import * as t from '@babel/types'
3
+ import type { File } from '@babel/types'
4
+
5
+ import { traverseForChanges } from 'src/utils/change-tracking'
6
+ import type { ChangeState } from 'src/utils/change-tracking'
7
+
8
+ /**
9
+ * Removes a function expression's name when the body does not use its binding
10
+ * and NamedEvaluation restores the same `.name`. Other positions could infer no
11
+ * name or a different one.
12
+ *
13
+ * @example
14
+ * // ◀️ before
15
+ * var greet = function greet() {};
16
+ * var a = function bar() {};
17
+ *
18
+ * // ▶️ after
19
+ * var greet = function () {};
20
+ * var a = function bar() {};
21
+ */
22
+ export function removeAnonymousFunctionNames(ast: File): boolean {
23
+ return traverseForChanges(ast, visitor)
24
+ }
25
+
26
+ const visitor: Visitor<ChangeState> = {
27
+ FunctionExpression(path, state) {
28
+ const { id } = path.node
29
+ if (!id) {
30
+ return
31
+ }
32
+ // Reads and writes both observe the expression's immutable inner binding.
33
+ const binding = path.scope.getBinding(id.name)
34
+ if (
35
+ binding &&
36
+ binding.identifier === id &&
37
+ (binding.references > 0 || binding.constantViolations.length > 0)
38
+ ) {
39
+ return
40
+ }
41
+ if (inferredName(path) !== id.name) {
42
+ return
43
+ }
44
+ path.node.id = null
45
+ state.changed = true
46
+ },
47
+ }
48
+
49
+ // No inferred name means `.name` would become empty.
50
+ function inferredName(path: NodePath<t.FunctionExpression>): string | null {
51
+ const { node } = path
52
+ const { parentPath } = path
53
+ if (!parentPath) {
54
+ return null
55
+ }
56
+ const parent = parentPath.node
57
+ if (t.isVariableDeclarator(parent) && parent.init === node && t.isIdentifier(parent.id)) {
58
+ return parent.id.name
59
+ }
60
+ if (
61
+ t.isAssignmentExpression(parent) &&
62
+ parent.operator === '=' &&
63
+ parent.right === node &&
64
+ t.isIdentifier(parent.left)
65
+ ) {
66
+ return parent.left.name
67
+ }
68
+ if (t.isAssignmentPattern(parent) && parent.right === node && t.isIdentifier(parent.left)) {
69
+ return parent.left.name
70
+ }
71
+ if (
72
+ (t.isObjectProperty(parent) || t.isClassProperty(parent)) &&
73
+ parent.value === node &&
74
+ !parent.computed
75
+ ) {
76
+ if (t.isIdentifier(parent.key)) {
77
+ return parent.key.name
78
+ }
79
+ if (t.isStringLiteral(parent.key)) {
80
+ return parent.key.value
81
+ }
82
+ }
83
+ return null
84
+ }
@@ -0,0 +1,115 @@
1
+ import type { NodePath, Visitor } from '@babel/traverse'
2
+ import * as t from '@babel/types'
3
+ import type { File } from '@babel/types'
4
+
5
+ import { traverseForChanges } from 'src/utils/change-tracking'
6
+ import type { ChangeState } from 'src/utils/change-tracking'
7
+
8
+ /**
9
+ * Removes statements after unconditional control flow in the same statement list.
10
+ *
11
+ * Hoisted function and `var` declarations stay because reachable code may still
12
+ * observe their bindings.
13
+ *
14
+ * @example
15
+ * // ◀️ before
16
+ * function foo() {
17
+ * return "bar";
18
+ * log("baz");
19
+ * }
20
+ *
21
+ * // ▶️ after
22
+ * function foo() {
23
+ * return "bar";
24
+ * }
25
+ */
26
+ export function removeUnreachableCode(ast: File): boolean {
27
+ return traverseForChanges(ast, visitor)
28
+ }
29
+
30
+ const visitor: Visitor<ChangeState> = {
31
+ BlockStatement(path, state) {
32
+ if (pruneBody(path.get('body'))) {
33
+ state.changed = true
34
+ }
35
+ },
36
+ SwitchCase(path, state) {
37
+ if (pruneBody(path.get('consequent'))) {
38
+ state.changed = true
39
+ }
40
+ },
41
+ }
42
+
43
+ function pruneBody(body: NodePath<t.Statement>[]): boolean {
44
+ let killZone = false
45
+ const toRemove: NodePath<t.Statement>[] = []
46
+ for (const stmt of body) {
47
+ if (killZone) {
48
+ if (isSafeToRemoveAfterCompletion(stmt)) {
49
+ toRemove.push(stmt)
50
+ }
51
+ continue
52
+ }
53
+ if (isCompletion(stmt.node)) {
54
+ killZone = true
55
+ }
56
+ }
57
+ for (const removed of toRemove) {
58
+ removed.remove()
59
+ }
60
+ return toRemove.length > 0
61
+ }
62
+
63
+ function isCompletion(node: t.Node): boolean {
64
+ return (
65
+ t.isReturnStatement(node) ||
66
+ t.isThrowStatement(node) ||
67
+ t.isBreakStatement(node) ||
68
+ t.isContinueStatement(node)
69
+ )
70
+ }
71
+
72
+ // Hoisted bindings and referenced lexical TDZ remain observable from reachable
73
+ // code. An unreferenced lexical declaration is inert.
74
+ function isSafeToRemoveAfterCompletion(path: NodePath<t.Statement>): boolean {
75
+ const { node } = path
76
+ if (t.isFunctionDeclaration(node) || (t.isVariableDeclaration(node) && node.kind === 'var')) {
77
+ return false
78
+ }
79
+ if (t.isClassDeclaration(node) || (t.isVariableDeclaration(node) && node.kind !== 'var')) {
80
+ return !declaresReferencedBinding(path)
81
+ }
82
+ return !nestsHoistedDeclaration(node)
83
+ }
84
+
85
+ // A write before `let x` also observes its TDZ.
86
+ function declaresReferencedBinding(path: NodePath<t.Statement>): boolean {
87
+ for (const name of Object.keys(path.getBindingIdentifiers())) {
88
+ const binding = path.scope.getBinding(name)
89
+ if (binding && (binding.references > 0 || binding.constantViolations.length > 0)) {
90
+ return true
91
+ }
92
+ }
93
+ return false
94
+ }
95
+
96
+ function nestsHoistedDeclaration(node: t.Node): boolean {
97
+ let found = false
98
+ t.traverseFast(node, (child) => {
99
+ if (found) {
100
+ return t.traverseFast.skip
101
+ }
102
+ if (child !== node && t.isFunction(child)) {
103
+ // Function declarations may add Annex B aliases. Function bodies do not.
104
+ if (t.isFunctionDeclaration(child)) {
105
+ found = true
106
+ }
107
+ return t.traverseFast.skip
108
+ }
109
+ if (t.isVariableDeclaration(child) && child.kind === 'var') {
110
+ found = true
111
+ }
112
+ return found ? t.traverseFast.skip : undefined
113
+ })
114
+ return found
115
+ }
@@ -0,0 +1,296 @@
1
+ import traverse from '@babel/traverse'
2
+ import type { NodePath, Scope, Visitor } from '@babel/traverse'
3
+ import * as t from '@babel/types'
4
+ import type { File } from '@babel/types'
5
+
6
+ import { isSideEffectFree } from 'src/analysis/purity'
7
+ import { isDirectEvalCall } from 'src/utils/ast'
8
+ import type { ChangeState } from 'src/utils/change-tracking'
9
+ import { hasAnnexBFunctionAlias } from 'src/utils/paths'
10
+
11
+ /**
12
+ * Removes unused declarations and expression statements that have no effects.
13
+ * Sweeps continue because each removal can strand another binding.
14
+ *
15
+ * @example
16
+ * // ◀️ before
17
+ * function foo() { return "foo"; }
18
+ * function bar() { return "bar"; }
19
+ * var unused = 1;
20
+ * foo();
21
+ *
22
+ * // ▶️ after
23
+ * function foo() { return "foo"; }
24
+ * foo();
25
+ */
26
+ export function removeUnusedCode(ast: File): boolean {
27
+ // Earlier relocation can leave references on detached paths. Rebuild once,
28
+ // then maintain counts during removal.
29
+ crawlProgramScope(ast)
30
+ let anyChanged = false
31
+ const state: SweepState = {
32
+ changed: true,
33
+ evalReachableScopes: collectEvalReachableScopes(ast),
34
+ }
35
+ while (state.changed) {
36
+ state.changed = false
37
+ traverse(ast, sweepVisitor, undefined, state)
38
+ if (state.changed) {
39
+ anyChanged = true
40
+ }
41
+ }
42
+ return anyChanged
43
+ }
44
+
45
+ function crawlProgramScope(ast: File): void {
46
+ traverse(ast, {
47
+ Program(path) {
48
+ path.scope.crawl()
49
+ path.stop()
50
+ },
51
+ })
52
+ }
53
+
54
+ // Direct eval can read otherwise unreferenced bindings in its scope ancestry.
55
+ // Sweep state keeps this set local to one pass.
56
+ interface SweepState extends ChangeState {
57
+ evalReachableScopes: Set<t.Node>
58
+ }
59
+
60
+ function collectEvalReachableScopes(ast: File): Set<t.Node> {
61
+ const reachable = new Set<t.Node>()
62
+ traverse(ast, {
63
+ CallExpression(path) {
64
+ if (!isDirectEvalCall(path.node)) {
65
+ return
66
+ }
67
+ let scope: Scope | undefined = path.scope
68
+ while (scope) {
69
+ reachable.add(scope.block)
70
+ scope = scope.parent
71
+ }
72
+ },
73
+ })
74
+ return reachable
75
+ }
76
+
77
+ function bindingReachableByEval(
78
+ binding: { scope: Scope },
79
+ evalReachableScopes: Set<t.Node>,
80
+ ): boolean {
81
+ return evalReachableScopes.has(binding.scope.block)
82
+ }
83
+
84
+ const sweepVisitor: Visitor<SweepState> = {
85
+ VariableDeclarator(path, state) {
86
+ if (tryRemoveDeclarator(path, state.evalReachableScopes)) {
87
+ state.changed = true
88
+ }
89
+ },
90
+ FunctionDeclaration(path, state) {
91
+ if (tryRemoveFunctionDecl(path, state.evalReachableScopes)) {
92
+ state.changed = true
93
+ }
94
+ },
95
+ ClassDeclaration(path, state) {
96
+ if (tryRemoveClassDecl(path, state.evalReachableScopes)) {
97
+ state.changed = true
98
+ }
99
+ },
100
+ ExpressionStatement(path, state) {
101
+ if (tryRemoveExpressionStatement(path)) {
102
+ state.changed = true
103
+ }
104
+ },
105
+ }
106
+
107
+ const dereferenceVisitor: Visitor = {
108
+ Identifier(path) {
109
+ if (!path.isReferencedIdentifier()) {
110
+ return
111
+ }
112
+ const binding = path.scope.getBinding(path.node.name)
113
+ if (binding) {
114
+ binding.dereference()
115
+ }
116
+ },
117
+ }
118
+
119
+ function dereferenceSubtree(rootPath: NodePath): void {
120
+ if (rootPath.isReferencedIdentifier()) {
121
+ const binding = rootPath.scope.getBinding((rootPath.node as t.Identifier).name)
122
+ if (binding) {
123
+ binding.dereference()
124
+ }
125
+ }
126
+ rootPath.traverse(dereferenceVisitor)
127
+ }
128
+
129
+ function tryRemoveDeclarator(
130
+ path: NodePath<t.VariableDeclarator>,
131
+ evalReachableScopes: Set<t.Node>,
132
+ ): boolean {
133
+ const declaration = path.parentPath
134
+ if (!declaration || !declaration.isVariableDeclaration()) {
135
+ return false
136
+ }
137
+
138
+ const grandparent = declaration.parentPath
139
+ if (!grandparent) {
140
+ return false
141
+ }
142
+ if (grandparent.isExportNamedDeclaration() || grandparent.isExportDefaultDeclaration()) {
143
+ return false
144
+ }
145
+ if (
146
+ grandparent.isForStatement() ||
147
+ grandparent.isForInStatement() ||
148
+ grandparent.isForOfStatement()
149
+ ) {
150
+ return false
151
+ }
152
+
153
+ const { id } = path.node
154
+ if (!t.isIdentifier(id)) {
155
+ return false
156
+ }
157
+
158
+ const { init } = path.node
159
+ if (init && !isSideEffectFree(init, path.scope, path.get('init') as NodePath)) {
160
+ return false
161
+ }
162
+
163
+ const binding = path.scope.getBinding(id.name)
164
+ if (!binding) {
165
+ return false
166
+ }
167
+ if (binding.references > 0) {
168
+ return false
169
+ }
170
+ if (binding.constantViolations.length > 0) {
171
+ return false
172
+ }
173
+ if (bindingReachableByEval(binding, evalReachableScopes)) {
174
+ return false
175
+ }
176
+
177
+ if (path.node.init) {
178
+ dereferenceSubtree(path.get('init') as NodePath)
179
+ }
180
+ path.scope.removeBinding(id.name)
181
+ path.remove()
182
+ if (declaration.node && declaration.node.declarations.length === 0) {
183
+ declaration.remove()
184
+ }
185
+ return true
186
+ }
187
+
188
+ function tryRemoveFunctionDecl(
189
+ path: NodePath<t.FunctionDeclaration>,
190
+ evalReachableScopes: Set<t.Node>,
191
+ ): boolean {
192
+ if (isExported(path)) {
193
+ return false
194
+ }
195
+ // Babel misses the sloppy Annex B alias of a block-level function.
196
+ if (hasAnnexBFunctionAlias(path)) {
197
+ return false
198
+ }
199
+ const { id } = path.node
200
+ if (!id) {
201
+ return false
202
+ }
203
+
204
+ const binding = path.scope.getBinding(id.name)
205
+ if (!binding) {
206
+ return false
207
+ }
208
+ if (binding.references > 0) {
209
+ return false
210
+ }
211
+ if (binding.constantViolations.length > 0) {
212
+ return false
213
+ }
214
+ if (bindingReachableByEval(binding, evalReachableScopes)) {
215
+ return false
216
+ }
217
+
218
+ dereferenceSubtree(path.get('body') as NodePath)
219
+ path.scope.removeBinding(id.name)
220
+ path.remove()
221
+ return true
222
+ }
223
+
224
+ function tryRemoveClassDecl(
225
+ path: NodePath<t.ClassDeclaration>,
226
+ evalReachableScopes: Set<t.Node>,
227
+ ): boolean {
228
+ if (isExported(path)) {
229
+ return false
230
+ }
231
+ const { id } = path.node
232
+ if (!id) {
233
+ return false
234
+ }
235
+
236
+ // `extends`, computed keys, static fields, and static blocks run on evaluation.
237
+ if (path.node.superClass && !isSideEffectFree(path.node.superClass, path.scope, path)) {
238
+ return false
239
+ }
240
+ for (const member of path.node.body.body) {
241
+ // Public and private static fields have different Babel node types.
242
+ if ((t.isClassProperty(member) || t.isClassPrivateProperty(member)) && member.static) {
243
+ return false
244
+ }
245
+ if (t.isStaticBlock(member)) {
246
+ return false
247
+ }
248
+ if (
249
+ 'computed' in member &&
250
+ member.computed &&
251
+ 'key' in member &&
252
+ !isSideEffectFree(member.key, path.scope, path)
253
+ ) {
254
+ return false
255
+ }
256
+ }
257
+
258
+ const binding = path.scope.getBinding(id.name)
259
+ if (!binding) {
260
+ return false
261
+ }
262
+ if (binding.references > 0) {
263
+ return false
264
+ }
265
+ if (binding.constantViolations.length > 0) {
266
+ return false
267
+ }
268
+ if (bindingReachableByEval(binding, evalReachableScopes)) {
269
+ return false
270
+ }
271
+
272
+ dereferenceSubtree(path.get('body') as NodePath)
273
+ if (path.node.superClass) {
274
+ dereferenceSubtree(path.get('superClass') as NodePath)
275
+ }
276
+ path.scope.removeBinding(id.name)
277
+ path.remove()
278
+ return true
279
+ }
280
+
281
+ function tryRemoveExpressionStatement(path: NodePath<t.ExpressionStatement>): boolean {
282
+ if (!isSideEffectFree(path.node.expression, path.scope, path.get('expression'))) {
283
+ return false
284
+ }
285
+ dereferenceSubtree(path.get('expression') as NodePath)
286
+ path.remove()
287
+ return true
288
+ }
289
+
290
+ function isExported(path: NodePath): boolean {
291
+ const parent = path.parentPath
292
+ if (!parent) {
293
+ return false
294
+ }
295
+ return parent.isExportNamedDeclaration() || parent.isExportDefaultDeclaration()
296
+ }