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,178 @@
1
+ import type { NodePath } from '@babel/traverse'
2
+ import * as t from '@babel/types'
3
+
4
+ import { isPure } from 'src/analysis/purity'
5
+
6
+ // Moving a gated node can change whether it runs.
7
+ export function isConditionalGate(parent: NodePath, cur: NodePath): boolean {
8
+ if (parent.isLogicalExpression() && parent.node.right === cur.node) {
9
+ return true
10
+ }
11
+ if (
12
+ parent.isConditionalExpression() &&
13
+ (parent.node.consequent === cur.node || parent.node.alternate === cur.node)
14
+ ) {
15
+ return true
16
+ }
17
+ if (parent.isOptionalMemberExpression()) {
18
+ // Computed keys run only after the optional base proves non-nullish.
19
+ if (parent.node.object === cur.node) {
20
+ return parent.node.optional
21
+ }
22
+ if (parent.node.computed && parent.node.property === cur.node) {
23
+ return true
24
+ }
25
+ }
26
+ if (parent.isOptionalCallExpression()) {
27
+ if (parent.node.callee === cur.node) {
28
+ return parent.node.optional
29
+ }
30
+ // Arguments run only once the callee proves non-nullish.
31
+ if (parent.node.arguments.some((arg) => arg === cur.node)) {
32
+ return true
33
+ }
34
+ }
35
+ return false
36
+ }
37
+
38
+ // Hoisting to statement position requires no gate or earlier observable work.
39
+ export function reorderableToStatement(path: NodePath, stmtPath: NodePath): boolean {
40
+ let cur: NodePath = path
41
+ let parent = cur.parentPath
42
+ while (parent && parent !== stmtPath) {
43
+ if (isConditionalGate(parent, cur)) {
44
+ return false
45
+ }
46
+ if (precedingEvaluationHasSideEffect(parent, cur)) {
47
+ return false
48
+ }
49
+ cur = parent
50
+ parent = cur.parentPath
51
+ }
52
+ return true
53
+ }
54
+
55
+ // Unknown parent shapes are unsafe. Known operands use `isPure` with their live
56
+ // scope and path.
57
+ function precedingEvaluationHasSideEffect(parentPath: NodePath, curPath: NodePath): boolean {
58
+ const parent = parentPath.node
59
+ const cur = curPath.node
60
+
61
+ if (t.isBinaryExpression(parent)) {
62
+ return parent.right === cur && impure([parentPath.get('left') as NodePath])
63
+ }
64
+ if (
65
+ t.isLogicalExpression(parent) ||
66
+ t.isUnaryExpression(parent) ||
67
+ t.isSpreadElement(parent) ||
68
+ t.isVariableDeclarator(parent)
69
+ ) {
70
+ return false
71
+ }
72
+ if (t.isVariableDeclaration(parent)) {
73
+ const declarators = precedingSiblings(parentPath, 'declarations', cur)
74
+ return impure(declarators.map((decl) => decl.get('init') as NodePath))
75
+ }
76
+ if (t.isAssignmentExpression(parent) && parent.right === cur) {
77
+ const target = parentPath.get('left') as NodePath
78
+ // Compound assignment reads its target before the right side. Plain `=`
79
+ // only resolves the write reference.
80
+ if (parent.operator === '=') {
81
+ return !writeReferenceIsPure(target)
82
+ }
83
+ return !compoundTargetReadIsPure(target)
84
+ }
85
+ if (t.isSequenceExpression(parent)) {
86
+ return impure(precedingSiblings(parentPath, 'expressions', cur))
87
+ }
88
+ if (
89
+ t.isCallExpression(parent) ||
90
+ t.isOptionalCallExpression(parent) ||
91
+ t.isNewExpression(parent)
92
+ ) {
93
+ const args = parentPath.get('arguments') as NodePath[]
94
+ const index = args.findIndex((arg) => arg.node === cur)
95
+ if (index === -1) {
96
+ return false
97
+ }
98
+ return impure([parentPath.get('callee') as NodePath, ...args.slice(0, index)])
99
+ }
100
+ if (t.isMemberExpression(parent) || t.isOptionalMemberExpression(parent)) {
101
+ return parent.property === cur && impure([parentPath.get('object') as NodePath])
102
+ }
103
+ if (t.isArrayExpression(parent)) {
104
+ return impure(precedingSiblings(parentPath, 'elements', cur))
105
+ }
106
+ if (t.isObjectProperty(parent)) {
107
+ return parent.value === cur && parent.computed && impure([parentPath.get('key') as NodePath])
108
+ }
109
+ if (t.isObjectExpression(parent)) {
110
+ return precedingSiblings(parentPath, 'properties', cur).some((prop) => !propertyIsPure(prop))
111
+ }
112
+ if (t.isTemplateLiteral(parent)) {
113
+ return impure(precedingSiblings(parentPath, 'expressions', cur))
114
+ }
115
+ if (t.isConditionalExpression(parent)) {
116
+ // Branches were caught as gates, leaving only the test here.
117
+ return false
118
+ }
119
+ return true
120
+ }
121
+
122
+ // List entries evaluated before `cur` are the ones a hoist must cross.
123
+ function precedingSiblings<T extends NodePath>(
124
+ parentPath: NodePath,
125
+ listKey: string,
126
+ cur: t.Node,
127
+ ): T[] {
128
+ const siblings = parentPath.get(listKey) as T[]
129
+ return siblings.slice(
130
+ 0,
131
+ siblings.findIndex((sibling) => sibling.node === cur),
132
+ )
133
+ }
134
+
135
+ function isPurePath(path: NodePath): boolean {
136
+ return isPure(path.node, path.scope, path)
137
+ }
138
+
139
+ function impure(paths: (NodePath | null | undefined)[]): boolean {
140
+ return paths.some((path) => path != null && path.node != null && !isPurePath(path))
141
+ }
142
+
143
+ // Member targets evaluate their base and computed key before the right side.
144
+ function writeReferenceIsPure(target: NodePath): boolean {
145
+ if (target.isIdentifier()) {
146
+ return true
147
+ }
148
+ if (target.isMemberExpression()) {
149
+ return (
150
+ isPurePath(target.get('object') as NodePath) &&
151
+ (!target.node.computed || isPurePath(target.get('property') as NodePath))
152
+ )
153
+ }
154
+ return false
155
+ }
156
+
157
+ // Compound assignment reads the target. Members and unresolved names may invoke
158
+ // accessors.
159
+ function compoundTargetReadIsPure(target: NodePath): boolean {
160
+ return target.isIdentifier() && isPurePath(target)
161
+ }
162
+
163
+ function propertyIsPure(propPath: NodePath): boolean {
164
+ const prop = propPath.node
165
+ if (t.isSpreadElement(prop)) {
166
+ return false
167
+ }
168
+ if (t.isObjectMethod(prop)) {
169
+ return !prop.computed || isPurePath(propPath.get('key') as NodePath)
170
+ }
171
+ if (t.isObjectProperty(prop)) {
172
+ if (prop.computed && !isPurePath(propPath.get('key') as NodePath)) {
173
+ return false
174
+ }
175
+ return t.isExpression(prop.value) && isPurePath(propPath.get('value') as NodePath)
176
+ }
177
+ return false
178
+ }
@@ -0,0 +1,96 @@
1
+ import * as t from '@babel/types'
2
+
3
+ const MAX_STRING_LITERAL_LENGTH = 4096
4
+
5
+ // RegExp literals allocate mutable objects and are not primitive values.
6
+ export function isPrimitiveLiteral(node: t.Node): boolean {
7
+ return (
8
+ t.isStringLiteral(node) ||
9
+ t.isNumericLiteral(node) ||
10
+ t.isBooleanLiteral(node) ||
11
+ t.isNullLiteral(node) ||
12
+ t.isBigIntLiteral(node)
13
+ )
14
+ }
15
+
16
+ // `+bigint` is the only literal-shaped unary form that throws.
17
+ export function isLiteralShaped(node: t.Node): boolean {
18
+ return literalShapeKind(node) !== null
19
+ }
20
+
21
+ // Unary minus and bitwise not preserve BigInt; unary plus throws on it.
22
+ function literalShapeKind(node: t.Node): 'bigint' | 'other' | null {
23
+ if (t.isBigIntLiteral(node)) {
24
+ return 'bigint'
25
+ }
26
+ if (isPrimitiveLiteral(node) || t.isRegExpLiteral(node)) {
27
+ return 'other'
28
+ }
29
+ if (t.isUnaryExpression(node)) {
30
+ const inner = literalShapeKind(node.argument)
31
+ if (inner === null) {
32
+ return null
33
+ }
34
+ if (node.operator === '!') {
35
+ return 'other'
36
+ }
37
+ if (node.operator === '+') {
38
+ return inner === 'bigint' ? null : 'other'
39
+ }
40
+ if (node.operator === '-' || node.operator === '~') {
41
+ return inner
42
+ }
43
+ }
44
+ return null
45
+ }
46
+
47
+ export function valueToLiteral(value: unknown): t.Expression | null {
48
+ if (typeof value === 'string') {
49
+ if (value.length > MAX_STRING_LITERAL_LENGTH) {
50
+ return null
51
+ }
52
+ return t.stringLiteral(value)
53
+ }
54
+ if (typeof value === 'boolean') {
55
+ return t.booleanLiteral(value)
56
+ }
57
+ if (value === null) {
58
+ return t.nullLiteral()
59
+ }
60
+ if (value === undefined) {
61
+ // `void 0` cannot be shadowed like the identifier `undefined`.
62
+ return t.unaryExpression('void', t.numericLiteral(0))
63
+ }
64
+ if (typeof value === 'bigint') {
65
+ // A UnaryExpression lets the generator parenthesize negative BigInt where
66
+ // precedence requires it.
67
+ if (value < 0n) {
68
+ return t.unaryExpression('-', t.bigIntLiteral(-value))
69
+ }
70
+ return t.bigIntLiteral(value)
71
+ }
72
+ if (typeof value === 'number') {
73
+ if (Number.isNaN(value) || !Number.isFinite(value)) {
74
+ return null
75
+ }
76
+ if (Object.is(value, -0)) {
77
+ return t.unaryExpression('-', t.numericLiteral(0))
78
+ }
79
+ if (value < 0) {
80
+ return t.unaryExpression('-', t.numericLiteral(-value))
81
+ }
82
+ return t.numericLiteral(value)
83
+ }
84
+ if (Array.isArray(value)) {
85
+ const elements: t.Expression[] = []
86
+ for (const item of value) {
87
+ const lit = valueToLiteral(item)
88
+ if (!lit) {
89
+ return null
90
+ }
91
+ elements.push(lit)
92
+ }
93
+ return t.arrayExpression(elements)
94
+ }
95
+ return null
96
+ }
@@ -0,0 +1,132 @@
1
+ import type { NodePath } from '@babel/traverse'
2
+ import * as t from '@babel/types'
3
+
4
+ import { isSideEffectFree } from 'src/analysis/purity'
5
+ import { isStandaloneDeclaration } from 'src/utils/paths'
6
+
7
+ // Lowers a short-circuit expression assigned to a bare identifier:
8
+ //
9
+ // x = L;
10
+ // if (<fallbackTest(x)>) x = R;
11
+ //
12
+ // `fallbackTest` selects `!x`, `x`, or `x == null`. The parent must accept a
13
+ // multi-statement replacement.
14
+ export function lowerLogicalWriteStatement(
15
+ path: NodePath<t.LogicalExpression>,
16
+ fallbackTest: (target: t.Identifier) => t.Expression,
17
+ ): boolean {
18
+ const { node } = path
19
+ const parent = path.parentPath
20
+ if (!parent) {
21
+ return false
22
+ }
23
+
24
+ if (
25
+ parent.isVariableDeclarator() &&
26
+ parent.node.init === node &&
27
+ t.isIdentifier(parent.node.id)
28
+ ) {
29
+ const declStmt = parent.parentPath
30
+ if (
31
+ !declStmt?.isVariableDeclaration() ||
32
+ // The split would emit illegal `const x;` and then write it.
33
+ declStmt.node.kind === 'const' ||
34
+ declStmt.node.declarations.length !== 1 ||
35
+ !isStandaloneDeclaration(declStmt)
36
+ ) {
37
+ return false
38
+ }
39
+ const target = parent.node.id
40
+ // The split writes `x` before evaluating the right side. Refuse any right
41
+ // side that could observe the new value or the ended TDZ.
42
+ if (rightCanObserveTarget(node.right, target.name)) {
43
+ return false
44
+ }
45
+ declStmt.replaceWithMultiple([
46
+ t.variableDeclaration(declStmt.node.kind, [
47
+ t.variableDeclarator(t.cloneNode(target), node.left),
48
+ ]),
49
+ guardedReassignment(target, fallbackTest, node.right),
50
+ ])
51
+ return true
52
+ }
53
+
54
+ if (
55
+ parent.isAssignmentExpression() &&
56
+ parent.node.operator === '=' &&
57
+ parent.node.right === node &&
58
+ t.isIdentifier(parent.node.left)
59
+ ) {
60
+ const exprStmt = parent.parentPath
61
+ if (!exprStmt?.isExpressionStatement() || exprStmt.node.expression !== parent.node) {
62
+ return false
63
+ }
64
+ const target = parent.node.left
65
+ // An unresolved name may be a global accessor. The split adds a getter read
66
+ // and can add a second setter call.
67
+ if (!path.scope.getBinding(target.name)) {
68
+ return false
69
+ }
70
+ // Writing first would change `x = 0 || x` before the right side reads `x`.
71
+ if (
72
+ !isSideEffectFree(node.right, path.scope, path) ||
73
+ referencesName(node.right, target.name)
74
+ ) {
75
+ return false
76
+ }
77
+ exprStmt.replaceWithMultiple([
78
+ t.expressionStatement(t.assignmentExpression('=', t.cloneNode(target), node.left)),
79
+ guardedReassignment(target, fallbackTest, node.right),
80
+ ])
81
+ return true
82
+ }
83
+
84
+ return false
85
+ }
86
+
87
+ // Calls, getters, and suspension can observe the target through code outside the
88
+ // expression, even when the target name does not appear directly.
89
+ function rightCanObserveTarget(rightNode: t.Node, targetName: string): boolean {
90
+ let observes = false
91
+ t.traverseFast(rightNode, (node) => {
92
+ if (
93
+ (t.isIdentifier(node) && node.name === targetName) ||
94
+ t.isCallExpression(node) ||
95
+ t.isOptionalCallExpression(node) ||
96
+ t.isNewExpression(node) ||
97
+ t.isTaggedTemplateExpression(node) ||
98
+ t.isMemberExpression(node) ||
99
+ t.isOptionalMemberExpression(node) ||
100
+ t.isAwaitExpression(node) ||
101
+ t.isYieldExpression(node)
102
+ ) {
103
+ observes = true
104
+ }
105
+ return observes ? t.traverseFast.stop : undefined
106
+ })
107
+ return observes
108
+ }
109
+
110
+ function referencesName(node: t.Node, name: string): boolean {
111
+ let found = false
112
+ t.traverseFast(node, (child) => {
113
+ if (!found && t.isIdentifier(child) && child.name === name) {
114
+ found = true
115
+ }
116
+ return found ? t.traverseFast.skip : undefined
117
+ })
118
+ return found
119
+ }
120
+
121
+ function guardedReassignment(
122
+ target: t.Identifier,
123
+ fallbackTest: (target: t.Identifier) => t.Expression,
124
+ value: t.Expression,
125
+ ): t.IfStatement {
126
+ return t.ifStatement(
127
+ fallbackTest(t.cloneNode(target)),
128
+ t.blockStatement([
129
+ t.expressionStatement(t.assignmentExpression('=', t.cloneNode(target), value)),
130
+ ]),
131
+ )
132
+ }
@@ -0,0 +1,109 @@
1
+ import type { NodePath, Visitor } from '@babel/traverse'
2
+ import * as t from '@babel/types'
3
+
4
+ /**
5
+ * Collects labels whose body is this loop. Their `continue` statements must be
6
+ * redirected after lowering. A hoisted initializer belongs outside the
7
+ * outermost label.
8
+ */
9
+ export function collectLoopLabels(path: NodePath<t.Statement>): {
10
+ outermost: NodePath<t.Statement>
11
+ ourLabels: Set<string>
12
+ } {
13
+ const ourLabels = new Set<string>()
14
+ let outermost: NodePath<t.Statement> = path
15
+ for (;;) {
16
+ const parent = outermost.parentPath
17
+ if (!parent?.isLabeledStatement() || parent.node.body !== outermost.node) {
18
+ break
19
+ }
20
+ outermost = parent
21
+ ourLabels.add(parent.node.label.name)
22
+ }
23
+ return { outermost, ourLabels }
24
+ }
25
+
26
+ /**
27
+ * Generates an inner-block label after reserving labels above and inside the
28
+ * loop. `scope.generateUid` alone can miss an enclosing user label.
29
+ */
30
+ export function generateLoopLabel(path: NodePath<t.Statement>, hint: string): string {
31
+ const taken = new Set<string>()
32
+ let ancestor: NodePath | null = path.parentPath
33
+ while (ancestor) {
34
+ if (ancestor.isLabeledStatement()) {
35
+ taken.add(ancestor.node.label.name)
36
+ }
37
+ ancestor = ancestor.parentPath
38
+ }
39
+ path.traverse({
40
+ LabeledStatement(inner) {
41
+ taken.add(inner.node.label.name)
42
+ },
43
+ })
44
+ // `generateUid` supplies its own counter, even after stripping hint digits.
45
+ let name = path.scope.generateUid(hint)
46
+ while (taken.has(name)) {
47
+ name = path.scope.generateUid(hint)
48
+ }
49
+ return name
50
+ }
51
+
52
+ /**
53
+ * Redirects this loop's `continue` statements to the lowered inner block.
54
+ * Nested loops, other labels, and nested functions stay untouched. The count
55
+ * lets callers omit an unused label.
56
+ */
57
+ export function redirectContinues(
58
+ rootPath: NodePath,
59
+ innerLabel: string,
60
+ ourLabels: Set<string>,
61
+ ): number {
62
+ const state: RedirectState = { count: 0, innerLabel, ourLabels, rootPath }
63
+ rootPath.traverse(redirectVisitor, state)
64
+ return state.count
65
+ }
66
+
67
+ interface RedirectState {
68
+ count: number
69
+ innerLabel: string
70
+ ourLabels: Set<string>
71
+ rootPath: NodePath
72
+ }
73
+
74
+ const redirectVisitor: Visitor<RedirectState> = {
75
+ Function(path) {
76
+ path.skip()
77
+ },
78
+ ContinueStatement(path, state) {
79
+ const { label } = path.node
80
+ if (!label) {
81
+ if (!hasInterposedLoop(path, state.rootPath)) {
82
+ path.replaceWith(t.breakStatement(t.identifier(state.innerLabel)))
83
+ state.count++
84
+ }
85
+ return
86
+ }
87
+ if (state.ourLabels.has(label.name)) {
88
+ path.replaceWith(t.breakStatement(t.identifier(state.innerLabel)))
89
+ state.count++
90
+ }
91
+ },
92
+ }
93
+
94
+ function hasInterposedLoop(path: NodePath, rootPath: NodePath): boolean {
95
+ let current: NodePath | null = path.parentPath
96
+ while (current && current !== rootPath) {
97
+ if (
98
+ current.isForStatement() ||
99
+ current.isForInStatement() ||
100
+ current.isForOfStatement() ||
101
+ current.isWhileStatement() ||
102
+ current.isDoWhileStatement()
103
+ ) {
104
+ return true
105
+ }
106
+ current = current.parentPath
107
+ }
108
+ return false
109
+ }
@@ -0,0 +1,103 @@
1
+ import type { NodePath } from '@babel/traverse'
2
+ import * as t from '@babel/types'
3
+
4
+ // Only statement-list positions accept several replacements. Loop heads do not.
5
+ export function isStandaloneDeclaration(declPath: NodePath<t.VariableDeclaration>): boolean {
6
+ const owner = declPath.parentPath
7
+ if (!owner) {
8
+ return false
9
+ }
10
+ if (owner.isForStatement() && owner.node.init === declPath.node) {
11
+ return false
12
+ }
13
+ if (owner.isForInStatement() && owner.node.left === declPath.node) {
14
+ return false
15
+ }
16
+ if (owner.isForOfStatement() && owner.node.left === declPath.node) {
17
+ return false
18
+ }
19
+ return owner.isBlock() || owner.isSwitchCase()
20
+ }
21
+
22
+ // Babel treats destructured writes as binding identifiers. Labels must still be
23
+ // excluded because they bind no variable.
24
+ export function referencesOrWritesVariable(path: NodePath<t.Identifier>): boolean {
25
+ if (path.parentPath?.isLabeledStatement()) {
26
+ return false
27
+ }
28
+ // Separate calls avoid an incorrect `never` narrowing across `||`.
29
+ const isRead = path.isReferencedIdentifier()
30
+ const isWrite = path.isBindingIdentifier()
31
+ return isRead || isWrite
32
+ }
33
+
34
+ // Replacing a callee or tag member with a bare value would lose its `this`.
35
+ export function isCalleeOrTagOf(path: NodePath): boolean {
36
+ const parent = path.parentPath
37
+ if (!parent) {
38
+ return false
39
+ }
40
+ if (
41
+ (parent.isCallExpression() || parent.isOptionalCallExpression()) &&
42
+ parent.node.callee === path.node
43
+ ) {
44
+ return true
45
+ }
46
+ return parent.isTaggedTemplateExpression() && parent.node.tag === path.node
47
+ }
48
+
49
+ // Strictness comes from modules, classes, or an enclosing directive prologue.
50
+ export function isInStrictContext(path: NodePath): boolean {
51
+ for (let current: NodePath | null = path; current; current = current.parentPath) {
52
+ if (current.isClass()) {
53
+ return true
54
+ }
55
+ if (current.isProgram()) {
56
+ return current.node.sourceType === 'module' || hasUseStrictDirective(current.node.directives)
57
+ }
58
+ if (
59
+ current.isFunction() &&
60
+ t.isBlockStatement(current.node.body) &&
61
+ hasUseStrictDirective(current.node.body.directives)
62
+ ) {
63
+ return true
64
+ }
65
+ }
66
+ return false
67
+ }
68
+
69
+ function hasUseStrictDirective(directives: readonly t.Directive[]): boolean {
70
+ return directives.some((directive) => directive.value.value === 'use strict')
71
+ }
72
+
73
+ // ECMA-262 B.3.3 gives sloppy block-level functions a `var` alias that Babel
74
+ // omits from the enclosing scope. Renaming, moving, or removing one can expose it.
75
+ export function hasAnnexBFunctionAlias(path: NodePath): boolean {
76
+ if (!path.isFunctionDeclaration()) {
77
+ return false
78
+ }
79
+ const parent = path.parentPath
80
+ if (!parent || parent.isProgram()) {
81
+ return false
82
+ }
83
+ if (
84
+ parent.isBlockStatement() &&
85
+ parent.key === 'body' &&
86
+ parent.parentPath != null &&
87
+ parent.parentPath.isFunction()
88
+ ) {
89
+ return false
90
+ }
91
+ return !isInStrictContext(parent)
92
+ }
93
+
94
+ // A later write to `const` must keep throwing after transformation.
95
+ export function hasConstantViolation(path: NodePath<t.VariableDeclaration>): boolean {
96
+ for (const name of Object.keys(path.getBindingIdentifiers())) {
97
+ const binding = path.scope.getBinding(name)
98
+ if (binding && binding.constantViolations.length > 0) {
99
+ return true
100
+ }
101
+ }
102
+ return false
103
+ }
@@ -0,0 +1,22 @@
1
+ // Mulberry32 is deterministic per seed and is not cryptographic.
2
+ export function mulberry32(seed: number): () => number {
3
+ let state = seed >>> 0
4
+ return () => {
5
+ state = (state + 0x6d_2b_79_f5) >>> 0
6
+ let t = state
7
+ t = Math.imul(t ^ (t >>> 15), t | 1)
8
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61)
9
+ return ((t ^ (t >>> 14)) >>> 0) / 4_294_967_296
10
+ }
11
+ }
12
+
13
+ // Fisher-Yates shuffle, in place.
14
+ export function shuffleInPlace<T>(items: T[], rng: () => number): T[] {
15
+ for (let index = items.length - 1; index > 0; index--) {
16
+ const pick = Math.floor(rng() * (index + 1))
17
+ const swapped = items[index]!
18
+ items[index] = items[pick]!
19
+ items[pick] = swapped
20
+ }
21
+ return items
22
+ }