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,404 @@
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 { initializerReaches } from 'src/analysis/document-order'
6
+ import { isInsideWith, isProtoKey } from 'src/utils/ast'
7
+ import { traverseForChanges } from 'src/utils/change-tracking'
8
+ import type { ChangeState } from 'src/utils/change-tracking'
9
+ import { isCalleeOrTagOf } from 'src/utils/paths'
10
+
11
+ /**
12
+ * Replaces a constant object or array wrapper with one binding per entry when
13
+ * every reference is a static-key read. The inliner can then see function
14
+ * values directly.
15
+ *
16
+ * @example
17
+ * // ◀️ before
18
+ * var ops = {
19
+ * double: function (x) { return x * 2; },
20
+ * negate: function (x) { return -x; },
21
+ * };
22
+ * log(ops.double(3), ops.negate(7));
23
+ *
24
+ * // ▶️ after
25
+ * var double = function (x) { return x * 2; };
26
+ * var negate = function (x) { return -x; };
27
+ * log(double(3), negate(7));
28
+ */
29
+ export function extractObjectProperties(ast: File): boolean {
30
+ return traverseForChanges(ast, extractVisitor)
31
+ }
32
+
33
+ const extractVisitor: Visitor<ChangeState> = {
34
+ VariableDeclaration: {
35
+ exit(path, state) {
36
+ if (tryExtractDeclaration(path)) {
37
+ state.changed = true
38
+ }
39
+ },
40
+ },
41
+ }
42
+
43
+ function tryExtractDeclaration(path: NodePath<t.VariableDeclaration>): boolean {
44
+ if (path.node.declarations.length !== 1) {
45
+ return false
46
+ }
47
+ // Babel hides lexical replacements for a `for` initializer inside an IIFE.
48
+ if (path.key === 'init' && path.parentPath?.isForStatement() && path.node.kind !== 'var') {
49
+ return false
50
+ }
51
+ const [declaratorPath] = path.get('declarations')
52
+ if (!declaratorPath) {
53
+ return false
54
+ }
55
+
56
+ const idPath = declaratorPath.get('id')
57
+ if (!idPath.isIdentifier()) {
58
+ return false
59
+ }
60
+ const idName = idPath.node.name
61
+
62
+ const initPath = declaratorPath.get('init')
63
+ if (!initPath.node) {
64
+ return false
65
+ }
66
+
67
+ if (initPath.isObjectExpression()) {
68
+ const entries = collectObjectEntries(initPath)
69
+ if (entries === null) {
70
+ return false
71
+ }
72
+ return performExtraction(path, idName, initPath, entries, false)
73
+ }
74
+ if (initPath.isArrayExpression()) {
75
+ const entries = collectArrayEntries(initPath)
76
+ if (entries === null) {
77
+ return false
78
+ }
79
+ return performExtraction(path, idName, initPath, entries, true)
80
+ }
81
+ return false
82
+ }
83
+
84
+ interface Entry {
85
+ key: string
86
+ value: t.Expression
87
+ // Bare calls are safe only for arrows or functions that never read `this`.
88
+ callSafe: boolean
89
+ }
90
+
91
+ function collectObjectEntries(initPath: NodePath<t.ObjectExpression>): Entry[] | null {
92
+ const entries: Entry[] = []
93
+ const seen = new Set<string>()
94
+ for (const propPath of initPath.get('properties')) {
95
+ if (!propPath.isObjectProperty()) {
96
+ // Object methods receive the literal as `this`.
97
+ return null
98
+ }
99
+ // The `__proto__` setter form creates no own property to extract.
100
+ if (isProtoSetter(propPath.node)) {
101
+ return null
102
+ }
103
+ const key = staticPropertyKey(propPath.node)
104
+ if (key === null) {
105
+ return null
106
+ }
107
+ if (seen.has(key)) {
108
+ return null
109
+ }
110
+ seen.add(key)
111
+
112
+ const valuePath = propPath.get('value')
113
+ if (Array.isArray(valuePath)) {
114
+ return null
115
+ }
116
+ if (!valuePath.isExpression()) {
117
+ return null
118
+ }
119
+ if (functionRefsThis(valuePath)) {
120
+ return null
121
+ }
122
+
123
+ entries.push({ key, value: valuePath.node, callSafe: isCallSafeValue(valuePath.node) })
124
+ }
125
+ return entries
126
+ }
127
+
128
+ function isProtoSetter(node: t.ObjectProperty): boolean {
129
+ return !node.computed && !node.shorthand && isProtoKey(node.key)
130
+ }
131
+
132
+ function isCallSafeValue(node: t.Expression): boolean {
133
+ // Surviving function values are receiver-independent.
134
+ return t.isArrowFunctionExpression(node) || t.isFunctionExpression(node)
135
+ }
136
+
137
+ function collectArrayEntries(initPath: NodePath<t.ArrayExpression>): Entry[] | null {
138
+ const entries: Entry[] = []
139
+ const elementPaths = initPath.get('elements')
140
+ for (let i = 0; i < elementPaths.length; i++) {
141
+ const elementPath = elementPaths[i]!
142
+ // An array hole has no value node to bind.
143
+ if (!elementPath.node) {
144
+ return null
145
+ }
146
+ if (elementPath.isSpreadElement()) {
147
+ return null
148
+ }
149
+ if (!elementPath.isExpression()) {
150
+ return null
151
+ }
152
+ if (functionRefsThis(elementPath)) {
153
+ return null
154
+ }
155
+ entries.push({
156
+ key: String(i),
157
+ value: elementPath.node,
158
+ callSafe: isCallSafeValue(elementPath.node),
159
+ })
160
+ }
161
+ return entries
162
+ }
163
+
164
+ function performExtraction(
165
+ path: NodePath<t.VariableDeclaration>,
166
+ idName: string,
167
+ initPath: NodePath<t.ObjectExpression | t.ArrayExpression>,
168
+ entries: Entry[],
169
+ isArray: boolean,
170
+ ): boolean {
171
+ const binding = path.scope.getBinding(idName)
172
+ if (!binding) {
173
+ return false
174
+ }
175
+ if (!binding.constant) {
176
+ return false
177
+ }
178
+
179
+ // `with` can shadow a synthesized entry binding.
180
+ if (isInsideWith(path)) {
181
+ return false
182
+ }
183
+
184
+ if (initContainsSelfReference(binding, initPath)) {
185
+ return false
186
+ }
187
+
188
+ const callSafeByKey = new Map(entries.map((entry) => [entry.key, entry.callSafe]))
189
+ const validKeys = new Set(entries.map((entry) => entry.key))
190
+
191
+ interface Replacement {
192
+ memberPath: NodePath<t.MemberExpression>
193
+ key: string
194
+ }
195
+ const replacements: Replacement[] = []
196
+ const seenMembers = new Set<t.MemberExpression>()
197
+
198
+ for (const ref of binding.referencePaths) {
199
+ // Before initialization, a wrapper read throws while a bare binding may not.
200
+ if (!initializerReaches(path, ref)) {
201
+ return false
202
+ }
203
+ // `with` can redirect the rewritten reference.
204
+ if (isInsideWith(ref)) {
205
+ return false
206
+ }
207
+
208
+ const parent = ref.parentPath
209
+ if (!parent || !parent.isMemberExpression()) {
210
+ return false
211
+ }
212
+ if (parent.node.object !== ref.node) {
213
+ return false
214
+ }
215
+
216
+ const key = staticMemberKey(parent.node)
217
+ if (key === null) {
218
+ return false
219
+ }
220
+ if (!validKeys.has(key)) {
221
+ return false
222
+ }
223
+
224
+ if (isWriteTargetMember(parent)) {
225
+ return false
226
+ }
227
+ // Extracting a method call drops its receiver.
228
+ if (isCalleeOrTagOf(parent) && !callSafeByKey.get(key)) {
229
+ return false
230
+ }
231
+
232
+ // Earlier extraction can publish the same member twice. Replace each node
233
+ // once to avoid a detached-path error.
234
+ if (seenMembers.has(parent.node)) {
235
+ continue
236
+ }
237
+ seenMembers.add(parent.node)
238
+
239
+ replacements.push({ memberPath: parent, key })
240
+ }
241
+
242
+ const { scope } = path
243
+ const usedNames = new Set<string>()
244
+ const keyToName = new Map<string, string>()
245
+ for (const { key } of entries) {
246
+ const baseName = isArray ? `${idName}${key}` : key
247
+ const baseNameIsFree =
248
+ t.isValidIdentifier(baseName) &&
249
+ !usedNames.has(baseName) &&
250
+ !scope.hasBinding(baseName) &&
251
+ !scope.hasGlobal(baseName) &&
252
+ !scope.hasReference(baseName)
253
+ const chosen = baseNameIsFree
254
+ ? baseName
255
+ : scope.generateUid(t.isValidIdentifier(baseName) ? baseName : 'property')
256
+ usedNames.add(chosen)
257
+ keyToName.set(key, chosen)
258
+ }
259
+
260
+ const newDecls = entries.map(({ key, value }) =>
261
+ t.variableDeclaration(path.node.kind, [
262
+ t.variableDeclarator(t.identifier(keyToName.get(key)!), value),
263
+ ]),
264
+ )
265
+
266
+ const newPaths = path.replaceWithMultiple(newDecls)
267
+ for (const newPath of newPaths) {
268
+ newPath.scope.registerDeclaration(newPath)
269
+ }
270
+
271
+ // Publish rewritten references for the next fixed-point iteration.
272
+ for (const { memberPath, key } of replacements) {
273
+ const name = keyToName.get(key)!
274
+ const [newRefPath] = memberPath.replaceWith(t.identifier(name))
275
+ if (newRefPath) {
276
+ const refBinding = newRefPath.scope.getBinding(name)
277
+ if (refBinding) {
278
+ refBinding.referencePaths.push(newRefPath)
279
+ refBinding.references++
280
+ }
281
+ }
282
+ }
283
+
284
+ // Remove the dead wrapper binding before the same name is visited again.
285
+ path.scope.removeBinding(idName)
286
+
287
+ return true
288
+ }
289
+
290
+ function staticPropertyKey(prop: t.ObjectProperty): string | null {
291
+ const { key } = prop
292
+ if (!prop.computed && t.isIdentifier(key)) {
293
+ return key.name
294
+ }
295
+ return literalKeyToString(key)
296
+ }
297
+
298
+ function staticMemberKey(node: t.MemberExpression): string | null {
299
+ if (!node.computed) {
300
+ if (t.isIdentifier(node.property)) {
301
+ return node.property.name
302
+ }
303
+ return null
304
+ }
305
+ return literalKeyToString(node.property)
306
+ }
307
+
308
+ function literalKeyToString(node: t.Node): string | null {
309
+ if (t.isStringLiteral(node)) {
310
+ return node.value
311
+ }
312
+ if (t.isNumericLiteral(node)) {
313
+ return String(node.value)
314
+ }
315
+ if (t.isBigIntLiteral(node)) {
316
+ return node.value.toString()
317
+ }
318
+ return null
319
+ }
320
+
321
+ // A member nested inside a destructuring target is still a write.
322
+ function isWriteTargetMember(memberPath: NodePath<t.MemberExpression>): boolean {
323
+ let current: NodePath = memberPath
324
+ let parent: NodePath | null = current.parentPath
325
+ while (parent) {
326
+ if (parent.isAssignmentExpression() && parent.node.left === current.node) {
327
+ return true
328
+ }
329
+ if (parent.isUpdateExpression() && parent.node.argument === current.node) {
330
+ return true
331
+ }
332
+ if (
333
+ parent.isUnaryExpression() &&
334
+ parent.node.operator === 'delete' &&
335
+ parent.node.argument === current.node
336
+ ) {
337
+ return true
338
+ }
339
+ if (
340
+ (parent.isForInStatement() || parent.isForOfStatement()) &&
341
+ parent.node.left === current.node
342
+ ) {
343
+ return true
344
+ }
345
+ if (
346
+ parent.isArrayPattern() ||
347
+ parent.isObjectPattern() ||
348
+ parent.isObjectProperty() ||
349
+ parent.isRestElement() ||
350
+ (parent.isAssignmentPattern() && parent.node.left === current.node)
351
+ ) {
352
+ current = parent
353
+ parent = current.parentPath
354
+ continue
355
+ }
356
+ break
357
+ }
358
+ return false
359
+ }
360
+
361
+ function functionRefsThis(path: NodePath): boolean {
362
+ const { node } = path
363
+ if (!t.isFunctionExpression(node) && !t.isFunctionDeclaration(node)) {
364
+ return false
365
+ }
366
+ const state: { found: boolean } = { found: false }
367
+ path.traverse(functionRefsThisVisitor, state)
368
+ return state.found
369
+ }
370
+
371
+ const functionRefsThisVisitor: Visitor<{ found: boolean }> = {
372
+ ThisExpression(_path, state) {
373
+ state.found = true
374
+ },
375
+ Function(path, state) {
376
+ if (path.isArrowFunctionExpression()) {
377
+ return
378
+ }
379
+ // Computed method keys use the enclosing `this`. The key node itself needs a
380
+ // separate check because traversal starts below it.
381
+ if ((path.isObjectMethod() || path.isClassMethod()) && path.node.computed) {
382
+ const keyPath = path.get('key') as NodePath
383
+ if (keyPath.isThisExpression()) {
384
+ state.found = true
385
+ } else {
386
+ keyPath.traverse(functionRefsThisVisitor, state)
387
+ }
388
+ }
389
+ path.skip()
390
+ },
391
+ }
392
+
393
+ function initContainsSelfReference(binding: Binding, initPath: NodePath): boolean {
394
+ for (const ref of binding.referencePaths) {
395
+ let ancestor: NodePath | null = ref
396
+ while (ancestor) {
397
+ if (ancestor === initPath) {
398
+ return true
399
+ }
400
+ ancestor = ancestor.parentPath
401
+ }
402
+ }
403
+ return false
404
+ }