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,1074 @@
|
|
|
1
|
+
import traverse from '@babel/traverse'
|
|
2
|
+
import type { NodePath, Scope } from '@babel/traverse'
|
|
3
|
+
import * as t from '@babel/types'
|
|
4
|
+
import type { File } from '@babel/types'
|
|
5
|
+
|
|
6
|
+
import { analyzeInlineability } from 'src/analysis/inlineability'
|
|
7
|
+
import type { InlineCandidate } from 'src/analysis/inlineability'
|
|
8
|
+
import { isPure } from 'src/analysis/purity'
|
|
9
|
+
import { enclosingScopeHasDirectEval, isInsideWith, walkOwnFunctionScope } from 'src/utils/ast'
|
|
10
|
+
import { reorderableToStatement } from 'src/utils/evaluation-order'
|
|
11
|
+
import { generateLoopLabel } from 'src/utils/loop-lowering'
|
|
12
|
+
import { hasAnnexBFunctionAlias, isInStrictContext } from 'src/utils/paths'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Replaces eligible calls with function bodies, working from inner calls out.
|
|
16
|
+
* Declarations are removed after their last reference disappears.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* // ◀️ before
|
|
20
|
+
* function id(x) { return x; }
|
|
21
|
+
* var y = id(compute());
|
|
22
|
+
*
|
|
23
|
+
* // ▶️ after
|
|
24
|
+
* var _x = compute();
|
|
25
|
+
* var y = _x;
|
|
26
|
+
*/
|
|
27
|
+
export function inlineFunctions(ast: File): boolean {
|
|
28
|
+
const candidates = analyzeInlineability(ast)
|
|
29
|
+
if (candidates.size === 0) {
|
|
30
|
+
return false
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let anyInlined = false
|
|
34
|
+
|
|
35
|
+
traverse(ast, {
|
|
36
|
+
CallExpression: {
|
|
37
|
+
exit(path) {
|
|
38
|
+
// Direct statement-array edits bypass Babel's binding updates. Crawl the
|
|
39
|
+
// owner so later sibling calls see new initialized temps in this pass.
|
|
40
|
+
const tempOwner = path.getFunctionParent() ?? path.scope.getProgramParent().path
|
|
41
|
+
if (tryInlineAtCallExpr(path, candidates)) {
|
|
42
|
+
anyInlined = true
|
|
43
|
+
tempOwner.scope.crawl()
|
|
44
|
+
rebindCandidates(candidates)
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
if (!anyInlined) {
|
|
51
|
+
return false
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
dropZeroRefCandidates(candidates)
|
|
55
|
+
return true
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function tryInlineAtCallExpr(
|
|
59
|
+
path: NodePath<t.CallExpression>,
|
|
60
|
+
candidates: Map<string, InlineCandidate>,
|
|
61
|
+
): boolean {
|
|
62
|
+
const { callee } = path.node
|
|
63
|
+
if (!t.isIdentifier(callee)) {
|
|
64
|
+
return false
|
|
65
|
+
}
|
|
66
|
+
const candidate = candidates.get(callee.name)
|
|
67
|
+
if (!candidate) {
|
|
68
|
+
return false
|
|
69
|
+
}
|
|
70
|
+
const binding = path.scope.getBinding(callee.name)
|
|
71
|
+
if (!binding || binding !== candidate.binding) {
|
|
72
|
+
return false
|
|
73
|
+
}
|
|
74
|
+
// Earlier inlining can add free references to this candidate's live body.
|
|
75
|
+
const freeVars = computeLiveFreeVars(candidate)
|
|
76
|
+
if (!isSafeAtCallSite(candidate, freeVars, path, candidates)) {
|
|
77
|
+
return false
|
|
78
|
+
}
|
|
79
|
+
return inlineCallSite(path, candidate, freeVars)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// A crawl replaces Binding objects. Re-resolve candidates from their declarations.
|
|
83
|
+
function rebindCandidates(candidates: Map<string, InlineCandidate>): void {
|
|
84
|
+
for (const [name, candidate] of candidates) {
|
|
85
|
+
const declId = declarationIdentifier(candidate.declarationPath)
|
|
86
|
+
const binding = candidate.declarationPath.removed
|
|
87
|
+
? null
|
|
88
|
+
: candidate.declarationPath.scope.getBinding(name)
|
|
89
|
+
if (!binding || !declId || binding.identifier !== declId) {
|
|
90
|
+
candidates.delete(name)
|
|
91
|
+
continue
|
|
92
|
+
}
|
|
93
|
+
candidate.binding = binding
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function dropZeroRefCandidates(candidates: Map<string, InlineCandidate>): void {
|
|
98
|
+
const first = candidates.values().next().value
|
|
99
|
+
if (!first) {
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
first.declarationPath.scope.getProgramParent().crawl()
|
|
103
|
+
for (const candidate of candidates.values()) {
|
|
104
|
+
const declPath = candidate.declarationPath
|
|
105
|
+
if (declPath.removed) {
|
|
106
|
+
continue
|
|
107
|
+
}
|
|
108
|
+
const declId = declarationIdentifier(declPath)
|
|
109
|
+
if (!declId) {
|
|
110
|
+
continue
|
|
111
|
+
}
|
|
112
|
+
// An outer same-named binding must not decide whether this declaration drops.
|
|
113
|
+
const binding = declPath.scope.getBinding(candidate.name)
|
|
114
|
+
if (!binding || binding.identifier !== declId || binding.references > 0) {
|
|
115
|
+
continue
|
|
116
|
+
}
|
|
117
|
+
declPath.remove()
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function declarationIdentifier(declPath: NodePath): t.Identifier | null {
|
|
122
|
+
if (declPath.isFunctionDeclaration()) {
|
|
123
|
+
return declPath.node.id ?? null
|
|
124
|
+
}
|
|
125
|
+
if (declPath.isVariableDeclaration() && declPath.node.declarations.length === 1) {
|
|
126
|
+
const { id } = declPath.node.declarations[0]!
|
|
127
|
+
return t.isIdentifier(id) ? id : null
|
|
128
|
+
}
|
|
129
|
+
return null
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function isSafeAtCallSite(
|
|
133
|
+
candidate: InlineCandidate,
|
|
134
|
+
freeVars: Set<string>,
|
|
135
|
+
callPath: NodePath<t.CallExpression>,
|
|
136
|
+
candidates: Map<string, InlineCandidate>,
|
|
137
|
+
): boolean {
|
|
138
|
+
// `with`, direct eval, and Annex B aliases can redirect free names outside
|
|
139
|
+
// Babel's scope table.
|
|
140
|
+
if (isInsideWith(callPath) || enclosingScopeHasDirectEval(callPath)) {
|
|
141
|
+
return false
|
|
142
|
+
}
|
|
143
|
+
if (annexBHoistShadowsFreeVar(callPath, freeVars)) {
|
|
144
|
+
return false
|
|
145
|
+
}
|
|
146
|
+
// Spliced code inherits call-site strictness, so it must match the callee.
|
|
147
|
+
if (isInStrictContext(candidate.fnPath) !== isInStrictContext(callPath)) {
|
|
148
|
+
return false
|
|
149
|
+
}
|
|
150
|
+
// In a loop, spliced uninitialized `var` bindings would retain prior values.
|
|
151
|
+
if (
|
|
152
|
+
callIsInsideLoop(callPath) &&
|
|
153
|
+
bodyHasUninitializedVar(candidate.fnNode.body as t.BlockStatement)
|
|
154
|
+
) {
|
|
155
|
+
return false
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
for (const freeVar of freeVars) {
|
|
159
|
+
const declSiteBinding = candidate.fnPath.scope.parent?.getBinding(freeVar)
|
|
160
|
+
const callSiteBinding = callPath.scope.getBinding(freeVar)
|
|
161
|
+
if (declSiteBinding !== callSiteBinding) {
|
|
162
|
+
return false
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (isInUnsafeExpressionPosition(callPath)) {
|
|
167
|
+
return false
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const stmtParent = callPath.getStatementParent()
|
|
171
|
+
if (!stmtParent) {
|
|
172
|
+
return false
|
|
173
|
+
}
|
|
174
|
+
// The call must run exactly once at statement position. Repeating loop slots,
|
|
175
|
+
// short-circuiting chains, case tests, and earlier effects are refused.
|
|
176
|
+
if (stmtParent.isLoop() && callInReevaluatedLoopPosition(callPath, stmtParent)) {
|
|
177
|
+
return false
|
|
178
|
+
}
|
|
179
|
+
if (withinOptionalChain(callPath, stmtParent)) {
|
|
180
|
+
return false
|
|
181
|
+
}
|
|
182
|
+
if (!reorderableToStatement(callPath, stmtParent)) {
|
|
183
|
+
return false
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (argsContainContextExpression(callPath)) {
|
|
187
|
+
return false
|
|
188
|
+
}
|
|
189
|
+
if (argsContainCandidateCalls(callPath, candidates)) {
|
|
190
|
+
return false
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return true
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ECMA-262 B.3.2.1 creates a sloppy block-function alias that Babel misses in
|
|
197
|
+
// the enclosing scope.
|
|
198
|
+
function annexBHoistShadowsFreeVar(
|
|
199
|
+
callPath: NodePath<t.CallExpression>,
|
|
200
|
+
freeVars: Set<string>,
|
|
201
|
+
): boolean {
|
|
202
|
+
if (freeVars.size === 0 || isInStrictContext(callPath)) {
|
|
203
|
+
return false
|
|
204
|
+
}
|
|
205
|
+
const owner = callPath.getFunctionParent() ?? callPath.scope.getProgramParent().path
|
|
206
|
+
let found = false
|
|
207
|
+
owner.traverse({
|
|
208
|
+
Function(path) {
|
|
209
|
+
path.skip()
|
|
210
|
+
},
|
|
211
|
+
FunctionDeclaration(path) {
|
|
212
|
+
const { id } = path.node
|
|
213
|
+
if (id && freeVars.has(id.name) && hasAnnexBFunctionAlias(path)) {
|
|
214
|
+
found = true
|
|
215
|
+
path.stop()
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
})
|
|
219
|
+
return found
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Only loops in the same function repeat the spliced body.
|
|
223
|
+
function callIsInsideLoop(callPath: NodePath): boolean {
|
|
224
|
+
for (let current: NodePath | null = callPath.parentPath; current; current = current.parentPath) {
|
|
225
|
+
if (current.isLoop()) {
|
|
226
|
+
return true
|
|
227
|
+
}
|
|
228
|
+
if (current.isFunction() || current.isProgram()) {
|
|
229
|
+
return false
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return false
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function bodyHasUninitializedVar(body: t.BlockStatement): boolean {
|
|
236
|
+
let found = false
|
|
237
|
+
walkOwnFunctionScope(body, (node) => {
|
|
238
|
+
if (t.isVariableDeclarator(node) && t.isIdentifier(node.id) && node.init == null) {
|
|
239
|
+
found = true
|
|
240
|
+
}
|
|
241
|
+
})
|
|
242
|
+
return found
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function bodyHasTryFinally(body: t.BlockStatement): boolean {
|
|
246
|
+
let found = false
|
|
247
|
+
walkOwnFunctionScope(body, (node) => {
|
|
248
|
+
if (t.isTryStatement(node) && node.finalizer != null) {
|
|
249
|
+
found = true
|
|
250
|
+
}
|
|
251
|
+
})
|
|
252
|
+
return found
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Loop tests and updates repeat. Initializers and iterator sources run once.
|
|
256
|
+
function callInReevaluatedLoopPosition(callPath: NodePath, loopPath: NodePath): boolean {
|
|
257
|
+
let cur: NodePath = callPath
|
|
258
|
+
while (cur.parentPath && cur.parentPath !== loopPath) {
|
|
259
|
+
cur = cur.parentPath
|
|
260
|
+
}
|
|
261
|
+
if (loopPath.isForStatement()) {
|
|
262
|
+
return cur.key !== 'init'
|
|
263
|
+
}
|
|
264
|
+
if (loopPath.isForInStatement() || loopPath.isForOfStatement()) {
|
|
265
|
+
return cur.key !== 'right'
|
|
266
|
+
}
|
|
267
|
+
return true
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Any optional ancestor may short-circuit the call.
|
|
271
|
+
function withinOptionalChain(callPath: NodePath, stmtParent: NodePath): boolean {
|
|
272
|
+
let cur: NodePath = callPath
|
|
273
|
+
for (;;) {
|
|
274
|
+
const parent = cur.parentPath
|
|
275
|
+
if (!parent || parent === stmtParent) {
|
|
276
|
+
return false
|
|
277
|
+
}
|
|
278
|
+
if (parent.isOptionalMemberExpression() || parent.isOptionalCallExpression()) {
|
|
279
|
+
return true
|
|
280
|
+
}
|
|
281
|
+
cur = parent
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function argsContainContextExpression(callPath: NodePath<t.CallExpression>): boolean {
|
|
286
|
+
const stmtParent = callPath.getStatementParent()
|
|
287
|
+
if (!stmtParent) {
|
|
288
|
+
return false
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (
|
|
292
|
+
callPath.parentPath === stmtParent ||
|
|
293
|
+
(callPath.parentPath?.isExpressionStatement() && callPath.parentPath === stmtParent)
|
|
294
|
+
) {
|
|
295
|
+
return false
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
for (const arg of callPath.node.arguments) {
|
|
299
|
+
if (containsThisOrArguments(arg)) {
|
|
300
|
+
return true
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return false
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function containsThisOrArguments(node: t.Node): boolean {
|
|
307
|
+
let found = false
|
|
308
|
+
t.traverseFast(node, (visited) => {
|
|
309
|
+
// Non-arrow functions bind their own `this` and `arguments`.
|
|
310
|
+
if (t.isFunction(visited) && !t.isArrowFunctionExpression(visited)) {
|
|
311
|
+
return t.traverseFast.skip
|
|
312
|
+
}
|
|
313
|
+
const isContextRef =
|
|
314
|
+
t.isThisExpression(visited) || (t.isIdentifier(visited) && visited.name === 'arguments')
|
|
315
|
+
if (isContextRef) {
|
|
316
|
+
found = true
|
|
317
|
+
}
|
|
318
|
+
return isContextRef ? t.traverseFast.stop : undefined
|
|
319
|
+
})
|
|
320
|
+
return found
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function argsContainCandidateCalls(
|
|
324
|
+
callPath: NodePath<t.CallExpression>,
|
|
325
|
+
candidates: Map<string, InlineCandidate>,
|
|
326
|
+
): boolean {
|
|
327
|
+
for (const arg of callPath.node.arguments) {
|
|
328
|
+
if (containsCandidateCall(arg, candidates)) {
|
|
329
|
+
return true
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return false
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function containsCandidateCall(node: t.Node, candidates: Map<string, InlineCandidate>): boolean {
|
|
336
|
+
let found = false
|
|
337
|
+
t.traverseFast(node, (visited) => {
|
|
338
|
+
const isCandidateCall =
|
|
339
|
+
t.isCallExpression(visited) &&
|
|
340
|
+
t.isIdentifier(visited.callee) &&
|
|
341
|
+
candidates.has(visited.callee.name)
|
|
342
|
+
if (isCandidateCall) {
|
|
343
|
+
found = true
|
|
344
|
+
}
|
|
345
|
+
return isCandidateCall ? t.traverseFast.stop : undefined
|
|
346
|
+
})
|
|
347
|
+
return found
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function isInUnsafeExpressionPosition(path: NodePath): boolean {
|
|
351
|
+
let current: NodePath = path
|
|
352
|
+
while (current.parentPath) {
|
|
353
|
+
const parent = current.parentPath
|
|
354
|
+
if (parent.isStatement()) {
|
|
355
|
+
return false
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (
|
|
359
|
+
parent.isConditionalExpression() &&
|
|
360
|
+
(current.key === 'consequent' || current.key === 'alternate')
|
|
361
|
+
) {
|
|
362
|
+
return true
|
|
363
|
+
}
|
|
364
|
+
if (parent.isLogicalExpression() && current.key === 'right') {
|
|
365
|
+
return true
|
|
366
|
+
}
|
|
367
|
+
if (
|
|
368
|
+
parent.isSequenceExpression() &&
|
|
369
|
+
parent.node.expressions.indexOf(current.node as t.Expression) > 0
|
|
370
|
+
) {
|
|
371
|
+
return true
|
|
372
|
+
}
|
|
373
|
+
// Default values run per call in the function's parameter scope.
|
|
374
|
+
if (parent.isAssignmentPattern() && current.key === 'right') {
|
|
375
|
+
return true
|
|
376
|
+
}
|
|
377
|
+
// Logical-assignment right sides are conditional.
|
|
378
|
+
if (
|
|
379
|
+
parent.isAssignmentExpression() &&
|
|
380
|
+
current.key === 'right' &&
|
|
381
|
+
(parent.node.operator === '||=' ||
|
|
382
|
+
parent.node.operator === '&&=' ||
|
|
383
|
+
parent.node.operator === '??=')
|
|
384
|
+
) {
|
|
385
|
+
return true
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
current = parent
|
|
389
|
+
}
|
|
390
|
+
return false
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function getReturnPattern(stmts: t.Statement[]): 'none' | 'trailing' | 'complex' {
|
|
394
|
+
if (stmts.length === 0) {
|
|
395
|
+
return 'none'
|
|
396
|
+
}
|
|
397
|
+
for (let i = 0; i < stmts.length - 1; i++) {
|
|
398
|
+
if (containsReturn(stmts[i]!)) {
|
|
399
|
+
return 'complex'
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
const last = stmts.at(-1)!
|
|
403
|
+
if (t.isReturnStatement(last)) {
|
|
404
|
+
return 'trailing'
|
|
405
|
+
}
|
|
406
|
+
if (containsReturn(last)) {
|
|
407
|
+
return 'complex'
|
|
408
|
+
}
|
|
409
|
+
return 'none'
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function containsReturn(node: t.Node): boolean {
|
|
413
|
+
let found = false
|
|
414
|
+
t.traverseFast(node, (visited) => {
|
|
415
|
+
if (t.isFunction(visited) && visited !== node) {
|
|
416
|
+
return t.traverseFast.skip
|
|
417
|
+
}
|
|
418
|
+
const isReturn = t.isReturnStatement(visited)
|
|
419
|
+
if (isReturn) {
|
|
420
|
+
found = true
|
|
421
|
+
}
|
|
422
|
+
return isReturn ? t.traverseFast.stop : undefined
|
|
423
|
+
})
|
|
424
|
+
return found
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function inlineCallSite(
|
|
428
|
+
callPath: NodePath<t.CallExpression>,
|
|
429
|
+
candidate: InlineCandidate,
|
|
430
|
+
freeVars: Set<string>,
|
|
431
|
+
): boolean {
|
|
432
|
+
if (callPath.removed) {
|
|
433
|
+
return false
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const args = callPath.node.arguments as t.Expression[]
|
|
437
|
+
const argPaths = callPath.get('arguments') as NodePath<t.Expression>[]
|
|
438
|
+
const body = candidate.fnNode.body as t.BlockStatement
|
|
439
|
+
const returnPattern = getReturnPattern(body.body)
|
|
440
|
+
|
|
441
|
+
const stmtParent = callPath.getStatementParent()
|
|
442
|
+
if (!stmtParent) {
|
|
443
|
+
return false
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const containerPath = stmtParent.parentPath
|
|
447
|
+
if (!containerPath) {
|
|
448
|
+
return false
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
let containerArray: t.Statement[] | null = null
|
|
452
|
+
if (containerPath.isBlock()) {
|
|
453
|
+
containerArray = (containerPath.node as t.Block).body
|
|
454
|
+
} else if (containerPath.isSwitchCase()) {
|
|
455
|
+
containerArray = containerPath.node.consequent
|
|
456
|
+
}
|
|
457
|
+
if (!containerArray) {
|
|
458
|
+
return false
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const stmtIndex = containerArray.indexOf(stmtParent.node)
|
|
462
|
+
if (stmtIndex === -1) {
|
|
463
|
+
return false
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const { scope } = stmtParent
|
|
467
|
+
const labelName = generateLoopLabel(stmtParent, 'inlined')
|
|
468
|
+
|
|
469
|
+
// Returns can write directly into a bare assignment or `var` initializer.
|
|
470
|
+
// Hoisted `var` already defaults to `undefined`. A plain assignment is safe
|
|
471
|
+
// only with one trailing return and no fall-through path.
|
|
472
|
+
const trailingReturn =
|
|
473
|
+
returnPattern === 'trailing' ? (body.body.at(-1) as t.ReturnStatement) : null
|
|
474
|
+
const trailingReturnHasArgument = trailingReturn != null && trailingReturn.argument != null
|
|
475
|
+
const rawTarget =
|
|
476
|
+
returnPattern === 'none' ? null : identifyDirectAssignmentTarget(callPath, freeVars, args)
|
|
477
|
+
// A finalizer runs before the call publishes its return value. Direct writes
|
|
478
|
+
// inside its `try` would expose the value too early.
|
|
479
|
+
const returnObservableByFinally = returnPattern === 'complex' && bodyHasTryFinally(body)
|
|
480
|
+
const directTarget =
|
|
481
|
+
rawTarget == null ||
|
|
482
|
+
returnObservableByFinally ||
|
|
483
|
+
(rawTarget.kind === 'assign' && !trailingReturnHasArgument)
|
|
484
|
+
? null
|
|
485
|
+
: rawTarget
|
|
486
|
+
const retVarName = directTarget ? directTarget.name : scope.generateUid('returnValue')
|
|
487
|
+
|
|
488
|
+
const nameMap = new Map<string, string>()
|
|
489
|
+
const elidedParams = new Set<string>()
|
|
490
|
+
for (let i = 0; i < candidate.params.length; i++) {
|
|
491
|
+
const param = candidate.params[i]!
|
|
492
|
+
const argPath = i < args.length ? argPaths[i]! : null
|
|
493
|
+
if (
|
|
494
|
+
argPath &&
|
|
495
|
+
argEligibleForElision(argPath, callPath.scope) &&
|
|
496
|
+
!freeVars.has((argPath.node as t.Identifier).name) &&
|
|
497
|
+
isParamReadOnly(candidate, param) &&
|
|
498
|
+
// An elided parameter must not alias the target that receives return writes.
|
|
499
|
+
(argPath.node as t.Identifier).name !== retVarName
|
|
500
|
+
) {
|
|
501
|
+
nameMap.set(param, (argPath.node as t.Identifier).name)
|
|
502
|
+
elidedParams.add(param)
|
|
503
|
+
} else {
|
|
504
|
+
nameMap.set(param, scope.generateUid(param))
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
for (const name of collectFunctionScopedNames(body)) {
|
|
508
|
+
if (!nameMap.has(name)) {
|
|
509
|
+
nameMap.set(name, scope.generateUid(name))
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const labelMap = new Map<string, string>()
|
|
514
|
+
collectLabels(body, labelMap, scope)
|
|
515
|
+
|
|
516
|
+
// Each call site needs an independently renamed deep clone.
|
|
517
|
+
const clonedBody = t.cloneNode(body, true).body
|
|
518
|
+
|
|
519
|
+
for (const stmt of clonedBody) {
|
|
520
|
+
renameInBody(stmt, nameMap)
|
|
521
|
+
}
|
|
522
|
+
for (const stmt of clonedBody) {
|
|
523
|
+
renameLabelsInNode(stmt, labelMap)
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
let emittedAnyBreak = false
|
|
527
|
+
if (returnPattern === 'complex') {
|
|
528
|
+
walkStatementsMut(
|
|
529
|
+
clonedBody,
|
|
530
|
+
{
|
|
531
|
+
onReturn(node, ctx) {
|
|
532
|
+
// A bare return must overwrite any earlier return value with `undefined`.
|
|
533
|
+
const argument = node.argument ?? t.unaryExpression('void', t.numericLiteral(0))
|
|
534
|
+
const stmts: t.Statement[] = [
|
|
535
|
+
t.expressionStatement(t.assignmentExpression('=', t.identifier(retVarName), argument)),
|
|
536
|
+
]
|
|
537
|
+
if (!ctx.tailPosition) {
|
|
538
|
+
stmts.push(t.breakStatement(t.identifier(labelName)))
|
|
539
|
+
emittedAnyBreak = true
|
|
540
|
+
}
|
|
541
|
+
return stmts
|
|
542
|
+
},
|
|
543
|
+
},
|
|
544
|
+
{ insideNestedLoop: false, insideNestedSwitch: false, tailPosition: true },
|
|
545
|
+
)
|
|
546
|
+
} else if (returnPattern === 'trailing') {
|
|
547
|
+
const lastIdx = clonedBody.length - 1
|
|
548
|
+
const last = clonedBody[lastIdx]!
|
|
549
|
+
if (t.isReturnStatement(last)) {
|
|
550
|
+
if (last.argument) {
|
|
551
|
+
clonedBody[lastIdx] = t.expressionStatement(
|
|
552
|
+
t.assignmentExpression('=', t.identifier(retVarName), last.argument),
|
|
553
|
+
)
|
|
554
|
+
} else {
|
|
555
|
+
clonedBody.splice(lastIdx, 1)
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const paramAssignments: t.Statement[] = []
|
|
561
|
+
for (let i = 0; i < candidate.params.length; i++) {
|
|
562
|
+
if (elidedParams.has(candidate.params[i]!)) {
|
|
563
|
+
continue
|
|
564
|
+
}
|
|
565
|
+
const newName = nameMap.get(candidate.params[i]!)!
|
|
566
|
+
const argNode =
|
|
567
|
+
i < args.length ? t.cloneNode(args[i]!) : t.unaryExpression('void', t.numericLiteral(0))
|
|
568
|
+
paramAssignments.push(
|
|
569
|
+
t.variableDeclaration('var', [t.variableDeclarator(t.identifier(newName), argNode)]),
|
|
570
|
+
)
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
for (let i = candidate.params.length; i < args.length; i++) {
|
|
574
|
+
paramAssignments.push(t.expressionStatement(t.cloneNode(args[i]!)))
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Only rewritten early returns need the labeled wrapper.
|
|
578
|
+
const needsLabel = returnPattern === 'complex' && emittedAnyBreak
|
|
579
|
+
|
|
580
|
+
if (directTarget) {
|
|
581
|
+
const replacement: t.Statement[] = []
|
|
582
|
+
if (directTarget.kind === 'declarator') {
|
|
583
|
+
replacement.push(
|
|
584
|
+
t.variableDeclaration('var', [t.variableDeclarator(t.identifier(directTarget.name))]),
|
|
585
|
+
)
|
|
586
|
+
}
|
|
587
|
+
if (needsLabel) {
|
|
588
|
+
replacement.push(
|
|
589
|
+
t.labeledStatement(
|
|
590
|
+
t.identifier(labelName),
|
|
591
|
+
t.blockStatement([...paramAssignments, ...clonedBody]),
|
|
592
|
+
),
|
|
593
|
+
)
|
|
594
|
+
} else {
|
|
595
|
+
replacement.push(...paramAssignments, ...clonedBody)
|
|
596
|
+
}
|
|
597
|
+
stmtParent.replaceWithMultiple(replacement)
|
|
598
|
+
return true
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if (needsLabel) {
|
|
602
|
+
const retVarDecl = t.variableDeclaration('var', [
|
|
603
|
+
t.variableDeclarator(t.identifier(retVarName)),
|
|
604
|
+
])
|
|
605
|
+
const labeledBlock = t.labeledStatement(
|
|
606
|
+
t.identifier(labelName),
|
|
607
|
+
t.blockStatement([...paramAssignments, ...clonedBody]),
|
|
608
|
+
)
|
|
609
|
+
containerArray.splice(stmtIndex, 0, retVarDecl, labeledBlock)
|
|
610
|
+
callPath.replaceWith(t.identifier(retVarName))
|
|
611
|
+
} else if (returnPattern === 'none') {
|
|
612
|
+
containerArray.splice(stmtIndex, 0, ...paramAssignments, ...clonedBody)
|
|
613
|
+
callPath.replaceWith(t.unaryExpression('void', t.numericLiteral(0)))
|
|
614
|
+
} else {
|
|
615
|
+
const retVarDecl = t.variableDeclaration('var', [
|
|
616
|
+
t.variableDeclarator(t.identifier(retVarName)),
|
|
617
|
+
])
|
|
618
|
+
containerArray.splice(stmtIndex, 0, retVarDecl, ...paramAssignments, ...clonedBody)
|
|
619
|
+
callPath.replaceWith(t.identifier(retVarName))
|
|
620
|
+
}
|
|
621
|
+
return true
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
type DirectTarget = { kind: 'declarator'; name: string } | { kind: 'assign'; name: string }
|
|
625
|
+
|
|
626
|
+
function identifyDirectAssignmentTarget(
|
|
627
|
+
callPath: NodePath<t.CallExpression>,
|
|
628
|
+
freeVars: Set<string>,
|
|
629
|
+
args: t.Expression[],
|
|
630
|
+
): DirectTarget | null {
|
|
631
|
+
const parent = callPath.parentPath
|
|
632
|
+
if (!parent) {
|
|
633
|
+
return null
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if (
|
|
637
|
+
parent.isVariableDeclarator() &&
|
|
638
|
+
t.isIdentifier(parent.node.id) &&
|
|
639
|
+
parent.node.init === callPath.node
|
|
640
|
+
) {
|
|
641
|
+
const declPath = parent.parentPath
|
|
642
|
+
if (!declPath || !declPath.isVariableDeclaration() || declPath.node.declarations.length !== 1) {
|
|
643
|
+
return null
|
|
644
|
+
}
|
|
645
|
+
// Only `var` can become an uninitialized direct target without changing TDZ
|
|
646
|
+
// or block scope.
|
|
647
|
+
if (declPath.node.kind !== 'var') {
|
|
648
|
+
return null
|
|
649
|
+
}
|
|
650
|
+
const owner = declPath.parentPath
|
|
651
|
+
if (!owner) {
|
|
652
|
+
return null
|
|
653
|
+
}
|
|
654
|
+
if (owner.isForStatement() && owner.node.init === declPath.node) {
|
|
655
|
+
return null
|
|
656
|
+
}
|
|
657
|
+
if (owner.isForInStatement() && owner.node.left === declPath.node) {
|
|
658
|
+
return null
|
|
659
|
+
}
|
|
660
|
+
if (owner.isForOfStatement() && owner.node.left === declPath.node) {
|
|
661
|
+
return null
|
|
662
|
+
}
|
|
663
|
+
const { name } = parent.node.id
|
|
664
|
+
if (freeVars.has(name)) {
|
|
665
|
+
return null
|
|
666
|
+
}
|
|
667
|
+
if (argsContainIdentName(args, name)) {
|
|
668
|
+
return null
|
|
669
|
+
}
|
|
670
|
+
return { kind: 'declarator', name }
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
if (
|
|
674
|
+
parent.isAssignmentExpression() &&
|
|
675
|
+
parent.node.operator === '=' &&
|
|
676
|
+
t.isIdentifier(parent.node.left) &&
|
|
677
|
+
parent.node.right === callPath.node
|
|
678
|
+
) {
|
|
679
|
+
const stmt = parent.parentPath
|
|
680
|
+
if (!stmt || !stmt.isExpressionStatement() || stmt.node.expression !== parent.node) {
|
|
681
|
+
return null
|
|
682
|
+
}
|
|
683
|
+
const { name } = parent.node.left
|
|
684
|
+
if (freeVars.has(name)) {
|
|
685
|
+
return null
|
|
686
|
+
}
|
|
687
|
+
if (argsContainIdentName(args, name)) {
|
|
688
|
+
return null
|
|
689
|
+
}
|
|
690
|
+
return { kind: 'assign', name }
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
return null
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
function argsContainIdentName(args: t.Expression[], name: string): boolean {
|
|
697
|
+
for (const arg of args) {
|
|
698
|
+
if (t.isIdentifier(arg) && arg.name === name) {
|
|
699
|
+
return true
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return false
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
function isParamReadOnly(candidate: InlineCandidate, paramName: string): boolean {
|
|
706
|
+
const binding = candidate.fnPath.scope.bindings[paramName]
|
|
707
|
+
return !binding || binding.constantViolations.length === 0
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// Eliding a parameter repeats the argument read at each use. The read must be
|
|
711
|
+
// pure and resolve to an invariant binding.
|
|
712
|
+
function argEligibleForElision(argPath: NodePath, scope: Scope): boolean {
|
|
713
|
+
if (!argPath.isIdentifier()) {
|
|
714
|
+
return false
|
|
715
|
+
}
|
|
716
|
+
const binding = scope.getBinding(argPath.node.name)
|
|
717
|
+
if (!binding || binding.constantViolations.length > 0) {
|
|
718
|
+
return false
|
|
719
|
+
}
|
|
720
|
+
return isPure(argPath.node, scope, argPath)
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// Catch parameters stay scoped to their copied handlers. Counting them here
|
|
724
|
+
// would rename same-named free references outside the handler.
|
|
725
|
+
function collectFunctionScopedNames(body: t.Node): Set<string> {
|
|
726
|
+
const names = new Set<string>()
|
|
727
|
+
walkOwnFunctionScope(body, (node) => {
|
|
728
|
+
if (t.isVariableDeclarator(node) && t.isIdentifier(node.id)) {
|
|
729
|
+
names.add(node.id.name)
|
|
730
|
+
}
|
|
731
|
+
})
|
|
732
|
+
return names
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// Optional catch bindings contribute no names.
|
|
736
|
+
function catchParamNames(clause: t.CatchClause): Set<string> {
|
|
737
|
+
if (!clause.param) {
|
|
738
|
+
return new Set()
|
|
739
|
+
}
|
|
740
|
+
return new Set(Object.keys(t.getBindingIdentifiers(clause.param)))
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// Nested functions do not contribute names to the current function scope.
|
|
744
|
+
function collectOwnScopeBindings(fn: t.Function): Set<string> {
|
|
745
|
+
const declared = new Set<string>()
|
|
746
|
+
for (const param of fn.params) {
|
|
747
|
+
for (const name of Object.keys(t.getBindingIdentifiers(param))) {
|
|
748
|
+
declared.add(name)
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
if (t.isBlockStatement(fn.body)) {
|
|
752
|
+
for (const name of collectFunctionScopedNames(fn.body)) {
|
|
753
|
+
declared.add(name)
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
return declared
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// Recompute free names from the live body after earlier inlining. Conservative
|
|
760
|
+
// extras only add call-site identity checks.
|
|
761
|
+
function computeLiveFreeVars(candidate: InlineCandidate): Set<string> {
|
|
762
|
+
const body = candidate.fnNode.body as t.BlockStatement
|
|
763
|
+
const ownBound = collectOwnScopeBindings(candidate.fnNode)
|
|
764
|
+
|
|
765
|
+
const free = new Set<string>()
|
|
766
|
+
const shadows = new ShadowStack()
|
|
767
|
+
|
|
768
|
+
t.traverse(body, {
|
|
769
|
+
enter(node, ancestors) {
|
|
770
|
+
if (t.isFunction(node)) {
|
|
771
|
+
shadows.push(node, collectOwnScopeBindings(node))
|
|
772
|
+
return
|
|
773
|
+
}
|
|
774
|
+
if (t.isCatchClause(node)) {
|
|
775
|
+
shadows.push(node, catchParamNames(node))
|
|
776
|
+
return
|
|
777
|
+
}
|
|
778
|
+
if (!t.isIdentifier(node)) {
|
|
779
|
+
return
|
|
780
|
+
}
|
|
781
|
+
const slot = ancestors.at(-1)
|
|
782
|
+
if (slot && isNonReferenceSlot(slot.node, slot.key)) {
|
|
783
|
+
return
|
|
784
|
+
}
|
|
785
|
+
const { name } = node
|
|
786
|
+
if (ownBound.has(name) || shadows.covers(name)) {
|
|
787
|
+
return
|
|
788
|
+
}
|
|
789
|
+
free.add(name)
|
|
790
|
+
},
|
|
791
|
+
exit(node) {
|
|
792
|
+
if (t.isFunction(node) || t.isCatchClause(node)) {
|
|
793
|
+
shadows.pop(node)
|
|
794
|
+
}
|
|
795
|
+
},
|
|
796
|
+
})
|
|
797
|
+
return free
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// Live shadow frames prevent renaming references owned by an inner binding.
|
|
801
|
+
class ShadowStack {
|
|
802
|
+
private readonly counts = new Map<string, number>()
|
|
803
|
+
private readonly framesByNode = new WeakMap<t.Node, Set<string>>()
|
|
804
|
+
|
|
805
|
+
push(node: t.Node, names: Set<string>): void {
|
|
806
|
+
if (names.size === 0) {
|
|
807
|
+
return
|
|
808
|
+
}
|
|
809
|
+
this.framesByNode.set(node, names)
|
|
810
|
+
for (const name of names) {
|
|
811
|
+
this.counts.set(name, (this.counts.get(name) ?? 0) + 1)
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
pop(node: t.Node): void {
|
|
816
|
+
const names = this.framesByNode.get(node)
|
|
817
|
+
if (!names) {
|
|
818
|
+
return
|
|
819
|
+
}
|
|
820
|
+
this.framesByNode.delete(node)
|
|
821
|
+
for (const name of names) {
|
|
822
|
+
const count = this.counts.get(name)!
|
|
823
|
+
if (count <= 1) {
|
|
824
|
+
this.counts.delete(name)
|
|
825
|
+
} else {
|
|
826
|
+
this.counts.set(name, count - 1)
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
covers(name: string): boolean {
|
|
832
|
+
return (this.counts.get(name) ?? 0) > 0
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
function collectLabels(body: t.BlockStatement, labelMap: Map<string, string>, scope: Scope): void {
|
|
837
|
+
walkOwnFunctionScope(body, (node) => {
|
|
838
|
+
if (t.isLabeledStatement(node) && !labelMap.has(node.label.name)) {
|
|
839
|
+
labelMap.set(node.label.name, scope.generateUid(node.label.name))
|
|
840
|
+
}
|
|
841
|
+
})
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
function renameLabelsInNode(node: t.Node, labelMap: Map<string, string>): void {
|
|
845
|
+
if (t.isFunction(node)) {
|
|
846
|
+
return
|
|
847
|
+
}
|
|
848
|
+
walkOwnFunctionScope(node, (visited) => {
|
|
849
|
+
if (t.isLabeledStatement(visited) && labelMap.has(visited.label.name)) {
|
|
850
|
+
visited.label = t.identifier(labelMap.get(visited.label.name)!)
|
|
851
|
+
}
|
|
852
|
+
if (t.isBreakStatement(visited) && visited.label && labelMap.has(visited.label.name)) {
|
|
853
|
+
visited.label = t.identifier(labelMap.get(visited.label.name)!)
|
|
854
|
+
}
|
|
855
|
+
if (t.isContinueStatement(visited) && visited.label && labelMap.has(visited.label.name)) {
|
|
856
|
+
visited.label = t.identifier(labelMap.get(visited.label.name)!)
|
|
857
|
+
}
|
|
858
|
+
})
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// Rename the detached clone before splicing. Babel's scope renamer needs a live
|
|
862
|
+
// path, while splicing first would publish unrenamed hoisted variables.
|
|
863
|
+
function renameInBody(node: t.Node, nameMap: Map<string, string>): void {
|
|
864
|
+
const shadows = new ShadowStack()
|
|
865
|
+
|
|
866
|
+
t.traverse(node, {
|
|
867
|
+
enter(visited, ancestors) {
|
|
868
|
+
if (t.isFunction(visited)) {
|
|
869
|
+
shadows.push(visited, mappedShadow(collectOwnScopeBindings(visited), nameMap))
|
|
870
|
+
return
|
|
871
|
+
}
|
|
872
|
+
if (t.isCatchClause(visited)) {
|
|
873
|
+
shadows.push(visited, mappedShadow(catchParamNames(visited), nameMap))
|
|
874
|
+
return
|
|
875
|
+
}
|
|
876
|
+
if (!t.isIdentifier(visited)) {
|
|
877
|
+
return
|
|
878
|
+
}
|
|
879
|
+
const mapped = nameMap.get(visited.name)
|
|
880
|
+
if (mapped === undefined) {
|
|
881
|
+
return
|
|
882
|
+
}
|
|
883
|
+
if (shadows.covers(visited.name)) {
|
|
884
|
+
return
|
|
885
|
+
}
|
|
886
|
+
const slot = ancestors.at(-1)
|
|
887
|
+
if (slot && isNonReferenceSlot(slot.node, slot.key)) {
|
|
888
|
+
return
|
|
889
|
+
}
|
|
890
|
+
visited.name = mapped
|
|
891
|
+
},
|
|
892
|
+
exit(visited) {
|
|
893
|
+
if (t.isFunction(visited) || t.isCatchClause(visited)) {
|
|
894
|
+
shadows.pop(visited)
|
|
895
|
+
}
|
|
896
|
+
},
|
|
897
|
+
})
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// Only remapped names need shadow tracking.
|
|
901
|
+
function mappedShadow(names: Set<string>, nameMap: Map<string, string>): Set<string> {
|
|
902
|
+
const introduced = new Set<string>()
|
|
903
|
+
for (const name of names) {
|
|
904
|
+
if (nameMap.has(name)) {
|
|
905
|
+
introduced.add(name)
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
return introduced
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
function isNonReferenceSlot(parent: t.Node, key: string): boolean {
|
|
912
|
+
if (key === 'property' && t.isMemberExpression(parent) && !parent.computed) {
|
|
913
|
+
return true
|
|
914
|
+
}
|
|
915
|
+
if (
|
|
916
|
+
key === 'key' &&
|
|
917
|
+
(t.isObjectProperty(parent) || t.isObjectMethod(parent) || t.isClassMethod(parent)) &&
|
|
918
|
+
!(parent as t.ObjectProperty).computed
|
|
919
|
+
) {
|
|
920
|
+
return true
|
|
921
|
+
}
|
|
922
|
+
if (
|
|
923
|
+
key === 'label' &&
|
|
924
|
+
(t.isLabeledStatement(parent) || t.isBreakStatement(parent) || t.isContinueStatement(parent))
|
|
925
|
+
) {
|
|
926
|
+
return true
|
|
927
|
+
}
|
|
928
|
+
return false
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
interface StatementWalkContext {
|
|
932
|
+
insideNestedLoop: boolean
|
|
933
|
+
insideNestedSwitch: boolean
|
|
934
|
+
// Tail position propagates through final blocks, branches, and labels. Try,
|
|
935
|
+
// switch, loops, and `with` interrupt it because more code may run afterward.
|
|
936
|
+
tailPosition: boolean
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
interface StatementWalkHooks {
|
|
940
|
+
onContinue?: (node: t.ContinueStatement, ctx: StatementWalkContext) => t.Statement | null
|
|
941
|
+
onBreak?: (node: t.BreakStatement, ctx: StatementWalkContext) => void
|
|
942
|
+
onReturn?: (node: t.ReturnStatement, ctx: StatementWalkContext) => t.Statement[] | null
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// Nested-function control flow belongs to that function.
|
|
946
|
+
function walkStatementsMut(
|
|
947
|
+
stmts: t.Statement[],
|
|
948
|
+
hooks: StatementWalkHooks,
|
|
949
|
+
ctx?: StatementWalkContext,
|
|
950
|
+
): void {
|
|
951
|
+
const context = ctx ?? {
|
|
952
|
+
insideNestedLoop: false,
|
|
953
|
+
insideNestedSwitch: false,
|
|
954
|
+
tailPosition: false,
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
for (let index = 0; index < stmts.length; index++) {
|
|
958
|
+
const stmt = stmts[index]!
|
|
959
|
+
const isLast = index === stmts.length - 1
|
|
960
|
+
const stmtCtx: StatementWalkContext = {
|
|
961
|
+
...context,
|
|
962
|
+
tailPosition: context.tailPosition && isLast,
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
if (t.isContinueStatement(stmt) && hooks.onContinue) {
|
|
966
|
+
const replacement = hooks.onContinue(stmt, stmtCtx)
|
|
967
|
+
if (replacement) {
|
|
968
|
+
stmts[index] = replacement
|
|
969
|
+
}
|
|
970
|
+
continue
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
if (t.isBreakStatement(stmt) && hooks.onBreak) {
|
|
974
|
+
hooks.onBreak(stmt, stmtCtx)
|
|
975
|
+
continue
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
if (t.isReturnStatement(stmt) && hooks.onReturn) {
|
|
979
|
+
const replacement = hooks.onReturn(stmt, stmtCtx)
|
|
980
|
+
if (replacement) {
|
|
981
|
+
stmts.splice(index, 1, ...replacement)
|
|
982
|
+
index += replacement.length - 1
|
|
983
|
+
}
|
|
984
|
+
continue
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
walkIntoCompound(stmt, hooks, stmtCtx)
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
function walkIntoCompound(
|
|
992
|
+
node: t.Statement,
|
|
993
|
+
hooks: StatementWalkHooks,
|
|
994
|
+
ctx: StatementWalkContext,
|
|
995
|
+
): void {
|
|
996
|
+
if (t.isBlockStatement(node)) {
|
|
997
|
+
walkStatementsMut(node.body, hooks, ctx)
|
|
998
|
+
return
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
if (t.isIfStatement(node)) {
|
|
1002
|
+
node.consequent = walkSingleChild(node.consequent, hooks, ctx)
|
|
1003
|
+
if (node.alternate) {
|
|
1004
|
+
node.alternate = walkSingleChild(node.alternate, hooks, ctx)
|
|
1005
|
+
}
|
|
1006
|
+
return
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
if (t.isLabeledStatement(node)) {
|
|
1010
|
+
node.body = walkSingleChild(node.body, hooks, ctx)
|
|
1011
|
+
return
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
if (t.isTryStatement(node)) {
|
|
1015
|
+
const inner: StatementWalkContext = { ...ctx, tailPosition: false }
|
|
1016
|
+
walkStatementsMut(node.block.body, hooks, inner)
|
|
1017
|
+
if (node.handler) {
|
|
1018
|
+
walkStatementsMut(node.handler.body.body, hooks, inner)
|
|
1019
|
+
}
|
|
1020
|
+
if (node.finalizer) {
|
|
1021
|
+
walkStatementsMut(node.finalizer.body, hooks, inner)
|
|
1022
|
+
}
|
|
1023
|
+
return
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
if (t.isSwitchStatement(node)) {
|
|
1027
|
+
const inner: StatementWalkContext = {
|
|
1028
|
+
...ctx,
|
|
1029
|
+
insideNestedSwitch: true,
|
|
1030
|
+
tailPosition: false,
|
|
1031
|
+
}
|
|
1032
|
+
for (const switchCase of node.cases) {
|
|
1033
|
+
walkStatementsMut(switchCase.consequent, hooks, inner)
|
|
1034
|
+
}
|
|
1035
|
+
return
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
if (
|
|
1039
|
+
t.isWhileStatement(node) ||
|
|
1040
|
+
t.isDoWhileStatement(node) ||
|
|
1041
|
+
t.isForStatement(node) ||
|
|
1042
|
+
t.isForInStatement(node) ||
|
|
1043
|
+
t.isForOfStatement(node)
|
|
1044
|
+
) {
|
|
1045
|
+
const inner: StatementWalkContext = {
|
|
1046
|
+
...ctx,
|
|
1047
|
+
insideNestedLoop: true,
|
|
1048
|
+
tailPosition: false,
|
|
1049
|
+
}
|
|
1050
|
+
node.body = walkSingleChild(node.body, hooks, inner)
|
|
1051
|
+
return
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
if (t.isWithStatement(node)) {
|
|
1055
|
+
node.body = walkSingleChild(node.body, hooks, { ...ctx, tailPosition: false })
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
function walkSingleChild(
|
|
1060
|
+
child: t.Statement,
|
|
1061
|
+
hooks: StatementWalkHooks,
|
|
1062
|
+
ctx: StatementWalkContext,
|
|
1063
|
+
): t.Statement {
|
|
1064
|
+
if (t.isBlockStatement(child)) {
|
|
1065
|
+
walkStatementsMut(child.body, hooks, ctx)
|
|
1066
|
+
return child
|
|
1067
|
+
}
|
|
1068
|
+
const wrapped = [child]
|
|
1069
|
+
walkStatementsMut(wrapped, hooks, ctx)
|
|
1070
|
+
if (wrapped.length === 1) {
|
|
1071
|
+
return wrapped[0]!
|
|
1072
|
+
}
|
|
1073
|
+
return t.blockStatement(wrapped)
|
|
1074
|
+
}
|