webfuscator 1.2.1 → 1.3.0
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/dist/analysis/constant.js +3 -4
- package/dist/analysis/constant.js.map +1 -1
- package/dist/analysis/inlineability.js +1 -1
- package/dist/analysis/inlineability.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 +3 -3
- package/dist/preparation/expand-destructuring.js +6 -6
- package/dist/preparation/expand-destructuring.js.map +1 -1
- package/dist/transforms/arrow-to-function.js +3 -5
- package/dist/transforms/arrow-to-function.js.map +1 -1
- package/dist/transforms/conceal-strings.d.ts +57 -0
- package/dist/transforms/conceal-strings.d.ts.map +1 -0
- package/dist/transforms/conceal-strings.js +361 -0
- package/dist/transforms/conceal-strings.js.map +1 -0
- package/dist/transforms/const-let-to-var.d.ts +3 -3
- package/dist/transforms/const-let-to-var.d.ts.map +1 -1
- package/dist/transforms/const-let-to-var.js +36 -9
- package/dist/transforms/const-let-to-var.js.map +1 -1
- package/dist/transforms/const-to-let.d.ts +2 -3
- package/dist/transforms/const-to-let.d.ts.map +1 -1
- package/dist/transforms/const-to-let.js +3 -5
- package/dist/transforms/const-to-let.js.map +1 -1
- package/dist/transforms/fold-builtin-methods.d.ts.map +1 -1
- package/dist/transforms/fold-builtin-methods.js +48 -10
- package/dist/transforms/fold-builtin-methods.js.map +1 -1
- package/dist/transforms/fold-constants.js +3 -0
- package/dist/transforms/fold-constants.js.map +1 -1
- package/dist/transforms/hoist-variables.d.ts +30 -0
- package/dist/transforms/hoist-variables.d.ts.map +1 -0
- package/dist/transforms/hoist-variables.js +224 -0
- package/dist/transforms/hoist-variables.js.map +1 -0
- package/dist/transforms/inline-functions.js +3 -4
- package/dist/transforms/inline-functions.js.map +1 -1
- package/dist/transforms/mangle-properties.js +7 -4
- 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/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/pack-declarations-into-parameters.js +1 -5
- package/dist/transforms/pack-declarations-into-parameters.js.map +1 -1
- package/dist/transforms/pack.d.ts +5 -7
- package/dist/transforms/pack.d.ts.map +1 -1
- package/dist/transforms/pack.js +10 -17
- package/dist/transforms/pack.js.map +1 -1
- package/dist/transforms/remove-unused-code.d.ts.map +1 -1
- package/dist/transforms/remove-unused-code.js +7 -39
- package/dist/transforms/remove-unused-code.js.map +1 -1
- package/dist/transforms/rename-identifiers.d.ts.map +1 -1
- package/dist/transforms/rename-identifiers.js +29 -17
- package/dist/transforms/rename-identifiers.js.map +1 -1
- package/dist/transforms/reorder-function-declarations.d.ts +25 -0
- package/dist/transforms/reorder-function-declarations.d.ts.map +1 -0
- package/dist/transforms/reorder-function-declarations.js +64 -0
- package/dist/transforms/reorder-function-declarations.js.map +1 -0
- package/dist/transforms/specials-to-strings.js +4 -3
- package/dist/transforms/specials-to-strings.js.map +1 -1
- package/dist/transforms/update-to-assignment.js +1 -16
- package/dist/transforms/update-to-assignment.js.map +1 -1
- package/dist/utils/ast.d.ts +1 -2
- package/dist/utils/ast.d.ts.map +1 -1
- package/dist/utils/ast.js +12 -42
- package/dist/utils/ast.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/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 +10 -12
- package/src/analysis/constant.ts +3 -6
- package/src/analysis/inlineability.ts +1 -1
- package/src/index.ts +1 -0
- package/src/obfuscator.ts +51 -14
- package/src/options.ts +21 -3
- package/src/preparation/expand-destructuring.ts +6 -6
- package/src/transforms/arrow-to-function.ts +3 -5
- package/src/transforms/conceal-strings.ts +530 -0
- package/src/transforms/const-let-to-var.ts +37 -9
- package/src/transforms/const-to-let.ts +3 -5
- package/src/transforms/fold-builtin-methods.ts +67 -12
- package/src/transforms/fold-constants.ts +3 -0
- package/src/transforms/hoist-variables.ts +272 -0
- package/src/transforms/inline-functions.ts +3 -4
- package/src/transforms/mangle-properties.ts +7 -7
- package/src/transforms/merge-variable-declarations.ts +91 -0
- package/src/transforms/numbers-to-strings.ts +2 -12
- package/src/transforms/pack-declarations-into-parameters.ts +1 -5
- package/src/transforms/pack.ts +10 -18
- package/src/transforms/remove-unused-code.ts +9 -64
- package/src/transforms/rename-identifiers.ts +42 -17
- package/src/transforms/reorder-function-declarations.ts +80 -0
- package/src/transforms/specials-to-strings.ts +4 -3
- package/src/transforms/update-to-assignment.ts +1 -17
- package/src/utils/ast.ts +15 -51
- package/src/utils/literal.ts +91 -2
- package/src/utils/statement-lists.ts +45 -0
- package/DISCLOSURE +0 -9
- package/README.md +0 -67
|
@@ -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, isInsideWith } 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,9 +502,61 @@ function foldsIntoDirective(path: NodePath<t.CallExpression>, replacement: t.Exp
|
|
|
506
502
|
)
|
|
507
503
|
}
|
|
508
504
|
|
|
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, path)) {
|
|
511
|
+
path.replaceWith(t.sequenceExpression([expression, result]))
|
|
512
|
+
return
|
|
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, path),
|
|
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
|
+
|
|
509
557
|
function handleCall(path: NodePath<t.CallExpression>, patchedRoots: Set<string>): void {
|
|
510
|
-
// `with`
|
|
511
|
-
if (isInsideWith(path)
|
|
558
|
+
// `with` can replace an intrinsic that Babel treats as an unbound global.
|
|
559
|
+
if (isInsideWith(path)) {
|
|
512
560
|
return
|
|
513
561
|
}
|
|
514
562
|
const callee = path.node.callee
|
|
@@ -559,7 +607,7 @@ function handleMember(path: NodePath<t.MemberExpression>, patchedRoots: Set<stri
|
|
|
559
607
|
return
|
|
560
608
|
}
|
|
561
609
|
// Delay the ancestor walk until cheap shape checks pass.
|
|
562
|
-
if (isInsideWith(path)
|
|
610
|
+
if (isInsideWith(path)) {
|
|
563
611
|
return
|
|
564
612
|
}
|
|
565
613
|
|
|
@@ -589,10 +637,18 @@ function handleMember(path: NodePath<t.MemberExpression>, patchedRoots: Set<stri
|
|
|
589
637
|
}
|
|
590
638
|
|
|
591
639
|
if (propName === 'length') {
|
|
640
|
+
if (t.isArrayExpression(obj) && foldArrayLiteralLength(path, obj)) {
|
|
641
|
+
return
|
|
642
|
+
}
|
|
592
643
|
const recv = evaluateConstant(obj, path.scope, path)
|
|
593
644
|
if (recv.known && typeof recv.value === 'string') {
|
|
594
645
|
path.replaceWith(t.numericLiteral(recv.value.length))
|
|
595
646
|
}
|
|
647
|
+
return
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
if (t.isExpression(obj)) {
|
|
651
|
+
foldStringIndex(path, obj, propName)
|
|
596
652
|
}
|
|
597
653
|
}
|
|
598
654
|
|
|
@@ -625,7 +681,6 @@ function foldGlobalCall(
|
|
|
625
681
|
foldNumberCtor(path)
|
|
626
682
|
return
|
|
627
683
|
}
|
|
628
|
-
|
|
629
684
|
if (GLOBAL_FUNCTIONS.has(name)) {
|
|
630
685
|
if (path.scope.getBinding(name)) {
|
|
631
686
|
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
|
|
@@ -0,0 +1,272 @@
|
|
|
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 and declarations inside `with` stay in place. Static blocks
|
|
13
|
+
* keep separate declarations.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* // ◀️ before
|
|
17
|
+
* function f() {
|
|
18
|
+
* var x = 1;
|
|
19
|
+
* if (c) {
|
|
20
|
+
* var y = g();
|
|
21
|
+
* }
|
|
22
|
+
* return x + y;
|
|
23
|
+
* }
|
|
24
|
+
*
|
|
25
|
+
* // ▶️ after
|
|
26
|
+
* function f() {
|
|
27
|
+
* var x, y;
|
|
28
|
+
* x = 1;
|
|
29
|
+
* if (c) {
|
|
30
|
+
* y = g();
|
|
31
|
+
* }
|
|
32
|
+
* return x + y;
|
|
33
|
+
* }
|
|
34
|
+
*/
|
|
35
|
+
export function hoistVariables(ast: File): boolean {
|
|
36
|
+
const state: HoistState = {
|
|
37
|
+
changed: false,
|
|
38
|
+
scopes: new Map(),
|
|
39
|
+
}
|
|
40
|
+
traverse(ast, {
|
|
41
|
+
VariableDeclaration(path) {
|
|
42
|
+
if (path.node.kind !== 'var' || path.parentPath.isExportNamedDeclaration()) {
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
const owner = ownerPath(path)
|
|
46
|
+
if (owner === null || isInsideOwnerWith(path, owner)) {
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const scope = hoistedScope(state.scopes, owner)
|
|
51
|
+
addNames(scope.names, path.getBindingIdentifiers())
|
|
52
|
+
if (scope.declaration?.node === path.node) {
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
demoteDeclaration(path)
|
|
57
|
+
scope.changed = true
|
|
58
|
+
state.changed = true
|
|
59
|
+
},
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
for (const scope of state.scopes.values()) {
|
|
63
|
+
if (scope.changed) {
|
|
64
|
+
finishDeclaration(scope)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return state.changed
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
type OwnerPath = NodePath<t.Function | t.Program | t.StaticBlock>
|
|
72
|
+
|
|
73
|
+
interface HoistState extends ChangeState {
|
|
74
|
+
scopes: Map<t.Node, HoistedScope>
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface HoistedScope {
|
|
78
|
+
changed: boolean
|
|
79
|
+
declaration: NodePath<t.VariableDeclaration> | null
|
|
80
|
+
names: Map<string, t.Identifier>
|
|
81
|
+
owner: OwnerPath
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function ownerPath(path: NodePath<t.VariableDeclaration>): OwnerPath | null {
|
|
85
|
+
let current: NodePath | null = path.parentPath
|
|
86
|
+
while (current) {
|
|
87
|
+
if (current.isStaticBlock()) {
|
|
88
|
+
return current as OwnerPath
|
|
89
|
+
}
|
|
90
|
+
if (current.isProgram()) {
|
|
91
|
+
return current as OwnerPath
|
|
92
|
+
}
|
|
93
|
+
if (t.isFunction(current.node) && t.isBlockStatement(current.node.body)) {
|
|
94
|
+
return current as OwnerPath
|
|
95
|
+
}
|
|
96
|
+
current = current.parentPath
|
|
97
|
+
}
|
|
98
|
+
return null
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function isInsideOwnerWith(path: NodePath, owner: OwnerPath): boolean {
|
|
102
|
+
for (let current = path.parentPath; current && current !== owner; current = current.parentPath) {
|
|
103
|
+
if (current.isWithStatement()) {
|
|
104
|
+
return true
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return false
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function hoistedScope(scopes: Map<t.Node, HoistedScope>, owner: OwnerPath): HoistedScope {
|
|
111
|
+
const existing = scopes.get(owner.node)
|
|
112
|
+
if (existing) {
|
|
113
|
+
return existing
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const declaration = leadingBareDeclaration(owner)
|
|
117
|
+
const scope: HoistedScope = {
|
|
118
|
+
changed: false,
|
|
119
|
+
declaration,
|
|
120
|
+
names: new Map(),
|
|
121
|
+
owner,
|
|
122
|
+
}
|
|
123
|
+
if (declaration) {
|
|
124
|
+
addNames(scope.names, declaration.getBindingIdentifiers())
|
|
125
|
+
}
|
|
126
|
+
scopes.set(owner.node, scope)
|
|
127
|
+
return scope
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function leadingBareDeclaration(owner: OwnerPath): NodePath<t.VariableDeclaration> | null {
|
|
131
|
+
const first = ownerBody(owner)[0]
|
|
132
|
+
if (!first?.isVariableDeclaration({ kind: 'var' })) {
|
|
133
|
+
return null
|
|
134
|
+
}
|
|
135
|
+
return first.node.declarations.every((declarator) => declarator.init == null) ? first : null
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function ownerBody(owner: OwnerPath): NodePath<t.Statement>[] {
|
|
139
|
+
if (owner.isFunction()) {
|
|
140
|
+
const body = owner.get('body')
|
|
141
|
+
return body.isBlockStatement() ? (body.get('body') as NodePath<t.Statement>[]) : []
|
|
142
|
+
}
|
|
143
|
+
return owner.get('body') as NodePath<t.Statement>[]
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function demoteDeclaration(path: NodePath<t.VariableDeclaration>): void {
|
|
147
|
+
const parent = path.parentPath
|
|
148
|
+
if (parent.isForStatement() && path.key === 'init') {
|
|
149
|
+
demoteForInit(path)
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
if ((parent.isForInStatement() || parent.isForOfStatement()) && path.key === 'left') {
|
|
153
|
+
demoteForX(path, parent)
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const assignments = initializerAssignments(path.node).map((expression) =>
|
|
158
|
+
t.expressionStatement(expression),
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
if (typeof path.listKey === 'string') {
|
|
162
|
+
if (assignments.length === 0) {
|
|
163
|
+
path.remove()
|
|
164
|
+
} else {
|
|
165
|
+
path.replaceWithMultiple(assignments)
|
|
166
|
+
}
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
if (assignments.length === 0) {
|
|
170
|
+
path.replaceWith(t.emptyStatement())
|
|
171
|
+
} else if (assignments.length === 1) {
|
|
172
|
+
path.replaceWith(assignments[0]!)
|
|
173
|
+
} else {
|
|
174
|
+
path.replaceWith(t.blockStatement(assignments))
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function demoteForInit(path: NodePath<t.VariableDeclaration>): void {
|
|
179
|
+
const assignments = initializerAssignments(path.node)
|
|
180
|
+
if (assignments.length === 0) {
|
|
181
|
+
path.remove()
|
|
182
|
+
return
|
|
183
|
+
}
|
|
184
|
+
const replacement = assignments.length === 1 ? assignments[0]! : t.sequenceExpression(assignments)
|
|
185
|
+
path.replaceWith(replacement)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function demoteForX(
|
|
189
|
+
path: NodePath<t.VariableDeclaration>,
|
|
190
|
+
parent: NodePath<t.ForInStatement | t.ForOfStatement>,
|
|
191
|
+
): void {
|
|
192
|
+
const declarator = path.node.declarations[0]!
|
|
193
|
+
const target = declarator.id as t.ArrayPattern | t.Identifier | t.ObjectPattern
|
|
194
|
+
if (declarator.init) {
|
|
195
|
+
const assignment = initializerAssignment(declarator, target)
|
|
196
|
+
const right = parent.get('right') as NodePath<t.Expression>
|
|
197
|
+
right.replaceWith(t.sequenceExpression([assignment, right.node]))
|
|
198
|
+
}
|
|
199
|
+
path.replaceWith(t.cloneNode(target))
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function initializerAssignments(node: t.VariableDeclaration): t.AssignmentExpression[] {
|
|
203
|
+
const assignments: t.AssignmentExpression[] = []
|
|
204
|
+
for (const declarator of node.declarations) {
|
|
205
|
+
if (declarator.init != null) {
|
|
206
|
+
const target = declarator.id as t.ArrayPattern | t.Identifier | t.ObjectPattern
|
|
207
|
+
assignments.push(initializerAssignment(declarator, target))
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return assignments
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function initializerAssignment(
|
|
214
|
+
declarator: t.VariableDeclarator,
|
|
215
|
+
target: t.ArrayPattern | t.Identifier | t.ObjectPattern,
|
|
216
|
+
): t.AssignmentExpression {
|
|
217
|
+
return t.assignmentExpression('=', t.cloneNode(target), declarator.init!)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function addNames(
|
|
221
|
+
names: Map<string, t.Identifier>,
|
|
222
|
+
identifiers: Record<string, t.Identifier>,
|
|
223
|
+
): void {
|
|
224
|
+
for (const [name, identifier] of Object.entries(identifiers)) {
|
|
225
|
+
if (!names.has(name)) {
|
|
226
|
+
names.set(name, identifier)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function finishDeclaration(scope: HoistedScope): void {
|
|
232
|
+
const existingNames = new Set<string>()
|
|
233
|
+
if (scope.declaration) {
|
|
234
|
+
for (const name of Object.keys(scope.declaration.getBindingIdentifiers())) {
|
|
235
|
+
existingNames.add(name)
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const declarators = [...scope.names]
|
|
239
|
+
.filter(([name]) => !existingNames.has(name))
|
|
240
|
+
.map(([, identifier]) => t.variableDeclarator(t.cloneNode(identifier)))
|
|
241
|
+
|
|
242
|
+
if (scope.declaration) {
|
|
243
|
+
if (declarators.length > 0) {
|
|
244
|
+
scope.declaration.pushContainer('declarations', declarators)
|
|
245
|
+
}
|
|
246
|
+
return
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (declarators.length === 0) {
|
|
250
|
+
return
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const declaration = t.variableDeclaration('var', declarators)
|
|
254
|
+
insertDeclaration(scope.owner, declaration)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function insertDeclaration(owner: OwnerPath, declaration: t.VariableDeclaration): void {
|
|
258
|
+
if (owner.isFunction()) {
|
|
259
|
+
const body = owner.get('body')
|
|
260
|
+
if (body.isBlockStatement()) {
|
|
261
|
+
body.unshiftContainer('body', declaration)
|
|
262
|
+
}
|
|
263
|
+
return
|
|
264
|
+
}
|
|
265
|
+
if (owner.isProgram()) {
|
|
266
|
+
owner.unshiftContainer('body', declaration)
|
|
267
|
+
return
|
|
268
|
+
}
|
|
269
|
+
if (owner.isStaticBlock()) {
|
|
270
|
+
owner.unshiftContainer('body', declaration)
|
|
271
|
+
}
|
|
272
|
+
}
|
|
@@ -6,7 +6,7 @@ import type { File } from '@babel/types'
|
|
|
6
6
|
import { analyzeInlineability } from '../analysis/inlineability'
|
|
7
7
|
import type { InlineCandidate } from '../analysis/inlineability'
|
|
8
8
|
import { isPure } from '../analysis/purity'
|
|
9
|
-
import {
|
|
9
|
+
import { isInsideWith, walkOwnFunctionScope } from '../utils/ast'
|
|
10
10
|
import { reorderableToStatement } from '../utils/evaluation-order'
|
|
11
11
|
import { generateLoopLabel } from '../utils/loop-lowering'
|
|
12
12
|
import { hasAnnexBFunctionAlias, isInStrictContext } from '../utils/paths'
|
|
@@ -135,9 +135,8 @@ function isSafeAtCallSite(
|
|
|
135
135
|
callPath: NodePath<t.CallExpression>,
|
|
136
136
|
candidates: Map<string, InlineCandidate>,
|
|
137
137
|
): boolean {
|
|
138
|
-
// `with
|
|
139
|
-
|
|
140
|
-
if (isInsideWith(callPath) || enclosingScopeHasDirectEval(callPath)) {
|
|
138
|
+
// `with` and Annex B aliases can resolve a free name outside Babel's scope table.
|
|
139
|
+
if (isInsideWith(callPath)) {
|
|
141
140
|
return false
|
|
142
141
|
}
|
|
143
142
|
if (annexBHoistShadowsFreeVar(callPath, freeVars)) {
|
|
@@ -507,7 +507,7 @@ function rewriteKeyedProperty(path: NodePath<KeyedProperty>, state: PropertyMang
|
|
|
507
507
|
if (mangled === name) {
|
|
508
508
|
return
|
|
509
509
|
}
|
|
510
|
-
node.key = t.
|
|
510
|
+
node.key = t.stringLiteral(mangled)
|
|
511
511
|
node.computed = false
|
|
512
512
|
if (t.isObjectProperty(node)) {
|
|
513
513
|
node.shorthand = false
|
|
@@ -532,7 +532,7 @@ function rewriteMember(path: NodePath<Member>, state: PropertyManglerState): voi
|
|
|
532
532
|
}
|
|
533
533
|
// Babel narrows members by `computed`, so both fields need widening.
|
|
534
534
|
const computed = node as Member & { computed: boolean; property: t.Expression }
|
|
535
|
-
computed.property = t.
|
|
535
|
+
computed.property = t.stringLiteral(mangled)
|
|
536
536
|
computed.computed = true
|
|
537
537
|
state.changed = true
|
|
538
538
|
}
|
|
@@ -586,7 +586,7 @@ function rewriteTerminalNames(
|
|
|
586
586
|
node.value = mangled
|
|
587
587
|
return node
|
|
588
588
|
}
|
|
589
|
-
return t.
|
|
589
|
+
return t.stringLiteral(mangled)
|
|
590
590
|
}
|
|
591
591
|
if (t.isSequenceExpression(node)) {
|
|
592
592
|
const last = node.expressions.length - 1
|
|
@@ -684,10 +684,10 @@ function isTransparentAnnotationParent(path: NodePath, child: t.Node): boolean {
|
|
|
684
684
|
|
|
685
685
|
function hasAnnotation(node: t.Node, annotation: string): boolean {
|
|
686
686
|
return (
|
|
687
|
-
node.leadingComments?.some(
|
|
688
|
-
(
|
|
689
|
-
|
|
690
|
-
) ?? false
|
|
687
|
+
node.leadingComments?.some((comment) => {
|
|
688
|
+
const value = comment.value.trim()
|
|
689
|
+
return value === `@${annotation}` || value === `#${annotation}`
|
|
690
|
+
}) ?? false
|
|
691
691
|
)
|
|
692
692
|
}
|
|
693
693
|
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import * as t from '@babel/types'
|
|
2
|
+
import type { File } from '@babel/types'
|
|
3
|
+
|
|
4
|
+
import { rewriteStatementLists } from '../utils/statement-lists'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Merges adjacent variable declarations when their kinds and export wrappers
|
|
8
|
+
* match. It runs in programs, blocks, switch cases, and static blocks, and
|
|
9
|
+
* removes unlabeled empty statements between compatible declarations.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* // ◀️ before
|
|
13
|
+
* let a = 1;
|
|
14
|
+
* let b = 2;
|
|
15
|
+
*
|
|
16
|
+
* // ▶️ after
|
|
17
|
+
* let a = 1,
|
|
18
|
+
* b = 2;
|
|
19
|
+
*/
|
|
20
|
+
export function mergeVariableDeclarations(ast: File): boolean {
|
|
21
|
+
return rewriteStatementLists(ast, (statements) =>
|
|
22
|
+
mergeAdjacent(statements.map((path) => path.node)),
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type StatementLike = t.Program['body'][number]
|
|
27
|
+
|
|
28
|
+
interface MergeableDeclaration {
|
|
29
|
+
declaration: t.VariableDeclaration
|
|
30
|
+
exported: boolean
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function mergeAdjacent(list: readonly StatementLike[]): StatementLike[] | null {
|
|
34
|
+
const out: StatementLike[] = []
|
|
35
|
+
let pendingEmpty: t.EmptyStatement[] = []
|
|
36
|
+
let merged = false
|
|
37
|
+
|
|
38
|
+
for (const statement of list) {
|
|
39
|
+
if (t.isEmptyStatement(statement) && mergeableDeclaration(out.at(-1)) !== null) {
|
|
40
|
+
pendingEmpty.push(statement)
|
|
41
|
+
continue
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const previous = mergeableDeclaration(out.at(-1))
|
|
45
|
+
const current = mergeableDeclaration(statement)
|
|
46
|
+
if (previous && current && canMerge(previous, current)) {
|
|
47
|
+
out[out.length - 1] = mergePair(previous, current)
|
|
48
|
+
pendingEmpty = []
|
|
49
|
+
merged = true
|
|
50
|
+
continue
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
out.push(...pendingEmpty, statement)
|
|
54
|
+
pendingEmpty = []
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
out.push(...pendingEmpty)
|
|
58
|
+
return merged ? out : null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function mergeableDeclaration(statement: StatementLike | undefined): MergeableDeclaration | null {
|
|
62
|
+
if (statement === undefined) {
|
|
63
|
+
return null
|
|
64
|
+
}
|
|
65
|
+
if (t.isVariableDeclaration(statement)) {
|
|
66
|
+
return { declaration: statement, exported: false }
|
|
67
|
+
}
|
|
68
|
+
if (t.isExportNamedDeclaration(statement) && t.isVariableDeclaration(statement.declaration)) {
|
|
69
|
+
return { declaration: statement.declaration, exported: true }
|
|
70
|
+
}
|
|
71
|
+
return null
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function canMerge(previous: MergeableDeclaration, current: MergeableDeclaration): boolean {
|
|
75
|
+
return (
|
|
76
|
+
previous.exported === current.exported && previous.declaration.kind === current.declaration.kind
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function mergePair(previous: MergeableDeclaration, current: MergeableDeclaration): StatementLike {
|
|
81
|
+
const declaration = t.variableDeclaration(previous.declaration.kind, [
|
|
82
|
+
...previous.declaration.declarations,
|
|
83
|
+
...current.declaration.declarations,
|
|
84
|
+
])
|
|
85
|
+
|
|
86
|
+
if (!previous.exported) {
|
|
87
|
+
return declaration
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return t.exportNamedDeclaration(declaration)
|
|
91
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Visitor } from '@babel/traverse'
|
|
2
2
|
import * as t from '@babel/types'
|
|
3
3
|
import type { File } from '@babel/types'
|
|
4
4
|
|
|
5
|
+
import { isNonComputedKey } from '../utils/ast'
|
|
5
6
|
import { traverseForChanges } from '../utils/change-tracking'
|
|
6
7
|
import type { ChangeState } from '../utils/change-tracking'
|
|
7
8
|
|
|
@@ -48,14 +49,3 @@ const visitor: Visitor<ChangeState> = {
|
|
|
48
49
|
state.changed = true
|
|
49
50
|
},
|
|
50
51
|
}
|
|
51
|
-
|
|
52
|
-
function isNonComputedKey(path: NodePath<t.NumericLiteral>): boolean {
|
|
53
|
-
const parentPath = path.parentPath
|
|
54
|
-
if (!parentPath) {
|
|
55
|
-
return false
|
|
56
|
-
}
|
|
57
|
-
const parent = parentPath.node
|
|
58
|
-
return (
|
|
59
|
-
'key' in parent && parent.key === path.node && 'computed' in parent && parent.computed === false
|
|
60
|
-
)
|
|
61
|
-
}
|
|
@@ -2,7 +2,7 @@ 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 {
|
|
5
|
+
import { hasDuplicateParamNames } from '../utils/ast'
|
|
6
6
|
import { traverseForChanges } from '../utils/change-tracking'
|
|
7
7
|
import type { ChangeState } from '../utils/change-tracking'
|
|
8
8
|
import { isLiteralShaped } from '../utils/literal'
|
|
@@ -75,10 +75,6 @@ function packLeadingIntoParams(path: NodePath<t.Function>): boolean {
|
|
|
75
75
|
if (referencesArguments(path)) {
|
|
76
76
|
return false
|
|
77
77
|
}
|
|
78
|
-
// Direct eval can inspect the newly unmapped `arguments` by name.
|
|
79
|
-
if (enclosingScopeHasDirectEval(path)) {
|
|
80
|
-
return false
|
|
81
|
-
}
|
|
82
78
|
// A caller with extra arguments could fill the new parameter slot.
|
|
83
79
|
if (!allCallSitesRespectArity(path, params.length)) {
|
|
84
80
|
return false
|
package/src/transforms/pack.ts
CHANGED
|
@@ -114,13 +114,11 @@ const STANDARD_GLOBALS: readonly string[] = [
|
|
|
114
114
|
* bundler-injected binding, or a live import, none reachable from the body and
|
|
115
115
|
* none distinguishable from a global at build time, so free names are routed
|
|
116
116
|
* through a parameter object whose accessors run in the original top-level scope
|
|
117
|
-
* and resolve each name there.
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
* top-level await, dynamic `import()`, or a top-level `arguments` will not run
|
|
123
|
-
* once moved into a Function body.
|
|
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.
|
|
124
122
|
*
|
|
125
123
|
* @example
|
|
126
124
|
* // ◀️ before
|
|
@@ -230,10 +228,9 @@ function packProgram(programPath: NodePath<t.Program>, ctx: TransformContext): b
|
|
|
230
228
|
|
|
231
229
|
const isRoutable = (path: NodePath<t.Identifier>): boolean => {
|
|
232
230
|
const name = path.node.name
|
|
233
|
-
//
|
|
234
|
-
//
|
|
235
|
-
|
|
236
|
-
if (name === objectName || name === 'arguments' || name === 'eval') {
|
|
231
|
+
// Keep the wrapper parameter and `arguments` bare. The latter must bind to
|
|
232
|
+
// the wrapper function's own arguments object.
|
|
233
|
+
if (name === objectName || name === 'arguments') {
|
|
237
234
|
return false
|
|
238
235
|
}
|
|
239
236
|
// Inside `with`, a free name may resolve to a property of the with object at
|
|
@@ -298,18 +295,13 @@ function packProgram(programPath: NodePath<t.Program>, ctx: TransformContext): b
|
|
|
298
295
|
}
|
|
299
296
|
programPath.traverse(routingVisitor)
|
|
300
297
|
|
|
301
|
-
// The script completion value is its trailing expression; returning it keeps
|
|
302
|
-
// `eval` of the packed output producing the same value.
|
|
303
298
|
const body = programNode.body
|
|
304
|
-
const lastStatement = body.at(-1)
|
|
305
|
-
if (lastStatement && t.isExpressionStatement(lastStatement)) {
|
|
306
|
-
body[body.length - 1] = t.returnStatement(lastStatement.expression)
|
|
307
|
-
}
|
|
308
|
-
|
|
309
299
|
const innerDirectives = bodyStrict ? [t.directive(t.directiveLiteral('use strict'))] : []
|
|
310
300
|
const innerCode = generate(t.program(body, innerDirectives, 'script'), {
|
|
311
301
|
minified: true,
|
|
312
302
|
comments: false,
|
|
303
|
+
// `comments: false` still allows @license and @preserve.
|
|
304
|
+
shouldPrintComment: () => false,
|
|
313
305
|
}).code
|
|
314
306
|
|
|
315
307
|
const objectExpression = buildAccessorObject({
|