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,92 @@
1
+ import traverse from '@babel/traverse'
2
+ import type { Visitor } from '@babel/traverse'
3
+ import * as t from '@babel/types'
4
+ import type { File } from '@babel/types'
5
+
6
+ import { isInDirectivePrologue } from 'src/utils/ast'
7
+
8
+ /**
9
+ * Rewrites safe template literals as string concatenation. Tagged templates
10
+ * stay intact because the tag receives their quasi structure.
11
+ *
12
+ * Templates use the "string" ToPrimitive hint, while `x + ""` uses "default".
13
+ * Those hints can call object coercion methods in a different order, so only
14
+ * expressions known to produce primitives are expanded.
15
+ *
16
+ * An empty first quasi still matters. It makes `` `${5}` `` produce `"5"`
17
+ * instead of the number `5`.
18
+ *
19
+ * @example
20
+ * // ◀️ before
21
+ * var msg = `sum is ${a + b}`;
22
+ * var raw = `static text`;
23
+ *
24
+ * // ▶️ after
25
+ * var msg = "sum is " + (a + b);
26
+ * var raw = "static text";
27
+ */
28
+ export function expandTemplateLiterals(ast: File): void {
29
+ traverse(ast, visitor)
30
+ }
31
+
32
+ const visitor: Visitor = {
33
+ TemplateLiteral(path) {
34
+ const { parent } = path
35
+ if (t.isTaggedTemplateExpression(parent) && parent.quasi === path.node) {
36
+ return
37
+ }
38
+
39
+ const { quasis, expressions } = path.node
40
+
41
+ if (expressions.length === 0) {
42
+ // Rewriting a leading template as a string would create a directive.
43
+ if (t.isExpressionStatement(parent) && isInDirectivePrologue(path.parentPath!)) {
44
+ return
45
+ }
46
+ path.replaceWith(t.stringLiteral(quasiValue(quasis[0])))
47
+ return
48
+ }
49
+
50
+ for (const expr of expressions) {
51
+ if (!t.isExpression(expr) || !producesPrimitive(expr)) {
52
+ return
53
+ }
54
+ }
55
+
56
+ let result: t.Expression = t.stringLiteral(quasiValue(quasis[0]))
57
+
58
+ for (let i = 0; i < expressions.length; i++) {
59
+ const expr = expressions[i] as t.Expression
60
+ result = t.binaryExpression('+', result, expr)
61
+ const nextQuasi = quasis[i + 1]
62
+ if (nextQuasi) {
63
+ const value = quasiValue(nextQuasi)
64
+ if (value !== '') {
65
+ result = t.binaryExpression('+', result, t.stringLiteral(value))
66
+ }
67
+ }
68
+ }
69
+
70
+ path.replaceWith(result)
71
+ },
72
+ }
73
+
74
+ // These expression kinds always produce primitives, making the coercion hint
75
+ // irrelevant. Any kind that can produce an object is excluded.
76
+ function producesPrimitive(node: t.Expression): boolean {
77
+ return (
78
+ t.isStringLiteral(node) ||
79
+ t.isNumericLiteral(node) ||
80
+ t.isBooleanLiteral(node) ||
81
+ t.isNullLiteral(node) ||
82
+ t.isBigIntLiteral(node) ||
83
+ t.isTemplateLiteral(node) ||
84
+ t.isBinaryExpression(node) ||
85
+ t.isUnaryExpression(node) ||
86
+ t.isUpdateExpression(node)
87
+ )
88
+ }
89
+
90
+ function quasiValue(quasi: t.TemplateElement | undefined): string {
91
+ return quasi?.value.cooked ?? quasi?.value.raw ?? ''
92
+ }
@@ -0,0 +1,320 @@
1
+ import traverse from '@babel/traverse'
2
+ import type { NodePath } from '@babel/traverse'
3
+ import * as t from '@babel/types'
4
+ import type { File } from '@babel/types'
5
+
6
+ import { isInsideWith, isProtoKey } from 'src/utils/ast'
7
+ import { isPrimitiveLiteral } from 'src/utils/literal'
8
+
9
+ /**
10
+ * Moves safe instance fields into the constructor and safe static fields into
11
+ * static blocks. The whole class stays intact unless every field can be lowered.
12
+ *
13
+ * `Object.defineProperty` preserves CreateDataProperty semantics. A plain
14
+ * assignment could invoke an inherited setter or fail on an inherited read-only
15
+ * property. Static blocks also preserve the class binding and `this`.
16
+ *
17
+ * Accessor collisions, special keys, inferred names, constructor parameter
18
+ * scope, and relocated `this`, `super`, or `new.target` can all expose a rewrite.
19
+ * Private or computed fields, derived classes, `with`, and a shadowed `Object`
20
+ * are therefore refused.
21
+ *
22
+ * @example
23
+ * // ◀️ before
24
+ * class A {
25
+ * x = 1;
26
+ * static z = 2;
27
+ * }
28
+ *
29
+ * // ▶️ after
30
+ * class A {
31
+ * constructor() {
32
+ * Object.defineProperty(this, "x", {
33
+ * value: 1,
34
+ * writable: true,
35
+ * enumerable: true,
36
+ * configurable: true
37
+ * });
38
+ * }
39
+ * static {
40
+ * Object.defineProperty(this, "z", {
41
+ * value: 2,
42
+ * writable: true,
43
+ * enumerable: true,
44
+ * configurable: true
45
+ * });
46
+ * }
47
+ * }
48
+ */
49
+ export function rewriteClassFields(ast: File): void {
50
+ traverse(ast, visitor)
51
+ }
52
+
53
+ const FORBIDDEN_STATIC_KEYS = new Set([
54
+ 'length',
55
+ 'name',
56
+ 'prototype',
57
+ '__proto__',
58
+ 'caller',
59
+ 'arguments',
60
+ ])
61
+
62
+ const visitor = {
63
+ Class(path: NodePath<t.Class>) {
64
+ const memberPaths = path.get('body.body')
65
+ const instanceFields: t.ClassProperty[] = []
66
+ const staticFields: t.ClassProperty[] = []
67
+ let hasPrivateField = false
68
+ let hasComputedAccessor = false
69
+ const instanceAccessorKeys = new Set<string>()
70
+ const staticAccessorKeys = new Set<string>()
71
+ let ctorPath: NodePath<t.ClassMethod> | null = null
72
+
73
+ for (const memberPath of memberPaths) {
74
+ const member = memberPath.node
75
+ if (t.isClassProperty(member)) {
76
+ ;(member.static ? staticFields : instanceFields).push(member)
77
+ } else if (t.isClassPrivateProperty(member)) {
78
+ hasPrivateField = true
79
+ } else if (t.isClassMethod(member) && member.kind === 'constructor') {
80
+ ctorPath = memberPath as NodePath<t.ClassMethod>
81
+ } else if (t.isClassMethod(member) && (member.kind === 'get' || member.kind === 'set')) {
82
+ const name = plainKeyName(member.key)
83
+ if (member.computed || name === null) {
84
+ hasComputedAccessor = true
85
+ } else {
86
+ ;(member.static ? staticAccessorKeys : instanceAccessorKeys).add(name)
87
+ }
88
+ }
89
+ }
90
+
91
+ if (instanceFields.length === 0 && staticFields.length === 0) {
92
+ return
93
+ }
94
+ if (
95
+ !canDesugar(path, {
96
+ instanceFields,
97
+ staticFields,
98
+ hasPrivateField,
99
+ hasComputedAccessor,
100
+ instanceAccessorKeys,
101
+ staticAccessorKeys,
102
+ ctorPath,
103
+ })
104
+ ) {
105
+ return
106
+ }
107
+
108
+ // Path methods keep Babel's queued sibling paths aligned after each edit.
109
+ for (const memberPath of memberPaths) {
110
+ const member = memberPath.node
111
+ if (!t.isClassProperty(member)) {
112
+ continue
113
+ }
114
+ if (member.static) {
115
+ memberPath.replaceWith(t.staticBlock([defineField(t.thisExpression(), member)]))
116
+ } else {
117
+ memberPath.remove()
118
+ }
119
+ }
120
+
121
+ if (instanceFields.length > 0) {
122
+ const installs = instanceFields.map((field) => defineField(t.thisExpression(), field))
123
+ if (ctorPath) {
124
+ ctorPath.get('body').unshiftContainer('body', installs)
125
+ } else {
126
+ path
127
+ .get('body')
128
+ .unshiftContainer(
129
+ 'body',
130
+ t.classMethod(
131
+ 'constructor',
132
+ t.identifier('constructor'),
133
+ [],
134
+ t.blockStatement(installs),
135
+ ),
136
+ )
137
+ }
138
+ }
139
+ },
140
+ }
141
+
142
+ interface Collected {
143
+ instanceFields: t.ClassProperty[]
144
+ staticFields: t.ClassProperty[]
145
+ hasPrivateField: boolean
146
+ hasComputedAccessor: boolean
147
+ instanceAccessorKeys: Set<string>
148
+ staticAccessorKeys: Set<string>
149
+ ctorPath: NodePath<t.ClassMethod> | null
150
+ }
151
+
152
+ function canDesugar(path: NodePath<t.Class>, c: Collected): boolean {
153
+ if (c.hasPrivateField || c.hasComputedAccessor || path.node.superClass != null) {
154
+ return false
155
+ }
156
+ // `with` and outer bindings can redirect the synthesized `Object` reference.
157
+ if (isInsideWith(path) || objectIsShadowed(path, c.ctorPath)) {
158
+ return false
159
+ }
160
+ if (c.instanceFields.length > 0 && c.ctorPath && !hasSimpleParams(c.ctorPath.node)) {
161
+ return false
162
+ }
163
+ for (const field of c.instanceFields) {
164
+ if (!instanceFieldIsSafe(field, c.ctorPath != null, c.instanceAccessorKeys)) {
165
+ return false
166
+ }
167
+ }
168
+ for (const field of c.staticFields) {
169
+ if (!staticFieldIsSafe(field, c.staticAccessorKeys)) {
170
+ return false
171
+ }
172
+ }
173
+ return true
174
+ }
175
+
176
+ function objectIsShadowed(
177
+ path: NodePath<t.Class>,
178
+ ctorPath: NodePath<t.ClassMethod> | null,
179
+ ): boolean {
180
+ return (
181
+ path.scope.getBinding('Object') != null ||
182
+ (ctorPath != null && ctorPath.scope.getBinding('Object') != null)
183
+ )
184
+ }
185
+
186
+ // ECMA-262 10.2.2 initializes base-class fields before parameter defaults.
187
+ // Moving fields into the body reverses that order for non-simple parameters.
188
+ function hasSimpleParams(ctor: t.ClassMethod): boolean {
189
+ return ctor.params.every((param) => t.isIdentifier(param))
190
+ }
191
+
192
+ function instanceFieldIsSafe(
193
+ field: t.ClassProperty,
194
+ hasCtor: boolean,
195
+ accessorKeys: Set<string>,
196
+ ): boolean {
197
+ if (field.computed || !isPlainKey(field.key) || isProtoKey(field.key)) {
198
+ return false
199
+ }
200
+ if (shadowsAccessor(field.key, accessorKeys)) {
201
+ return false
202
+ }
203
+ if (field.value != null && isAnonymousFunctionDefinition(field.value)) {
204
+ return false
205
+ }
206
+ if (hasCtor) {
207
+ // Only a literal cannot capture a constructor parameter or local.
208
+ return field.value == null || isPrimitiveLiteral(field.value)
209
+ }
210
+ return field.value == null || !initObservesMethodContext(field.value, false)
211
+ }
212
+
213
+ function staticFieldIsSafe(field: t.ClassProperty, accessorKeys: Set<string>): boolean {
214
+ if (field.computed || !isPlainKey(field.key) || isForbiddenStaticKey(field.key)) {
215
+ return false
216
+ }
217
+ if (shadowsAccessor(field.key, accessorKeys)) {
218
+ return false
219
+ }
220
+ if (field.value == null) {
221
+ return true
222
+ }
223
+ if (isAnonymousFunctionDefinition(field.value)) {
224
+ return false
225
+ }
226
+ // Static blocks can change how these context-sensitive forms resolve.
227
+ return !containsPrivateName(field.value) && !initObservesMethodContext(field.value, true)
228
+ }
229
+
230
+ // CreateDataProperty ignores a same-named accessor. Lowering could reorder it.
231
+ function shadowsAccessor(key: t.Node, accessorKeys: Set<string>): boolean {
232
+ const name = plainKeyName(key)
233
+ return name !== null && accessorKeys.has(name)
234
+ }
235
+
236
+ function containsPrivateName(node: t.Node): boolean {
237
+ let found = false
238
+ t.traverseFast(node, (child) => {
239
+ if (t.isPrivateName(child)) {
240
+ found = true
241
+ }
242
+ return found ? t.traverseFast.stop : undefined
243
+ })
244
+ return found
245
+ }
246
+
247
+ // These descriptor flags match CreateDataProperty.
248
+ function defineField(target: t.Expression, field: t.ClassProperty): t.ExpressionStatement {
249
+ return t.expressionStatement(
250
+ t.callExpression(t.memberExpression(t.identifier('Object'), t.identifier('defineProperty')), [
251
+ target,
252
+ t.stringLiteral(plainKeyName(field.key)!),
253
+ t.objectExpression([
254
+ t.objectProperty(t.identifier('value'), fieldValue(field)),
255
+ t.objectProperty(t.identifier('writable'), t.booleanLiteral(true)),
256
+ t.objectProperty(t.identifier('enumerable'), t.booleanLiteral(true)),
257
+ t.objectProperty(t.identifier('configurable'), t.booleanLiteral(true)),
258
+ ]),
259
+ ]),
260
+ )
261
+ }
262
+
263
+ function fieldValue(field: t.ClassProperty): t.Expression {
264
+ return field.value ?? t.unaryExpression('void', t.numericLiteral(0))
265
+ }
266
+
267
+ // Numeric and BigInt keys both pass through ToPropertyKey, so `1` and `1n`
268
+ // name `"1"`.
269
+ function plainKeyName(key: t.Node): string | null {
270
+ if (t.isIdentifier(key)) {
271
+ return key.name
272
+ }
273
+ if (t.isStringLiteral(key)) {
274
+ return key.value
275
+ }
276
+ if (t.isNumericLiteral(key) || t.isBigIntLiteral(key)) {
277
+ return String(key.value)
278
+ }
279
+ return null
280
+ }
281
+
282
+ function isPlainKey(key: t.Node): boolean {
283
+ return plainKeyName(key) !== null
284
+ }
285
+
286
+ function isForbiddenStaticKey(key: t.Node): boolean {
287
+ if (t.isIdentifier(key) || t.isStringLiteral(key)) {
288
+ return FORBIDDEN_STATIC_KEYS.has(plainKeyName(key)!)
289
+ }
290
+ return false
291
+ }
292
+
293
+ // A descriptor would infer `"value"` instead of the field key as `.name`.
294
+ function isAnonymousFunctionDefinition(node: t.Node): boolean {
295
+ return (
296
+ t.isArrowFunctionExpression(node) ||
297
+ (t.isFunctionExpression(node) && node.id == null) ||
298
+ (t.isClassExpression(node) && node.id == null)
299
+ )
300
+ }
301
+
302
+ // Relocation changes `new.target`, `super`, and static `this`. Nested non-arrow
303
+ // functions rebind them and do not count.
304
+ function initObservesMethodContext(node: t.Node, forbidThis: boolean): boolean {
305
+ let found = false
306
+ t.traverseFast(node, (child) => {
307
+ if (t.isFunction(child) && !t.isArrowFunctionExpression(child)) {
308
+ return t.traverseFast.skip
309
+ }
310
+ if (
311
+ t.isSuper(child) ||
312
+ (t.isMetaProperty(child) && child.meta.name === 'new' && child.property.name === 'target') ||
313
+ (forbidThis && t.isThisExpression(child))
314
+ ) {
315
+ found = true
316
+ }
317
+ return found ? t.traverseFast.skip : undefined
318
+ })
319
+ return found
320
+ }
@@ -0,0 +1,40 @@
1
+ import traverse from '@babel/traverse'
2
+ import type { NodePath } from '@babel/traverse'
3
+ import * as t from '@babel/types'
4
+ import type { File } from '@babel/types'
5
+
6
+ import { isInDirectivePrologue } from 'src/utils/ast'
7
+
8
+ /**
9
+ * Splits comma-sequenced expression statements into one statement each.
10
+ *
11
+ * @example
12
+ * // ◀️ before
13
+ * a(), b(), c();
14
+ *
15
+ * // ▶️ after
16
+ * a();
17
+ * b();
18
+ * c();
19
+ */
20
+ export function splitSequenceExpressions(ast: File): void {
21
+ traverse(ast, visitor)
22
+ }
23
+
24
+ const visitor = {
25
+ ExpressionStatement(path: NodePath<t.ExpressionStatement>) {
26
+ if (!t.isSequenceExpression(path.node.expression)) {
27
+ return
28
+ }
29
+ if (typeof path.listKey !== 'string') {
30
+ return
31
+ }
32
+ // Splitting a leading string out of a sequence could create a directive.
33
+ const [first] = path.node.expression.expressions
34
+ if (t.isStringLiteral(first) && isInDirectivePrologue(path)) {
35
+ return
36
+ }
37
+ const statements = path.node.expression.expressions.map((expr) => t.expressionStatement(expr))
38
+ path.replaceWithMultiple(statements)
39
+ },
40
+ }
@@ -0,0 +1,39 @@
1
+ import traverse from '@babel/traverse'
2
+ import type { NodePath } from '@babel/traverse'
3
+ import * as t from '@babel/types'
4
+ import type { File } from '@babel/types'
5
+
6
+ /**
7
+ * Splits multi-declarator `var`/`let`/`const` into one declarator per statement,
8
+ * preserving the kind. Loop heads and exports stay intact because their parent
9
+ * slots do not accept several statements.
10
+ *
11
+ * @example
12
+ * // ◀️ before
13
+ * var a = 1, b = 2, c;
14
+ * let x = 1, y = 2;
15
+ *
16
+ * // ▶️ after
17
+ * var a = 1;
18
+ * var b = 2;
19
+ * var c;
20
+ * let x = 1;
21
+ * let y = 2;
22
+ */
23
+ export function splitVariableDeclarations(ast: File): void {
24
+ traverse(ast, visitor)
25
+ }
26
+
27
+ const visitor = {
28
+ VariableDeclaration(path: NodePath<t.VariableDeclaration>) {
29
+ if (typeof path.listKey !== 'string') {
30
+ return
31
+ }
32
+ if (path.node.declarations.length <= 1) {
33
+ return
34
+ }
35
+ const { kind } = path.node
36
+ const replacements = path.node.declarations.map((decl) => t.variableDeclaration(kind, [decl]))
37
+ path.replaceWithMultiple(replacements)
38
+ },
39
+ }
@@ -0,0 +1,107 @@
1
+ import traverse from '@babel/traverse'
2
+ import type { NodePath } from '@babel/traverse'
3
+ import * as t from '@babel/types'
4
+ import type { File } from '@babel/types'
5
+
6
+ import { isProtoKey } from 'src/utils/ast'
7
+
8
+ /**
9
+ * Rewrites property keys in object literals and classes as computed strings.
10
+ * Already-computed keys, private names, and class constructors stay unchanged.
11
+ * A method named `["constructor"]` is not a constructor and may be rewritten.
12
+ *
13
+ * The ECMA-262 B.3.1 `__proto__` setter form also stays unchanged.
14
+ * `{ __proto__: x }` sets `[[Prototype]]`, while `{ ["__proto__"]: x }` creates
15
+ * an own property.
16
+ *
17
+ * @example
18
+ * // ◀️ before
19
+ * const o = { foo: 1, bar() { return 2; }, [computed]: 3 };
20
+ * class A { m() {} static s() {} }
21
+ *
22
+ * // ▶️ after
23
+ * const o = { ["foo"]: 1, ["bar"]() { return 2; }, [computed]: 3 };
24
+ * class A { ["m"]() {} static ["s"]() {} }
25
+ */
26
+ export function stringifyObjectKeys(ast: File): void {
27
+ traverse(ast, visitor)
28
+ }
29
+
30
+ const visitor = {
31
+ ObjectProperty(path: NodePath<t.ObjectProperty>) {
32
+ if (isProtoSetter(path.node) || keyOrderIsSpreadSensitive(path)) {
33
+ return
34
+ }
35
+ if (stringifyKey(path.node)) {
36
+ path.node.shorthand = false
37
+ }
38
+ },
39
+ ObjectMethod(path: NodePath<t.ObjectMethod>) {
40
+ if (keyOrderIsSpreadSensitive(path)) {
41
+ return
42
+ }
43
+ stringifyKey(path.node)
44
+ },
45
+ ClassMethod(path: { node: t.ClassMethod }) {
46
+ if (path.node.kind === 'constructor') {
47
+ return
48
+ }
49
+ stringifyKey(path.node)
50
+ },
51
+ }
52
+
53
+ // With a spread present, V8 moves non-computed accessors after data properties
54
+ // but leaves computed accessors in place. Rewriting would reorder own keys.
55
+ function keyOrderIsSpreadSensitive(path: NodePath<t.ObjectProperty | t.ObjectMethod>): boolean {
56
+ const object = path.parentPath
57
+ if (!object || !object.isObjectExpression()) {
58
+ return false
59
+ }
60
+ const { properties } = object.node
61
+ const hasSpread = properties.some((prop) => t.isSpreadElement(prop))
62
+ if (!hasSpread) {
63
+ return false
64
+ }
65
+ return properties.some(
66
+ (prop) =>
67
+ t.isObjectMethod(prop) && (prop.kind === 'get' || prop.kind === 'set') && !prop.computed,
68
+ )
69
+ }
70
+
71
+ function isProtoSetter(node: t.ObjectProperty): boolean {
72
+ return !node.computed && !node.shorthand && isProtoKey(node.key)
73
+ }
74
+
75
+ function stringifyKey(node: t.ObjectProperty | t.ObjectMethod | t.ClassMethod): boolean {
76
+ if (node.computed) {
77
+ return false
78
+ }
79
+ if (t.isPrivateName(node.key)) {
80
+ return false
81
+ }
82
+ const stringKey = keyToString(node.key)
83
+ if (stringKey === null) {
84
+ return false
85
+ }
86
+ // Babel 8 narrows keyed nodes by `computed`, so both fields need widening.
87
+ const rewritten = node as t.ObjectProperty | t.ObjectMethod | t.ClassMethod
88
+ rewritten.key = t.stringLiteral(stringKey)
89
+ rewritten.computed = true
90
+ return true
91
+ }
92
+
93
+ function keyToString(key: t.Node): string | null {
94
+ if (t.isIdentifier(key)) {
95
+ return key.name
96
+ }
97
+ if (t.isStringLiteral(key)) {
98
+ return key.value
99
+ }
100
+ if (t.isNumericLiteral(key)) {
101
+ return String(key.value)
102
+ }
103
+ if (t.isBigIntLiteral(key)) {
104
+ return key.value.toString()
105
+ }
106
+ return null
107
+ }
@@ -0,0 +1,63 @@
1
+ import traverse from '@babel/traverse'
2
+ import * as t from '@babel/types'
3
+ import type { File } from '@babel/types'
4
+
5
+ type LoopNode =
6
+ | t.WhileStatement
7
+ | t.DoWhileStatement
8
+ | t.ForStatement
9
+ | t.ForInStatement
10
+ | t.ForOfStatement
11
+
12
+ /**
13
+ * Wraps single-statement bodies of control-flow constructs in BlockStatements,
14
+ * and converts concise arrow bodies into block bodies with an explicit `return`.
15
+ * Leaves an `IfStatement` alternate alone so `else if` chains survive.
16
+ *
17
+ * @example
18
+ * // ◀️ before
19
+ * if (x) log(1);
20
+ * else log(2);
21
+ * var fn = x => x + 1;
22
+ *
23
+ * // ▶️ after
24
+ * if (x) {
25
+ * log(1);
26
+ * } else {
27
+ * log(2);
28
+ * }
29
+ * var fn = x => {
30
+ * return x + 1;
31
+ * };
32
+ */
33
+ export function wrapSingleStatements(ast: File): void {
34
+ traverse(ast, visitor as Parameters<typeof traverse>[1])
35
+ }
36
+
37
+ const visitor = {
38
+ IfStatement(path: { node: t.IfStatement }) {
39
+ const { node } = path
40
+ if (!t.isBlockStatement(node.consequent)) {
41
+ node.consequent = t.blockStatement([node.consequent])
42
+ }
43
+ if (node.alternate && !t.isBlockStatement(node.alternate) && !t.isIfStatement(node.alternate)) {
44
+ node.alternate = t.blockStatement([node.alternate])
45
+ }
46
+ },
47
+ Loop(path: { node: LoopNode }) {
48
+ wrapLoopBody(path.node)
49
+ },
50
+ ArrowFunctionExpression(path: { node: t.ArrowFunctionExpression }) {
51
+ const { node } = path
52
+ if (!t.isBlockStatement(node.body)) {
53
+ node.body = t.blockStatement([t.returnStatement(node.body)])
54
+ node.expression = false
55
+ }
56
+ },
57
+ } as const
58
+
59
+ function wrapLoopBody(node: LoopNode): void {
60
+ if (!t.isBlockStatement(node.body)) {
61
+ node.body = t.blockStatement([node.body])
62
+ }
63
+ }