webfuscator 1.2.1 → 1.3.1
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/README.md +3 -3
- package/dist/analysis/constant.d.ts.map +1 -1
- package/dist/analysis/constant.js +10 -27
- package/dist/analysis/constant.js.map +1 -1
- package/dist/analysis/inlineability.d.ts.map +1 -1
- package/dist/analysis/inlineability.js +5 -13
- package/dist/analysis/inlineability.js.map +1 -1
- package/dist/analysis/purity.d.ts +3 -3
- package/dist/analysis/purity.d.ts.map +1 -1
- package/dist/analysis/purity.js +28 -75
- package/dist/analysis/purity.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/obfuscator.d.ts +2 -1
- package/dist/obfuscator.d.ts.map +1 -1
- package/dist/obfuscator.js +47 -12
- package/dist/obfuscator.js.map +1 -1
- package/dist/options.d.ts +17 -4
- package/dist/options.d.ts.map +1 -1
- package/dist/preparation/expand-destructuring.d.ts +2 -3
- package/dist/preparation/expand-destructuring.d.ts.map +1 -1
- package/dist/preparation/expand-destructuring.js +2 -8
- package/dist/preparation/expand-destructuring.js.map +1 -1
- package/dist/transforms/arguments-to-parameters.d.ts +5 -4
- package/dist/transforms/arguments-to-parameters.d.ts.map +1 -1
- package/dist/transforms/arguments-to-parameters.js +5 -4
- package/dist/transforms/arguments-to-parameters.js.map +1 -1
- package/dist/transforms/arrow-to-function.d.ts +4 -3
- package/dist/transforms/arrow-to-function.d.ts.map +1 -1
- package/dist/transforms/arrow-to-function.js +6 -10
- package/dist/transforms/arrow-to-function.js.map +1 -1
- package/dist/transforms/collapse-single-use-temps.d.ts +2 -1
- package/dist/transforms/collapse-single-use-temps.d.ts.map +1 -1
- package/dist/transforms/collapse-single-use-temps.js +12 -20
- package/dist/transforms/collapse-single-use-temps.js.map +1 -1
- package/dist/transforms/conceal-strings.d.ts +56 -0
- package/dist/transforms/conceal-strings.d.ts.map +1 -0
- package/dist/transforms/conceal-strings.js +356 -0
- package/dist/transforms/conceal-strings.js.map +1 -0
- package/dist/transforms/const-let-to-var.d.ts +4 -3
- package/dist/transforms/const-let-to-var.d.ts.map +1 -1
- package/dist/transforms/const-let-to-var.js +8 -67
- package/dist/transforms/const-let-to-var.js.map +1 -1
- package/dist/transforms/const-to-let.d.ts +1 -3
- package/dist/transforms/const-to-let.d.ts.map +1 -1
- package/dist/transforms/const-to-let.js +1 -8
- package/dist/transforms/const-to-let.js.map +1 -1
- package/dist/transforms/drop-console.d.ts.map +1 -1
- package/dist/transforms/drop-console.js +3 -5
- package/dist/transforms/drop-console.js.map +1 -1
- package/dist/transforms/expand-binary-assignment.d.ts.map +1 -1
- package/dist/transforms/expand-binary-assignment.js +0 -5
- package/dist/transforms/expand-binary-assignment.js.map +1 -1
- package/dist/transforms/expand-logical-assignment.d.ts.map +1 -1
- package/dist/transforms/expand-logical-assignment.js +0 -5
- package/dist/transforms/expand-logical-assignment.js.map +1 -1
- package/dist/transforms/extract-object-properties.js +5 -12
- package/dist/transforms/extract-object-properties.js.map +1 -1
- package/dist/transforms/fold-builtin-methods.d.ts.map +1 -1
- package/dist/transforms/fold-builtin-methods.js +44 -14
- package/dist/transforms/fold-builtin-methods.js.map +1 -1
- package/dist/transforms/fold-constants.js +5 -2
- package/dist/transforms/fold-constants.js.map +1 -1
- package/dist/transforms/function-declaration-to-expression.d.ts +5 -6
- package/dist/transforms/function-declaration-to-expression.d.ts.map +1 -1
- package/dist/transforms/function-declaration-to-expression.js +5 -6
- package/dist/transforms/function-declaration-to-expression.js.map +1 -1
- package/dist/transforms/hoist-variables.d.ts +29 -0
- package/dist/transforms/hoist-variables.d.ts.map +1 -0
- package/dist/transforms/hoist-variables.js +215 -0
- package/dist/transforms/hoist-variables.js.map +1 -0
- package/dist/transforms/inline-functions.js +11 -40
- package/dist/transforms/inline-functions.js.map +1 -1
- package/dist/transforms/logical-to-ternary.d.ts +1 -1
- package/dist/transforms/logical-to-ternary.js +5 -9
- package/dist/transforms/logical-to-ternary.js.map +1 -1
- package/dist/transforms/mangle-properties.d.ts +2 -5
- package/dist/transforms/mangle-properties.d.ts.map +1 -1
- package/dist/transforms/mangle-properties.js +9 -9
- package/dist/transforms/mangle-properties.js.map +1 -1
- package/dist/transforms/merge-variable-declarations.d.ts +17 -0
- package/dist/transforms/merge-variable-declarations.d.ts.map +1 -0
- package/dist/transforms/merge-variable-declarations.js +68 -0
- package/dist/transforms/merge-variable-declarations.js.map +1 -0
- package/dist/transforms/nullish-coalescing-to-ternary.d.ts +4 -5
- package/dist/transforms/nullish-coalescing-to-ternary.d.ts.map +1 -1
- package/dist/transforms/nullish-coalescing-to-ternary.js +7 -12
- package/dist/transforms/nullish-coalescing-to-ternary.js.map +1 -1
- package/dist/transforms/numbers-to-strings.d.ts.map +1 -1
- package/dist/transforms/numbers-to-strings.js +1 -8
- package/dist/transforms/numbers-to-strings.js.map +1 -1
- package/dist/transforms/object-method-to-property.d.ts +1 -1
- package/dist/transforms/object-method-to-property.js +1 -1
- package/dist/transforms/optional-chaining-to-ternary.d.ts +4 -3
- package/dist/transforms/optional-chaining-to-ternary.d.ts.map +1 -1
- package/dist/transforms/optional-chaining-to-ternary.js +9 -9
- package/dist/transforms/optional-chaining-to-ternary.js.map +1 -1
- package/dist/transforms/pack-declarations-into-parameters.d.ts.map +1 -1
- package/dist/transforms/pack-declarations-into-parameters.js +5 -21
- package/dist/transforms/pack-declarations-into-parameters.js.map +1 -1
- package/dist/transforms/pack.d.ts +4 -13
- package/dist/transforms/pack.d.ts.map +1 -1
- package/dist/transforms/pack.js +9 -29
- package/dist/transforms/pack.js.map +1 -1
- package/dist/transforms/remove-anonymous-function-names.d.ts +2 -4
- package/dist/transforms/remove-anonymous-function-names.d.ts.map +1 -1
- package/dist/transforms/remove-anonymous-function-names.js +2 -40
- package/dist/transforms/remove-anonymous-function-names.js.map +1 -1
- package/dist/transforms/remove-unreachable-code.js +2 -21
- package/dist/transforms/remove-unreachable-code.js.map +1 -1
- package/dist/transforms/remove-unused-code.d.ts.map +1 -1
- package/dist/transforms/remove-unused-code.js +12 -49
- package/dist/transforms/remove-unused-code.js.map +1 -1
- package/dist/transforms/rename-identifiers.d.ts +3 -3
- package/dist/transforms/rename-identifiers.d.ts.map +1 -1
- package/dist/transforms/rename-identifiers.js +31 -35
- package/dist/transforms/rename-identifiers.js.map +1 -1
- package/dist/transforms/reorder-function-declarations.d.ts +24 -0
- package/dist/transforms/reorder-function-declarations.d.ts.map +1 -0
- package/dist/transforms/reorder-function-declarations.js +62 -0
- package/dist/transforms/reorder-function-declarations.js.map +1 -0
- package/dist/transforms/specials-to-strings.d.ts.map +1 -1
- package/dist/transforms/specials-to-strings.js +2 -3
- package/dist/transforms/specials-to-strings.js.map +1 -1
- package/dist/transforms/switch-to-if.js +5 -15
- package/dist/transforms/switch-to-if.js.map +1 -1
- package/dist/transforms/ternary-to-if.d.ts.map +1 -1
- package/dist/transforms/ternary-to-if.js +5 -9
- package/dist/transforms/ternary-to-if.js.map +1 -1
- package/dist/transforms/update-to-assignment.d.ts.map +1 -1
- package/dist/transforms/update-to-assignment.js +6 -23
- package/dist/transforms/update-to-assignment.js.map +1 -1
- package/dist/transforms/yodify-conditions.d.ts +3 -4
- package/dist/transforms/yodify-conditions.d.ts.map +1 -1
- package/dist/transforms/yodify-conditions.js +3 -4
- package/dist/transforms/yodify-conditions.js.map +1 -1
- package/dist/utils/ast.d.ts +1 -3
- package/dist/utils/ast.d.ts.map +1 -1
- package/dist/utils/ast.js +4 -60
- package/dist/utils/ast.js.map +1 -1
- package/dist/utils/evaluation-order.js +1 -1
- package/dist/utils/evaluation-order.js.map +1 -1
- package/dist/utils/literal.d.ts.map +1 -1
- package/dist/utils/literal.js +74 -2
- package/dist/utils/literal.js.map +1 -1
- package/dist/utils/logical-lowering.d.ts.map +1 -1
- package/dist/utils/logical-lowering.js +7 -9
- package/dist/utils/logical-lowering.js.map +1 -1
- package/dist/utils/nullish.d.ts +4 -0
- package/dist/utils/nullish.d.ts.map +1 -0
- package/dist/utils/nullish.js +8 -0
- package/dist/utils/nullish.js.map +1 -0
- package/dist/utils/paths.d.ts +0 -2
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +0 -28
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/statement-lists.d.ts +9 -0
- package/dist/utils/statement-lists.d.ts.map +1 -0
- package/dist/utils/statement-lists.js +27 -0
- package/dist/utils/statement-lists.js.map +1 -0
- package/package.json +6 -2
- package/src/analysis/constant.ts +12 -33
- package/src/analysis/inlineability.ts +5 -13
- package/src/analysis/purity.ts +29 -92
- package/src/index.ts +1 -0
- package/src/obfuscator.ts +51 -14
- package/src/options.ts +21 -3
- package/src/preparation/expand-destructuring.ts +2 -10
- package/src/transforms/arguments-to-parameters.ts +5 -4
- package/src/transforms/arrow-to-function.ts +6 -10
- package/src/transforms/collapse-single-use-temps.ts +12 -23
- package/src/transforms/conceal-strings.ts +525 -0
- package/src/transforms/const-let-to-var.ts +9 -72
- package/src/transforms/const-to-let.ts +1 -8
- package/src/transforms/drop-console.ts +3 -5
- package/src/transforms/expand-binary-assignment.ts +0 -5
- package/src/transforms/expand-logical-assignment.ts +0 -5
- package/src/transforms/extract-object-properties.ts +5 -14
- package/src/transforms/fold-builtin-methods.ts +63 -17
- package/src/transforms/fold-constants.ts +5 -2
- package/src/transforms/function-declaration-to-expression.ts +5 -6
- package/src/transforms/hoist-variables.ts +262 -0
- package/src/transforms/inline-functions.ts +16 -47
- package/src/transforms/logical-to-ternary.ts +5 -10
- package/src/transforms/mangle-properties.ts +9 -12
- package/src/transforms/merge-variable-declarations.ts +91 -0
- package/src/transforms/nullish-coalescing-to-ternary.ts +7 -13
- package/src/transforms/numbers-to-strings.ts +2 -12
- package/src/transforms/object-method-to-property.ts +1 -1
- package/src/transforms/optional-chaining-to-ternary.ts +9 -9
- package/src/transforms/pack-declarations-into-parameters.ts +11 -21
- package/src/transforms/pack.ts +9 -30
- package/src/transforms/remove-anonymous-function-names.ts +3 -46
- package/src/transforms/remove-unreachable-code.ts +2 -22
- package/src/transforms/remove-unused-code.ts +14 -74
- package/src/transforms/rename-identifiers.ts +44 -35
- package/src/transforms/reorder-function-declarations.ts +78 -0
- package/src/transforms/specials-to-strings.ts +2 -3
- package/src/transforms/switch-to-if.ts +5 -22
- package/src/transforms/ternary-to-if.ts +5 -9
- package/src/transforms/update-to-assignment.ts +6 -24
- package/src/transforms/yodify-conditions.ts +3 -4
- package/src/utils/ast.ts +4 -69
- package/src/utils/evaluation-order.ts +1 -1
- package/src/utils/literal.ts +91 -2
- package/src/utils/logical-lowering.ts +7 -11
- package/src/utils/nullish.ts +23 -0
- package/src/utils/paths.ts +0 -32
- package/src/utils/statement-lists.ts +45 -0
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import * as t from '@babel/types'
|
|
1
|
+
import type { Visitor } from '@babel/traverse'
|
|
3
2
|
import type { File } from '@babel/types'
|
|
4
3
|
|
|
5
4
|
import { traverseForChanges } from '../utils/change-tracking'
|
|
6
5
|
import type { ChangeState } from '../utils/change-tracking'
|
|
7
6
|
|
|
8
7
|
/**
|
|
9
|
-
* Removes a function expression's name when
|
|
10
|
-
* and NamedEvaluation restores the same `.name`. Other positions could infer no
|
|
11
|
-
* name or a different one.
|
|
8
|
+
* Removes a function expression's name when its body does not reference it.
|
|
12
9
|
*
|
|
13
10
|
* @example
|
|
14
11
|
* // ◀️ before
|
|
@@ -17,7 +14,7 @@ import type { ChangeState } from '../utils/change-tracking'
|
|
|
17
14
|
*
|
|
18
15
|
* // ▶️ after
|
|
19
16
|
* var greet = function () {};
|
|
20
|
-
* var a = function
|
|
17
|
+
* var a = function () {};
|
|
21
18
|
*/
|
|
22
19
|
export function removeAnonymousFunctionNames(ast: File): boolean {
|
|
23
20
|
return traverseForChanges(ast, visitor)
|
|
@@ -38,47 +35,7 @@ const visitor: Visitor<ChangeState> = {
|
|
|
38
35
|
) {
|
|
39
36
|
return
|
|
40
37
|
}
|
|
41
|
-
if (inferredName(path) !== id.name) {
|
|
42
|
-
return
|
|
43
|
-
}
|
|
44
38
|
path.node.id = null
|
|
45
39
|
state.changed = true
|
|
46
40
|
},
|
|
47
41
|
}
|
|
48
|
-
|
|
49
|
-
// No inferred name means `.name` would become empty.
|
|
50
|
-
function inferredName(path: NodePath<t.FunctionExpression>): string | null {
|
|
51
|
-
const node = path.node
|
|
52
|
-
const parentPath = path.parentPath
|
|
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
|
-
}
|
|
@@ -69,41 +69,21 @@ function isCompletion(node: t.Node): boolean {
|
|
|
69
69
|
)
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
// Hoisted bindings and referenced lexical TDZ remain observable from reachable
|
|
73
|
-
// code. An unreferenced lexical declaration is inert.
|
|
74
72
|
function isSafeToRemoveAfterCompletion(path: NodePath<t.Statement>): boolean {
|
|
75
73
|
const node = path.node
|
|
76
74
|
if (t.isFunctionDeclaration(node) || (t.isVariableDeclaration(node) && node.kind === 'var')) {
|
|
77
75
|
return false
|
|
78
76
|
}
|
|
79
|
-
|
|
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
|
|
77
|
+
return !nestsHoistedVar(node)
|
|
94
78
|
}
|
|
95
79
|
|
|
96
|
-
function
|
|
80
|
+
function nestsHoistedVar(node: t.Node): boolean {
|
|
97
81
|
let found = false
|
|
98
82
|
t.traverseFast(node, (child) => {
|
|
99
83
|
if (found) {
|
|
100
84
|
return t.traverseFast.skip
|
|
101
85
|
}
|
|
102
86
|
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
87
|
return t.traverseFast.skip
|
|
108
88
|
}
|
|
109
89
|
if (t.isVariableDeclaration(child) && child.kind === 'var') {
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import traverse from '@babel/traverse'
|
|
2
|
-
import type { NodePath,
|
|
2
|
+
import type { NodePath, Visitor } from '@babel/traverse'
|
|
3
3
|
import * as t from '@babel/types'
|
|
4
4
|
import type { File } from '@babel/types'
|
|
5
5
|
|
|
6
6
|
import { isSideEffectFree } from '../analysis/purity'
|
|
7
|
-
import { isDirectEvalCall } from '../utils/ast'
|
|
8
7
|
import type { ChangeState } from '../utils/change-tracking'
|
|
9
|
-
import { hasAnnexBFunctionAlias } from '../utils/paths'
|
|
10
8
|
|
|
11
9
|
/**
|
|
12
10
|
* Removes unused declarations and expression statements that have no effects.
|
|
@@ -28,10 +26,7 @@ export function removeUnusedCode(ast: File): boolean {
|
|
|
28
26
|
// then maintain counts during removal.
|
|
29
27
|
crawlProgramScope(ast)
|
|
30
28
|
let anyChanged = false
|
|
31
|
-
const state:
|
|
32
|
-
changed: true,
|
|
33
|
-
evalReachableScopes: collectEvalReachableScopes(ast),
|
|
34
|
-
}
|
|
29
|
+
const state: ChangeState = { changed: true }
|
|
35
30
|
while (state.changed) {
|
|
36
31
|
state.changed = false
|
|
37
32
|
traverse(ast, sweepVisitor, undefined, state)
|
|
@@ -51,49 +46,19 @@ function crawlProgramScope(ast: File): void {
|
|
|
51
46
|
})
|
|
52
47
|
}
|
|
53
48
|
|
|
54
|
-
|
|
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> = {
|
|
49
|
+
const sweepVisitor: Visitor<ChangeState> = {
|
|
85
50
|
VariableDeclarator(path, state) {
|
|
86
|
-
if (tryRemoveDeclarator(path
|
|
51
|
+
if (tryRemoveDeclarator(path)) {
|
|
87
52
|
state.changed = true
|
|
88
53
|
}
|
|
89
54
|
},
|
|
90
55
|
FunctionDeclaration(path, state) {
|
|
91
|
-
if (tryRemoveFunctionDecl(path
|
|
56
|
+
if (tryRemoveFunctionDecl(path)) {
|
|
92
57
|
state.changed = true
|
|
93
58
|
}
|
|
94
59
|
},
|
|
95
60
|
ClassDeclaration(path, state) {
|
|
96
|
-
if (tryRemoveClassDecl(path
|
|
61
|
+
if (tryRemoveClassDecl(path)) {
|
|
97
62
|
state.changed = true
|
|
98
63
|
}
|
|
99
64
|
},
|
|
@@ -126,10 +91,7 @@ function dereferenceSubtree(rootPath: NodePath): void {
|
|
|
126
91
|
rootPath.traverse(dereferenceVisitor)
|
|
127
92
|
}
|
|
128
93
|
|
|
129
|
-
function tryRemoveDeclarator(
|
|
130
|
-
path: NodePath<t.VariableDeclarator>,
|
|
131
|
-
evalReachableScopes: Set<t.Node>,
|
|
132
|
-
): boolean {
|
|
94
|
+
function tryRemoveDeclarator(path: NodePath<t.VariableDeclarator>): boolean {
|
|
133
95
|
const declaration = path.parentPath
|
|
134
96
|
if (!declaration || !declaration.isVariableDeclaration()) {
|
|
135
97
|
return false
|
|
@@ -156,7 +118,7 @@ function tryRemoveDeclarator(
|
|
|
156
118
|
}
|
|
157
119
|
|
|
158
120
|
const init = path.node.init
|
|
159
|
-
if (init && !isSideEffectFree(init, path.scope
|
|
121
|
+
if (init && !isSideEffectFree(init, path.scope)) {
|
|
160
122
|
return false
|
|
161
123
|
}
|
|
162
124
|
|
|
@@ -167,13 +129,9 @@ function tryRemoveDeclarator(
|
|
|
167
129
|
if (binding.references > 0) {
|
|
168
130
|
return false
|
|
169
131
|
}
|
|
170
|
-
if (binding.constantViolations.length > 0) {
|
|
171
|
-
return false
|
|
172
|
-
}
|
|
173
|
-
if (bindingReachableByEval(binding, evalReachableScopes)) {
|
|
132
|
+
if (declaration.node.kind !== 'const' && binding.constantViolations.length > 0) {
|
|
174
133
|
return false
|
|
175
134
|
}
|
|
176
|
-
|
|
177
135
|
if (path.node.init) {
|
|
178
136
|
dereferenceSubtree(path.get('init') as NodePath)
|
|
179
137
|
}
|
|
@@ -185,17 +143,10 @@ function tryRemoveDeclarator(
|
|
|
185
143
|
return true
|
|
186
144
|
}
|
|
187
145
|
|
|
188
|
-
function tryRemoveFunctionDecl(
|
|
189
|
-
path: NodePath<t.FunctionDeclaration>,
|
|
190
|
-
evalReachableScopes: Set<t.Node>,
|
|
191
|
-
): boolean {
|
|
146
|
+
function tryRemoveFunctionDecl(path: NodePath<t.FunctionDeclaration>): boolean {
|
|
192
147
|
if (isExported(path)) {
|
|
193
148
|
return false
|
|
194
149
|
}
|
|
195
|
-
// Babel misses the sloppy Annex B alias of a block-level function.
|
|
196
|
-
if (hasAnnexBFunctionAlias(path)) {
|
|
197
|
-
return false
|
|
198
|
-
}
|
|
199
150
|
const id = path.node.id
|
|
200
151
|
if (!id) {
|
|
201
152
|
return false
|
|
@@ -211,20 +162,13 @@ function tryRemoveFunctionDecl(
|
|
|
211
162
|
if (binding.constantViolations.length > 0) {
|
|
212
163
|
return false
|
|
213
164
|
}
|
|
214
|
-
if (bindingReachableByEval(binding, evalReachableScopes)) {
|
|
215
|
-
return false
|
|
216
|
-
}
|
|
217
|
-
|
|
218
165
|
dereferenceSubtree(path.get('body') as NodePath)
|
|
219
166
|
path.scope.removeBinding(id.name)
|
|
220
167
|
path.remove()
|
|
221
168
|
return true
|
|
222
169
|
}
|
|
223
170
|
|
|
224
|
-
function tryRemoveClassDecl(
|
|
225
|
-
path: NodePath<t.ClassDeclaration>,
|
|
226
|
-
evalReachableScopes: Set<t.Node>,
|
|
227
|
-
): boolean {
|
|
171
|
+
function tryRemoveClassDecl(path: NodePath<t.ClassDeclaration>): boolean {
|
|
228
172
|
if (isExported(path)) {
|
|
229
173
|
return false
|
|
230
174
|
}
|
|
@@ -234,7 +178,7 @@ function tryRemoveClassDecl(
|
|
|
234
178
|
}
|
|
235
179
|
|
|
236
180
|
// `extends`, computed keys, static fields, and static blocks run on evaluation.
|
|
237
|
-
if (path.node.superClass && !isSideEffectFree(path.node.superClass, path.scope
|
|
181
|
+
if (path.node.superClass && !isSideEffectFree(path.node.superClass, path.scope)) {
|
|
238
182
|
return false
|
|
239
183
|
}
|
|
240
184
|
for (const member of path.node.body.body) {
|
|
@@ -249,7 +193,7 @@ function tryRemoveClassDecl(
|
|
|
249
193
|
'computed' in member &&
|
|
250
194
|
member.computed &&
|
|
251
195
|
'key' in member &&
|
|
252
|
-
!isSideEffectFree(member.key, path.scope
|
|
196
|
+
!isSideEffectFree(member.key, path.scope)
|
|
253
197
|
) {
|
|
254
198
|
return false
|
|
255
199
|
}
|
|
@@ -265,10 +209,6 @@ function tryRemoveClassDecl(
|
|
|
265
209
|
if (binding.constantViolations.length > 0) {
|
|
266
210
|
return false
|
|
267
211
|
}
|
|
268
|
-
if (bindingReachableByEval(binding, evalReachableScopes)) {
|
|
269
|
-
return false
|
|
270
|
-
}
|
|
271
|
-
|
|
272
212
|
dereferenceSubtree(path.get('body') as NodePath)
|
|
273
213
|
if (path.node.superClass) {
|
|
274
214
|
dereferenceSubtree(path.get('superClass') as NodePath)
|
|
@@ -279,7 +219,7 @@ function tryRemoveClassDecl(
|
|
|
279
219
|
}
|
|
280
220
|
|
|
281
221
|
function tryRemoveExpressionStatement(path: NodePath<t.ExpressionStatement>): boolean {
|
|
282
|
-
if (!isSideEffectFree(path.node.expression, path.scope
|
|
222
|
+
if (!isSideEffectFree(path.node.expression, path.scope)) {
|
|
283
223
|
return false
|
|
284
224
|
}
|
|
285
225
|
dereferenceSubtree(path.get('expression') as NodePath)
|
|
@@ -4,15 +4,14 @@ import * as t from '@babel/types'
|
|
|
4
4
|
import type { File } from '@babel/types'
|
|
5
5
|
|
|
6
6
|
import type { TransformContext } from '../options'
|
|
7
|
-
import {
|
|
8
|
-
import { hasAnnexBFunctionAlias, referencesOrWritesVariable } from '../utils/paths'
|
|
7
|
+
import { referencesOrWritesVariable } from '../utils/paths'
|
|
9
8
|
import { mulberry32 } from '../utils/random'
|
|
10
9
|
import { StringGenerator } from '../utils/string-generator'
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
|
-
* Renames bindings and labels with
|
|
14
|
-
*
|
|
15
|
-
* sibling scopes.
|
|
12
|
+
* Renames bindings and labels with the configured string generator. Generated
|
|
13
|
+
* names do not capture unresolved names or bindings still used from an outer
|
|
14
|
+
* scope. Separate sibling scopes may reuse the same names.
|
|
16
15
|
*
|
|
17
16
|
* @example
|
|
18
17
|
* // ◀️ before
|
|
@@ -31,6 +30,7 @@ import { StringGenerator } from '../utils/string-generator'
|
|
|
31
30
|
*/
|
|
32
31
|
export function renameIdentifiers(ast: File, ctx: TransformContext): boolean {
|
|
33
32
|
const generator = new StringGenerator(ctx.stringGeneratorMode, mulberry32(ctx.seed))
|
|
33
|
+
const separatedDirectExports = separateDirectExports(ast.program)
|
|
34
34
|
|
|
35
35
|
let programScope: Scope | undefined
|
|
36
36
|
traverse(ast, {
|
|
@@ -40,14 +40,13 @@ export function renameIdentifiers(ast: File, ctx: TransformContext): boolean {
|
|
|
40
40
|
},
|
|
41
41
|
})
|
|
42
42
|
if (!programScope) {
|
|
43
|
-
return
|
|
43
|
+
return separatedDirectExports
|
|
44
44
|
}
|
|
45
45
|
programScope.crawl()
|
|
46
46
|
|
|
47
47
|
const liveAncestors = new Map<Scope, Set<Binding>>()
|
|
48
48
|
// Reserve unbound names so generated bindings cannot capture them.
|
|
49
49
|
const freeNames = new Set<string>()
|
|
50
|
-
// `with` and direct eval pin bindings to their source names.
|
|
51
50
|
const unrenamable = new Set<Binding>()
|
|
52
51
|
// Reserve pinned source names globally, including before their binding is seen.
|
|
53
52
|
const pinnedNames = new Set<string>()
|
|
@@ -68,9 +67,6 @@ export function renameIdentifiers(ast: File, ctx: TransformContext): boolean {
|
|
|
68
67
|
}
|
|
69
68
|
return
|
|
70
69
|
}
|
|
71
|
-
if (isInsideWith(path)) {
|
|
72
|
-
pin(binding, path.node.name)
|
|
73
|
-
}
|
|
74
70
|
let scope: Scope | undefined = path.scope
|
|
75
71
|
while (scope && scope !== binding.scope) {
|
|
76
72
|
let live = liveAncestors.get(scope)
|
|
@@ -82,30 +78,6 @@ export function renameIdentifiers(ast: File, ctx: TransformContext): boolean {
|
|
|
82
78
|
scope = scope.parent
|
|
83
79
|
}
|
|
84
80
|
},
|
|
85
|
-
CallExpression(path) {
|
|
86
|
-
// Direct eval can read every visible binding by its source name.
|
|
87
|
-
if (!isDirectEvalCall(path.node)) {
|
|
88
|
-
return
|
|
89
|
-
}
|
|
90
|
-
let scope: Scope | undefined = path.scope
|
|
91
|
-
while (scope) {
|
|
92
|
-
for (const name of Object.keys(scope.bindings)) {
|
|
93
|
-
pin(scope.bindings[name]!, name)
|
|
94
|
-
}
|
|
95
|
-
scope = scope.parent
|
|
96
|
-
}
|
|
97
|
-
},
|
|
98
|
-
FunctionDeclaration(path) {
|
|
99
|
-
// ECMA-262 B.3.3 gives sloppy block functions an alias Babel does not model.
|
|
100
|
-
if (!hasAnnexBFunctionAlias(path)) {
|
|
101
|
-
return
|
|
102
|
-
}
|
|
103
|
-
const name = path.node.id?.name
|
|
104
|
-
const binding = name === undefined ? undefined : path.scope.getBinding(name)
|
|
105
|
-
if (binding && name !== undefined) {
|
|
106
|
-
pin(binding, name)
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
81
|
CatchClause(path) {
|
|
110
82
|
// Babel splits a catch parameter and same-named `var` that share one runtime
|
|
111
83
|
// variable. They must keep the same name.
|
|
@@ -169,7 +141,7 @@ export function renameIdentifiers(ast: File, ctx: TransformContext): boolean {
|
|
|
169
141
|
})
|
|
170
142
|
|
|
171
143
|
if (bindingToName.size === 0) {
|
|
172
|
-
return renameLabels(ast, generator)
|
|
144
|
+
return renameLabels(ast, generator) || separatedDirectExports
|
|
173
145
|
}
|
|
174
146
|
|
|
175
147
|
traverse(ast, {
|
|
@@ -199,6 +171,43 @@ export function renameIdentifiers(ast: File, ctx: TransformContext): boolean {
|
|
|
199
171
|
return true
|
|
200
172
|
}
|
|
201
173
|
|
|
174
|
+
function separateDirectExports(program: t.Program): boolean {
|
|
175
|
+
const body: t.Program['body'] = []
|
|
176
|
+
let changed = false
|
|
177
|
+
|
|
178
|
+
for (const statement of program.body) {
|
|
179
|
+
const declaration = t.isExportNamedDeclaration(statement) ? statement.declaration : null
|
|
180
|
+
if (
|
|
181
|
+
!t.isVariableDeclaration(declaration) &&
|
|
182
|
+
!t.isFunctionDeclaration(declaration) &&
|
|
183
|
+
!t.isClassDeclaration(declaration)
|
|
184
|
+
) {
|
|
185
|
+
body.push(statement)
|
|
186
|
+
continue
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const names = Object.keys(t.getBindingIdentifiers(declaration))
|
|
190
|
+
if (names.length === 0) {
|
|
191
|
+
body.push(statement)
|
|
192
|
+
continue
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
body.push(
|
|
196
|
+
declaration,
|
|
197
|
+
t.exportNamedDeclaration(
|
|
198
|
+
null,
|
|
199
|
+
names.map((name) => t.exportSpecifier(t.identifier(name), t.identifier(name))),
|
|
200
|
+
),
|
|
201
|
+
)
|
|
202
|
+
changed = true
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (changed) {
|
|
206
|
+
program.body = body
|
|
207
|
+
}
|
|
208
|
+
return changed
|
|
209
|
+
}
|
|
210
|
+
|
|
202
211
|
function isNonBindingPosition(path: { node: t.Identifier; parent: t.Node }): boolean {
|
|
203
212
|
const node = path.node
|
|
204
213
|
const parent = path.parent
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { NodePath } from '@babel/traverse'
|
|
2
|
+
import type * as t from '@babel/types'
|
|
3
|
+
import type { File } from '@babel/types'
|
|
4
|
+
|
|
5
|
+
import { rewriteStatementLists } from '../utils/statement-lists'
|
|
6
|
+
import type { StatementListContainer } from '../utils/statement-lists'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Moves hoisted function declarations to the end of each program, block, switch
|
|
10
|
+
* case, or static block. It keeps declaration order, including duplicate names.
|
|
11
|
+
*
|
|
12
|
+
* If moving every declaration would turn a string expression into a directive,
|
|
13
|
+
* one declaration stays before the string. Direct exports stay in place.
|
|
14
|
+
* Indirect exports move with their local declaration.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* // ◀️ before
|
|
18
|
+
* function area(r) {
|
|
19
|
+
* return r * r;
|
|
20
|
+
* }
|
|
21
|
+
* log(area(2));
|
|
22
|
+
*
|
|
23
|
+
* // ▶️ after
|
|
24
|
+
* log(area(2));
|
|
25
|
+
* function area(r) {
|
|
26
|
+
* return r * r;
|
|
27
|
+
* }
|
|
28
|
+
*/
|
|
29
|
+
export function reorderFunctionDeclarations(ast: File): boolean {
|
|
30
|
+
return rewriteStatementLists(ast, (statements, container) =>
|
|
31
|
+
reorderedStatements(statements, containerAllowsDirective(container)),
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Only programs and function bodies recognize directives, so only they need the guard.
|
|
36
|
+
function containerAllowsDirective(container: NodePath<StatementListContainer>): boolean {
|
|
37
|
+
if (container.isProgram()) {
|
|
38
|
+
return true
|
|
39
|
+
}
|
|
40
|
+
return container.isBlockStatement() && container.parentPath.isFunction()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function reorderedStatements(
|
|
44
|
+
children: NodePath<t.Statement>[],
|
|
45
|
+
canCreateDirective: boolean,
|
|
46
|
+
): t.Statement[] | null {
|
|
47
|
+
const rest: t.Statement[] = []
|
|
48
|
+
const moved: t.Statement[] = []
|
|
49
|
+
for (const child of children) {
|
|
50
|
+
if (isMovable(child)) {
|
|
51
|
+
moved.push(child.node)
|
|
52
|
+
} else {
|
|
53
|
+
rest.push(child.node)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (moved.length === 0) {
|
|
58
|
+
return null
|
|
59
|
+
}
|
|
60
|
+
if (canCreateDirective && createsDirective(rest)) {
|
|
61
|
+
rest.unshift(moved.shift()!)
|
|
62
|
+
}
|
|
63
|
+
const reordered = [...rest, ...moved]
|
|
64
|
+
return reordered.every((node, index) => node === children[index]?.node) ? null : reordered
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function createsDirective(statements: readonly t.Statement[]): boolean {
|
|
68
|
+
const first = statements[0]
|
|
69
|
+
return (
|
|
70
|
+
first !== undefined &&
|
|
71
|
+
first.type === 'ExpressionStatement' &&
|
|
72
|
+
first.expression.type === 'StringLiteral'
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function isMovable(child: NodePath<t.Statement>): boolean {
|
|
77
|
+
return child.isFunctionDeclaration() && child.node.id !== null
|
|
78
|
+
}
|
|
@@ -4,7 +4,6 @@ import * as t from '@babel/types'
|
|
|
4
4
|
import type { File } from '@babel/types'
|
|
5
5
|
|
|
6
6
|
import type { TransformContext } from '../options'
|
|
7
|
-
import { enclosingScopeHasDirectEval, isInsideWith } from '../utils/ast'
|
|
8
7
|
import type { ChangeState } from '../utils/change-tracking'
|
|
9
8
|
import { mulberry32 } from '../utils/random'
|
|
10
9
|
import { StringGenerator } from '../utils/string-generator'
|
|
@@ -69,8 +68,8 @@ const visitor: Visitor<State> = {
|
|
|
69
68
|
if (path.scope.getBinding(name)) {
|
|
70
69
|
return
|
|
71
70
|
}
|
|
72
|
-
//
|
|
73
|
-
if (isWriteTarget(path)
|
|
71
|
+
// A replacement cannot occupy a write target.
|
|
72
|
+
if (isWriteTarget(path)) {
|
|
74
73
|
return
|
|
75
74
|
}
|
|
76
75
|
const str = t.stringLiteral(state.generator.next())
|
|
@@ -47,8 +47,7 @@ const visitor: Visitor<ChangeState> = {
|
|
|
47
47
|
|
|
48
48
|
function transformSwitch(path: NodePath<t.SwitchStatement>): boolean {
|
|
49
49
|
const node = path.node
|
|
50
|
-
// Separate `if` blocks cannot preserve cross-case lexical scope
|
|
51
|
-
// function hoisting.
|
|
50
|
+
// Separate `if` blocks cannot preserve cross-case lexical scope.
|
|
52
51
|
if (hasBlockScopedDeclaration(node)) {
|
|
53
52
|
return false
|
|
54
53
|
}
|
|
@@ -56,7 +55,7 @@ function transformSwitch(path: NodePath<t.SwitchStatement>): boolean {
|
|
|
56
55
|
lowerToIfElseChain(path)
|
|
57
56
|
return true
|
|
58
57
|
}
|
|
59
|
-
if (!labeledLoweringIsSafe(node, path.scope
|
|
58
|
+
if (!labeledLoweringIsSafe(node, path.scope)) {
|
|
60
59
|
return false
|
|
61
60
|
}
|
|
62
61
|
lowerToLabeledBlock(path)
|
|
@@ -69,8 +68,7 @@ function hasBlockScopedDeclaration(node: t.SwitchStatement): boolean {
|
|
|
69
68
|
if (
|
|
70
69
|
(t.isVariableDeclaration(stmt) && stmt.kind !== 'var') ||
|
|
71
70
|
t.isClassDeclaration(stmt) ||
|
|
72
|
-
t.isFunctionDeclaration(stmt)
|
|
73
|
-
isLabeledFunctionDeclaration(stmt)
|
|
71
|
+
t.isFunctionDeclaration(stmt)
|
|
74
72
|
) {
|
|
75
73
|
return true
|
|
76
74
|
}
|
|
@@ -79,29 +77,14 @@ function hasBlockScopedDeclaration(node: t.SwitchStatement): boolean {
|
|
|
79
77
|
return false
|
|
80
78
|
}
|
|
81
79
|
|
|
82
|
-
// Babel hides an Annex B.3.4 labeled function under LabeledStatement wrappers.
|
|
83
|
-
function isLabeledFunctionDeclaration(stmt: t.Statement): boolean {
|
|
84
|
-
let current: t.Statement = stmt
|
|
85
|
-
while (t.isLabeledStatement(current)) {
|
|
86
|
-
current = current.body
|
|
87
|
-
}
|
|
88
|
-
return t.isFunctionDeclaration(current)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
80
|
// A non-final default needs match results from later cases. Precomputing that
|
|
92
81
|
// result rereads tests, so every test must be pure in this shape.
|
|
93
|
-
function labeledLoweringIsSafe(
|
|
94
|
-
node: t.SwitchStatement,
|
|
95
|
-
scope: Scope,
|
|
96
|
-
referencePath: NodePath,
|
|
97
|
-
): boolean {
|
|
82
|
+
function labeledLoweringIsSafe(node: t.SwitchStatement, scope: Scope): boolean {
|
|
98
83
|
const defaultIndex = node.cases.findIndex((caseClause) => caseClause.test === null)
|
|
99
84
|
if (defaultIndex === -1 || defaultIndex === node.cases.length - 1) {
|
|
100
85
|
return true
|
|
101
86
|
}
|
|
102
|
-
return node.cases.every(
|
|
103
|
-
(caseClause) => caseClause.test == null || isPure(caseClause.test, scope, referencePath),
|
|
104
|
-
)
|
|
87
|
+
return node.cases.every((caseClause) => caseClause.test == null || isPure(caseClause.test, scope))
|
|
105
88
|
}
|
|
106
89
|
|
|
107
90
|
function canLowerToIfElseChain(node: t.SwitchStatement): boolean {
|
|
@@ -2,7 +2,6 @@ import type { NodePath, Visitor } from '@babel/traverse'
|
|
|
2
2
|
import * as t from '@babel/types'
|
|
3
3
|
import type { File } from '@babel/types'
|
|
4
4
|
|
|
5
|
-
import { isInsideWith } from '../utils/ast'
|
|
6
5
|
import { traverseForChanges } from '../utils/change-tracking'
|
|
7
6
|
import type { ChangeState } from '../utils/change-tracking'
|
|
8
7
|
import { isConditionalGate, reorderableToStatement } from '../utils/evaluation-order'
|
|
@@ -35,7 +34,7 @@ export function ternaryToIf(ast: File): boolean {
|
|
|
35
34
|
|
|
36
35
|
const preparationVisitor: Visitor<ChangeState> = {
|
|
37
36
|
ConditionalExpression(path, state) {
|
|
38
|
-
if (!isPathLive(path)
|
|
37
|
+
if (!isPathLive(path)) {
|
|
39
38
|
return
|
|
40
39
|
}
|
|
41
40
|
const stmtPath = path.getStatementParent()
|
|
@@ -50,9 +49,8 @@ const preparationVisitor: Visitor<ChangeState> = {
|
|
|
50
49
|
|
|
51
50
|
const visitor: Visitor<ChangeState> = {
|
|
52
51
|
ConditionalExpression(path, state) {
|
|
53
|
-
// Statement replacement detaches queued siblings.
|
|
54
|
-
|
|
55
|
-
if (!isPathLive(path) || isInsideWith(path)) {
|
|
52
|
+
// Statement replacement detaches queued siblings.
|
|
53
|
+
if (!isPathLive(path)) {
|
|
56
54
|
return
|
|
57
55
|
}
|
|
58
56
|
if (lowerConditional(path)) {
|
|
@@ -129,16 +127,14 @@ function lowerConditional(path: NodePath<t.ConditionalExpression>): boolean {
|
|
|
129
127
|
t.isIdentifier(parent.node.id) &&
|
|
130
128
|
parent.node.init === path.node &&
|
|
131
129
|
parent.parentPath?.isVariableDeclaration() &&
|
|
132
|
-
// Splitting lexical declarations would end TDZ early, and bare `const` is
|
|
133
|
-
// invalid. Keep their initializer behind a result temp.
|
|
134
|
-
parent.parentPath.node.kind === 'var' &&
|
|
135
130
|
parent.parentPath.node.declarations.length === 1 &&
|
|
136
131
|
isStandaloneDeclaration(parent.parentPath)
|
|
137
132
|
) {
|
|
138
133
|
const declPath = parent.parentPath
|
|
139
134
|
const target = parent.node.id
|
|
135
|
+
const declarationKind = declPath.node.kind === 'const' ? 'let' : declPath.node.kind
|
|
140
136
|
declPath.replaceWithMultiple([
|
|
141
|
-
t.variableDeclaration(
|
|
137
|
+
t.variableDeclaration(declarationKind, [t.variableDeclarator(t.cloneNode(target))]),
|
|
142
138
|
t.ifStatement(
|
|
143
139
|
test,
|
|
144
140
|
t.blockStatement(seqWithAssignment(consequent, target)),
|