webfuscator 1.3.0 → 1.3.2

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 (180) hide show
  1. package/DISCLOSURE +9 -0
  2. package/README.md +67 -0
  3. package/dist/analysis/constant.d.ts.map +1 -1
  4. package/dist/analysis/constant.js +10 -26
  5. package/dist/analysis/constant.js.map +1 -1
  6. package/dist/analysis/inlineability.d.ts.map +1 -1
  7. package/dist/analysis/inlineability.js +4 -12
  8. package/dist/analysis/inlineability.js.map +1 -1
  9. package/dist/analysis/purity.d.ts +3 -3
  10. package/dist/analysis/purity.d.ts.map +1 -1
  11. package/dist/analysis/purity.js +28 -75
  12. package/dist/analysis/purity.js.map +1 -1
  13. package/dist/preparation/expand-destructuring.d.ts +2 -3
  14. package/dist/preparation/expand-destructuring.d.ts.map +1 -1
  15. package/dist/preparation/expand-destructuring.js +2 -8
  16. package/dist/preparation/expand-destructuring.js.map +1 -1
  17. package/dist/transforms/arguments-to-parameters.d.ts +5 -4
  18. package/dist/transforms/arguments-to-parameters.d.ts.map +1 -1
  19. package/dist/transforms/arguments-to-parameters.js +5 -4
  20. package/dist/transforms/arguments-to-parameters.js.map +1 -1
  21. package/dist/transforms/arrow-to-function.d.ts +4 -3
  22. package/dist/transforms/arrow-to-function.d.ts.map +1 -1
  23. package/dist/transforms/arrow-to-function.js +6 -8
  24. package/dist/transforms/arrow-to-function.js.map +1 -1
  25. package/dist/transforms/collapse-single-use-temps.d.ts +2 -1
  26. package/dist/transforms/collapse-single-use-temps.d.ts.map +1 -1
  27. package/dist/transforms/collapse-single-use-temps.js +12 -20
  28. package/dist/transforms/collapse-single-use-temps.js.map +1 -1
  29. package/dist/transforms/conceal-strings.d.ts +1 -2
  30. package/dist/transforms/conceal-strings.d.ts.map +1 -1
  31. package/dist/transforms/conceal-strings.js +2 -7
  32. package/dist/transforms/conceal-strings.js.map +1 -1
  33. package/dist/transforms/const-let-to-var.d.ts +4 -3
  34. package/dist/transforms/const-let-to-var.d.ts.map +1 -1
  35. package/dist/transforms/const-let-to-var.js +8 -94
  36. package/dist/transforms/const-let-to-var.js.map +1 -1
  37. package/dist/transforms/const-to-let.d.ts +1 -2
  38. package/dist/transforms/const-to-let.d.ts.map +1 -1
  39. package/dist/transforms/const-to-let.js +1 -6
  40. package/dist/transforms/const-to-let.js.map +1 -1
  41. package/dist/transforms/drop-console.d.ts.map +1 -1
  42. package/dist/transforms/drop-console.js +3 -5
  43. package/dist/transforms/drop-console.js.map +1 -1
  44. package/dist/transforms/expand-binary-assignment.d.ts.map +1 -1
  45. package/dist/transforms/expand-binary-assignment.js +0 -5
  46. package/dist/transforms/expand-binary-assignment.js.map +1 -1
  47. package/dist/transforms/expand-logical-assignment.d.ts.map +1 -1
  48. package/dist/transforms/expand-logical-assignment.js +0 -5
  49. package/dist/transforms/expand-logical-assignment.js.map +1 -1
  50. package/dist/transforms/extract-object-properties.js +5 -12
  51. package/dist/transforms/extract-object-properties.js.map +1 -1
  52. package/dist/transforms/fold-builtin-methods.js +3 -11
  53. package/dist/transforms/fold-builtin-methods.js.map +1 -1
  54. package/dist/transforms/fold-constants.js +2 -2
  55. package/dist/transforms/fold-constants.js.map +1 -1
  56. package/dist/transforms/function-declaration-to-expression.d.ts +5 -6
  57. package/dist/transforms/function-declaration-to-expression.d.ts.map +1 -1
  58. package/dist/transforms/function-declaration-to-expression.js +5 -6
  59. package/dist/transforms/function-declaration-to-expression.js.map +1 -1
  60. package/dist/transforms/hoist-variables.d.ts +1 -2
  61. package/dist/transforms/hoist-variables.d.ts.map +1 -1
  62. package/dist/transforms/hoist-variables.js +2 -11
  63. package/dist/transforms/hoist-variables.js.map +1 -1
  64. package/dist/transforms/inline-functions.js +11 -39
  65. package/dist/transforms/inline-functions.js.map +1 -1
  66. package/dist/transforms/logical-to-ternary.d.ts +1 -1
  67. package/dist/transforms/logical-to-ternary.js +5 -9
  68. package/dist/transforms/logical-to-ternary.js.map +1 -1
  69. package/dist/transforms/mangle-properties.d.ts +2 -5
  70. package/dist/transforms/mangle-properties.d.ts.map +1 -1
  71. package/dist/transforms/mangle-properties.js +2 -5
  72. package/dist/transforms/mangle-properties.js.map +1 -1
  73. package/dist/transforms/nullish-coalescing-to-ternary.d.ts +4 -5
  74. package/dist/transforms/nullish-coalescing-to-ternary.d.ts.map +1 -1
  75. package/dist/transforms/nullish-coalescing-to-ternary.js +7 -12
  76. package/dist/transforms/nullish-coalescing-to-ternary.js.map +1 -1
  77. package/dist/transforms/object-method-to-property.d.ts +1 -1
  78. package/dist/transforms/object-method-to-property.js +1 -1
  79. package/dist/transforms/optional-chaining-to-ternary.d.ts +4 -3
  80. package/dist/transforms/optional-chaining-to-ternary.d.ts.map +1 -1
  81. package/dist/transforms/optional-chaining-to-ternary.js +9 -9
  82. package/dist/transforms/optional-chaining-to-ternary.js.map +1 -1
  83. package/dist/transforms/pack-declarations-into-parameters.d.ts.map +1 -1
  84. package/dist/transforms/pack-declarations-into-parameters.js +4 -16
  85. package/dist/transforms/pack-declarations-into-parameters.js.map +1 -1
  86. package/dist/transforms/pack.d.ts +4 -11
  87. package/dist/transforms/pack.d.ts.map +1 -1
  88. package/dist/transforms/pack.js +4 -17
  89. package/dist/transforms/pack.js.map +1 -1
  90. package/dist/transforms/remove-anonymous-function-names.d.ts +2 -4
  91. package/dist/transforms/remove-anonymous-function-names.d.ts.map +1 -1
  92. package/dist/transforms/remove-anonymous-function-names.js +2 -40
  93. package/dist/transforms/remove-anonymous-function-names.js.map +1 -1
  94. package/dist/transforms/remove-unreachable-code.js +2 -21
  95. package/dist/transforms/remove-unreachable-code.js.map +1 -1
  96. package/dist/transforms/remove-unused-code.d.ts.map +1 -1
  97. package/dist/transforms/remove-unused-code.js +5 -10
  98. package/dist/transforms/remove-unused-code.js.map +1 -1
  99. package/dist/transforms/rename-identifiers.d.ts +3 -3
  100. package/dist/transforms/rename-identifiers.d.ts.map +1 -1
  101. package/dist/transforms/rename-identifiers.js +4 -20
  102. package/dist/transforms/rename-identifiers.js.map +1 -1
  103. package/dist/transforms/reorder-function-declarations.d.ts +1 -2
  104. package/dist/transforms/reorder-function-declarations.d.ts.map +1 -1
  105. package/dist/transforms/reorder-function-declarations.js +2 -4
  106. package/dist/transforms/reorder-function-declarations.js.map +1 -1
  107. package/dist/transforms/specials-to-strings.d.ts.map +1 -1
  108. package/dist/transforms/specials-to-strings.js +2 -4
  109. package/dist/transforms/specials-to-strings.js.map +1 -1
  110. package/dist/transforms/switch-to-if.js +5 -15
  111. package/dist/transforms/switch-to-if.js.map +1 -1
  112. package/dist/transforms/ternary-to-if.d.ts.map +1 -1
  113. package/dist/transforms/ternary-to-if.js +5 -9
  114. package/dist/transforms/ternary-to-if.js.map +1 -1
  115. package/dist/transforms/update-to-assignment.d.ts.map +1 -1
  116. package/dist/transforms/update-to-assignment.js +6 -8
  117. package/dist/transforms/update-to-assignment.js.map +1 -1
  118. package/dist/transforms/yodify-conditions.d.ts +3 -4
  119. package/dist/transforms/yodify-conditions.d.ts.map +1 -1
  120. package/dist/transforms/yodify-conditions.js +3 -4
  121. package/dist/transforms/yodify-conditions.js.map +1 -1
  122. package/dist/utils/ast.d.ts +0 -1
  123. package/dist/utils/ast.d.ts.map +1 -1
  124. package/dist/utils/ast.js +0 -26
  125. package/dist/utils/ast.js.map +1 -1
  126. package/dist/utils/evaluation-order.js +1 -1
  127. package/dist/utils/evaluation-order.js.map +1 -1
  128. package/dist/utils/logical-lowering.d.ts.map +1 -1
  129. package/dist/utils/logical-lowering.js +7 -9
  130. package/dist/utils/logical-lowering.js.map +1 -1
  131. package/dist/utils/nullish.d.ts +4 -0
  132. package/dist/utils/nullish.d.ts.map +1 -0
  133. package/dist/utils/nullish.js +8 -0
  134. package/dist/utils/nullish.js.map +1 -0
  135. package/dist/utils/paths.d.ts +0 -2
  136. package/dist/utils/paths.d.ts.map +1 -1
  137. package/dist/utils/paths.js +0 -28
  138. package/dist/utils/paths.js.map +1 -1
  139. package/package.json +3 -13
  140. package/src/analysis/constant.ts +12 -30
  141. package/src/analysis/inlineability.ts +4 -12
  142. package/src/analysis/purity.ts +29 -92
  143. package/src/preparation/expand-destructuring.ts +2 -10
  144. package/src/transforms/arguments-to-parameters.ts +5 -4
  145. package/src/transforms/arrow-to-function.ts +6 -8
  146. package/src/transforms/collapse-single-use-temps.ts +12 -23
  147. package/src/transforms/conceal-strings.ts +3 -8
  148. package/src/transforms/const-let-to-var.ts +9 -100
  149. package/src/transforms/const-to-let.ts +1 -6
  150. package/src/transforms/drop-console.ts +3 -5
  151. package/src/transforms/expand-binary-assignment.ts +0 -5
  152. package/src/transforms/expand-logical-assignment.ts +0 -5
  153. package/src/transforms/extract-object-properties.ts +5 -14
  154. package/src/transforms/fold-builtin-methods.ts +3 -12
  155. package/src/transforms/fold-constants.ts +2 -2
  156. package/src/transforms/function-declaration-to-expression.ts +5 -6
  157. package/src/transforms/hoist-variables.ts +2 -12
  158. package/src/transforms/inline-functions.ts +16 -46
  159. package/src/transforms/logical-to-ternary.ts +5 -10
  160. package/src/transforms/mangle-properties.ts +2 -5
  161. package/src/transforms/nullish-coalescing-to-ternary.ts +7 -13
  162. package/src/transforms/object-method-to-property.ts +1 -1
  163. package/src/transforms/optional-chaining-to-ternary.ts +9 -9
  164. package/src/transforms/pack-declarations-into-parameters.ts +10 -16
  165. package/src/transforms/pack.ts +4 -17
  166. package/src/transforms/remove-anonymous-function-names.ts +3 -46
  167. package/src/transforms/remove-unreachable-code.ts +2 -22
  168. package/src/transforms/remove-unused-code.ts +5 -10
  169. package/src/transforms/rename-identifiers.ts +4 -20
  170. package/src/transforms/reorder-function-declarations.ts +2 -4
  171. package/src/transforms/specials-to-strings.ts +2 -4
  172. package/src/transforms/switch-to-if.ts +5 -22
  173. package/src/transforms/ternary-to-if.ts +5 -9
  174. package/src/transforms/update-to-assignment.ts +6 -8
  175. package/src/transforms/yodify-conditions.ts +3 -4
  176. package/src/utils/ast.ts +0 -29
  177. package/src/utils/evaluation-order.ts +1 -1
  178. package/src/utils/logical-lowering.ts +7 -11
  179. package/src/utils/nullish.ts +23 -0
  180. package/src/utils/paths.ts +0 -32
