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
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "webfuscator",
3
+ "version": "0.9.4",
4
+ "description": "JavaScript obfuscator built on Babel",
5
+ "keywords": [
6
+ "babel",
7
+ "code-protection",
8
+ "javascript",
9
+ "mangler",
10
+ "minifier",
11
+ "obfuscation",
12
+ "obfuscator"
13
+ ],
14
+ "homepage": "https://github.com/esoware/webfuscator#readme",
15
+ "bugs": {
16
+ "url": "https://github.com/esoware/webfuscator/issues"
17
+ },
18
+ "license": "MIT",
19
+ "author": "Esoware",
20
+ "contentPolicy": {
21
+ "class": "dual-use"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/esoware/webfuscator.git"
26
+ },
27
+ "files": [
28
+ "DISCLOSURE",
29
+ "dist",
30
+ "src"
31
+ ],
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "type": "module",
36
+ "main": "dist/index.js",
37
+ "types": "dist/index.d.ts",
38
+ "exports": {
39
+ ".": {
40
+ "types": "./dist/index.d.ts",
41
+ "default": "./dist/index.js"
42
+ }
43
+ },
44
+ "scripts": {
45
+ "prepublishOnly": "pnpm check",
46
+ "prepare": "pnpm run build",
47
+ "build": "node --eval \"require('node:fs').rmSync('dist', { force: true, recursive: true })\" && tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
48
+ "lint": "oxlint src tests",
49
+ "lint:fix": "oxlint --fix src tests",
50
+ "format": "oxfmt \"src/**/*.ts\" \"tests/**/*.ts\"",
51
+ "format:check": "oxfmt --check \"src/**/*.ts\" \"tests/**/*.ts\"",
52
+ "typecheck": "tsc --noEmit",
53
+ "knip": "knip",
54
+ "test": "vitest run",
55
+ "check": "pnpm run typecheck && pnpm run lint && pnpm run format:check && pnpm run knip && pnpm run test"
56
+ },
57
+ "dependencies": {
58
+ "@babel/generator": "^8.0.0",
59
+ "@babel/parser": "^8.0.4",
60
+ "@babel/traverse": "^8.0.4",
61
+ "@babel/types": "^8.0.4"
62
+ },
63
+ "devDependencies": {
64
+ "@types/node": "^26.2.0",
65
+ "knip": "^6.32.2",
66
+ "oxfmt": "^0.64.0",
67
+ "oxlint": "^1.79.0",
68
+ "tsc-alias": "^1.9.2",
69
+ "typescript": "^7.0.2",
70
+ "vitest": "^4.1.11"
71
+ },
72
+ "engines": {
73
+ "node": "^22.18.0 || >=24.11.0"
74
+ },
75
+ "packageManager": "pnpm@11.22.0"
76
+ }
@@ -0,0 +1,310 @@
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 { isSideEffectFree } from 'src/analysis/purity'
10
+ import { enclosingScopeHasDirectEval, isInsideWith } from 'src/utils/ast'
11
+
12
+ export type Evaluation = { known: true; value: unknown } | { known: false }
13
+
14
+ const UNKNOWN: Evaluation = { known: false }
15
+
16
+ // Regexp, function, and class values are known only to be truthy. Identity,
17
+ // contents, coercion, and `typeof` remain unknown.
18
+ const OPAQUE_TRUTHY = Symbol('opaque-truthy')
19
+
20
+ export function isOpaque(value: unknown): boolean {
21
+ return value === OPAQUE_TRUTHY
22
+ }
23
+
24
+ interface Env {
25
+ scope: Scope | undefined
26
+ seen: Set<Binding>
27
+ // Use the binding path while recursing so later declarations still hit TDZ.
28
+ referencePath: NodePath | undefined
29
+ }
30
+
31
+ type Evaluator = (node: t.Node, env: Env) => Evaluation
32
+
33
+ const EVALUATORS: Partial<Record<t.Node['type'], Evaluator>> = {
34
+ NumericLiteral: (node) => ({ known: true, value: (node as t.NumericLiteral).value }),
35
+ StringLiteral: (node) => ({ known: true, value: (node as t.StringLiteral).value }),
36
+ BooleanLiteral: (node) => ({ known: true, value: (node as t.BooleanLiteral).value }),
37
+ NullLiteral: () => ({ known: true, value: null }),
38
+ BigIntLiteral: (node) => {
39
+ try {
40
+ return { known: true, value: BigInt((node as t.BigIntLiteral).value) }
41
+ } catch {
42
+ return UNKNOWN
43
+ }
44
+ },
45
+ RegExpLiteral: () => ({ known: true, value: OPAQUE_TRUTHY }),
46
+ TemplateLiteral: (node) => {
47
+ const template = node as t.TemplateLiteral
48
+ if (template.expressions.length > 0) {
49
+ return UNKNOWN
50
+ }
51
+ const [quasi] = template.quasis
52
+ return { known: true, value: quasi?.value.cooked ?? quasi?.value.raw ?? '' }
53
+ },
54
+ UnaryExpression: evaluateUnary,
55
+ BinaryExpression: evaluateBinary,
56
+ LogicalExpression: evaluateLogical,
57
+ ConditionalExpression: evaluateConditional,
58
+ Identifier: evaluateIdentifier,
59
+ }
60
+
61
+ export function evaluateConstant(
62
+ node: t.Node,
63
+ scope?: Scope,
64
+ referencePath?: NodePath,
65
+ ): Evaluation {
66
+ return run(node, { scope, seen: new Set(), referencePath })
67
+ }
68
+
69
+ function run(node: t.Node, env: Env): Evaluation {
70
+ const evaluator = EVALUATORS[node.type]
71
+ return evaluator ? evaluator(node, env) : UNKNOWN
72
+ }
73
+
74
+ function evaluateUnary(node: t.Node, env: Env): Evaluation {
75
+ const unary = node as t.UnaryExpression
76
+ if (unary.operator === 'void') {
77
+ return isSideEffectFree(unary.argument, env.scope, env.referencePath)
78
+ ? { known: true, value: undefined }
79
+ : UNKNOWN
80
+ }
81
+ if (unary.operator === 'delete' || unary.operator === 'throw') {
82
+ return UNKNOWN
83
+ }
84
+ const argument = run(unary.argument, env)
85
+ if (!argument.known) {
86
+ return UNKNOWN
87
+ }
88
+ return applyUnary(unary.operator, argument.value)
89
+ }
90
+
91
+ function evaluateBinary(node: t.Node, env: Env): Evaluation {
92
+ const binary = node as t.BinaryExpression
93
+ if (binary.operator === 'in' || binary.operator === 'instanceof') {
94
+ return UNKNOWN
95
+ }
96
+ if (!t.isExpression(binary.left)) {
97
+ return UNKNOWN
98
+ }
99
+ const left = run(binary.left, env)
100
+ if (!left.known) {
101
+ return UNKNOWN
102
+ }
103
+ const right = run(binary.right, env)
104
+ if (!right.known) {
105
+ return UNKNOWN
106
+ }
107
+ return applyBinary(binary.operator, left.value, right.value)
108
+ }
109
+
110
+ function evaluateLogical(node: t.Node, env: Env): Evaluation {
111
+ const logical = node as t.LogicalExpression
112
+ const left = run(logical.left, env)
113
+ if (!left.known) {
114
+ return UNKNOWN
115
+ }
116
+ if (logical.operator === '&&') {
117
+ return left.value ? run(logical.right, env) : left
118
+ }
119
+ if (logical.operator === '||') {
120
+ return left.value ? left : run(logical.right, env)
121
+ }
122
+ if (logical.operator === '??') {
123
+ return left.value === null || left.value === undefined ? run(logical.right, env) : left
124
+ }
125
+ return UNKNOWN
126
+ }
127
+
128
+ function evaluateConditional(node: t.Node, env: Env): Evaluation {
129
+ const conditional = node as t.ConditionalExpression
130
+ const test = run(conditional.test, env)
131
+ if (!test.known) {
132
+ return UNKNOWN
133
+ }
134
+ return run(test.value ? conditional.consequent : conditional.alternate, env)
135
+ }
136
+
137
+ function evaluateIdentifier(node: t.Node, env: Env): Evaluation {
138
+ const identifier = node as t.Identifier
139
+
140
+ if (!env.scope) {
141
+ return UNKNOWN
142
+ }
143
+
144
+ // `with` and direct `eval` can redirect names outside Babel's scope table.
145
+ if (
146
+ env.referencePath &&
147
+ (isInsideWith(env.referencePath) || enclosingScopeHasDirectEval(env.referencePath))
148
+ ) {
149
+ return UNKNOWN
150
+ }
151
+ const binding = env.scope.getBinding(identifier.name)
152
+
153
+ // Only an unbound `undefined` names the immutable global property.
154
+ if (identifier.name === 'undefined') {
155
+ return binding ? UNKNOWN : { known: true, value: undefined }
156
+ }
157
+
158
+ if (!binding || env.seen.has(binding)) {
159
+ return UNKNOWN
160
+ }
161
+
162
+ if (t.isFunctionDeclaration(binding.path.node)) {
163
+ return binding.constant ? { known: true, value: OPAQUE_TRUTHY } : UNKNOWN
164
+ }
165
+ if (t.isClassDeclaration(binding.path.node)) {
166
+ if (!binding.constant) {
167
+ return UNKNOWN
168
+ }
169
+ // A nested function can read the class before its declaration runs.
170
+ if (readCrossesFunctionBoundary(binding, env.referencePath)) {
171
+ return UNKNOWN
172
+ }
173
+ return declarationReaches(binding.path, env.referencePath)
174
+ ? { known: true, value: OPAQUE_TRUTHY }
175
+ : UNKNOWN
176
+ }
177
+
178
+ if (binding.kind === 'var' || binding.kind === 'let' || binding.kind === 'const') {
179
+ const declarator = binding.path.node
180
+ if (!t.isVariableDeclarator(declarator) || !declarator.init) {
181
+ return UNKNOWN
182
+ }
183
+ if (!t.isIdentifier(declarator.id) || declarator.id.name !== identifier.name) {
184
+ return UNKNOWN
185
+ }
186
+ // A nested function can read the binding before its initializer runs.
187
+ if (readCrossesFunctionBoundary(binding, env.referencePath)) {
188
+ return UNKNOWN
189
+ }
190
+ // A preceding `var` initializer may still sit on an untaken branch.
191
+ if (!initializerReaches(binding.path, env.referencePath)) {
192
+ return UNKNOWN
193
+ }
194
+ // Babel counts a looped `var` initializer as its own constant violation.
195
+ if (
196
+ !binding.constant &&
197
+ !(
198
+ binding.constantViolations.length === 1 &&
199
+ binding.constantViolations[0]!.node === declarator
200
+ )
201
+ ) {
202
+ return UNKNOWN
203
+ }
204
+ return run(declarator.init, {
205
+ scope: binding.scope,
206
+ seen: new Set([...env.seen, binding]),
207
+ referencePath: binding.path,
208
+ })
209
+ }
210
+
211
+ return UNKNOWN
212
+ }
213
+
214
+ function applyUnary(op: t.UnaryExpression['operator'], value: unknown): Evaluation {
215
+ if (isOpaque(value)) {
216
+ // Only truthiness is known for an opaque object.
217
+ return op === '!' ? { known: true, value: false } : UNKNOWN
218
+ }
219
+ // `+bigint` throws TypeError; folding it would erase the throw.
220
+ if (op === '+' && typeof value === 'bigint') {
221
+ return UNKNOWN
222
+ }
223
+ try {
224
+ switch (op) {
225
+ case '!':
226
+ return { known: true, value: !value }
227
+ case '+':
228
+ return { known: true, value: Number(value as number) }
229
+ case '-':
230
+ return { known: true, value: -(value as number) }
231
+ case '~':
232
+ return { known: true, value: ~(value as number) }
233
+ case 'typeof':
234
+ return { known: true, value: typeof value }
235
+ }
236
+ } catch {
237
+ return UNKNOWN
238
+ }
239
+ return UNKNOWN
240
+ }
241
+
242
+ function applyBinary(
243
+ op: t.BinaryExpression['operator'],
244
+ left: unknown,
245
+ right: unknown,
246
+ ): Evaluation {
247
+ // Binary operators observe more than an opaque object's truthiness.
248
+ if (isOpaque(left) || isOpaque(right)) {
249
+ return UNKNOWN
250
+ }
251
+ if (op === '===') {
252
+ return { known: true, value: left === right }
253
+ }
254
+ if (op === '!==') {
255
+ return { known: true, value: left !== right }
256
+ }
257
+ if (op === '==') {
258
+ return { known: true, value: looseEquals(left, right) }
259
+ }
260
+ if (op === '!=') {
261
+ return { known: true, value: !looseEquals(left, right) }
262
+ }
263
+
264
+ const a = left as number
265
+ const b = right as number
266
+ try {
267
+ switch (op) {
268
+ case '+':
269
+ return { known: true, value: a + b }
270
+ case '-':
271
+ return { known: true, value: a - b }
272
+ case '*':
273
+ return { known: true, value: a * b }
274
+ case '/':
275
+ return { known: true, value: a / b }
276
+ case '%':
277
+ return { known: true, value: a % b }
278
+ case '**':
279
+ return { known: true, value: a ** b }
280
+ case '&':
281
+ return { known: true, value: a & b }
282
+ case '|':
283
+ return { known: true, value: a | b }
284
+ case '^':
285
+ return { known: true, value: a ^ b }
286
+ case '<<':
287
+ return { known: true, value: a << b }
288
+ case '>>':
289
+ return { known: true, value: a >> b }
290
+ case '>>>':
291
+ return { known: true, value: a >>> b }
292
+ case '<':
293
+ return { known: true, value: a < b }
294
+ case '<=':
295
+ return { known: true, value: a <= b }
296
+ case '>':
297
+ return { known: true, value: a > b }
298
+ case '>=':
299
+ return { known: true, value: a >= b }
300
+ }
301
+ } catch {
302
+ return UNKNOWN
303
+ }
304
+ return UNKNOWN
305
+ }
306
+
307
+ function looseEquals(left: unknown, right: unknown): boolean {
308
+ // oxlint-disable-next-line eqeqeq
309
+ return (left as object) == (right as object)
310
+ }
@@ -0,0 +1,141 @@
1
+ import type { Binding, NodePath } from '@babel/traverse'
2
+ import * as t from '@babel/types'
3
+
4
+ // AST ancestry gives cloned and synthesized nodes an order when offsets cannot.
5
+ export function declarationReaches(
6
+ bindingPath: NodePath,
7
+ referencePath: NodePath | undefined,
8
+ ): boolean {
9
+ if (!referencePath) {
10
+ return true
11
+ }
12
+ if (!bothPathsOrdered(bindingPath, referencePath)) {
13
+ return false
14
+ }
15
+ return precedesInAncestry(bindingPath, ancestry(referencePath))
16
+ }
17
+
18
+ // Detached paths and a path compared with itself prove no useful ordering.
19
+ function bothPathsOrdered(bindingPath: NodePath, referencePath: NodePath): boolean {
20
+ return bindingPath !== referencePath && bindingPath.node != null && referencePath.node != null
21
+ }
22
+
23
+ function precedesInAncestry(bindingPath: NodePath, referenceAncestry: NodePath[]): boolean {
24
+ const bindingAncestry = ancestry(bindingPath)
25
+ let i = bindingAncestry.length - 1
26
+ let j = referenceAncestry.length - 1
27
+ while (i >= 0 && j >= 0 && bindingAncestry[i] === referenceAncestry[j]) {
28
+ i--
29
+ j--
30
+ }
31
+ if (i < 0) {
32
+ return true
33
+ }
34
+ if (j < 0) {
35
+ return false
36
+ }
37
+ return siblingPrecedes(bindingAncestry[i]!, referenceAncestry[j]!)
38
+ }
39
+
40
+ // A declaration inside one of these statements may not run before an outside
41
+ // reference, even when it appears first.
42
+ const CONDITIONAL_CONTAINERS = new Set<t.Node['type']>([
43
+ 'IfStatement',
44
+ 'SwitchStatement',
45
+ 'SwitchCase',
46
+ 'ForStatement',
47
+ 'ForInStatement',
48
+ 'ForOfStatement',
49
+ 'WhileStatement',
50
+ 'DoWhileStatement',
51
+ 'TryStatement',
52
+ 'ConditionalExpression',
53
+ 'LogicalExpression',
54
+ // A labeled block can exit before a later declaration.
55
+ 'LabeledStatement',
56
+ ])
57
+
58
+ // No branch may separate the initializer from the reference while walking to
59
+ // their nearest shared ancestor.
60
+ export function initializerReaches(
61
+ bindingPath: NodePath,
62
+ referencePath: NodePath | undefined,
63
+ ): boolean {
64
+ if (!referencePath) {
65
+ return true
66
+ }
67
+ if (!bothPathsOrdered(bindingPath, referencePath)) {
68
+ return false
69
+ }
70
+ const referenceAncestry = ancestry(referencePath)
71
+ if (!precedesInAncestry(bindingPath, referenceAncestry)) {
72
+ return false
73
+ }
74
+ const referenceAncestors = new Set<NodePath>(referenceAncestry)
75
+ let current: NodePath | null = bindingPath.parentPath
76
+ while (current && !referenceAncestors.has(current)) {
77
+ // A detached ancestor invalidates the remaining path chain.
78
+ if (!current.node) {
79
+ return false
80
+ }
81
+ if (CONDITIONAL_CONTAINERS.has(current.node.type)) {
82
+ return false
83
+ }
84
+ current = current.parentPath
85
+ }
86
+ return true
87
+ }
88
+
89
+ // A nested function may run before the declaration, defeating document order.
90
+ export function readCrossesFunctionBoundary(
91
+ binding: Binding,
92
+ referencePath: NodePath | undefined,
93
+ ): boolean {
94
+ if (!referencePath) {
95
+ return false
96
+ }
97
+ const bindingBlock = binding.scope.block
98
+ let current: NodePath | null = referencePath.parentPath
99
+ while (current) {
100
+ if (current.node === bindingBlock) {
101
+ return false
102
+ }
103
+ if (current.isFunction()) {
104
+ return true
105
+ }
106
+ current = current.parentPath
107
+ }
108
+ return false
109
+ }
110
+
111
+ function ancestry(path: NodePath): NodePath[] {
112
+ const chain: NodePath[] = []
113
+ let current: NodePath | null = path
114
+ while (current) {
115
+ chain.push(current)
116
+ current = current.parentPath
117
+ }
118
+ return chain
119
+ }
120
+
121
+ function siblingPrecedes(left: NodePath, right: NodePath): boolean {
122
+ if (left.listKey && left.listKey === right.listKey) {
123
+ return (left.key as number) < (right.key as number)
124
+ }
125
+ const parent = left.parentPath?.node
126
+ if (!parent) {
127
+ return false
128
+ }
129
+ const slotLeft = (left.listKey ?? left.key) as string
130
+ const slotRight = (right.listKey ?? right.key) as string
131
+ const keys = (t.VISITOR_KEYS as Record<string, string[]>)[parent.type]
132
+ if (!keys) {
133
+ return false
134
+ }
135
+ const indexLeft = keys.indexOf(slotLeft)
136
+ const indexRight = keys.indexOf(slotRight)
137
+ if (indexLeft === -1 || indexRight === -1) {
138
+ return false
139
+ }
140
+ return indexLeft < indexRight
141
+ }