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,246 @@
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 { initializerReaches } from 'src/analysis/document-order'
7
+ import { enclosingScopeHasDirectEval, isInsideWith } from 'src/utils/ast'
8
+ import { hasConstantViolation } from 'src/utils/paths'
9
+
10
+ /**
11
+ * Lifts safe lexical declarations to function-scoped `var`, renaming collisions
12
+ * caused by hoisting. TDZ reads, reassigned constants, captured loop bindings,
13
+ * uninitialized loop heads, program scope, `with`, and direct eval are refused.
14
+ *
15
+ * @example
16
+ * // ◀️ before
17
+ * function f() {
18
+ * const x = 1;
19
+ * return x;
20
+ * }
21
+ *
22
+ * // ▶️ after
23
+ * function f() {
24
+ * var x = 1;
25
+ * return x;
26
+ * }
27
+ */
28
+ export function constLetToVar(ast: File): boolean {
29
+ const state = { changed: false }
30
+ traverse(ast, {
31
+ VariableDeclaration(path) {
32
+ const { kind } = path.node
33
+ if (kind !== 'let' && kind !== 'const') {
34
+ return
35
+ }
36
+ // Babel cannot see names supplied by `with` or direct eval.
37
+ if (isInsideWith(path) || enclosingScopeHasDirectEval(path)) {
38
+ return
39
+ }
40
+ // A `var` write would lose the `const` TypeError.
41
+ if (kind === 'const' && hasConstantViolation(path)) {
42
+ return
43
+ }
44
+ // Hoisted `var` reads `undefined` where a lexical binding throws in TDZ.
45
+ if (hasTdzRiskyReference(path)) {
46
+ return
47
+ }
48
+ // An uninitialized lexical loop head resets on each entry. `var` does not.
49
+ if (forHeadMissingInitializer(path)) {
50
+ return
51
+ }
52
+ // Top-level `var` creates a global property. Lexical declarations do not.
53
+ const blockScope = path.scope
54
+ const varScope = blockScope.getFunctionParent() ?? blockScope.getProgramParent()
55
+ if (varScope.path.isProgram()) {
56
+ return
57
+ }
58
+ // One `var` cannot preserve per-iteration bindings captured by closures.
59
+ if (capturedInEnclosingLoop(path)) {
60
+ return
61
+ }
62
+ transformBlockScopedVariable(path, varScope)
63
+ state.changed = true
64
+ },
65
+
66
+ ClassDeclaration(path) {
67
+ const { id } = path.node
68
+ if (!id) {
69
+ return
70
+ }
71
+ if (isInsideWith(path) || enclosingScopeHasDirectEval(path)) {
72
+ return
73
+ }
74
+
75
+ const { scope } = path.parentPath
76
+ if (!isVarScope(scope) && scope.parent!.hasBinding(id.name, { noUids: true })) {
77
+ path.scope.rename(id.name)
78
+ state.changed = true
79
+ }
80
+ },
81
+ })
82
+ return state.changed
83
+ }
84
+
85
+ const conflictingFunctionsVisitor: Visitor<{ names: string[] }> = {
86
+ Scope(path, { names }) {
87
+ for (const name of names) {
88
+ const binding = path.scope.getOwnBinding(name)
89
+ if (binding?.kind === 'hoisted') {
90
+ path.scope.rename(name)
91
+ }
92
+ }
93
+ },
94
+ Expression(path) {
95
+ path.skip()
96
+ },
97
+ Declaration(path) {
98
+ path.skip()
99
+ },
100
+ }
101
+
102
+ // Same-scope uses need a reachable initializer. Nested uses also need their
103
+ // function value created after initialization.
104
+ function hasTdzRiskyReference(path: NodePath<t.VariableDeclaration>): boolean {
105
+ const declHome = nearestFunctionOrProgram(path)
106
+ for (const name of Object.keys(path.getBindingIdentifiers())) {
107
+ const binding = path.scope.getBinding(name)
108
+ if (!binding) {
109
+ continue
110
+ }
111
+ for (const usage of [...binding.referencePaths, ...binding.constantViolations]) {
112
+ if (usage.findParent((ancestor) => ancestor === path)) {
113
+ return true
114
+ }
115
+ const usageHome = nearestFunctionOrProgram(usage)
116
+ if (usageHome === declHome) {
117
+ if (!initializerReaches(path, usage)) {
118
+ return true
119
+ }
120
+ continue
121
+ }
122
+ const nested = outermostNestedFunction(usage, declHome)
123
+ if (!nested || nested.isFunctionDeclaration() || !initializerReaches(path, nested)) {
124
+ return true
125
+ }
126
+ }
127
+ }
128
+ return false
129
+ }
130
+
131
+ function forHeadMissingInitializer(path: NodePath<t.VariableDeclaration>): boolean {
132
+ if (!t.isForStatement(path.parent) || path.key !== 'init') {
133
+ return false
134
+ }
135
+ return path.node.declarations.some((decl) => decl.init == null)
136
+ }
137
+
138
+ // A nested function or class can observe the loop's per-iteration binding.
139
+ function capturedInEnclosingLoop(path: NodePath<t.VariableDeclaration>): boolean {
140
+ const loop = enclosingLoop(path)
141
+ if (!loop) {
142
+ return false
143
+ }
144
+ for (const name of Object.keys(path.getBindingIdentifiers())) {
145
+ const binding = path.scope.getBinding(name)
146
+ if (!binding) {
147
+ continue
148
+ }
149
+ for (const usage of [...binding.referencePaths, ...binding.constantViolations]) {
150
+ for (
151
+ let current = usage.parentPath;
152
+ current && current !== loop;
153
+ current = current.parentPath
154
+ ) {
155
+ if (current.isFunction() || current.isClass()) {
156
+ return true
157
+ }
158
+ }
159
+ }
160
+ }
161
+ return false
162
+ }
163
+
164
+ function transformBlockScopedVariable(
165
+ path: NodePath<t.VariableDeclaration>,
166
+ varScope: Scope,
167
+ ): void {
168
+ path.node.kind = 'var'
169
+
170
+ const bindingNames = Object.keys(path.getBindingIdentifiers())
171
+ for (const name of bindingNames) {
172
+ const binding = path.scope.getOwnBinding(name)
173
+ if (!binding) {
174
+ continue
175
+ }
176
+ binding.kind = 'var'
177
+ }
178
+
179
+ if (enclosingLoop(path) && !isVarInLoopHead(path)) {
180
+ for (const decl of path.node.declarations) {
181
+ // Without an initializer, `var` retains the prior iteration's value.
182
+ decl.init ??= t.buildUndefinedNode()
183
+ }
184
+ }
185
+
186
+ const blockScope = path.scope
187
+ if (varScope !== blockScope) {
188
+ for (const name of bindingNames) {
189
+ let newName = name
190
+ if (
191
+ blockScope.parent!.hasBinding(name, { noUids: true }) ||
192
+ blockScope.parent!.hasGlobal(name)
193
+ ) {
194
+ newName = blockScope.generateUid(name)
195
+ blockScope.rename(name, newName)
196
+ }
197
+
198
+ blockScope.moveBindingTo(newName, varScope)
199
+ }
200
+ }
201
+
202
+ blockScope.path.traverse(conflictingFunctionsVisitor, {
203
+ names: bindingNames,
204
+ })
205
+ }
206
+
207
+ function nearestFunctionOrProgram(path: NodePath): NodePath {
208
+ return path.findParent((ancestor) => ancestor.isFunction() || ancestor.isProgram())!
209
+ }
210
+
211
+ // The shallowest nested function decides when this use can run.
212
+ function outermostNestedFunction(usage: NodePath, home: NodePath): NodePath | null {
213
+ let result: NodePath | null = null
214
+ for (let current = usage.parentPath; current && current !== home; current = current.parentPath) {
215
+ if (current.isFunction()) {
216
+ result = current
217
+ }
218
+ }
219
+ return result
220
+ }
221
+
222
+ function enclosingLoop(path: NodePath): NodePath<t.Loop> | null {
223
+ for (let current = path.parentPath; current; current = current.parentPath) {
224
+ if (current.isFunction()) {
225
+ return null
226
+ }
227
+ if (current.isLoop()) {
228
+ return current
229
+ }
230
+ }
231
+ return null
232
+ }
233
+
234
+ function isVarScope(scope: Scope): boolean {
235
+ return scope.path.isFunctionParent() || scope.path.isProgram()
236
+ }
237
+
238
+ function isVarInLoopHead(path: NodePath<t.VariableDeclaration>): boolean {
239
+ if (t.isForStatement(path.parent)) {
240
+ return path.key === 'init'
241
+ }
242
+ if (t.isForXStatement(path.parent)) {
243
+ return path.key === 'left'
244
+ }
245
+ return false
246
+ }
@@ -0,0 +1,38 @@
1
+ import type { Visitor } from '@babel/traverse'
2
+ import type { File } from '@babel/types'
3
+
4
+ import { enclosingScopeHasDirectEval } from 'src/utils/ast'
5
+ import { traverseForChanges } from 'src/utils/change-tracking'
6
+ import type { ChangeState } from 'src/utils/change-tracking'
7
+ import { hasConstantViolation } from 'src/utils/paths'
8
+
9
+ /**
10
+ * Changes `const` to `let` when no write can observe the lost TypeError. Direct
11
+ * eval and recorded constant violations keep the declaration unchanged. Scope
12
+ * and TDZ behavior are otherwise identical.
13
+ *
14
+ * @example
15
+ * // ◀️ before
16
+ * const foo = "bar";
17
+ * const { a, b } = obj;
18
+ *
19
+ * // ▶️ after
20
+ * let foo = "bar";
21
+ * let { a, b } = obj;
22
+ */
23
+ export function constToLet(ast: File): boolean {
24
+ return traverseForChanges(ast, visitor)
25
+ }
26
+
27
+ const visitor: Visitor<ChangeState> = {
28
+ VariableDeclaration(path, state) {
29
+ if (path.node.kind !== 'const') {
30
+ return
31
+ }
32
+ if (enclosingScopeHasDirectEval(path) || hasConstantViolation(path)) {
33
+ return
34
+ }
35
+ path.node.kind = 'let'
36
+ state.changed = true
37
+ },
38
+ }
@@ -0,0 +1,69 @@
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
+ import { collectLoopLabels, generateLoopLabel, redirectContinues } from 'src/utils/loop-lowering'
8
+
9
+ /**
10
+ * Rewrites `do` loops as `while (true)` with an exit test after the body.
11
+ * Continues become breaks from an inner block so they still reach that test.
12
+ * The inner label is omitted when nothing uses it.
13
+ *
14
+ * @example
15
+ * // ◀️ before
16
+ * do {
17
+ * step();
18
+ * if (skip()) continue;
19
+ * log();
20
+ * } while (more());
21
+ *
22
+ * // ▶️ after
23
+ * while (true) {
24
+ * _doIteration: {
25
+ * step();
26
+ * if (skip()) break _doIteration;
27
+ * log();
28
+ * }
29
+ * if (!more()) break;
30
+ * }
31
+ */
32
+ export function doWhileToWhile(ast: File): boolean {
33
+ return traverseForChanges(ast, visitor)
34
+ }
35
+
36
+ const visitor: Visitor<ChangeState> = {
37
+ DoWhileStatement(path, state) {
38
+ transformDoWhile(path)
39
+ state.changed = true
40
+ },
41
+ }
42
+
43
+ function transformDoWhile(path: NodePath<t.DoWhileStatement>): void {
44
+ const { node } = path
45
+ const { ourLabels } = collectLoopLabels(path)
46
+ const innerLabel = generateLoopLabel(path, 'doIteration')
47
+
48
+ const blockBody: t.BlockStatement = t.isBlockStatement(node.body)
49
+ ? node.body
50
+ : t.blockStatement([node.body])
51
+ const labeledBlock = t.labeledStatement(t.identifier(innerLabel), blockBody)
52
+ const exitTest = t.ifStatement(
53
+ t.unaryExpression('!', node.test),
54
+ t.blockStatement([t.breakStatement()]),
55
+ )
56
+ const whileStmt = t.whileStatement(
57
+ t.booleanLiteral(true),
58
+ t.blockStatement([labeledBlock, exitTest]),
59
+ )
60
+
61
+ const [newPath] = path.replaceWith(whileStmt)
62
+ const rewriteCount = redirectContinues(newPath, innerLabel, ourLabels)
63
+
64
+ // Keep the block so body bindings cannot shadow names in the exit test.
65
+ if (rewriteCount === 0) {
66
+ const whileBlock = newPath.node.body as t.BlockStatement
67
+ whileBlock.body = [blockBody, exitTest]
68
+ }
69
+ }
@@ -0,0 +1,119 @@
1
+ import traverse from '@babel/traverse'
2
+ import type { NodePath, 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 { isInsideWith } from 'src/utils/ast'
8
+ import { traverseForChanges } from 'src/utils/change-tracking'
9
+ import type { ChangeState } from 'src/utils/change-tracking'
10
+
11
+ /**
12
+ * Removes `console.*(...)` expression statements when `console` is not
13
+ * locally shadowed. Matches dot, bracket, and optional-chain forms.
14
+ * Leaves indirect uses alone (`console.log.call(...)`, assignments,
15
+ * `await console.log(...)`).
16
+ *
17
+ * @example
18
+ * // ◀️ before
19
+ * function foo() {
20
+ * console.log("hi");
21
+ * return "bar";
22
+ * }
23
+ * function bar(console) {
24
+ * console.log("kept");
25
+ * }
26
+ *
27
+ * // ▶️ after
28
+ * function foo() {
29
+ * return "bar";
30
+ * }
31
+ * function bar(console) {
32
+ * console.log("kept");
33
+ * }
34
+ */
35
+ export function dropConsole(ast: File): boolean {
36
+ // A reassigned global `console` can invoke user code.
37
+ if (globalConsoleIsReassigned(ast)) {
38
+ return false
39
+ }
40
+ return traverseForChanges(ast, visitor)
41
+ }
42
+
43
+ function globalConsoleIsReassigned(ast: File): boolean {
44
+ let reassigned = false
45
+ traverse(ast, {
46
+ Identifier(path) {
47
+ if (path.node.name !== 'console' || reassigned) {
48
+ return
49
+ }
50
+ // Per-call checks already handle local shadows.
51
+ if (path.scope.getBinding('console')) {
52
+ return
53
+ }
54
+ if (isWriteTarget(path)) {
55
+ reassigned = true
56
+ path.stop()
57
+ }
58
+ },
59
+ })
60
+ return reassigned
61
+ }
62
+
63
+ function isWriteTarget(path: NodePath<t.Identifier>): boolean {
64
+ const { node, parentPath } = path
65
+ if (!parentPath) {
66
+ return false
67
+ }
68
+ if (parentPath.isAssignmentExpression({ left: node })) {
69
+ return true
70
+ }
71
+ if (parentPath.isUpdateExpression({ argument: node })) {
72
+ return true
73
+ }
74
+ // Destructuring and iterator-loop targets also write the name.
75
+ return path.isBindingIdentifier()
76
+ }
77
+
78
+ const visitor: Visitor<ChangeState> = {
79
+ ExpressionStatement(path, state) {
80
+ const expr = path.node.expression
81
+ if (!t.isCallExpression(expr) && !t.isOptionalCallExpression(expr)) {
82
+ return
83
+ }
84
+ const { callee } = expr
85
+ if (!t.isMemberExpression(callee) && !t.isOptionalMemberExpression(callee)) {
86
+ return
87
+ }
88
+ const { object } = callee
89
+ if (!t.isIdentifier(object) || object.name !== 'console') {
90
+ return
91
+ }
92
+ // `with` can supply a different `console` outside Babel's scope table.
93
+ if (path.scope.getBinding('console') || isInsideWith(path)) {
94
+ return
95
+ }
96
+ // Removing the call also removes argument and computed-key evaluation.
97
+ if (!callArgumentsAreInert(expr, callee, path)) {
98
+ return
99
+ }
100
+ path.remove()
101
+ state.changed = true
102
+ },
103
+ }
104
+
105
+ function callArgumentsAreInert(
106
+ expr: t.CallExpression | t.OptionalCallExpression,
107
+ callee: t.MemberExpression | t.OptionalMemberExpression,
108
+ path: NodePath<t.ExpressionStatement>,
109
+ ): boolean {
110
+ if (callee.computed && !isSideEffectFree(callee.property, path.scope, path)) {
111
+ return false
112
+ }
113
+ for (const arg of expr.arguments) {
114
+ if (!t.isExpression(arg) || !isSideEffectFree(arg, path.scope, path)) {
115
+ return false
116
+ }
117
+ }
118
+ return true
119
+ }
@@ -0,0 +1,38 @@
1
+ import type { 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 every `debugger` statement.
10
+ *
11
+ * @example
12
+ * // ◀️ before
13
+ * function foo() {
14
+ * debugger;
15
+ * return "bar";
16
+ * }
17
+ *
18
+ * // ▶️ after
19
+ * function foo() {
20
+ * return "bar";
21
+ * }
22
+ */
23
+ export function dropDebugger(ast: File): boolean {
24
+ return traverseForChanges(ast, visitor)
25
+ }
26
+
27
+ const visitor: Visitor<ChangeState> = {
28
+ DebuggerStatement(path, state) {
29
+ // Babel cannot remove a required single-statement child. Keep the slot valid
30
+ // with an empty statement.
31
+ if (Array.isArray(path.container)) {
32
+ path.remove()
33
+ } else {
34
+ path.replaceWith(t.emptyStatement())
35
+ }
36
+ state.changed = true
37
+ },
38
+ }
@@ -0,0 +1,94 @@
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 { isInsideWith } from 'src/utils/ast'
6
+ import { traverseForChanges } from 'src/utils/change-tracking'
7
+ import type { ChangeState } from 'src/utils/change-tracking'
8
+
9
+ /**
10
+ * Rewrites arithmetic and bitwise compound assignments as `LHS = LHS op RHS`.
11
+ * Logical compound assignments are handled by `expandLogicalAssignment`.
12
+ *
13
+ * Member objects and computed keys are cached so the read and write use the same
14
+ * evaluated receiver. The cache assignment stays on the outer write target,
15
+ * which evaluates before its right side.
16
+ *
17
+ * Compound and expanded forms use the same operator semantics, including BigInt.
18
+ *
19
+ * @example
20
+ * // ◀️ before
21
+ * a += b;
22
+ * a **= 2;
23
+ * obj.x -= y;
24
+ * arr[idx()] |= v;
25
+ *
26
+ * // ▶️ after
27
+ * a = a + b;
28
+ * a = a ** 2;
29
+ * (_obj = obj).x = _obj.x - y;
30
+ * (_arr = arr)[_idx = idx()] = _arr[_idx] | v;
31
+ */
32
+ export function expandBinaryAssignment(ast: File): boolean {
33
+ return traverseForChanges(ast, visitor)
34
+ }
35
+
36
+ const visitor: Visitor<ChangeState> = {
37
+ AssignmentExpression(path, state) {
38
+ if (lowerBinaryAssignment(path)) {
39
+ state.changed = true
40
+ }
41
+ },
42
+ }
43
+
44
+ const LOGICAL_COMPOUND = new Set(['||', '&&', '??'])
45
+
46
+ function lowerBinaryAssignment(path: NodePath<t.AssignmentExpression>): boolean {
47
+ const { operator, left, right } = path.node
48
+ if (operator === '=' || operator.length < 2) {
49
+ return false
50
+ }
51
+ const op = operator.slice(0, -1)
52
+ if (LOGICAL_COMPOUND.has(op)) {
53
+ return false
54
+ }
55
+
56
+ const read = t.cloneNode(left, true)
57
+
58
+ if (t.isMemberExpression(left) && t.isMemberExpression(read)) {
59
+ // `with` can turn a bare object name into a getter or shadow a memo temp.
60
+ if (isInsideWith(path)) {
61
+ return false
62
+ }
63
+ const { object } = left
64
+ if (t.isExpression(object)) {
65
+ const memo = path.scope.maybeGenerateMemoised(object)
66
+ if (memo) {
67
+ read.object = memo
68
+ left.object = t.assignmentExpression('=', t.cloneNode(memo), object)
69
+ }
70
+ }
71
+
72
+ if (left.computed) {
73
+ const { property } = left
74
+ if (t.isExpression(property)) {
75
+ const memoProp = path.scope.maybeGenerateMemoised(property)
76
+ if (memoProp) {
77
+ read.property = memoProp
78
+ left.property = t.assignmentExpression('=', t.cloneNode(memoProp), property)
79
+ }
80
+ }
81
+ }
82
+ } else if (!t.isIdentifier(left)) {
83
+ return false
84
+ }
85
+
86
+ path.replaceWith(
87
+ t.assignmentExpression(
88
+ '=',
89
+ left,
90
+ t.binaryExpression(op as t.BinaryExpression['operator'], read as t.Expression, right),
91
+ ),
92
+ )
93
+ return true
94
+ }
@@ -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 { isInsideWith } from 'src/utils/ast'
6
+ import { traverseForChanges } from 'src/utils/change-tracking'
7
+ import type { ChangeState } from 'src/utils/change-tracking'
8
+
9
+ /**
10
+ * Rewrites `||=`/`&&=`/`??=` as `LHS op (LHS = RHS)`. Member objects and
11
+ * computed keys are cached so the read and write use the same receiver.
12
+ *
13
+ * @example
14
+ * // ◀️ before
15
+ * a ||= b;
16
+ * a &&= b;
17
+ * a ??= b;
18
+ * obj.x ||= y;
19
+ * arr[idx()] ??= v;
20
+ *
21
+ * // ▶️ after
22
+ * a || (a = b);
23
+ * a && (a = b);
24
+ * a ?? (a = b);
25
+ * (_obj = obj).x || (_obj.x = y);
26
+ * (_arr = arr)[_idx = idx()] ?? (_arr[_idx] = v);
27
+ */
28
+ export function expandLogicalAssignment(ast: File): boolean {
29
+ return traverseForChanges(ast, visitor)
30
+ }
31
+
32
+ const visitor: Visitor<ChangeState> = {
33
+ AssignmentExpression(path, state) {
34
+ if (lowerLogicalAssignment(path)) {
35
+ state.changed = true
36
+ }
37
+ },
38
+ }
39
+
40
+ function lowerLogicalAssignment(path: NodePath<t.AssignmentExpression>): boolean {
41
+ const { operator, left, right } = path.node
42
+ if (operator.length < 2) {
43
+ return false
44
+ }
45
+ const op = operator.slice(0, -1)
46
+ if (op !== '||' && op !== '&&' && op !== '??') {
47
+ return false
48
+ }
49
+
50
+ const lhs = t.cloneNode(left, true)
51
+
52
+ if (t.isMemberExpression(left) && t.isMemberExpression(lhs)) {
53
+ // `with` can turn a bare object name into a getter or shadow a memo temp.
54
+ if (isInsideWith(path)) {
55
+ return false
56
+ }
57
+ const { object } = left
58
+ if (t.isExpression(object)) {
59
+ const memo = path.scope.maybeGenerateMemoised(object)
60
+ if (memo) {
61
+ left.object = memo
62
+ lhs.object = t.assignmentExpression('=', t.cloneNode(memo), object)
63
+ }
64
+ }
65
+
66
+ if (left.computed) {
67
+ const { property } = left
68
+ if (t.isExpression(property)) {
69
+ const memoProp = path.scope.maybeGenerateMemoised(property)
70
+ if (memoProp) {
71
+ left.property = memoProp
72
+ lhs.property = t.assignmentExpression('=', t.cloneNode(memoProp), property)
73
+ }
74
+ }
75
+ }
76
+ } else if (!t.isIdentifier(left)) {
77
+ return false
78
+ }
79
+
80
+ path.replaceWith(
81
+ t.logicalExpression(op, lhs as t.Expression, t.assignmentExpression('=', left, right)),
82
+ )
83
+ return true
84
+ }