webfuscator 1.2.1 → 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 (209) hide show
  1. package/README.md +3 -3
  2. package/dist/analysis/constant.d.ts.map +1 -1
  3. package/dist/analysis/constant.js +10 -27
  4. package/dist/analysis/constant.js.map +1 -1
  5. package/dist/analysis/inlineability.d.ts.map +1 -1
  6. package/dist/analysis/inlineability.js +5 -13
  7. package/dist/analysis/inlineability.js.map +1 -1
  8. package/dist/analysis/purity.d.ts +3 -3
  9. package/dist/analysis/purity.d.ts.map +1 -1
  10. package/dist/analysis/purity.js +28 -75
  11. package/dist/analysis/purity.js.map +1 -1
  12. package/dist/index.d.ts +1 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/obfuscator.d.ts +2 -1
  15. package/dist/obfuscator.d.ts.map +1 -1
  16. package/dist/obfuscator.js +47 -12
  17. package/dist/obfuscator.js.map +1 -1
  18. package/dist/options.d.ts +17 -4
  19. package/dist/options.d.ts.map +1 -1
  20. package/dist/preparation/expand-destructuring.d.ts +2 -3
  21. package/dist/preparation/expand-destructuring.d.ts.map +1 -1
  22. package/dist/preparation/expand-destructuring.js +2 -8
  23. package/dist/preparation/expand-destructuring.js.map +1 -1
  24. package/dist/transforms/arguments-to-parameters.d.ts +5 -4
  25. package/dist/transforms/arguments-to-parameters.d.ts.map +1 -1
  26. package/dist/transforms/arguments-to-parameters.js +5 -4
  27. package/dist/transforms/arguments-to-parameters.js.map +1 -1
  28. package/dist/transforms/arrow-to-function.d.ts +4 -3
  29. package/dist/transforms/arrow-to-function.d.ts.map +1 -1
  30. package/dist/transforms/arrow-to-function.js +6 -10
  31. package/dist/transforms/arrow-to-function.js.map +1 -1
  32. package/dist/transforms/collapse-single-use-temps.d.ts +2 -1
  33. package/dist/transforms/collapse-single-use-temps.d.ts.map +1 -1
  34. package/dist/transforms/collapse-single-use-temps.js +12 -20
  35. package/dist/transforms/collapse-single-use-temps.js.map +1 -1
  36. package/dist/transforms/conceal-strings.d.ts +56 -0
  37. package/dist/transforms/conceal-strings.d.ts.map +1 -0
  38. package/dist/transforms/conceal-strings.js +356 -0
  39. package/dist/transforms/conceal-strings.js.map +1 -0
  40. package/dist/transforms/const-let-to-var.d.ts +4 -3
  41. package/dist/transforms/const-let-to-var.d.ts.map +1 -1
  42. package/dist/transforms/const-let-to-var.js +8 -67
  43. package/dist/transforms/const-let-to-var.js.map +1 -1
  44. package/dist/transforms/const-to-let.d.ts +1 -3
  45. package/dist/transforms/const-to-let.d.ts.map +1 -1
  46. package/dist/transforms/const-to-let.js +1 -8
  47. package/dist/transforms/const-to-let.js.map +1 -1
  48. package/dist/transforms/drop-console.d.ts.map +1 -1
  49. package/dist/transforms/drop-console.js +3 -5
  50. package/dist/transforms/drop-console.js.map +1 -1
  51. package/dist/transforms/expand-binary-assignment.d.ts.map +1 -1
  52. package/dist/transforms/expand-binary-assignment.js +0 -5
  53. package/dist/transforms/expand-binary-assignment.js.map +1 -1
  54. package/dist/transforms/expand-logical-assignment.d.ts.map +1 -1
  55. package/dist/transforms/expand-logical-assignment.js +0 -5
  56. package/dist/transforms/expand-logical-assignment.js.map +1 -1
  57. package/dist/transforms/extract-object-properties.js +5 -12
  58. package/dist/transforms/extract-object-properties.js.map +1 -1
  59. package/dist/transforms/fold-builtin-methods.d.ts.map +1 -1
  60. package/dist/transforms/fold-builtin-methods.js +44 -14
  61. package/dist/transforms/fold-builtin-methods.js.map +1 -1
  62. package/dist/transforms/fold-constants.js +5 -2
  63. package/dist/transforms/fold-constants.js.map +1 -1
  64. package/dist/transforms/function-declaration-to-expression.d.ts +5 -6
  65. package/dist/transforms/function-declaration-to-expression.d.ts.map +1 -1
  66. package/dist/transforms/function-declaration-to-expression.js +5 -6
  67. package/dist/transforms/function-declaration-to-expression.js.map +1 -1
  68. package/dist/transforms/hoist-variables.d.ts +29 -0
  69. package/dist/transforms/hoist-variables.d.ts.map +1 -0
  70. package/dist/transforms/hoist-variables.js +215 -0
  71. package/dist/transforms/hoist-variables.js.map +1 -0
  72. package/dist/transforms/inline-functions.js +11 -40
  73. package/dist/transforms/inline-functions.js.map +1 -1
  74. package/dist/transforms/logical-to-ternary.d.ts +1 -1
  75. package/dist/transforms/logical-to-ternary.js +5 -9
  76. package/dist/transforms/logical-to-ternary.js.map +1 -1
  77. package/dist/transforms/mangle-properties.d.ts +2 -5
  78. package/dist/transforms/mangle-properties.d.ts.map +1 -1
  79. package/dist/transforms/mangle-properties.js +9 -9
  80. package/dist/transforms/mangle-properties.js.map +1 -1
  81. package/dist/transforms/merge-variable-declarations.d.ts +17 -0
  82. package/dist/transforms/merge-variable-declarations.d.ts.map +1 -0
  83. package/dist/transforms/merge-variable-declarations.js +68 -0
  84. package/dist/transforms/merge-variable-declarations.js.map +1 -0
  85. package/dist/transforms/nullish-coalescing-to-ternary.d.ts +4 -5
  86. package/dist/transforms/nullish-coalescing-to-ternary.d.ts.map +1 -1
  87. package/dist/transforms/nullish-coalescing-to-ternary.js +7 -12
  88. package/dist/transforms/nullish-coalescing-to-ternary.js.map +1 -1
  89. package/dist/transforms/numbers-to-strings.d.ts.map +1 -1
  90. package/dist/transforms/numbers-to-strings.js +1 -8
  91. package/dist/transforms/numbers-to-strings.js.map +1 -1
  92. package/dist/transforms/object-method-to-property.d.ts +1 -1
  93. package/dist/transforms/object-method-to-property.js +1 -1
  94. package/dist/transforms/optional-chaining-to-ternary.d.ts +4 -3
  95. package/dist/transforms/optional-chaining-to-ternary.d.ts.map +1 -1
  96. package/dist/transforms/optional-chaining-to-ternary.js +9 -9
  97. package/dist/transforms/optional-chaining-to-ternary.js.map +1 -1
  98. package/dist/transforms/pack-declarations-into-parameters.d.ts.map +1 -1
  99. package/dist/transforms/pack-declarations-into-parameters.js +5 -21
  100. package/dist/transforms/pack-declarations-into-parameters.js.map +1 -1
  101. package/dist/transforms/pack.d.ts +4 -13
  102. package/dist/transforms/pack.d.ts.map +1 -1
  103. package/dist/transforms/pack.js +9 -29
  104. package/dist/transforms/pack.js.map +1 -1
  105. package/dist/transforms/remove-anonymous-function-names.d.ts +2 -4
  106. package/dist/transforms/remove-anonymous-function-names.d.ts.map +1 -1
  107. package/dist/transforms/remove-anonymous-function-names.js +2 -40
  108. package/dist/transforms/remove-anonymous-function-names.js.map +1 -1
  109. package/dist/transforms/remove-unreachable-code.js +2 -21
  110. package/dist/transforms/remove-unreachable-code.js.map +1 -1
  111. package/dist/transforms/remove-unused-code.d.ts.map +1 -1
  112. package/dist/transforms/remove-unused-code.js +12 -49
  113. package/dist/transforms/remove-unused-code.js.map +1 -1
  114. package/dist/transforms/rename-identifiers.d.ts +3 -3
  115. package/dist/transforms/rename-identifiers.d.ts.map +1 -1
  116. package/dist/transforms/rename-identifiers.js +31 -35
  117. package/dist/transforms/rename-identifiers.js.map +1 -1
  118. package/dist/transforms/reorder-function-declarations.d.ts +24 -0
  119. package/dist/transforms/reorder-function-declarations.d.ts.map +1 -0
  120. package/dist/transforms/reorder-function-declarations.js +62 -0
  121. package/dist/transforms/reorder-function-declarations.js.map +1 -0
  122. package/dist/transforms/specials-to-strings.d.ts.map +1 -1
  123. package/dist/transforms/specials-to-strings.js +2 -3
  124. package/dist/transforms/specials-to-strings.js.map +1 -1
  125. package/dist/transforms/switch-to-if.js +5 -15
  126. package/dist/transforms/switch-to-if.js.map +1 -1
  127. package/dist/transforms/ternary-to-if.d.ts.map +1 -1
  128. package/dist/transforms/ternary-to-if.js +5 -9
  129. package/dist/transforms/ternary-to-if.js.map +1 -1
  130. package/dist/transforms/update-to-assignment.d.ts.map +1 -1
  131. package/dist/transforms/update-to-assignment.js +6 -23
  132. package/dist/transforms/update-to-assignment.js.map +1 -1
  133. package/dist/transforms/yodify-conditions.d.ts +3 -4
  134. package/dist/transforms/yodify-conditions.d.ts.map +1 -1
  135. package/dist/transforms/yodify-conditions.js +3 -4
  136. package/dist/transforms/yodify-conditions.js.map +1 -1
  137. package/dist/utils/ast.d.ts +1 -3
  138. package/dist/utils/ast.d.ts.map +1 -1
  139. package/dist/utils/ast.js +4 -60
  140. package/dist/utils/ast.js.map +1 -1
  141. package/dist/utils/evaluation-order.js +1 -1
  142. package/dist/utils/evaluation-order.js.map +1 -1
  143. package/dist/utils/literal.d.ts.map +1 -1
  144. package/dist/utils/literal.js +74 -2
  145. package/dist/utils/literal.js.map +1 -1
  146. package/dist/utils/logical-lowering.d.ts.map +1 -1
  147. package/dist/utils/logical-lowering.js +7 -9
  148. package/dist/utils/logical-lowering.js.map +1 -1
  149. package/dist/utils/nullish.d.ts +4 -0
  150. package/dist/utils/nullish.d.ts.map +1 -0
  151. package/dist/utils/nullish.js +8 -0
  152. package/dist/utils/nullish.js.map +1 -0
  153. package/dist/utils/paths.d.ts +0 -2
  154. package/dist/utils/paths.d.ts.map +1 -1
  155. package/dist/utils/paths.js +0 -28
  156. package/dist/utils/paths.js.map +1 -1
  157. package/dist/utils/statement-lists.d.ts +9 -0
  158. package/dist/utils/statement-lists.d.ts.map +1 -0
  159. package/dist/utils/statement-lists.js +27 -0
  160. package/dist/utils/statement-lists.js.map +1 -0
  161. package/package.json +6 -2
  162. package/src/analysis/constant.ts +12 -33
  163. package/src/analysis/inlineability.ts +5 -13
  164. package/src/analysis/purity.ts +29 -92
  165. package/src/index.ts +1 -0
  166. package/src/obfuscator.ts +51 -14
  167. package/src/options.ts +21 -3
  168. package/src/preparation/expand-destructuring.ts +2 -10
  169. package/src/transforms/arguments-to-parameters.ts +5 -4
  170. package/src/transforms/arrow-to-function.ts +6 -10
  171. package/src/transforms/collapse-single-use-temps.ts +12 -23
  172. package/src/transforms/conceal-strings.ts +525 -0
  173. package/src/transforms/const-let-to-var.ts +9 -72
  174. package/src/transforms/const-to-let.ts +1 -8
  175. package/src/transforms/drop-console.ts +3 -5
  176. package/src/transforms/expand-binary-assignment.ts +0 -5
  177. package/src/transforms/expand-logical-assignment.ts +0 -5
  178. package/src/transforms/extract-object-properties.ts +5 -14
  179. package/src/transforms/fold-builtin-methods.ts +63 -17
  180. package/src/transforms/fold-constants.ts +5 -2
  181. package/src/transforms/function-declaration-to-expression.ts +5 -6
  182. package/src/transforms/hoist-variables.ts +262 -0
  183. package/src/transforms/inline-functions.ts +16 -47
  184. package/src/transforms/logical-to-ternary.ts +5 -10
  185. package/src/transforms/mangle-properties.ts +9 -12
  186. package/src/transforms/merge-variable-declarations.ts +91 -0
  187. package/src/transforms/nullish-coalescing-to-ternary.ts +7 -13
  188. package/src/transforms/numbers-to-strings.ts +2 -12
  189. package/src/transforms/object-method-to-property.ts +1 -1
  190. package/src/transforms/optional-chaining-to-ternary.ts +9 -9
  191. package/src/transforms/pack-declarations-into-parameters.ts +11 -21
  192. package/src/transforms/pack.ts +9 -30
  193. package/src/transforms/remove-anonymous-function-names.ts +3 -46
  194. package/src/transforms/remove-unreachable-code.ts +2 -22
  195. package/src/transforms/remove-unused-code.ts +14 -74
  196. package/src/transforms/rename-identifiers.ts +44 -35
  197. package/src/transforms/reorder-function-declarations.ts +78 -0
  198. package/src/transforms/specials-to-strings.ts +2 -3
  199. package/src/transforms/switch-to-if.ts +5 -22
  200. package/src/transforms/ternary-to-if.ts +5 -9
  201. package/src/transforms/update-to-assignment.ts +6 -24
  202. package/src/transforms/yodify-conditions.ts +3 -4
  203. package/src/utils/ast.ts +4 -69
  204. package/src/utils/evaluation-order.ts +1 -1
  205. package/src/utils/literal.ts +91 -2
  206. package/src/utils/logical-lowering.ts +7 -11
  207. package/src/utils/nullish.ts +23 -0
  208. package/src/utils/paths.ts +0 -32
  209. package/src/utils/statement-lists.ts +45 -0
