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
@@ -2,15 +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
  import { lowerLogicalWriteStatement } from '../utils/logical-lowering'
8
+ import { buildNonNullishCheck, buildNullishCheck } from '../utils/nullish'
9
9
 
10
10
  /**
11
- * Rewrites `a ?? b` as a guarded assignment or `a != null ? a : b`. A
12
- * side-effectful left side is cached. Loose inequality checks both nullish
13
- * values in one comparison.
11
+ * Rewrites `a ?? b` as a guarded assignment or strict null and undefined
12
+ * checks. A side-effectful left side is cached.
14
13
  *
15
14
  * @example
16
15
  * // ◀️ before
@@ -19,8 +18,8 @@ import { lowerLogicalWriteStatement } from '../utils/logical-lowering'
19
18
  *
20
19
  * // ▶️ after
21
20
  * var x = a;
22
- * if (x == null) x = b;
23
- * f((_t = get()) != null ? _t : fallback);
21
+ * if (x === null ? true : x === void 0) x = b;
22
+ * f(((_t = get()) !== null ? _t !== void 0 : false) ? _t : fallback);
24
23
  */
25
24
  export function nullishCoalescingToTernary(ast: File): boolean {
26
25
  return traverseForChanges(ast, visitor)
@@ -39,11 +38,6 @@ function lowerNullishCoalescing(path: NodePath<t.LogicalExpression>): boolean {
39
38
  if (node.operator !== '??') {
40
39
  return false
41
40
  }
42
- // `with` can turn a repeated name into a getter and shadow a cache temp.
43
- if (isInsideWith(path)) {
44
- return false
45
- }
46
-
47
41
  if (lowerLogicalWriteStatement(path, nullishFallbackTest)) {
48
42
  return true
49
43
  }
@@ -64,7 +58,7 @@ function lowerNullishCoalescing(path: NodePath<t.LogicalExpression>): boolean {
64
58
 
65
59
  path.replaceWith(
66
60
  t.conditionalExpression(
67
- t.binaryExpression('!=', test, t.nullLiteral()),
61
+ buildNonNullishCheck(test, t.cloneNode(consequent)),
68
62
  consequent,
69
63
  node.right,
70
64
  ),
@@ -73,5 +67,5 @@ function lowerNullishCoalescing(path: NodePath<t.LogicalExpression>): boolean {
73
67
  }
74
68
 
75
69
  function nullishFallbackTest(target: t.Identifier): t.Expression {
76
- return t.binaryExpression('==', target, t.nullLiteral())
70
+ return buildNullishCheck(target, t.cloneNode(target))
77
71
  }
@@ -16,7 +16,7 @@ import type { ChangeState } from '../utils/change-tracking'
16
16
  *
17
17
  * Non-computed `__proto__` methods stay intact because the property form invokes
18
18
  * ECMA-262 B.3.1 prototype-setting behavior. Computed keys create own properties
19
- * and remain safe.
19
+ * and can be rewritten.
20
20
  *
21
21
  * @example
22
22
  * // ◀️ before
@@ -2,9 +2,9 @@ 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'
7
+ import { buildNullishCheck } from '../utils/nullish'
8
8
 
9
9
  /**
10
10
  * Rewrites optional chains as nested ternaries. Side-effectful receivers are
@@ -18,9 +18,10 @@ import type { ChangeState } from '../utils/change-tracking'
18
18
  * a?.b?.c;
19
19
  *
20
20
  * // ▶️ after
21
- * obj == null ? void 0 : (_m = obj.m).call(obj, arg);
22
- * obj == null ? true : delete obj.x;
23
- * a == null ? void 0 : (_b = a.b) == null ? void 0 : _b.c;
21
+ * (obj === null ? true : obj === void 0) ? void 0 : (_m = obj.m).call(obj, arg);
22
+ * (obj === null ? true : obj === void 0) ? true : delete obj.x;
23
+ * (a === null ? true : a === void 0) ? void 0 :
24
+ * ((_b = a.b) === null ? true : _b === void 0) ? void 0 : _b.c;
24
25
  */
25
26
  export function optionalChainingToTernary(ast: File): boolean {
26
27
  return traverseForChanges(ast, visitor)
@@ -40,10 +41,6 @@ const visitor: Visitor<ChangeState> = {
40
41
  }
41
42
 
42
43
  function lowerOptionalChain(path: OptionalPath): boolean {
43
- // `with` can make a repeated receiver name invoke a getter twice.
44
- if (isInsideWith(path)) {
45
- return false
46
- }
47
44
  if (isInsideOptionalChain(path)) {
48
45
  return false
49
46
  }
@@ -173,16 +170,19 @@ function transformChain(path: OptionalPath): boolean {
173
170
  }
174
171
 
175
172
  let check: t.Expression
173
+ let repeatedCheck: t.Expression
176
174
 
177
175
  if (path.scope.isStatic(chain)) {
178
176
  // The receiver stays in the member expression, so the check needs its own
179
177
  // node. One node under two parents is visited twice, and a later in-place
180
178
  // rewrite such as renameIdentifiers then applies itself twice to it.
181
179
  check = t.cloneNode(chain)
180
+ repeatedCheck = t.cloneNode(chain)
182
181
  } else {
183
182
  const tmp = path.scope.generateUidIdentifierBasedOnNode(chain)
184
183
  path.scope.push({ id: t.cloneNode(tmp) })
185
184
  check = t.assignmentExpression('=', t.cloneNode(tmp), chain)
185
+ repeatedCheck = t.cloneNode(tmp)
186
186
  if (isCall) {
187
187
  ;(node as t.CallExpression).callee = t.cloneNode(tmp)
188
188
  } else {
@@ -208,7 +208,7 @@ function transformChain(path: OptionalPath): boolean {
208
208
  }
209
209
  }
210
210
 
211
- const condition = t.binaryExpression('==', check, t.nullLiteral())
211
+ const condition = buildNullishCheck(check, repeatedCheck)
212
212
 
213
213
  let alternate = replacementPath.node as t.Expression
214
214
  if (isDelete && i === optionals.length - 1) {
@@ -6,7 +6,6 @@ import { hasDuplicateParamNames } from '../utils/ast'
6
6
  import { traverseForChanges } from '../utils/change-tracking'
7
7
  import type { ChangeState } from '../utils/change-tracking'
8
8
  import { isLiteralShaped } from '../utils/literal'
9
- import { hasAnnexBFunctionAlias } from '../utils/paths'
10
9
 
11
10
  /**
12
11
  * Moves leading literal-valued `var` declarations into trailing default
@@ -50,10 +49,6 @@ function packLeadingIntoParams(path: NodePath<t.Function>): boolean {
50
49
  ) {
51
50
  return false
52
51
  }
53
- // An Annex B alias splits call sites across bindings Babel does not connect.
54
- if (hasAnnexBFunctionAlias(path)) {
55
- return false
56
- }
57
52
  const params = path.node.params
58
53
  if (params.some((param) => t.isRestElement(param))) {
59
54
  return false
@@ -159,7 +154,11 @@ function methodComputedKey(path: NodePath<t.Function>): NodePath | null {
159
154
  // arity and without spread.
160
155
  function allCallSitesRespectArity(path: NodePath<t.Function>, arity: number): boolean {
161
156
  const external = functionBinding(path)
162
- if (!external || !external.constant || !bindingCallsRespectArity(external, arity)) {
157
+ if (
158
+ !external ||
159
+ (external.kind !== 'const' && !external.constant) ||
160
+ !bindingCallsRespectArity(external, arity)
161
+ ) {
163
162
  return false
164
163
  }
165
164
  if (path.isFunctionExpression() && path.node.id) {
@@ -189,17 +188,12 @@ function bindingCallsRespectArity(binding: Binding, arity: number): boolean {
189
188
  return true
190
189
  }
191
190
 
192
- // Annex B can hoist function names from nested blocks into this function scope.
193
191
  function functionDeclarationNames(body: t.BlockStatement): Set<string> {
194
- const names = new Set<string>()
195
- t.traverseFast(body, (node) => {
196
- if (t.isFunctionDeclaration(node) && node.id) {
197
- names.add(node.id.name)
198
- return t.traverseFast.skip
199
- }
200
- return node !== body && t.isFunction(node) ? t.traverseFast.skip : undefined
201
- })
202
- return names
192
+ return new Set(
193
+ body.body.flatMap((statement) =>
194
+ t.isFunctionDeclaration(statement) && statement.id ? [statement.id.name] : [],
195
+ ),
196
+ )
203
197
  }
204
198
 
205
199
  function functionBinding(path: NodePath<t.Function>): Binding | null {
@@ -5,7 +5,6 @@ import * as t from '@babel/types'
5
5
  import type { File } from '@babel/types'
6
6
 
7
7
  import type { TransformContext } from '../options'
8
- import { isInsideWith } from '../utils/ast'
9
8
  import { isCalleeOrTagOf, isInStrictContext, referencesOrWritesVariable } from '../utils/paths'
10
9
  import { mulberry32 } from '../utils/random'
11
10
  import { StringGenerator } from '../utils/string-generator'
@@ -108,17 +107,10 @@ const STANDARD_GLOBALS: readonly string[] = [
108
107
  ]
109
108
 
110
109
  /**
111
- * Serializes the whole program to a string and rebuilds it at runtime through
112
- * the `Function` constructor. A detached function body only reaches true globals
113
- * and its own parameters. A free name might instead be a CommonJS `require`, a
114
- * bundler-injected binding, or a live import, none reachable from the body and
115
- * none distinguishable from a global at build time, so free names are routed
116
- * through a parameter object whose accessors run in the original top-level scope
117
- * and resolve each name there. Imports remain above the call. Names inside
118
- * `with` stay bare so the object still controls lookup. A Function body cannot
119
- * contain exports, so pack throws when it finds one. Code that needs
120
- * `import.meta`, top-level await, dynamic `import()`, or top-level `arguments`
121
- * will not run after the move.
110
+ * Serializes the whole program and runs it through the `Function` constructor.
111
+ * Imports stay outside the generated function. Reads and writes to other free
112
+ * names go through accessors created in the original top-level scope. Export
113
+ * statements are rejected because they cannot appear in a `Function` body.
122
114
  *
123
115
  * @example
124
116
  * // ◀️ before
@@ -233,11 +225,6 @@ function packProgram(programPath: NodePath<t.Program>, ctx: TransformContext): b
233
225
  if (name === objectName || name === 'arguments') {
234
226
  return false
235
227
  }
236
- // Inside `with`, a free name may resolve to a property of the with object at
237
- // runtime, so routing it to the top-level accessor would change behavior.
238
- if (isInsideWith(path)) {
239
- return false
240
- }
241
228
  if (!referencesOrWritesVariable(path)) {
242
229
  return false
243
230
  }
@@ -1,14 +1,11 @@
1
- import type { NodePath, Visitor } from '@babel/traverse'
2
- import * as t from '@babel/types'
1
+ import type { Visitor } from '@babel/traverse'
3
2
  import type { File } from '@babel/types'
4
3
 
5
4
  import { traverseForChanges } from '../utils/change-tracking'
6
5
  import type { ChangeState } from '../utils/change-tracking'
7
6
 
8
7
  /**
9
- * Removes a function expression's name when the body does not use its binding
10
- * and NamedEvaluation restores the same `.name`. Other positions could infer no
11
- * name or a different one.
8
+ * Removes a function expression's name when its body does not reference it.
12
9
  *
13
10
  * @example
14
11
  * // ◀️ before
@@ -17,7 +14,7 @@ import type { ChangeState } from '../utils/change-tracking'
17
14
  *
18
15
  * // ▶️ after
19
16
  * var greet = function () {};
20
- * var a = function bar() {};
17
+ * var a = function () {};
21
18
  */
22
19
  export function removeAnonymousFunctionNames(ast: File): boolean {
23
20
  return traverseForChanges(ast, visitor)
@@ -38,47 +35,7 @@ const visitor: Visitor<ChangeState> = {
38
35
  ) {
39
36
  return
40
37
  }
41
- if (inferredName(path) !== id.name) {
42
- return
43
- }
44
38
  path.node.id = null
45
39
  state.changed = true
46
40
  },
47
41
  }
48
-
49
- // No inferred name means `.name` would become empty.
50
- function inferredName(path: NodePath<t.FunctionExpression>): string | null {
51
- const node = path.node
52
- const parentPath = path.parentPath
53
- if (!parentPath) {
54
- return null
55
- }
56
- const parent = parentPath.node
57
- if (t.isVariableDeclarator(parent) && parent.init === node && t.isIdentifier(parent.id)) {
58
- return parent.id.name
59
- }
60
- if (
61
- t.isAssignmentExpression(parent) &&
62
- parent.operator === '=' &&
63
- parent.right === node &&
64
- t.isIdentifier(parent.left)
65
- ) {
66
- return parent.left.name
67
- }
68
- if (t.isAssignmentPattern(parent) && parent.right === node && t.isIdentifier(parent.left)) {
69
- return parent.left.name
70
- }
71
- if (
72
- (t.isObjectProperty(parent) || t.isClassProperty(parent)) &&
73
- parent.value === node &&
74
- !parent.computed
75
- ) {
76
- if (t.isIdentifier(parent.key)) {
77
- return parent.key.name
78
- }
79
- if (t.isStringLiteral(parent.key)) {
80
- return parent.key.value
81
- }
82
- }
83
- return null
84
- }
@@ -69,41 +69,21 @@ function isCompletion(node: t.Node): boolean {
69
69
  )
70
70
  }
71
71
 
72
- // Hoisted bindings and referenced lexical TDZ remain observable from reachable
73
- // code. An unreferenced lexical declaration is inert.
74
72
  function isSafeToRemoveAfterCompletion(path: NodePath<t.Statement>): boolean {
75
73
  const node = path.node
76
74
  if (t.isFunctionDeclaration(node) || (t.isVariableDeclaration(node) && node.kind === 'var')) {
77
75
  return false
78
76
  }
79
- if (t.isClassDeclaration(node) || (t.isVariableDeclaration(node) && node.kind !== 'var')) {
80
- return !declaresReferencedBinding(path)
81
- }
82
- return !nestsHoistedDeclaration(node)
83
- }
84
-
85
- // A write before `let x` also observes its TDZ.
86
- function declaresReferencedBinding(path: NodePath<t.Statement>): boolean {
87
- for (const name of Object.keys(path.getBindingIdentifiers())) {
88
- const binding = path.scope.getBinding(name)
89
- if (binding && (binding.references > 0 || binding.constantViolations.length > 0)) {
90
- return true
91
- }
92
- }
93
- return false
77
+ return !nestsHoistedVar(node)
94
78
  }
95
79
 
96
- function nestsHoistedDeclaration(node: t.Node): boolean {
80
+ function nestsHoistedVar(node: t.Node): boolean {
97
81
  let found = false
98
82
  t.traverseFast(node, (child) => {
99
83
  if (found) {
100
84
  return t.traverseFast.skip
101
85
  }
102
86
  if (child !== node && t.isFunction(child)) {
103
- // Function declarations may add Annex B aliases. Function bodies do not.
104
- if (t.isFunctionDeclaration(child)) {
105
- found = true
106
- }
107
87
  return t.traverseFast.skip
108
88
  }
109
89
  if (t.isVariableDeclaration(child) && child.kind === 'var') {
@@ -5,7 +5,6 @@ import type { File } from '@babel/types'
5
5
 
6
6
  import { isSideEffectFree } from '../analysis/purity'
7
7
  import type { ChangeState } from '../utils/change-tracking'
8
- import { hasAnnexBFunctionAlias } from '../utils/paths'
9
8
 
10
9
  /**
11
10
  * Removes unused declarations and expression statements that have no effects.
@@ -119,7 +118,7 @@ function tryRemoveDeclarator(path: NodePath<t.VariableDeclarator>): boolean {
119
118
  }
120
119
 
121
120
  const init = path.node.init
122
- if (init && !isSideEffectFree(init, path.scope, path.get('init') as NodePath)) {
121
+ if (init && !isSideEffectFree(init, path.scope)) {
123
122
  return false
124
123
  }
125
124
 
@@ -130,7 +129,7 @@ function tryRemoveDeclarator(path: NodePath<t.VariableDeclarator>): boolean {
130
129
  if (binding.references > 0) {
131
130
  return false
132
131
  }
133
- if (binding.constantViolations.length > 0) {
132
+ if (declaration.node.kind !== 'const' && binding.constantViolations.length > 0) {
134
133
  return false
135
134
  }
136
135
  if (path.node.init) {
@@ -148,10 +147,6 @@ function tryRemoveFunctionDecl(path: NodePath<t.FunctionDeclaration>): boolean {
148
147
  if (isExported(path)) {
149
148
  return false
150
149
  }
151
- // Babel misses the sloppy Annex B alias of a block-level function.
152
- if (hasAnnexBFunctionAlias(path)) {
153
- return false
154
- }
155
150
  const id = path.node.id
156
151
  if (!id) {
157
152
  return false
@@ -183,7 +178,7 @@ function tryRemoveClassDecl(path: NodePath<t.ClassDeclaration>): boolean {
183
178
  }
184
179
 
185
180
  // `extends`, computed keys, static fields, and static blocks run on evaluation.
186
- if (path.node.superClass && !isSideEffectFree(path.node.superClass, path.scope, path)) {
181
+ if (path.node.superClass && !isSideEffectFree(path.node.superClass, path.scope)) {
187
182
  return false
188
183
  }
189
184
  for (const member of path.node.body.body) {
@@ -198,7 +193,7 @@ function tryRemoveClassDecl(path: NodePath<t.ClassDeclaration>): boolean {
198
193
  'computed' in member &&
199
194
  member.computed &&
200
195
  'key' in member &&
201
- !isSideEffectFree(member.key, path.scope, path)
196
+ !isSideEffectFree(member.key, path.scope)
202
197
  ) {
203
198
  return false
204
199
  }
@@ -224,7 +219,7 @@ function tryRemoveClassDecl(path: NodePath<t.ClassDeclaration>): boolean {
224
219
  }
225
220
 
226
221
  function tryRemoveExpressionStatement(path: NodePath<t.ExpressionStatement>): boolean {
227
- if (!isSideEffectFree(path.node.expression, path.scope, path.get('expression'))) {
222
+ if (!isSideEffectFree(path.node.expression, path.scope)) {
228
223
  return false
229
224
  }
230
225
  dereferenceSubtree(path.get('expression') as NodePath)
@@ -4,15 +4,14 @@ import * as t from '@babel/types'
4
4
  import type { File } from '@babel/types'
5
5
 
6
6
  import type { TransformContext } from '../options'
7
- import { isInsideWith } from '../utils/ast'
8
- import { hasAnnexBFunctionAlias, referencesOrWritesVariable } from '../utils/paths'
7
+ import { referencesOrWritesVariable } from '../utils/paths'
9
8
  import { mulberry32 } from '../utils/random'
10
9
  import { StringGenerator } from '../utils/string-generator'
11
10
 
12
11
  /**
13
- * Renames bindings and labels with a fresh `StringGenerator`. Each scope avoids
14
- * live ancestor names but otherwise restarts the cursor, allowing safe reuse in
15
- * sibling scopes.
12
+ * Renames bindings and labels with the configured string generator. Generated
13
+ * names do not capture unresolved names or bindings still used from an outer
14
+ * scope. Separate sibling scopes may reuse the same names.
16
15
  *
17
16
  * @example
18
17
  * // ◀️ before
@@ -48,7 +47,6 @@ export function renameIdentifiers(ast: File, ctx: TransformContext): boolean {
48
47
  const liveAncestors = new Map<Scope, Set<Binding>>()
49
48
  // Reserve unbound names so generated bindings cannot capture them.
50
49
  const freeNames = new Set<string>()
51
- // A reference inside `with` pins its binding to the source name.
52
50
  const unrenamable = new Set<Binding>()
53
51
  // Reserve pinned source names globally, including before their binding is seen.
54
52
  const pinnedNames = new Set<string>()
@@ -69,9 +67,6 @@ export function renameIdentifiers(ast: File, ctx: TransformContext): boolean {
69
67
  }
70
68
  return
71
69
  }
72
- if (isInsideWith(path)) {
73
- pin(binding, path.node.name)
74
- }
75
70
  let scope: Scope | undefined = path.scope
76
71
  while (scope && scope !== binding.scope) {
77
72
  let live = liveAncestors.get(scope)
@@ -83,17 +78,6 @@ export function renameIdentifiers(ast: File, ctx: TransformContext): boolean {
83
78
  scope = scope.parent
84
79
  }
85
80
  },
86
- FunctionDeclaration(path) {
87
- // ECMA-262 B.3.3 gives sloppy block functions an alias Babel does not model.
88
- if (!hasAnnexBFunctionAlias(path)) {
89
- return
90
- }
91
- const name = path.node.id?.name
92
- const binding = name === undefined ? undefined : path.scope.getBinding(name)
93
- if (binding && name !== undefined) {
94
- pin(binding, name)
95
- }
96
- },
97
81
  CatchClause(path) {
98
82
  // Babel splits a catch parameter and same-named `var` that share one runtime
99
83
  // variable. They must keep the same name.
@@ -2,14 +2,12 @@ import type { NodePath } from '@babel/traverse'
2
2
  import type * as t from '@babel/types'
3
3
  import type { File } from '@babel/types'
4
4
 
5
- import { hasAnnexBFunctionAlias } from '../utils/paths'
6
5
  import { rewriteStatementLists } from '../utils/statement-lists'
7
6
  import type { StatementListContainer } from '../utils/statement-lists'
8
7
 
9
8
  /**
10
9
  * Moves hoisted function declarations to the end of each program, block, switch
11
- * case, or static block. It keeps declaration order, including duplicate names,
12
- * and leaves sloppy Annex B block functions in place.
10
+ * case, or static block. It keeps declaration order, including duplicate names.
13
11
  *
14
12
  * If moving every declaration would turn a string expression into a directive,
15
13
  * one declaration stays before the string. Direct exports stay in place.
@@ -76,5 +74,5 @@ function createsDirective(statements: readonly t.Statement[]): boolean {
76
74
  }
77
75
 
78
76
  function isMovable(child: NodePath<t.Statement>): boolean {
79
- return child.isFunctionDeclaration() && child.node.id !== null && !hasAnnexBFunctionAlias(child)
77
+ return child.isFunctionDeclaration() && child.node.id !== null
80
78
  }
@@ -4,7 +4,6 @@ import * as t from '@babel/types'
4
4
  import type { File } from '@babel/types'
5
5
 
6
6
  import type { TransformContext } from '../options'
7
- import { isInsideWith } from '../utils/ast'
8
7
  import type { ChangeState } from '../utils/change-tracking'
9
8
  import { mulberry32 } from '../utils/random'
10
9
  import { StringGenerator } from '../utils/string-generator'
@@ -69,9 +68,8 @@ const visitor: Visitor<State> = {
69
68
  if (path.scope.getBinding(name)) {
70
69
  return
71
70
  }
72
- // A replacement cannot occupy a write target. `with` may also shadow `NaN`,
73
- // `Infinity`, or `undefined`.
74
- if (isWriteTarget(path) || isInsideWith(path)) {
71
+ // A replacement cannot occupy a write target.
72
+ if (isWriteTarget(path)) {
75
73
  return
76
74
  }
77
75
  const str = t.stringLiteral(state.generator.next())
@@ -47,8 +47,7 @@ const visitor: Visitor<ChangeState> = {
47
47
 
48
48
  function transformSwitch(path: NodePath<t.SwitchStatement>): boolean {
49
49
  const node = path.node
50
- // Separate `if` blocks cannot preserve cross-case lexical scope or Annex B
51
- // function hoisting.
50
+ // Separate `if` blocks cannot preserve cross-case lexical scope.
52
51
  if (hasBlockScopedDeclaration(node)) {
53
52
  return false
54
53
  }
@@ -56,7 +55,7 @@ function transformSwitch(path: NodePath<t.SwitchStatement>): boolean {
56
55
  lowerToIfElseChain(path)
57
56
  return true
58
57
  }
59
- if (!labeledLoweringIsSafe(node, path.scope, path)) {
58
+ if (!labeledLoweringIsSafe(node, path.scope)) {
60
59
  return false
61
60
  }
62
61
  lowerToLabeledBlock(path)
@@ -69,8 +68,7 @@ function hasBlockScopedDeclaration(node: t.SwitchStatement): boolean {
69
68
  if (
70
69
  (t.isVariableDeclaration(stmt) && stmt.kind !== 'var') ||
71
70
  t.isClassDeclaration(stmt) ||
72
- t.isFunctionDeclaration(stmt) ||
73
- isLabeledFunctionDeclaration(stmt)
71
+ t.isFunctionDeclaration(stmt)
74
72
  ) {
75
73
  return true
76
74
  }
@@ -79,29 +77,14 @@ function hasBlockScopedDeclaration(node: t.SwitchStatement): boolean {
79
77
  return false
80
78
  }
81
79
 
82
- // Babel hides an Annex B.3.4 labeled function under LabeledStatement wrappers.
83
- function isLabeledFunctionDeclaration(stmt: t.Statement): boolean {
84
- let current: t.Statement = stmt
85
- while (t.isLabeledStatement(current)) {
86
- current = current.body
87
- }
88
- return t.isFunctionDeclaration(current)
89
- }
90
-
91
80
  // A non-final default needs match results from later cases. Precomputing that
92
81
  // result rereads tests, so every test must be pure in this shape.
93
- function labeledLoweringIsSafe(
94
- node: t.SwitchStatement,
95
- scope: Scope,
96
- referencePath: NodePath,
97
- ): boolean {
82
+ function labeledLoweringIsSafe(node: t.SwitchStatement, scope: Scope): boolean {
98
83
  const defaultIndex = node.cases.findIndex((caseClause) => caseClause.test === null)
99
84
  if (defaultIndex === -1 || defaultIndex === node.cases.length - 1) {
100
85
  return true
101
86
  }
102
- return node.cases.every(
103
- (caseClause) => caseClause.test == null || isPure(caseClause.test, scope, referencePath),
104
- )
87
+ return node.cases.every((caseClause) => caseClause.test == null || isPure(caseClause.test, scope))
105
88
  }
106
89
 
107
90
  function canLowerToIfElseChain(node: t.SwitchStatement): boolean {
@@ -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
  import { isConditionalGate, reorderableToStatement } from '../utils/evaluation-order'
@@ -35,7 +34,7 @@ export function ternaryToIf(ast: File): boolean {
35
34
 
36
35
  const preparationVisitor: Visitor<ChangeState> = {
37
36
  ConditionalExpression(path, state) {
38
- if (!isPathLive(path) || isInsideWith(path)) {
37
+ if (!isPathLive(path)) {
39
38
  return
40
39
  }
41
40
  const stmtPath = path.getStatementParent()
@@ -50,9 +49,8 @@ const preparationVisitor: Visitor<ChangeState> = {
50
49
 
51
50
  const visitor: Visitor<ChangeState> = {
52
51
  ConditionalExpression(path, state) {
53
- // Statement replacement detaches queued siblings. `with` can also shadow the
54
- // synthesized result temporary.
55
- if (!isPathLive(path) || isInsideWith(path)) {
52
+ // Statement replacement detaches queued siblings.
53
+ if (!isPathLive(path)) {
56
54
  return
57
55
  }
58
56
  if (lowerConditional(path)) {
@@ -129,16 +127,14 @@ function lowerConditional(path: NodePath<t.ConditionalExpression>): boolean {
129
127
  t.isIdentifier(parent.node.id) &&
130
128
  parent.node.init === path.node &&
131
129
  parent.parentPath?.isVariableDeclaration() &&
132
- // Splitting lexical declarations would end TDZ early, and bare `const` is
133
- // invalid. Keep their initializer behind a result temp.
134
- parent.parentPath.node.kind === 'var' &&
135
130
  parent.parentPath.node.declarations.length === 1 &&
136
131
  isStandaloneDeclaration(parent.parentPath)
137
132
  ) {
138
133
  const declPath = parent.parentPath
139
134
  const target = parent.node.id
135
+ const declarationKind = declPath.node.kind === 'const' ? 'let' : declPath.node.kind
140
136
  declPath.replaceWithMultiple([
141
- t.variableDeclaration(declPath.node.kind, [t.variableDeclarator(t.cloneNode(target))]),
137
+ t.variableDeclaration(declarationKind, [t.variableDeclarator(t.cloneNode(target))]),
142
138
  t.ifStatement(
143
139
  test,
144
140
  t.blockStatement(seqWithAssignment(consequent, target)),
@@ -2,7 +2,6 @@ import type { Binding, 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
 
@@ -44,10 +43,6 @@ const visitor: Visitor<ChangeState> = {
44
43
  if (!prefix && !valueIsDiscarded(path)) {
45
44
  return
46
45
  }
47
- // `with` can retarget the name and defeat the numeric proof.
48
- if (isInsideWith(path)) {
49
- return
50
- }
51
46
  const binding = path.scope.getBinding(argument.name)
52
47
  if (!isNumericBinding(binding)) {
53
48
  return
@@ -75,19 +70,22 @@ function valueIsDiscarded(path: NodePath<t.UpdateExpression>): boolean {
75
70
  return false
76
71
  }
77
72
 
78
- // The initializer and every mutation must keep the binding numeric.
73
+ // Mutable bindings must stay numeric. A const write cannot change its value.
79
74
  function isNumericBinding(binding: Binding | undefined): boolean {
80
- if (!binding || (binding.kind !== 'var' && binding.kind !== 'let')) {
75
+ if (!binding || (binding.kind !== 'var' && binding.kind !== 'let' && binding.kind !== 'const')) {
81
76
  return false
82
77
  }
83
78
  const declarator = binding.path.node
84
79
  if (!t.isVariableDeclarator(declarator) || !t.isNumericLiteral(declarator.init)) {
85
80
  return false
86
81
  }
87
- // Babel misses the string writes from an Annex B `for-in` initializer.
82
+ // Babel does not record each value assigned by a `for-in` or `for-of` head.
88
83
  if (isForInOfLoopVariable(binding)) {
89
84
  return false
90
85
  }
86
+ if (binding.kind === 'const') {
87
+ return true
88
+ }
91
89
  return binding.constantViolations.every((violation) => keepsNumeric(violation.node))
92
90
  }
93
91