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,336 @@
1
+ import traverse from '@babel/traverse'
2
+ import type { Binding, NodePath, Visitor } from '@babel/traverse'
3
+ import * as t from '@babel/types'
4
+ import type { File } from '@babel/types'
5
+
6
+ import { walkOwnFunctionScope } from 'src/utils/ast'
7
+ import { hasAnnexBFunctionAlias, referencesOrWritesVariable } from 'src/utils/paths'
8
+
9
+ export interface InlineCandidate {
10
+ name: string
11
+ fnNode: t.Function
12
+ fnPath: NodePath<t.Function>
13
+ binding: Binding
14
+ params: string[]
15
+ declarationPath: NodePath
16
+ }
17
+
18
+ // Tarjan SCC removal prevents endless expansion of mutually recursive bodies.
19
+ export function analyzeInlineability(ast: File): Map<string, InlineCandidate> {
20
+ const rawCandidates: InlineCandidate[] = []
21
+
22
+ // Earlier passes can leave `binding.referencePaths` on detached nodes.
23
+ traverse(ast, {
24
+ Program(path) {
25
+ path.scope.crawl()
26
+ },
27
+ FunctionDeclaration(path) {
28
+ if (!path.node.id) {
29
+ return
30
+ }
31
+ // Babel misses the sloppy Annex B `var` alias of a block-level function.
32
+ if (hasAnnexBFunctionAlias(path)) {
33
+ return
34
+ }
35
+ const binding = path.scope.getBinding(path.node.id.name)
36
+ if (!binding) {
37
+ return
38
+ }
39
+
40
+ const candidate = checkCandidate(path.node.id.name, binding, path.node, path, path)
41
+ if (candidate) {
42
+ rawCandidates.push(candidate)
43
+ }
44
+ },
45
+
46
+ VariableDeclarator(path) {
47
+ if (!t.isIdentifier(path.node.id)) {
48
+ return
49
+ }
50
+ const { init } = path.node
51
+ if (!t.isFunctionExpression(init)) {
52
+ return
53
+ }
54
+
55
+ const binding = path.scope.getBinding(path.node.id.name)
56
+ if (!binding) {
57
+ return
58
+ }
59
+
60
+ const declPath = path.parentPath
61
+ if (!declPath) {
62
+ return
63
+ }
64
+
65
+ const initPath = path.get('init') as NodePath<t.FunctionExpression>
66
+ const candidate = checkCandidate(path.node.id.name, binding, init, initPath, declPath)
67
+ if (candidate) {
68
+ rawCandidates.push(candidate)
69
+ }
70
+ },
71
+ })
72
+
73
+ const candidateByName = new Map<string, InlineCandidate>()
74
+ for (const candidate of rawCandidates) {
75
+ candidateByName.set(candidate.name, candidate)
76
+ }
77
+
78
+ // Self-reference is already excluded, leaving Tarjan to remove larger cycles.
79
+ const edges = new Map<string, Set<string>>()
80
+ for (const candidate of rawCandidates) {
81
+ const callees = new Set<string>()
82
+ walkOwnFunctionScope(candidate.fnNode.body, (node) => {
83
+ if (t.isIdentifier(node) && node.name !== candidate.name && candidateByName.has(node.name)) {
84
+ callees.add(node.name)
85
+ }
86
+ })
87
+ edges.set(candidate.name, callees)
88
+ }
89
+
90
+ const inCycle = new Set<string>()
91
+ for (const scc of tarjanSCC(
92
+ rawCandidates.map((candidate) => candidate.name),
93
+ edges,
94
+ )) {
95
+ if (scc.length > 1) {
96
+ for (const name of scc) {
97
+ inCycle.add(name)
98
+ }
99
+ }
100
+ }
101
+
102
+ for (const name of inCycle) {
103
+ candidateByName.delete(name)
104
+ }
105
+ return candidateByName
106
+ }
107
+
108
+ function checkCandidate(
109
+ name: string,
110
+ binding: Binding,
111
+ fnNode: t.Function,
112
+ fnPath: NodePath,
113
+ declarationPath: NodePath,
114
+ ): InlineCandidate | null {
115
+ if (fnNode.generator || fnNode.async) {
116
+ return null
117
+ }
118
+ if (!t.isBlockStatement(fnNode.body)) {
119
+ return null
120
+ }
121
+ // Writes live in `constantViolations`, not `referencePaths`. Any write can
122
+ // make a call resolve to a different function.
123
+ if (binding.constantViolations.length > 0) {
124
+ return null
125
+ }
126
+ // Splicing loses a named function expression's inner binding.
127
+ if (t.isFunctionExpression(fnNode) && fnNode.id) {
128
+ return null
129
+ }
130
+ // The flat renamer cannot preserve nested declarations after relocation.
131
+ if (bodyHasUnrelocatableDeclaration(fnNode.body)) {
132
+ return null
133
+ }
134
+ if (!validateCallSites(binding)) {
135
+ return null
136
+ }
137
+
138
+ const params: (string | null)[] = fnNode.params.map((param) =>
139
+ t.isIdentifier(param) ? param.name : null,
140
+ )
141
+ if (params.some((param) => param === null)) {
142
+ return null
143
+ }
144
+
145
+ const analysis = analyzeBody(name, fnPath as NodePath<t.Function>)
146
+ if (analysis.unsafe || analysis.closureCapture) {
147
+ return null
148
+ }
149
+
150
+ return {
151
+ name,
152
+ fnNode,
153
+ fnPath: fnPath as NodePath<t.Function>,
154
+ binding,
155
+ params: params as string[],
156
+ declarationPath,
157
+ }
158
+ }
159
+
160
+ function bodyHasUnrelocatableDeclaration(body: t.BlockStatement): boolean {
161
+ let found = false
162
+ t.traverseFast(body, (node) => {
163
+ if (found) {
164
+ return t.traverseFast.skip
165
+ }
166
+ // Function declarations hoist into the relocated scope. Expressions do not.
167
+ if (node !== body && t.isFunction(node)) {
168
+ if (t.isFunctionDeclaration(node)) {
169
+ found = true
170
+ }
171
+ return t.traverseFast.skip
172
+ }
173
+ if (
174
+ t.isClassDeclaration(node) ||
175
+ (t.isVariableDeclaration(node) && node.kind !== 'var') ||
176
+ (t.isVariableDeclarator(node) && !t.isIdentifier(node.id))
177
+ ) {
178
+ found = true
179
+ }
180
+ return found ? t.traverseFast.skip : undefined
181
+ })
182
+ return found
183
+ }
184
+
185
+ function validateCallSites(binding: Binding): boolean {
186
+ for (const ref of binding.referencePaths) {
187
+ const parent = ref.parentPath
188
+ if (!parent || !parent.isCallExpression() || parent.node.callee !== ref.node) {
189
+ return false
190
+ }
191
+ if (parent.node.arguments.some((arg) => t.isSpreadElement(arg))) {
192
+ return false
193
+ }
194
+ }
195
+ return true
196
+ }
197
+
198
+ interface BodyAnalysis {
199
+ unsafe: boolean
200
+ closureCapture: boolean
201
+ }
202
+
203
+ function analyzeBody(name: string, fnPath: NodePath<t.Function>): BodyAnalysis {
204
+ const state: AnalyzeBodyState = {
205
+ candidateName: name,
206
+ outerScope: fnPath.scope,
207
+ innerFnDepth: 0,
208
+ unsafe: false,
209
+ closureCapture: false,
210
+ }
211
+ // Start at the body to exclude the function id and parameter bindings.
212
+ const bodyPath = fnPath.get('body') as NodePath
213
+ bodyPath.traverse(analyzeBodyVisitor, state)
214
+
215
+ return { unsafe: state.unsafe, closureCapture: state.closureCapture }
216
+ }
217
+
218
+ interface AnalyzeBodyState {
219
+ candidateName: string
220
+ outerScope: NodePath<t.Function>['scope']
221
+ innerFnDepth: number
222
+ unsafe: boolean
223
+ closureCapture: boolean
224
+ }
225
+
226
+ const analyzeBodyVisitor: Visitor<AnalyzeBodyState> = {
227
+ Function: {
228
+ enter(path, state) {
229
+ if (state.unsafe || state.closureCapture) {
230
+ path.stop()
231
+ return
232
+ }
233
+ if (!path.isArrowFunctionExpression()) {
234
+ state.innerFnDepth++
235
+ }
236
+ },
237
+ exit(path, state) {
238
+ if (!path.isArrowFunctionExpression()) {
239
+ state.innerFnDepth--
240
+ }
241
+ },
242
+ },
243
+ ThisExpression(path, state) {
244
+ if (state.innerFnDepth === 0) {
245
+ state.unsafe = true
246
+ path.stop()
247
+ }
248
+ },
249
+ Super(path, state) {
250
+ if (state.innerFnDepth === 0) {
251
+ state.unsafe = true
252
+ path.stop()
253
+ }
254
+ },
255
+ MetaProperty(path, state) {
256
+ if (
257
+ state.innerFnDepth === 0 &&
258
+ path.node.meta.name === 'new' &&
259
+ path.node.property.name === 'target'
260
+ ) {
261
+ state.unsafe = true
262
+ path.stop()
263
+ }
264
+ },
265
+ WithStatement(path, state) {
266
+ state.unsafe = true
267
+ path.stop()
268
+ },
269
+ Identifier(path, state) {
270
+ const { name } = path.node
271
+ if (
272
+ state.innerFnDepth === 0 &&
273
+ (name === 'arguments' || name === 'eval' || name === state.candidateName)
274
+ ) {
275
+ state.unsafe = true
276
+ path.stop()
277
+ return
278
+ }
279
+ // Destructured writes are neither references nor direct assignment children.
280
+ if (!referencesOrWritesVariable(path)) {
281
+ return
282
+ }
283
+ if (state.innerFnDepth > 0 && state.outerScope.bindings[name]) {
284
+ const binding = path.scope.getBinding(name)
285
+ if (binding && state.outerScope.bindings[name] === binding) {
286
+ state.closureCapture = true
287
+ path.stop()
288
+ }
289
+ }
290
+ },
291
+ }
292
+
293
+ function tarjanSCC(nodes: string[], edges: Map<string, Set<string>>): string[][] {
294
+ let index = 0
295
+ const stack: string[] = []
296
+ const onStack = new Set<string>()
297
+ const indices = new Map<string, number>()
298
+ const lowlinks = new Map<string, number>()
299
+ const result: string[][] = []
300
+
301
+ function strongConnect(vertex: string): void {
302
+ indices.set(vertex, index)
303
+ lowlinks.set(vertex, index)
304
+ index++
305
+ stack.push(vertex)
306
+ onStack.add(vertex)
307
+
308
+ for (const successor of edges.get(vertex) ?? []) {
309
+ if (!indices.has(successor)) {
310
+ strongConnect(successor)
311
+ lowlinks.set(vertex, Math.min(lowlinks.get(vertex)!, lowlinks.get(successor)!))
312
+ } else if (onStack.has(successor)) {
313
+ lowlinks.set(vertex, Math.min(lowlinks.get(vertex)!, indices.get(successor)!))
314
+ }
315
+ }
316
+
317
+ if (lowlinks.get(vertex) === indices.get(vertex)) {
318
+ const scc: string[] = []
319
+ let popped: string
320
+ do {
321
+ popped = stack.pop()!
322
+ onStack.delete(popped)
323
+ scc.push(popped)
324
+ } while (popped !== vertex)
325
+ result.push(scc)
326
+ }
327
+ }
328
+
329
+ for (const vertex of nodes) {
330
+ if (!indices.has(vertex)) {
331
+ strongConnect(vertex)
332
+ }
333
+ }
334
+
335
+ return result
336
+ }
@@ -0,0 +1,315 @@
1
+ import type { Binding, NodePath, Scope } from '@babel/traverse'
2
+ import * as t from '@babel/types'
3
+
4
+ import {
5
+ declarationReaches,
6
+ initializerReaches,
7
+ readCrossesFunctionBoundary,
8
+ } from 'src/analysis/document-order'
9
+ import { isInsideWith } from 'src/utils/ast'
10
+ import { isPrimitiveLiteral } from 'src/utils/literal'
11
+
12
+ // Purity also requires deterministic repetition and no throw. Identifier reads
13
+ // need scope and reference paths to prove resolution, initialization, and the
14
+ // absence of `with` interception.
15
+ export function isPure(node: t.Node, scope?: Scope, referencePath?: NodePath): boolean {
16
+ if (
17
+ t.isNumericLiteral(node) ||
18
+ t.isStringLiteral(node) ||
19
+ t.isBooleanLiteral(node) ||
20
+ t.isNullLiteral(node) ||
21
+ t.isBigIntLiteral(node) ||
22
+ t.isRegExpLiteral(node) ||
23
+ t.isThisExpression(node)
24
+ ) {
25
+ return true
26
+ }
27
+ if (t.isIdentifier(node)) {
28
+ return identifierReadIsSafe(node, scope, referencePath)
29
+ }
30
+ if (t.isTemplateLiteral(node)) {
31
+ // ToString can call object code or throw on Symbol.
32
+ for (const expr of node.expressions) {
33
+ if (!t.isExpression(expr) || primitiveLiteralKind(expr) === null) {
34
+ return false
35
+ }
36
+ }
37
+ return true
38
+ }
39
+ if (t.isUnaryExpression(node)) {
40
+ // Numeric unary operators invoke ToPrimitive. `delete` and `throw` act.
41
+ if (node.operator === '!' || node.operator === 'typeof' || node.operator === 'void') {
42
+ return isPure(node.argument, scope, referencePath)
43
+ }
44
+ return false
45
+ }
46
+ if (t.isBinaryExpression(node) && node.operator !== 'in' && node.operator !== 'instanceof') {
47
+ if (!t.isExpression(node.left)) {
48
+ return false
49
+ }
50
+ if (!isPure(node.left, scope, referencePath) || !isPure(node.right, scope, referencePath)) {
51
+ return false
52
+ }
53
+ // Only strict equality avoids coercion. Other operators need safe literals.
54
+ if (node.operator === '===' || node.operator === '!==') {
55
+ return true
56
+ }
57
+ return coercesWithoutEffect(node.left, node.right, node.operator)
58
+ }
59
+ if (t.isLogicalExpression(node)) {
60
+ return isPure(node.left, scope, referencePath) && isPure(node.right, scope, referencePath)
61
+ }
62
+ if (t.isConditionalExpression(node)) {
63
+ return (
64
+ isPure(node.test, scope, referencePath) &&
65
+ isPure(node.consequent, scope, referencePath) &&
66
+ isPure(node.alternate, scope, referencePath)
67
+ )
68
+ }
69
+ if (t.isSequenceExpression(node)) {
70
+ for (const expr of node.expressions) {
71
+ if (!isPure(expr, scope, referencePath)) {
72
+ return false
73
+ }
74
+ }
75
+ return true
76
+ }
77
+ return false
78
+ }
79
+
80
+ // A droppable identifier read must resolve after binding initialization.
81
+ export function isSideEffectFree(node: t.Node, scope?: Scope, referencePath?: NodePath): boolean {
82
+ if (
83
+ t.isNumericLiteral(node) ||
84
+ t.isStringLiteral(node) ||
85
+ t.isBooleanLiteral(node) ||
86
+ t.isNullLiteral(node) ||
87
+ t.isBigIntLiteral(node) ||
88
+ t.isRegExpLiteral(node)
89
+ ) {
90
+ return true
91
+ }
92
+ if (t.isTemplateLiteral(node)) {
93
+ // ToString can call object code or throw on Symbol.
94
+ for (const expr of node.expressions) {
95
+ if (!t.isExpression(expr) || primitiveLiteralKind(expr) === null) {
96
+ return false
97
+ }
98
+ }
99
+ return true
100
+ }
101
+ if (t.isIdentifier(node)) {
102
+ return identifierReadIsSafe(node, scope, referencePath)
103
+ }
104
+ if (t.isFunctionExpression(node) || t.isArrowFunctionExpression(node)) {
105
+ return true
106
+ }
107
+ if (t.isClassExpression(node)) {
108
+ // `extends` and computed keys run during class definition.
109
+ if (node.superClass) {
110
+ if (!isSideEffectFree(node.superClass, scope, referencePath)) {
111
+ return false
112
+ }
113
+ // ClassDefinitionEvaluation accepts only `null` or a constructor.
114
+ if (!isConstructableSuperClass(node.superClass)) {
115
+ return false
116
+ }
117
+ }
118
+ for (const member of node.body.body) {
119
+ // Static fields and blocks run during class definition.
120
+ if ((t.isClassProperty(member) || t.isClassPrivateProperty(member)) && member.static) {
121
+ return false
122
+ }
123
+ if (t.isStaticBlock(member)) {
124
+ return false
125
+ }
126
+ if (
127
+ 'computed' in member &&
128
+ member.computed &&
129
+ 'key' in member &&
130
+ !isSideEffectFree(member.key, scope, referencePath)
131
+ ) {
132
+ return false
133
+ }
134
+ }
135
+ return true
136
+ }
137
+ if (t.isArrayExpression(node)) {
138
+ for (const element of node.elements) {
139
+ if (element === null) {
140
+ continue
141
+ }
142
+ if (t.isSpreadElement(element)) {
143
+ return false
144
+ }
145
+ if (!isSideEffectFree(element, scope, referencePath)) {
146
+ return false
147
+ }
148
+ }
149
+ return true
150
+ }
151
+ if (t.isObjectExpression(node)) {
152
+ for (const prop of node.properties) {
153
+ if (t.isSpreadElement(prop)) {
154
+ return false
155
+ }
156
+ if (t.isObjectMethod(prop)) {
157
+ if (prop.computed && !isSideEffectFree(prop.key, scope, referencePath)) {
158
+ return false
159
+ }
160
+ continue
161
+ }
162
+ if (!t.isObjectProperty(prop)) {
163
+ return false
164
+ }
165
+ if (prop.computed && !isSideEffectFree(prop.key, scope, referencePath)) {
166
+ return false
167
+ }
168
+ if (!t.isExpression(prop.value)) {
169
+ return false
170
+ }
171
+ if (!isSideEffectFree(prop.value, scope, referencePath)) {
172
+ return false
173
+ }
174
+ }
175
+ return true
176
+ }
177
+ if (t.isUnaryExpression(node)) {
178
+ if (node.operator === 'delete' || node.operator === 'throw') {
179
+ return false
180
+ }
181
+ if (node.operator === '!' || node.operator === 'typeof' || node.operator === 'void') {
182
+ return isSideEffectFree(node.argument, scope, referencePath)
183
+ }
184
+ return false
185
+ }
186
+ if (t.isBinaryExpression(node) && node.operator !== 'in' && node.operator !== 'instanceof') {
187
+ if (!t.isExpression(node.left)) {
188
+ return false
189
+ }
190
+ if (
191
+ !isSideEffectFree(node.left, scope, referencePath) ||
192
+ !isSideEffectFree(node.right, scope, referencePath)
193
+ ) {
194
+ return false
195
+ }
196
+ // Coercing operators need compatible literals that cannot throw.
197
+ if (node.operator === '===' || node.operator === '!==') {
198
+ return true
199
+ }
200
+ return coercesWithoutEffect(node.left, node.right, node.operator)
201
+ }
202
+ if (t.isLogicalExpression(node)) {
203
+ return (
204
+ isSideEffectFree(node.left, scope, referencePath) &&
205
+ isSideEffectFree(node.right, scope, referencePath)
206
+ )
207
+ }
208
+ if (t.isConditionalExpression(node)) {
209
+ return (
210
+ isSideEffectFree(node.test, scope, referencePath) &&
211
+ isSideEffectFree(node.consequent, scope, referencePath) &&
212
+ isSideEffectFree(node.alternate, scope, referencePath)
213
+ )
214
+ }
215
+ if (t.isSequenceExpression(node)) {
216
+ for (const expr of node.expressions) {
217
+ if (!isSideEffectFree(expr, scope, referencePath)) {
218
+ return false
219
+ }
220
+ }
221
+ return true
222
+ }
223
+ return false
224
+ }
225
+
226
+ // A safe read needs a resolved, initialized binding outside `with`. Unbound
227
+ // `undefined` is safe because the global property is immutable.
228
+ function identifierReadIsSafe(
229
+ node: t.Identifier,
230
+ scope: Scope | undefined,
231
+ referencePath: NodePath | undefined,
232
+ ): boolean {
233
+ // A `with` property can shadow any name, including `undefined`.
234
+ if (referencePath && isInsideWith(referencePath)) {
235
+ return false
236
+ }
237
+ const isUndefinedName = node.name === 'undefined'
238
+ if (!scope) {
239
+ return isUndefinedName
240
+ }
241
+ const binding = scope.getBinding(node.name)
242
+ if (!binding) {
243
+ return isUndefinedName
244
+ }
245
+ if (t.isFunctionDeclaration(binding.path.node) || binding.kind === 'module') {
246
+ return true
247
+ }
248
+ if (binding.kind === 'param') {
249
+ // Earlier parameter defaults can read later parameters in TDZ.
250
+ if (!referencePath || !readIsInOwnParamList(binding, referencePath)) {
251
+ return true
252
+ }
253
+ return declarationReaches(binding.path, referencePath)
254
+ }
255
+ // Lexical reads need an unconditionally reached declaration in the same call.
256
+ if (isLexicalBinding(binding)) {
257
+ if (referencePath && readCrossesFunctionBoundary(binding, referencePath)) {
258
+ return false
259
+ }
260
+ return initializerReaches(binding.path, referencePath)
261
+ }
262
+ return declarationReaches(binding.path, referencePath)
263
+ }
264
+
265
+ // Only `null` and plain function expressions are known-valid superclass values.
266
+ function isConstructableSuperClass(node: t.Node): boolean {
267
+ if (t.isNullLiteral(node)) {
268
+ return true
269
+ }
270
+ return t.isFunctionExpression(node) && !node.generator && !node.async
271
+ }
272
+
273
+ // Track BigInt separately because mixed arithmetic throws.
274
+ function primitiveLiteralKind(node: t.Node): 'bigint' | 'other' | null {
275
+ if (t.isBigIntLiteral(node)) {
276
+ return 'bigint'
277
+ }
278
+ return isPrimitiveLiteral(node) ? 'other' : null
279
+ }
280
+
281
+ // BigInt division, remainder, exponentiation, and unsigned shift can throw from
282
+ // values or operator support that the type alone cannot prove safe.
283
+ const THROWING_BIGINT_OPERATORS = new Set<string>(['/', '%', '**', '>>>'])
284
+
285
+ function coercesWithoutEffect(left: t.Node, right: t.Node, operator: string): boolean {
286
+ const leftKind = primitiveLiteralKind(left)
287
+ const rightKind = primitiveLiteralKind(right)
288
+ if (leftKind === null || rightKind === null) {
289
+ return false
290
+ }
291
+ // Mixed BigInt arithmetic throws.
292
+ if (leftKind !== rightKind) {
293
+ return false
294
+ }
295
+ return leftKind !== 'bigint' || !THROWING_BIGINT_OPERATORS.has(operator)
296
+ }
297
+
298
+ function isLexicalBinding(binding: Binding): boolean {
299
+ return (
300
+ binding.kind === 'let' || binding.kind === 'const' || t.isClassDeclaration(binding.path.node)
301
+ )
302
+ }
303
+
304
+ function readIsInOwnParamList(binding: Binding, referencePath: NodePath): boolean {
305
+ const owner = binding.scope.block
306
+ let current: NodePath | null = referencePath
307
+ while (current) {
308
+ const parent: NodePath | null = current.parentPath
309
+ if (parent && parent.node === owner && current.listKey === 'params') {
310
+ return true
311
+ }
312
+ current = parent
313
+ }
314
+ return false
315
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { obfuscate } from 'src/obfuscator'
2
+ export type { ManglePropertiesOptions, ObfuscatorOptions, PropertyNameGenerator } from 'src/options'