webfuscator 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +16 -10
  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
@@ -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
  },
@@ -4,7 +4,6 @@ import * as t from '@babel/types'
4
4
  import type { File } from '@babel/types'
5
5
 
6
6
  import { isSideEffectFree } from '../analysis/purity'
7
- import { isInsideWith } from '../utils/ast'
8
7
  import { traverseForChanges } from '../utils/change-tracking'
9
8
  import type { ChangeState } from '../utils/change-tracking'
10
9
 
@@ -90,8 +89,7 @@ const visitor: Visitor<ChangeState> = {
90
89
  if (!t.isIdentifier(object) || object.name !== 'console') {
91
90
  return
92
91
  }
93
- // `with` can supply a different `console` outside Babel's scope table.
94
- if (path.scope.getBinding('console') || isInsideWith(path)) {
92
+ if (path.scope.getBinding('console')) {
95
93
  return
96
94
  }
97
95
  // Removing the call also removes argument and computed-key evaluation.
@@ -108,11 +106,11 @@ function callArgumentsAreInert(
108
106
  callee: t.MemberExpression | t.OptionalMemberExpression,
109
107
  path: NodePath<t.ExpressionStatement>,
110
108
  ): boolean {
111
- if (callee.computed && !isSideEffectFree(callee.property, path.scope, path)) {
109
+ if (callee.computed && !isSideEffectFree(callee.property, path.scope)) {
112
110
  return false
113
111
  }
114
112
  for (const arg of expr.arguments) {
115
- if (!t.isExpression(arg) || !isSideEffectFree(arg, path.scope, path)) {
113
+ if (!t.isExpression(arg) || !isSideEffectFree(arg, path.scope)) {
116
114
  return false
117
115
  }
118
116
  }
@@ -2,7 +2,6 @@ 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
 
@@ -58,10 +57,6 @@ function lowerBinaryAssignment(path: NodePath<t.AssignmentExpression>): boolean
58
57
  const read = t.cloneNode(left, true)
59
58
 
60
59
  if (t.isMemberExpression(left) && t.isMemberExpression(read)) {
61
- // `with` can turn a bare object name into a getter or shadow a memo temp.
62
- if (isInsideWith(path)) {
63
- return false
64
- }
65
60
  const object = left.object
66
61
  if (t.isExpression(object)) {
67
62
  const memo = path.scope.maybeGenerateMemoised(object)
@@ -2,7 +2,6 @@ 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
 
@@ -52,10 +51,6 @@ function lowerLogicalAssignment(path: NodePath<t.AssignmentExpression>): boolean
52
51
  const lhs = t.cloneNode(left, true)
53
52
 
54
53
  if (t.isMemberExpression(left) && t.isMemberExpression(lhs)) {
55
- // `with` can turn a bare object name into a getter or shadow a memo temp.
56
- if (isInsideWith(path)) {
57
- return false
58
- }
59
54
  const object = left.object
60
55
  if (t.isExpression(object)) {
61
56
  const memo = path.scope.maybeGenerateMemoised(object)
@@ -3,7 +3,7 @@ import * as t from '@babel/types'
3
3
  import type { File } from '@babel/types'
4
4
 
5
5
  import { initializerReaches } from '../analysis/document-order'
6
- import { isInsideWith, isProtoKey } from '../utils/ast'
6
+ import { isProtoKey } from '../utils/ast'
7
7
  import { traverseForChanges } from '../utils/change-tracking'
8
8
  import type { ChangeState } from '../utils/change-tracking'
9
9
  import { isCalleeOrTagOf } from '../utils/paths'
@@ -172,12 +172,7 @@ function performExtraction(
172
172
  if (!binding) {
173
173
  return false
174
174
  }
175
- if (!binding.constant) {
176
- return false
177
- }
178
-
179
- // `with` can shadow a synthesized entry binding.
180
- if (isInsideWith(path)) {
175
+ if (path.node.kind !== 'const' && !binding.constant) {
181
176
  return false
182
177
  }
183
178
 
@@ -196,15 +191,11 @@ function performExtraction(
196
191
  const seenMembers = new Set<t.MemberExpression>()
197
192
 
198
193
  for (const ref of binding.referencePaths) {
199
- // Before initialization, a wrapper read throws while a bare binding may not.
200
- if (!initializerReaches(path, ref)) {
194
+ // A pre-initializer `var` wrapper read sees `undefined`, while an extracted
195
+ // binding read would not perform the member access.
196
+ if (path.node.kind === 'var' && !initializerReaches(path, ref)) {
201
197
  return false
202
198
  }
203
- // `with` can redirect the rewritten reference.
204
- if (isInsideWith(ref)) {
205
- return false
206
- }
207
-
208
199
  const parent = ref.parentPath
209
200
  if (!parent || !parent.isMemberExpression()) {
210
201
  return false
@@ -5,7 +5,7 @@ import type { File } from '@babel/types'
5
5
 
6
6
  import { evaluateConstant, isOpaque } from '../analysis/constant'
7
7
  import { isSideEffectFree } from '../analysis/purity'
8
- import { isInDirectivePrologue, isInsideWith } from '../utils/ast'
8
+ import { isInDirectivePrologue } from '../utils/ast'
9
9
  import type { ChangeState } from '../utils/change-tracking'
10
10
  import { valueToLiteral } from '../utils/literal'
11
11
 
@@ -507,7 +507,7 @@ function replaceWithPreservedExpression(
507
507
  expression: t.Expression,
508
508
  result: t.Expression,
509
509
  ): void {
510
- if (!isSideEffectFree(expression, path.scope, path)) {
510
+ if (!isSideEffectFree(expression, path.scope)) {
511
511
  path.replaceWith(t.sequenceExpression([expression, result]))
512
512
  return
513
513
  }
@@ -527,7 +527,7 @@ function foldArrayLiteralLength(
527
527
  }
528
528
  const effects = array.elements.filter(
529
529
  (element): element is t.Expression =>
530
- element !== null && t.isExpression(element) && !isSideEffectFree(element, path.scope, path),
530
+ element !== null && t.isExpression(element) && !isSideEffectFree(element, path.scope),
531
531
  )
532
532
  const length = t.numericLiteral(array.elements.length)
533
533
  path.replaceWith(effects.length === 0 ? length : t.sequenceExpression([...effects, length]))
@@ -555,10 +555,6 @@ function foldStringIndex(
555
555
  }
556
556
 
557
557
  function handleCall(path: NodePath<t.CallExpression>, patchedRoots: Set<string>): void {
558
- // `with` can replace an intrinsic that Babel treats as an unbound global.
559
- if (isInsideWith(path)) {
560
- return
561
- }
562
558
  const callee = path.node.callee
563
559
 
564
560
  if (t.isIdentifier(callee)) {
@@ -606,11 +602,6 @@ function handleMember(path: NodePath<t.MemberExpression>, patchedRoots: Set<stri
606
602
  if (isReferenceRequiredPosition(path)) {
607
603
  return
608
604
  }
609
- // Delay the ancestor walk until cheap shape checks pass.
610
- if (isInsideWith(path)) {
611
- return
612
- }
613
-
614
605
  const obj = path.node.object
615
606
 
616
607
  if (t.isIdentifier(obj) && !path.scope.getBinding(obj.name) && !patchedRoots.has(obj.name)) {
@@ -190,7 +190,7 @@ function tryReassociateCommutative(path: NodePath<t.BinaryExpression>, op: Commu
190
190
  }
191
191
  // Grouping constants can move one operand's coercion past another operand's
192
192
  // evaluation. Multiple non-constant operands must therefore be pure.
193
- if (others.length > 1 && !others.every((leaf) => isPure(leaf, path.scope, path))) {
193
+ if (others.length > 1 && !others.every((leaf) => isPure(leaf, path.scope))) {
194
194
  return false
195
195
  }
196
196
 
@@ -296,7 +296,7 @@ function tryReassociateSubtract(path: NodePath<t.BinaryExpression>): boolean {
296
296
  }
297
297
  // Reassociation can move `base` coercion past a subtractee's evaluation.
298
298
  // Non-constant subtractees must be pure.
299
- if (!otherSubs.every((leaf) => isPure(leaf, path.scope, path))) {
299
+ if (!otherSubs.every((leaf) => isPure(leaf, path.scope))) {
300
300
  return false
301
301
  }
302
302
  const sum = combineConstants(constSubs, '+')
@@ -6,14 +6,13 @@ import { traverseForChanges } from '../utils/change-tracking'
6
6
  import type { ChangeState } from '../utils/change-tracking'
7
7
 
8
8
  /**
9
- * Rewrites safe function declarations as `var` initializers. The function value
10
- * is no longer hoisted, so nothing may observe the binding before assignment.
9
+ * Rewrites function declarations at script top level or directly inside a
10
+ * function body as `var` initializers. It leaves a declaration unchanged when
11
+ * an earlier statement could execute user code before the assignment.
11
12
  *
12
- * Preceding statements must be inert. Name inference and recursive references
13
- * still use the `var` binding.
13
+ * Name inference and recursive references still use the `var` binding.
14
14
  *
15
- * Block-level declarations and module top-level declarations stay unchanged due
16
- * to Annex B aliases and live exports.
15
+ * Block-level and module top-level declarations stay unchanged.
17
16
  *
18
17
  * @example
19
18
  * // ◀️ before
@@ -9,8 +9,7 @@ import type { ChangeState } from '../utils/change-tracking'
9
9
  * Moves `var` declarations to the start of their var scope without moving
10
10
  * initializer evaluation.
11
11
  *
12
- * Direct exports and declarations inside `with` stay in place. Static blocks
13
- * keep separate declarations.
12
+ * Direct exports stay in place. Static blocks keep separate declarations.
14
13
  *
15
14
  * @example
16
15
  * // ◀️ before
@@ -43,7 +42,7 @@ export function hoistVariables(ast: File): boolean {
43
42
  return
44
43
  }
45
44
  const owner = ownerPath(path)
46
- if (owner === null || isInsideOwnerWith(path, owner)) {
45
+ if (owner === null) {
47
46
  return
48
47
  }
49
48
 
@@ -98,15 +97,6 @@ function ownerPath(path: NodePath<t.VariableDeclaration>): OwnerPath | null {
98
97
  return null
99
98
  }
100
99
 
101
- function isInsideOwnerWith(path: NodePath, owner: OwnerPath): boolean {
102
- for (let current = path.parentPath; current && current !== owner; current = current.parentPath) {
103
- if (current.isWithStatement()) {
104
- return true
105
- }
106
- }
107
- return false
108
- }
109
-
110
100
  function hoistedScope(scopes: Map<t.Node, HoistedScope>, owner: OwnerPath): HoistedScope {
111
101
  const existing = scopes.get(owner.node)
112
102
  if (existing) {
@@ -6,10 +6,10 @@ import type { File } from '@babel/types'
6
6
  import { analyzeInlineability } from '../analysis/inlineability'
7
7
  import type { InlineCandidate } from '../analysis/inlineability'
8
8
  import { isPure } from '../analysis/purity'
9
- import { isInsideWith, walkOwnFunctionScope } from '../utils/ast'
9
+ import { walkOwnFunctionScope } from '../utils/ast'
10
10
  import { reorderableToStatement } from '../utils/evaluation-order'
11
11
  import { generateLoopLabel } from '../utils/loop-lowering'
12
- import { hasAnnexBFunctionAlias, isInStrictContext } from '../utils/paths'
12
+ import { isInStrictContext } from '../utils/paths'
13
13
 
14
14
  /**
15
15
  * Replaces eligible calls with function bodies, working from inner calls out.
@@ -135,13 +135,6 @@ function isSafeAtCallSite(
135
135
  callPath: NodePath<t.CallExpression>,
136
136
  candidates: Map<string, InlineCandidate>,
137
137
  ): boolean {
138
- // `with` and Annex B aliases can resolve a free name outside Babel's scope table.
139
- if (isInsideWith(callPath)) {
140
- return false
141
- }
142
- if (annexBHoistShadowsFreeVar(callPath, freeVars)) {
143
- return false
144
- }
145
138
  // Spliced code inherits call-site strictness, so it must match the callee.
146
139
  if (isInStrictContext(candidate.fnPath) !== isInStrictContext(callPath)) {
147
140
  return false
@@ -192,32 +185,6 @@ function isSafeAtCallSite(
192
185
  return true
193
186
  }
194
187
 
195
- // ECMA-262 B.3.2.1 creates a sloppy block-function alias that Babel misses in
196
- // the enclosing scope.
197
- function annexBHoistShadowsFreeVar(
198
- callPath: NodePath<t.CallExpression>,
199
- freeVars: Set<string>,
200
- ): boolean {
201
- if (freeVars.size === 0 || isInStrictContext(callPath)) {
202
- return false
203
- }
204
- const owner = callPath.getFunctionParent() ?? callPath.scope.getProgramParent().path
205
- let found = false
206
- owner.traverse({
207
- Function(path) {
208
- path.skip()
209
- },
210
- FunctionDeclaration(path) {
211
- const id = path.node.id
212
- if (id && freeVars.has(id.name) && hasAnnexBFunctionAlias(path)) {
213
- found = true
214
- path.stop()
215
- }
216
- },
217
- })
218
- return found
219
- }
220
-
221
188
  // Only loops in the same function repeat the spliced body.
222
189
  function callIsInsideLoop(callPath: NodePath): boolean {
223
190
  for (let current: NodePath | null = callPath.parentPath; current; current = current.parentPath) {
@@ -580,7 +547,9 @@ function inlineCallSite(
580
547
  const replacement: t.Statement[] = []
581
548
  if (directTarget.kind === 'declarator') {
582
549
  replacement.push(
583
- t.variableDeclaration('var', [t.variableDeclarator(t.identifier(directTarget.name))]),
550
+ t.variableDeclaration(directTarget.declarationKind, [
551
+ t.variableDeclarator(t.identifier(directTarget.name)),
552
+ ]),
584
553
  )
585
554
  }
586
555
  if (needsLabel) {
@@ -620,7 +589,9 @@ function inlineCallSite(
620
589
  return true
621
590
  }
622
591
 
623
- type DirectTarget = { kind: 'declarator'; name: string } | { kind: 'assign'; name: string }
592
+ type DirectTarget =
593
+ | { kind: 'declarator'; name: string; declarationKind: 'let' | 'var' }
594
+ | { kind: 'assign'; name: string }
624
595
 
625
596
  function identifyDirectAssignmentTarget(
626
597
  callPath: NodePath<t.CallExpression>,
@@ -641,9 +612,8 @@ function identifyDirectAssignmentTarget(
641
612
  if (!declPath || !declPath.isVariableDeclaration() || declPath.node.declarations.length !== 1) {
642
613
  return null
643
614
  }
644
- // Only `var` can become an uninitialized direct target without changing TDZ
645
- // or block scope.
646
- if (declPath.node.kind !== 'var') {
615
+ const declarationKind = declPath.node.kind
616
+ if (declarationKind !== 'var' && declarationKind !== 'let') {
647
617
  return null
648
618
  }
649
619
  const owner = declPath.parentPath
@@ -666,7 +636,7 @@ function identifyDirectAssignmentTarget(
666
636
  if (argsContainIdentName(args, name)) {
667
637
  return null
668
638
  }
669
- return { kind: 'declarator', name }
639
+ return { kind: 'declarator', name, declarationKind }
670
640
  }
671
641
 
672
642
  if (
@@ -713,10 +683,10 @@ function argEligibleForElision(argPath: NodePath, scope: Scope): boolean {
713
683
  return false
714
684
  }
715
685
  const binding = scope.getBinding(argPath.node.name)
716
- if (!binding || binding.constantViolations.length > 0) {
686
+ if (!binding || (binding.kind !== 'const' && binding.constantViolations.length > 0)) {
717
687
  return false
718
688
  }
719
- return isPure(argPath.node, scope, argPath)
689
+ return isPure(argPath.node, scope)
720
690
  }
721
691
 
722
692
  // Catch parameters stay scoped to their copied handlers. Counting them here
@@ -930,8 +900,8 @@ function isNonReferenceSlot(parent: t.Node, key: string): boolean {
930
900
  interface StatementWalkContext {
931
901
  insideNestedLoop: boolean
932
902
  insideNestedSwitch: boolean
933
- // Tail position propagates through final blocks, branches, and labels. Try,
934
- // switch, loops, and `with` interrupt it because more code may run afterward.
903
+ // Tail position propagates through final blocks, branches, labels, and `with`.
904
+ // Try, switch, and loops interrupt it because more code may run afterward.
935
905
  tailPosition: boolean
936
906
  }
937
907
 
@@ -1051,7 +1021,7 @@ function walkIntoCompound(
1051
1021
  }
1052
1022
 
1053
1023
  if (t.isWithStatement(node)) {
1054
- node.body = walkSingleChild(node.body, hooks, { ...ctx, tailPosition: false })
1024
+ node.body = walkSingleChild(node.body, hooks, ctx)
1055
1025
  }
1056
1026
  }
1057
1027
 
@@ -3,10 +3,10 @@ import * as t from '@babel/types'
3
3
  import type { File } from '@babel/types'
4
4
 
5
5
  import { isPure } from '../analysis/purity'
6
- import { isInsideWith } from '../utils/ast'
7
6
  import { traverseForChanges } from '../utils/change-tracking'
8
7
  import type { ChangeState } from '../utils/change-tracking'
9
8
  import { lowerLogicalWriteStatement } from '../utils/logical-lowering'
9
+ import { buildNonNullishCheck, buildNullishCheck } from '../utils/nullish'
10
10
 
11
11
  /**
12
12
  * Rewrites logical expressions as guarded assignments or ternaries. A
@@ -23,7 +23,7 @@ import { lowerLogicalWriteStatement } from '../utils/logical-lowering'
23
23
  * if (!x) x = b;
24
24
  * y = c;
25
25
  * if (y) y = d;
26
- * f((_t = get()) != null ? _t : fallback);
26
+ * f(((_t = get()) !== null ? _t !== void 0 : false) ? _t : fallback);
27
27
  */
28
28
  export function logicalToTernary(ast: File): boolean {
29
29
  return traverseForChanges(ast, visitor)
@@ -45,11 +45,6 @@ function lowerLogical(path: NodePath<t.LogicalExpression>): boolean {
45
45
  if (op !== '&&' && op !== '||' && op !== '??') {
46
46
  return false
47
47
  }
48
- // `with` can turn repeated names into getters and shadow a cache temp.
49
- if (isInsideWith(path)) {
50
- return false
51
- }
52
-
53
48
  if (lowerLogicalWriteStatement(path, (target) => buildFallbackTest(op, target))) {
54
49
  return true
55
50
  }
@@ -60,7 +55,7 @@ function lowerLogical(path: NodePath<t.LogicalExpression>): boolean {
60
55
  let valueSrc: t.Expression
61
56
 
62
57
  // Babel purity misses user coercion in unary and binary expressions.
63
- if (isPure(leftPath.node, scope, leftPath)) {
58
+ if (isPure(leftPath.node, scope)) {
64
59
  testSrc = node.left
65
60
  valueSrc = t.cloneNode(node.left)
66
61
  } else {
@@ -71,7 +66,7 @@ function lowerLogical(path: NodePath<t.LogicalExpression>): boolean {
71
66
  }
72
67
 
73
68
  const test: t.Expression =
74
- op === '??' ? t.binaryExpression('!=', testSrc, t.nullLiteral()) : testSrc
69
+ op === '??' ? buildNonNullishCheck(testSrc, t.cloneNode(valueSrc)) : testSrc
75
70
 
76
71
  path.replaceWith(
77
72
  op === '&&'
@@ -88,5 +83,5 @@ function buildFallbackTest(op: '&&' | '||' | '??', target: t.Identifier): t.Expr
88
83
  if (op === '&&') {
89
84
  return target
90
85
  }
91
- return t.binaryExpression('==', target, t.nullLiteral())
86
+ return buildNullishCheck(target, t.cloneNode(target))
92
87
  }
@@ -13,11 +13,8 @@ import { StringGenerator } from '../utils/string-generator'
13
13
 
14
14
  /**
15
15
  * Renames statically known public properties through one program-wide mapping.
16
- * Property selection follows Terser, with extra collision checks for names
17
- * that this pipeline must leave unchanged.
18
- *
19
- * This pass runs before shape preparation, while Babel still distinguishes
20
- * `obj.name` from `obj["name"]`.
16
+ * Options select or reserve names, and collision checks keep every unchanged
17
+ * property distinct from the generated names.
21
18
  *
22
19
  * @example
23
20
  * // ◀️ before