webfuscator 1.1.0 → 1.2.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 +17 -12
- package/dist/analysis/constant.js +3 -3
- package/dist/analysis/constant.js.map +1 -1
- package/dist/analysis/inlineability.d.ts.map +1 -1
- package/dist/analysis/inlineability.js +39 -27
- package/dist/analysis/inlineability.js.map +1 -1
- package/dist/analysis/purity.js +1 -1
- package/dist/analysis/purity.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/obfuscator.d.ts.map +1 -1
- package/dist/obfuscator.js.map +1 -1
- package/dist/options.d.ts +7 -0
- package/dist/options.d.ts.map +1 -1
- package/dist/preparation/expand-destructuring.js +4 -3
- package/dist/preparation/expand-destructuring.js.map +1 -1
- package/dist/preparation/expand-template-literals.js +3 -2
- package/dist/preparation/expand-template-literals.js.map +1 -1
- package/dist/preparation/split-sequence-expressions.js +1 -1
- package/dist/preparation/split-sequence-expressions.js.map +1 -1
- package/dist/preparation/split-variable-declarations.js +1 -1
- package/dist/preparation/split-variable-declarations.js.map +1 -1
- package/dist/preparation/stringify-object-keys.js +1 -1
- package/dist/preparation/stringify-object-keys.js.map +1 -1
- package/dist/preparation/wrap-single-statements.js +2 -2
- package/dist/preparation/wrap-single-statements.js.map +1 -1
- package/dist/transforms/arguments-to-parameters.js +1 -1
- package/dist/transforms/arguments-to-parameters.js.map +1 -1
- package/dist/transforms/arrow-to-function.js.map +1 -1
- package/dist/transforms/collapse-single-use-temps.js +7 -7
- package/dist/transforms/collapse-single-use-temps.js.map +1 -1
- package/dist/transforms/const-let-to-var.js +3 -3
- package/dist/transforms/const-let-to-var.js.map +1 -1
- package/dist/transforms/const-to-let.js.map +1 -1
- package/dist/transforms/do-while-to-while.js +3 -3
- package/dist/transforms/do-while-to-while.js.map +1 -1
- package/dist/transforms/drop-console.js +4 -3
- package/dist/transforms/drop-console.js.map +1 -1
- package/dist/transforms/drop-debugger.js.map +1 -1
- package/dist/transforms/expand-binary-assignment.js +5 -3
- package/dist/transforms/expand-binary-assignment.js.map +1 -1
- package/dist/transforms/expand-logical-assignment.js +5 -3
- package/dist/transforms/expand-logical-assignment.js.map +1 -1
- package/dist/transforms/extract-object-properties.js +7 -7
- package/dist/transforms/extract-object-properties.js.map +1 -1
- package/dist/transforms/fold-builtin-methods.js +16 -15
- 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/for-to-while.js +5 -5
- package/dist/transforms/for-to-while.js.map +1 -1
- package/dist/transforms/function-declaration-to-expression.js +3 -3
- package/dist/transforms/function-declaration-to-expression.js.map +1 -1
- package/dist/transforms/inline-functions.js +7 -7
- package/dist/transforms/inline-functions.js.map +1 -1
- package/dist/transforms/logical-to-ternary.js +2 -2
- package/dist/transforms/logical-to-ternary.js.map +1 -1
- package/dist/transforms/mangle-properties.d.ts.map +1 -1
- package/dist/transforms/mangle-properties.js +35 -86
- package/dist/transforms/mangle-properties.js.map +1 -1
- package/dist/transforms/nullish-coalescing-to-ternary.js +2 -2
- package/dist/transforms/nullish-coalescing-to-ternary.js.map +1 -1
- package/dist/transforms/numbers-to-strings.js +2 -2
- package/dist/transforms/numbers-to-strings.js.map +1 -1
- package/dist/transforms/object-method-to-property.js +1 -1
- package/dist/transforms/object-method-to-property.js.map +1 -1
- package/dist/transforms/optional-chaining-to-ternary.js +2 -2
- package/dist/transforms/optional-chaining-to-ternary.js.map +1 -1
- package/dist/transforms/pack-declarations-into-parameters.js +2 -2
- package/dist/transforms/pack-declarations-into-parameters.js.map +1 -1
- package/dist/transforms/pack.d.ts.map +1 -1
- package/dist/transforms/pack.js +194 -26
- package/dist/transforms/pack.js.map +1 -1
- package/dist/transforms/remove-anonymous-function-names.js +3 -3
- package/dist/transforms/remove-anonymous-function-names.js.map +1 -1
- package/dist/transforms/remove-unreachable-code.js +1 -1
- package/dist/transforms/remove-unreachable-code.js.map +1 -1
- package/dist/transforms/remove-unused-code.js +4 -4
- 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 +4 -3
- package/dist/transforms/rename-identifiers.js.map +1 -1
- package/dist/transforms/specials-to-strings.d.ts.map +1 -1
- package/dist/transforms/specials-to-strings.js +2 -2
- package/dist/transforms/specials-to-strings.js.map +1 -1
- package/dist/transforms/switch-to-if.js +7 -7
- package/dist/transforms/switch-to-if.js.map +1 -1
- package/dist/transforms/ternary-to-if.js +7 -4
- package/dist/transforms/ternary-to-if.js.map +1 -1
- package/dist/transforms/update-to-assignment.js +4 -2
- package/dist/transforms/update-to-assignment.js.map +1 -1
- package/dist/transforms/yodify-conditions.js +3 -3
- package/dist/transforms/yodify-conditions.js.map +1 -1
- package/dist/utils/ast.d.ts.map +1 -1
- package/dist/utils/ast.js +2 -1
- package/dist/utils/ast.js.map +1 -1
- package/dist/utils/evaluation-order.js.map +1 -1
- package/dist/utils/logical-lowering.js +2 -2
- package/dist/utils/logical-lowering.js.map +1 -1
- package/dist/utils/loop-lowering.js +1 -1
- package/dist/utils/loop-lowering.js.map +1 -1
- package/dist/utils/string-generator.js +1 -1
- package/dist/utils/string-generator.js.map +1 -1
- package/package.json +14 -12
- package/src/analysis/constant.ts +4 -4
- package/src/analysis/inlineability.ts +43 -35
- package/src/analysis/purity.ts +3 -3
- package/src/index.ts +4 -2
- package/src/obfuscator.ts +42 -42
- package/src/options.ts +8 -1
- package/src/preparation/expand-destructuring.ts +5 -4
- package/src/preparation/expand-template-literals.ts +4 -3
- package/src/preparation/split-sequence-expressions.ts +2 -2
- package/src/preparation/split-variable-declarations.ts +1 -1
- package/src/preparation/stringify-object-keys.ts +2 -2
- package/src/preparation/wrap-single-statements.ts +2 -2
- package/src/transforms/arguments-to-parameters.ts +5 -5
- package/src/transforms/arrow-to-function.ts +3 -3
- package/src/transforms/collapse-single-use-temps.ts +14 -14
- package/src/transforms/const-let-to-var.ts +6 -6
- package/src/transforms/const-to-let.ts +4 -4
- package/src/transforms/do-while-to-while.ts +6 -6
- package/src/transforms/drop-console.ts +8 -7
- package/src/transforms/drop-debugger.ts +2 -2
- package/src/transforms/expand-binary-assignment.ts +8 -6
- package/src/transforms/expand-logical-assignment.ts +8 -6
- package/src/transforms/extract-object-properties.ts +12 -12
- package/src/transforms/fold-builtin-methods.ts +20 -19
- package/src/transforms/fold-constants.ts +8 -8
- package/src/transforms/for-to-while.ts +8 -8
- package/src/transforms/function-declaration-to-expression.ts +5 -5
- package/src/transforms/inline-functions.ts +14 -14
- package/src/transforms/logical-to-ternary.ts +7 -7
- package/src/transforms/mangle-properties.ts +43 -97
- package/src/transforms/nullish-coalescing-to-ternary.ts +6 -6
- package/src/transforms/numbers-to-strings.ts +4 -4
- package/src/transforms/object-method-to-property.ts +4 -4
- package/src/transforms/optional-chaining-to-ternary.ts +5 -5
- package/src/transforms/pack-declarations-into-parameters.ts +7 -7
- package/src/transforms/pack.ts +212 -33
- package/src/transforms/remove-anonymous-function-names.ts +5 -5
- package/src/transforms/remove-unreachable-code.ts +3 -3
- package/src/transforms/remove-unused-code.ts +8 -8
- package/src/transforms/rename-identifiers.ts +9 -8
- package/src/transforms/specials-to-strings.ts +7 -7
- package/src/transforms/switch-to-if.ts +12 -12
- package/src/transforms/ternary-to-if.ts +12 -9
- package/src/transforms/update-to-assignment.ts +7 -5
- package/src/transforms/yodify-conditions.ts +6 -6
- package/src/utils/ast.ts +2 -1
- package/src/utils/evaluation-order.ts +1 -1
- package/src/utils/logical-lowering.ts +3 -3
- package/src/utils/loop-lowering.ts +1 -1
- package/src/utils/string-generator.ts +1 -1
package/src/transforms/pack.ts
CHANGED
|
@@ -4,11 +4,108 @@ import type { NodePath, Visitor } from '@babel/traverse'
|
|
|
4
4
|
import * as t from '@babel/types'
|
|
5
5
|
import type { File } from '@babel/types'
|
|
6
6
|
|
|
7
|
-
import type { TransformContext } from '
|
|
8
|
-
import { isInsideWith } from '
|
|
9
|
-
import { isCalleeOrTagOf, isInStrictContext, referencesOrWritesVariable } from '
|
|
10
|
-
import { mulberry32 } from '
|
|
11
|
-
import { StringGenerator } from '
|
|
7
|
+
import type { TransformContext } from '../options'
|
|
8
|
+
import { isInsideWith } from '../utils/ast'
|
|
9
|
+
import { isCalleeOrTagOf, isInStrictContext, referencesOrWritesVariable } from '../utils/paths'
|
|
10
|
+
import { mulberry32 } from '../utils/random'
|
|
11
|
+
import { StringGenerator } from '../utils/string-generator'
|
|
12
|
+
|
|
13
|
+
// The ECMAScript intrinsics and the common host globals. A detached Function
|
|
14
|
+
// body reaches every one of them and gets the same value, so pack can leave them
|
|
15
|
+
// bare. The CommonJS wrapper names (require, module, exports, __dirname,
|
|
16
|
+
// __filename) are left out on purpose. They are function parameters, not
|
|
17
|
+
// properties of the global object, so a Function body cannot see them and pack
|
|
18
|
+
// routes them like any other free name. `skipGlobals: true` opts into this list.
|
|
19
|
+
const STANDARD_GLOBALS: readonly string[] = [
|
|
20
|
+
'globalThis',
|
|
21
|
+
'self',
|
|
22
|
+
'Infinity',
|
|
23
|
+
'NaN',
|
|
24
|
+
'undefined',
|
|
25
|
+
'parseInt',
|
|
26
|
+
'parseFloat',
|
|
27
|
+
'isNaN',
|
|
28
|
+
'isFinite',
|
|
29
|
+
'decodeURI',
|
|
30
|
+
'decodeURIComponent',
|
|
31
|
+
'encodeURI',
|
|
32
|
+
'encodeURIComponent',
|
|
33
|
+
'escape',
|
|
34
|
+
'unescape',
|
|
35
|
+
'btoa',
|
|
36
|
+
'atob',
|
|
37
|
+
'structuredClone',
|
|
38
|
+
'fetch',
|
|
39
|
+
'queueMicrotask',
|
|
40
|
+
'setTimeout',
|
|
41
|
+
'clearTimeout',
|
|
42
|
+
'setInterval',
|
|
43
|
+
'clearInterval',
|
|
44
|
+
'setImmediate',
|
|
45
|
+
'clearImmediate',
|
|
46
|
+
'Object',
|
|
47
|
+
'Function',
|
|
48
|
+
'Boolean',
|
|
49
|
+
'Symbol',
|
|
50
|
+
'BigInt',
|
|
51
|
+
'Number',
|
|
52
|
+
'Math',
|
|
53
|
+
'Date',
|
|
54
|
+
'String',
|
|
55
|
+
'RegExp',
|
|
56
|
+
'JSON',
|
|
57
|
+
'Promise',
|
|
58
|
+
'Proxy',
|
|
59
|
+
'Reflect',
|
|
60
|
+
'Error',
|
|
61
|
+
'EvalError',
|
|
62
|
+
'RangeError',
|
|
63
|
+
'ReferenceError',
|
|
64
|
+
'SyntaxError',
|
|
65
|
+
'TypeError',
|
|
66
|
+
'URIError',
|
|
67
|
+
'AggregateError',
|
|
68
|
+
'Array',
|
|
69
|
+
'Map',
|
|
70
|
+
'Set',
|
|
71
|
+
'WeakMap',
|
|
72
|
+
'WeakSet',
|
|
73
|
+
'WeakRef',
|
|
74
|
+
'FinalizationRegistry',
|
|
75
|
+
'ArrayBuffer',
|
|
76
|
+
'SharedArrayBuffer',
|
|
77
|
+
'DataView',
|
|
78
|
+
'Atomics',
|
|
79
|
+
'Int8Array',
|
|
80
|
+
'Uint8Array',
|
|
81
|
+
'Uint8ClampedArray',
|
|
82
|
+
'Int16Array',
|
|
83
|
+
'Uint16Array',
|
|
84
|
+
'Int32Array',
|
|
85
|
+
'Uint32Array',
|
|
86
|
+
'Float32Array',
|
|
87
|
+
'Float64Array',
|
|
88
|
+
'BigInt64Array',
|
|
89
|
+
'BigUint64Array',
|
|
90
|
+
'console',
|
|
91
|
+
'crypto',
|
|
92
|
+
'performance',
|
|
93
|
+
'Intl',
|
|
94
|
+
'URL',
|
|
95
|
+
'URLSearchParams',
|
|
96
|
+
'TextEncoder',
|
|
97
|
+
'TextDecoder',
|
|
98
|
+
'WebAssembly',
|
|
99
|
+
'window',
|
|
100
|
+
'document',
|
|
101
|
+
'location',
|
|
102
|
+
'postMessage',
|
|
103
|
+
'alert',
|
|
104
|
+
'confirm',
|
|
105
|
+
'global',
|
|
106
|
+
'process',
|
|
107
|
+
'Buffer',
|
|
108
|
+
]
|
|
12
109
|
|
|
13
110
|
/**
|
|
14
111
|
* Serializes the whole program to a string and rebuilds it at runtime through
|
|
@@ -94,14 +191,19 @@ function packProgram(programPath: NodePath<t.Program>, ctx: TransformContext): b
|
|
|
94
191
|
const readProperties = new Map<string, string>()
|
|
95
192
|
const typeofProperties = new Map<string, string>()
|
|
96
193
|
const settable = new Set<string>()
|
|
194
|
+
const freshProperty = (): string => {
|
|
195
|
+
let property: string
|
|
196
|
+
do {
|
|
197
|
+
// `__proto__` as an object-literal key would set the prototype rather
|
|
198
|
+
// than name an accessor.
|
|
199
|
+
property = generator.next()
|
|
200
|
+
} while (property === '__proto__')
|
|
201
|
+
return property
|
|
202
|
+
}
|
|
97
203
|
const propertyFor = (registry: Map<string, string>, name: string): string => {
|
|
98
204
|
let property = registry.get(name)
|
|
99
205
|
if (property === undefined) {
|
|
100
|
-
|
|
101
|
-
// `__proto__` as an object-literal key would set the prototype rather
|
|
102
|
-
// than name an accessor.
|
|
103
|
-
property = generator.next()
|
|
104
|
-
} while (property === '__proto__')
|
|
206
|
+
property = freshProperty()
|
|
105
207
|
registry.set(name, property)
|
|
106
208
|
}
|
|
107
209
|
return property
|
|
@@ -109,8 +211,25 @@ function packProgram(programPath: NodePath<t.Program>, ctx: TransformContext): b
|
|
|
109
211
|
const memberFor = (property: string): t.MemberExpression =>
|
|
110
212
|
t.memberExpression(t.identifier(objectName), t.stringLiteral(property), true)
|
|
111
213
|
|
|
214
|
+
// Import locals read as free once detached but are unreachable from a Function
|
|
215
|
+
// body, so they route even when the caller lists them in skipGlobals.
|
|
216
|
+
const importedNames = new Set<string>()
|
|
217
|
+
for (const declaration of importDeclarations) {
|
|
218
|
+
for (const specifier of declaration.specifiers) {
|
|
219
|
+
importedNames.add(specifier.local.name)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const skipOption = ctx.pack?.skipGlobals ?? false
|
|
223
|
+
let skipNames: readonly string[] = []
|
|
224
|
+
if (skipOption === true) {
|
|
225
|
+
skipNames = STANDARD_GLOBALS
|
|
226
|
+
} else if (skipOption !== false) {
|
|
227
|
+
skipNames = skipOption
|
|
228
|
+
}
|
|
229
|
+
const skip = new Set<string>(skipNames)
|
|
230
|
+
|
|
112
231
|
const isRoutable = (path: NodePath<t.Identifier>): boolean => {
|
|
113
|
-
const
|
|
232
|
+
const name = path.node.name
|
|
114
233
|
// The wrapper parameter stays as written. `arguments` and `eval` are never
|
|
115
234
|
// routed: `arguments` binds to its own function's object, and leaving `eval`
|
|
116
235
|
// bare keeps a direct eval reading the body scope rather than the global one.
|
|
@@ -125,10 +244,28 @@ function packProgram(programPath: NodePath<t.Program>, ctx: TransformContext): b
|
|
|
125
244
|
if (!referencesOrWritesVariable(path)) {
|
|
126
245
|
return false
|
|
127
246
|
}
|
|
128
|
-
|
|
247
|
+
if (path.scope.getBinding(name)) {
|
|
248
|
+
return false
|
|
249
|
+
}
|
|
250
|
+
return importedNames.has(name) || !skip.has(name)
|
|
129
251
|
}
|
|
130
252
|
|
|
253
|
+
// A strict script's top-level `this` is the global object, but a strict
|
|
254
|
+
// Function called plainly sees `undefined`. Rather than reach for `.call`,
|
|
255
|
+
// route top-level `this` through a captured property like any free name.
|
|
256
|
+
const routeTopLevelThis = bodyStrict && !wasModule
|
|
257
|
+
let thisProperty: string | null = null
|
|
258
|
+
|
|
131
259
|
const routingVisitor: Visitor = {
|
|
260
|
+
ThisExpression(path) {
|
|
261
|
+
if (!routeTopLevelThis || !isTopLevelThis(path)) {
|
|
262
|
+
return
|
|
263
|
+
}
|
|
264
|
+
if (thisProperty === null) {
|
|
265
|
+
thisProperty = freshProperty()
|
|
266
|
+
}
|
|
267
|
+
path.replaceWith(memberFor(thisProperty))
|
|
268
|
+
},
|
|
132
269
|
UnaryExpression(path) {
|
|
133
270
|
if (path.node.operator !== 'typeof') {
|
|
134
271
|
return
|
|
@@ -147,7 +284,7 @@ function packProgram(programPath: NodePath<t.Program>, ctx: TransformContext): b
|
|
|
147
284
|
if (isBareDeleteArgument(path) || !isRoutable(path)) {
|
|
148
285
|
return
|
|
149
286
|
}
|
|
150
|
-
const
|
|
287
|
+
const name = path.node.name
|
|
151
288
|
if (isWriteReference(path)) {
|
|
152
289
|
settable.add(name)
|
|
153
290
|
}
|
|
@@ -163,7 +300,7 @@ function packProgram(programPath: NodePath<t.Program>, ctx: TransformContext): b
|
|
|
163
300
|
|
|
164
301
|
// The script completion value is its trailing expression; returning it keeps
|
|
165
302
|
// `eval` of the packed output producing the same value.
|
|
166
|
-
const
|
|
303
|
+
const body = programNode.body
|
|
167
304
|
const lastStatement = body.at(-1)
|
|
168
305
|
if (lastStatement && t.isExpressionStatement(lastStatement)) {
|
|
169
306
|
body[body.length - 1] = t.returnStatement(lastStatement.expression)
|
|
@@ -181,6 +318,7 @@ function packProgram(programPath: NodePath<t.Program>, ctx: TransformContext): b
|
|
|
181
318
|
settable,
|
|
182
319
|
setterParam,
|
|
183
320
|
strict: bodyStrict,
|
|
321
|
+
thisProperty,
|
|
184
322
|
})
|
|
185
323
|
|
|
186
324
|
const constructorReference = shadowedConstructor
|
|
@@ -193,16 +331,7 @@ function packProgram(programPath: NodePath<t.Program>, ctx: TransformContext): b
|
|
|
193
331
|
t.stringLiteral(objectName),
|
|
194
332
|
t.stringLiteral(innerCode),
|
|
195
333
|
])
|
|
196
|
-
|
|
197
|
-
// called strict function would instead see `undefined`, so pass the global
|
|
198
|
-
// `this` of the sloppy output script.
|
|
199
|
-
const invocation =
|
|
200
|
-
bodyStrict && !wasModule
|
|
201
|
-
? t.callExpression(t.memberExpression(wrapper, t.identifier('call')), [
|
|
202
|
-
t.thisExpression(),
|
|
203
|
-
objectExpression,
|
|
204
|
-
])
|
|
205
|
-
: t.callExpression(wrapper, [objectExpression])
|
|
334
|
+
const invocation = t.callExpression(wrapper, [objectExpression])
|
|
206
335
|
|
|
207
336
|
programNode.body = [...importDeclarations, t.expressionStatement(invocation)]
|
|
208
337
|
programNode.directives = []
|
|
@@ -238,6 +367,45 @@ function bindsFunctionConstructor(declaration: t.ImportDeclaration): boolean {
|
|
|
238
367
|
return declaration.specifiers.some((specifier) => specifier.local.name === 'Function')
|
|
239
368
|
}
|
|
240
369
|
|
|
370
|
+
// A `this` refers to the top-level `this` when no enclosing non-arrow function,
|
|
371
|
+
// class field, or static block captures it before the program does. A computed
|
|
372
|
+
// member key is the exception: it evaluates in the scope surrounding its method
|
|
373
|
+
// or field, not that member's `this`, so a `this` reached through a boundary's
|
|
374
|
+
// `key` skips it and keeps looking outward. `thisBinding` keeps every path
|
|
375
|
+
// predicate off the walked `current`, whose narrowed union would otherwise trip
|
|
376
|
+
// NodePath's invariant generic when reassigned to `child`.
|
|
377
|
+
function isTopLevelThis(path: NodePath<t.ThisExpression>): boolean {
|
|
378
|
+
let child: NodePath = path
|
|
379
|
+
let current: NodePath | null = path.parentPath
|
|
380
|
+
while (current) {
|
|
381
|
+
const binding = thisBinding(current)
|
|
382
|
+
if (binding === 'program') {
|
|
383
|
+
return true
|
|
384
|
+
}
|
|
385
|
+
if (binding === 'captured' && child.key !== 'key') {
|
|
386
|
+
return false
|
|
387
|
+
}
|
|
388
|
+
child = current
|
|
389
|
+
current = current.parentPath
|
|
390
|
+
}
|
|
391
|
+
return false
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function thisBinding(boundary: NodePath): 'captured' | 'program' | null {
|
|
395
|
+
if (boundary.isProgram()) {
|
|
396
|
+
return 'program'
|
|
397
|
+
}
|
|
398
|
+
if (
|
|
399
|
+
(boundary.isFunction() && !boundary.isArrowFunctionExpression()) ||
|
|
400
|
+
boundary.isClassProperty() ||
|
|
401
|
+
boundary.isClassPrivateProperty() ||
|
|
402
|
+
boundary.isStaticBlock()
|
|
403
|
+
) {
|
|
404
|
+
return 'captured'
|
|
405
|
+
}
|
|
406
|
+
return null
|
|
407
|
+
}
|
|
408
|
+
|
|
241
409
|
function isBareDeleteArgument(path: NodePath<t.Identifier>): boolean {
|
|
242
410
|
const parent = path.parentPath
|
|
243
411
|
return (
|
|
@@ -252,7 +420,7 @@ function isWriteReference(path: NodePath<t.Identifier>): boolean {
|
|
|
252
420
|
if (!parent) {
|
|
253
421
|
return false
|
|
254
422
|
}
|
|
255
|
-
const
|
|
423
|
+
const node = path.node
|
|
256
424
|
if (parent.isAssignmentExpression() && parent.node.left === node) {
|
|
257
425
|
return true
|
|
258
426
|
}
|
|
@@ -291,13 +459,13 @@ interface AccessorObjectSpec {
|
|
|
291
459
|
setterParam: string
|
|
292
460
|
settable: Set<string>
|
|
293
461
|
strict: boolean
|
|
462
|
+
thisProperty: string | null
|
|
294
463
|
typeofProperties: Map<string, string>
|
|
295
464
|
}
|
|
296
465
|
|
|
297
466
|
function buildAccessorObject(spec: AccessorObjectSpec): t.ObjectExpression {
|
|
298
|
-
const
|
|
299
|
-
const
|
|
300
|
-
for (const [name, property] of readProperties) {
|
|
467
|
+
const properties: (t.ObjectMethod | t.ObjectProperty)[] = []
|
|
468
|
+
for (const [name, property] of spec.readProperties) {
|
|
301
469
|
properties.push(
|
|
302
470
|
t.objectMethod(
|
|
303
471
|
'get',
|
|
@@ -306,23 +474,28 @@ function buildAccessorObject(spec: AccessorObjectSpec): t.ObjectExpression {
|
|
|
306
474
|
t.blockStatement([t.returnStatement(t.identifier(name))]),
|
|
307
475
|
),
|
|
308
476
|
)
|
|
309
|
-
if (settable.has(name)) {
|
|
477
|
+
if (spec.settable.has(name)) {
|
|
310
478
|
const setterBody = t.blockStatement([
|
|
311
479
|
t.expressionStatement(
|
|
312
|
-
t.assignmentExpression('=', t.identifier(name), t.identifier(setterParam)),
|
|
480
|
+
t.assignmentExpression('=', t.identifier(name), t.identifier(spec.setterParam)),
|
|
313
481
|
),
|
|
314
482
|
])
|
|
315
483
|
// A strict script becomes a sloppy output script, so the setter stays
|
|
316
484
|
// strict to keep an undeclared-global write throwing.
|
|
317
|
-
if (strict) {
|
|
485
|
+
if (spec.strict) {
|
|
318
486
|
setterBody.directives.push(t.directive(t.directiveLiteral('use strict')))
|
|
319
487
|
}
|
|
320
488
|
properties.push(
|
|
321
|
-
t.objectMethod(
|
|
489
|
+
t.objectMethod(
|
|
490
|
+
'set',
|
|
491
|
+
t.stringLiteral(property),
|
|
492
|
+
[t.identifier(spec.setterParam)],
|
|
493
|
+
setterBody,
|
|
494
|
+
),
|
|
322
495
|
)
|
|
323
496
|
}
|
|
324
497
|
}
|
|
325
|
-
for (const [name, property] of typeofProperties) {
|
|
498
|
+
for (const [name, property] of spec.typeofProperties) {
|
|
326
499
|
properties.push(
|
|
327
500
|
t.objectMethod(
|
|
328
501
|
'get',
|
|
@@ -332,5 +505,11 @@ function buildAccessorObject(spec: AccessorObjectSpec): t.ObjectExpression {
|
|
|
332
505
|
),
|
|
333
506
|
)
|
|
334
507
|
}
|
|
508
|
+
// This is a data property. Its `this` value evaluates in the output script's
|
|
509
|
+
// top-level scope, where `this` is the global object. A getter would bind
|
|
510
|
+
// `this` to the accessor object instead.
|
|
511
|
+
if (spec.thisProperty !== null) {
|
|
512
|
+
properties.push(t.objectProperty(t.stringLiteral(spec.thisProperty), t.thisExpression()))
|
|
513
|
+
}
|
|
335
514
|
return t.objectExpression(properties)
|
|
336
515
|
}
|
|
@@ -2,8 +2,8 @@ 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 { traverseForChanges } from '
|
|
6
|
-
import type { ChangeState } from '
|
|
5
|
+
import { traverseForChanges } from '../utils/change-tracking'
|
|
6
|
+
import type { ChangeState } from '../utils/change-tracking'
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Removes a function expression's name when the body does not use its binding
|
|
@@ -25,7 +25,7 @@ export function removeAnonymousFunctionNames(ast: File): boolean {
|
|
|
25
25
|
|
|
26
26
|
const visitor: Visitor<ChangeState> = {
|
|
27
27
|
FunctionExpression(path, state) {
|
|
28
|
-
const
|
|
28
|
+
const id = path.node.id
|
|
29
29
|
if (!id) {
|
|
30
30
|
return
|
|
31
31
|
}
|
|
@@ -48,8 +48,8 @@ const visitor: Visitor<ChangeState> = {
|
|
|
48
48
|
|
|
49
49
|
// No inferred name means `.name` would become empty.
|
|
50
50
|
function inferredName(path: NodePath<t.FunctionExpression>): string | null {
|
|
51
|
-
const
|
|
52
|
-
const
|
|
51
|
+
const node = path.node
|
|
52
|
+
const parentPath = path.parentPath
|
|
53
53
|
if (!parentPath) {
|
|
54
54
|
return null
|
|
55
55
|
}
|
|
@@ -2,8 +2,8 @@ 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 { traverseForChanges } from '
|
|
6
|
-
import type { ChangeState } from '
|
|
5
|
+
import { traverseForChanges } from '../utils/change-tracking'
|
|
6
|
+
import type { ChangeState } from '../utils/change-tracking'
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Removes statements after unconditional control flow in the same statement list.
|
|
@@ -72,7 +72,7 @@ function isCompletion(node: t.Node): boolean {
|
|
|
72
72
|
// Hoisted bindings and referenced lexical TDZ remain observable from reachable
|
|
73
73
|
// code. An unreferenced lexical declaration is inert.
|
|
74
74
|
function isSafeToRemoveAfterCompletion(path: NodePath<t.Statement>): boolean {
|
|
75
|
-
const
|
|
75
|
+
const node = path.node
|
|
76
76
|
if (t.isFunctionDeclaration(node) || (t.isVariableDeclaration(node) && node.kind === 'var')) {
|
|
77
77
|
return false
|
|
78
78
|
}
|
|
@@ -3,10 +3,10 @@ import type { NodePath, Scope, Visitor } from '@babel/traverse'
|
|
|
3
3
|
import * as t from '@babel/types'
|
|
4
4
|
import type { File } from '@babel/types'
|
|
5
5
|
|
|
6
|
-
import { isSideEffectFree } from '
|
|
7
|
-
import { isDirectEvalCall } from '
|
|
8
|
-
import type { ChangeState } from '
|
|
9
|
-
import { hasAnnexBFunctionAlias } from '
|
|
6
|
+
import { isSideEffectFree } from '../analysis/purity'
|
|
7
|
+
import { isDirectEvalCall } from '../utils/ast'
|
|
8
|
+
import type { ChangeState } from '../utils/change-tracking'
|
|
9
|
+
import { hasAnnexBFunctionAlias } from '../utils/paths'
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Removes unused declarations and expression statements that have no effects.
|
|
@@ -150,12 +150,12 @@ function tryRemoveDeclarator(
|
|
|
150
150
|
return false
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
const
|
|
153
|
+
const id = path.node.id
|
|
154
154
|
if (!t.isIdentifier(id)) {
|
|
155
155
|
return false
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
const
|
|
158
|
+
const init = path.node.init
|
|
159
159
|
if (init && !isSideEffectFree(init, path.scope, path.get('init') as NodePath)) {
|
|
160
160
|
return false
|
|
161
161
|
}
|
|
@@ -196,7 +196,7 @@ function tryRemoveFunctionDecl(
|
|
|
196
196
|
if (hasAnnexBFunctionAlias(path)) {
|
|
197
197
|
return false
|
|
198
198
|
}
|
|
199
|
-
const
|
|
199
|
+
const id = path.node.id
|
|
200
200
|
if (!id) {
|
|
201
201
|
return false
|
|
202
202
|
}
|
|
@@ -228,7 +228,7 @@ function tryRemoveClassDecl(
|
|
|
228
228
|
if (isExported(path)) {
|
|
229
229
|
return false
|
|
230
230
|
}
|
|
231
|
-
const
|
|
231
|
+
const id = path.node.id
|
|
232
232
|
if (!id) {
|
|
233
233
|
return false
|
|
234
234
|
}
|
|
@@ -3,11 +3,11 @@ import type { Binding, Scope } from '@babel/traverse'
|
|
|
3
3
|
import * as t from '@babel/types'
|
|
4
4
|
import type { File } from '@babel/types'
|
|
5
5
|
|
|
6
|
-
import type { TransformContext } from '
|
|
7
|
-
import { isDirectEvalCall, isInsideWith } from '
|
|
8
|
-
import { hasAnnexBFunctionAlias, referencesOrWritesVariable } from '
|
|
9
|
-
import { mulberry32 } from '
|
|
10
|
-
import { StringGenerator } from '
|
|
6
|
+
import type { TransformContext } from '../options'
|
|
7
|
+
import { isDirectEvalCall, isInsideWith } from '../utils/ast'
|
|
8
|
+
import { hasAnnexBFunctionAlias, referencesOrWritesVariable } from '../utils/paths'
|
|
9
|
+
import { mulberry32 } from '../utils/random'
|
|
10
|
+
import { StringGenerator } from '../utils/string-generator'
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Renames bindings and labels with a fresh `StringGenerator`. Each scope avoids
|
|
@@ -125,7 +125,7 @@ export function renameIdentifiers(ast: File, ctx: TransformContext): boolean {
|
|
|
125
125
|
|
|
126
126
|
traverse(ast, {
|
|
127
127
|
Scopable(path) {
|
|
128
|
-
const
|
|
128
|
+
const scope = path.scope
|
|
129
129
|
// Test global reservations during allocation to avoid copying them per scope.
|
|
130
130
|
const offLimits = new Set<string>()
|
|
131
131
|
const live = liveAncestors.get(scope)
|
|
@@ -174,7 +174,7 @@ export function renameIdentifiers(ast: File, ctx: TransformContext): boolean {
|
|
|
174
174
|
|
|
175
175
|
traverse(ast, {
|
|
176
176
|
Identifier(path) {
|
|
177
|
-
const
|
|
177
|
+
const node = path.node
|
|
178
178
|
const directName = idToName.get(node)
|
|
179
179
|
if (directName !== undefined) {
|
|
180
180
|
node.name = directName
|
|
@@ -200,7 +200,8 @@ export function renameIdentifiers(ast: File, ctx: TransformContext): boolean {
|
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
function isNonBindingPosition(path: { node: t.Identifier; parent: t.Node }): boolean {
|
|
203
|
-
const
|
|
203
|
+
const node = path.node
|
|
204
|
+
const parent = path.parent
|
|
204
205
|
// A PrivateName id is not a variable and must match its class declaration.
|
|
205
206
|
if (t.isPrivateName(parent)) {
|
|
206
207
|
return true
|
|
@@ -3,11 +3,11 @@ import type { NodePath, Visitor } from '@babel/traverse'
|
|
|
3
3
|
import * as t from '@babel/types'
|
|
4
4
|
import type { File } from '@babel/types'
|
|
5
5
|
|
|
6
|
-
import type { TransformContext } from '
|
|
7
|
-
import { enclosingScopeHasDirectEval, isInsideWith } from '
|
|
8
|
-
import type { ChangeState } from '
|
|
9
|
-
import { mulberry32 } from '
|
|
10
|
-
import { StringGenerator } from '
|
|
6
|
+
import type { TransformContext } from '../options'
|
|
7
|
+
import { enclosingScopeHasDirectEval, isInsideWith } from '../utils/ast'
|
|
8
|
+
import type { ChangeState } from '../utils/change-tracking'
|
|
9
|
+
import { mulberry32 } from '../utils/random'
|
|
10
|
+
import { StringGenerator } from '../utils/string-generator'
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Rewrites five constant-like values through strings for later string mangling:
|
|
@@ -59,7 +59,7 @@ const visitor: Visitor<State> = {
|
|
|
59
59
|
state.changed = true
|
|
60
60
|
},
|
|
61
61
|
Identifier(path, state) {
|
|
62
|
-
const
|
|
62
|
+
const name = path.node.name
|
|
63
63
|
if (!GLOBAL_SPECIALS.has(name)) {
|
|
64
64
|
return
|
|
65
65
|
}
|
|
@@ -93,7 +93,7 @@ function buildGlobalReplacement(name: string, str: t.StringLiteral): t.Expressio
|
|
|
93
93
|
// Babel calls update and iterator targets references, but replacements there
|
|
94
94
|
// would not be assignable. Delete also distinguishes references from values.
|
|
95
95
|
function isWriteTarget(path: NodePath<t.Identifier>): boolean {
|
|
96
|
-
const
|
|
96
|
+
const parentPath = path.parentPath
|
|
97
97
|
if (!parentPath) {
|
|
98
98
|
return false
|
|
99
99
|
}
|
|
@@ -2,11 +2,11 @@ import type { NodePath, Scope, Visitor } from '@babel/traverse'
|
|
|
2
2
|
import * as t from '@babel/types'
|
|
3
3
|
import type { File } from '@babel/types'
|
|
4
4
|
|
|
5
|
-
import { isPure } from '
|
|
6
|
-
import { traverseForChanges } from '
|
|
7
|
-
import type { ChangeState } from '
|
|
8
|
-
import { isPrimitiveLiteral } from '
|
|
9
|
-
import { generateLoopLabel } from '
|
|
5
|
+
import { isPure } from '../analysis/purity'
|
|
6
|
+
import { traverseForChanges } from '../utils/change-tracking'
|
|
7
|
+
import type { ChangeState } from '../utils/change-tracking'
|
|
8
|
+
import { isPrimitiveLiteral } from '../utils/literal'
|
|
9
|
+
import { generateLoopLabel } from '../utils/loop-lowering'
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Lowers `switch` to an `if` chain or, when cases fall through, a labeled block
|
|
@@ -46,7 +46,7 @@ const visitor: Visitor<ChangeState> = {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
function transformSwitch(path: NodePath<t.SwitchStatement>): boolean {
|
|
49
|
-
const
|
|
49
|
+
const node = path.node
|
|
50
50
|
// Separate `if` blocks cannot preserve cross-case lexical scope or Annex B
|
|
51
51
|
// function hoisting.
|
|
52
52
|
if (hasBlockScopedDeclaration(node)) {
|
|
@@ -114,7 +114,7 @@ function canLowerToIfElseChain(node: t.SwitchStatement): boolean {
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
function caseEligibleForIfElseChain(caseClause: t.SwitchCase, isLast: boolean): boolean {
|
|
117
|
-
const
|
|
117
|
+
const consequent = caseClause.consequent
|
|
118
118
|
if (consequent.length === 0) {
|
|
119
119
|
return isLast
|
|
120
120
|
}
|
|
@@ -189,8 +189,8 @@ function buildBody(consequent: t.Statement[]): t.BlockStatement {
|
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
function lowerToIfElseChain(path: NodePath<t.SwitchStatement>): void {
|
|
192
|
-
const
|
|
193
|
-
const
|
|
192
|
+
const node = path.node
|
|
193
|
+
const scope = path.scope
|
|
194
194
|
|
|
195
195
|
let discriminantExpr: t.Expression
|
|
196
196
|
const preStmts: t.Statement[] = []
|
|
@@ -244,8 +244,8 @@ function lowerToIfElseChain(path: NodePath<t.SwitchStatement>): void {
|
|
|
244
244
|
}
|
|
245
245
|
|
|
246
246
|
function lowerToLabeledBlock(path: NodePath<t.SwitchStatement>): void {
|
|
247
|
-
const
|
|
248
|
-
const
|
|
247
|
+
const node = path.node
|
|
248
|
+
const scope = path.scope
|
|
249
249
|
|
|
250
250
|
// `scope.generateUid` alone can collide with enclosing or nested labels.
|
|
251
251
|
const labelName = generateLoopLabel(path, 'switch')
|
|
@@ -314,7 +314,7 @@ function lowerToLabeledBlock(path: NodePath<t.SwitchStatement>): void {
|
|
|
314
314
|
|
|
315
315
|
const labeledBlock = t.labeledStatement(t.identifier(labelName), t.blockStatement(statements))
|
|
316
316
|
|
|
317
|
-
const
|
|
317
|
+
const labeledPath = path.replaceWith(labeledBlock)[0]
|
|
318
318
|
rewriteUnlabeledBreaks(labeledPath, labelName)
|
|
319
319
|
}
|
|
320
320
|
|
|
@@ -2,11 +2,11 @@ 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 '
|
|
6
|
-
import { traverseForChanges } from '
|
|
7
|
-
import type { ChangeState } from '
|
|
8
|
-
import { isConditionalGate, reorderableToStatement } from '
|
|
9
|
-
import { isStandaloneDeclaration } from '
|
|
5
|
+
import { isInsideWith } from '../utils/ast'
|
|
6
|
+
import { traverseForChanges } from '../utils/change-tracking'
|
|
7
|
+
import type { ChangeState } from '../utils/change-tracking'
|
|
8
|
+
import { isConditionalGate, reorderableToStatement } from '../utils/evaluation-order'
|
|
9
|
+
import { isStandaloneDeclaration } from '../utils/paths'
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Lowers conditional expressions to `if` statements. Expression values use a
|
|
@@ -67,7 +67,9 @@ function lowerConditional(path: NodePath<t.ConditionalExpression>): boolean {
|
|
|
67
67
|
return false
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
const
|
|
70
|
+
const test = path.node.test
|
|
71
|
+
const consequent = path.node.consequent
|
|
72
|
+
const alternate = path.node.alternate
|
|
71
73
|
|
|
72
74
|
if (parent.isExpressionStatement() && parent.node.expression === path.node) {
|
|
73
75
|
parent.replaceWith(
|
|
@@ -163,7 +165,7 @@ function lowerConditional(path: NodePath<t.ConditionalExpression>): boolean {
|
|
|
163
165
|
return false
|
|
164
166
|
}
|
|
165
167
|
|
|
166
|
-
const
|
|
168
|
+
const scope = path.scope
|
|
167
169
|
const tmpId = scope.generateUidIdentifier('ternaryResult')
|
|
168
170
|
scope.push({ id: t.cloneNode(tmpId) })
|
|
169
171
|
|
|
@@ -293,8 +295,9 @@ function lowerEnclosingLogical(path: NodePath, stmtPath: NodePath): boolean {
|
|
|
293
295
|
if (!logical || logical.removed || !logical.container) {
|
|
294
296
|
return false
|
|
295
297
|
}
|
|
296
|
-
const
|
|
297
|
-
const
|
|
298
|
+
const node = logical.node
|
|
299
|
+
const left = node.left
|
|
300
|
+
const right = node.right
|
|
298
301
|
let test: t.Expression
|
|
299
302
|
let valueRef: t.Expression
|
|
300
303
|
if (t.isIdentifier(left) && logical.scope.hasBinding(left.name)) {
|
|
@@ -2,9 +2,9 @@ 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 { isDirectEvalCall, isInsideWith } from '
|
|
6
|
-
import { traverseForChanges } from '
|
|
7
|
-
import type { ChangeState } from '
|
|
5
|
+
import { isDirectEvalCall, isInsideWith } from '../utils/ast'
|
|
6
|
+
import { traverseForChanges } from '../utils/change-tracking'
|
|
7
|
+
import type { ChangeState } from '../utils/change-tracking'
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Rewrites numeric updates as compound assignments for later expansion. Prefix
|
|
@@ -35,7 +35,9 @@ export function updateToAssignment(ast: File): boolean {
|
|
|
35
35
|
|
|
36
36
|
const visitor: Visitor<ChangeState> = {
|
|
37
37
|
UpdateExpression(path, state) {
|
|
38
|
-
const
|
|
38
|
+
const argument = path.node.argument
|
|
39
|
+
const prefix = path.node.prefix
|
|
40
|
+
const operator = path.node.operator
|
|
39
41
|
if (!t.isIdentifier(argument)) {
|
|
40
42
|
return
|
|
41
43
|
}
|
|
@@ -61,7 +63,7 @@ const visitor: Visitor<ChangeState> = {
|
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
function valueIsDiscarded(path: NodePath<t.UpdateExpression>): boolean {
|
|
64
|
-
const
|
|
66
|
+
const parentPath = path.parentPath
|
|
65
67
|
if (!parentPath) {
|
|
66
68
|
return false
|
|
67
69
|
}
|