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
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { NodePath, Visitor } from '@babel/traverse'
|
|
2
|
+
import * as t from '@babel/types'
|
|
3
|
+
import type { File } from '@babel/types'
|
|
4
|
+
|
|
5
|
+
import { traverseForChanges } from 'src/utils/change-tracking'
|
|
6
|
+
import type { ChangeState } from 'src/utils/change-tracking'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Removes a function expression's name when the body does not use its binding
|
|
10
|
+
* and NamedEvaluation restores the same `.name`. Other positions could infer no
|
|
11
|
+
* name or a different one.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* // ◀️ before
|
|
15
|
+
* var greet = function greet() {};
|
|
16
|
+
* var a = function bar() {};
|
|
17
|
+
*
|
|
18
|
+
* // ▶️ after
|
|
19
|
+
* var greet = function () {};
|
|
20
|
+
* var a = function bar() {};
|
|
21
|
+
*/
|
|
22
|
+
export function removeAnonymousFunctionNames(ast: File): boolean {
|
|
23
|
+
return traverseForChanges(ast, visitor)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const visitor: Visitor<ChangeState> = {
|
|
27
|
+
FunctionExpression(path, state) {
|
|
28
|
+
const { id } = path.node
|
|
29
|
+
if (!id) {
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
// Reads and writes both observe the expression's immutable inner binding.
|
|
33
|
+
const binding = path.scope.getBinding(id.name)
|
|
34
|
+
if (
|
|
35
|
+
binding &&
|
|
36
|
+
binding.identifier === id &&
|
|
37
|
+
(binding.references > 0 || binding.constantViolations.length > 0)
|
|
38
|
+
) {
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
if (inferredName(path) !== id.name) {
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
path.node.id = null
|
|
45
|
+
state.changed = true
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// No inferred name means `.name` would become empty.
|
|
50
|
+
function inferredName(path: NodePath<t.FunctionExpression>): string | null {
|
|
51
|
+
const { node } = path
|
|
52
|
+
const { parentPath } = path
|
|
53
|
+
if (!parentPath) {
|
|
54
|
+
return null
|
|
55
|
+
}
|
|
56
|
+
const parent = parentPath.node
|
|
57
|
+
if (t.isVariableDeclarator(parent) && parent.init === node && t.isIdentifier(parent.id)) {
|
|
58
|
+
return parent.id.name
|
|
59
|
+
}
|
|
60
|
+
if (
|
|
61
|
+
t.isAssignmentExpression(parent) &&
|
|
62
|
+
parent.operator === '=' &&
|
|
63
|
+
parent.right === node &&
|
|
64
|
+
t.isIdentifier(parent.left)
|
|
65
|
+
) {
|
|
66
|
+
return parent.left.name
|
|
67
|
+
}
|
|
68
|
+
if (t.isAssignmentPattern(parent) && parent.right === node && t.isIdentifier(parent.left)) {
|
|
69
|
+
return parent.left.name
|
|
70
|
+
}
|
|
71
|
+
if (
|
|
72
|
+
(t.isObjectProperty(parent) || t.isClassProperty(parent)) &&
|
|
73
|
+
parent.value === node &&
|
|
74
|
+
!parent.computed
|
|
75
|
+
) {
|
|
76
|
+
if (t.isIdentifier(parent.key)) {
|
|
77
|
+
return parent.key.name
|
|
78
|
+
}
|
|
79
|
+
if (t.isStringLiteral(parent.key)) {
|
|
80
|
+
return parent.key.value
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return null
|
|
84
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { NodePath, Visitor } from '@babel/traverse'
|
|
2
|
+
import * as t from '@babel/types'
|
|
3
|
+
import type { File } from '@babel/types'
|
|
4
|
+
|
|
5
|
+
import { traverseForChanges } from 'src/utils/change-tracking'
|
|
6
|
+
import type { ChangeState } from 'src/utils/change-tracking'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Removes statements after unconditional control flow in the same statement list.
|
|
10
|
+
*
|
|
11
|
+
* Hoisted function and `var` declarations stay because reachable code may still
|
|
12
|
+
* observe their bindings.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* // ◀️ before
|
|
16
|
+
* function foo() {
|
|
17
|
+
* return "bar";
|
|
18
|
+
* log("baz");
|
|
19
|
+
* }
|
|
20
|
+
*
|
|
21
|
+
* // ▶️ after
|
|
22
|
+
* function foo() {
|
|
23
|
+
* return "bar";
|
|
24
|
+
* }
|
|
25
|
+
*/
|
|
26
|
+
export function removeUnreachableCode(ast: File): boolean {
|
|
27
|
+
return traverseForChanges(ast, visitor)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const visitor: Visitor<ChangeState> = {
|
|
31
|
+
BlockStatement(path, state) {
|
|
32
|
+
if (pruneBody(path.get('body'))) {
|
|
33
|
+
state.changed = true
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
SwitchCase(path, state) {
|
|
37
|
+
if (pruneBody(path.get('consequent'))) {
|
|
38
|
+
state.changed = true
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function pruneBody(body: NodePath<t.Statement>[]): boolean {
|
|
44
|
+
let killZone = false
|
|
45
|
+
const toRemove: NodePath<t.Statement>[] = []
|
|
46
|
+
for (const stmt of body) {
|
|
47
|
+
if (killZone) {
|
|
48
|
+
if (isSafeToRemoveAfterCompletion(stmt)) {
|
|
49
|
+
toRemove.push(stmt)
|
|
50
|
+
}
|
|
51
|
+
continue
|
|
52
|
+
}
|
|
53
|
+
if (isCompletion(stmt.node)) {
|
|
54
|
+
killZone = true
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
for (const removed of toRemove) {
|
|
58
|
+
removed.remove()
|
|
59
|
+
}
|
|
60
|
+
return toRemove.length > 0
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function isCompletion(node: t.Node): boolean {
|
|
64
|
+
return (
|
|
65
|
+
t.isReturnStatement(node) ||
|
|
66
|
+
t.isThrowStatement(node) ||
|
|
67
|
+
t.isBreakStatement(node) ||
|
|
68
|
+
t.isContinueStatement(node)
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Hoisted bindings and referenced lexical TDZ remain observable from reachable
|
|
73
|
+
// code. An unreferenced lexical declaration is inert.
|
|
74
|
+
function isSafeToRemoveAfterCompletion(path: NodePath<t.Statement>): boolean {
|
|
75
|
+
const { node } = path
|
|
76
|
+
if (t.isFunctionDeclaration(node) || (t.isVariableDeclaration(node) && node.kind === 'var')) {
|
|
77
|
+
return false
|
|
78
|
+
}
|
|
79
|
+
if (t.isClassDeclaration(node) || (t.isVariableDeclaration(node) && node.kind !== 'var')) {
|
|
80
|
+
return !declaresReferencedBinding(path)
|
|
81
|
+
}
|
|
82
|
+
return !nestsHoistedDeclaration(node)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// A write before `let x` also observes its TDZ.
|
|
86
|
+
function declaresReferencedBinding(path: NodePath<t.Statement>): boolean {
|
|
87
|
+
for (const name of Object.keys(path.getBindingIdentifiers())) {
|
|
88
|
+
const binding = path.scope.getBinding(name)
|
|
89
|
+
if (binding && (binding.references > 0 || binding.constantViolations.length > 0)) {
|
|
90
|
+
return true
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return false
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function nestsHoistedDeclaration(node: t.Node): boolean {
|
|
97
|
+
let found = false
|
|
98
|
+
t.traverseFast(node, (child) => {
|
|
99
|
+
if (found) {
|
|
100
|
+
return t.traverseFast.skip
|
|
101
|
+
}
|
|
102
|
+
if (child !== node && t.isFunction(child)) {
|
|
103
|
+
// Function declarations may add Annex B aliases. Function bodies do not.
|
|
104
|
+
if (t.isFunctionDeclaration(child)) {
|
|
105
|
+
found = true
|
|
106
|
+
}
|
|
107
|
+
return t.traverseFast.skip
|
|
108
|
+
}
|
|
109
|
+
if (t.isVariableDeclaration(child) && child.kind === 'var') {
|
|
110
|
+
found = true
|
|
111
|
+
}
|
|
112
|
+
return found ? t.traverseFast.skip : undefined
|
|
113
|
+
})
|
|
114
|
+
return found
|
|
115
|
+
}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import traverse from '@babel/traverse'
|
|
2
|
+
import type { NodePath, Scope, Visitor } from '@babel/traverse'
|
|
3
|
+
import * as t from '@babel/types'
|
|
4
|
+
import type { File } from '@babel/types'
|
|
5
|
+
|
|
6
|
+
import { isSideEffectFree } from 'src/analysis/purity'
|
|
7
|
+
import { isDirectEvalCall } from 'src/utils/ast'
|
|
8
|
+
import type { ChangeState } from 'src/utils/change-tracking'
|
|
9
|
+
import { hasAnnexBFunctionAlias } from 'src/utils/paths'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Removes unused declarations and expression statements that have no effects.
|
|
13
|
+
* Sweeps continue because each removal can strand another binding.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* // ◀️ before
|
|
17
|
+
* function foo() { return "foo"; }
|
|
18
|
+
* function bar() { return "bar"; }
|
|
19
|
+
* var unused = 1;
|
|
20
|
+
* foo();
|
|
21
|
+
*
|
|
22
|
+
* // ▶️ after
|
|
23
|
+
* function foo() { return "foo"; }
|
|
24
|
+
* foo();
|
|
25
|
+
*/
|
|
26
|
+
export function removeUnusedCode(ast: File): boolean {
|
|
27
|
+
// Earlier relocation can leave references on detached paths. Rebuild once,
|
|
28
|
+
// then maintain counts during removal.
|
|
29
|
+
crawlProgramScope(ast)
|
|
30
|
+
let anyChanged = false
|
|
31
|
+
const state: SweepState = {
|
|
32
|
+
changed: true,
|
|
33
|
+
evalReachableScopes: collectEvalReachableScopes(ast),
|
|
34
|
+
}
|
|
35
|
+
while (state.changed) {
|
|
36
|
+
state.changed = false
|
|
37
|
+
traverse(ast, sweepVisitor, undefined, state)
|
|
38
|
+
if (state.changed) {
|
|
39
|
+
anyChanged = true
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return anyChanged
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function crawlProgramScope(ast: File): void {
|
|
46
|
+
traverse(ast, {
|
|
47
|
+
Program(path) {
|
|
48
|
+
path.scope.crawl()
|
|
49
|
+
path.stop()
|
|
50
|
+
},
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Direct eval can read otherwise unreferenced bindings in its scope ancestry.
|
|
55
|
+
// Sweep state keeps this set local to one pass.
|
|
56
|
+
interface SweepState extends ChangeState {
|
|
57
|
+
evalReachableScopes: Set<t.Node>
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function collectEvalReachableScopes(ast: File): Set<t.Node> {
|
|
61
|
+
const reachable = new Set<t.Node>()
|
|
62
|
+
traverse(ast, {
|
|
63
|
+
CallExpression(path) {
|
|
64
|
+
if (!isDirectEvalCall(path.node)) {
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
let scope: Scope | undefined = path.scope
|
|
68
|
+
while (scope) {
|
|
69
|
+
reachable.add(scope.block)
|
|
70
|
+
scope = scope.parent
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
})
|
|
74
|
+
return reachable
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function bindingReachableByEval(
|
|
78
|
+
binding: { scope: Scope },
|
|
79
|
+
evalReachableScopes: Set<t.Node>,
|
|
80
|
+
): boolean {
|
|
81
|
+
return evalReachableScopes.has(binding.scope.block)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const sweepVisitor: Visitor<SweepState> = {
|
|
85
|
+
VariableDeclarator(path, state) {
|
|
86
|
+
if (tryRemoveDeclarator(path, state.evalReachableScopes)) {
|
|
87
|
+
state.changed = true
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
FunctionDeclaration(path, state) {
|
|
91
|
+
if (tryRemoveFunctionDecl(path, state.evalReachableScopes)) {
|
|
92
|
+
state.changed = true
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
ClassDeclaration(path, state) {
|
|
96
|
+
if (tryRemoveClassDecl(path, state.evalReachableScopes)) {
|
|
97
|
+
state.changed = true
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
ExpressionStatement(path, state) {
|
|
101
|
+
if (tryRemoveExpressionStatement(path)) {
|
|
102
|
+
state.changed = true
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const dereferenceVisitor: Visitor = {
|
|
108
|
+
Identifier(path) {
|
|
109
|
+
if (!path.isReferencedIdentifier()) {
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
const binding = path.scope.getBinding(path.node.name)
|
|
113
|
+
if (binding) {
|
|
114
|
+
binding.dereference()
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function dereferenceSubtree(rootPath: NodePath): void {
|
|
120
|
+
if (rootPath.isReferencedIdentifier()) {
|
|
121
|
+
const binding = rootPath.scope.getBinding((rootPath.node as t.Identifier).name)
|
|
122
|
+
if (binding) {
|
|
123
|
+
binding.dereference()
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
rootPath.traverse(dereferenceVisitor)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function tryRemoveDeclarator(
|
|
130
|
+
path: NodePath<t.VariableDeclarator>,
|
|
131
|
+
evalReachableScopes: Set<t.Node>,
|
|
132
|
+
): boolean {
|
|
133
|
+
const declaration = path.parentPath
|
|
134
|
+
if (!declaration || !declaration.isVariableDeclaration()) {
|
|
135
|
+
return false
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const grandparent = declaration.parentPath
|
|
139
|
+
if (!grandparent) {
|
|
140
|
+
return false
|
|
141
|
+
}
|
|
142
|
+
if (grandparent.isExportNamedDeclaration() || grandparent.isExportDefaultDeclaration()) {
|
|
143
|
+
return false
|
|
144
|
+
}
|
|
145
|
+
if (
|
|
146
|
+
grandparent.isForStatement() ||
|
|
147
|
+
grandparent.isForInStatement() ||
|
|
148
|
+
grandparent.isForOfStatement()
|
|
149
|
+
) {
|
|
150
|
+
return false
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const { id } = path.node
|
|
154
|
+
if (!t.isIdentifier(id)) {
|
|
155
|
+
return false
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const { init } = path.node
|
|
159
|
+
if (init && !isSideEffectFree(init, path.scope, path.get('init') as NodePath)) {
|
|
160
|
+
return false
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const binding = path.scope.getBinding(id.name)
|
|
164
|
+
if (!binding) {
|
|
165
|
+
return false
|
|
166
|
+
}
|
|
167
|
+
if (binding.references > 0) {
|
|
168
|
+
return false
|
|
169
|
+
}
|
|
170
|
+
if (binding.constantViolations.length > 0) {
|
|
171
|
+
return false
|
|
172
|
+
}
|
|
173
|
+
if (bindingReachableByEval(binding, evalReachableScopes)) {
|
|
174
|
+
return false
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (path.node.init) {
|
|
178
|
+
dereferenceSubtree(path.get('init') as NodePath)
|
|
179
|
+
}
|
|
180
|
+
path.scope.removeBinding(id.name)
|
|
181
|
+
path.remove()
|
|
182
|
+
if (declaration.node && declaration.node.declarations.length === 0) {
|
|
183
|
+
declaration.remove()
|
|
184
|
+
}
|
|
185
|
+
return true
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function tryRemoveFunctionDecl(
|
|
189
|
+
path: NodePath<t.FunctionDeclaration>,
|
|
190
|
+
evalReachableScopes: Set<t.Node>,
|
|
191
|
+
): boolean {
|
|
192
|
+
if (isExported(path)) {
|
|
193
|
+
return false
|
|
194
|
+
}
|
|
195
|
+
// Babel misses the sloppy Annex B alias of a block-level function.
|
|
196
|
+
if (hasAnnexBFunctionAlias(path)) {
|
|
197
|
+
return false
|
|
198
|
+
}
|
|
199
|
+
const { id } = path.node
|
|
200
|
+
if (!id) {
|
|
201
|
+
return false
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const binding = path.scope.getBinding(id.name)
|
|
205
|
+
if (!binding) {
|
|
206
|
+
return false
|
|
207
|
+
}
|
|
208
|
+
if (binding.references > 0) {
|
|
209
|
+
return false
|
|
210
|
+
}
|
|
211
|
+
if (binding.constantViolations.length > 0) {
|
|
212
|
+
return false
|
|
213
|
+
}
|
|
214
|
+
if (bindingReachableByEval(binding, evalReachableScopes)) {
|
|
215
|
+
return false
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
dereferenceSubtree(path.get('body') as NodePath)
|
|
219
|
+
path.scope.removeBinding(id.name)
|
|
220
|
+
path.remove()
|
|
221
|
+
return true
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function tryRemoveClassDecl(
|
|
225
|
+
path: NodePath<t.ClassDeclaration>,
|
|
226
|
+
evalReachableScopes: Set<t.Node>,
|
|
227
|
+
): boolean {
|
|
228
|
+
if (isExported(path)) {
|
|
229
|
+
return false
|
|
230
|
+
}
|
|
231
|
+
const { id } = path.node
|
|
232
|
+
if (!id) {
|
|
233
|
+
return false
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// `extends`, computed keys, static fields, and static blocks run on evaluation.
|
|
237
|
+
if (path.node.superClass && !isSideEffectFree(path.node.superClass, path.scope, path)) {
|
|
238
|
+
return false
|
|
239
|
+
}
|
|
240
|
+
for (const member of path.node.body.body) {
|
|
241
|
+
// Public and private static fields have different Babel node types.
|
|
242
|
+
if ((t.isClassProperty(member) || t.isClassPrivateProperty(member)) && member.static) {
|
|
243
|
+
return false
|
|
244
|
+
}
|
|
245
|
+
if (t.isStaticBlock(member)) {
|
|
246
|
+
return false
|
|
247
|
+
}
|
|
248
|
+
if (
|
|
249
|
+
'computed' in member &&
|
|
250
|
+
member.computed &&
|
|
251
|
+
'key' in member &&
|
|
252
|
+
!isSideEffectFree(member.key, path.scope, path)
|
|
253
|
+
) {
|
|
254
|
+
return false
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const binding = path.scope.getBinding(id.name)
|
|
259
|
+
if (!binding) {
|
|
260
|
+
return false
|
|
261
|
+
}
|
|
262
|
+
if (binding.references > 0) {
|
|
263
|
+
return false
|
|
264
|
+
}
|
|
265
|
+
if (binding.constantViolations.length > 0) {
|
|
266
|
+
return false
|
|
267
|
+
}
|
|
268
|
+
if (bindingReachableByEval(binding, evalReachableScopes)) {
|
|
269
|
+
return false
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
dereferenceSubtree(path.get('body') as NodePath)
|
|
273
|
+
if (path.node.superClass) {
|
|
274
|
+
dereferenceSubtree(path.get('superClass') as NodePath)
|
|
275
|
+
}
|
|
276
|
+
path.scope.removeBinding(id.name)
|
|
277
|
+
path.remove()
|
|
278
|
+
return true
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function tryRemoveExpressionStatement(path: NodePath<t.ExpressionStatement>): boolean {
|
|
282
|
+
if (!isSideEffectFree(path.node.expression, path.scope, path.get('expression'))) {
|
|
283
|
+
return false
|
|
284
|
+
}
|
|
285
|
+
dereferenceSubtree(path.get('expression') as NodePath)
|
|
286
|
+
path.remove()
|
|
287
|
+
return true
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function isExported(path: NodePath): boolean {
|
|
291
|
+
const parent = path.parentPath
|
|
292
|
+
if (!parent) {
|
|
293
|
+
return false
|
|
294
|
+
}
|
|
295
|
+
return parent.isExportNamedDeclaration() || parent.isExportDefaultDeclaration()
|
|
296
|
+
}
|