@@ -1,18 +1,11 @@
1
- import type { Binding, NodePath, Scope } from '@babel/traverse'
1
+ import type { Scope } from '@babel/traverse'
2
2
  import * as t from '@babel/types'
3
3
 
4
- import { isInsideWith } from '../utils/ast'
5
4
  import { isPrimitiveLiteral } from '../utils/literal'
6
- import {
7
- declarationReaches,
8
- initializerReaches,
9
- readCrossesFunctionBoundary,
10
- } from './document-order'
11
5
 
12
6
  // Purity also requires deterministic repetition and no throw. Identifier reads
13
- // need scope and reference paths to prove resolution, initialization, and the
14
- // absence of `with` interception.
15
- export function isPure(node: t.Node, scope?: Scope, referencePath?: NodePath): boolean {
7
+ // need scope to prove resolution.
8
+ export function isPure(node: t.Node, scope?: Scope): boolean {
16
9
  if (
17
10
  t.isNumericLiteral(node) ||
18
11
  t.isStringLiteral(node) ||
@@ -25,7 +18,7 @@ export function isPure(node: t.Node, scope?: Scope, referencePath?: NodePath): b
25
18
  return true
26
19
  }
27
20
  if (t.isIdentifier(node)) {
28
- return identifierReadIsSafe(node, scope, referencePath)
21
+ return identifierReadIsSafe(node, scope)
29
22
  }
30
23
  if (t.isTemplateLiteral(node)) {
31
24
  // ToString can call object code or throw on Symbol.
@@ -39,7 +32,7 @@ export function isPure(node: t.Node, scope?: Scope, referencePath?: NodePath): b
39
32
  if (t.isUnaryExpression(node)) {
40
33
  // Numeric unary operators invoke ToPrimitive. `delete` and `throw` act.
41
34
  if (node.operator === '!' || node.operator === 'typeof' || node.operator === 'void') {
42
- return isPure(node.argument, scope, referencePath)
35
+ return isPure(node.argument, scope)
43
36
  }
44
37
  return false
45
38
  }
@@ -47,7 +40,7 @@ export function isPure(node: t.Node, scope?: Scope, referencePath?: NodePath): b
47
40
  if (!t.isExpression(node.left)) {
48
41
  return false
49
42
  }
50
- if (!isPure(node.left, scope, referencePath) || !isPure(node.right, scope, referencePath)) {
43
+ if (!isPure(node.left, scope) || !isPure(node.right, scope)) {
51
44
  return false
52
45
  }
53
46
  // Only strict equality avoids coercion. Other operators need safe literals.
@@ -57,18 +50,16 @@ export function isPure(node: t.Node, scope?: Scope, referencePath?: NodePath): b
57
50
  return coercesWithoutEffect(node.left, node.right, node.operator)
58
51
  }
59
52
  if (t.isLogicalExpression(node)) {
60
- return isPure(node.left, scope, referencePath) && isPure(node.right, scope, referencePath)
53
+ return isPure(node.left, scope) && isPure(node.right, scope)
61
54
  }
62
55
  if (t.isConditionalExpression(node)) {
63
56
  return (
64
- isPure(node.test, scope, referencePath) &&
65
- isPure(node.consequent, scope, referencePath) &&
66
- isPure(node.alternate, scope, referencePath)
57
+ isPure(node.test, scope) && isPure(node.consequent, scope) && isPure(node.alternate, scope)
67
58
  )
68
59
  }
69
60
  if (t.isSequenceExpression(node)) {
70
61
  for (const expr of node.expressions) {
71
- if (!isPure(expr, scope, referencePath)) {
62
+ if (!isPure(expr, scope)) {
72
63
  return false
73
64
  }
74
65
  }
@@ -77,8 +68,9 @@ export function isPure(node: t.Node, scope?: Scope, referencePath?: NodePath): b
77
68
  return false
78
69
  }
79
70
 
80
- // A droppable identifier read must resolve after binding initialization.
81
- export function isSideEffectFree(node: t.Node, scope?: Scope, referencePath?: NodePath): boolean {
71
+ // A droppable identifier read must resolve to a local binding or immutable
72
+ // global `undefined`.
73
+ export function isSideEffectFree(node: t.Node, scope?: Scope): boolean {
82
74
  if (
83
75
  t.isNumericLiteral(node) ||
84
76
  t.isStringLiteral(node) ||
@@ -99,7 +91,7 @@ export function isSideEffectFree(node: t.Node, scope?: Scope, referencePath?: No
99
91
  return true
100
92
  }
101
93
  if (t.isIdentifier(node)) {
102
- return identifierReadIsSafe(node, scope, referencePath)
94
+ return identifierReadIsSafe(node, scope)
103
95
  }
104
96
  if (t.isFunctionExpression(node) || t.isArrowFunctionExpression(node)) {
105
97
  return true
@@ -107,7 +99,7 @@ export function isSideEffectFree(node: t.Node, scope?: Scope, referencePath?: No
107
99
  if (t.isClassExpression(node)) {
108
100
  // `extends` and computed keys run during class definition.
109
101
  if (node.superClass) {
110
- if (!isSideEffectFree(node.superClass, scope, referencePath)) {
102
+ if (!isSideEffectFree(node.superClass, scope)) {
111
103
  return false
112
104
  }
113
105
  // ClassDefinitionEvaluation accepts only `null` or a constructor.
@@ -127,7 +119,7 @@ export function isSideEffectFree(node: t.Node, scope?: Scope, referencePath?: No
127
119
  'computed' in member &&
128
120
  member.computed &&
129
121
  'key' in member &&
130
- !isSideEffectFree(member.key, scope, referencePath)
122
+ !isSideEffectFree(member.key, scope)
131
123
  ) {
132
124
  return false
133
125
  }
@@ -142,7 +134,7 @@ export function isSideEffectFree(node: t.Node, scope?: Scope, referencePath?: No
142
134
  if (t.isSpreadElement(element)) {
143
135
  return false
144
136
  }
145
- if (!isSideEffectFree(element, scope, referencePath)) {
137
+ if (!isSideEffectFree(element, scope)) {
146
138
  return false
147
139
  }
148
140
  }
@@ -154,7 +146,7 @@ export function isSideEffectFree(node: t.Node, scope?: Scope, referencePath?: No
154
146
  return false
155
147
  }
156
148
  if (t.isObjectMethod(prop)) {
157
- if (prop.computed && !isSideEffectFree(prop.key, scope, referencePath)) {
149
+ if (prop.computed && !isSideEffectFree(prop.key, scope)) {
158
150
  return false
159
151
  }
160
152
  continue
@@ -162,13 +154,13 @@ export function isSideEffectFree(node: t.Node, scope?: Scope, referencePath?: No
162
154
  if (!t.isObjectProperty(prop)) {
163
155
  return false
164
156
  }
165
- if (prop.computed && !isSideEffectFree(prop.key, scope, referencePath)) {
157
+ if (prop.computed && !isSideEffectFree(prop.key, scope)) {
166
158
  return false
167
159
  }
168
160
  if (!t.isExpression(prop.value)) {
169
161
  return false
170
162
  }
171
- if (!isSideEffectFree(prop.value, scope, referencePath)) {
163
+ if (!isSideEffectFree(prop.value, scope)) {
172
164
  return false
173
165
  }
174
166
  }
@@ -179,7 +171,7 @@ export function isSideEffectFree(node: t.Node, scope?: Scope, referencePath?: No
179
171
  return false
180
172
  }
181
173
  if (node.operator === '!' || node.operator === 'typeof' || node.operator === 'void') {
182
- return isSideEffectFree(node.argument, scope, referencePath)
174
+ return isSideEffectFree(node.argument, scope)
183
175
  }
184
176
  return false
185
177
  }
@@ -187,10 +179,7 @@ export function isSideEffectFree(node: t.Node, scope?: Scope, referencePath?: No
187
179
  if (!t.isExpression(node.left)) {
188
180
  return false
189
181
  }
190
- if (
191
- !isSideEffectFree(node.left, scope, referencePath) ||
192
- !isSideEffectFree(node.right, scope, referencePath)
193
- ) {
182
+ if (!isSideEffectFree(node.left, scope) || !isSideEffectFree(node.right, scope)) {
194
183
  return false
195
184
  }
196
185
  // Coercing operators need compatible literals that cannot throw.
@@ -200,21 +189,18 @@ export function isSideEffectFree(node: t.Node, scope?: Scope, referencePath?: No
200
189
  return coercesWithoutEffect(node.left, node.right, node.operator)
201
190
  }
202
191
  if (t.isLogicalExpression(node)) {
203
- return (
204
- isSideEffectFree(node.left, scope, referencePath) &&
205
- isSideEffectFree(node.right, scope, referencePath)
206
- )
192
+ return isSideEffectFree(node.left, scope) && isSideEffectFree(node.right, scope)
207
193
  }
208
194
  if (t.isConditionalExpression(node)) {
209
195
  return (
210
- isSideEffectFree(node.test, scope, referencePath) &&
211
- isSideEffectFree(node.consequent, scope, referencePath) &&
212
- isSideEffectFree(node.alternate, scope, referencePath)
196
+ isSideEffectFree(node.test, scope) &&
197
+ isSideEffectFree(node.consequent, scope) &&
198
+ isSideEffectFree(node.alternate, scope)
213
199
  )
214
200
  }
215
201
  if (t.isSequenceExpression(node)) {
216
202
  for (const expr of node.expressions) {
217
- if (!isSideEffectFree(expr, scope, referencePath)) {
203
+ if (!isSideEffectFree(expr, scope)) {
218
204
  return false
219
205
  }
220
206
  }
@@ -223,43 +209,13 @@ export function isSideEffectFree(node: t.Node, scope?: Scope, referencePath?: No
223
209
  return false
224
210
  }
225
211
 
226
- // A safe read needs a resolved, initialized binding outside `with`. Unbound
227
- // `undefined` is safe because the global property is immutable.
228
- function identifierReadIsSafe(
229
- node: t.Identifier,
230
- scope: Scope | undefined,
231
- referencePath: NodePath | undefined,
232
- ): boolean {
233
- // A `with` property can shadow any name, including `undefined`.
234
- if (referencePath && isInsideWith(referencePath)) {
235
- return false
236
- }
212
+ // Unbound `undefined` is safe because the global property is immutable.
213
+ function identifierReadIsSafe(node: t.Identifier, scope: Scope | undefined): boolean {
237
214
  const isUndefinedName = node.name === 'undefined'
238
215
  if (!scope) {
239
216
  return isUndefinedName
240
217
  }
241
- const binding = scope.getBinding(node.name)
242
- if (!binding) {
243
- return isUndefinedName
244
- }
245
- if (t.isFunctionDeclaration(binding.path.node) || binding.kind === 'module') {
246
- return true
247
- }
248
- if (binding.kind === 'param') {
249
- // Earlier parameter defaults can read later parameters in TDZ.
250
- if (!referencePath || !readIsInOwnParamList(binding, referencePath)) {
251
- return true
252
- }
253
- return declarationReaches(binding.path, referencePath)
254
- }
255
- // Lexical reads need an unconditionally reached declaration in the same call.
256
- if (isLexicalBinding(binding)) {
257
- if (referencePath && readCrossesFunctionBoundary(binding, referencePath)) {
258
- return false
259
- }
260
- return initializerReaches(binding.path, referencePath)
261
- }
262
- return declarationReaches(binding.path, referencePath)
218
+ return scope.getBinding(node.name) !== undefined || isUndefinedName
263
219
  }
264
220
 
265
221
  // Only `null` and plain function expressions are known-valid superclass values.
@@ -294,22 +250,3 @@ function coercesWithoutEffect(left: t.Node, right: t.Node, operator: string): bo
294
250
  }
295
251
  return leftKind !== 'bigint' || !THROWING_BIGINT_OPERATORS.has(operator)
296
252
  }
297
-
298
- function isLexicalBinding(binding: Binding): boolean {
299
- return (
300
- binding.kind === 'let' || binding.kind === 'const' || t.isClassDeclaration(binding.path.node)
301
- )
302
- }
303
-
304
- function readIsInOwnParamList(binding: Binding, referencePath: NodePath): boolean {
305
- const owner = binding.scope.block
306
- let current: NodePath | null = referencePath
307
- while (current) {
308
- const parent: NodePath | null = current.parentPath
309
- if (parent && parent.node === owner && current.listKey === 'params') {
310
- return true
311
- }
312
- current = parent
313
- }
314
- return false
315
- }
@@ -3,8 +3,6 @@ import type { NodePath, Scope } from '@babel/traverse'
3
3
  import * as t from '@babel/types'
4
4
  import type { File } from '@babel/types'
5
5
 
6
- import { isInsideWith } from '../utils/ast'
7
-
8
6
  type Kind = t.VariableDeclaration['kind']
9
7
 
10
8
  // Babel 8 omits `AssignmentPattern` from `LVal`, so list these targets directly.
@@ -18,9 +16,8 @@ type BindingTarget = t.Identifier | t.ObjectPattern | t.ArrayPattern | t.Assignm
18
16
  * The source, computed keys, and getters keep their original evaluation counts.
19
17
  * Defaults compare against `void 0` because `undefined` can be shadowed.
20
18
  *
21
- * Patterns inside `with` stay untouched because an object property can capture
22
- * a generated temporary. The `RequireObjectCoercible` guard runs before computed
23
- * keys to preserve ObjectBindingPattern evaluation order.
19
+ * The `RequireObjectCoercible` guard runs before computed keys to preserve
20
+ * ObjectBindingPattern evaluation order.
24
21
  *
25
22
  * @example
26
23
  * // ◀️ before
@@ -42,11 +39,6 @@ const visitor = {
42
39
  if (typeof path.listKey !== 'string') {
43
40
  return
44
41
  }
45
- // A `with` property can capture the generated temporary.
46
- if (isInsideWith(path)) {
47
- return
48
- }
49
-
50
42
  const kind = path.node.kind
51
43
  const replacement: t.Statement[] = []
52
44
  let changed = false
@@ -8,10 +8,11 @@ import type { ChangeState } from '../utils/change-tracking'
8
8
  import { isInStrictContext } from '../utils/paths'
9
9
 
10
10
  /**
11
- * Replaces `arguments[i]` reads with their mapped parameter in sloppy functions
12
- * with simple parameter lists. Writes, deletion, reassigned parameters, and any
13
- * escape of the arguments object make the mapping unsafe. Missing parameters
14
- * are never added because that would change `fn.length`.
11
+ * Replaces numeric `arguments[i]` reads with the corresponding parameter in
12
+ * sloppy functions with simple parameter lists. It leaves a read unchanged when
13
+ * that index is written or its parameter is reassigned. Reassigning, deleting,
14
+ * or passing around `arguments` disables the rewrite. Missing parameters are
15
+ * never added because that would change `fn.length`.
15
16
  *
16
17
  * @example
17
18
  * // ◀️ before
@@ -2,14 +2,14 @@ import type { NodePath, Visitor } from '@babel/traverse'
2
2
  import * as t from '@babel/types'
3
3
  import type { File } from '@babel/types'
4
4
 
5
- import { isInsideWith } from '../utils/ast'
6
5
  import { traverseForChanges } from '../utils/change-tracking'
7
6
  import type { ChangeState } from '../utils/change-tracking'
8
7
 
9
8
  /**
10
- * Lowers arrows with Babel's capture-aware `arrowFunctionToExpression`. Arrows
11
- * that reach `super` stay intact. Values that escape visible call positions also
12
- * stay intact because only the arrow rejects construction with `new`.
9
+ * Converts arrows with Babel's capture-aware `arrowFunctionToExpression`.
10
+ * Arrows that use `super` stay unchanged. An arrow also stays unchanged unless
11
+ * every use is an immediate call or a discarded expression, because a normal
12
+ * function can be constructed with `new` and has a `prototype`.
13
13
  *
14
14
  * @example
15
15
  * // ◀️ before
@@ -64,9 +64,7 @@ const visitor: Visitor<ChangeState> = {
64
64
  !valueStaysInView(path) ||
65
65
  arrowReferencesSuper(path) ||
66
66
  inUnsafeCaptureContext(path) ||
67
- usesReassignedArguments(path) ||
68
- // `with` can capture Babel's `this` or `arguments` alias.
69
- isInsideWith(path)
67
+ usesReassignedArguments(path)
70
68
  ) {
71
69
  return
72
70
  }
@@ -140,7 +138,7 @@ function valueStaysInView(valuePath: NodePath): boolean {
140
138
  return false
141
139
  }
142
140
  const binding = valuePath.scope.getBinding(parent.node.id.name)
143
- if (!binding || !binding.constant) {
141
+ if (!binding || (binding.kind !== 'const' && !binding.constant)) {
144
142
  return false
145
143
  }
146
144
  return binding.referencePaths.every(
@@ -10,14 +10,14 @@ import {
10
10
  readCrossesFunctionBoundary,
11
11
  } from '../analysis/document-order'
12
12
  import { isPure } from '../analysis/purity'
13
- import { isInsideWith } from '../utils/ast'
14
13
  import { reorderableToStatement } from '../utils/evaluation-order'
15
14
  import { valueToLiteral } from '../utils/literal'
16
15
  import { isCalleeOrTagOf } from '../utils/paths'
17
16
 
18
17
  /**
19
18
  * Drops single-use temporaries through substitution, direct branch writes, or
20
- * constant replacement. Sweeps continue until no shape exposes another.
19
+ * constant replacement. It repeats these rewrites so one removal can expose
20
+ * another.
21
21
  *
22
22
  * @example
23
23
  * // ◀️ before
@@ -94,21 +94,9 @@ function resolveSingleUseBinding(path: NodePath<t.VariableDeclarator>): Binding
94
94
  if (binding.references === 0) {
95
95
  return null
96
96
  }
97
- // `with` can redirect the temporary or its consumer outside Babel's scope table.
98
- if (bindingTouchesWith(binding)) {
99
- return null
100
- }
101
97
  return binding
102
98
  }
103
99
 
104
- function bindingTouchesWith(binding: Binding): boolean {
105
- return (
106
- isInsideWith(binding.path) ||
107
- binding.referencePaths.some((ref) => isInsideWith(ref)) ||
108
- binding.constantViolations.some((violation) => isInsideWith(violation))
109
- )
110
- }
111
-
112
100
  function findProgramScope(ast: File): Scope | undefined {
113
101
  let result: Scope | undefined
114
102
  traverse(ast, {
@@ -145,7 +133,7 @@ function trySubstituteSingleUse(
145
133
  let removeWriteStmt: boolean
146
134
 
147
135
  if (init) {
148
- if (binding.constantViolations.length > 0) {
136
+ if (binding.kind !== 'const' && binding.constantViolations.length > 0) {
149
137
  return false
150
138
  }
151
139
  writeStmt = declStmt
@@ -333,8 +321,9 @@ function tryRenameWritesToReader(
333
321
  return false
334
322
  }
335
323
 
336
- // Redirecting into a later lexical binding could write it in TDZ.
337
- if (!declarationReaches(yBinding.path, violation)) {
324
+ // A later `var` initializer can overwrite a redirected write. Writes before
325
+ // lexical initialization are intentionally ignored.
326
+ if (yBinding.kind === 'var' && !declarationReaches(yBinding.path, violation)) {
338
327
  return false
339
328
  }
340
329
  }
@@ -662,7 +651,7 @@ function spanStatementIsInert(path: NodePath, tempBinding: Binding): boolean {
662
651
  }
663
652
  if (t.isIfStatement(node)) {
664
653
  const test = path.get('test') as NodePath
665
- if (!isPure(test.node, test.scope, test)) {
654
+ if (!isPure(test.node, test.scope)) {
666
655
  return false
667
656
  }
668
657
  if (!spanStatementIsInert(path.get('consequent') as NodePath, tempBinding)) {
@@ -675,9 +664,9 @@ function spanStatementIsInert(path: NodePath, tempBinding: Binding): boolean {
675
664
  const expr = path.get('expression') as NodePath
676
665
  if (isTempWrite(expr.node, tempBinding)) {
677
666
  const rhs = expr.get('right') as NodePath
678
- return isPure(rhs.node, rhs.scope, rhs)
667
+ return isPure(rhs.node, rhs.scope)
679
668
  }
680
- return isPure(expr.node, expr.scope, expr)
669
+ return isPure(expr.node, expr.scope)
681
670
  }
682
671
  return false
683
672
  }
@@ -723,7 +712,7 @@ function tryPropagateConstant(
723
712
  let writeSourcePath: NodePath
724
713
 
725
714
  if (init) {
726
- if (binding.constantViolations.length > 0) {
715
+ if (binding.kind !== 'const' && binding.constantViolations.length > 0) {
727
716
  return false
728
717
  }
729
718
  if (!isUnconditionallyExecuted(declStmt, declaratorPath.scope)) {
@@ -890,10 +879,10 @@ function isStatementMovementSafe(path: NodePath): boolean {
890
879
  if (t.isEmptyStatement(node)) {
891
880
  return true
892
881
  }
893
- // Crossing a declaration can change name resolution or TDZ.
882
+ // Only inert expression statements are safe to cross.
894
883
  if (t.isExpressionStatement(node)) {
895
884
  const expr = path.get('expression') as NodePath
896
- return isPure(expr.node, expr.scope, expr)
885
+ return isPure(expr.node, expr.scope)
897
886
  }
898
887
  return false
899
888
  }
@@ -20,8 +20,7 @@ const RECORD_WIDTH = 4
20
20
  * `scope: 'program'` creates one vault. `scope: 'function'` creates a vault per
21
21
  * function but repeats the program-wide pool in each one. Parameter defaults and
22
22
  * computed keys use the enclosing vault because they run before the function
23
- * body. Code inside `with` gets a lexical vault so the object cannot capture
24
- * decoder references.
23
+ * body.
25
24
  *
26
25
  * Directives, module specifiers, import attributes, and non-computed keys are not
27
26
  * runtime expressions, so they stay unchanged.
@@ -91,7 +90,7 @@ interface ResolvedOptions {
91
90
  scope: 'function' | 'program'
92
91
  }
93
92
 
94
- type VaultOwner = t.Program | t.Function | t.BlockStatement
93
+ type VaultOwner = t.Program | t.Function
95
94
  type VaultOwnerPath = NodePath<VaultOwner>
96
95
 
97
96
  interface Occurrence {
@@ -208,10 +207,6 @@ function findVaultOwner(
208
207
  if (current.isFunction() && child.key === 'body' && scope === 'function') {
209
208
  return current as VaultOwnerPath
210
209
  }
211
- if (current.isWithStatement() && child.key === 'body') {
212
- const body = current.get('body')
213
- return body.isBlockStatement() ? (body as VaultOwnerPath) : null
214
- }
215
210
  if (current.isProgram()) {
216
211
  return current
217
212
  }
@@ -375,7 +370,7 @@ function maskPosition(position: number, key: number, step: number, indexMask: nu
375
370
  }
376
371
 
377
372
  function statementBody(owner: VaultOwnerPath): NodePath<t.Program | t.BlockStatement> {
378
- if (owner.isProgram() || owner.isBlockStatement()) {
373
+ if (owner.isProgram()) {
379
374
  return owner
380
375
  }
381
376
  const body = owner.get('body') as NodePath
@@ -3,14 +3,11 @@ import type { NodePath, Scope, Visitor } from '@babel/traverse'
3
3
  import * as t from '@babel/types'
4
4
  import type { File } from '@babel/types'
5
5
 
6
- import { initializerReaches } from '../analysis/document-order'
7
- import { isInsideWith } from '../utils/ast'
8
- import { hasConstantViolation } from '../utils/paths'
9
-
10
6
  /**
11
- * Converts safe `let` and `const` declarations to `var` and renames bindings
12
- * that would conflict after hoisting. It skips TDZ reads, reassigned constants,
13
- * captured loop bindings, uninitialized loop heads, script globals, and `with`.
7
+ * Converts `let` and `const` declarations to `var` and renames bindings that
8
+ * would conflict after hoisting. It leaves loop declarations unchanged when
9
+ * `var` would lose a captured per-iteration binding or reuse the previous
10
+ * iteration's value.
14
11
  *
15
12
  * @example
16
13
  * // ◀️ before
@@ -33,36 +30,12 @@ export function constLetToVar(ast: File): boolean {
33
30
  if (kind !== 'let' && kind !== 'const') {
34
31
  return
35
32
  }
36
- // Babel's scope table cannot account for properties supplied by `with`.
37
- if (isInsideWith(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
33
  // An uninitialized lexical loop head resets on each entry. `var` does not.
49
34
  if (forHeadMissingInitializer(path)) {
50
35
  return
51
36
  }
52
37
  const blockScope = path.scope
53
38
  const varScope = blockScope.getFunctionParent() ?? blockScope.getProgramParent()
54
- if (varScope.path.isProgram()) {
55
- // Script `var` creates a global-object property; module `var` does not.
56
- if (varScope.path.node.sourceType !== 'module') {
57
- return
58
- }
59
- // A circular importer can read this binding before initialization. A
60
- // lexical binding throws in TDZ then, while `var` reads `undefined`. That
61
- // external read does not appear in this file's references.
62
- if (bindsExportedName(path)) {
63
- return
64
- }
65
- }
66
39
  // One `var` cannot preserve per-iteration bindings captured by closures.
67
40
  if (capturedInEnclosingLoop(path)) {
68
41
  return
@@ -76,10 +49,6 @@ export function constLetToVar(ast: File): boolean {
76
49
  if (!id) {
77
50
  return
78
51
  }
79
- if (isInsideWith(path)) {
80
- return
81
- }
82
-
83
52
  const scope = path.parentPath.scope
84
53
  if (!isVarScope(scope) && scope.parent!.hasBinding(id.name, { noUids: true })) {
85
54
  path.scope.rename(id.name)
@@ -107,35 +76,6 @@ const conflictingFunctionsVisitor: Visitor<{ names: string[] }> = {
107
76
  },
108
77
  }
109
78
 
110
- // Same-scope uses need a reachable initializer. Nested uses also need their
111
- // function value created after initialization.
112
- function hasTdzRiskyReference(path: NodePath<t.VariableDeclaration>): boolean {
113
- const declHome = nearestFunctionOrProgram(path)
114
- for (const name of Object.keys(path.getBindingIdentifiers())) {
115
- const binding = path.scope.getBinding(name)
116
- if (!binding) {
117
- continue
118
- }
119
- for (const usage of [...binding.referencePaths, ...binding.constantViolations]) {
120
- if (usage.findParent((ancestor) => ancestor === path)) {
121
- return true
122
- }
123
- const usageHome = nearestFunctionOrProgram(usage)
124
- if (usageHome === declHome) {
125
- if (!initializerReaches(path, usage)) {
126
- return true
127
- }
128
- continue
129
- }
130
- const nested = outermostNestedFunction(usage, declHome)
131
- if (!nested || nested.isFunctionDeclaration() || !initializerReaches(path, nested)) {
132
- return true
133
- }
134
- }
135
- }
136
- return false
137
- }
138
-
139
79
  function forHeadMissingInitializer(path: NodePath<t.VariableDeclaration>): boolean {
140
80
  if (!t.isForStatement(path.parent) || path.key !== 'init') {
141
81
  return false
@@ -143,26 +83,6 @@ function forHeadMissingInitializer(path: NodePath<t.VariableDeclaration>): boole
143
83
  return path.node.declarations.some((decl) => decl.init == null)
144
84
  }
145
85
 
146
- // A source clause re-exports another module's binding, not this local binding.
147
- function bindsExportedName(path: NodePath<t.VariableDeclaration>): boolean {
148
- if (path.parentPath.isExportNamedDeclaration()) {
149
- return true
150
- }
151
- const names = new Set(Object.keys(path.getBindingIdentifiers()))
152
- const program = path.scope.getProgramParent().path.node as t.Program
153
- for (const statement of program.body) {
154
- if (!t.isExportNamedDeclaration(statement) || statement.source) {
155
- continue
156
- }
157
- for (const specifier of statement.specifiers) {
158
- if (t.isExportSpecifier(specifier) && names.has(specifier.local.name)) {
159
- return true
160
- }
161
- }
162
- }
163
- return false
164
- }
165
-
166
86
  // A nested function or class can observe the loop's per-iteration binding.
167
87
  function capturedInEnclosingLoop(path: NodePath<t.VariableDeclaration>): boolean {
168
88
  const loop = enclosingLoop(path)
@@ -174,7 +94,11 @@ function capturedInEnclosingLoop(path: NodePath<t.VariableDeclaration>): boolean
174
94
  if (!binding) {
175
95
  continue
176
96
  }
177
- for (const usage of [...binding.referencePaths, ...binding.constantViolations]) {
97
+ const usages =
98
+ binding.kind === 'const'
99
+ ? binding.referencePaths
100
+ : [...binding.referencePaths, ...binding.constantViolations]
101
+ for (const usage of usages) {
178
102
  for (
179
103
  let current = usage.parentPath;
180
104
  current && current !== loop;
@@ -232,21 +156,6 @@ function transformBlockScopedVariable(
232
156
  })
233
157
  }
234
158
 
235
- function nearestFunctionOrProgram(path: NodePath): NodePath {
236
- return path.findParent((ancestor) => ancestor.isFunction() || ancestor.isProgram())!
237
- }
238
-
239
- // The shallowest nested function decides when this use can run.
240
- function outermostNestedFunction(usage: NodePath, home: NodePath): NodePath | null {
241
- let result: NodePath | null = null
242
- for (let current = usage.parentPath; current && current !== home; current = current.parentPath) {
243
- if (current.isFunction()) {
244
- result = current
245
- }
246
- }
247
- return result
248
- }
249
-
250
159
  function enclosingLoop(path: NodePath): NodePath<t.Loop> | null {
251
160
  for (let current = path.parentPath; current; current = current.parentPath) {
252
161
  if (current.isFunction()) {
@@ -3,11 +3,9 @@ import type { File } from '@babel/types'
3
3
 
4
4
  import { traverseForChanges } from '../utils/change-tracking'
5
5
  import type { ChangeState } from '../utils/change-tracking'
6
- import { hasConstantViolation } from '../utils/paths'
7
6
 
8
7
  /**
9
- * Changes `const` to `let` when Babel finds no writes. Read-only bindings keep
10
- * the same scope and TDZ behavior; the declarations differ only on writes.
8
+ * Changes `const` declarations to `let`.
11
9
  *
12
10
  * @example
13
11
  * // ◀️ before
@@ -27,9 +25,6 @@ const visitor: Visitor<ChangeState> = {
27
25
  if (path.node.kind !== 'const') {
28
26
  return
29
27
  }
30
- if (hasConstantViolation(path)) {
31
- return
32
- }
33
28
  path.node.kind = 'let'
34
29
  state.changed = true
35
30
  },