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
@@ -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
 
@@ -7,10 +7,9 @@ import type { ChangeState } from '../utils/change-tracking'
7
7
  import { isLiteralShaped } from '../utils/literal'
8
8
 
9
9
  /**
10
- * Moves a safe literal-shaped right operand to the left of a comparison.
11
- * Relational operators are flipped after swapping.
12
- *
13
- * Unary operations over literals also count when their coercion cannot act.
10
+ * Moves a right operand made from literals and unary operators to the left of a
11
+ * comparison when swapping does not change coercion order. Relational operators
12
+ * are flipped after swapping.
14
13
  *
15
14
  * @example
16
15
  * // ◀️ before
package/src/utils/ast.ts CHANGED
@@ -69,32 +69,3 @@ export function isNonComputedKey(path: NodePath): boolean {
69
69
  'key' in parent && parent.key === path.node && 'computed' in parent && parent.computed === false
70
70
  )
71
71
  }
72
-
73
- // `with` can shadow names outside Babel's scope table.
74
- export function isInsideWith(path: NodePath): boolean {
75
- if (!programHasWith(path.scope.getProgramParent().block)) {
76
- return false
77
- }
78
- return path.findParent((ancestor) => ancestor.isWithStatement()) !== null
79
- }
80
-
81
- // Cache the program-wide scan because `isInsideWith` runs for many identifiers.
82
- // Transforms never add `with`, so a cached miss remains valid.
83
- const programWith = new WeakMap<t.Node, boolean>()
84
-
85
- function programHasWith(programNode: t.Node): boolean {
86
- const cached = programWith.get(programNode)
87
- if (cached !== undefined) {
88
- return cached
89
- }
90
- let found = false
91
- t.traverseFast(programNode, (node) => {
92
- if (!t.isWithStatement(node)) {
93
- return
94
- }
95
- found = true
96
- return t.traverseFast.stop
97
- })
98
- programWith.set(programNode, found)
99
- return found
100
- }
@@ -133,7 +133,7 @@ function precedingSiblings<T extends NodePath>(
133
133
  }
134
134
 
135
135
  function isPurePath(path: NodePath): boolean {
136
- return isPure(path.node, path.scope, path)
136
+ return isPure(path.node, path.scope)
137
137
  }
138
138
 
139
139
  function impure(paths: (NodePath | null | undefined)[]): boolean {
@@ -9,7 +9,7 @@ import { isStandaloneDeclaration } from './paths'
9
9
  // x = L;
10
10
  // if (<fallbackTest(x)>) x = R;
11
11
  //
12
- // `fallbackTest` selects `!x`, `x`, or `x == null`. The parent must accept a
12
+ // `fallbackTest` selects the short-circuit condition. The parent must accept a
13
13
  // multi-statement replacement.
14
14
  export function lowerLogicalWriteStatement(
15
15
  path: NodePath<t.LogicalExpression>,
@@ -29,21 +29,20 @@ export function lowerLogicalWriteStatement(
29
29
  const declStmt = parent.parentPath
30
30
  if (
31
31
  !declStmt?.isVariableDeclaration() ||
32
- // The split would emit illegal `const x;` and then write it.
33
- declStmt.node.kind === 'const' ||
34
32
  declStmt.node.declarations.length !== 1 ||
35
33
  !isStandaloneDeclaration(declStmt)
36
34
  ) {
37
35
  return false
38
36
  }
39
37
  const target = parent.node.id
40
- // The split writes `x` before evaluating the right side. Refuse any right
41
- // side that could observe the new value or the ended TDZ.
42
- if (rightCanObserveTarget(node.right, target.name)) {
38
+ // A `var` initializer can observe its previous value while evaluating the
39
+ // right side. Reads before lexical initialization are intentionally ignored.
40
+ if (declStmt.node.kind === 'var' && rightCanObserveTarget(node.right, target.name)) {
43
41
  return false
44
42
  }
43
+ const declarationKind = declStmt.node.kind === 'const' ? 'let' : declStmt.node.kind
45
44
  declStmt.replaceWithMultiple([
46
- t.variableDeclaration(declStmt.node.kind, [
45
+ t.variableDeclaration(declarationKind, [
47
46
  t.variableDeclarator(t.cloneNode(target), node.left),
48
47
  ]),
49
48
  guardedReassignment(target, fallbackTest, node.right),
@@ -68,10 +67,7 @@ export function lowerLogicalWriteStatement(
68
67
  return false
69
68
  }
70
69
  // Writing first would change `x = 0 || x` before the right side reads `x`.
71
- if (
72
- !isSideEffectFree(node.right, path.scope, path) ||
73
- referencesName(node.right, target.name)
74
- ) {
70
+ if (!isSideEffectFree(node.right, path.scope) || referencesName(node.right, target.name)) {
75
71
  return false
76
72
  }
77
73
  exprStmt.replaceWithMultiple([
@@ -0,0 +1,23 @@
1
+ import * as t from '@babel/types'
2
+
3
+ export function buildNullishCheck(
4
+ firstRead: t.Expression,
5
+ secondRead: t.Expression,
6
+ ): t.ConditionalExpression {
7
+ return t.conditionalExpression(
8
+ t.binaryExpression('===', firstRead, t.nullLiteral()),
9
+ t.booleanLiteral(true),
10
+ t.binaryExpression('===', secondRead, t.unaryExpression('void', t.numericLiteral(0))),
11
+ )
12
+ }
13
+
14
+ export function buildNonNullishCheck(
15
+ firstRead: t.Expression,
16
+ secondRead: t.Expression,
17
+ ): t.ConditionalExpression {
18
+ return t.conditionalExpression(
19
+ t.binaryExpression('!==', firstRead, t.nullLiteral()),
20
+ t.binaryExpression('!==', secondRead, t.unaryExpression('void', t.numericLiteral(0))),
21
+ t.booleanLiteral(false),
22
+ )
23
+ }
@@ -69,35 +69,3 @@ export function isInStrictContext(path: NodePath): boolean {
69
69
  function hasUseStrictDirective(directives: readonly t.Directive[]): boolean {
70
70
  return directives.some((directive) => directive.value.value === 'use strict')
71
71
  }
72
-
73
- // ECMA-262 B.3.3 gives sloppy block-level functions a `var` alias that Babel
74
- // omits from the enclosing scope. Renaming, moving, or removing one can expose it.
75
- export function hasAnnexBFunctionAlias(path: NodePath): boolean {
76
- if (!path.isFunctionDeclaration()) {
77
- return false
78
- }
79
- const parent = path.parentPath
80
- if (!parent || parent.isProgram()) {
81
- return false
82
- }
83
- if (
84
- parent.isBlockStatement() &&
85
- parent.key === 'body' &&
86
- parent.parentPath != null &&
87
- parent.parentPath.isFunction()
88
- ) {
89
- return false
90
- }
91
- return !isInStrictContext(parent)
92
- }
93
-
94
- // A later write to `const` must keep throwing after transformation.
95
- export function hasConstantViolation(path: NodePath<t.VariableDeclaration>): boolean {
96
- for (const name of Object.keys(path.getBindingIdentifiers())) {
97
- const binding = path.scope.getBinding(name)
98
- if (binding && binding.constantViolations.length > 0) {
99
- return true
100
- }
101
- }
102
- return false
103
- }