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.
- package/DISCLOSURE +9 -0
- package/LICENSE +21 -0
- package/README.md +83 -0
- package/dist/analysis/constant.d.ts +11 -0
- package/dist/analysis/constant.d.ts.map +1 -0
- package/dist/analysis/constant.js +261 -0
- package/dist/analysis/constant.js.map +1 -0
- package/dist/analysis/document-order.d.ts +5 -0
- package/dist/analysis/document-order.d.ts.map +1 -0
- package/dist/analysis/document-order.js +124 -0
- package/dist/analysis/document-order.js.map +1 -0
- package/dist/analysis/inlineability.d.ts +13 -0
- package/dist/analysis/inlineability.d.ts.map +1 -0
- package/dist/analysis/inlineability.js +270 -0
- package/dist/analysis/inlineability.js.map +1 -0
- package/dist/analysis/purity.d.ts +5 -0
- package/dist/analysis/purity.d.ts.map +1 -0
- package/dist/analysis/purity.js +281 -0
- package/dist/analysis/purity.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/obfuscator.d.ts +4 -0
- package/dist/obfuscator.d.ts.map +1 -0
- package/dist/obfuscator.js +222 -0
- package/dist/obfuscator.js.map +1 -0
- package/dist/options.d.ts +73 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +2 -0
- package/dist/options.js.map +1 -0
- package/dist/preparation/dot-to-bracket.d.ts +16 -0
- package/dist/preparation/dot-to-bracket.d.ts.map +1 -0
- package/dist/preparation/dot-to-bracket.js +36 -0
- package/dist/preparation/dot-to-bracket.js.map +1 -0
- package/dist/preparation/expand-destructuring.d.ts +26 -0
- package/dist/preparation/expand-destructuring.d.ts.map +1 -0
- package/dist/preparation/expand-destructuring.js +154 -0
- package/dist/preparation/expand-destructuring.js.map +1 -0
- package/dist/preparation/expand-template-literals.d.ts +23 -0
- package/dist/preparation/expand-template-literals.d.ts.map +1 -0
- package/dist/preparation/expand-template-literals.js +78 -0
- package/dist/preparation/expand-template-literals.js.map +1 -0
- package/dist/preparation/rewrite-class-fields.d.ts +43 -0
- package/dist/preparation/rewrite-class-fields.d.ts.map +1 -0
- package/dist/preparation/rewrite-class-fields.js +270 -0
- package/dist/preparation/rewrite-class-fields.js.map +1 -0
- package/dist/preparation/split-sequence-expressions.d.ts +15 -0
- package/dist/preparation/split-sequence-expressions.d.ts.map +1 -0
- package/dist/preparation/split-sequence-expressions.js +36 -0
- package/dist/preparation/split-sequence-expressions.js.map +1 -0
- package/dist/preparation/split-variable-declarations.d.ts +20 -0
- package/dist/preparation/split-variable-declarations.d.ts.map +1 -0
- package/dist/preparation/split-variable-declarations.js +36 -0
- package/dist/preparation/split-variable-declarations.js.map +1 -0
- package/dist/preparation/stringify-object-keys.d.ts +21 -0
- package/dist/preparation/stringify-object-keys.d.ts.map +1 -0
- package/dist/preparation/stringify-object-keys.js +96 -0
- package/dist/preparation/stringify-object-keys.js.map +1 -0
- package/dist/preparation/wrap-single-statements.d.ts +24 -0
- package/dist/preparation/wrap-single-statements.d.ts.map +1 -0
- package/dist/preparation/wrap-single-statements.js +53 -0
- package/dist/preparation/wrap-single-statements.js.map +1 -0
- package/dist/transforms/arguments-to-parameters.d.ts +20 -0
- package/dist/transforms/arguments-to-parameters.d.ts.map +1 -0
- package/dist/transforms/arguments-to-parameters.js +162 -0
- package/dist/transforms/arguments-to-parameters.js.map +1 -0
- package/dist/transforms/arrow-to-function.d.ts +46 -0
- package/dist/transforms/arrow-to-function.d.ts.map +1 -0
- package/dist/transforms/arrow-to-function.js +203 -0
- package/dist/transforms/arrow-to-function.js.map +1 -0
- package/dist/transforms/collapse-single-use-temps.d.ts +16 -0
- package/dist/transforms/collapse-single-use-temps.d.ts.map +1 -0
- package/dist/transforms/collapse-single-use-temps.js +805 -0
- package/dist/transforms/collapse-single-use-temps.js.map +1 -0
- package/dist/transforms/const-let-to-var.d.ts +21 -0
- package/dist/transforms/const-let-to-var.d.ts.map +1 -0
- package/dist/transforms/const-let-to-var.js +217 -0
- package/dist/transforms/const-let-to-var.js.map +1 -0
- package/dist/transforms/const-to-let.d.ts +17 -0
- package/dist/transforms/const-to-let.d.ts.map +1 -0
- package/dist/transforms/const-to-let.js +33 -0
- package/dist/transforms/const-to-let.js.map +1 -0
- package/dist/transforms/do-while-to-while.d.ts +26 -0
- package/dist/transforms/do-while-to-while.d.ts.map +1 -0
- package/dist/transforms/do-while-to-while.js +54 -0
- package/dist/transforms/do-while-to-while.js.map +1 -0
- package/dist/transforms/drop-console.d.ts +27 -0
- package/dist/transforms/drop-console.d.ts.map +1 -0
- package/dist/transforms/drop-console.js +107 -0
- package/dist/transforms/drop-console.js.map +1 -0
- package/dist/transforms/drop-debugger.d.ts +18 -0
- package/dist/transforms/drop-debugger.d.ts.map +1 -0
- package/dist/transforms/drop-debugger.js +34 -0
- package/dist/transforms/drop-debugger.js.map +1 -0
- package/dist/transforms/expand-binary-assignment.d.ts +26 -0
- package/dist/transforms/expand-binary-assignment.d.ts.map +1 -0
- package/dist/transforms/expand-binary-assignment.js +78 -0
- package/dist/transforms/expand-binary-assignment.js.map +1 -0
- package/dist/transforms/expand-logical-assignment.d.ts +22 -0
- package/dist/transforms/expand-logical-assignment.d.ts.map +1 -0
- package/dist/transforms/expand-logical-assignment.js +73 -0
- package/dist/transforms/expand-logical-assignment.js.map +1 -0
- package/dist/transforms/extract-object-properties.d.ts +21 -0
- package/dist/transforms/extract-object-properties.d.ts.map +1 -0
- package/dist/transforms/extract-object-properties.js +340 -0
- package/dist/transforms/extract-object-properties.js.map +1 -0
- package/dist/transforms/fold-builtin-methods.d.ts +23 -0
- package/dist/transforms/fold-builtin-methods.d.ts.map +1 -0
- package/dist/transforms/fold-builtin-methods.js +839 -0
- package/dist/transforms/fold-builtin-methods.js.map +1 -0
- package/dist/transforms/fold-constants.d.ts +16 -0
- package/dist/transforms/fold-constants.d.ts.map +1 -0
- package/dist/transforms/fold-constants.js +421 -0
- package/dist/transforms/fold-constants.js.map +1 -0
- package/dist/transforms/for-to-while.d.ts +29 -0
- package/dist/transforms/for-to-while.d.ts.map +1 -0
- package/dist/transforms/for-to-while.js +91 -0
- package/dist/transforms/for-to-while.js.map +1 -0
- package/dist/transforms/function-declaration-to-expression.d.ts +28 -0
- package/dist/transforms/function-declaration-to-expression.d.ts.map +1 -0
- package/dist/transforms/function-declaration-to-expression.js +95 -0
- package/dist/transforms/function-declaration-to-expression.js.map +1 -0
- package/dist/transforms/inline-functions.d.ts +16 -0
- package/dist/transforms/inline-functions.d.ts.map +1 -0
- package/dist/transforms/inline-functions.js +896 -0
- package/dist/transforms/inline-functions.js.map +1 -0
- package/dist/transforms/logical-to-ternary.d.ts +20 -0
- package/dist/transforms/logical-to-ternary.d.ts.map +1 -0
- package/dist/transforms/logical-to-ternary.js +78 -0
- package/dist/transforms/logical-to-ternary.js.map +1 -0
- package/dist/transforms/mangle-properties.d.ts +27 -0
- package/dist/transforms/mangle-properties.d.ts.map +1 -0
- package/dist/transforms/mangle-properties.js +703 -0
- package/dist/transforms/mangle-properties.js.map +1 -0
- package/dist/transforms/nullish-coalescing-to-ternary.d.ts +18 -0
- package/dist/transforms/nullish-coalescing-to-ternary.d.ts.map +1 -0
- package/dist/transforms/nullish-coalescing-to-ternary.js +61 -0
- package/dist/transforms/nullish-coalescing-to-ternary.js.map +1 -0
- package/dist/transforms/numbers-to-strings.d.ts +19 -0
- package/dist/transforms/numbers-to-strings.d.ts.map +1 -0
- package/dist/transforms/numbers-to-strings.js +51 -0
- package/dist/transforms/numbers-to-strings.js.map +1 -0
- package/dist/transforms/object-method-to-property.d.ts +34 -0
- package/dist/transforms/object-method-to-property.d.ts.map +1 -0
- package/dist/transforms/object-method-to-property.js +78 -0
- package/dist/transforms/object-method-to-property.js.map +1 -0
- package/dist/transforms/optional-chaining-to-ternary.d.ts +19 -0
- package/dist/transforms/optional-chaining-to-ternary.d.ts.map +1 -0
- package/dist/transforms/optional-chaining-to-ternary.js +206 -0
- package/dist/transforms/optional-chaining-to-ternary.js.map +1 -0
- package/dist/transforms/pack-declarations-into-parameters.d.ts +22 -0
- package/dist/transforms/pack-declarations-into-parameters.d.ts.map +1 -0
- package/dist/transforms/pack-declarations-into-parameters.js +208 -0
- package/dist/transforms/pack-declarations-into-parameters.js.map +1 -0
- package/dist/transforms/remove-anonymous-function-names.d.ts +17 -0
- package/dist/transforms/remove-anonymous-function-names.d.ts.map +1 -0
- package/dist/transforms/remove-anonymous-function-names.js +72 -0
- package/dist/transforms/remove-anonymous-function-names.js.map +1 -0
- package/dist/transforms/remove-unreachable-code.d.ts +21 -0
- package/dist/transforms/remove-unreachable-code.d.ts.map +1 -0
- package/dist/transforms/remove-unreachable-code.js +103 -0
- package/dist/transforms/remove-unreachable-code.js.map +1 -0
- package/dist/transforms/remove-unused-code.d.ts +18 -0
- package/dist/transforms/remove-unused-code.d.ts.map +1 -0
- package/dist/transforms/remove-unused-code.js +249 -0
- package/dist/transforms/remove-unused-code.js.map +1 -0
- package/dist/transforms/rename-identifiers.d.ts +24 -0
- package/dist/transforms/rename-identifiers.d.ts.map +1 -0
- package/dist/transforms/rename-identifiers.js +264 -0
- package/dist/transforms/rename-identifiers.js.map +1 -0
- package/dist/transforms/specials-to-strings.d.ts +30 -0
- package/dist/transforms/specials-to-strings.d.ts.map +1 -0
- package/dist/transforms/specials-to-strings.js +101 -0
- package/dist/transforms/specials-to-strings.js.map +1 -0
- package/dist/transforms/switch-to-if.d.ts +28 -0
- package/dist/transforms/switch-to-if.d.ts.map +1 -0
- package/dist/transforms/switch-to-if.js +279 -0
- package/dist/transforms/switch-to-if.js.map +1 -0
- package/dist/transforms/ternary-to-if.d.ts +20 -0
- package/dist/transforms/ternary-to-if.d.ts.map +1 -0
- package/dist/transforms/ternary-to-if.js +315 -0
- package/dist/transforms/ternary-to-if.js.map +1 -0
- package/dist/transforms/update-to-assignment.d.ts +26 -0
- package/dist/transforms/update-to-assignment.d.ts.map +1 -0
- package/dist/transforms/update-to-assignment.js +120 -0
- package/dist/transforms/update-to-assignment.js.map +1 -0
- package/dist/transforms/yodify-conditions.d.ts +22 -0
- package/dist/transforms/yodify-conditions.d.ts.map +1 -0
- package/dist/transforms/yodify-conditions.js +91 -0
- package/dist/transforms/yodify-conditions.js.map +1 -0
- package/dist/utils/ast.d.ts +10 -0
- package/dist/utils/ast.d.ts.map +1 -0
- package/dist/utils/ast.js +112 -0
- package/dist/utils/ast.js.map +1 -0
- package/dist/utils/change-tracking.d.ts +8 -0
- package/dist/utils/change-tracking.d.ts.map +1 -0
- package/dist/utils/change-tracking.js +8 -0
- package/dist/utils/change-tracking.js.map +1 -0
- package/dist/utils/dom-properties.d.ts +2 -0
- package/dist/utils/dom-properties.d.ts.map +1 -0
- package/dist/utils/dom-properties.js +9252 -0
- package/dist/utils/dom-properties.js.map +1 -0
- package/dist/utils/evaluation-order.d.ts +4 -0
- package/dist/utils/evaluation-order.d.ts.map +1 -0
- package/dist/utils/evaluation-order.js +152 -0
- package/dist/utils/evaluation-order.js.map +1 -0
- package/dist/utils/literal.d.ts +5 -0
- package/dist/utils/literal.d.ts.map +1 -0
- package/dist/utils/literal.js +90 -0
- package/dist/utils/literal.js.map +1 -0
- package/dist/utils/logical-lowering.d.ts +4 -0
- package/dist/utils/logical-lowering.d.ts.map +1 -0
- package/dist/utils/logical-lowering.js +104 -0
- package/dist/utils/logical-lowering.js.map +1 -0
- package/dist/utils/loop-lowering.d.ts +23 -0
- package/dist/utils/loop-lowering.d.ts.map +1 -0
- package/dist/utils/loop-lowering.js +88 -0
- package/dist/utils/loop-lowering.js.map +1 -0
- package/dist/utils/paths.d.ts +9 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +90 -0
- package/dist/utils/paths.js.map +1 -0
- package/dist/utils/random.d.ts +3 -0
- package/dist/utils/random.d.ts.map +1 -0
- package/dist/utils/random.js +22 -0
- package/dist/utils/random.js.map +1 -0
- package/dist/utils/string-generator.d.ts +37 -0
- package/dist/utils/string-generator.d.ts.map +1 -0
- package/dist/utils/string-generator.js +175 -0
- package/dist/utils/string-generator.js.map +1 -0
- package/package.json +76 -0
- package/src/analysis/constant.ts +310 -0
- package/src/analysis/document-order.ts +141 -0
- package/src/analysis/inlineability.ts +336 -0
- package/src/analysis/purity.ts +315 -0
- package/src/index.ts +2 -0
- package/src/obfuscator.ts +271 -0
- package/src/options.ts +114 -0
- package/src/preparation/dot-to-bracket.ts +41 -0
- package/src/preparation/expand-destructuring.ts +211 -0
- package/src/preparation/expand-template-literals.ts +92 -0
- package/src/preparation/rewrite-class-fields.ts +320 -0
- package/src/preparation/split-sequence-expressions.ts +40 -0
- package/src/preparation/split-variable-declarations.ts +39 -0
- package/src/preparation/stringify-object-keys.ts +107 -0
- package/src/preparation/wrap-single-statements.ts +63 -0
- package/src/transforms/arguments-to-parameters.ts +187 -0
- package/src/transforms/arrow-to-function.ts +225 -0
- package/src/transforms/collapse-single-use-temps.ts +957 -0
- package/src/transforms/const-let-to-var.ts +246 -0
- package/src/transforms/const-to-let.ts +38 -0
- package/src/transforms/do-while-to-while.ts +69 -0
- package/src/transforms/drop-console.ts +119 -0
- package/src/transforms/drop-debugger.ts +38 -0
- package/src/transforms/expand-binary-assignment.ts +94 -0
- package/src/transforms/expand-logical-assignment.ts +84 -0
- package/src/transforms/extract-object-properties.ts +404 -0
- package/src/transforms/fold-builtin-methods.ts +949 -0
- package/src/transforms/fold-constants.ts +462 -0
- package/src/transforms/for-to-while.ts +109 -0
- package/src/transforms/function-declaration-to-expression.ts +108 -0
- package/src/transforms/inline-functions.ts +1074 -0
- package/src/transforms/logical-to-ternary.ts +92 -0
- package/src/transforms/mangle-properties.ts +819 -0
- package/src/transforms/nullish-coalescing-to-ternary.ts +77 -0
- package/src/transforms/numbers-to-strings.ts +61 -0
- package/src/transforms/object-method-to-property.ts +86 -0
- package/src/transforms/optional-chaining-to-ternary.ts +225 -0
- package/src/transforms/pack-declarations-into-parameters.ts +233 -0
- package/src/transforms/remove-anonymous-function-names.ts +84 -0
- package/src/transforms/remove-unreachable-code.ts +115 -0
- package/src/transforms/remove-unused-code.ts +296 -0
- package/src/transforms/rename-identifiers.ts +289 -0
- package/src/transforms/specials-to-strings.ts +116 -0
- package/src/transforms/switch-to-if.ts +339 -0
- package/src/transforms/ternary-to-if.ts +387 -0
- package/src/transforms/update-to-assignment.ts +132 -0
- package/src/transforms/yodify-conditions.ts +103 -0
- package/src/utils/ast.ts +129 -0
- package/src/utils/change-tracking.ts +15 -0
- package/src/utils/dom-properties.ts +9252 -0
- package/src/utils/evaluation-order.ts +178 -0
- package/src/utils/literal.ts +96 -0
- package/src/utils/logical-lowering.ts +132 -0
- package/src/utils/loop-lowering.ts +109 -0
- package/src/utils/paths.ts +103 -0
- package/src/utils/random.ts +22 -0
- 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
|
+
}
|