webfuscator 0.9.4

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 (289) hide show
  1. package/DISCLOSURE +9 -0
  2. package/LICENSE +21 -0
  3. package/README.md +83 -0
  4. package/dist/analysis/constant.d.ts +11 -0
  5. package/dist/analysis/constant.d.ts.map +1 -0
  6. package/dist/analysis/constant.js +261 -0
  7. package/dist/analysis/constant.js.map +1 -0
  8. package/dist/analysis/document-order.d.ts +5 -0
  9. package/dist/analysis/document-order.d.ts.map +1 -0
  10. package/dist/analysis/document-order.js +124 -0
  11. package/dist/analysis/document-order.js.map +1 -0
  12. package/dist/analysis/inlineability.d.ts +13 -0
  13. package/dist/analysis/inlineability.d.ts.map +1 -0
  14. package/dist/analysis/inlineability.js +270 -0
  15. package/dist/analysis/inlineability.js.map +1 -0
  16. package/dist/analysis/purity.d.ts +5 -0
  17. package/dist/analysis/purity.d.ts.map +1 -0
  18. package/dist/analysis/purity.js +281 -0
  19. package/dist/analysis/purity.js.map +1 -0
  20. package/dist/index.d.ts +3 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +2 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/obfuscator.d.ts +4 -0
  25. package/dist/obfuscator.d.ts.map +1 -0
  26. package/dist/obfuscator.js +222 -0
  27. package/dist/obfuscator.js.map +1 -0
  28. package/dist/options.d.ts +73 -0
  29. package/dist/options.d.ts.map +1 -0
  30. package/dist/options.js +2 -0
  31. package/dist/options.js.map +1 -0
  32. package/dist/preparation/dot-to-bracket.d.ts +16 -0
  33. package/dist/preparation/dot-to-bracket.d.ts.map +1 -0
  34. package/dist/preparation/dot-to-bracket.js +36 -0
  35. package/dist/preparation/dot-to-bracket.js.map +1 -0
  36. package/dist/preparation/expand-destructuring.d.ts +26 -0
  37. package/dist/preparation/expand-destructuring.d.ts.map +1 -0
  38. package/dist/preparation/expand-destructuring.js +154 -0
  39. package/dist/preparation/expand-destructuring.js.map +1 -0
  40. package/dist/preparation/expand-template-literals.d.ts +23 -0
  41. package/dist/preparation/expand-template-literals.d.ts.map +1 -0
  42. package/dist/preparation/expand-template-literals.js +78 -0
  43. package/dist/preparation/expand-template-literals.js.map +1 -0
  44. package/dist/preparation/rewrite-class-fields.d.ts +43 -0
  45. package/dist/preparation/rewrite-class-fields.d.ts.map +1 -0
  46. package/dist/preparation/rewrite-class-fields.js +270 -0
  47. package/dist/preparation/rewrite-class-fields.js.map +1 -0
  48. package/dist/preparation/split-sequence-expressions.d.ts +15 -0
  49. package/dist/preparation/split-sequence-expressions.d.ts.map +1 -0
  50. package/dist/preparation/split-sequence-expressions.js +36 -0
  51. package/dist/preparation/split-sequence-expressions.js.map +1 -0
  52. package/dist/preparation/split-variable-declarations.d.ts +20 -0
  53. package/dist/preparation/split-variable-declarations.d.ts.map +1 -0
  54. package/dist/preparation/split-variable-declarations.js +36 -0
  55. package/dist/preparation/split-variable-declarations.js.map +1 -0
  56. package/dist/preparation/stringify-object-keys.d.ts +21 -0
  57. package/dist/preparation/stringify-object-keys.d.ts.map +1 -0
  58. package/dist/preparation/stringify-object-keys.js +96 -0
  59. package/dist/preparation/stringify-object-keys.js.map +1 -0
  60. package/dist/preparation/wrap-single-statements.d.ts +24 -0
  61. package/dist/preparation/wrap-single-statements.d.ts.map +1 -0
  62. package/dist/preparation/wrap-single-statements.js +53 -0
  63. package/dist/preparation/wrap-single-statements.js.map +1 -0
  64. package/dist/transforms/arguments-to-parameters.d.ts +20 -0
  65. package/dist/transforms/arguments-to-parameters.d.ts.map +1 -0
  66. package/dist/transforms/arguments-to-parameters.js +162 -0
  67. package/dist/transforms/arguments-to-parameters.js.map +1 -0
  68. package/dist/transforms/arrow-to-function.d.ts +46 -0
  69. package/dist/transforms/arrow-to-function.d.ts.map +1 -0
  70. package/dist/transforms/arrow-to-function.js +203 -0
  71. package/dist/transforms/arrow-to-function.js.map +1 -0
  72. package/dist/transforms/collapse-single-use-temps.d.ts +16 -0
  73. package/dist/transforms/collapse-single-use-temps.d.ts.map +1 -0
  74. package/dist/transforms/collapse-single-use-temps.js +805 -0
  75. package/dist/transforms/collapse-single-use-temps.js.map +1 -0
  76. package/dist/transforms/const-let-to-var.d.ts +21 -0
  77. package/dist/transforms/const-let-to-var.d.ts.map +1 -0
  78. package/dist/transforms/const-let-to-var.js +217 -0
  79. package/dist/transforms/const-let-to-var.js.map +1 -0
  80. package/dist/transforms/const-to-let.d.ts +17 -0
  81. package/dist/transforms/const-to-let.d.ts.map +1 -0
  82. package/dist/transforms/const-to-let.js +33 -0
  83. package/dist/transforms/const-to-let.js.map +1 -0
  84. package/dist/transforms/do-while-to-while.d.ts +26 -0
  85. package/dist/transforms/do-while-to-while.d.ts.map +1 -0
  86. package/dist/transforms/do-while-to-while.js +54 -0
  87. package/dist/transforms/do-while-to-while.js.map +1 -0
  88. package/dist/transforms/drop-console.d.ts +27 -0
  89. package/dist/transforms/drop-console.d.ts.map +1 -0
  90. package/dist/transforms/drop-console.js +107 -0
  91. package/dist/transforms/drop-console.js.map +1 -0
  92. package/dist/transforms/drop-debugger.d.ts +18 -0
  93. package/dist/transforms/drop-debugger.d.ts.map +1 -0
  94. package/dist/transforms/drop-debugger.js +34 -0
  95. package/dist/transforms/drop-debugger.js.map +1 -0
  96. package/dist/transforms/expand-binary-assignment.d.ts +26 -0
  97. package/dist/transforms/expand-binary-assignment.d.ts.map +1 -0
  98. package/dist/transforms/expand-binary-assignment.js +78 -0
  99. package/dist/transforms/expand-binary-assignment.js.map +1 -0
  100. package/dist/transforms/expand-logical-assignment.d.ts +22 -0
  101. package/dist/transforms/expand-logical-assignment.d.ts.map +1 -0
  102. package/dist/transforms/expand-logical-assignment.js +73 -0
  103. package/dist/transforms/expand-logical-assignment.js.map +1 -0
  104. package/dist/transforms/extract-object-properties.d.ts +21 -0
  105. package/dist/transforms/extract-object-properties.d.ts.map +1 -0
  106. package/dist/transforms/extract-object-properties.js +340 -0
  107. package/dist/transforms/extract-object-properties.js.map +1 -0
  108. package/dist/transforms/fold-builtin-methods.d.ts +23 -0
  109. package/dist/transforms/fold-builtin-methods.d.ts.map +1 -0
  110. package/dist/transforms/fold-builtin-methods.js +839 -0
  111. package/dist/transforms/fold-builtin-methods.js.map +1 -0
  112. package/dist/transforms/fold-constants.d.ts +16 -0
  113. package/dist/transforms/fold-constants.d.ts.map +1 -0
  114. package/dist/transforms/fold-constants.js +421 -0
  115. package/dist/transforms/fold-constants.js.map +1 -0
  116. package/dist/transforms/for-to-while.d.ts +29 -0
  117. package/dist/transforms/for-to-while.d.ts.map +1 -0
  118. package/dist/transforms/for-to-while.js +91 -0
  119. package/dist/transforms/for-to-while.js.map +1 -0
  120. package/dist/transforms/function-declaration-to-expression.d.ts +28 -0
  121. package/dist/transforms/function-declaration-to-expression.d.ts.map +1 -0
  122. package/dist/transforms/function-declaration-to-expression.js +95 -0
  123. package/dist/transforms/function-declaration-to-expression.js.map +1 -0
  124. package/dist/transforms/inline-functions.d.ts +16 -0
  125. package/dist/transforms/inline-functions.d.ts.map +1 -0
  126. package/dist/transforms/inline-functions.js +896 -0
  127. package/dist/transforms/inline-functions.js.map +1 -0
  128. package/dist/transforms/logical-to-ternary.d.ts +20 -0
  129. package/dist/transforms/logical-to-ternary.d.ts.map +1 -0
  130. package/dist/transforms/logical-to-ternary.js +78 -0
  131. package/dist/transforms/logical-to-ternary.js.map +1 -0
  132. package/dist/transforms/mangle-properties.d.ts +27 -0
  133. package/dist/transforms/mangle-properties.d.ts.map +1 -0
  134. package/dist/transforms/mangle-properties.js +703 -0
  135. package/dist/transforms/mangle-properties.js.map +1 -0
  136. package/dist/transforms/nullish-coalescing-to-ternary.d.ts +18 -0
  137. package/dist/transforms/nullish-coalescing-to-ternary.d.ts.map +1 -0
  138. package/dist/transforms/nullish-coalescing-to-ternary.js +61 -0
  139. package/dist/transforms/nullish-coalescing-to-ternary.js.map +1 -0
  140. package/dist/transforms/numbers-to-strings.d.ts +19 -0
  141. package/dist/transforms/numbers-to-strings.d.ts.map +1 -0
  142. package/dist/transforms/numbers-to-strings.js +51 -0
  143. package/dist/transforms/numbers-to-strings.js.map +1 -0
  144. package/dist/transforms/object-method-to-property.d.ts +34 -0
  145. package/dist/transforms/object-method-to-property.d.ts.map +1 -0
  146. package/dist/transforms/object-method-to-property.js +78 -0
  147. package/dist/transforms/object-method-to-property.js.map +1 -0
  148. package/dist/transforms/optional-chaining-to-ternary.d.ts +19 -0
  149. package/dist/transforms/optional-chaining-to-ternary.d.ts.map +1 -0
  150. package/dist/transforms/optional-chaining-to-ternary.js +206 -0
  151. package/dist/transforms/optional-chaining-to-ternary.js.map +1 -0
  152. package/dist/transforms/pack-declarations-into-parameters.d.ts +22 -0
  153. package/dist/transforms/pack-declarations-into-parameters.d.ts.map +1 -0
  154. package/dist/transforms/pack-declarations-into-parameters.js +208 -0
  155. package/dist/transforms/pack-declarations-into-parameters.js.map +1 -0
  156. package/dist/transforms/remove-anonymous-function-names.d.ts +17 -0
  157. package/dist/transforms/remove-anonymous-function-names.d.ts.map +1 -0
  158. package/dist/transforms/remove-anonymous-function-names.js +72 -0
  159. package/dist/transforms/remove-anonymous-function-names.js.map +1 -0
  160. package/dist/transforms/remove-unreachable-code.d.ts +21 -0
  161. package/dist/transforms/remove-unreachable-code.d.ts.map +1 -0
  162. package/dist/transforms/remove-unreachable-code.js +103 -0
  163. package/dist/transforms/remove-unreachable-code.js.map +1 -0
  164. package/dist/transforms/remove-unused-code.d.ts +18 -0
  165. package/dist/transforms/remove-unused-code.d.ts.map +1 -0
  166. package/dist/transforms/remove-unused-code.js +249 -0
  167. package/dist/transforms/remove-unused-code.js.map +1 -0
  168. package/dist/transforms/rename-identifiers.d.ts +24 -0
  169. package/dist/transforms/rename-identifiers.d.ts.map +1 -0
  170. package/dist/transforms/rename-identifiers.js +264 -0
  171. package/dist/transforms/rename-identifiers.js.map +1 -0
  172. package/dist/transforms/specials-to-strings.d.ts +30 -0
  173. package/dist/transforms/specials-to-strings.d.ts.map +1 -0
  174. package/dist/transforms/specials-to-strings.js +101 -0
  175. package/dist/transforms/specials-to-strings.js.map +1 -0
  176. package/dist/transforms/switch-to-if.d.ts +28 -0
  177. package/dist/transforms/switch-to-if.d.ts.map +1 -0
  178. package/dist/transforms/switch-to-if.js +279 -0
  179. package/dist/transforms/switch-to-if.js.map +1 -0
  180. package/dist/transforms/ternary-to-if.d.ts +20 -0
  181. package/dist/transforms/ternary-to-if.d.ts.map +1 -0
  182. package/dist/transforms/ternary-to-if.js +315 -0
  183. package/dist/transforms/ternary-to-if.js.map +1 -0
  184. package/dist/transforms/update-to-assignment.d.ts +26 -0
  185. package/dist/transforms/update-to-assignment.d.ts.map +1 -0
  186. package/dist/transforms/update-to-assignment.js +120 -0
  187. package/dist/transforms/update-to-assignment.js.map +1 -0
  188. package/dist/transforms/yodify-conditions.d.ts +22 -0
  189. package/dist/transforms/yodify-conditions.d.ts.map +1 -0
  190. package/dist/transforms/yodify-conditions.js +91 -0
  191. package/dist/transforms/yodify-conditions.js.map +1 -0
  192. package/dist/utils/ast.d.ts +10 -0
  193. package/dist/utils/ast.d.ts.map +1 -0
  194. package/dist/utils/ast.js +112 -0
  195. package/dist/utils/ast.js.map +1 -0
  196. package/dist/utils/change-tracking.d.ts +8 -0
  197. package/dist/utils/change-tracking.d.ts.map +1 -0
  198. package/dist/utils/change-tracking.js +8 -0
  199. package/dist/utils/change-tracking.js.map +1 -0
  200. package/dist/utils/dom-properties.d.ts +2 -0
  201. package/dist/utils/dom-properties.d.ts.map +1 -0
  202. package/dist/utils/dom-properties.js +9252 -0
  203. package/dist/utils/dom-properties.js.map +1 -0
  204. package/dist/utils/evaluation-order.d.ts +4 -0
  205. package/dist/utils/evaluation-order.d.ts.map +1 -0
  206. package/dist/utils/evaluation-order.js +152 -0
  207. package/dist/utils/evaluation-order.js.map +1 -0
  208. package/dist/utils/literal.d.ts +5 -0
  209. package/dist/utils/literal.d.ts.map +1 -0
  210. package/dist/utils/literal.js +90 -0
  211. package/dist/utils/literal.js.map +1 -0
  212. package/dist/utils/logical-lowering.d.ts +4 -0
  213. package/dist/utils/logical-lowering.d.ts.map +1 -0
  214. package/dist/utils/logical-lowering.js +104 -0
  215. package/dist/utils/logical-lowering.js.map +1 -0
  216. package/dist/utils/loop-lowering.d.ts +23 -0
  217. package/dist/utils/loop-lowering.d.ts.map +1 -0
  218. package/dist/utils/loop-lowering.js +88 -0
  219. package/dist/utils/loop-lowering.js.map +1 -0
  220. package/dist/utils/paths.d.ts +9 -0
  221. package/dist/utils/paths.d.ts.map +1 -0
  222. package/dist/utils/paths.js +90 -0
  223. package/dist/utils/paths.js.map +1 -0
  224. package/dist/utils/random.d.ts +3 -0
  225. package/dist/utils/random.d.ts.map +1 -0
  226. package/dist/utils/random.js +22 -0
  227. package/dist/utils/random.js.map +1 -0
  228. package/dist/utils/string-generator.d.ts +37 -0
  229. package/dist/utils/string-generator.d.ts.map +1 -0
  230. package/dist/utils/string-generator.js +175 -0
  231. package/dist/utils/string-generator.js.map +1 -0
  232. package/package.json +76 -0
  233. package/src/analysis/constant.ts +310 -0
  234. package/src/analysis/document-order.ts +141 -0
  235. package/src/analysis/inlineability.ts +336 -0
  236. package/src/analysis/purity.ts +315 -0
  237. package/src/index.ts +2 -0
  238. package/src/obfuscator.ts +271 -0
  239. package/src/options.ts +114 -0
  240. package/src/preparation/dot-to-bracket.ts +41 -0
  241. package/src/preparation/expand-destructuring.ts +211 -0
  242. package/src/preparation/expand-template-literals.ts +92 -0
  243. package/src/preparation/rewrite-class-fields.ts +320 -0
  244. package/src/preparation/split-sequence-expressions.ts +40 -0
  245. package/src/preparation/split-variable-declarations.ts +39 -0
  246. package/src/preparation/stringify-object-keys.ts +107 -0
  247. package/src/preparation/wrap-single-statements.ts +63 -0
  248. package/src/transforms/arguments-to-parameters.ts +187 -0
  249. package/src/transforms/arrow-to-function.ts +225 -0
  250. package/src/transforms/collapse-single-use-temps.ts +957 -0
  251. package/src/transforms/const-let-to-var.ts +246 -0
  252. package/src/transforms/const-to-let.ts +38 -0
  253. package/src/transforms/do-while-to-while.ts +69 -0
  254. package/src/transforms/drop-console.ts +119 -0
  255. package/src/transforms/drop-debugger.ts +38 -0
  256. package/src/transforms/expand-binary-assignment.ts +94 -0
  257. package/src/transforms/expand-logical-assignment.ts +84 -0
  258. package/src/transforms/extract-object-properties.ts +404 -0
  259. package/src/transforms/fold-builtin-methods.ts +949 -0
  260. package/src/transforms/fold-constants.ts +462 -0
  261. package/src/transforms/for-to-while.ts +109 -0
  262. package/src/transforms/function-declaration-to-expression.ts +108 -0
  263. package/src/transforms/inline-functions.ts +1074 -0
  264. package/src/transforms/logical-to-ternary.ts +92 -0
  265. package/src/transforms/mangle-properties.ts +819 -0
  266. package/src/transforms/nullish-coalescing-to-ternary.ts +77 -0
  267. package/src/transforms/numbers-to-strings.ts +61 -0
  268. package/src/transforms/object-method-to-property.ts +86 -0
  269. package/src/transforms/optional-chaining-to-ternary.ts +225 -0
  270. package/src/transforms/pack-declarations-into-parameters.ts +233 -0
  271. package/src/transforms/remove-anonymous-function-names.ts +84 -0
  272. package/src/transforms/remove-unreachable-code.ts +115 -0
  273. package/src/transforms/remove-unused-code.ts +296 -0
  274. package/src/transforms/rename-identifiers.ts +289 -0
  275. package/src/transforms/specials-to-strings.ts +116 -0
  276. package/src/transforms/switch-to-if.ts +339 -0
  277. package/src/transforms/ternary-to-if.ts +387 -0
  278. package/src/transforms/update-to-assignment.ts +132 -0
  279. package/src/transforms/yodify-conditions.ts +103 -0
  280. package/src/utils/ast.ts +129 -0
  281. package/src/utils/change-tracking.ts +15 -0
  282. package/src/utils/dom-properties.ts +9252 -0
  283. package/src/utils/evaluation-order.ts +178 -0
  284. package/src/utils/literal.ts +96 -0
  285. package/src/utils/logical-lowering.ts +132 -0
  286. package/src/utils/loop-lowering.ts +109 -0
  287. package/src/utils/paths.ts +103 -0
  288. package/src/utils/random.ts +22 -0
  289. package/src/utils/string-generator.ts +206 -0
