webfuscator 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DISCLOSURE +9 -0
- package/LICENSE +21 -0
- package/README.md +83 -0
- package/dist/analysis/constant.d.ts +11 -0
- package/dist/analysis/constant.d.ts.map +1 -0
- package/dist/analysis/constant.js +261 -0
- package/dist/analysis/constant.js.map +1 -0
- package/dist/analysis/document-order.d.ts +5 -0
- package/dist/analysis/document-order.d.ts.map +1 -0
- package/dist/analysis/document-order.js +124 -0
- package/dist/analysis/document-order.js.map +1 -0
- package/dist/analysis/inlineability.d.ts +13 -0
- package/dist/analysis/inlineability.d.ts.map +1 -0
- package/dist/analysis/inlineability.js +270 -0
- package/dist/analysis/inlineability.js.map +1 -0
- package/dist/analysis/purity.d.ts +5 -0
- package/dist/analysis/purity.d.ts.map +1 -0
- package/dist/analysis/purity.js +281 -0
- package/dist/analysis/purity.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/obfuscator.d.ts +4 -0
- package/dist/obfuscator.d.ts.map +1 -0
- package/dist/obfuscator.js +222 -0
- package/dist/obfuscator.js.map +1 -0
- package/dist/options.d.ts +73 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +2 -0
- package/dist/options.js.map +1 -0
- package/dist/preparation/dot-to-bracket.d.ts +16 -0
- package/dist/preparation/dot-to-bracket.d.ts.map +1 -0
- package/dist/preparation/dot-to-bracket.js +36 -0
- package/dist/preparation/dot-to-bracket.js.map +1 -0
- package/dist/preparation/expand-destructuring.d.ts +26 -0
- package/dist/preparation/expand-destructuring.d.ts.map +1 -0
- package/dist/preparation/expand-destructuring.js +154 -0
- package/dist/preparation/expand-destructuring.js.map +1 -0
- package/dist/preparation/expand-template-literals.d.ts +23 -0
- package/dist/preparation/expand-template-literals.d.ts.map +1 -0
- package/dist/preparation/expand-template-literals.js +78 -0
- package/dist/preparation/expand-template-literals.js.map +1 -0
- package/dist/preparation/rewrite-class-fields.d.ts +43 -0
- package/dist/preparation/rewrite-class-fields.d.ts.map +1 -0
- package/dist/preparation/rewrite-class-fields.js +270 -0
- package/dist/preparation/rewrite-class-fields.js.map +1 -0
- package/dist/preparation/split-sequence-expressions.d.ts +15 -0
- package/dist/preparation/split-sequence-expressions.d.ts.map +1 -0
- package/dist/preparation/split-sequence-expressions.js +36 -0
- package/dist/preparation/split-sequence-expressions.js.map +1 -0
- package/dist/preparation/split-variable-declarations.d.ts +20 -0
- package/dist/preparation/split-variable-declarations.d.ts.map +1 -0
- package/dist/preparation/split-variable-declarations.js +36 -0
- package/dist/preparation/split-variable-declarations.js.map +1 -0
- package/dist/preparation/stringify-object-keys.d.ts +21 -0
- package/dist/preparation/stringify-object-keys.d.ts.map +1 -0
- package/dist/preparation/stringify-object-keys.js +96 -0
- package/dist/preparation/stringify-object-keys.js.map +1 -0
- package/dist/preparation/wrap-single-statements.d.ts +24 -0
- package/dist/preparation/wrap-single-statements.d.ts.map +1 -0
- package/dist/preparation/wrap-single-statements.js +53 -0
- package/dist/preparation/wrap-single-statements.js.map +1 -0
- package/dist/transforms/arguments-to-parameters.d.ts +20 -0
- package/dist/transforms/arguments-to-parameters.d.ts.map +1 -0
- package/dist/transforms/arguments-to-parameters.js +162 -0
- package/dist/transforms/arguments-to-parameters.js.map +1 -0
- package/dist/transforms/arrow-to-function.d.ts +46 -0
- package/dist/transforms/arrow-to-function.d.ts.map +1 -0
- package/dist/transforms/arrow-to-function.js +203 -0
- package/dist/transforms/arrow-to-function.js.map +1 -0
- package/dist/transforms/collapse-single-use-temps.d.ts +16 -0
- package/dist/transforms/collapse-single-use-temps.d.ts.map +1 -0
- package/dist/transforms/collapse-single-use-temps.js +805 -0
- package/dist/transforms/collapse-single-use-temps.js.map +1 -0
- package/dist/transforms/const-let-to-var.d.ts +21 -0
- package/dist/transforms/const-let-to-var.d.ts.map +1 -0
- package/dist/transforms/const-let-to-var.js +217 -0
- package/dist/transforms/const-let-to-var.js.map +1 -0
- package/dist/transforms/const-to-let.d.ts +17 -0
- package/dist/transforms/const-to-let.d.ts.map +1 -0
- package/dist/transforms/const-to-let.js +33 -0
- package/dist/transforms/const-to-let.js.map +1 -0
- package/dist/transforms/do-while-to-while.d.ts +26 -0
- package/dist/transforms/do-while-to-while.d.ts.map +1 -0
- package/dist/transforms/do-while-to-while.js +54 -0
- package/dist/transforms/do-while-to-while.js.map +1 -0
- package/dist/transforms/drop-console.d.ts +27 -0
- package/dist/transforms/drop-console.d.ts.map +1 -0
- package/dist/transforms/drop-console.js +107 -0
- package/dist/transforms/drop-console.js.map +1 -0
- package/dist/transforms/drop-debugger.d.ts +18 -0
- package/dist/transforms/drop-debugger.d.ts.map +1 -0
- package/dist/transforms/drop-debugger.js +34 -0
- package/dist/transforms/drop-debugger.js.map +1 -0
- package/dist/transforms/expand-binary-assignment.d.ts +26 -0
- package/dist/transforms/expand-binary-assignment.d.ts.map +1 -0
- package/dist/transforms/expand-binary-assignment.js +78 -0
- package/dist/transforms/expand-binary-assignment.js.map +1 -0
- package/dist/transforms/expand-logical-assignment.d.ts +22 -0
- package/dist/transforms/expand-logical-assignment.d.ts.map +1 -0
- package/dist/transforms/expand-logical-assignment.js +73 -0
- package/dist/transforms/expand-logical-assignment.js.map +1 -0
- package/dist/transforms/extract-object-properties.d.ts +21 -0
- package/dist/transforms/extract-object-properties.d.ts.map +1 -0
- package/dist/transforms/extract-object-properties.js +340 -0
- package/dist/transforms/extract-object-properties.js.map +1 -0
- package/dist/transforms/fold-builtin-methods.d.ts +23 -0
- package/dist/transforms/fold-builtin-methods.d.ts.map +1 -0
- package/dist/transforms/fold-builtin-methods.js +839 -0
- package/dist/transforms/fold-builtin-methods.js.map +1 -0
- package/dist/transforms/fold-constants.d.ts +16 -0
- package/dist/transforms/fold-constants.d.ts.map +1 -0
- package/dist/transforms/fold-constants.js +421 -0
- package/dist/transforms/fold-constants.js.map +1 -0
- package/dist/transforms/for-to-while.d.ts +29 -0
- package/dist/transforms/for-to-while.d.ts.map +1 -0
- package/dist/transforms/for-to-while.js +91 -0
- package/dist/transforms/for-to-while.js.map +1 -0
- package/dist/transforms/function-declaration-to-expression.d.ts +28 -0
- package/dist/transforms/function-declaration-to-expression.d.ts.map +1 -0
- package/dist/transforms/function-declaration-to-expression.js +95 -0
- package/dist/transforms/function-declaration-to-expression.js.map +1 -0
- package/dist/transforms/inline-functions.d.ts +16 -0
- package/dist/transforms/inline-functions.d.ts.map +1 -0
- package/dist/transforms/inline-functions.js +896 -0
- package/dist/transforms/inline-functions.js.map +1 -0
- package/dist/transforms/logical-to-ternary.d.ts +20 -0
- package/dist/transforms/logical-to-ternary.d.ts.map +1 -0
- package/dist/transforms/logical-to-ternary.js +78 -0
- package/dist/transforms/logical-to-ternary.js.map +1 -0
- package/dist/transforms/mangle-properties.d.ts +27 -0
- package/dist/transforms/mangle-properties.d.ts.map +1 -0
- package/dist/transforms/mangle-properties.js +703 -0
- package/dist/transforms/mangle-properties.js.map +1 -0
- package/dist/transforms/nullish-coalescing-to-ternary.d.ts +18 -0
- package/dist/transforms/nullish-coalescing-to-ternary.d.ts.map +1 -0
- package/dist/transforms/nullish-coalescing-to-ternary.js +61 -0
- package/dist/transforms/nullish-coalescing-to-ternary.js.map +1 -0
- package/dist/transforms/numbers-to-strings.d.ts +19 -0
- package/dist/transforms/numbers-to-strings.d.ts.map +1 -0
- package/dist/transforms/numbers-to-strings.js +51 -0
- package/dist/transforms/numbers-to-strings.js.map +1 -0
- package/dist/transforms/object-method-to-property.d.ts +34 -0
- package/dist/transforms/object-method-to-property.d.ts.map +1 -0
- package/dist/transforms/object-method-to-property.js +78 -0
- package/dist/transforms/object-method-to-property.js.map +1 -0
- package/dist/transforms/optional-chaining-to-ternary.d.ts +19 -0
- package/dist/transforms/optional-chaining-to-ternary.d.ts.map +1 -0
- package/dist/transforms/optional-chaining-to-ternary.js +206 -0
- package/dist/transforms/optional-chaining-to-ternary.js.map +1 -0
- package/dist/transforms/pack-declarations-into-parameters.d.ts +22 -0
- package/dist/transforms/pack-declarations-into-parameters.d.ts.map +1 -0
- package/dist/transforms/pack-declarations-into-parameters.js +208 -0
- package/dist/transforms/pack-declarations-into-parameters.js.map +1 -0
- package/dist/transforms/remove-anonymous-function-names.d.ts +17 -0
- package/dist/transforms/remove-anonymous-function-names.d.ts.map +1 -0
- package/dist/transforms/remove-anonymous-function-names.js +72 -0
- package/dist/transforms/remove-anonymous-function-names.js.map +1 -0
- package/dist/transforms/remove-unreachable-code.d.ts +21 -0
- package/dist/transforms/remove-unreachable-code.d.ts.map +1 -0
- package/dist/transforms/remove-unreachable-code.js +103 -0
- package/dist/transforms/remove-unreachable-code.js.map +1 -0
- package/dist/transforms/remove-unused-code.d.ts +18 -0
- package/dist/transforms/remove-unused-code.d.ts.map +1 -0
- package/dist/transforms/remove-unused-code.js +249 -0
- package/dist/transforms/remove-unused-code.js.map +1 -0
- package/dist/transforms/rename-identifiers.d.ts +24 -0
- package/dist/transforms/rename-identifiers.d.ts.map +1 -0
- package/dist/transforms/rename-identifiers.js +264 -0
- package/dist/transforms/rename-identifiers.js.map +1 -0
- package/dist/transforms/specials-to-strings.d.ts +30 -0
- package/dist/transforms/specials-to-strings.d.ts.map +1 -0
- package/dist/transforms/specials-to-strings.js +101 -0
- package/dist/transforms/specials-to-strings.js.map +1 -0
- package/dist/transforms/switch-to-if.d.ts +28 -0
- package/dist/transforms/switch-to-if.d.ts.map +1 -0
- package/dist/transforms/switch-to-if.js +279 -0
- package/dist/transforms/switch-to-if.js.map +1 -0
- package/dist/transforms/ternary-to-if.d.ts +20 -0
- package/dist/transforms/ternary-to-if.d.ts.map +1 -0
- package/dist/transforms/ternary-to-if.js +315 -0
- package/dist/transforms/ternary-to-if.js.map +1 -0
- package/dist/transforms/update-to-assignment.d.ts +26 -0
- package/dist/transforms/update-to-assignment.d.ts.map +1 -0
- package/dist/transforms/update-to-assignment.js +120 -0
- package/dist/transforms/update-to-assignment.js.map +1 -0
- package/dist/transforms/yodify-conditions.d.ts +22 -0
- package/dist/transforms/yodify-conditions.d.ts.map +1 -0
- package/dist/transforms/yodify-conditions.js +91 -0
- package/dist/transforms/yodify-conditions.js.map +1 -0
- package/dist/utils/ast.d.ts +10 -0
- package/dist/utils/ast.d.ts.map +1 -0
- package/dist/utils/ast.js +112 -0
- package/dist/utils/ast.js.map +1 -0
- package/dist/utils/change-tracking.d.ts +8 -0
- package/dist/utils/change-tracking.d.ts.map +1 -0
- package/dist/utils/change-tracking.js +8 -0
- package/dist/utils/change-tracking.js.map +1 -0
- package/dist/utils/dom-properties.d.ts +2 -0
- package/dist/utils/dom-properties.d.ts.map +1 -0
- package/dist/utils/dom-properties.js +9252 -0
- package/dist/utils/dom-properties.js.map +1 -0
- package/dist/utils/evaluation-order.d.ts +4 -0
- package/dist/utils/evaluation-order.d.ts.map +1 -0
- package/dist/utils/evaluation-order.js +152 -0
- package/dist/utils/evaluation-order.js.map +1 -0
- package/dist/utils/literal.d.ts +5 -0
- package/dist/utils/literal.d.ts.map +1 -0
- package/dist/utils/literal.js +90 -0
- package/dist/utils/literal.js.map +1 -0
- package/dist/utils/logical-lowering.d.ts +4 -0
- package/dist/utils/logical-lowering.d.ts.map +1 -0
- package/dist/utils/logical-lowering.js +104 -0
- package/dist/utils/logical-lowering.js.map +1 -0
- package/dist/utils/loop-lowering.d.ts +23 -0
- package/dist/utils/loop-lowering.d.ts.map +1 -0
- package/dist/utils/loop-lowering.js +88 -0
- package/dist/utils/loop-lowering.js.map +1 -0
- package/dist/utils/paths.d.ts +9 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +90 -0
- package/dist/utils/paths.js.map +1 -0
- package/dist/utils/random.d.ts +3 -0
- package/dist/utils/random.d.ts.map +1 -0
- package/dist/utils/random.js +22 -0
- package/dist/utils/random.js.map +1 -0
- package/dist/utils/string-generator.d.ts +37 -0
- package/dist/utils/string-generator.d.ts.map +1 -0
- package/dist/utils/string-generator.js +175 -0
- package/dist/utils/string-generator.js.map +1 -0
- package/package.json +76 -0
- package/src/analysis/constant.ts +310 -0
- package/src/analysis/document-order.ts +141 -0
- package/src/analysis/inlineability.ts +336 -0
- package/src/analysis/purity.ts +315 -0
- package/src/index.ts +2 -0
- package/src/obfuscator.ts +271 -0
- package/src/options.ts +114 -0
- package/src/preparation/dot-to-bracket.ts +41 -0
- package/src/preparation/expand-destructuring.ts +211 -0
- package/src/preparation/expand-template-literals.ts +92 -0
- package/src/preparation/rewrite-class-fields.ts +320 -0
- package/src/preparation/split-sequence-expressions.ts +40 -0
- package/src/preparation/split-variable-declarations.ts +39 -0
- package/src/preparation/stringify-object-keys.ts +107 -0
- package/src/preparation/wrap-single-statements.ts +63 -0
- package/src/transforms/arguments-to-parameters.ts +187 -0
- package/src/transforms/arrow-to-function.ts +225 -0
- package/src/transforms/collapse-single-use-temps.ts +957 -0
- package/src/transforms/const-let-to-var.ts +246 -0
- package/src/transforms/const-to-let.ts +38 -0
- package/src/transforms/do-while-to-while.ts +69 -0
- package/src/transforms/drop-console.ts +119 -0
- package/src/transforms/drop-debugger.ts +38 -0
- package/src/transforms/expand-binary-assignment.ts +94 -0
- package/src/transforms/expand-logical-assignment.ts +84 -0
- package/src/transforms/extract-object-properties.ts +404 -0
- package/src/transforms/fold-builtin-methods.ts +949 -0
- package/src/transforms/fold-constants.ts +462 -0
- package/src/transforms/for-to-while.ts +109 -0
- package/src/transforms/function-declaration-to-expression.ts +108 -0
- package/src/transforms/inline-functions.ts +1074 -0
- package/src/transforms/logical-to-ternary.ts +92 -0
- package/src/transforms/mangle-properties.ts +819 -0
- package/src/transforms/nullish-coalescing-to-ternary.ts +77 -0
- package/src/transforms/numbers-to-strings.ts +61 -0
- package/src/transforms/object-method-to-property.ts +86 -0
- package/src/transforms/optional-chaining-to-ternary.ts +225 -0
- package/src/transforms/pack-declarations-into-parameters.ts +233 -0
- package/src/transforms/remove-anonymous-function-names.ts +84 -0
- package/src/transforms/remove-unreachable-code.ts +115 -0
- package/src/transforms/remove-unused-code.ts +296 -0
- package/src/transforms/rename-identifiers.ts +289 -0
- package/src/transforms/specials-to-strings.ts +116 -0
- package/src/transforms/switch-to-if.ts +339 -0
- package/src/transforms/ternary-to-if.ts +387 -0
- package/src/transforms/update-to-assignment.ts +132 -0
- package/src/transforms/yodify-conditions.ts +103 -0
- package/src/utils/ast.ts +129 -0
- package/src/utils/change-tracking.ts +15 -0
- package/src/utils/dom-properties.ts +9252 -0
- package/src/utils/evaluation-order.ts +178 -0
- package/src/utils/literal.ts +96 -0
- package/src/utils/logical-lowering.ts +132 -0
- package/src/utils/loop-lowering.ts +109 -0
- package/src/utils/paths.ts +103 -0
- package/src/utils/random.ts +22 -0
- package/src/utils/string-generator.ts +206 -0
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
import type { NodePath, Visitor } from '@babel/traverse'
|
|
2
|
+
import * as t from '@babel/types'
|
|
3
|
+
import type { File } from '@babel/types'
|
|
4
|
+
|
|
5
|
+
import { evaluateConstant } from 'src/analysis/constant'
|
|
6
|
+
import { isPure } from 'src/analysis/purity'
|
|
7
|
+
import { traverseForChanges } from 'src/utils/change-tracking'
|
|
8
|
+
import type { ChangeState } from 'src/utils/change-tracking'
|
|
9
|
+
import { valueToLiteral } from 'src/utils/literal'
|
|
10
|
+
import { isCalleeOrTagOf } from 'src/utils/paths'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Folds pure constant expressions to literals via `evaluateConstant`.
|
|
14
|
+
* Logical and conditional nodes can fold directly to a chosen nonliteral branch.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* // ◀️ before
|
|
18
|
+
* var v = 1 + 2 * 3;
|
|
19
|
+
* if (true && cond) f();
|
|
20
|
+
*
|
|
21
|
+
* // ▶️ after
|
|
22
|
+
* var v = 7;
|
|
23
|
+
* if (cond) f();
|
|
24
|
+
*/
|
|
25
|
+
export function foldConstants(ast: File): boolean {
|
|
26
|
+
return traverseForChanges(ast, visitor)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const visitor: Visitor<ChangeState> = {
|
|
30
|
+
UnaryExpression: {
|
|
31
|
+
exit(path, state) {
|
|
32
|
+
if (tryFoldExprNode(path)) {
|
|
33
|
+
state.changed = true
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
BinaryExpression: {
|
|
38
|
+
exit(path, state) {
|
|
39
|
+
if (tryFoldExprNode(path)) {
|
|
40
|
+
state.changed = true
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
LogicalExpression: {
|
|
45
|
+
exit(path, state) {
|
|
46
|
+
if (tryFoldExprNode(path)) {
|
|
47
|
+
state.changed = true
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
ConditionalExpression: {
|
|
52
|
+
exit(path, state) {
|
|
53
|
+
if (tryFoldExprNode(path)) {
|
|
54
|
+
state.changed = true
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function tryFoldExprNode(path: NodePath): boolean {
|
|
61
|
+
if (path.isUnaryExpression()) {
|
|
62
|
+
return tryFoldToLiteral(path)
|
|
63
|
+
}
|
|
64
|
+
if (path.isBinaryExpression()) {
|
|
65
|
+
if (tryFoldToLiteral(path)) {
|
|
66
|
+
return true
|
|
67
|
+
}
|
|
68
|
+
return tryReassociate(path)
|
|
69
|
+
}
|
|
70
|
+
if (path.isLogicalExpression()) {
|
|
71
|
+
return tryFoldLogical(path)
|
|
72
|
+
}
|
|
73
|
+
if (path.isConditionalExpression()) {
|
|
74
|
+
return tryFoldConditional(path)
|
|
75
|
+
}
|
|
76
|
+
return false
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function tryFoldLogical(path: NodePath<t.LogicalExpression>): boolean {
|
|
80
|
+
// Folding a callee to a member reference can manufacture a `this` binding.
|
|
81
|
+
if (isCalleeOrTagOf(path) || isReferenceSensitiveUnaryOperand(path)) {
|
|
82
|
+
return false
|
|
83
|
+
}
|
|
84
|
+
const left = evaluateConstant(path.node.left, path.scope, path)
|
|
85
|
+
if (!left.known) {
|
|
86
|
+
return false
|
|
87
|
+
}
|
|
88
|
+
const op = path.node.operator
|
|
89
|
+
let chosen: t.Expression
|
|
90
|
+
if (op === '&&') {
|
|
91
|
+
chosen = left.value ? path.node.right : path.node.left
|
|
92
|
+
} else if (op === '||') {
|
|
93
|
+
chosen = left.value ? path.node.left : path.node.right
|
|
94
|
+
} else if (op === '??') {
|
|
95
|
+
chosen = left.value === null || left.value === undefined ? path.node.right : path.node.left
|
|
96
|
+
} else {
|
|
97
|
+
return false
|
|
98
|
+
}
|
|
99
|
+
path.replaceWith(chosen)
|
|
100
|
+
return true
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function tryFoldConditional(path: NodePath<t.ConditionalExpression>): boolean {
|
|
104
|
+
if (isCalleeOrTagOf(path) || isReferenceSensitiveUnaryOperand(path)) {
|
|
105
|
+
return false
|
|
106
|
+
}
|
|
107
|
+
const test = evaluateConstant(path.node.test, path.scope, path)
|
|
108
|
+
if (!test.known) {
|
|
109
|
+
return false
|
|
110
|
+
}
|
|
111
|
+
path.replaceWith(test.value ? path.node.consequent : path.node.alternate)
|
|
112
|
+
return true
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// `delete` and `typeof` distinguish references from values. Folding their operand
|
|
116
|
+
// can change deletion, strict syntax, or an unresolved-name exception.
|
|
117
|
+
function isReferenceSensitiveUnaryOperand(path: NodePath): boolean {
|
|
118
|
+
const parent = path.parentPath
|
|
119
|
+
return (
|
|
120
|
+
parent != null &&
|
|
121
|
+
parent.isUnaryExpression() &&
|
|
122
|
+
(parent.node.operator === 'delete' || parent.node.operator === 'typeof') &&
|
|
123
|
+
parent.node.argument === path.node
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function tryFoldToLiteral(
|
|
128
|
+
path: NodePath<t.UnaryExpression> | NodePath<t.BinaryExpression>,
|
|
129
|
+
): boolean {
|
|
130
|
+
const result = evaluateConstant(path.node, path.scope, path)
|
|
131
|
+
if (!result.known) {
|
|
132
|
+
return false
|
|
133
|
+
}
|
|
134
|
+
const lit = valueToLiteral(result.value)
|
|
135
|
+
if (!lit) {
|
|
136
|
+
return false
|
|
137
|
+
}
|
|
138
|
+
// Babel revisits replacements, so an identical replacement would loop.
|
|
139
|
+
if (structurallyEqual(path.node, lit)) {
|
|
140
|
+
return false
|
|
141
|
+
}
|
|
142
|
+
path.replaceWith(lit)
|
|
143
|
+
return true
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
type SafeConstant = { kind: 'number'; value: number } | { kind: 'bigint'; value: bigint }
|
|
147
|
+
|
|
148
|
+
type CommutativeOp = '*' | '&' | '|' | '^'
|
|
149
|
+
|
|
150
|
+
function tryReassociate(path: NodePath<t.BinaryExpression>): boolean {
|
|
151
|
+
const op = path.node.operator
|
|
152
|
+
if (op === '*' || op === '&' || op === '|' || op === '^') {
|
|
153
|
+
return tryReassociateCommutative(path, op)
|
|
154
|
+
}
|
|
155
|
+
if (op === '-') {
|
|
156
|
+
return tryReassociateSubtract(path)
|
|
157
|
+
}
|
|
158
|
+
return false
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function tryReassociateCommutative(path: NodePath<t.BinaryExpression>, op: CommutativeOp): boolean {
|
|
162
|
+
const parent = path.parentPath
|
|
163
|
+
if (parent && parent.isBinaryExpression() && parent.node.operator === op) {
|
|
164
|
+
return false
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const leaves: t.Expression[] = []
|
|
168
|
+
collectCommutativeChain(path.node, op, leaves)
|
|
169
|
+
|
|
170
|
+
const constants: SafeConstant[] = []
|
|
171
|
+
const others: t.Expression[] = []
|
|
172
|
+
for (const leaf of leaves) {
|
|
173
|
+
const c = asSafeConstant(leaf)
|
|
174
|
+
if (c === null) {
|
|
175
|
+
others.push(leaf)
|
|
176
|
+
} else {
|
|
177
|
+
constants.push(c)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (constants.length < 2) {
|
|
181
|
+
return false
|
|
182
|
+
}
|
|
183
|
+
// IEEE-754 multiplication is not associative. BigInt and Int32 bitwise chains
|
|
184
|
+
// are exact, while a wholly constant chain can be evaluated as written.
|
|
185
|
+
if (op === '*' && others.length > 0 && constants.some((c) => c.kind === 'number')) {
|
|
186
|
+
return false
|
|
187
|
+
}
|
|
188
|
+
// Grouping constants can move one operand's coercion past another operand's
|
|
189
|
+
// evaluation. Multiple non-constant operands must therefore be pure.
|
|
190
|
+
if (others.length > 1 && !others.every((leaf) => isPure(leaf, path.scope, path))) {
|
|
191
|
+
return false
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const combined = combineConstants(constants, op)
|
|
195
|
+
if (combined === null) {
|
|
196
|
+
return false
|
|
197
|
+
}
|
|
198
|
+
const combinedLit = constantToLiteral(combined)
|
|
199
|
+
|
|
200
|
+
let replacement: t.Expression
|
|
201
|
+
if (others.length === 0) {
|
|
202
|
+
replacement = combinedLit
|
|
203
|
+
} else {
|
|
204
|
+
replacement = others[0]!
|
|
205
|
+
for (let i = 1; i < others.length; i++) {
|
|
206
|
+
replacement = t.binaryExpression(op, replacement, others[i]!)
|
|
207
|
+
}
|
|
208
|
+
replacement = t.binaryExpression(op, replacement, combinedLit)
|
|
209
|
+
}
|
|
210
|
+
path.replaceWith(replacement)
|
|
211
|
+
return true
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function collectCommutativeChain(
|
|
215
|
+
node: t.BinaryExpression,
|
|
216
|
+
op: CommutativeOp,
|
|
217
|
+
out: t.Expression[],
|
|
218
|
+
): void {
|
|
219
|
+
if (t.isBinaryExpression(node.left) && node.left.operator === op) {
|
|
220
|
+
collectCommutativeChain(node.left, op, out)
|
|
221
|
+
} else if (t.isExpression(node.left)) {
|
|
222
|
+
out.push(node.left)
|
|
223
|
+
}
|
|
224
|
+
if (t.isBinaryExpression(node.right) && node.right.operator === op) {
|
|
225
|
+
collectCommutativeChain(node.right, op, out)
|
|
226
|
+
} else {
|
|
227
|
+
out.push(node.right)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function tryReassociateSubtract(path: NodePath<t.BinaryExpression>): boolean {
|
|
232
|
+
const parent = path.parentPath
|
|
233
|
+
if (
|
|
234
|
+
parent &&
|
|
235
|
+
parent.isBinaryExpression() &&
|
|
236
|
+
parent.node.operator === '-' &&
|
|
237
|
+
parent.node.left === path.node
|
|
238
|
+
) {
|
|
239
|
+
return false
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const subtractees: t.Expression[] = []
|
|
243
|
+
let node: t.BinaryExpression = path.node
|
|
244
|
+
while (true) {
|
|
245
|
+
subtractees.unshift(node.right)
|
|
246
|
+
const { left } = node
|
|
247
|
+
if (t.isBinaryExpression(left) && left.operator === '-') {
|
|
248
|
+
node = left
|
|
249
|
+
continue
|
|
250
|
+
}
|
|
251
|
+
break
|
|
252
|
+
}
|
|
253
|
+
if (!t.isExpression(node.left)) {
|
|
254
|
+
return false
|
|
255
|
+
}
|
|
256
|
+
const base = node.left
|
|
257
|
+
|
|
258
|
+
const constSubs: SafeConstant[] = []
|
|
259
|
+
const otherSubs: t.Expression[] = []
|
|
260
|
+
for (const subtractee of subtractees) {
|
|
261
|
+
const c = asSafeConstant(subtractee)
|
|
262
|
+
if (c === null) {
|
|
263
|
+
otherSubs.push(subtractee)
|
|
264
|
+
} else {
|
|
265
|
+
constSubs.push(c)
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const baseConst = asSafeConstant(base)
|
|
270
|
+
// IEEE-754 subtraction is not associative. BigInt subtraction is exact.
|
|
271
|
+
if (constSubs.some((c) => c.kind === 'number') || baseConst?.kind === 'number') {
|
|
272
|
+
return false
|
|
273
|
+
}
|
|
274
|
+
if (baseConst !== null) {
|
|
275
|
+
if (constSubs.length === 0) {
|
|
276
|
+
return false
|
|
277
|
+
}
|
|
278
|
+
const combined = combineConstants([baseConst, ...constSubs], '-')
|
|
279
|
+
if (combined === null) {
|
|
280
|
+
return false
|
|
281
|
+
}
|
|
282
|
+
const combinedLit = constantToLiteral(combined)
|
|
283
|
+
let replacement: t.Expression = combinedLit
|
|
284
|
+
for (const other of otherSubs) {
|
|
285
|
+
replacement = t.binaryExpression('-', replacement, other)
|
|
286
|
+
}
|
|
287
|
+
path.replaceWith(replacement)
|
|
288
|
+
return true
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (constSubs.length < 2) {
|
|
292
|
+
return false
|
|
293
|
+
}
|
|
294
|
+
// Reassociation can move `base` coercion past a subtractee's evaluation.
|
|
295
|
+
// Non-constant subtractees must be pure.
|
|
296
|
+
if (!otherSubs.every((leaf) => isPure(leaf, path.scope, path))) {
|
|
297
|
+
return false
|
|
298
|
+
}
|
|
299
|
+
const sum = combineConstants(constSubs, '+')
|
|
300
|
+
if (sum === null) {
|
|
301
|
+
return false
|
|
302
|
+
}
|
|
303
|
+
const sumLit = constantToLiteral(sum)
|
|
304
|
+
let replacement: t.Expression = base
|
|
305
|
+
for (const other of otherSubs) {
|
|
306
|
+
replacement = t.binaryExpression('-', replacement, other)
|
|
307
|
+
}
|
|
308
|
+
replacement = t.binaryExpression('-', replacement, sumLit)
|
|
309
|
+
path.replaceWith(replacement)
|
|
310
|
+
return true
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function asSafeConstant(node: t.Node): SafeConstant | null {
|
|
314
|
+
if (t.isNumericLiteral(node)) {
|
|
315
|
+
return Number.isInteger(node.value) && Math.abs(node.value) <= Number.MAX_SAFE_INTEGER
|
|
316
|
+
? { kind: 'number', value: node.value }
|
|
317
|
+
: null
|
|
318
|
+
}
|
|
319
|
+
if (t.isUnaryExpression(node) && node.operator === '-' && t.isNumericLiteral(node.argument)) {
|
|
320
|
+
const negated = -node.argument.value
|
|
321
|
+
return Number.isInteger(negated) && Math.abs(negated) <= Number.MAX_SAFE_INTEGER
|
|
322
|
+
? { kind: 'number', value: negated }
|
|
323
|
+
: null
|
|
324
|
+
}
|
|
325
|
+
if (t.isBigIntLiteral(node)) {
|
|
326
|
+
try {
|
|
327
|
+
return { kind: 'bigint', value: BigInt(node.value) }
|
|
328
|
+
} catch {
|
|
329
|
+
return null
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return null
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function constantToLiteral(c: SafeConstant): t.Expression {
|
|
336
|
+
if (c.kind === 'bigint') {
|
|
337
|
+
return c.value < 0n
|
|
338
|
+
? t.unaryExpression('-', t.bigIntLiteral(-c.value))
|
|
339
|
+
: t.bigIntLiteral(c.value)
|
|
340
|
+
}
|
|
341
|
+
// `t.numericLiteral(-0)` is rejected by Babel's validator; emit `-0`.
|
|
342
|
+
if (Object.is(c.value, -0)) {
|
|
343
|
+
return t.unaryExpression('-', t.numericLiteral(0))
|
|
344
|
+
}
|
|
345
|
+
if (c.value < 0) {
|
|
346
|
+
return t.unaryExpression('-', t.numericLiteral(-c.value))
|
|
347
|
+
}
|
|
348
|
+
return t.numericLiteral(c.value)
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function isInt32(value: number): boolean {
|
|
352
|
+
return (value | 0) === value
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function combineConstants(
|
|
356
|
+
consts: SafeConstant[],
|
|
357
|
+
op: CommutativeOp | '-' | '+',
|
|
358
|
+
): SafeConstant | null {
|
|
359
|
+
if (consts.length === 0) {
|
|
360
|
+
return null
|
|
361
|
+
}
|
|
362
|
+
const { kind } = consts[0]!
|
|
363
|
+
for (const c of consts) {
|
|
364
|
+
if (c.kind !== kind) {
|
|
365
|
+
return null
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (kind === 'bigint') {
|
|
370
|
+
let acc = consts[0]!.value as bigint
|
|
371
|
+
for (let i = 1; i < consts.length; i++) {
|
|
372
|
+
const c = consts[i]!.value as bigint
|
|
373
|
+
acc = applyBigint(acc, c, op)
|
|
374
|
+
}
|
|
375
|
+
return { kind: 'bigint', value: acc }
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (op === '&' || op === '|' || op === '^') {
|
|
379
|
+
for (const c of consts) {
|
|
380
|
+
if (!isInt32(c.value as number)) {
|
|
381
|
+
return null
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
let acc = consts[0]!.value as number
|
|
386
|
+
for (let i = 1; i < consts.length; i++) {
|
|
387
|
+
const c = consts[i]!.value as number
|
|
388
|
+
acc = applyNumber(acc, c, op)
|
|
389
|
+
}
|
|
390
|
+
if (!Number.isFinite(acc)) {
|
|
391
|
+
return null
|
|
392
|
+
}
|
|
393
|
+
if (
|
|
394
|
+
(op === '*' || op === '+' || op === '-') &&
|
|
395
|
+
(!Number.isInteger(acc) || Math.abs(acc) > Number.MAX_SAFE_INTEGER)
|
|
396
|
+
) {
|
|
397
|
+
return null
|
|
398
|
+
}
|
|
399
|
+
return { kind: 'number', value: acc }
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function applyBigint(a: bigint, b: bigint, op: CommutativeOp | '-' | '+'): bigint {
|
|
403
|
+
switch (op) {
|
|
404
|
+
case '*':
|
|
405
|
+
return a * b
|
|
406
|
+
case '&':
|
|
407
|
+
return a & b
|
|
408
|
+
case '|':
|
|
409
|
+
return a | b
|
|
410
|
+
case '^':
|
|
411
|
+
return a ^ b
|
|
412
|
+
case '-':
|
|
413
|
+
return a - b
|
|
414
|
+
case '+':
|
|
415
|
+
return a + b
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function applyNumber(a: number, b: number, op: CommutativeOp | '-' | '+'): number {
|
|
420
|
+
switch (op) {
|
|
421
|
+
case '*':
|
|
422
|
+
return a * b
|
|
423
|
+
case '&':
|
|
424
|
+
return a & b
|
|
425
|
+
case '|':
|
|
426
|
+
return a | b
|
|
427
|
+
case '^':
|
|
428
|
+
return a ^ b
|
|
429
|
+
case '-':
|
|
430
|
+
return a - b
|
|
431
|
+
case '+':
|
|
432
|
+
return a + b
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function structurallyEqual(a: t.Node, b: t.Node): boolean {
|
|
437
|
+
if (a.type !== b.type) {
|
|
438
|
+
return false
|
|
439
|
+
}
|
|
440
|
+
if (t.isNumericLiteral(a) && t.isNumericLiteral(b)) {
|
|
441
|
+
return a.value === b.value
|
|
442
|
+
}
|
|
443
|
+
if (t.isStringLiteral(a) && t.isStringLiteral(b)) {
|
|
444
|
+
return a.value === b.value
|
|
445
|
+
}
|
|
446
|
+
if (t.isBooleanLiteral(a) && t.isBooleanLiteral(b)) {
|
|
447
|
+
return a.value === b.value
|
|
448
|
+
}
|
|
449
|
+
if (t.isNullLiteral(a)) {
|
|
450
|
+
return true
|
|
451
|
+
}
|
|
452
|
+
if (t.isBigIntLiteral(a) && t.isBigIntLiteral(b)) {
|
|
453
|
+
return a.value === b.value
|
|
454
|
+
}
|
|
455
|
+
if (t.isIdentifier(a) && t.isIdentifier(b)) {
|
|
456
|
+
return a.name === b.name
|
|
457
|
+
}
|
|
458
|
+
if (t.isUnaryExpression(a) && t.isUnaryExpression(b)) {
|
|
459
|
+
return a.operator === b.operator && structurallyEqual(a.argument, b.argument)
|
|
460
|
+
}
|
|
461
|
+
return false
|
|
462
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { NodePath, Visitor } from '@babel/traverse'
|
|
2
|
+
import * as t from '@babel/types'
|
|
3
|
+
import type { File } from '@babel/types'
|
|
4
|
+
|
|
5
|
+
import { traverseForChanges } from 'src/utils/change-tracking'
|
|
6
|
+
import type { ChangeState } from 'src/utils/change-tracking'
|
|
7
|
+
import { collectLoopLabels, generateLoopLabel, redirectContinues } from 'src/utils/loop-lowering'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Rewrites classic `for` loops as an initializer followed by `while`. Continues
|
|
11
|
+
* become breaks from an inner block so the update still runs. Iterator loops
|
|
12
|
+
* stay unchanged.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* // ◀️ before
|
|
16
|
+
* for (var i = 0; i < 3; i++) {
|
|
17
|
+
* if (skip(i)) {
|
|
18
|
+
* continue;
|
|
19
|
+
* }
|
|
20
|
+
* log(i);
|
|
21
|
+
* }
|
|
22
|
+
*
|
|
23
|
+
* // ▶️ after
|
|
24
|
+
* var i = 0;
|
|
25
|
+
* while (i < 3) {
|
|
26
|
+
* _forIter: {
|
|
27
|
+
* if (skip(i)) {
|
|
28
|
+
* break _forIter;
|
|
29
|
+
* }
|
|
30
|
+
* log(i);
|
|
31
|
+
* }
|
|
32
|
+
* i++;
|
|
33
|
+
* }
|
|
34
|
+
*/
|
|
35
|
+
export function forToWhile(ast: File): boolean {
|
|
36
|
+
return traverseForChanges(ast, visitor)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const visitor: Visitor<ChangeState> = {
|
|
40
|
+
ForStatement(path, state) {
|
|
41
|
+
if (transformFor(path)) {
|
|
42
|
+
state.changed = true
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function transformFor(path: NodePath<t.ForStatement>): boolean {
|
|
48
|
+
const { node } = path
|
|
49
|
+
|
|
50
|
+
// One hoisted binding cannot preserve lexical per-iteration captures.
|
|
51
|
+
if (t.isVariableDeclaration(node.init) && node.init.kind !== 'var') {
|
|
52
|
+
return false
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const { outermost, ourLabels } = collectLoopLabels(path)
|
|
56
|
+
const innerLabel = generateLoopLabel(path, 'forIteration')
|
|
57
|
+
|
|
58
|
+
const initStmt = buildInitStatement(node.init)
|
|
59
|
+
|
|
60
|
+
const blockBody: t.BlockStatement = t.isBlockStatement(node.body)
|
|
61
|
+
? node.body
|
|
62
|
+
: t.blockStatement([node.body])
|
|
63
|
+
|
|
64
|
+
const labeledBlock = t.labeledStatement(t.identifier(innerLabel), blockBody)
|
|
65
|
+
|
|
66
|
+
const whileBody: t.Statement[] = [labeledBlock]
|
|
67
|
+
if (node.update) {
|
|
68
|
+
whileBody.push(t.expressionStatement(node.update))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const test = node.test ?? t.booleanLiteral(true)
|
|
72
|
+
const whileStmt = t.whileStatement(test, t.blockStatement(whileBody))
|
|
73
|
+
|
|
74
|
+
const [newPath] = path.replaceWith(whileStmt)
|
|
75
|
+
const rewriteCount = redirectContinues(newPath, innerLabel, ourLabels)
|
|
76
|
+
|
|
77
|
+
// Keep the block so body bindings cannot shadow names in the update.
|
|
78
|
+
if (rewriteCount === 0) {
|
|
79
|
+
const whileBlock = newPath.node.body as t.BlockStatement
|
|
80
|
+
whileBlock.body[0] = blockBody
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
hoistInit(outermost, initStmt)
|
|
84
|
+
return true
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Babel repoints a non-list path after `insertBefore`, which can drop the init
|
|
88
|
+
// during the later loop replacement. Replace that slot with one block instead.
|
|
89
|
+
function hoistInit(outermost: NodePath<t.Statement>, initStmt: t.Statement | null): void {
|
|
90
|
+
if (!initStmt) {
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
if (outermost.inList) {
|
|
94
|
+
outermost.insertBefore(initStmt)
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
const loopStatement = outermost.node
|
|
98
|
+
outermost.replaceWith(t.blockStatement([initStmt, loopStatement]))
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function buildInitStatement(init: t.ForStatement['init']): t.Statement | null {
|
|
102
|
+
if (!init) {
|
|
103
|
+
return null
|
|
104
|
+
}
|
|
105
|
+
if (t.isVariableDeclaration(init)) {
|
|
106
|
+
return init
|
|
107
|
+
}
|
|
108
|
+
return t.expressionStatement(init)
|
|
109
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { NodePath, Visitor } from '@babel/traverse'
|
|
2
|
+
import * as t from '@babel/types'
|
|
3
|
+
import type { File } from '@babel/types'
|
|
4
|
+
|
|
5
|
+
import { traverseForChanges } from 'src/utils/change-tracking'
|
|
6
|
+
import type { ChangeState } from 'src/utils/change-tracking'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Rewrites safe function declarations as `var` initializers. The function value
|
|
10
|
+
* is no longer hoisted, so nothing may observe the binding before assignment.
|
|
11
|
+
*
|
|
12
|
+
* Preceding statements must be inert. Name inference and recursive references
|
|
13
|
+
* still use the `var` binding.
|
|
14
|
+
*
|
|
15
|
+
* Block-level declarations and module top-level declarations stay unchanged due
|
|
16
|
+
* to Annex B aliases and live exports.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* // ◀️ before
|
|
20
|
+
* const TAX = 0.2;
|
|
21
|
+
* function withTax(n) {
|
|
22
|
+
* return n + n * TAX;
|
|
23
|
+
* }
|
|
24
|
+
* total = withTax(100);
|
|
25
|
+
*
|
|
26
|
+
* // ▶️ after
|
|
27
|
+
* const TAX = 0.2;
|
|
28
|
+
* var withTax = function (n) {
|
|
29
|
+
* return n + n * TAX;
|
|
30
|
+
* };
|
|
31
|
+
* total = withTax(100);
|
|
32
|
+
*/
|
|
33
|
+
export function functionDeclarationToExpression(ast: File): boolean {
|
|
34
|
+
return traverseForChanges(ast, visitor)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const visitor: Visitor<ChangeState> = {
|
|
38
|
+
FunctionDeclaration(path, state) {
|
|
39
|
+
const { id } = path.node
|
|
40
|
+
if (!id || !isConvertible(path, id)) {
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
const { node } = path
|
|
44
|
+
const fn = t.functionExpression(null, node.params, node.body, node.generator, node.async)
|
|
45
|
+
path.replaceWith(t.variableDeclaration('var', [t.variableDeclarator(t.cloneNode(id), fn)]))
|
|
46
|
+
state.changed = true
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function isConvertible(path: NodePath<t.FunctionDeclaration>, id: t.Identifier): boolean {
|
|
51
|
+
const parent = path.parentPath
|
|
52
|
+
if (!parent) {
|
|
53
|
+
return false
|
|
54
|
+
}
|
|
55
|
+
const atScriptTop = parent.isProgram() && parent.node.sourceType !== 'module'
|
|
56
|
+
const inFunctionBody = parent.isBlockStatement() && parent.parentPath.isFunction()
|
|
57
|
+
if (!atScriptTop && !inFunctionBody) {
|
|
58
|
+
return false
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const binding = path.scope.getBinding(id.name)
|
|
62
|
+
if (!binding || binding.path.node !== path.node) {
|
|
63
|
+
return false
|
|
64
|
+
}
|
|
65
|
+
if (binding.constantViolations.some(isRedeclaration)) {
|
|
66
|
+
return false
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const { body } = parent.node as t.Program | t.BlockStatement
|
|
70
|
+
const index = path.key as number
|
|
71
|
+
for (let i = 0; i < index; i++) {
|
|
72
|
+
if (!isInertStatement(body[i]!)) {
|
|
73
|
+
return false
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return true
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Redeclarations make the hoisted value order-sensitive.
|
|
80
|
+
function isRedeclaration(path: NodePath): boolean {
|
|
81
|
+
return path.isFunctionDeclaration() || path.isClassDeclaration() || path.isVariableDeclarator()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// An inert prologue cannot run user code or read a converted function binding.
|
|
85
|
+
function isInertStatement(stmt: t.Statement): boolean {
|
|
86
|
+
if (t.isFunctionDeclaration(stmt) || t.isImportDeclaration(stmt) || t.isEmptyStatement(stmt)) {
|
|
87
|
+
return true
|
|
88
|
+
}
|
|
89
|
+
if (t.isVariableDeclaration(stmt)) {
|
|
90
|
+
return stmt.declarations.every((declarator) => isInertInit(declarator.init))
|
|
91
|
+
}
|
|
92
|
+
return false
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function isInertInit(init: t.VariableDeclarator['init']): boolean {
|
|
96
|
+
return (
|
|
97
|
+
init == null ||
|
|
98
|
+
t.isNumericLiteral(init) ||
|
|
99
|
+
t.isStringLiteral(init) ||
|
|
100
|
+
t.isBooleanLiteral(init) ||
|
|
101
|
+
t.isNullLiteral(init) ||
|
|
102
|
+
t.isBigIntLiteral(init) ||
|
|
103
|
+
t.isRegExpLiteral(init) ||
|
|
104
|
+
t.isThisExpression(init) ||
|
|
105
|
+
t.isFunctionExpression(init) ||
|
|
106
|
+
t.isArrowFunctionExpression(init)
|
|
107
|
+
)
|
|
108
|
+
}
|