@@ -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 { enclosingScopeHasDirectEval, 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,14 +135,6 @@ function isSafeAtCallSite(
135
135
  callPath: NodePath<t.CallExpression>,
136
136
  candidates: Map<string, InlineCandidate>,
137
137
  ): boolean {
138
- // `with`, direct eval, and Annex B aliases can redirect free names outside
139
- // Babel's scope table.
140
- if (isInsideWith(callPath) || enclosingScopeHasDirectEval(callPath)) {
141
- return false
142
- }
143
- if (annexBHoistShadowsFreeVar(callPath, freeVars)) {
144
- return false
145
- }
146
138
  // Spliced code inherits call-site strictness, so it must match the callee.
147
139
  if (isInStrictContext(candidate.fnPath) !== isInStrictContext(callPath)) {
148
140
  return false
@@ -193,32 +185,6 @@ function isSafeAtCallSite(
193
185
  return true
194
186
  }
195
187
 
196
- // ECMA-262 B.3.2.1 creates a sloppy block-function alias that Babel misses in
197
- // the enclosing scope.
198
- function annexBHoistShadowsFreeVar(
199
- callPath: NodePath<t.CallExpression>,
200
- freeVars: Set<string>,
201
- ): boolean {
202
- if (freeVars.size === 0 || isInStrictContext(callPath)) {
203
- return false
204
- }
205
- const owner = callPath.getFunctionParent() ?? callPath.scope.getProgramParent().path
206
- let found = false
207
- owner.traverse({
208
- Function(path) {
209
- path.skip()
210
- },
211
- FunctionDeclaration(path) {
212
- const id = path.node.id
213
- if (id && freeVars.has(id.name) && hasAnnexBFunctionAlias(path)) {
214
- found = true
215
- path.stop()
216
- }
217
- },
218
- })
219
- return found
220
- }
221
-
222
188
  // Only loops in the same function repeat the spliced body.
223
189
  function callIsInsideLoop(callPath: NodePath): boolean {
224
190
  for (let current: NodePath | null = callPath.parentPath; current; current = current.parentPath) {
@@ -581,7 +547,9 @@ function inlineCallSite(
581
547
  const replacement: t.Statement[] = []
582
548
  if (directTarget.kind === 'declarator') {
583
549
  replacement.push(
584
- t.variableDeclaration('var', [t.variableDeclarator(t.identifier(directTarget.name))]),
550
+ t.variableDeclaration(directTarget.declarationKind, [
551
+ t.variableDeclarator(t.identifier(directTarget.name)),
552
+ ]),
585
553
  )
586
554
  }
587
555
  if (needsLabel) {
@@ -621,7 +589,9 @@ function inlineCallSite(
621
589
  return true
622
590
  }
623
591
 
624
- 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 }
625
595
 
626
596
  function identifyDirectAssignmentTarget(
627
597
  callPath: NodePath<t.CallExpression>,
@@ -642,9 +612,8 @@ function identifyDirectAssignmentTarget(
642
612
  if (!declPath || !declPath.isVariableDeclaration() || declPath.node.declarations.length !== 1) {
643
613
  return null
644
614
  }
645
- // Only `var` can become an uninitialized direct target without changing TDZ
646
- // or block scope.
647
- if (declPath.node.kind !== 'var') {
615
+ const declarationKind = declPath.node.kind
616
+ if (declarationKind !== 'var' && declarationKind !== 'let') {
648
617
  return null
649
618
  }
650
619
  const owner = declPath.parentPath
@@ -667,7 +636,7 @@ function identifyDirectAssignmentTarget(
667
636
  if (argsContainIdentName(args, name)) {
668
637
  return null
669
638
  }
670
- return { kind: 'declarator', name }
639
+ return { kind: 'declarator', name, declarationKind }
671
640
  }
672
641
 
673
642
  if (
@@ -714,10 +683,10 @@ function argEligibleForElision(argPath: NodePath, scope: Scope): boolean {
714
683
  return false
715
684
  }
716
685
  const binding = scope.getBinding(argPath.node.name)
717
- if (!binding || binding.constantViolations.length > 0) {
686
+ if (!binding || (binding.kind !== 'const' && binding.constantViolations.length > 0)) {
718
687
  return false
719
688
  }
720
- return isPure(argPath.node, scope, argPath)
689
+ return isPure(argPath.node, scope)
721
690
  }
722
691
 
723
692
  // Catch parameters stay scoped to their copied handlers. Counting them here
@@ -931,8 +900,8 @@ function isNonReferenceSlot(parent: t.Node, key: string): boolean {
931
900
  interface StatementWalkContext {
932
901
  insideNestedLoop: boolean
933
902
  insideNestedSwitch: boolean
934
- // Tail position propagates through final blocks, branches, and labels. Try,
935
- // 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.
936
905
  tailPosition: boolean
937
906
  }
938
907
 
@@ -1052,7 +1021,7 @@ function walkIntoCompound(
1052
1021
  }
1053
1022
 
1054
1023
  if (t.isWithStatement(node)) {
1055
- node.body = walkSingleChild(node.body, hooks, { ...ctx, tailPosition: false })
1024
+ node.body = walkSingleChild(node.body, hooks, ctx)
1056
1025
  }
1057
1026
  }
1058
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
@@ -507,7 +504,7 @@ function rewriteKeyedProperty(path: NodePath<KeyedProperty>, state: PropertyMang
507
504
  if (mangled === name) {
508
505
  return
509
506
  }
510
- node.key = t.inheritsComments(t.stringLiteral(mangled), node.key)
507
+ node.key = t.stringLiteral(mangled)
511
508
  node.computed = false
512
509
  if (t.isObjectProperty(node)) {
513
510
  node.shorthand = false
@@ -532,7 +529,7 @@ function rewriteMember(path: NodePath<Member>, state: PropertyManglerState): voi
532
529
  }
533
530
  // Babel narrows members by `computed`, so both fields need widening.
534
531
  const computed = node as Member & { computed: boolean; property: t.Expression }
535
- computed.property = t.inheritsComments(t.stringLiteral(mangled), node.property)
532
+ computed.property = t.stringLiteral(mangled)
536
533
  computed.computed = true
537
534
  state.changed = true
538
535
  }
@@ -586,7 +583,7 @@ function rewriteTerminalNames(
586
583
  node.value = mangled
587
584
  return node
588
585
  }
589
- return t.inheritsComments(t.stringLiteral(mangled), node)
586
+ return t.stringLiteral(mangled)
590
587
  }
591
588
  if (t.isSequenceExpression(node)) {
592
589
  const last = node.expressions.length - 1
@@ -684,10 +681,10 @@ function isTransparentAnnotationParent(path: NodePath, child: t.Node): boolean {
684
681
 
685
682
  function hasAnnotation(node: t.Node, annotation: string): boolean {
686
683
  return (
687
- node.leadingComments?.some(
688
- (comment) =>
689
- comment.value.includes(`@${annotation}`) || comment.value.includes(`#${annotation}`),
690
- ) ?? false
684
+ node.leadingComments?.some((comment) => {
685
+ const value = comment.value.trim()
686
+ return value === `@${annotation}` || value === `#${annotation}`
687
+ }) ?? false
691
688
  )
692
689
  }
693
690
 
@@ -0,0 +1,91 @@
1
+ import * as t from '@babel/types'
2
+ import type { File } from '@babel/types'
3
+
4
+ import { rewriteStatementLists } from '../utils/statement-lists'
5
+
6
+ /**
7
+ * Merges adjacent variable declarations when their kinds and export wrappers
8
+ * match. It runs in programs, blocks, switch cases, and static blocks, and
9
+ * removes unlabeled empty statements between compatible declarations.
10
+ *
11
+ * @example
12
+ * // ◀️ before
13
+ * let a = 1;
14
+ * let b = 2;
15
+ *
16
+ * // ▶️ after
17
+ * let a = 1,
18
+ * b = 2;
19
+ */
20
+ export function mergeVariableDeclarations(ast: File): boolean {
21
+ return rewriteStatementLists(ast, (statements) =>
22
+ mergeAdjacent(statements.map((path) => path.node)),
23
+ )
24
+ }
25
+
26
+ type StatementLike = t.Program['body'][number]
27
+
28
+ interface MergeableDeclaration {
29
+ declaration: t.VariableDeclaration
30
+ exported: boolean
31
+ }
32
+
33
+ function mergeAdjacent(list: readonly StatementLike[]): StatementLike[] | null {
34
+ const out: StatementLike[] = []
35
+ let pendingEmpty: t.EmptyStatement[] = []
36
+ let merged = false
37
+
38
+ for (const statement of list) {
39
+ if (t.isEmptyStatement(statement) && mergeableDeclaration(out.at(-1)) !== null) {
40
+ pendingEmpty.push(statement)
41
+ continue
42
+ }
43
+
44
+ const previous = mergeableDeclaration(out.at(-1))
45
+ const current = mergeableDeclaration(statement)
46
+ if (previous && current && canMerge(previous, current)) {
47
+ out[out.length - 1] = mergePair(previous, current)
48
+ pendingEmpty = []
49
+ merged = true
50
+ continue
51
+ }
52
+
53
+ out.push(...pendingEmpty, statement)
54
+ pendingEmpty = []
55
+ }
56
+
57
+ out.push(...pendingEmpty)
58
+ return merged ? out : null
59
+ }
60
+
61
+ function mergeableDeclaration(statement: StatementLike | undefined): MergeableDeclaration | null {
62
+ if (statement === undefined) {
63
+ return null
64
+ }
65
+ if (t.isVariableDeclaration(statement)) {
66
+ return { declaration: statement, exported: false }
67
+ }
68
+ if (t.isExportNamedDeclaration(statement) && t.isVariableDeclaration(statement.declaration)) {
69
+ return { declaration: statement.declaration, exported: true }
70
+ }
71
+ return null
72
+ }
73
+
74
+ function canMerge(previous: MergeableDeclaration, current: MergeableDeclaration): boolean {
75
+ return (
76
+ previous.exported === current.exported && previous.declaration.kind === current.declaration.kind
77
+ )
78
+ }
79
+
80
+ function mergePair(previous: MergeableDeclaration, current: MergeableDeclaration): StatementLike {
81
+ const declaration = t.variableDeclaration(previous.declaration.kind, [
82
+ ...previous.declaration.declarations,
83
+ ...current.declaration.declarations,
84
+ ])
85
+
86
+ if (!previous.exported) {
87
+ return declaration
88
+ }
89
+
90
+ return t.exportNamedDeclaration(declaration)
91
+ }
@@ -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
  }
@@ -1,7 +1,8 @@
1
- import type { NodePath, Visitor } from '@babel/traverse'
1
+ import type { Visitor } from '@babel/traverse'
2
2
  import * as t from '@babel/types'
3
3
  import type { File } from '@babel/types'
4
4
 
5
+ import { isNonComputedKey } from '../utils/ast'
5
6
  import { traverseForChanges } from '../utils/change-tracking'
6
7
  import type { ChangeState } from '../utils/change-tracking'
7
8
 
@@ -48,14 +49,3 @@ const visitor: Visitor<ChangeState> = {
48
49
  state.changed = true
49
50
  },
50
51
  }
51
-
52
- function isNonComputedKey(path: NodePath<t.NumericLiteral>): boolean {
53
- const parentPath = path.parentPath
54
- if (!parentPath) {
55
- return false
56
- }
57
- const parent = parentPath.node
58
- return (
59
- 'key' in parent && parent.key === path.node && 'computed' in parent && parent.computed === false
60
- )
61
- }
@@ -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) {
@@ -2,11 +2,10 @@ 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 { enclosingScopeHasDirectEval, hasDuplicateParamNames } from '../utils/ast'
5
+ 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
@@ -75,10 +70,6 @@ function packLeadingIntoParams(path: NodePath<t.Function>): boolean {
75
70
  if (referencesArguments(path)) {
76
71
  return false
77
72
  }
78
- // Direct eval can inspect the newly unmapped `arguments` by name.
79
- if (enclosingScopeHasDirectEval(path)) {
80
- return false
81
- }
82
73
  // A caller with extra arguments could fill the new parameter slot.
83
74
  if (!allCallSitesRespectArity(path, params.length)) {
84
75
  return false
@@ -163,7 +154,11 @@ function methodComputedKey(path: NodePath<t.Function>): NodePath | null {
163
154
  // arity and without spread.
164
155
  function allCallSitesRespectArity(path: NodePath<t.Function>, arity: number): boolean {
165
156
  const external = functionBinding(path)
166
- if (!external || !external.constant || !bindingCallsRespectArity(external, arity)) {
157
+ if (
158
+ !external ||
159
+ (external.kind !== 'const' && !external.constant) ||
160
+ !bindingCallsRespectArity(external, arity)
161
+ ) {
167
162
  return false
168
163
  }
169
164
  if (path.isFunctionExpression() && path.node.id) {
@@ -193,17 +188,12 @@ function bindingCallsRespectArity(binding: Binding, arity: number): boolean {
193
188
  return true
194
189
  }
195
190
 
196
- // Annex B can hoist function names from nested blocks into this function scope.
197
191
  function functionDeclarationNames(body: t.BlockStatement): Set<string> {
198
- const names = new Set<string>()
199
- t.traverseFast(body, (node) => {
200
- if (t.isFunctionDeclaration(node) && node.id) {
201
- names.add(node.id.name)
202
- return t.traverseFast.skip
203
- }
204
- return node !== body && t.isFunction(node) ? t.traverseFast.skip : undefined
205
- })
206
- return names
192
+ return new Set(
193
+ body.body.flatMap((statement) =>
194
+ t.isFunctionDeclaration(statement) && statement.id ? [statement.id.name] : [],
195
+ ),
196
+ )
207
197
  }
208
198
 
209
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,19 +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. Import declarations return above the call. `eval`
118
- * is left un-routed so a direct eval still reads the body scope. Names inside a
119
- * `with` stay un-routed too, since they may resolve against the with object at
120
- * runtime. Export statements cannot live inside a Function body, so pack throws
121
- * on them. It packs everything else as written. Code that needs `import.meta`,
122
- * top-level await, dynamic `import()`, or a top-level `arguments` will not run
123
- * once moved into a Function body.
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.
124
114
  *
125
115
  * @example
126
116
  * // ◀️ before
@@ -230,15 +220,9 @@ function packProgram(programPath: NodePath<t.Program>, ctx: TransformContext): b
230
220
 
231
221
  const isRoutable = (path: NodePath<t.Identifier>): boolean => {
232
222
  const name = path.node.name
233
- // The wrapper parameter stays as written. `arguments` and `eval` are never
234
- // routed: `arguments` binds to its own function's object, and leaving `eval`
235
- // bare keeps a direct eval reading the body scope rather than the global one.
236
- if (name === objectName || name === 'arguments' || name === 'eval') {
237
- return false
238
- }
239
- // Inside `with`, a free name may resolve to a property of the with object at
240
- // runtime, so routing it to the top-level accessor would change behavior.
241
- if (isInsideWith(path)) {
223
+ // Keep the wrapper parameter and `arguments` bare. The latter must bind to
224
+ // the wrapper function's own arguments object.
225
+ if (name === objectName || name === 'arguments') {
242
226
  return false
243
227
  }
244
228
  if (!referencesOrWritesVariable(path)) {
@@ -298,18 +282,13 @@ function packProgram(programPath: NodePath<t.Program>, ctx: TransformContext): b
298
282
  }
299
283
  programPath.traverse(routingVisitor)
300
284
 
301
- // The script completion value is its trailing expression; returning it keeps
302
- // `eval` of the packed output producing the same value.
303
285
  const body = programNode.body
304
- const lastStatement = body.at(-1)
305
- if (lastStatement && t.isExpressionStatement(lastStatement)) {
306
- body[body.length - 1] = t.returnStatement(lastStatement.expression)
307
- }
308
-
309
286
  const innerDirectives = bodyStrict ? [t.directive(t.directiveLiteral('use strict'))] : []
310
287
  const innerCode = generate(t.program(body, innerDirectives, 'script'), {
311
288
  minified: true,
312
289
  comments: false,
290
+ // `comments: false` still allows @license and @preserve.
291
+ shouldPrintComment: () => false,
313
292
  }).code
314
293
 
315
294
  const objectExpression = buildAccessorObject({