@@ -0,0 +1,339 @@
1
+ import type { NodePath, Scope, Visitor } from '@babel/traverse'
2
+ import * as t from '@babel/types'
3
+ import type { File } from '@babel/types'
4
+
5
+ import { isPure } from 'src/analysis/purity'
6
+ import { traverseForChanges } from 'src/utils/change-tracking'
7
+ import type { ChangeState } from 'src/utils/change-tracking'
8
+ import { isPrimitiveLiteral } from 'src/utils/literal'
9
+ import { generateLoopLabel } from 'src/utils/loop-lowering'
10
+
11
+ /**
12
+ * Lowers `switch` to an `if` chain or, when cases fall through, a labeled block
13
+ * with a fallthrough flag. Case tests keep source order and evaluation count.
14
+ * Cases with lexical or function declarations stay unchanged because separate
15
+ * `if` blocks cannot preserve the shared switch scope.
16
+ *
17
+ * @example
18
+ * // ◀️ before
19
+ * switch (x) {
20
+ * case 1: a(); break;
21
+ * case 2: b();
22
+ * case 3: c(); break;
23
+ * default: d();
24
+ * }
25
+ *
26
+ * // ▶️ after
27
+ * _switch: {
28
+ * var _switchValue = x;
29
+ * var _switchFall = false;
30
+ * if (_switchFall || _switchValue === 1) { _switchFall = true; a(); break _switch; }
31
+ * if (_switchFall || _switchValue === 2) { _switchFall = true; b(); }
32
+ * if (_switchFall || _switchValue === 3) { _switchFall = true; c(); break _switch; }
33
+ * d();
34
+ * }
35
+ */
36
+ export function switchToIf(ast: File): boolean {
37
+ return traverseForChanges(ast, visitor)
38
+ }
39
+
40
+ const visitor: Visitor<ChangeState> = {
41
+ SwitchStatement(path, state) {
42
+ if (transformSwitch(path)) {
43
+ state.changed = true
44
+ }
45
+ },
46
+ }
47
+
48
+ function transformSwitch(path: NodePath<t.SwitchStatement>): boolean {
49
+ const { node } = path
50
+ // Separate `if` blocks cannot preserve cross-case lexical scope or Annex B
51
+ // function hoisting.
52
+ if (hasBlockScopedDeclaration(node)) {
53
+ return false
54
+ }
55
+ if (canLowerToIfElseChain(node)) {
56
+ lowerToIfElseChain(path)
57
+ return true
58
+ }
59
+ if (!labeledLoweringIsSafe(node, path.scope, path)) {
60
+ return false
61
+ }
62
+ lowerToLabeledBlock(path)
63
+ return true
64
+ }
65
+
66
+ function hasBlockScopedDeclaration(node: t.SwitchStatement): boolean {
67
+ for (const caseClause of node.cases) {
68
+ for (const stmt of caseClause.consequent) {
69
+ if (
70
+ (t.isVariableDeclaration(stmt) && stmt.kind !== 'var') ||
71
+ t.isClassDeclaration(stmt) ||
72
+ t.isFunctionDeclaration(stmt) ||
73
+ isLabeledFunctionDeclaration(stmt)
74
+ ) {
75
+ return true
76
+ }
77
+ }
78
+ }
79
+ return false
80
+ }
81
+
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
+ // A non-final default needs match results from later cases. Precomputing that
92
+ // 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 {
98
+ const defaultIndex = node.cases.findIndex((caseClause) => caseClause.test === null)
99
+ if (defaultIndex === -1 || defaultIndex === node.cases.length - 1) {
100
+ return true
101
+ }
102
+ return node.cases.every(
103
+ (caseClause) => caseClause.test == null || isPure(caseClause.test, scope, referencePath),
104
+ )
105
+ }
106
+
107
+ function canLowerToIfElseChain(node: t.SwitchStatement): boolean {
108
+ for (let i = 0; i < node.cases.length; i++) {
109
+ if (!caseEligibleForIfElseChain(node.cases[i]!, i === node.cases.length - 1)) {
110
+ return false
111
+ }
112
+ }
113
+ return true
114
+ }
115
+
116
+ function caseEligibleForIfElseChain(caseClause: t.SwitchCase, isLast: boolean): boolean {
117
+ const { consequent } = caseClause
118
+ if (consequent.length === 0) {
119
+ return isLast
120
+ }
121
+ const last = consequent.at(-1)!
122
+ const terminates = isCaseTerminator(last)
123
+ if (!terminates && !isLast) {
124
+ return false
125
+ }
126
+ for (let i = 0; i < consequent.length - 1; i++) {
127
+ if (containsTopLevelUnlabeledBreak(consequent[i]!)) {
128
+ return false
129
+ }
130
+ }
131
+ if (!(t.isBreakStatement(last) && !last.label) && containsTopLevelUnlabeledBreak(last)) {
132
+ return false
133
+ }
134
+ return true
135
+ }
136
+
137
+ function isCaseTerminator(stmt: t.Statement): boolean {
138
+ if (t.isReturnStatement(stmt) || t.isThrowStatement(stmt)) {
139
+ return true
140
+ }
141
+ if (t.isBreakStatement(stmt) || t.isContinueStatement(stmt)) {
142
+ return true
143
+ }
144
+ return false
145
+ }
146
+
147
+ function containsTopLevelUnlabeledBreak(node: t.Node): boolean {
148
+ if (t.isBreakStatement(node) && !node.label) {
149
+ return true
150
+ }
151
+ if (t.isFunction(node) || t.isLoop(node) || t.isSwitchStatement(node)) {
152
+ return false
153
+ }
154
+ const keys = (t.VISITOR_KEYS as Record<string, string[]>)[node.type]
155
+ if (!keys) {
156
+ return false
157
+ }
158
+ for (const key of keys) {
159
+ const child = (node as unknown as Record<string, unknown>)[key]
160
+ if (Array.isArray(child)) {
161
+ for (const entry of child) {
162
+ if (
163
+ entry &&
164
+ typeof (entry as t.Node).type === 'string' &&
165
+ containsTopLevelUnlabeledBreak(entry as t.Node)
166
+ ) {
167
+ return true
168
+ }
169
+ }
170
+ } else if (
171
+ child &&
172
+ typeof (child as t.Node).type === 'string' &&
173
+ containsTopLevelUnlabeledBreak(child as t.Node)
174
+ ) {
175
+ return true
176
+ }
177
+ }
178
+ return false
179
+ }
180
+
181
+ // Declaration-bearing cases were refused, leaving only a trailing break to drop.
182
+ function buildBody(consequent: t.Statement[]): t.BlockStatement {
183
+ const body = [...consequent]
184
+ const last = body.at(-1)
185
+ if (last && t.isBreakStatement(last) && !last.label) {
186
+ body.pop()
187
+ }
188
+ return t.blockStatement(body)
189
+ }
190
+
191
+ function lowerToIfElseChain(path: NodePath<t.SwitchStatement>): void {
192
+ const { node } = path
193
+ const { scope } = path
194
+
195
+ let discriminantExpr: t.Expression
196
+ const preStmts: t.Statement[] = []
197
+ if (isPrimitiveLiteral(node.discriminant)) {
198
+ discriminantExpr = node.discriminant
199
+ } else {
200
+ const valueName = scope.generateUid('switchValue')
201
+ discriminantExpr = t.identifier(valueName)
202
+ preStmts.push(
203
+ t.variableDeclaration('var', [
204
+ t.variableDeclarator(t.identifier(valueName), node.discriminant),
205
+ ]),
206
+ )
207
+ }
208
+
209
+ const nonDefaultCases = node.cases.filter((caseClause) => caseClause.test !== null)
210
+ const defaultCase = node.cases.find((caseClause) => caseClause.test === null)
211
+
212
+ let chain: t.Statement | null = null
213
+ if (defaultCase) {
214
+ const defaultBody = buildBody(defaultCase.consequent)
215
+ if (defaultBody.body.length > 0) {
216
+ chain = defaultBody
217
+ }
218
+ }
219
+ for (let i = nonDefaultCases.length - 1; i >= 0; i--) {
220
+ const caseClause = nonDefaultCases[i]!
221
+ const test = t.binaryExpression('===', t.cloneNode(discriminantExpr), caseClause.test!)
222
+ chain = t.ifStatement(test, buildBody(caseClause.consequent), chain ?? undefined)
223
+ }
224
+
225
+ const replacement: t.Statement[] = [...preStmts]
226
+ if (chain) {
227
+ replacement.push(chain)
228
+ }
229
+
230
+ if (replacement.length === 0) {
231
+ path.remove()
232
+ return
233
+ }
234
+ if (replacement.length === 1) {
235
+ path.replaceWith(replacement[0]!)
236
+ return
237
+ }
238
+ // Wrap single-statement slots so replacement cannot drop an enclosing label.
239
+ if (typeof path.listKey === 'string') {
240
+ path.replaceWithMultiple(replacement)
241
+ return
242
+ }
243
+ path.replaceWith(t.blockStatement(replacement))
244
+ }
245
+
246
+ function lowerToLabeledBlock(path: NodePath<t.SwitchStatement>): void {
247
+ const { node } = path
248
+ const { scope } = path
249
+
250
+ // `scope.generateUid` alone can collide with enclosing or nested labels.
251
+ const labelName = generateLoopLabel(path, 'switch')
252
+ const valueName = scope.generateUid('switchValue')
253
+ const fallName = scope.generateUid('switchFall')
254
+
255
+ const defaultIndex = node.cases.findIndex((caseClause) => caseClause.test === null)
256
+ const defaultIsLast = defaultIndex === node.cases.length - 1
257
+
258
+ const statements: t.Statement[] = [
259
+ t.variableDeclaration('var', [
260
+ t.variableDeclarator(t.identifier(valueName), node.discriminant),
261
+ ]),
262
+ t.variableDeclaration('var', [
263
+ t.variableDeclarator(t.identifier(fallName), t.booleanLiteral(false)),
264
+ ]),
265
+ ]
266
+
267
+ // `_switchDefault` records whether any case matches while the default body
268
+ // remains in source order. Safety checks already proved rereads pure.
269
+ let defaultName: string | null = null
270
+ if (defaultIndex !== -1 && !defaultIsLast) {
271
+ defaultName = scope.generateUid('switchDefault')
272
+ const nonDefaultTests = node.cases
273
+ .map((caseClause) => caseClause.test)
274
+ .filter((test): test is t.Expression => test !== null)
275
+ const anyMatch: t.Expression = nonDefaultTests
276
+ .map((test) => t.binaryExpression('===', t.identifier(valueName), t.cloneNode(test)))
277
+ .reduce<t.Expression>(
278
+ (acc, expr, i) => (i === 0 ? expr : t.logicalExpression('||', acc, expr)),
279
+ t.booleanLiteral(false),
280
+ )
281
+ statements.push(
282
+ t.variableDeclaration('var', [
283
+ t.variableDeclarator(t.identifier(defaultName), t.unaryExpression('!', anyMatch)),
284
+ ]),
285
+ )
286
+ }
287
+
288
+ for (const caseClause of node.cases) {
289
+ const isDefault = caseClause.test === null
290
+
291
+ // A final default always runs once control reaches it, with nothing after it.
292
+ if (isDefault && defaultIsLast) {
293
+ statements.push(...caseClause.consequent)
294
+ continue
295
+ }
296
+
297
+ const condition: t.Expression = isDefault
298
+ ? t.logicalExpression('||', t.identifier(fallName), t.identifier(defaultName!))
299
+ : t.logicalExpression(
300
+ '||',
301
+ t.identifier(fallName),
302
+ t.binaryExpression('===', t.identifier(valueName), caseClause.test as t.Expression),
303
+ )
304
+
305
+ const body: t.Statement[] = [
306
+ t.expressionStatement(
307
+ t.assignmentExpression('=', t.identifier(fallName), t.booleanLiteral(true)),
308
+ ),
309
+ ...caseClause.consequent,
310
+ ]
311
+
312
+ statements.push(t.ifStatement(condition, t.blockStatement(body)))
313
+ }
314
+
315
+ const labeledBlock = t.labeledStatement(t.identifier(labelName), t.blockStatement(statements))
316
+
317
+ const [labeledPath] = path.replaceWith(labeledBlock)
318
+ rewriteUnlabeledBreaks(labeledPath, labelName)
319
+ }
320
+
321
+ function rewriteUnlabeledBreaks(path: NodePath<t.Node>, labelName: string): void {
322
+ path.traverse(rewriteBreaksVisitor, { labelName })
323
+ }
324
+
325
+ interface RewriteBreaksState {
326
+ labelName: string
327
+ }
328
+
329
+ const rewriteBreaksVisitor: Visitor<RewriteBreaksState> = {
330
+ BreakStatement(path, state) {
331
+ if (path.node.label) {
332
+ return
333
+ }
334
+ path.node.label = t.identifier(state.labelName)
335
+ },
336
+ 'Function|Loop|SwitchStatement'(path) {
337
+ ;(path as NodePath).skip()
338
+ },
339
+ }