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
|
@@ -4,7 +4,6 @@ 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 { isInsideWith } from '../utils/ast'
|
|
8
7
|
import { traverseForChanges } from '../utils/change-tracking'
|
|
9
8
|
import type { ChangeState } from '../utils/change-tracking'
|
|
10
9
|
|
|
@@ -90,8 +89,7 @@ const visitor: Visitor<ChangeState> = {
|
|
|
90
89
|
if (!t.isIdentifier(object) || object.name !== 'console') {
|
|
91
90
|
return
|
|
92
91
|
}
|
|
93
|
-
|
|
94
|
-
if (path.scope.getBinding('console') || isInsideWith(path)) {
|
|
92
|
+
if (path.scope.getBinding('console')) {
|
|
95
93
|
return
|
|
96
94
|
}
|
|
97
95
|
// Removing the call also removes argument and computed-key evaluation.
|
|
@@ -108,11 +106,11 @@ function callArgumentsAreInert(
|
|
|
108
106
|
callee: t.MemberExpression | t.OptionalMemberExpression,
|
|
109
107
|
path: NodePath<t.ExpressionStatement>,
|
|
110
108
|
): boolean {
|
|
111
|
-
if (callee.computed && !isSideEffectFree(callee.property, path.scope
|
|
109
|
+
if (callee.computed && !isSideEffectFree(callee.property, path.scope)) {
|
|
112
110
|
return false
|
|
113
111
|
}
|
|
114
112
|
for (const arg of expr.arguments) {
|
|
115
|
-
if (!t.isExpression(arg) || !isSideEffectFree(arg, path.scope
|
|
113
|
+
if (!t.isExpression(arg) || !isSideEffectFree(arg, path.scope)) {
|
|
116
114
|
return false
|
|
117
115
|
}
|
|
118
116
|
}
|
|
@@ -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
|
|
|
@@ -58,10 +57,6 @@ function lowerBinaryAssignment(path: NodePath<t.AssignmentExpression>): boolean
|
|
|
58
57
|
const read = t.cloneNode(left, true)
|
|
59
58
|
|
|
60
59
|
if (t.isMemberExpression(left) && t.isMemberExpression(read)) {
|
|
61
|
-
// `with` can turn a bare object name into a getter or shadow a memo temp.
|
|
62
|
-
if (isInsideWith(path)) {
|
|
63
|
-
return false
|
|
64
|
-
}
|
|
65
60
|
const object = left.object
|
|
66
61
|
if (t.isExpression(object)) {
|
|
67
62
|
const memo = path.scope.maybeGenerateMemoised(object)
|
|
@@ -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
|
|
|
@@ -52,10 +51,6 @@ function lowerLogicalAssignment(path: NodePath<t.AssignmentExpression>): boolean
|
|
|
52
51
|
const lhs = t.cloneNode(left, true)
|
|
53
52
|
|
|
54
53
|
if (t.isMemberExpression(left) && t.isMemberExpression(lhs)) {
|
|
55
|
-
// `with` can turn a bare object name into a getter or shadow a memo temp.
|
|
56
|
-
if (isInsideWith(path)) {
|
|
57
|
-
return false
|
|
58
|
-
}
|
|
59
54
|
const object = left.object
|
|
60
55
|
if (t.isExpression(object)) {
|
|
61
56
|
const memo = path.scope.maybeGenerateMemoised(object)
|
|
@@ -3,7 +3,7 @@ import * as t from '@babel/types'
|
|
|
3
3
|
import type { File } from '@babel/types'
|
|
4
4
|
|
|
5
5
|
import { initializerReaches } from '../analysis/document-order'
|
|
6
|
-
import {
|
|
6
|
+
import { isProtoKey } from '../utils/ast'
|
|
7
7
|
import { traverseForChanges } from '../utils/change-tracking'
|
|
8
8
|
import type { ChangeState } from '../utils/change-tracking'
|
|
9
9
|
import { isCalleeOrTagOf } from '../utils/paths'
|
|
@@ -172,12 +172,7 @@ function performExtraction(
|
|
|
172
172
|
if (!binding) {
|
|
173
173
|
return false
|
|
174
174
|
}
|
|
175
|
-
if (!binding.constant) {
|
|
176
|
-
return false
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// `with` can shadow a synthesized entry binding.
|
|
180
|
-
if (isInsideWith(path)) {
|
|
175
|
+
if (path.node.kind !== 'const' && !binding.constant) {
|
|
181
176
|
return false
|
|
182
177
|
}
|
|
183
178
|
|
|
@@ -196,15 +191,11 @@ function performExtraction(
|
|
|
196
191
|
const seenMembers = new Set<t.MemberExpression>()
|
|
197
192
|
|
|
198
193
|
for (const ref of binding.referencePaths) {
|
|
199
|
-
//
|
|
200
|
-
|
|
194
|
+
// A pre-initializer `var` wrapper read sees `undefined`, while an extracted
|
|
195
|
+
// binding read would not perform the member access.
|
|
196
|
+
if (path.node.kind === 'var' && !initializerReaches(path, ref)) {
|
|
201
197
|
return false
|
|
202
198
|
}
|
|
203
|
-
// `with` can redirect the rewritten reference.
|
|
204
|
-
if (isInsideWith(ref)) {
|
|
205
|
-
return false
|
|
206
|
-
}
|
|
207
|
-
|
|
208
199
|
const parent = ref.parentPath
|
|
209
200
|
if (!parent || !parent.isMemberExpression()) {
|
|
210
201
|
return false
|
|
@@ -4,7 +4,8 @@ import * as t from '@babel/types'
|
|
|
4
4
|
import type { File } from '@babel/types'
|
|
5
5
|
|
|
6
6
|
import { evaluateConstant, isOpaque } from '../analysis/constant'
|
|
7
|
-
import {
|
|
7
|
+
import { isSideEffectFree } from '../analysis/purity'
|
|
8
|
+
import { isInDirectivePrologue } from '../utils/ast'
|
|
8
9
|
import type { ChangeState } from '../utils/change-tracking'
|
|
9
10
|
import { valueToLiteral } from '../utils/literal'
|
|
10
11
|
|
|
@@ -104,18 +105,13 @@ function collectPatchedIntrinsics(ast: File): Set<string> {
|
|
|
104
105
|
markMutationCall(path, poison)
|
|
105
106
|
},
|
|
106
107
|
Identifier(path) {
|
|
107
|
-
// Global eval and Function can patch any intrinsic outside this scan.
|
|
108
108
|
const name = path.node.name
|
|
109
|
-
if (name !== '
|
|
109
|
+
if (name !== 'Function') {
|
|
110
110
|
return
|
|
111
111
|
}
|
|
112
112
|
if (!path.isReferencedIdentifier() || path.scope.getBinding(name)) {
|
|
113
113
|
return
|
|
114
114
|
}
|
|
115
|
-
if (name === 'eval') {
|
|
116
|
-
poison(INTRINSIC_ROOTS)
|
|
117
|
-
return
|
|
118
|
-
}
|
|
119
115
|
const parent = path.parentPath
|
|
120
116
|
if (
|
|
121
117
|
parent &&
|
|
@@ -496,7 +492,7 @@ function isReferenceRequiredPosition(path: NodePath<t.MemberExpression>): boolea
|
|
|
496
492
|
}
|
|
497
493
|
|
|
498
494
|
// Folding a leading call to a string can create a directive and change strictness.
|
|
499
|
-
function foldsIntoDirective(path: NodePath
|
|
495
|
+
function foldsIntoDirective(path: NodePath, replacement: t.Expression): boolean {
|
|
500
496
|
if (!t.isStringLiteral(replacement)) {
|
|
501
497
|
return false
|
|
502
498
|
}
|
|
@@ -506,11 +502,59 @@ function foldsIntoDirective(path: NodePath<t.CallExpression>, replacement: t.Exp
|
|
|
506
502
|
)
|
|
507
503
|
}
|
|
508
504
|
|
|
509
|
-
function
|
|
510
|
-
|
|
511
|
-
|
|
505
|
+
function replaceWithPreservedExpression(
|
|
506
|
+
path: NodePath<t.CallExpression | t.MemberExpression>,
|
|
507
|
+
expression: t.Expression,
|
|
508
|
+
result: t.Expression,
|
|
509
|
+
): void {
|
|
510
|
+
if (!isSideEffectFree(expression, path.scope)) {
|
|
511
|
+
path.replaceWith(t.sequenceExpression([expression, result]))
|
|
512
512
|
return
|
|
513
513
|
}
|
|
514
|
+
if (foldsIntoDirective(path, result)) {
|
|
515
|
+
path.replaceWith(t.sequenceExpression([t.numericLiteral(0), result]))
|
|
516
|
+
return
|
|
517
|
+
}
|
|
518
|
+
path.replaceWith(result)
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function foldArrayLiteralLength(
|
|
522
|
+
path: NodePath<t.MemberExpression>,
|
|
523
|
+
array: t.ArrayExpression,
|
|
524
|
+
): boolean {
|
|
525
|
+
if (array.elements.some((element) => t.isSpreadElement(element))) {
|
|
526
|
+
return false
|
|
527
|
+
}
|
|
528
|
+
const effects = array.elements.filter(
|
|
529
|
+
(element): element is t.Expression =>
|
|
530
|
+
element !== null && t.isExpression(element) && !isSideEffectFree(element, path.scope),
|
|
531
|
+
)
|
|
532
|
+
const length = t.numericLiteral(array.elements.length)
|
|
533
|
+
path.replaceWith(effects.length === 0 ? length : t.sequenceExpression([...effects, length]))
|
|
534
|
+
return true
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function foldStringIndex(
|
|
538
|
+
path: NodePath<t.MemberExpression>,
|
|
539
|
+
receiverNode: t.Expression,
|
|
540
|
+
propertyName: string,
|
|
541
|
+
): boolean {
|
|
542
|
+
const index = Number(propertyName)
|
|
543
|
+
if (!Number.isSafeInteger(index) || index < 0 || String(index) !== propertyName) {
|
|
544
|
+
return false
|
|
545
|
+
}
|
|
546
|
+
const receiver = evaluateConstant(receiverNode, path.scope, path)
|
|
547
|
+
if (!receiver.known || typeof receiver.value !== 'string') {
|
|
548
|
+
return false
|
|
549
|
+
}
|
|
550
|
+
if (index >= receiver.value.length) {
|
|
551
|
+
return false
|
|
552
|
+
}
|
|
553
|
+
replaceWithPreservedExpression(path, receiverNode, t.stringLiteral(receiver.value[index]!))
|
|
554
|
+
return true
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function handleCall(path: NodePath<t.CallExpression>, patchedRoots: Set<string>): void {
|
|
514
558
|
const callee = path.node.callee
|
|
515
559
|
|
|
516
560
|
if (t.isIdentifier(callee)) {
|
|
@@ -558,11 +602,6 @@ function handleMember(path: NodePath<t.MemberExpression>, patchedRoots: Set<stri
|
|
|
558
602
|
if (isReferenceRequiredPosition(path)) {
|
|
559
603
|
return
|
|
560
604
|
}
|
|
561
|
-
// Delay the ancestor walk until cheap shape checks pass.
|
|
562
|
-
if (isInsideWith(path) || enclosingScopeHasDirectEval(path)) {
|
|
563
|
-
return
|
|
564
|
-
}
|
|
565
|
-
|
|
566
605
|
const obj = path.node.object
|
|
567
606
|
|
|
568
607
|
if (t.isIdentifier(obj) && !path.scope.getBinding(obj.name) && !patchedRoots.has(obj.name)) {
|
|
@@ -589,10 +628,18 @@ function handleMember(path: NodePath<t.MemberExpression>, patchedRoots: Set<stri
|
|
|
589
628
|
}
|
|
590
629
|
|
|
591
630
|
if (propName === 'length') {
|
|
631
|
+
if (t.isArrayExpression(obj) && foldArrayLiteralLength(path, obj)) {
|
|
632
|
+
return
|
|
633
|
+
}
|
|
592
634
|
const recv = evaluateConstant(obj, path.scope, path)
|
|
593
635
|
if (recv.known && typeof recv.value === 'string') {
|
|
594
636
|
path.replaceWith(t.numericLiteral(recv.value.length))
|
|
595
637
|
}
|
|
638
|
+
return
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
if (t.isExpression(obj)) {
|
|
642
|
+
foldStringIndex(path, obj, propName)
|
|
596
643
|
}
|
|
597
644
|
}
|
|
598
645
|
|
|
@@ -625,7 +672,6 @@ function foldGlobalCall(
|
|
|
625
672
|
foldNumberCtor(path)
|
|
626
673
|
return
|
|
627
674
|
}
|
|
628
|
-
|
|
629
675
|
if (GLOBAL_FUNCTIONS.has(name)) {
|
|
630
676
|
if (path.scope.getBinding(name)) {
|
|
631
677
|
return
|
|
@@ -131,6 +131,9 @@ function tryFoldToLiteral(
|
|
|
131
131
|
if (!result.known) {
|
|
132
132
|
return false
|
|
133
133
|
}
|
|
134
|
+
if (typeof result.value === 'number' && !Number.isFinite(result.value)) {
|
|
135
|
+
return false
|
|
136
|
+
}
|
|
134
137
|
const lit = valueToLiteral(result.value)
|
|
135
138
|
if (!lit) {
|
|
136
139
|
return false
|
|
@@ -187,7 +190,7 @@ function tryReassociateCommutative(path: NodePath<t.BinaryExpression>, op: Commu
|
|
|
187
190
|
}
|
|
188
191
|
// Grouping constants can move one operand's coercion past another operand's
|
|
189
192
|
// evaluation. Multiple non-constant operands must therefore be pure.
|
|
190
|
-
if (others.length > 1 && !others.every((leaf) => isPure(leaf, path.scope
|
|
193
|
+
if (others.length > 1 && !others.every((leaf) => isPure(leaf, path.scope))) {
|
|
191
194
|
return false
|
|
192
195
|
}
|
|
193
196
|
|
|
@@ -293,7 +296,7 @@ function tryReassociateSubtract(path: NodePath<t.BinaryExpression>): boolean {
|
|
|
293
296
|
}
|
|
294
297
|
// Reassociation can move `base` coercion past a subtractee's evaluation.
|
|
295
298
|
// Non-constant subtractees must be pure.
|
|
296
|
-
if (!otherSubs.every((leaf) => isPure(leaf, path.scope
|
|
299
|
+
if (!otherSubs.every((leaf) => isPure(leaf, path.scope))) {
|
|
297
300
|
return false
|
|
298
301
|
}
|
|
299
302
|
const sum = combineConstants(constSubs, '+')
|
|
@@ -6,14 +6,13 @@ import { traverseForChanges } from '../utils/change-tracking'
|
|
|
6
6
|
import type { ChangeState } from '../utils/change-tracking'
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Rewrites
|
|
10
|
-
*
|
|
9
|
+
* Rewrites function declarations at script top level or directly inside a
|
|
10
|
+
* function body as `var` initializers. It leaves a declaration unchanged when
|
|
11
|
+
* an earlier statement could execute user code before the assignment.
|
|
11
12
|
*
|
|
12
|
-
*
|
|
13
|
-
* still use the `var` binding.
|
|
13
|
+
* Name inference and recursive references still use the `var` binding.
|
|
14
14
|
*
|
|
15
|
-
* Block-level
|
|
16
|
-
* to Annex B aliases and live exports.
|
|
15
|
+
* Block-level and module top-level declarations stay unchanged.
|
|
17
16
|
*
|
|
18
17
|
* @example
|
|
19
18
|
* // ◀️ before
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import traverse from '@babel/traverse'
|
|
2
|
+
import type { NodePath } from '@babel/traverse'
|
|
3
|
+
import * as t from '@babel/types'
|
|
4
|
+
import type { File } from '@babel/types'
|
|
5
|
+
|
|
6
|
+
import type { ChangeState } from '../utils/change-tracking'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Moves `var` declarations to the start of their var scope without moving
|
|
10
|
+
* initializer evaluation.
|
|
11
|
+
*
|
|
12
|
+
* Direct exports stay in place. Static blocks keep separate declarations.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* // ◀️ before
|
|
16
|
+
* function f() {
|
|
17
|
+
* var x = 1;
|
|
18
|
+
* if (c) {
|
|
19
|
+
* var y = g();
|
|
20
|
+
* }
|
|
21
|
+
* return x + y;
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* // ▶️ after
|
|
25
|
+
* function f() {
|
|
26
|
+
* var x, y;
|
|
27
|
+
* x = 1;
|
|
28
|
+
* if (c) {
|
|
29
|
+
* y = g();
|
|
30
|
+
* }
|
|
31
|
+
* return x + y;
|
|
32
|
+
* }
|
|
33
|
+
*/
|
|
34
|
+
export function hoistVariables(ast: File): boolean {
|
|
35
|
+
const state: HoistState = {
|
|
36
|
+
changed: false,
|
|
37
|
+
scopes: new Map(),
|
|
38
|
+
}
|
|
39
|
+
traverse(ast, {
|
|
40
|
+
VariableDeclaration(path) {
|
|
41
|
+
if (path.node.kind !== 'var' || path.parentPath.isExportNamedDeclaration()) {
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
const owner = ownerPath(path)
|
|
45
|
+
if (owner === null) {
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const scope = hoistedScope(state.scopes, owner)
|
|
50
|
+
addNames(scope.names, path.getBindingIdentifiers())
|
|
51
|
+
if (scope.declaration?.node === path.node) {
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
demoteDeclaration(path)
|
|
56
|
+
scope.changed = true
|
|
57
|
+
state.changed = true
|
|
58
|
+
},
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
for (const scope of state.scopes.values()) {
|
|
62
|
+
if (scope.changed) {
|
|
63
|
+
finishDeclaration(scope)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return state.changed
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
type OwnerPath = NodePath<t.Function | t.Program | t.StaticBlock>
|
|
71
|
+
|
|
72
|
+
interface HoistState extends ChangeState {
|
|
73
|
+
scopes: Map<t.Node, HoistedScope>
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface HoistedScope {
|
|
77
|
+
changed: boolean
|
|
78
|
+
declaration: NodePath<t.VariableDeclaration> | null
|
|
79
|
+
names: Map<string, t.Identifier>
|
|
80
|
+
owner: OwnerPath
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function ownerPath(path: NodePath<t.VariableDeclaration>): OwnerPath | null {
|
|
84
|
+
let current: NodePath | null = path.parentPath
|
|
85
|
+
while (current) {
|
|
86
|
+
if (current.isStaticBlock()) {
|
|
87
|
+
return current as OwnerPath
|
|
88
|
+
}
|
|
89
|
+
if (current.isProgram()) {
|
|
90
|
+
return current as OwnerPath
|
|
91
|
+
}
|
|
92
|
+
if (t.isFunction(current.node) && t.isBlockStatement(current.node.body)) {
|
|
93
|
+
return current as OwnerPath
|
|
94
|
+
}
|
|
95
|
+
current = current.parentPath
|
|
96
|
+
}
|
|
97
|
+
return null
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function hoistedScope(scopes: Map<t.Node, HoistedScope>, owner: OwnerPath): HoistedScope {
|
|
101
|
+
const existing = scopes.get(owner.node)
|
|
102
|
+
if (existing) {
|
|
103
|
+
return existing
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const declaration = leadingBareDeclaration(owner)
|
|
107
|
+
const scope: HoistedScope = {
|
|
108
|
+
changed: false,
|
|
109
|
+
declaration,
|
|
110
|
+
names: new Map(),
|
|
111
|
+
owner,
|
|
112
|
+
}
|
|
113
|
+
if (declaration) {
|
|
114
|
+
addNames(scope.names, declaration.getBindingIdentifiers())
|
|
115
|
+
}
|
|
116
|
+
scopes.set(owner.node, scope)
|
|
117
|
+
return scope
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function leadingBareDeclaration(owner: OwnerPath): NodePath<t.VariableDeclaration> | null {
|
|
121
|
+
const first = ownerBody(owner)[0]
|
|
122
|
+
if (!first?.isVariableDeclaration({ kind: 'var' })) {
|
|
123
|
+
return null
|
|
124
|
+
}
|
|
125
|
+
return first.node.declarations.every((declarator) => declarator.init == null) ? first : null
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function ownerBody(owner: OwnerPath): NodePath<t.Statement>[] {
|
|
129
|
+
if (owner.isFunction()) {
|
|
130
|
+
const body = owner.get('body')
|
|
131
|
+
return body.isBlockStatement() ? (body.get('body') as NodePath<t.Statement>[]) : []
|
|
132
|
+
}
|
|
133
|
+
return owner.get('body') as NodePath<t.Statement>[]
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function demoteDeclaration(path: NodePath<t.VariableDeclaration>): void {
|
|
137
|
+
const parent = path.parentPath
|
|
138
|
+
if (parent.isForStatement() && path.key === 'init') {
|
|
139
|
+
demoteForInit(path)
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
if ((parent.isForInStatement() || parent.isForOfStatement()) && path.key === 'left') {
|
|
143
|
+
demoteForX(path, parent)
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const assignments = initializerAssignments(path.node).map((expression) =>
|
|
148
|
+
t.expressionStatement(expression),
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
if (typeof path.listKey === 'string') {
|
|
152
|
+
if (assignments.length === 0) {
|
|
153
|
+
path.remove()
|
|
154
|
+
} else {
|
|
155
|
+
path.replaceWithMultiple(assignments)
|
|
156
|
+
}
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
if (assignments.length === 0) {
|
|
160
|
+
path.replaceWith(t.emptyStatement())
|
|
161
|
+
} else if (assignments.length === 1) {
|
|
162
|
+
path.replaceWith(assignments[0]!)
|
|
163
|
+
} else {
|
|
164
|
+
path.replaceWith(t.blockStatement(assignments))
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function demoteForInit(path: NodePath<t.VariableDeclaration>): void {
|
|
169
|
+
const assignments = initializerAssignments(path.node)
|
|
170
|
+
if (assignments.length === 0) {
|
|
171
|
+
path.remove()
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
const replacement = assignments.length === 1 ? assignments[0]! : t.sequenceExpression(assignments)
|
|
175
|
+
path.replaceWith(replacement)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function demoteForX(
|
|
179
|
+
path: NodePath<t.VariableDeclaration>,
|
|
180
|
+
parent: NodePath<t.ForInStatement | t.ForOfStatement>,
|
|
181
|
+
): void {
|
|
182
|
+
const declarator = path.node.declarations[0]!
|
|
183
|
+
const target = declarator.id as t.ArrayPattern | t.Identifier | t.ObjectPattern
|
|
184
|
+
if (declarator.init) {
|
|
185
|
+
const assignment = initializerAssignment(declarator, target)
|
|
186
|
+
const right = parent.get('right') as NodePath<t.Expression>
|
|
187
|
+
right.replaceWith(t.sequenceExpression([assignment, right.node]))
|
|
188
|
+
}
|
|
189
|
+
path.replaceWith(t.cloneNode(target))
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function initializerAssignments(node: t.VariableDeclaration): t.AssignmentExpression[] {
|
|
193
|
+
const assignments: t.AssignmentExpression[] = []
|
|
194
|
+
for (const declarator of node.declarations) {
|
|
195
|
+
if (declarator.init != null) {
|
|
196
|
+
const target = declarator.id as t.ArrayPattern | t.Identifier | t.ObjectPattern
|
|
197
|
+
assignments.push(initializerAssignment(declarator, target))
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return assignments
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function initializerAssignment(
|
|
204
|
+
declarator: t.VariableDeclarator,
|
|
205
|
+
target: t.ArrayPattern | t.Identifier | t.ObjectPattern,
|
|
206
|
+
): t.AssignmentExpression {
|
|
207
|
+
return t.assignmentExpression('=', t.cloneNode(target), declarator.init!)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function addNames(
|
|
211
|
+
names: Map<string, t.Identifier>,
|
|
212
|
+
identifiers: Record<string, t.Identifier>,
|
|
213
|
+
): void {
|
|
214
|
+
for (const [name, identifier] of Object.entries(identifiers)) {
|
|
215
|
+
if (!names.has(name)) {
|
|
216
|
+
names.set(name, identifier)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function finishDeclaration(scope: HoistedScope): void {
|
|
222
|
+
const existingNames = new Set<string>()
|
|
223
|
+
if (scope.declaration) {
|
|
224
|
+
for (const name of Object.keys(scope.declaration.getBindingIdentifiers())) {
|
|
225
|
+
existingNames.add(name)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const declarators = [...scope.names]
|
|
229
|
+
.filter(([name]) => !existingNames.has(name))
|
|
230
|
+
.map(([, identifier]) => t.variableDeclarator(t.cloneNode(identifier)))
|
|
231
|
+
|
|
232
|
+
if (scope.declaration) {
|
|
233
|
+
if (declarators.length > 0) {
|
|
234
|
+
scope.declaration.pushContainer('declarations', declarators)
|
|
235
|
+
}
|
|
236
|
+
return
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (declarators.length === 0) {
|
|
240
|
+
return
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const declaration = t.variableDeclaration('var', declarators)
|
|
244
|
+
insertDeclaration(scope.owner, declaration)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function insertDeclaration(owner: OwnerPath, declaration: t.VariableDeclaration): void {
|
|
248
|
+
if (owner.isFunction()) {
|
|
249
|
+
const body = owner.get('body')
|
|
250
|
+
if (body.isBlockStatement()) {
|
|
251
|
+
body.unshiftContainer('body', declaration)
|
|
252
|
+
}
|
|
253
|
+
return
|
|
254
|
+
}
|
|
255
|
+
if (owner.isProgram()) {
|
|
256
|
+
owner.unshiftContainer('body', declaration)
|
|
257
|
+
return
|
|
258
|
+
}
|
|
259
|
+
if (owner.isStaticBlock()) {
|
|
260
|
+
owner.unshiftContainer('body', declaration)
|
|
261
|
+
}
|
|
262
|
+
}
|