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,949 @@
1
+ import traverse from '@babel/traverse'
2
+ import type { NodePath, Visitor } from '@babel/traverse'
3
+ import * as t from '@babel/types'
4
+ import type { File } from '@babel/types'
5
+
6
+ import { evaluateConstant, isOpaque } from 'src/analysis/constant'
7
+ import { enclosingScopeHasDirectEval, isInDirectivePrologue, isInsideWith } from 'src/utils/ast'
8
+ import type { ChangeState } from 'src/utils/change-tracking'
9
+ import { valueToLiteral } from 'src/utils/literal'
10
+
11
+ /**
12
+ * Replaces built-in calls and reads with smaller syntax or host-computed
13
+ * constants. Host evaluation requires known arguments and receivers. Shadowed
14
+ * names, optional forms, and regexp methods stay unchanged.
15
+ *
16
+ * @example
17
+ * // ◀️ before
18
+ * Math.pow(2, 3);
19
+ * Array.of(1, 2, 3);
20
+ * "abc".toUpperCase();
21
+ * Math.PI;
22
+ * parseInt("0x1F", 16);
23
+ *
24
+ * // ▶️ after
25
+ * 8;
26
+ * [1, 2, 3];
27
+ * "ABC";
28
+ * 3.141592653589793;
29
+ * 31;
30
+ */
31
+ export function foldBuiltinMethods(ast: File): boolean {
32
+ const state: FoldState = { changed: false, patchedRoots: collectPatchedIntrinsics(ast) }
33
+ traverse(ast, visitor, undefined, state)
34
+ return state.changed
35
+ }
36
+
37
+ // Visitor state keeps patched-intrinsic data scoped to one traversal.
38
+ interface FoldState extends ChangeState {
39
+ patchedRoots: Set<string>
40
+ }
41
+
42
+ // A written intrinsic root may no longer match the host used for folding.
43
+ const INTRINSIC_ROOTS = new Set([
44
+ 'Math',
45
+ 'String',
46
+ 'Number',
47
+ 'Array',
48
+ 'Boolean',
49
+ 'BigInt',
50
+ 'Object',
51
+ 'Reflect',
52
+ 'parseInt',
53
+ 'parseFloat',
54
+ 'isNaN',
55
+ 'isFinite',
56
+ 'atob',
57
+ 'btoa',
58
+ 'encodeURI',
59
+ 'decodeURI',
60
+ 'encodeURIComponent',
61
+ 'decodeURIComponent',
62
+ ])
63
+
64
+ // These global objects expose intrinsic roots as writable properties.
65
+ const GLOBAL_OBJECT_NAMES = new Set(['globalThis', 'window', 'self', 'global'])
66
+
67
+ function collectPatchedIntrinsics(ast: File): Set<string> {
68
+ const poisoned = new Set<string>()
69
+ const poison = (roots: Iterable<string>): void => {
70
+ for (const root of roots) {
71
+ poisoned.add(root)
72
+ }
73
+ }
74
+
75
+ traverse(ast, {
76
+ // An alias can patch its intrinsic later, after further aliasing or calls.
77
+ VariableDeclarator(path) {
78
+ if (path.node.init && t.isIdentifier(path.node.id)) {
79
+ poison(aliasSourceRoots(path.node.init, path.scope))
80
+ }
81
+ },
82
+ AssignmentExpression(path) {
83
+ markWriteTarget(path.node.left, path.scope, poison)
84
+ if (path.node.operator === '=' && t.isIdentifier(path.node.left)) {
85
+ poison(aliasSourceRoots(path.node.right, path.scope))
86
+ }
87
+ },
88
+ UpdateExpression(path) {
89
+ markWriteTarget(path.node.argument, path.scope, poison)
90
+ },
91
+ UnaryExpression(path) {
92
+ // Deletion patches an intrinsic as surely as assignment.
93
+ if (path.node.operator === 'delete') {
94
+ markWriteTarget(path.node.argument, path.scope, poison)
95
+ }
96
+ },
97
+ ForInStatement(path) {
98
+ markWriteTarget(path.node.left, path.scope, poison)
99
+ },
100
+ ForOfStatement(path) {
101
+ markWriteTarget(path.node.left, path.scope, poison)
102
+ },
103
+ CallExpression(path) {
104
+ markMutationCall(path, poison)
105
+ },
106
+ Identifier(path) {
107
+ // Global eval and Function can patch any intrinsic outside this scan.
108
+ const { name } = path.node
109
+ if (name !== 'eval' && name !== 'Function') {
110
+ return
111
+ }
112
+ if (!path.isReferencedIdentifier() || path.scope.getBinding(name)) {
113
+ return
114
+ }
115
+ if (name === 'eval') {
116
+ poison(INTRINSIC_ROOTS)
117
+ return
118
+ }
119
+ const parent = path.parentPath
120
+ if (
121
+ parent &&
122
+ (parent.isCallExpression() || parent.isNewExpression()) &&
123
+ parent.node.callee === path.node
124
+ ) {
125
+ poison(INTRINSIC_ROOTS)
126
+ }
127
+ },
128
+ })
129
+ return poisoned
130
+ }
131
+
132
+ type PoisonSink = (roots: Iterable<string>) => void
133
+
134
+ // A global-object handle reaches every root. A local binding reaches none.
135
+ const NO_ROOTS: readonly string[] = []
136
+
137
+ // Destructuring writes recurse to each leaf target.
138
+ function markWriteTarget(target: t.Node, scope: NodePath['scope'], poison: PoisonSink): void {
139
+ if (t.isArrayPattern(target)) {
140
+ for (const element of target.elements) {
141
+ if (element) {
142
+ markWriteTarget(element, scope, poison)
143
+ }
144
+ }
145
+ return
146
+ }
147
+ if (t.isObjectPattern(target)) {
148
+ for (const prop of target.properties) {
149
+ markWriteTarget(t.isObjectProperty(prop) ? (prop.value as t.Node) : prop, scope, poison)
150
+ }
151
+ return
152
+ }
153
+ if (t.isRestElement(target)) {
154
+ markWriteTarget(target.argument, scope, poison)
155
+ return
156
+ }
157
+ if (t.isAssignmentPattern(target)) {
158
+ markWriteTarget(target.left, scope, poison)
159
+ return
160
+ }
161
+ poison(writeTargetRoots(target, scope))
162
+ }
163
+
164
+ // Only genuine Object and Reflect bulk mutation methods count as patches.
165
+ function markMutationCall(path: NodePath<t.CallExpression>, poison: PoisonSink): void {
166
+ const { callee } = path.node
167
+ // Preparation may already have changed the method to bracket syntax.
168
+ if (!t.isMemberExpression(callee) || !t.isIdentifier(callee.object)) {
169
+ return
170
+ }
171
+ const method = staticPropertyName(callee)
172
+ if (method === null || path.scope.getBinding(callee.object.name)) {
173
+ return
174
+ }
175
+ if (!isMutatingIntrinsicMethod(callee.object.name, method)) {
176
+ return
177
+ }
178
+ const [firstArg] = path.node.arguments
179
+ if (!firstArg || !t.isExpression(firstArg)) {
180
+ return
181
+ }
182
+ poison(mutationTargetRoots(firstArg, path.scope))
183
+ }
184
+
185
+ function isMutatingIntrinsicMethod(receiver: string, method: string): boolean {
186
+ if (receiver === 'Object') {
187
+ return (
188
+ method === 'assign' ||
189
+ method === 'defineProperty' ||
190
+ method === 'defineProperties' ||
191
+ method === 'setPrototypeOf'
192
+ )
193
+ }
194
+ if (receiver === 'Reflect') {
195
+ return (
196
+ method === 'set' ||
197
+ method === 'defineProperty' ||
198
+ method === 'deleteProperty' ||
199
+ method === 'setPrototypeOf'
200
+ )
201
+ }
202
+ return false
203
+ }
204
+
205
+ // A bare write reaches an intrinsic only through a genuine global reference.
206
+ function writeTargetRoots(target: t.Node, scope: NodePath['scope']): Iterable<string> {
207
+ if (t.isIdentifier(target)) {
208
+ if (scope.getBinding(target.name)) {
209
+ return NO_ROOTS
210
+ }
211
+ return INTRINSIC_ROOTS.has(target.name) ? [target.name] : NO_ROOTS
212
+ }
213
+ if (t.isMemberExpression(target)) {
214
+ return memberTargetRoots(target, scope)
215
+ }
216
+ return NO_ROOTS
217
+ }
218
+
219
+ // Mutating the global object can reach every intrinsic root.
220
+ function mutationTargetRoots(target: t.Node, scope: NodePath['scope']): Iterable<string> {
221
+ if (t.isIdentifier(target)) {
222
+ if (scope.getBinding(target.name)) {
223
+ return NO_ROOTS
224
+ }
225
+ if (INTRINSIC_ROOTS.has(target.name)) {
226
+ return [target.name]
227
+ }
228
+ return GLOBAL_OBJECT_NAMES.has(target.name) ? INTRINSIC_ROOTS : NO_ROOTS
229
+ }
230
+ if (t.isMemberExpression(target)) {
231
+ return memberTargetRoots(target, scope)
232
+ }
233
+ return NO_ROOTS
234
+ }
235
+
236
+ function memberTargetRoots(member: t.MemberExpression, scope: NodePath['scope']): Iterable<string> {
237
+ const root = memberRoot(member)
238
+ if (!root || scope.getBinding(root.name)) {
239
+ return NO_ROOTS
240
+ }
241
+ if (INTRINSIC_ROOTS.has(root.name)) {
242
+ return [root.name]
243
+ }
244
+ if (GLOBAL_OBJECT_NAMES.has(root.name)) {
245
+ const name = staticPropertyName(innermostMember(member))
246
+ if (name === null) {
247
+ return INTRINSIC_ROOTS
248
+ }
249
+ return INTRINSIC_ROOTS.has(name) ? [name] : NO_ROOTS
250
+ }
251
+ return NO_ROOTS
252
+ }
253
+
254
+ // Namespace and prototype aliases can patch roots. Method aliases cannot.
255
+ function aliasSourceRoots(value: t.Node, scope: NodePath['scope']): Iterable<string> {
256
+ if (t.isIdentifier(value)) {
257
+ if (scope.getBinding(value.name)) {
258
+ return NO_ROOTS
259
+ }
260
+ if (INTRINSIC_ROOTS.has(value.name)) {
261
+ return [value.name]
262
+ }
263
+ return GLOBAL_OBJECT_NAMES.has(value.name) ? INTRINSIC_ROOTS : NO_ROOTS
264
+ }
265
+ if (t.isMemberExpression(value)) {
266
+ const root = memberRoot(value)
267
+ if (!root || scope.getBinding(root.name)) {
268
+ return NO_ROOTS
269
+ }
270
+ if (INTRINSIC_ROOTS.has(root.name)) {
271
+ return staticPropertyName(value) === 'prototype' ? [root.name] : NO_ROOTS
272
+ }
273
+ if (GLOBAL_OBJECT_NAMES.has(root.name)) {
274
+ const name = staticPropertyName(innermostMember(value))
275
+ if (name !== null && INTRINSIC_ROOTS.has(name)) {
276
+ return [name]
277
+ }
278
+ }
279
+ }
280
+ return NO_ROOTS
281
+ }
282
+
283
+ function memberRoot(node: t.MemberExpression): t.Identifier | null {
284
+ let current: t.Expression = node
285
+ while (t.isMemberExpression(current)) {
286
+ current = current.object as t.Expression
287
+ }
288
+ return t.isIdentifier(current) ? current : null
289
+ }
290
+
291
+ // For `a.b.c`, `a.b` holds the property reached directly from the chain root.
292
+ function innermostMember(node: t.MemberExpression): t.MemberExpression {
293
+ let current = node
294
+ while (t.isMemberExpression(current.object)) {
295
+ current = current.object
296
+ }
297
+ return current
298
+ }
299
+
300
+ const visitor: Visitor<FoldState> = {
301
+ CallExpression: {
302
+ exit(path, state) {
303
+ if (tryFoldBuiltinCall(path, state.patchedRoots)) {
304
+ state.changed = true
305
+ }
306
+ },
307
+ },
308
+ MemberExpression: {
309
+ exit(path, state) {
310
+ if (tryFoldBuiltinMember(path, state.patchedRoots)) {
311
+ state.changed = true
312
+ }
313
+ },
314
+ },
315
+ }
316
+
317
+ function tryFoldBuiltinCall(path: NodePath<t.CallExpression>, patchedRoots: Set<string>): boolean {
318
+ const before = path.node
319
+ handleCall(path, patchedRoots)
320
+ return path.node !== before || path.removed
321
+ }
322
+
323
+ function tryFoldBuiltinMember(
324
+ path: NodePath<t.MemberExpression>,
325
+ patchedRoots: Set<string>,
326
+ ): boolean {
327
+ const before = path.node
328
+ handleMember(path, patchedRoots)
329
+ return path.node !== before || path.removed
330
+ }
331
+
332
+ const MATH_CONSTANTS = new Map<string, number>([
333
+ ['PI', Math.PI],
334
+ ['E', Math.E],
335
+ ['LN2', Math.LN2],
336
+ ['LN10', Math.LN10],
337
+ ['LOG2E', Math.LOG2E],
338
+ ['LOG10E', Math.LOG10E],
339
+ ['SQRT2', Math.SQRT2],
340
+ ['SQRT1_2', Math.SQRT1_2],
341
+ ])
342
+
343
+ const NUMBER_CONSTANTS = new Map<string, number>([
344
+ ['MAX_SAFE_INTEGER', Number.MAX_SAFE_INTEGER],
345
+ ['MIN_SAFE_INTEGER', Number.MIN_SAFE_INTEGER],
346
+ ['MAX_VALUE', Number.MAX_VALUE],
347
+ ['MIN_VALUE', Number.MIN_VALUE],
348
+ ['EPSILON', Number.EPSILON],
349
+ ])
350
+
351
+ const MATH_FUNCTIONS = new Set([
352
+ 'abs',
353
+ 'acos',
354
+ 'acosh',
355
+ 'asin',
356
+ 'asinh',
357
+ 'atan',
358
+ 'atan2',
359
+ 'atanh',
360
+ 'cbrt',
361
+ 'ceil',
362
+ 'clz32',
363
+ 'cos',
364
+ 'cosh',
365
+ 'exp',
366
+ 'expm1',
367
+ 'floor',
368
+ 'fround',
369
+ 'hypot',
370
+ 'imul',
371
+ 'log',
372
+ 'log10',
373
+ 'log1p',
374
+ 'log2',
375
+ 'max',
376
+ 'min',
377
+ 'round',
378
+ 'sign',
379
+ 'sin',
380
+ 'sinh',
381
+ 'sqrt',
382
+ 'tan',
383
+ 'tanh',
384
+ 'trunc',
385
+ ])
386
+
387
+ const NUMBER_NS_FUNCTIONS = new Set([
388
+ 'isNaN',
389
+ 'isFinite',
390
+ 'isInteger',
391
+ 'isSafeInteger',
392
+ 'parseInt',
393
+ 'parseFloat',
394
+ ])
395
+
396
+ const STRING_NS_FUNCTIONS = new Set(['fromCharCode', 'fromCodePoint'])
397
+
398
+ const GLOBAL_FUNCTIONS = new Set([
399
+ 'parseInt',
400
+ 'parseFloat',
401
+ 'isNaN',
402
+ 'isFinite',
403
+ 'atob',
404
+ 'btoa',
405
+ 'encodeURI',
406
+ 'decodeURI',
407
+ 'encodeURIComponent',
408
+ 'decodeURIComponent',
409
+ ])
410
+
411
+ const STRING_PROTOTYPE_METHODS = new Set([
412
+ 'charAt',
413
+ 'charCodeAt',
414
+ 'codePointAt',
415
+ 'toUpperCase',
416
+ 'toLowerCase',
417
+ 'slice',
418
+ 'substring',
419
+ 'substr',
420
+ 'trim',
421
+ 'trimStart',
422
+ 'trimEnd',
423
+ 'trimLeft',
424
+ 'trimRight',
425
+ 'indexOf',
426
+ 'lastIndexOf',
427
+ 'includes',
428
+ 'startsWith',
429
+ 'endsWith',
430
+ 'padStart',
431
+ 'padEnd',
432
+ 'at',
433
+ 'normalize',
434
+ 'repeat',
435
+ 'concat',
436
+ ])
437
+
438
+ const NUMBER_PROTOTYPE_METHODS = new Set(['toString', 'toFixed', 'toExponential', 'toPrecision'])
439
+
440
+ const BOOLEAN_PROTOTYPE_METHODS = new Set(['toString'])
441
+
442
+ const BIGINT_PROTOTYPE_METHODS = new Set(['toString'])
443
+
444
+ function staticPropertyName(node: t.MemberExpression): string | null {
445
+ const prop = node.property
446
+ if (!node.computed) {
447
+ if (t.isIdentifier(prop)) {
448
+ return prop.name
449
+ }
450
+ return null
451
+ }
452
+ if (t.isStringLiteral(prop)) {
453
+ return prop.value
454
+ }
455
+ if (t.isNumericLiteral(prop)) {
456
+ return String(prop.value)
457
+ }
458
+ return null
459
+ }
460
+
461
+ // Write targets require references. A folded literal would be invalid syntax,
462
+ // including when the member sits inside a destructuring target.
463
+ function isReferenceRequiredPosition(path: NodePath<t.MemberExpression>): boolean {
464
+ const { parentPath } = path
465
+ if (!parentPath) {
466
+ return false
467
+ }
468
+ const parent = parentPath.node
469
+ const { node } = path
470
+ if (t.isAssignmentExpression(parent) && parent.left === node) {
471
+ return true
472
+ }
473
+ if (t.isUpdateExpression(parent) && parent.argument === node) {
474
+ return true
475
+ }
476
+ if (t.isUnaryExpression(parent) && parent.operator === 'delete') {
477
+ return true
478
+ }
479
+ if ((t.isForInStatement(parent) || t.isForOfStatement(parent)) && parent.left === node) {
480
+ return true
481
+ }
482
+ if (t.isArrayPattern(parent)) {
483
+ return true
484
+ }
485
+ if (t.isRestElement(parent) && parent.argument === node) {
486
+ return true
487
+ }
488
+ if (t.isAssignmentPattern(parent) && parent.left === node) {
489
+ return true
490
+ }
491
+ if (t.isObjectProperty(parent) && parent.value === node) {
492
+ const grandparent = parentPath.parentPath
493
+ return grandparent != null && grandparent.isObjectPattern()
494
+ }
495
+ return false
496
+ }
497
+
498
+ // Folding a leading call to a string can create a directive and change strictness.
499
+ function foldsIntoDirective(path: NodePath<t.CallExpression>, replacement: t.Expression): boolean {
500
+ if (!t.isStringLiteral(replacement)) {
501
+ return false
502
+ }
503
+ const { parentPath } = path
504
+ return (
505
+ parentPath != null && parentPath.isExpressionStatement() && isInDirectivePrologue(parentPath)
506
+ )
507
+ }
508
+
509
+ function handleCall(path: NodePath<t.CallExpression>, patchedRoots: Set<string>): void {
510
+ // `with` and direct eval can shadow names outside Babel's scope table.
511
+ if (isInsideWith(path) || enclosingScopeHasDirectEval(path)) {
512
+ return
513
+ }
514
+ const { callee } = path.node
515
+
516
+ if (t.isIdentifier(callee)) {
517
+ foldGlobalCall(path, callee.name, patchedRoots)
518
+ return
519
+ }
520
+
521
+ if (!t.isMemberExpression(callee)) {
522
+ return
523
+ }
524
+ const propName = staticPropertyName(callee)
525
+ if (propName === null) {
526
+ return
527
+ }
528
+ const obj = callee.object
529
+ if (t.isSuper(obj)) {
530
+ return
531
+ }
532
+
533
+ if (t.isIdentifier(obj)) {
534
+ foldNamespaceCall(path, obj.name, propName, patchedRoots)
535
+ if (!t.isCallExpression(path.node)) {
536
+ return
537
+ }
538
+ }
539
+ foldReceiverCall(path, obj, propName, patchedRoots)
540
+ }
541
+
542
+ function handleMember(path: NodePath<t.MemberExpression>, patchedRoots: Set<string>): void {
543
+ const propName = staticPropertyName(path.node)
544
+ if (propName === null) {
545
+ return
546
+ }
547
+
548
+ const { parent } = path
549
+ // Calls use their own folding path.
550
+ if (
551
+ (t.isCallExpression(parent) ||
552
+ t.isOptionalCallExpression(parent) ||
553
+ t.isNewExpression(parent)) &&
554
+ parent.callee === path.node
555
+ ) {
556
+ return
557
+ }
558
+ if (isReferenceRequiredPosition(path)) {
559
+ return
560
+ }
561
+ // Delay the ancestor walk until cheap shape checks pass.
562
+ if (isInsideWith(path) || enclosingScopeHasDirectEval(path)) {
563
+ return
564
+ }
565
+
566
+ const obj = path.node.object
567
+
568
+ if (t.isIdentifier(obj) && !path.scope.getBinding(obj.name) && !patchedRoots.has(obj.name)) {
569
+ if (obj.name === 'Math') {
570
+ const value = MATH_CONSTANTS.get(propName)
571
+ if (value !== undefined) {
572
+ const lit = valueToLiteral(value)
573
+ if (lit) {
574
+ path.replaceWith(lit)
575
+ }
576
+ }
577
+ return
578
+ }
579
+ if (obj.name === 'Number') {
580
+ const value = NUMBER_CONSTANTS.get(propName)
581
+ if (value !== undefined) {
582
+ const lit = valueToLiteral(value)
583
+ if (lit) {
584
+ path.replaceWith(lit)
585
+ }
586
+ }
587
+ return
588
+ }
589
+ }
590
+
591
+ if (propName === 'length') {
592
+ const recv = evaluateConstant(obj, path.scope, path)
593
+ if (recv.known && typeof recv.value === 'string') {
594
+ path.replaceWith(t.numericLiteral(recv.value.length))
595
+ }
596
+ }
597
+ }
598
+
599
+ function foldGlobalCall(
600
+ path: NodePath<t.CallExpression>,
601
+ name: string,
602
+ patchedRoots: Set<string>,
603
+ ): void {
604
+ if (patchedRoots.has(name)) {
605
+ return
606
+ }
607
+ if (name === 'Boolean') {
608
+ if (path.scope.getBinding('Boolean')) {
609
+ return
610
+ }
611
+ foldBooleanCtor(path)
612
+ return
613
+ }
614
+ if (name === 'String') {
615
+ if (path.scope.getBinding('String')) {
616
+ return
617
+ }
618
+ foldStringCtor(path)
619
+ return
620
+ }
621
+ if (name === 'Number') {
622
+ if (path.scope.getBinding('Number')) {
623
+ return
624
+ }
625
+ foldNumberCtor(path)
626
+ return
627
+ }
628
+
629
+ if (GLOBAL_FUNCTIONS.has(name)) {
630
+ if (path.scope.getBinding(name)) {
631
+ return
632
+ }
633
+ const fn = (globalThis as unknown as Record<string, unknown>)[name]
634
+ if (typeof fn !== 'function') {
635
+ return
636
+ }
637
+ foldByReflection(path, fn as (...args: unknown[]) => unknown)
638
+ }
639
+ }
640
+
641
+ function foldNamespaceCall(
642
+ path: NodePath<t.CallExpression>,
643
+ ns: string,
644
+ member: string,
645
+ patchedRoots: Set<string>,
646
+ ): void {
647
+ if (path.scope.getBinding(ns) || patchedRoots.has(ns)) {
648
+ return
649
+ }
650
+
651
+ if (ns === 'Math') {
652
+ if (member === 'pow') {
653
+ foldMathPow(path)
654
+ return
655
+ }
656
+ if (MATH_FUNCTIONS.has(member)) {
657
+ foldByReflection(
658
+ path,
659
+ (Math as unknown as Record<string, (...a: unknown[]) => unknown>)[member],
660
+ )
661
+ }
662
+ return
663
+ }
664
+ if (ns === 'String') {
665
+ if (STRING_NS_FUNCTIONS.has(member)) {
666
+ foldByReflection(
667
+ path,
668
+ (String as unknown as Record<string, (...a: unknown[]) => unknown>)[member],
669
+ )
670
+ }
671
+ return
672
+ }
673
+ if (ns === 'Number') {
674
+ if (NUMBER_NS_FUNCTIONS.has(member)) {
675
+ foldByReflection(
676
+ path,
677
+ (Number as unknown as Record<string, (...a: unknown[]) => unknown>)[member],
678
+ )
679
+ }
680
+ return
681
+ }
682
+ if (ns === 'Array') {
683
+ if (member === 'of') {
684
+ foldArrayOf(path)
685
+ return
686
+ }
687
+ if (member === 'isArray') {
688
+ foldArrayIsArray(path)
689
+ }
690
+ }
691
+ }
692
+
693
+ function foldReceiverCall(
694
+ path: NodePath<t.CallExpression>,
695
+ receiverNode: t.Expression,
696
+ method: string,
697
+ patchedRoots: Set<string>,
698
+ ): void {
699
+ const recv = evaluateConstant(receiverNode, path.scope, path)
700
+ if (!recv.known) {
701
+ return
702
+ }
703
+ const { value } = recv
704
+
705
+ if (typeof value === 'string') {
706
+ if (!STRING_PROTOTYPE_METHODS.has(method) || patchedRoots.has('String')) {
707
+ return
708
+ }
709
+ foldPrototypeCall(path, value, method)
710
+ return
711
+ }
712
+ if (typeof value === 'number') {
713
+ if (!NUMBER_PROTOTYPE_METHODS.has(method) || patchedRoots.has('Number')) {
714
+ return
715
+ }
716
+ foldPrototypeCall(path, value, method)
717
+ return
718
+ }
719
+ if (typeof value === 'boolean') {
720
+ if (!BOOLEAN_PROTOTYPE_METHODS.has(method) || patchedRoots.has('Boolean')) {
721
+ return
722
+ }
723
+ foldPrototypeCall(path, value, method)
724
+ return
725
+ }
726
+ if (typeof value === 'bigint') {
727
+ if (!BIGINT_PROTOTYPE_METHODS.has(method) || patchedRoots.has('BigInt')) {
728
+ return
729
+ }
730
+ foldPrototypeCall(path, value, method)
731
+ }
732
+ }
733
+
734
+ function foldMathPow(path: NodePath<t.CallExpression>): void {
735
+ const args = path.node.arguments
736
+ if (args.length !== 2) {
737
+ return
738
+ }
739
+ const [left, right] = args
740
+ if (!t.isExpression(left) || !t.isExpression(right)) {
741
+ return
742
+ }
743
+ // `Math.pow` rejects two BigInt operands while `**` returns a BigInt. A known
744
+ // Number operand makes mixed BigInt input throw in both forms.
745
+ if (!operandIsNumber(left, path) && !operandIsNumber(right, path)) {
746
+ return
747
+ }
748
+ path.replaceWith(t.binaryExpression('**', left, right))
749
+ }
750
+
751
+ function operandIsNumber(node: t.Expression, path: NodePath<t.CallExpression>): boolean {
752
+ if (t.isNumericLiteral(node)) {
753
+ return true
754
+ }
755
+ // Unary plus produces a Number or throws before exponentiation.
756
+ if (t.isUnaryExpression(node) && node.operator === '+') {
757
+ return true
758
+ }
759
+ const evaluated = evaluateConstant(node, path.scope, path)
760
+ return evaluated.known && typeof evaluated.value === 'number'
761
+ }
762
+
763
+ function foldArrayOf(path: NodePath<t.CallExpression>): void {
764
+ const args = path.node.arguments
765
+ const elements: (t.Expression | t.SpreadElement | null)[] = []
766
+ for (const arg of args) {
767
+ if (t.isExpression(arg) || t.isSpreadElement(arg)) {
768
+ elements.push(arg)
769
+ continue
770
+ }
771
+ return
772
+ }
773
+ path.replaceWith(t.arrayExpression(elements))
774
+ }
775
+
776
+ function foldArrayIsArray(path: NodePath<t.CallExpression>): void {
777
+ const args = path.node.arguments
778
+ if (args.length !== 1) {
779
+ return
780
+ }
781
+ const [arg] = args
782
+ if (!t.isExpression(arg)) {
783
+ return
784
+ }
785
+ // A known scalar cannot be an array.
786
+ const evaluated = evaluateConstant(arg, path.scope, path)
787
+ if (!evaluated.known) {
788
+ return
789
+ }
790
+ const { value } = evaluated
791
+ if (
792
+ typeof value === 'string' ||
793
+ typeof value === 'number' ||
794
+ typeof value === 'boolean' ||
795
+ typeof value === 'bigint' ||
796
+ value === null ||
797
+ value === undefined
798
+ ) {
799
+ path.replaceWith(t.booleanLiteral(false))
800
+ }
801
+ }
802
+
803
+ function foldBooleanCtor(path: NodePath<t.CallExpression>): void {
804
+ const args = path.node.arguments
805
+ if (args.length === 0) {
806
+ path.replaceWith(t.booleanLiteral(false))
807
+ return
808
+ }
809
+ if (args.length !== 1) {
810
+ return
811
+ }
812
+ const [arg] = args
813
+ if (!t.isExpression(arg)) {
814
+ return
815
+ }
816
+ path.replaceWith(t.unaryExpression('!', t.unaryExpression('!', arg)))
817
+ }
818
+
819
+ function foldStringCtor(path: NodePath<t.CallExpression>): void {
820
+ const args = path.node.arguments
821
+ if (args.length === 0) {
822
+ const empty = t.stringLiteral('')
823
+ if (!foldsIntoDirective(path, empty)) {
824
+ path.replaceWith(empty)
825
+ }
826
+ return
827
+ }
828
+ if (args.length !== 1) {
829
+ return
830
+ }
831
+ const [arg] = args
832
+ if (!t.isExpression(arg)) {
833
+ return
834
+ }
835
+ const evaluated = evaluateConstant(arg, path.scope, path)
836
+ if (!evaluated.known || isOpaque(evaluated.value)) {
837
+ return
838
+ }
839
+ let result: string
840
+ try {
841
+ result = String(evaluated.value)
842
+ } catch {
843
+ return
844
+ }
845
+ const lit = valueToLiteral(result)
846
+ if (!lit || foldsIntoDirective(path, lit)) {
847
+ return
848
+ }
849
+ path.replaceWith(lit)
850
+ }
851
+
852
+ function foldNumberCtor(path: NodePath<t.CallExpression>): void {
853
+ const args = path.node.arguments
854
+ if (args.length === 0) {
855
+ path.replaceWith(t.numericLiteral(0))
856
+ return
857
+ }
858
+ if (args.length !== 1) {
859
+ return
860
+ }
861
+ const [arg] = args
862
+ if (!t.isExpression(arg)) {
863
+ return
864
+ }
865
+ const evaluated = evaluateConstant(arg, path.scope, path)
866
+ if (!evaluated.known || isOpaque(evaluated.value)) {
867
+ return
868
+ }
869
+ let result: number
870
+ try {
871
+ // BigInt conversion throws outside the safe-integer range.
872
+ result = Number(evaluated.value as never)
873
+ } catch {
874
+ return
875
+ }
876
+ const lit = valueToLiteral(result)
877
+ if (!lit) {
878
+ return
879
+ }
880
+ path.replaceWith(lit)
881
+ }
882
+
883
+ function foldByReflection(
884
+ path: NodePath<t.CallExpression>,
885
+ fn: ((...a: unknown[]) => unknown) | undefined,
886
+ ): void {
887
+ if (typeof fn !== 'function') {
888
+ return
889
+ }
890
+ const args = tryEvaluateArgs(path.node.arguments, path)
891
+ if (args === null) {
892
+ return
893
+ }
894
+ let result: unknown
895
+ try {
896
+ result = fn(...args)
897
+ } catch {
898
+ return
899
+ }
900
+ const lit = valueToLiteral(result)
901
+ if (!lit || foldsIntoDirective(path, lit)) {
902
+ return
903
+ }
904
+ path.replaceWith(lit)
905
+ }
906
+
907
+ function foldPrototypeCall(
908
+ path: NodePath<t.CallExpression>,
909
+ receiver: unknown,
910
+ method: string,
911
+ ): void {
912
+ const args = tryEvaluateArgs(path.node.arguments, path)
913
+ if (args === null) {
914
+ return
915
+ }
916
+ let result: unknown
917
+ try {
918
+ const fn = (receiver as unknown as Record<string, unknown>)[method]
919
+ if (typeof fn !== 'function') {
920
+ return
921
+ }
922
+ result = (fn as (...a: unknown[]) => unknown).apply(receiver, args)
923
+ } catch {
924
+ return
925
+ }
926
+ const lit = valueToLiteral(result)
927
+ if (!lit || foldsIntoDirective(path, lit)) {
928
+ return
929
+ }
930
+ path.replaceWith(lit)
931
+ }
932
+
933
+ function tryEvaluateArgs(
934
+ args: t.CallExpression['arguments'],
935
+ path: NodePath<t.CallExpression>,
936
+ ): unknown[] | null {
937
+ const result: unknown[] = []
938
+ for (const arg of args) {
939
+ if (!t.isExpression(arg)) {
940
+ return null
941
+ }
942
+ const evaluated = evaluateConstant(arg, path.scope, path)
943
+ if (!evaluated.known || isOpaque(evaluated.value)) {
944
+ return null
945
+ }
946
+ result.push(evaluated.value)
947
+ }
948
+ return result
949
+ }