webfuscator 1.3.0 → 1.3.2
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/README.md +67 -0
- package/dist/analysis/constant.d.ts.map +1 -1
- package/dist/analysis/constant.js +10 -26
- package/dist/analysis/constant.js.map +1 -1
- package/dist/analysis/inlineability.d.ts.map +1 -1
- package/dist/analysis/inlineability.js +4 -12
- 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/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 -8
- 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 +1 -2
- package/dist/transforms/conceal-strings.d.ts.map +1 -1
- package/dist/transforms/conceal-strings.js +2 -7
- package/dist/transforms/conceal-strings.js.map +1 -1
- 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 -94
- package/dist/transforms/const-let-to-var.js.map +1 -1
- package/dist/transforms/const-to-let.d.ts +1 -2
- package/dist/transforms/const-to-let.d.ts.map +1 -1
- package/dist/transforms/const-to-let.js +1 -6
- 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.js +3 -11
- package/dist/transforms/fold-builtin-methods.js.map +1 -1
- package/dist/transforms/fold-constants.js +2 -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 +1 -2
- package/dist/transforms/hoist-variables.d.ts.map +1 -1
- package/dist/transforms/hoist-variables.js +2 -11
- package/dist/transforms/hoist-variables.js.map +1 -1
- package/dist/transforms/inline-functions.js +11 -39
- 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 +2 -5
- package/dist/transforms/mangle-properties.js.map +1 -1
- 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/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 +4 -16
- package/dist/transforms/pack-declarations-into-parameters.js.map +1 -1
- package/dist/transforms/pack.d.ts +4 -11
- package/dist/transforms/pack.d.ts.map +1 -1
- package/dist/transforms/pack.js +4 -17
- 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 +5 -10
- 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 +4 -20
- package/dist/transforms/rename-identifiers.js.map +1 -1
- package/dist/transforms/reorder-function-declarations.d.ts +1 -2
- package/dist/transforms/reorder-function-declarations.d.ts.map +1 -1
- package/dist/transforms/reorder-function-declarations.js +2 -4
- package/dist/transforms/reorder-function-declarations.js.map +1 -1
- package/dist/transforms/specials-to-strings.d.ts.map +1 -1
- package/dist/transforms/specials-to-strings.js +2 -4
- 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 -8
- 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 +0 -1
- package/dist/utils/ast.d.ts.map +1 -1
- package/dist/utils/ast.js +0 -26
- 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/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/package.json +3 -13
- package/src/analysis/constant.ts +12 -30
- package/src/analysis/inlineability.ts +4 -12
- package/src/analysis/purity.ts +29 -92
- 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 -8
- package/src/transforms/collapse-single-use-temps.ts +12 -23
- package/src/transforms/conceal-strings.ts +3 -8
- package/src/transforms/const-let-to-var.ts +9 -100
- package/src/transforms/const-to-let.ts +1 -6
- 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 +3 -12
- package/src/transforms/fold-constants.ts +2 -2
- package/src/transforms/function-declaration-to-expression.ts +5 -6
- package/src/transforms/hoist-variables.ts +2 -12
- package/src/transforms/inline-functions.ts +16 -46
- package/src/transforms/logical-to-ternary.ts +5 -10
- package/src/transforms/mangle-properties.ts +2 -5
- package/src/transforms/nullish-coalescing-to-ternary.ts +7 -13
- 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 +10 -16
- package/src/transforms/pack.ts +4 -17
- 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 +5 -10
- package/src/transforms/rename-identifiers.ts +4 -20
- package/src/transforms/reorder-function-declarations.ts +2 -4
- package/src/transforms/specials-to-strings.ts +2 -4
- 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 -8
- package/src/transforms/yodify-conditions.ts +3 -4
- package/src/utils/ast.ts +0 -29
- package/src/utils/evaluation-order.ts +1 -1
- package/src/utils/logical-lowering.ts +7 -11
- package/src/utils/nullish.ts +23 -0
- package/src/utils/paths.ts +0 -32
package/src/transforms/pack.ts
CHANGED
|
@@ -5,7 +5,6 @@ import * as t from '@babel/types'
|
|
|
5
5
|
import type { File } from '@babel/types'
|
|
6
6
|
|
|
7
7
|
import type { TransformContext } from '../options'
|
|
8
|
-
import { isInsideWith } from '../utils/ast'
|
|
9
8
|
import { isCalleeOrTagOf, isInStrictContext, referencesOrWritesVariable } from '../utils/paths'
|
|
10
9
|
import { mulberry32 } from '../utils/random'
|
|
11
10
|
import { StringGenerator } from '../utils/string-generator'
|
|
@@ -108,17 +107,10 @@ const STANDARD_GLOBALS: readonly string[] = [
|
|
|
108
107
|
]
|
|
109
108
|
|
|
110
109
|
/**
|
|
111
|
-
* Serializes the whole program
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
* none distinguishable from a global at build time, so free names are routed
|
|
116
|
-
* through a parameter object whose accessors run in the original top-level scope
|
|
117
|
-
* and resolve each name there. Imports remain above the call. Names inside
|
|
118
|
-
* `with` stay bare so the object still controls lookup. A Function body cannot
|
|
119
|
-
* contain exports, so pack throws when it finds one. Code that needs
|
|
120
|
-
* `import.meta`, top-level await, dynamic `import()`, or top-level `arguments`
|
|
121
|
-
* will not run after the move.
|
|
110
|
+
* Serializes the whole program and runs it through the `Function` constructor.
|
|
111
|
+
* Imports stay outside the generated function. Reads and writes to other free
|
|
112
|
+
* names go through accessors created in the original top-level scope. Export
|
|
113
|
+
* statements are rejected because they cannot appear in a `Function` body.
|
|
122
114
|
*
|
|
123
115
|
* @example
|
|
124
116
|
* // ◀️ before
|
|
@@ -233,11 +225,6 @@ function packProgram(programPath: NodePath<t.Program>, ctx: TransformContext): b
|
|
|
233
225
|
if (name === objectName || name === 'arguments') {
|
|
234
226
|
return false
|
|
235
227
|
}
|
|
236
|
-
// Inside `with`, a free name may resolve to a property of the with object at
|
|
237
|
-
// runtime, so routing it to the top-level accessor would change behavior.
|
|
238
|
-
if (isInsideWith(path)) {
|
|
239
|
-
return false
|
|
240
|
-
}
|
|
241
228
|
if (!referencesOrWritesVariable(path)) {
|
|
242
229
|
return false
|
|
243
230
|
}
|
|
@@ -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') {
|
|
@@ -5,7 +5,6 @@ import type { File } from '@babel/types'
|
|
|
5
5
|
|
|
6
6
|
import { isSideEffectFree } from '../analysis/purity'
|
|
7
7
|
import type { ChangeState } from '../utils/change-tracking'
|
|
8
|
-
import { hasAnnexBFunctionAlias } from '../utils/paths'
|
|
9
8
|
|
|
10
9
|
/**
|
|
11
10
|
* Removes unused declarations and expression statements that have no effects.
|
|
@@ -119,7 +118,7 @@ function tryRemoveDeclarator(path: NodePath<t.VariableDeclarator>): boolean {
|
|
|
119
118
|
}
|
|
120
119
|
|
|
121
120
|
const init = path.node.init
|
|
122
|
-
if (init && !isSideEffectFree(init, path.scope
|
|
121
|
+
if (init && !isSideEffectFree(init, path.scope)) {
|
|
123
122
|
return false
|
|
124
123
|
}
|
|
125
124
|
|
|
@@ -130,7 +129,7 @@ function tryRemoveDeclarator(path: NodePath<t.VariableDeclarator>): boolean {
|
|
|
130
129
|
if (binding.references > 0) {
|
|
131
130
|
return false
|
|
132
131
|
}
|
|
133
|
-
if (binding.constantViolations.length > 0) {
|
|
132
|
+
if (declaration.node.kind !== 'const' && binding.constantViolations.length > 0) {
|
|
134
133
|
return false
|
|
135
134
|
}
|
|
136
135
|
if (path.node.init) {
|
|
@@ -148,10 +147,6 @@ function tryRemoveFunctionDecl(path: NodePath<t.FunctionDeclaration>): boolean {
|
|
|
148
147
|
if (isExported(path)) {
|
|
149
148
|
return false
|
|
150
149
|
}
|
|
151
|
-
// Babel misses the sloppy Annex B alias of a block-level function.
|
|
152
|
-
if (hasAnnexBFunctionAlias(path)) {
|
|
153
|
-
return false
|
|
154
|
-
}
|
|
155
150
|
const id = path.node.id
|
|
156
151
|
if (!id) {
|
|
157
152
|
return false
|
|
@@ -183,7 +178,7 @@ function tryRemoveClassDecl(path: NodePath<t.ClassDeclaration>): boolean {
|
|
|
183
178
|
}
|
|
184
179
|
|
|
185
180
|
// `extends`, computed keys, static fields, and static blocks run on evaluation.
|
|
186
|
-
if (path.node.superClass && !isSideEffectFree(path.node.superClass, path.scope
|
|
181
|
+
if (path.node.superClass && !isSideEffectFree(path.node.superClass, path.scope)) {
|
|
187
182
|
return false
|
|
188
183
|
}
|
|
189
184
|
for (const member of path.node.body.body) {
|
|
@@ -198,7 +193,7 @@ function tryRemoveClassDecl(path: NodePath<t.ClassDeclaration>): boolean {
|
|
|
198
193
|
'computed' in member &&
|
|
199
194
|
member.computed &&
|
|
200
195
|
'key' in member &&
|
|
201
|
-
!isSideEffectFree(member.key, path.scope
|
|
196
|
+
!isSideEffectFree(member.key, path.scope)
|
|
202
197
|
) {
|
|
203
198
|
return false
|
|
204
199
|
}
|
|
@@ -224,7 +219,7 @@ function tryRemoveClassDecl(path: NodePath<t.ClassDeclaration>): boolean {
|
|
|
224
219
|
}
|
|
225
220
|
|
|
226
221
|
function tryRemoveExpressionStatement(path: NodePath<t.ExpressionStatement>): boolean {
|
|
227
|
-
if (!isSideEffectFree(path.node.expression, path.scope
|
|
222
|
+
if (!isSideEffectFree(path.node.expression, path.scope)) {
|
|
228
223
|
return false
|
|
229
224
|
}
|
|
230
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
|
|
@@ -48,7 +47,6 @@ export function renameIdentifiers(ast: File, ctx: TransformContext): boolean {
|
|
|
48
47
|
const liveAncestors = new Map<Scope, Set<Binding>>()
|
|
49
48
|
// Reserve unbound names so generated bindings cannot capture them.
|
|
50
49
|
const freeNames = new Set<string>()
|
|
51
|
-
// A reference inside `with` pins its binding to the source name.
|
|
52
50
|
const unrenamable = new Set<Binding>()
|
|
53
51
|
// Reserve pinned source names globally, including before their binding is seen.
|
|
54
52
|
const pinnedNames = new Set<string>()
|
|
@@ -69,9 +67,6 @@ export function renameIdentifiers(ast: File, ctx: TransformContext): boolean {
|
|
|
69
67
|
}
|
|
70
68
|
return
|
|
71
69
|
}
|
|
72
|
-
if (isInsideWith(path)) {
|
|
73
|
-
pin(binding, path.node.name)
|
|
74
|
-
}
|
|
75
70
|
let scope: Scope | undefined = path.scope
|
|
76
71
|
while (scope && scope !== binding.scope) {
|
|
77
72
|
let live = liveAncestors.get(scope)
|
|
@@ -83,17 +78,6 @@ export function renameIdentifiers(ast: File, ctx: TransformContext): boolean {
|
|
|
83
78
|
scope = scope.parent
|
|
84
79
|
}
|
|
85
80
|
},
|
|
86
|
-
FunctionDeclaration(path) {
|
|
87
|
-
// ECMA-262 B.3.3 gives sloppy block functions an alias Babel does not model.
|
|
88
|
-
if (!hasAnnexBFunctionAlias(path)) {
|
|
89
|
-
return
|
|
90
|
-
}
|
|
91
|
-
const name = path.node.id?.name
|
|
92
|
-
const binding = name === undefined ? undefined : path.scope.getBinding(name)
|
|
93
|
-
if (binding && name !== undefined) {
|
|
94
|
-
pin(binding, name)
|
|
95
|
-
}
|
|
96
|
-
},
|
|
97
81
|
CatchClause(path) {
|
|
98
82
|
// Babel splits a catch parameter and same-named `var` that share one runtime
|
|
99
83
|
// variable. They must keep the same name.
|
|
@@ -2,14 +2,12 @@ import type { NodePath } from '@babel/traverse'
|
|
|
2
2
|
import type * as t from '@babel/types'
|
|
3
3
|
import type { File } from '@babel/types'
|
|
4
4
|
|
|
5
|
-
import { hasAnnexBFunctionAlias } from '../utils/paths'
|
|
6
5
|
import { rewriteStatementLists } from '../utils/statement-lists'
|
|
7
6
|
import type { StatementListContainer } from '../utils/statement-lists'
|
|
8
7
|
|
|
9
8
|
/**
|
|
10
9
|
* Moves hoisted function declarations to the end of each program, block, switch
|
|
11
|
-
* case, or static block. It keeps declaration order, including duplicate names
|
|
12
|
-
* and leaves sloppy Annex B block functions in place.
|
|
10
|
+
* case, or static block. It keeps declaration order, including duplicate names.
|
|
13
11
|
*
|
|
14
12
|
* If moving every declaration would turn a string expression into a directive,
|
|
15
13
|
* one declaration stays before the string. Direct exports stay in place.
|
|
@@ -76,5 +74,5 @@ function createsDirective(statements: readonly t.Statement[]): boolean {
|
|
|
76
74
|
}
|
|
77
75
|
|
|
78
76
|
function isMovable(child: NodePath<t.Statement>): boolean {
|
|
79
|
-
return child.isFunctionDeclaration() && child.node.id !== null
|
|
77
|
+
return child.isFunctionDeclaration() && child.node.id !== null
|
|
80
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 { 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,9 +68,8 @@ const visitor: Visitor<State> = {
|
|
|
69
68
|
if (path.scope.getBinding(name)) {
|
|
70
69
|
return
|
|
71
70
|
}
|
|
72
|
-
// A replacement cannot occupy a write target.
|
|
73
|
-
|
|
74
|
-
if (isWriteTarget(path) || isInsideWith(path)) {
|
|
71
|
+
// A replacement cannot occupy a write target.
|
|
72
|
+
if (isWriteTarget(path)) {
|
|
75
73
|
return
|
|
76
74
|
}
|
|
77
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)),
|
|
@@ -2,7 +2,6 @@ import type { Binding, 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
|
|
|
@@ -44,10 +43,6 @@ const visitor: Visitor<ChangeState> = {
|
|
|
44
43
|
if (!prefix && !valueIsDiscarded(path)) {
|
|
45
44
|
return
|
|
46
45
|
}
|
|
47
|
-
// `with` can retarget the name and defeat the numeric proof.
|
|
48
|
-
if (isInsideWith(path)) {
|
|
49
|
-
return
|
|
50
|
-
}
|
|
51
46
|
const binding = path.scope.getBinding(argument.name)
|
|
52
47
|
if (!isNumericBinding(binding)) {
|
|
53
48
|
return
|
|
@@ -75,19 +70,22 @@ function valueIsDiscarded(path: NodePath<t.UpdateExpression>): boolean {
|
|
|
75
70
|
return false
|
|
76
71
|
}
|
|
77
72
|
|
|
78
|
-
//
|
|
73
|
+
// Mutable bindings must stay numeric. A const write cannot change its value.
|
|
79
74
|
function isNumericBinding(binding: Binding | undefined): boolean {
|
|
80
|
-
if (!binding || (binding.kind !== 'var' && binding.kind !== 'let')) {
|
|
75
|
+
if (!binding || (binding.kind !== 'var' && binding.kind !== 'let' && binding.kind !== 'const')) {
|
|
81
76
|
return false
|
|
82
77
|
}
|
|
83
78
|
const declarator = binding.path.node
|
|
84
79
|
if (!t.isVariableDeclarator(declarator) || !t.isNumericLiteral(declarator.init)) {
|
|
85
80
|
return false
|
|
86
81
|
}
|
|
87
|
-
// Babel
|
|
82
|
+
// Babel does not record each value assigned by a `for-in` or `for-of` head.
|
|
88
83
|
if (isForInOfLoopVariable(binding)) {
|
|
89
84
|
return false
|
|
90
85
|
}
|
|
86
|
+
if (binding.kind === 'const') {
|
|
87
|
+
return true
|
|
88
|
+
}
|
|
91
89
|
return binding.constantViolations.every((violation) => keepsNumeric(violation.node))
|
|
92
90
|
}
|
|
93
91
|
|
|
@@ -7,10 +7,9 @@ import type { ChangeState } from '../utils/change-tracking'
|
|
|
7
7
|
import { isLiteralShaped } from '../utils/literal'
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Moves a
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* Unary operations over literals also count when their coercion cannot act.
|
|
10
|
+
* Moves a right operand made from literals and unary operators to the left of a
|
|
11
|
+
* comparison when swapping does not change coercion order. Relational operators
|
|
12
|
+
* are flipped after swapping.
|
|
14
13
|
*
|
|
15
14
|
* @example
|
|
16
15
|
* // ◀️ before
|
package/src/utils/ast.ts
CHANGED
|
@@ -69,32 +69,3 @@ export function isNonComputedKey(path: NodePath): boolean {
|
|
|
69
69
|
'key' in parent && parent.key === path.node && 'computed' in parent && parent.computed === false
|
|
70
70
|
)
|
|
71
71
|
}
|
|
72
|
-
|
|
73
|
-
// `with` can shadow names outside Babel's scope table.
|
|
74
|
-
export function isInsideWith(path: NodePath): boolean {
|
|
75
|
-
if (!programHasWith(path.scope.getProgramParent().block)) {
|
|
76
|
-
return false
|
|
77
|
-
}
|
|
78
|
-
return path.findParent((ancestor) => ancestor.isWithStatement()) !== null
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Cache the program-wide scan because `isInsideWith` runs for many identifiers.
|
|
82
|
-
// Transforms never add `with`, so a cached miss remains valid.
|
|
83
|
-
const programWith = new WeakMap<t.Node, boolean>()
|
|
84
|
-
|
|
85
|
-
function programHasWith(programNode: t.Node): boolean {
|
|
86
|
-
const cached = programWith.get(programNode)
|
|
87
|
-
if (cached !== undefined) {
|
|
88
|
-
return cached
|
|
89
|
-
}
|
|
90
|
-
let found = false
|
|
91
|
-
t.traverseFast(programNode, (node) => {
|
|
92
|
-
if (!t.isWithStatement(node)) {
|
|
93
|
-
return
|
|
94
|
-
}
|
|
95
|
-
found = true
|
|
96
|
-
return t.traverseFast.stop
|
|
97
|
-
})
|
|
98
|
-
programWith.set(programNode, found)
|
|
99
|
-
return found
|
|
100
|
-
}
|
|
@@ -133,7 +133,7 @@ function precedingSiblings<T extends NodePath>(
|
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
function isPurePath(path: NodePath): boolean {
|
|
136
|
-
return isPure(path.node, path.scope
|
|
136
|
+
return isPure(path.node, path.scope)
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
function impure(paths: (NodePath | null | undefined)[]): boolean {
|
|
@@ -9,7 +9,7 @@ import { isStandaloneDeclaration } from './paths'
|
|
|
9
9
|
// x = L;
|
|
10
10
|
// if (<fallbackTest(x)>) x = R;
|
|
11
11
|
//
|
|
12
|
-
// `fallbackTest` selects
|
|
12
|
+
// `fallbackTest` selects the short-circuit condition. The parent must accept a
|
|
13
13
|
// multi-statement replacement.
|
|
14
14
|
export function lowerLogicalWriteStatement(
|
|
15
15
|
path: NodePath<t.LogicalExpression>,
|
|
@@ -29,21 +29,20 @@ export function lowerLogicalWriteStatement(
|
|
|
29
29
|
const declStmt = parent.parentPath
|
|
30
30
|
if (
|
|
31
31
|
!declStmt?.isVariableDeclaration() ||
|
|
32
|
-
// The split would emit illegal `const x;` and then write it.
|
|
33
|
-
declStmt.node.kind === 'const' ||
|
|
34
32
|
declStmt.node.declarations.length !== 1 ||
|
|
35
33
|
!isStandaloneDeclaration(declStmt)
|
|
36
34
|
) {
|
|
37
35
|
return false
|
|
38
36
|
}
|
|
39
37
|
const target = parent.node.id
|
|
40
|
-
//
|
|
41
|
-
// side
|
|
42
|
-
if (rightCanObserveTarget(node.right, target.name)) {
|
|
38
|
+
// A `var` initializer can observe its previous value while evaluating the
|
|
39
|
+
// right side. Reads before lexical initialization are intentionally ignored.
|
|
40
|
+
if (declStmt.node.kind === 'var' && rightCanObserveTarget(node.right, target.name)) {
|
|
43
41
|
return false
|
|
44
42
|
}
|
|
43
|
+
const declarationKind = declStmt.node.kind === 'const' ? 'let' : declStmt.node.kind
|
|
45
44
|
declStmt.replaceWithMultiple([
|
|
46
|
-
t.variableDeclaration(
|
|
45
|
+
t.variableDeclaration(declarationKind, [
|
|
47
46
|
t.variableDeclarator(t.cloneNode(target), node.left),
|
|
48
47
|
]),
|
|
49
48
|
guardedReassignment(target, fallbackTest, node.right),
|
|
@@ -68,10 +67,7 @@ export function lowerLogicalWriteStatement(
|
|
|
68
67
|
return false
|
|
69
68
|
}
|
|
70
69
|
// Writing first would change `x = 0 || x` before the right side reads `x`.
|
|
71
|
-
if (
|
|
72
|
-
!isSideEffectFree(node.right, path.scope, path) ||
|
|
73
|
-
referencesName(node.right, target.name)
|
|
74
|
-
) {
|
|
70
|
+
if (!isSideEffectFree(node.right, path.scope) || referencesName(node.right, target.name)) {
|
|
75
71
|
return false
|
|
76
72
|
}
|
|
77
73
|
exprStmt.replaceWithMultiple([
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as t from '@babel/types'
|
|
2
|
+
|
|
3
|
+
export function buildNullishCheck(
|
|
4
|
+
firstRead: t.Expression,
|
|
5
|
+
secondRead: t.Expression,
|
|
6
|
+
): t.ConditionalExpression {
|
|
7
|
+
return t.conditionalExpression(
|
|
8
|
+
t.binaryExpression('===', firstRead, t.nullLiteral()),
|
|
9
|
+
t.booleanLiteral(true),
|
|
10
|
+
t.binaryExpression('===', secondRead, t.unaryExpression('void', t.numericLiteral(0))),
|
|
11
|
+
)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function buildNonNullishCheck(
|
|
15
|
+
firstRead: t.Expression,
|
|
16
|
+
secondRead: t.Expression,
|
|
17
|
+
): t.ConditionalExpression {
|
|
18
|
+
return t.conditionalExpression(
|
|
19
|
+
t.binaryExpression('!==', firstRead, t.nullLiteral()),
|
|
20
|
+
t.binaryExpression('!==', secondRead, t.unaryExpression('void', t.numericLiteral(0))),
|
|
21
|
+
t.booleanLiteral(false),
|
|
22
|
+
)
|
|
23
|
+
}
|
package/src/utils/paths.ts
CHANGED
|
@@ -69,35 +69,3 @@ export function isInStrictContext(path: NodePath): boolean {
|
|
|
69
69
|
function hasUseStrictDirective(directives: readonly t.Directive[]): boolean {
|
|
70
70
|
return directives.some((directive) => directive.value.value === 'use strict')
|
|
71
71
|
}
|
|
72
|
-
|
|
73
|
-
// ECMA-262 B.3.3 gives sloppy block-level functions a `var` alias that Babel
|
|
74
|
-
// omits from the enclosing scope. Renaming, moving, or removing one can expose it.
|
|
75
|
-
export function hasAnnexBFunctionAlias(path: NodePath): boolean {
|
|
76
|
-
if (!path.isFunctionDeclaration()) {
|
|
77
|
-
return false
|
|
78
|
-
}
|
|
79
|
-
const parent = path.parentPath
|
|
80
|
-
if (!parent || parent.isProgram()) {
|
|
81
|
-
return false
|
|
82
|
-
}
|
|
83
|
-
if (
|
|
84
|
-
parent.isBlockStatement() &&
|
|
85
|
-
parent.key === 'body' &&
|
|
86
|
-
parent.parentPath != null &&
|
|
87
|
-
parent.parentPath.isFunction()
|
|
88
|
-
) {
|
|
89
|
-
return false
|
|
90
|
-
}
|
|
91
|
-
return !isInStrictContext(parent)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// A later write to `const` must keep throwing after transformation.
|
|
95
|
-
export function hasConstantViolation(path: NodePath<t.VariableDeclaration>): boolean {
|
|
96
|
-
for (const name of Object.keys(path.getBindingIdentifiers())) {
|
|
97
|
-
const binding = path.scope.getBinding(name)
|
|
98
|
-
if (binding && binding.constantViolations.length > 0) {
|
|
99
|
-
return true
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
return false
|
|
103
|
-
}
|