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,77 @@
1
+ import type { NodePath, Visitor } from '@babel/traverse'
2
+ import * as t from '@babel/types'
3
+ import type { File } from '@babel/types'
4
+
5
+ import { isInsideWith } from 'src/utils/ast'
6
+ import { traverseForChanges } from 'src/utils/change-tracking'
7
+ import type { ChangeState } from 'src/utils/change-tracking'
8
+ import { lowerLogicalWriteStatement } from 'src/utils/logical-lowering'
9
+
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.
14
+ *
15
+ * @example
16
+ * // ◀️ before
17
+ * var x = a ?? b;
18
+ * f(get() ?? fallback);
19
+ *
20
+ * // ▶️ after
21
+ * var x = a;
22
+ * if (x == null) x = b;
23
+ * f((_t = get()) != null ? _t : fallback);
24
+ */
25
+ export function nullishCoalescingToTernary(ast: File): boolean {
26
+ return traverseForChanges(ast, visitor)
27
+ }
28
+
29
+ const visitor: Visitor<ChangeState> = {
30
+ LogicalExpression(path, state) {
31
+ if (lowerNullishCoalescing(path)) {
32
+ state.changed = true
33
+ }
34
+ },
35
+ }
36
+
37
+ function lowerNullishCoalescing(path: NodePath<t.LogicalExpression>): boolean {
38
+ const { node } = path
39
+ if (node.operator !== '??') {
40
+ return false
41
+ }
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
+ if (lowerLogicalWriteStatement(path, nullishFallbackTest)) {
48
+ return true
49
+ }
50
+
51
+ const { scope } = path
52
+ let test: t.Expression
53
+ let consequent: t.Expression
54
+
55
+ if (t.isIdentifier(node.left) && scope.hasBinding(node.left.name)) {
56
+ test = node.left
57
+ consequent = t.cloneNode(node.left)
58
+ } else {
59
+ const tmp = scope.generateUidIdentifierBasedOnNode(node.left)
60
+ scope.push({ id: t.cloneNode(tmp) })
61
+ test = t.assignmentExpression('=', t.cloneNode(tmp), node.left)
62
+ consequent = t.cloneNode(tmp)
63
+ }
64
+
65
+ path.replaceWith(
66
+ t.conditionalExpression(
67
+ t.binaryExpression('!=', test, t.nullLiteral()),
68
+ consequent,
69
+ node.right,
70
+ ),
71
+ )
72
+ return true
73
+ }
74
+
75
+ function nullishFallbackTest(target: t.Identifier): t.Expression {
76
+ return t.binaryExpression('==', target, t.nullLiteral())
77
+ }
@@ -0,0 +1,61 @@
1
+ import type { NodePath, Visitor } from '@babel/traverse'
2
+ import * as t from '@babel/types'
3
+ import type { File } from '@babel/types'
4
+
5
+ import { traverseForChanges } from 'src/utils/change-tracking'
6
+ import type { ChangeState } from 'src/utils/change-tracking'
7
+
8
+ /**
9
+ * Rewrites numeric literals as strings coerced with unary plus. Under `+`, `-`,
10
+ * or `~`, the existing operator supplies that coercion. Other unary operators
11
+ * keep the wrapper because their string behavior differs.
12
+ *
13
+ * Preparation has already converted numeric object keys to strings.
14
+ *
15
+ * @example
16
+ * // ◀️ before
17
+ * var x = arr[42] + 0.5 * 1e3;
18
+ * var y = -5 * ~3;
19
+ *
20
+ * // ▶️ after
21
+ * var x = arr[+"42"] + +"0.5" * +"1000";
22
+ * var y = -"5" * ~"3";
23
+ */
24
+ export function numbersToStrings(ast: File): boolean {
25
+ return traverseForChanges(ast, visitor)
26
+ }
27
+
28
+ const NUMERIC_UNARIES = new Set(['+', '-', '~'])
29
+
30
+ const visitor: Visitor<ChangeState> = {
31
+ NumericLiteral(path, state) {
32
+ // Non-computed keys are names, and class-field keys can still reach this pass.
33
+ if (isNonComputedKey(path)) {
34
+ return
35
+ }
36
+ const str = t.stringLiteral(String(path.node.value))
37
+ const { parent } = path
38
+ if (
39
+ t.isUnaryExpression(parent) &&
40
+ parent.argument === path.node &&
41
+ NUMERIC_UNARIES.has(parent.operator)
42
+ ) {
43
+ path.replaceWith(str)
44
+ } else {
45
+ path.replaceWith(t.unaryExpression('+', str))
46
+ }
47
+ path.skip()
48
+ state.changed = true
49
+ },
50
+ }
51
+
52
+ function isNonComputedKey(path: NodePath<t.NumericLiteral>): boolean {
53
+ const { parentPath } = path
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
+ }
@@ -0,0 +1,86 @@
1
+ import type { NodePath, Visitor } from '@babel/traverse'
2
+ import * as t from '@babel/types'
3
+ import type { File } from '@babel/types'
4
+
5
+ import { isProtoKey } from 'src/utils/ast'
6
+ import { traverseForChanges } from 'src/utils/change-tracking'
7
+ import type { ChangeState } from 'src/utils/change-tracking'
8
+
9
+ /**
10
+ * Rewrites generator and async object methods as function-valued properties so
11
+ * later passes can lift and inline them.
12
+ *
13
+ * Plain methods stay concise because a function property would gain construction
14
+ * and `prototype`. Generator and async functions remain non-constructors. Any
15
+ * method using `super` also stays intact because a function has no HomeObject.
16
+ *
17
+ * Non-computed `__proto__` methods stay intact because the property form invokes
18
+ * ECMA-262 B.3.1 prototype-setting behavior. Computed keys create own properties
19
+ * and remain safe.
20
+ *
21
+ * @example
22
+ * // ◀️ before
23
+ * const o = {
24
+ * async *stream() {},
25
+ * plain() {},
26
+ * get x() {
27
+ * return 1;
28
+ * },
29
+ * };
30
+ *
31
+ * // ▶️ after
32
+ * const o = {
33
+ * stream: async function* () {},
34
+ * plain() {},
35
+ * get x() {
36
+ * return 1;
37
+ * },
38
+ * };
39
+ */
40
+ export function objectMethodToProperty(ast: File): boolean {
41
+ return traverseForChanges(ast, visitor)
42
+ }
43
+
44
+ const visitor: Visitor<ChangeState> = {
45
+ ObjectMethod(path, state) {
46
+ const { node } = path
47
+ if (node.kind !== 'method' || (!node.generator && !node.async)) {
48
+ return
49
+ }
50
+ if (referencesSuper(path)) {
51
+ return
52
+ }
53
+ if (isNonComputedProtoKey(node)) {
54
+ return
55
+ }
56
+ const fn = t.functionExpression(null, node.params, node.body, node.generator, node.async)
57
+ path.replaceWith(t.objectProperty(node.key, fn, node.computed, false))
58
+ state.changed = true
59
+ },
60
+ }
61
+
62
+ function isNonComputedProtoKey(node: t.ObjectMethod): boolean {
63
+ return !node.computed && isProtoKey(node.key)
64
+ }
65
+
66
+ function referencesSuper(path: NodePath<t.ObjectMethod>): boolean {
67
+ const state = { found: false }
68
+ path.traverse(superVisitor, state)
69
+ return state.found
70
+ }
71
+
72
+ const superVisitor: Visitor<{ found: boolean }> = {
73
+ Function(path, state) {
74
+ if (path.isArrowFunctionExpression()) {
75
+ return
76
+ }
77
+ // A nested method's computed key can still use the outer HomeObject.
78
+ if ((path.isObjectMethod() || path.isClassMethod()) && path.node.computed) {
79
+ path.get('key').traverse(superVisitor, state)
80
+ }
81
+ path.skip()
82
+ },
83
+ Super(_path, state) {
84
+ state.found = true
85
+ },
86
+ }
@@ -0,0 +1,225 @@
1
+ import type { NodePath, Visitor } from '@babel/traverse'
2
+ import * as t from '@babel/types'
3
+ import type { File } from '@babel/types'
4
+
5
+ import { isInsideWith } from 'src/utils/ast'
6
+ import { traverseForChanges } from 'src/utils/change-tracking'
7
+ import type { ChangeState } from 'src/utils/change-tracking'
8
+
9
+ /**
10
+ * Rewrites optional chains as nested ternaries. Side-effectful receivers are
11
+ * cached, and member calls retain their `this`. Optional deletion short-circuits
12
+ * to `true` as required by ECMA-262 13.5.1.
13
+ *
14
+ * @example
15
+ * // ◀️ before
16
+ * obj?.m(arg);
17
+ * delete obj?.x;
18
+ * a?.b?.c;
19
+ *
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;
24
+ */
25
+ export function optionalChainingToTernary(ast: File): boolean {
26
+ return traverseForChanges(ast, visitor)
27
+ }
28
+
29
+ const visitor: Visitor<ChangeState> = {
30
+ OptionalMemberExpression(path, state) {
31
+ if (lowerOptionalChain(path)) {
32
+ state.changed = true
33
+ }
34
+ },
35
+ OptionalCallExpression(path, state) {
36
+ if (lowerOptionalChain(path)) {
37
+ state.changed = true
38
+ }
39
+ },
40
+ }
41
+
42
+ 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
+ if (isInsideOptionalChain(path)) {
48
+ return false
49
+ }
50
+ // A tagged optional chain needs explicit parentheses to remain valid and keep
51
+ // the tag receiver.
52
+ if (isBareTaggedTemplateTag(path)) {
53
+ path.replaceWith(t.parenthesizedExpression(path.node))
54
+ path.skip()
55
+ return true
56
+ }
57
+ return transformChain(path)
58
+ }
59
+
60
+ // Wrap a direct tagged-template parent once.
61
+ function isBareTaggedTemplateTag(path: OptionalPath): boolean {
62
+ const parent = path.parentPath
63
+ return parent != null && parent.isTaggedTemplateExpression() && parent.node.tag === path.node
64
+ }
65
+
66
+ type OptionalNode = t.OptionalMemberExpression | t.OptionalCallExpression
67
+ type OptionalPath = NodePath<OptionalNode>
68
+
69
+ function isInsideOptionalChain(path: NodePath): boolean {
70
+ const parent = path.parentPath
71
+ if (!parent) {
72
+ return false
73
+ }
74
+ if (parent.isOptionalMemberExpression()) {
75
+ return parent.node.object === path.node
76
+ }
77
+ if (parent.isOptionalCallExpression()) {
78
+ return parent.node.callee === path.node
79
+ }
80
+ return false
81
+ }
82
+
83
+ // Refuse member optional calls because `.call` is shadowable, `super` because its
84
+ // HomeObject cannot move, and callee or tag chains because ternaries lose their
85
+ // receiver. Check before mutating the chain.
86
+ function chainIsUnsafe(path: OptionalPath): boolean {
87
+ let node: t.Node = path.node
88
+ for (;;) {
89
+ if (t.isOptionalCallExpression(node)) {
90
+ const callee: t.Node = node.callee
91
+ if (node.optional && (t.isMemberExpression(callee) || t.isOptionalMemberExpression(callee))) {
92
+ return true
93
+ }
94
+ if (t.isSuper(callee)) {
95
+ return true
96
+ }
97
+ node = callee
98
+ } else if (t.isOptionalMemberExpression(node)) {
99
+ const object: t.Node = node.object
100
+ if (t.isSuper(object)) {
101
+ return true
102
+ }
103
+ node = object
104
+ } else {
105
+ break
106
+ }
107
+ }
108
+ let child: t.Node = path.node
109
+ let parent = path.parentPath
110
+ while (parent?.isParenthesizedExpression() && parent.node.expression === child) {
111
+ child = parent.node
112
+ parent = parent.parentPath
113
+ }
114
+ if (!parent) {
115
+ return false
116
+ }
117
+ if ((parent.isCallExpression() || parent.isNewExpression()) && parent.node.callee === child) {
118
+ return true
119
+ }
120
+ return parent.isTaggedTemplateExpression() && parent.node.tag === child
121
+ }
122
+
123
+ function transformChain(path: OptionalPath): boolean {
124
+ if (chainIsUnsafe(path)) {
125
+ return false
126
+ }
127
+
128
+ const optionals: (t.MemberExpression | t.CallExpression)[] = []
129
+
130
+ let cur: NodePath = path
131
+ while (cur.isOptionalMemberExpression() || cur.isOptionalCallExpression()) {
132
+ const { node } = cur
133
+ if (node.optional) {
134
+ optionals.push(node as unknown as t.MemberExpression | t.CallExpression)
135
+ }
136
+ // Demotion keeps the same fields and removes `optional`.
137
+ if (cur.isOptionalMemberExpression()) {
138
+ ;(node as unknown as { type: string }).type = 'MemberExpression'
139
+ cur = cur.get('object') as NodePath
140
+ } else {
141
+ ;(node as unknown as { type: string }).type = 'CallExpression'
142
+ cur = cur.get('callee') as NodePath
143
+ }
144
+ }
145
+
146
+ if (optionals.length === 0) {
147
+ return false
148
+ }
149
+
150
+ const parent = path.parentPath
151
+ const isDelete =
152
+ parent !== null &&
153
+ parent.isUnaryExpression() &&
154
+ parent.node.operator === 'delete' &&
155
+ parent.node.argument === path.node
156
+
157
+ // `void 0` cannot be shadowed like `undefined`.
158
+ const shortCircuit: t.Expression = isDelete
159
+ ? t.booleanLiteral(true)
160
+ : t.unaryExpression('void', t.numericLiteral(0))
161
+
162
+ let replacementPath: NodePath = path
163
+
164
+ for (let i = optionals.length - 1; i >= 0; i--) {
165
+ const node = optionals[i]!
166
+ const isCall = t.isCallExpression(node)
167
+ const chain: t.Expression | t.Super = isCall
168
+ ? ((node as t.CallExpression).callee as t.Expression | t.Super)
169
+ : (node as t.MemberExpression).object
170
+
171
+ if (t.isSuper(chain)) {
172
+ return false
173
+ }
174
+
175
+ let check: t.Expression
176
+
177
+ if (path.scope.isStatic(chain)) {
178
+ check = chain
179
+ } else {
180
+ const tmp = path.scope.generateUidIdentifierBasedOnNode(chain)
181
+ path.scope.push({ id: t.cloneNode(tmp) })
182
+ check = t.assignmentExpression('=', t.cloneNode(tmp), chain)
183
+ if (isCall) {
184
+ ;(node as t.CallExpression).callee = t.cloneNode(tmp)
185
+ } else {
186
+ ;(node as t.MemberExpression).object = t.cloneNode(tmp)
187
+ }
188
+ }
189
+
190
+ // A cached member call needs its original receiver as `this`.
191
+ if (isCall && t.isMemberExpression(chain)) {
192
+ const callNode = node as t.CallExpression
193
+ const { object } = chain
194
+ if (!t.isSuper(object)) {
195
+ let context: t.Expression
196
+ const memoized = path.scope.maybeGenerateMemoised(object)
197
+ if (memoized) {
198
+ context = t.cloneNode(memoized)
199
+ chain.object = t.assignmentExpression('=', memoized, object)
200
+ } else {
201
+ context = t.cloneNode(object) as t.Expression
202
+ }
203
+ callNode.arguments.unshift(t.cloneNode(context))
204
+ callNode.callee = t.memberExpression(callNode.callee as t.Expression, t.identifier('call'))
205
+ }
206
+ }
207
+
208
+ const condition = t.binaryExpression('==', check, t.nullLiteral())
209
+
210
+ let alternate = replacementPath.node as t.Expression
211
+ if (isDelete && i === optionals.length - 1) {
212
+ alternate = t.unaryExpression('delete', alternate)
213
+ }
214
+
215
+ const newTernary = t.conditionalExpression(condition, shortCircuit, alternate)
216
+ const replaced: NodePath<t.ConditionalExpression> = replacementPath.replaceWith(newTernary)[0]
217
+ replacementPath = replaced.get('alternate')
218
+ }
219
+
220
+ if (isDelete) {
221
+ // The ternary already contains deletion, so replace the parent `delete` too.
222
+ parent!.replaceWith(path.node)
223
+ }
224
+ return true
225
+ }
@@ -0,0 +1,233 @@
1
+ import type { Binding, NodePath, Visitor } from '@babel/traverse'
2
+ import * as t from '@babel/types'
3
+ import type { File } from '@babel/types'
4
+
5
+ import { enclosingScopeHasDirectEval, hasDuplicateParamNames } from 'src/utils/ast'
6
+ import { traverseForChanges } from 'src/utils/change-tracking'
7
+ import type { ChangeState } from 'src/utils/change-tracking'
8
+ import { isLiteralShaped } from 'src/utils/literal'
9
+ import { hasAnnexBFunctionAlias } from 'src/utils/paths'
10
+
11
+ /**
12
+ * Moves leading literal-valued `var` declarations into trailing default
13
+ * parameters. Arrows, rest parameters, name collisions, and functions with
14
+ * callers that could fill a new slot stay unchanged.
15
+ *
16
+ * Packing also requires unchanged `arguments`, directive, and hoisting behavior.
17
+ *
18
+ * @example
19
+ * // ◀️ before
20
+ * function run() {
21
+ * var x = 1;
22
+ * return x;
23
+ * }
24
+ *
25
+ * // ▶️ after
26
+ * function run(x = 1) {
27
+ * return x;
28
+ * }
29
+ */
30
+ export function packDeclarationsIntoParameters(ast: File): boolean {
31
+ return traverseForChanges(ast, visitor)
32
+ }
33
+
34
+ const visitor: Visitor<ChangeState> = {
35
+ Function(path, state) {
36
+ if (packLeadingIntoParams(path)) {
37
+ state.changed = true
38
+ }
39
+ },
40
+ }
41
+
42
+ function packLeadingIntoParams(path: NodePath<t.Function>): boolean {
43
+ // Accessors have fixed arity. Method calls are not analyzable, and arrows do
44
+ // not own `arguments`.
45
+ if (
46
+ path.isArrowFunctionExpression() ||
47
+ path.isObjectMethod() ||
48
+ path.isClassMethod() ||
49
+ path.isClassPrivateMethod()
50
+ ) {
51
+ return false
52
+ }
53
+ // An Annex B alias splits call sites across bindings Babel does not connect.
54
+ if (hasAnnexBFunctionAlias(path)) {
55
+ return false
56
+ }
57
+ const { params } = path.node
58
+ if (params.some((param) => t.isRestElement(param))) {
59
+ return false
60
+ }
61
+ // Adding a default makes duplicate sloppy parameters a syntax error.
62
+ if (hasDuplicateParamNames(params)) {
63
+ return false
64
+ }
65
+
66
+ const { body } = path.node
67
+ if (!t.isBlockStatement(body)) {
68
+ return false
69
+ }
70
+ // A strict directive forbids a non-simple parameter list.
71
+ if (body.directives.length > 0) {
72
+ return false
73
+ }
74
+ // Defaults make `arguments` unmapped.
75
+ if (referencesArguments(path)) {
76
+ return false
77
+ }
78
+ // Direct eval can inspect the newly unmapped `arguments` by name.
79
+ if (enclosingScopeHasDirectEval(path)) {
80
+ return false
81
+ }
82
+ // A caller with extra arguments could fill the new parameter slot.
83
+ if (!allCallSitesRespectArity(path, params.length)) {
84
+ return false
85
+ }
86
+
87
+ const existingNames = collectParamNames(params)
88
+ // FunctionDeclarationInstantiation can replace a packed default with a hoisted
89
+ // same-named function.
90
+ const bodyFunctionNames = functionDeclarationNames(body)
91
+
92
+ let packed = false
93
+ while (body.body.length > 0) {
94
+ const stmt = body.body[0]!
95
+ if (!t.isVariableDeclaration(stmt) || stmt.kind !== 'var') {
96
+ break
97
+ }
98
+ if (stmt.declarations.length !== 1) {
99
+ break
100
+ }
101
+ const decl = stmt.declarations[0]!
102
+ if (!t.isIdentifier(decl.id)) {
103
+ break
104
+ }
105
+ if (!decl.init) {
106
+ break
107
+ }
108
+ if (!isLiteralShaped(decl.init)) {
109
+ break
110
+ }
111
+ if (existingNames.has(decl.id.name) || bodyFunctionNames.has(decl.id.name)) {
112
+ break
113
+ }
114
+
115
+ params.push(t.assignmentPattern(t.cloneNode(decl.id), decl.init))
116
+ existingNames.add(decl.id.name)
117
+ body.body.shift()
118
+ packed = true
119
+ }
120
+ return packed
121
+ }
122
+
123
+ function referencesArguments(path: NodePath<t.Function>): boolean {
124
+ let found = false
125
+ const argumentsVisitor: Visitor = {
126
+ Function(inner) {
127
+ if (inner.isArrowFunctionExpression()) {
128
+ // Arrows read the enclosing `arguments`.
129
+ return
130
+ }
131
+ // Non-arrow bodies bind `arguments`, but computed method keys use the
132
+ // enclosing scope.
133
+ const key = methodComputedKey(inner)
134
+ if (key) {
135
+ if (key.isIdentifier() && key.node.name === 'arguments') {
136
+ found = true
137
+ }
138
+ key.traverse(argumentsVisitor)
139
+ }
140
+ inner.skip()
141
+ },
142
+ Identifier(inner) {
143
+ if (inner.node.name === 'arguments' && inner.isReferencedIdentifier()) {
144
+ found = true
145
+ }
146
+ },
147
+ }
148
+ path.traverse(argumentsVisitor)
149
+ return found
150
+ }
151
+
152
+ function methodComputedKey(path: NodePath<t.Function>): NodePath | null {
153
+ if (
154
+ (path.isObjectMethod() || path.isClassMethod() || path.isClassPrivateMethod()) &&
155
+ path.node.computed
156
+ ) {
157
+ return path.get('key') as NodePath
158
+ }
159
+ return null
160
+ }
161
+
162
+ // Every outer and recursive reference must be a direct call within the original
163
+ // arity and without spread.
164
+ function allCallSitesRespectArity(path: NodePath<t.Function>, arity: number): boolean {
165
+ const external = functionBinding(path)
166
+ if (!external || !external.constant || !bindingCallsRespectArity(external, arity)) {
167
+ return false
168
+ }
169
+ if (path.isFunctionExpression() && path.node.id) {
170
+ const inner = path.scope.getBinding(path.node.id.name)
171
+ if (inner && (!inner.constant || !bindingCallsRespectArity(inner, arity))) {
172
+ return false
173
+ }
174
+ }
175
+ return true
176
+ }
177
+
178
+ function bindingCallsRespectArity(binding: Binding, arity: number): boolean {
179
+ for (const ref of binding.referencePaths) {
180
+ const parent = ref.parentPath
181
+ if (
182
+ !parent ||
183
+ !(parent.isCallExpression() || parent.isOptionalCallExpression()) ||
184
+ parent.node.callee !== ref.node
185
+ ) {
186
+ return false
187
+ }
188
+ const args = parent.node.arguments
189
+ if (args.length > arity || args.some((arg) => t.isSpreadElement(arg))) {
190
+ return false
191
+ }
192
+ }
193
+ return true
194
+ }
195
+
196
+ // Annex B can hoist function names from nested blocks into this function scope.
197
+ 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
207
+ }
208
+
209
+ function functionBinding(path: NodePath<t.Function>): Binding | null {
210
+ if (path.isFunctionDeclaration() && path.node.id) {
211
+ return path.scope.parent?.getBinding(path.node.id.name) ?? null
212
+ }
213
+ const parent = path.parentPath
214
+ if (
215
+ parent?.isVariableDeclarator() &&
216
+ parent.node.init === path.node &&
217
+ t.isIdentifier(parent.node.id)
218
+ ) {
219
+ return parent.scope.getBinding(parent.node.id.name) ?? null
220
+ }
221
+ return null
222
+ }
223
+
224
+ // Parameter names inside patterns and defaults also block duplicate packing.
225
+ function collectParamNames(params: t.Function['params']): Set<string> {
226
+ const names = new Set<string>()
227
+ for (const param of params) {
228
+ for (const name of Object.keys(t.getBindingIdentifiers(param))) {
229
+ names.add(name)
230
+ }
231
+ }
232
+ return names
233
+ }