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
|
@@ -2,7 +2,6 @@ import type { Binding, NodePath, Visitor } from '@babel/traverse'
|
|
|
2
2
|
import * as t from '@babel/types'
|
|
3
3
|
import type { File } from '@babel/types'
|
|
4
4
|
|
|
5
|
-
import { isDirectEvalCall, isInsideWith } from '../utils/ast'
|
|
6
5
|
import { traverseForChanges } from '../utils/change-tracking'
|
|
7
6
|
import type { ChangeState } from '../utils/change-tracking'
|
|
8
7
|
|
|
@@ -44,18 +43,10 @@ const visitor: Visitor<ChangeState> = {
|
|
|
44
43
|
if (!prefix && !valueIsDiscarded(path)) {
|
|
45
44
|
return
|
|
46
45
|
}
|
|
47
|
-
// `with` can retarget the name and defeat the numeric proof.
|
|
48
|
-
if (isInsideWith(path)) {
|
|
49
|
-
return
|
|
50
|
-
}
|
|
51
46
|
const binding = path.scope.getBinding(argument.name)
|
|
52
47
|
if (!isNumericBinding(binding)) {
|
|
53
48
|
return
|
|
54
49
|
}
|
|
55
|
-
// Direct eval anywhere in the binding's scope can change its type.
|
|
56
|
-
if (bindingScopeReachesDirectEval(binding!)) {
|
|
57
|
-
return
|
|
58
|
-
}
|
|
59
50
|
const compound = operator === '++' ? '+=' : '-='
|
|
60
51
|
path.replaceWith(t.assignmentExpression(compound, argument, t.numericLiteral(1)))
|
|
61
52
|
state.changed = true
|
|
@@ -79,34 +70,25 @@ function valueIsDiscarded(path: NodePath<t.UpdateExpression>): boolean {
|
|
|
79
70
|
return false
|
|
80
71
|
}
|
|
81
72
|
|
|
82
|
-
//
|
|
73
|
+
// Mutable bindings must stay numeric. A const write cannot change its value.
|
|
83
74
|
function isNumericBinding(binding: Binding | undefined): boolean {
|
|
84
|
-
if (!binding || (binding.kind !== 'var' && binding.kind !== 'let')) {
|
|
75
|
+
if (!binding || (binding.kind !== 'var' && binding.kind !== 'let' && binding.kind !== 'const')) {
|
|
85
76
|
return false
|
|
86
77
|
}
|
|
87
78
|
const declarator = binding.path.node
|
|
88
79
|
if (!t.isVariableDeclarator(declarator) || !t.isNumericLiteral(declarator.init)) {
|
|
89
80
|
return false
|
|
90
81
|
}
|
|
91
|
-
// Babel
|
|
82
|
+
// Babel does not record each value assigned by a `for-in` or `for-of` head.
|
|
92
83
|
if (isForInOfLoopVariable(binding)) {
|
|
93
84
|
return false
|
|
94
85
|
}
|
|
86
|
+
if (binding.kind === 'const') {
|
|
87
|
+
return true
|
|
88
|
+
}
|
|
95
89
|
return binding.constantViolations.every((violation) => keepsNumeric(violation.node))
|
|
96
90
|
}
|
|
97
91
|
|
|
98
|
-
// A sibling or nested direct eval can still close over and reassign the binding.
|
|
99
|
-
function bindingScopeReachesDirectEval(binding: Binding): boolean {
|
|
100
|
-
let found = false
|
|
101
|
-
t.traverseFast(binding.scope.block, (node) => {
|
|
102
|
-
if (isDirectEvalCall(node)) {
|
|
103
|
-
found = true
|
|
104
|
-
}
|
|
105
|
-
return found ? t.traverseFast.stop : undefined
|
|
106
|
-
})
|
|
107
|
-
return found
|
|
108
|
-
}
|
|
109
|
-
|
|
110
92
|
function isForInOfLoopVariable(binding: Binding): boolean {
|
|
111
93
|
const declaration = binding.path.parentPath
|
|
112
94
|
if (!declaration || !declaration.isVariableDeclaration()) {
|
|
@@ -7,10 +7,9 @@ import type { ChangeState } from '../utils/change-tracking'
|
|
|
7
7
|
import { isLiteralShaped } from '../utils/literal'
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Moves a
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* Unary operations over literals also count when their coercion cannot act.
|
|
10
|
+
* Moves a right operand made from literals and unary operators to the left of a
|
|
11
|
+
* comparison when swapping does not change coercion order. Relational operators
|
|
12
|
+
* are flipped after swapping.
|
|
14
13
|
*
|
|
15
14
|
* @example
|
|
16
15
|
* // ◀️ before
|
package/src/utils/ast.ts
CHANGED
|
@@ -62,75 +62,10 @@ export function isProtoKey(key: t.Node): boolean {
|
|
|
62
62
|
)
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
//
|
|
66
|
-
export function
|
|
67
|
-
|
|
68
|
-
return false
|
|
69
|
-
}
|
|
70
|
-
return path.findParent((ancestor) => ancestor.isWithStatement()) !== null
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Direct eval can access local bindings by name. Indirect eval runs globally and
|
|
74
|
-
// does not count.
|
|
75
|
-
export function enclosingScopeHasDirectEval(path: NodePath): boolean {
|
|
76
|
-
if (!programContains(path.scope.getProgramParent().block, programDirectEval, isDirectEvalCall)) {
|
|
77
|
-
return false
|
|
78
|
-
}
|
|
79
|
-
let current: NodePath | null = path
|
|
80
|
-
while (current) {
|
|
81
|
-
if ((current.isFunction() || current.isProgram()) && ownScopeHasDirectEval(current.node)) {
|
|
82
|
-
return true
|
|
83
|
-
}
|
|
84
|
-
current = current.parentPath
|
|
85
|
-
}
|
|
86
|
-
return false
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Cache program-wide `eval` and `with` checks to avoid a subtree walk per name.
|
|
90
|
-
// No transform creates either construct, so a negative result stays valid.
|
|
91
|
-
const programDirectEval = new WeakMap<t.Node, boolean>()
|
|
92
|
-
const programWith = new WeakMap<t.Node, boolean>()
|
|
93
|
-
|
|
94
|
-
function programContains(
|
|
95
|
-
programNode: t.Node,
|
|
96
|
-
cache: WeakMap<t.Node, boolean>,
|
|
97
|
-
matches: (node: t.Node) => boolean,
|
|
98
|
-
): boolean {
|
|
99
|
-
const cached = cache.get(programNode)
|
|
100
|
-
if (cached !== undefined) {
|
|
101
|
-
return cached
|
|
102
|
-
}
|
|
103
|
-
let found = false
|
|
104
|
-
t.traverseFast(programNode, (node) => {
|
|
105
|
-
if (!matches(node)) {
|
|
106
|
-
return
|
|
107
|
-
}
|
|
108
|
-
found = true
|
|
109
|
-
return t.traverseFast.stop
|
|
110
|
-
})
|
|
111
|
-
cache.set(programNode, found)
|
|
112
|
-
return found
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function ownScopeHasDirectEval(owner: t.Node): boolean {
|
|
116
|
-
let found = false
|
|
117
|
-
// This walk stops at the first hit, unlike `walkOwnFunctionScope`.
|
|
118
|
-
t.traverseFast(owner, (node) => {
|
|
119
|
-
if (isDirectEvalCall(node)) {
|
|
120
|
-
found = true
|
|
121
|
-
return t.traverseFast.stop
|
|
122
|
-
}
|
|
123
|
-
return node !== owner && t.isFunction(node) ? t.traverseFast.skip : undefined
|
|
124
|
-
})
|
|
125
|
-
return found
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export function isDirectEvalCall(node: t.Node): boolean {
|
|
129
|
-
// `eval?.(...)` is an OptionalCallExpression yet still a direct eval, so both
|
|
130
|
-
// call node shapes count.
|
|
65
|
+
// A non-computed key names a property; it is not a runtime value.
|
|
66
|
+
export function isNonComputedKey(path: NodePath): boolean {
|
|
67
|
+
const parent = path.parent
|
|
131
68
|
return (
|
|
132
|
-
|
|
133
|
-
t.isIdentifier(node.callee) &&
|
|
134
|
-
node.callee.name === 'eval'
|
|
69
|
+
'key' in parent && parent.key === path.node && 'computed' in parent && parent.computed === false
|
|
135
70
|
)
|
|
136
71
|
}
|
|
@@ -133,7 +133,7 @@ function precedingSiblings<T extends NodePath>(
|
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
function isPurePath(path: NodePath): boolean {
|
|
136
|
-
return isPure(path.node, path.scope
|
|
136
|
+
return isPure(path.node, path.scope)
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
function impure(paths: (NodePath | null | undefined)[]): boolean {
|
package/src/utils/literal.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import generate from '@babel/generator'
|
|
1
2
|
import * as t from '@babel/types'
|
|
2
3
|
|
|
4
|
+
const MAX_GENERATED_LITERAL_LENGTH = 8192
|
|
5
|
+
const MAX_LITERAL_DEPTH = 32
|
|
6
|
+
const MAX_LITERAL_NODES = 512
|
|
3
7
|
const MAX_STRING_LITERAL_LENGTH = 4096
|
|
4
8
|
|
|
5
9
|
// RegExp literals allocate mutable objects and are not primitive values.
|
|
@@ -45,19 +49,56 @@ function literalShapeKind(node: t.Node): 'bigint' | 'other' | null {
|
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
export function valueToLiteral(value: unknown): t.Expression | null {
|
|
52
|
+
const literal = buildValueLiteral(value, new Set(), { nodesRemaining: MAX_LITERAL_NODES }, 0)
|
|
53
|
+
if (!literal || generate(literal).code.length > MAX_GENERATED_LITERAL_LENGTH) {
|
|
54
|
+
return null
|
|
55
|
+
}
|
|
56
|
+
return literal
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface LiteralBudget {
|
|
60
|
+
nodesRemaining: number
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function reserveLiteralNodes(budget: LiteralBudget, count: number): boolean {
|
|
64
|
+
if (budget.nodesRemaining < count) {
|
|
65
|
+
return false
|
|
66
|
+
}
|
|
67
|
+
budget.nodesRemaining -= count
|
|
68
|
+
return true
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function buildValueLiteral(
|
|
72
|
+
value: unknown,
|
|
73
|
+
seen: Set<object>,
|
|
74
|
+
budget: LiteralBudget,
|
|
75
|
+
depth: number,
|
|
76
|
+
): t.Expression | null {
|
|
77
|
+
if (depth > MAX_LITERAL_DEPTH) {
|
|
78
|
+
return null
|
|
79
|
+
}
|
|
48
80
|
if (typeof value === 'string') {
|
|
49
|
-
if (value.length > MAX_STRING_LITERAL_LENGTH) {
|
|
81
|
+
if (value.length > MAX_STRING_LITERAL_LENGTH || !reserveLiteralNodes(budget, 1)) {
|
|
50
82
|
return null
|
|
51
83
|
}
|
|
52
84
|
return t.stringLiteral(value)
|
|
53
85
|
}
|
|
54
86
|
if (typeof value === 'boolean') {
|
|
87
|
+
if (!reserveLiteralNodes(budget, 1)) {
|
|
88
|
+
return null
|
|
89
|
+
}
|
|
55
90
|
return t.booleanLiteral(value)
|
|
56
91
|
}
|
|
57
92
|
if (value === null) {
|
|
93
|
+
if (!reserveLiteralNodes(budget, 1)) {
|
|
94
|
+
return null
|
|
95
|
+
}
|
|
58
96
|
return t.nullLiteral()
|
|
59
97
|
}
|
|
60
98
|
if (value === undefined) {
|
|
99
|
+
if (!reserveLiteralNodes(budget, 2)) {
|
|
100
|
+
return null
|
|
101
|
+
}
|
|
61
102
|
// `void 0` cannot be shadowed like the identifier `undefined`.
|
|
62
103
|
return t.unaryExpression('void', t.numericLiteral(0))
|
|
63
104
|
}
|
|
@@ -65,8 +106,14 @@ export function valueToLiteral(value: unknown): t.Expression | null {
|
|
|
65
106
|
// A UnaryExpression lets the generator parenthesize negative BigInt where
|
|
66
107
|
// precedence requires it.
|
|
67
108
|
if (value < 0n) {
|
|
109
|
+
if (!reserveLiteralNodes(budget, 2)) {
|
|
110
|
+
return null
|
|
111
|
+
}
|
|
68
112
|
return t.unaryExpression('-', t.bigIntLiteral(-value))
|
|
69
113
|
}
|
|
114
|
+
if (!reserveLiteralNodes(budget, 1)) {
|
|
115
|
+
return null
|
|
116
|
+
}
|
|
70
117
|
return t.bigIntLiteral(value)
|
|
71
118
|
}
|
|
72
119
|
if (typeof value === 'number') {
|
|
@@ -74,17 +121,30 @@ export function valueToLiteral(value: unknown): t.Expression | null {
|
|
|
74
121
|
return null
|
|
75
122
|
}
|
|
76
123
|
if (Object.is(value, -0)) {
|
|
124
|
+
if (!reserveLiteralNodes(budget, 2)) {
|
|
125
|
+
return null
|
|
126
|
+
}
|
|
77
127
|
return t.unaryExpression('-', t.numericLiteral(0))
|
|
78
128
|
}
|
|
79
129
|
if (value < 0) {
|
|
130
|
+
if (!reserveLiteralNodes(budget, 2)) {
|
|
131
|
+
return null
|
|
132
|
+
}
|
|
80
133
|
return t.unaryExpression('-', t.numericLiteral(-value))
|
|
81
134
|
}
|
|
135
|
+
if (!reserveLiteralNodes(budget, 1)) {
|
|
136
|
+
return null
|
|
137
|
+
}
|
|
82
138
|
return t.numericLiteral(value)
|
|
83
139
|
}
|
|
84
140
|
if (Array.isArray(value)) {
|
|
141
|
+
if (seen.has(value) || !arrayHasLiteralShape(value) || !reserveLiteralNodes(budget, 1)) {
|
|
142
|
+
return null
|
|
143
|
+
}
|
|
144
|
+
seen.add(value)
|
|
85
145
|
const elements: t.Expression[] = []
|
|
86
146
|
for (const item of value) {
|
|
87
|
-
const lit =
|
|
147
|
+
const lit = buildValueLiteral(item, seen, budget, depth + 1)
|
|
88
148
|
if (!lit) {
|
|
89
149
|
return null
|
|
90
150
|
}
|
|
@@ -94,3 +154,32 @@ export function valueToLiteral(value: unknown): t.Expression | null {
|
|
|
94
154
|
}
|
|
95
155
|
return null
|
|
96
156
|
}
|
|
157
|
+
|
|
158
|
+
function arrayHasLiteralShape(value: unknown[]): boolean {
|
|
159
|
+
if (Object.getPrototypeOf(value) !== Array.prototype || !Object.isExtensible(value)) {
|
|
160
|
+
return false
|
|
161
|
+
}
|
|
162
|
+
const names = Object.getOwnPropertyNames(value)
|
|
163
|
+
if (names.length !== value.length + 1 || names.at(-1) !== 'length') {
|
|
164
|
+
return false
|
|
165
|
+
}
|
|
166
|
+
for (let index = 0; index < value.length; index++) {
|
|
167
|
+
if (
|
|
168
|
+
names[index] !== String(index) ||
|
|
169
|
+
!isOrdinaryDataProperty(Object.getOwnPropertyDescriptor(value, String(index)))
|
|
170
|
+
) {
|
|
171
|
+
return false
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return Object.getOwnPropertySymbols(value).length === 0
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function isOrdinaryDataProperty(descriptor: PropertyDescriptor | undefined): boolean {
|
|
178
|
+
return (
|
|
179
|
+
descriptor !== undefined &&
|
|
180
|
+
'value' in descriptor &&
|
|
181
|
+
descriptor.configurable === true &&
|
|
182
|
+
descriptor.enumerable === true &&
|
|
183
|
+
descriptor.writable === true
|
|
184
|
+
)
|
|
185
|
+
}
|
|
@@ -9,7 +9,7 @@ import { isStandaloneDeclaration } from './paths'
|
|
|
9
9
|
// x = L;
|
|
10
10
|
// if (<fallbackTest(x)>) x = R;
|
|
11
11
|
//
|
|
12
|
-
// `fallbackTest` selects
|
|
12
|
+
// `fallbackTest` selects the short-circuit condition. The parent must accept a
|
|
13
13
|
// multi-statement replacement.
|
|
14
14
|
export function lowerLogicalWriteStatement(
|
|
15
15
|
path: NodePath<t.LogicalExpression>,
|
|
@@ -29,21 +29,20 @@ export function lowerLogicalWriteStatement(
|
|
|
29
29
|
const declStmt = parent.parentPath
|
|
30
30
|
if (
|
|
31
31
|
!declStmt?.isVariableDeclaration() ||
|
|
32
|
-
// The split would emit illegal `const x;` and then write it.
|
|
33
|
-
declStmt.node.kind === 'const' ||
|
|
34
32
|
declStmt.node.declarations.length !== 1 ||
|
|
35
33
|
!isStandaloneDeclaration(declStmt)
|
|
36
34
|
) {
|
|
37
35
|
return false
|
|
38
36
|
}
|
|
39
37
|
const target = parent.node.id
|
|
40
|
-
//
|
|
41
|
-
// side
|
|
42
|
-
if (rightCanObserveTarget(node.right, target.name)) {
|
|
38
|
+
// A `var` initializer can observe its previous value while evaluating the
|
|
39
|
+
// right side. Reads before lexical initialization are intentionally ignored.
|
|
40
|
+
if (declStmt.node.kind === 'var' && rightCanObserveTarget(node.right, target.name)) {
|
|
43
41
|
return false
|
|
44
42
|
}
|
|
43
|
+
const declarationKind = declStmt.node.kind === 'const' ? 'let' : declStmt.node.kind
|
|
45
44
|
declStmt.replaceWithMultiple([
|
|
46
|
-
t.variableDeclaration(
|
|
45
|
+
t.variableDeclaration(declarationKind, [
|
|
47
46
|
t.variableDeclarator(t.cloneNode(target), node.left),
|
|
48
47
|
]),
|
|
49
48
|
guardedReassignment(target, fallbackTest, node.right),
|
|
@@ -68,10 +67,7 @@ export function lowerLogicalWriteStatement(
|
|
|
68
67
|
return false
|
|
69
68
|
}
|
|
70
69
|
// Writing first would change `x = 0 || x` before the right side reads `x`.
|
|
71
|
-
if (
|
|
72
|
-
!isSideEffectFree(node.right, path.scope, path) ||
|
|
73
|
-
referencesName(node.right, target.name)
|
|
74
|
-
) {
|
|
70
|
+
if (!isSideEffectFree(node.right, path.scope) || referencesName(node.right, target.name)) {
|
|
75
71
|
return false
|
|
76
72
|
}
|
|
77
73
|
exprStmt.replaceWithMultiple([
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as t from '@babel/types'
|
|
2
|
+
|
|
3
|
+
export function buildNullishCheck(
|
|
4
|
+
firstRead: t.Expression,
|
|
5
|
+
secondRead: t.Expression,
|
|
6
|
+
): t.ConditionalExpression {
|
|
7
|
+
return t.conditionalExpression(
|
|
8
|
+
t.binaryExpression('===', firstRead, t.nullLiteral()),
|
|
9
|
+
t.booleanLiteral(true),
|
|
10
|
+
t.binaryExpression('===', secondRead, t.unaryExpression('void', t.numericLiteral(0))),
|
|
11
|
+
)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function buildNonNullishCheck(
|
|
15
|
+
firstRead: t.Expression,
|
|
16
|
+
secondRead: t.Expression,
|
|
17
|
+
): t.ConditionalExpression {
|
|
18
|
+
return t.conditionalExpression(
|
|
19
|
+
t.binaryExpression('!==', firstRead, t.nullLiteral()),
|
|
20
|
+
t.binaryExpression('!==', secondRead, t.unaryExpression('void', t.numericLiteral(0))),
|
|
21
|
+
t.booleanLiteral(false),
|
|
22
|
+
)
|
|
23
|
+
}
|
package/src/utils/paths.ts
CHANGED
|
@@ -69,35 +69,3 @@ export function isInStrictContext(path: NodePath): boolean {
|
|
|
69
69
|
function hasUseStrictDirective(directives: readonly t.Directive[]): boolean {
|
|
70
70
|
return directives.some((directive) => directive.value.value === 'use strict')
|
|
71
71
|
}
|
|
72
|
-
|
|
73
|
-
// ECMA-262 B.3.3 gives sloppy block-level functions a `var` alias that Babel
|
|
74
|
-
// omits from the enclosing scope. Renaming, moving, or removing one can expose it.
|
|
75
|
-
export function hasAnnexBFunctionAlias(path: NodePath): boolean {
|
|
76
|
-
if (!path.isFunctionDeclaration()) {
|
|
77
|
-
return false
|
|
78
|
-
}
|
|
79
|
-
const parent = path.parentPath
|
|
80
|
-
if (!parent || parent.isProgram()) {
|
|
81
|
-
return false
|
|
82
|
-
}
|
|
83
|
-
if (
|
|
84
|
-
parent.isBlockStatement() &&
|
|
85
|
-
parent.key === 'body' &&
|
|
86
|
-
parent.parentPath != null &&
|
|
87
|
-
parent.parentPath.isFunction()
|
|
88
|
-
) {
|
|
89
|
-
return false
|
|
90
|
-
}
|
|
91
|
-
return !isInStrictContext(parent)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// A later write to `const` must keep throwing after transformation.
|
|
95
|
-
export function hasConstantViolation(path: NodePath<t.VariableDeclaration>): boolean {
|
|
96
|
-
for (const name of Object.keys(path.getBindingIdentifiers())) {
|
|
97
|
-
const binding = path.scope.getBinding(name)
|
|
98
|
-
if (binding && binding.constantViolations.length > 0) {
|
|
99
|
-
return true
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
return false
|
|
103
|
-
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { NodePath } from '@babel/traverse'
|
|
2
|
+
import type * as t from '@babel/types'
|
|
3
|
+
import type { File } from '@babel/types'
|
|
4
|
+
|
|
5
|
+
import { traverseForChanges } from './change-tracking'
|
|
6
|
+
import type { ChangeState } from './change-tracking'
|
|
7
|
+
|
|
8
|
+
export type StatementListContainer = t.Program | t.BlockStatement | t.StaticBlock | t.SwitchCase
|
|
9
|
+
|
|
10
|
+
type Rewrite = (
|
|
11
|
+
statements: NodePath<t.Statement>[],
|
|
12
|
+
container: NodePath<StatementListContainer>,
|
|
13
|
+
) => t.Statement[] | null
|
|
14
|
+
|
|
15
|
+
/** Applies a rewrite to every statement-list container in the AST. */
|
|
16
|
+
export function rewriteStatementLists(ast: File, rewrite: Rewrite): boolean {
|
|
17
|
+
return traverseForChanges(ast, {
|
|
18
|
+
Program(path, state) {
|
|
19
|
+
apply(path, 'body', rewrite, state)
|
|
20
|
+
},
|
|
21
|
+
BlockStatement(path, state) {
|
|
22
|
+
apply(path, 'body', rewrite, state)
|
|
23
|
+
},
|
|
24
|
+
StaticBlock(path, state) {
|
|
25
|
+
apply(path, 'body', rewrite, state)
|
|
26
|
+
},
|
|
27
|
+
SwitchCase(path, state) {
|
|
28
|
+
apply(path, 'consequent', rewrite, state)
|
|
29
|
+
},
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function apply(
|
|
34
|
+
container: NodePath<StatementListContainer>,
|
|
35
|
+
key: 'body' | 'consequent',
|
|
36
|
+
rewrite: Rewrite,
|
|
37
|
+
state: ChangeState,
|
|
38
|
+
): void {
|
|
39
|
+
const statements = container.get(key) as NodePath<t.Statement>[]
|
|
40
|
+
const replacement = rewrite(statements, container)
|
|
41
|
+
if (replacement) {
|
|
42
|
+
container.set(key, replacement)
|
|
43
|
+
state.changed = true
|
|
44
|
+
}
|
|
45
|
+
}
|