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,805 @@
|
|
|
1
|
+
import traverse from '@babel/traverse';
|
|
2
|
+
import * as t from '@babel/types';
|
|
3
|
+
import { evaluateConstant } from '../analysis/constant.js';
|
|
4
|
+
import { declarationReaches, initializerReaches, readCrossesFunctionBoundary, } from '../analysis/document-order.js';
|
|
5
|
+
import { isPure } from '../analysis/purity.js';
|
|
6
|
+
import { isInsideWith } from '../utils/ast.js';
|
|
7
|
+
import { reorderableToStatement } from '../utils/evaluation-order.js';
|
|
8
|
+
import { valueToLiteral } from '../utils/literal.js';
|
|
9
|
+
import { isCalleeOrTagOf } from '../utils/paths.js';
|
|
10
|
+
/**
|
|
11
|
+
* Drops single-use temporaries through substitution, direct branch writes, or
|
|
12
|
+
* constant replacement. Sweeps continue until no shape exposes another.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* // ◀️ before
|
|
16
|
+
* var _t;
|
|
17
|
+
* if (cond) { _t = 1; } else { _t = 2; }
|
|
18
|
+
* return _t;
|
|
19
|
+
*
|
|
20
|
+
* // ▶️ after
|
|
21
|
+
* if (cond) { return 1; } else { return 2; }
|
|
22
|
+
*/
|
|
23
|
+
export function collapseSingleUseTemps(ast) {
|
|
24
|
+
let anyChanged = false;
|
|
25
|
+
for (let pass = 0; pass < 20; pass++) {
|
|
26
|
+
const programScope = findProgramScope(ast);
|
|
27
|
+
if (!programScope) {
|
|
28
|
+
return anyChanged;
|
|
29
|
+
}
|
|
30
|
+
programScope.crawl();
|
|
31
|
+
const candidates = [];
|
|
32
|
+
traverse(ast, {
|
|
33
|
+
VariableDeclarator(path) {
|
|
34
|
+
if (resolveSingleUseBinding(path)) {
|
|
35
|
+
candidates.push(path);
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
let changedThisPass = false;
|
|
40
|
+
for (const candidate of candidates) {
|
|
41
|
+
if (candidate.removed) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
// Earlier rewrites can invalidate bindings collected by this sweep.
|
|
45
|
+
const binding = resolveSingleUseBinding(candidate);
|
|
46
|
+
if (!binding) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (binding.references === 1 &&
|
|
50
|
+
(trySubstituteSingleUse(binding, candidate) ||
|
|
51
|
+
tryRenameWritesToReader(binding, candidate) ||
|
|
52
|
+
tryScatterIntoTerminator(binding, candidate))) {
|
|
53
|
+
changedThisPass = true;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (tryPropagateConstant(binding, candidate)) {
|
|
57
|
+
changedThisPass = true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (!changedThisPass) {
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
anyChanged = true;
|
|
64
|
+
}
|
|
65
|
+
return anyChanged;
|
|
66
|
+
}
|
|
67
|
+
// Holes, patterns, dead bindings, and rebound names have no usable binding.
|
|
68
|
+
function resolveSingleUseBinding(path) {
|
|
69
|
+
if (!t.isIdentifier(path.node.id)) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
const binding = path.scope.getBinding(path.node.id.name);
|
|
73
|
+
if (!binding || binding.path.node !== path.node) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
if (binding.kind !== 'var' && binding.kind !== 'let' && binding.kind !== 'const') {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
if (binding.references === 0) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
// `with` can redirect the temporary or its consumer outside Babel's scope table.
|
|
83
|
+
if (bindingTouchesWith(binding)) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
return binding;
|
|
87
|
+
}
|
|
88
|
+
function bindingTouchesWith(binding) {
|
|
89
|
+
return (isInsideWith(binding.path) ||
|
|
90
|
+
binding.referencePaths.some((ref) => isInsideWith(ref)) ||
|
|
91
|
+
binding.constantViolations.some((violation) => isInsideWith(violation)));
|
|
92
|
+
}
|
|
93
|
+
function findProgramScope(ast) {
|
|
94
|
+
let result;
|
|
95
|
+
traverse(ast, {
|
|
96
|
+
Program(path) {
|
|
97
|
+
result = path.scope;
|
|
98
|
+
path.stop();
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
// Move a single initializer into its later read when the span is safe.
|
|
104
|
+
function trySubstituteSingleUse(binding, declaratorPath) {
|
|
105
|
+
const [readPath] = binding.referencePaths;
|
|
106
|
+
if (!readPath || !isPathLive(readPath)) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
const declStmt = declaratorPath.parentPath;
|
|
110
|
+
if (!declStmt || !declStmt.isVariableDeclaration()) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
if (declStmt.node.declarations.length !== 1) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
const { init } = declaratorPath.node;
|
|
117
|
+
let writeStmt;
|
|
118
|
+
let writeValueOwner;
|
|
119
|
+
let writeRhs;
|
|
120
|
+
let removeWriteStmt;
|
|
121
|
+
if (init) {
|
|
122
|
+
if (binding.constantViolations.length > 0) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
writeStmt = declStmt;
|
|
126
|
+
writeValueOwner = declaratorPath.get('init');
|
|
127
|
+
writeRhs = init;
|
|
128
|
+
removeWriteStmt = false;
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
if (binding.constantViolations.length !== 1) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
const violation = binding.constantViolations[0];
|
|
135
|
+
if (!violation.isAssignmentExpression() || violation.node.operator !== '=') {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
if (!t.isIdentifier(violation.node.left) ||
|
|
139
|
+
violation.node.left.name !== binding.identifier.name) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
if (!violation.parentPath?.isExpressionStatement()) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
if (violation.parentPath.node.expression !== violation.node) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
writeStmt = violation.parentPath;
|
|
149
|
+
writeValueOwner = violation.get('right');
|
|
150
|
+
writeRhs = violation.node.right;
|
|
151
|
+
removeWriteStmt = true;
|
|
152
|
+
}
|
|
153
|
+
const readStmt = safeGetStatementParent(readPath);
|
|
154
|
+
if (!readStmt) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
// The read must be unconditional and have no earlier observable work.
|
|
158
|
+
if (!reorderableToStatement(readPath, readStmt)) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
// Moving into a loop header would repeat evaluation.
|
|
162
|
+
if (readIsInLoopHeader(readPath, readStmt)) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
// Substituting a member into callee position would bind its receiver as `this`.
|
|
166
|
+
if (isCalleeOrTagOf(readPath) &&
|
|
167
|
+
(t.isMemberExpression(writeRhs) || t.isOptionalMemberExpression(writeRhs))) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
const container = writeStmt.parentPath;
|
|
171
|
+
if (!container) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
if (readStmt.parentPath !== container) {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
const stmtPaths = getContainerStatementPaths(container);
|
|
178
|
+
if (!stmtPaths) {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
const writeIdx = stmtPaths.findIndex((stmt) => stmt.node === writeStmt.node);
|
|
182
|
+
const readIdx = stmtPaths.findIndex((stmt) => stmt.node === readStmt.node);
|
|
183
|
+
if (writeIdx === -1 || readIdx === -1) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
if (writeIdx >= readIdx) {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
if (writeIdx + 1 < readIdx) {
|
|
190
|
+
// Moving across a statement requires pure code and no intervening declaration.
|
|
191
|
+
if (!writeValueOwner.isPure()) {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
for (let i = writeIdx + 1; i < readIdx; i++) {
|
|
195
|
+
const stmtPath = stmtPaths[i];
|
|
196
|
+
if (!stmtPath || !isStatementMovementSafe(stmtPath)) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
readPath.replaceWith(t.cloneNode(writeRhs));
|
|
202
|
+
if (removeWriteStmt) {
|
|
203
|
+
writeStmt.remove();
|
|
204
|
+
}
|
|
205
|
+
declStmt.remove();
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
// Redirect scattered temporary writes into their final consumer.
|
|
209
|
+
function tryRenameWritesToReader(binding, declaratorPath) {
|
|
210
|
+
if (declaratorPath.node.init !== null) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
if (binding.constantViolations.length === 0) {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
const [readPath] = binding.referencePaths;
|
|
217
|
+
if (!readPath || !isPathLive(readPath)) {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
const readParent = readPath.parentPath;
|
|
221
|
+
if (!readParent?.isAssignmentExpression() ||
|
|
222
|
+
readParent.node.operator !== '=' ||
|
|
223
|
+
readParent.node.right !== readPath.node ||
|
|
224
|
+
!t.isIdentifier(readParent.node.left)) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
const readStmt = readParent.parentPath;
|
|
228
|
+
if (!readStmt?.isExpressionStatement() || readStmt.node.expression !== readParent.node) {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
const yIdent = readParent.node.left;
|
|
232
|
+
const yBinding = readParent.scope.getBinding(yIdent.name);
|
|
233
|
+
if (!yBinding) {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
const declStmt = declaratorPath.parentPath;
|
|
237
|
+
if (!declStmt?.isVariableDeclaration()) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
if (declStmt.node.declarations.length !== 1) {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
const container = declStmt.parentPath;
|
|
244
|
+
if (!container || readStmt.parentPath !== container) {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
const stmtPaths = getContainerStatementPaths(container);
|
|
248
|
+
if (!stmtPaths) {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
const declIdx = stmtPaths.findIndex((stmt) => stmt.node === declStmt.node);
|
|
252
|
+
const readIdx = stmtPaths.findIndex((stmt) => stmt.node === readStmt.node);
|
|
253
|
+
if (declIdx === -1 || readIdx === -1) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
if (declIdx >= readIdx) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
for (const violation of binding.constantViolations) {
|
|
260
|
+
if (!violation.isAssignmentExpression() || violation.node.operator !== '=') {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
if (!t.isIdentifier(violation.node.left) ||
|
|
264
|
+
violation.node.left.name !== binding.identifier.name) {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
if (!violation.parentPath?.isExpressionStatement()) {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
const stmtAncestorIdx = ancestorIndexInContainer(violation, container, stmtPaths);
|
|
271
|
+
if (stmtAncestorIdx === -1) {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
if (stmtAncestorIdx <= declIdx || stmtAncestorIdx >= readIdx) {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
if (violation.scope.getBinding(yIdent.name) !== yBinding) {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
// Redirecting into a later lexical binding could write it in TDZ.
|
|
281
|
+
if (!declarationReaches(yBinding.path, violation)) {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
const spanPaths = [];
|
|
286
|
+
for (let i = declIdx + 1; i < readIdx; i++) {
|
|
287
|
+
const stmtPath = stmtPaths[i];
|
|
288
|
+
if (!stmtPath || !stmtPath.node) {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
spanPaths.push(stmtPath);
|
|
292
|
+
}
|
|
293
|
+
// Every path reaching the consumer must assign the temporary first.
|
|
294
|
+
if (!spanDefinitelyAssigns(binding, spanPaths)) {
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
for (const stmtPath of spanPaths) {
|
|
298
|
+
// Redirected writes make the consumer value visible earlier.
|
|
299
|
+
if (subtreeReadsBinding(stmtPath, yBinding)) {
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
// Other work in the span could observe the earlier consumer writes.
|
|
303
|
+
if (!spanStatementIsInert(stmtPath, binding)) {
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
for (const violation of binding.constantViolations) {
|
|
308
|
+
if (!t.isAssignmentExpression(violation.node)) {
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
;
|
|
312
|
+
violation.node.left.name = yIdent.name;
|
|
313
|
+
}
|
|
314
|
+
declStmt.remove();
|
|
315
|
+
readStmt.remove();
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
// Replace branch-tail writes followed by a terminator with direct terminators.
|
|
319
|
+
function tryScatterIntoTerminator(binding, declaratorPath) {
|
|
320
|
+
if (declaratorPath.node.init !== null) {
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
if (binding.constantViolations.length === 0) {
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
const [readPath] = binding.referencePaths;
|
|
327
|
+
if (!readPath || !isPathLive(readPath)) {
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
const readParent = readPath.parentPath;
|
|
331
|
+
if (!readParent) {
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
const isReturn = readParent.isReturnStatement() && readParent.node.argument === readPath.node;
|
|
335
|
+
const isThrow = readParent.isThrowStatement() && readParent.node.argument === readPath.node;
|
|
336
|
+
if (!isReturn && !isThrow) {
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
const declStmt = declaratorPath.parentPath;
|
|
340
|
+
if (!declStmt?.isVariableDeclaration()) {
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
if (declStmt.node.declarations.length !== 1) {
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
const container = declStmt.parentPath;
|
|
347
|
+
if (!container || readParent.parentPath !== container) {
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
const stmtPaths = getContainerStatementPaths(container);
|
|
351
|
+
if (!stmtPaths) {
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
const declIdx = stmtPaths.findIndex((stmt) => stmt.node === declStmt.node);
|
|
355
|
+
const readIdx = stmtPaths.findIndex((stmt) => stmt.node === readParent.node);
|
|
356
|
+
if (declIdx === -1 || readIdx === -1) {
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
if (declIdx >= readIdx) {
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
// Intervening statements would be skipped by direct branch terminators.
|
|
363
|
+
if (declIdx + 1 >= readIdx) {
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
const scatterIdx = readIdx - 1;
|
|
367
|
+
const scatterStmt = stmtPaths[scatterIdx].node;
|
|
368
|
+
const writeOwners = collectScatterWriteOwners(binding, scatterStmt);
|
|
369
|
+
if (!writeOwners) {
|
|
370
|
+
return false;
|
|
371
|
+
}
|
|
372
|
+
// Uncollected writes would survive after the binding is removed.
|
|
373
|
+
if (writeOwners.length !== binding.constantViolations.length) {
|
|
374
|
+
return false;
|
|
375
|
+
}
|
|
376
|
+
const violationNodes = new Set(binding.constantViolations.map((violation) => violation.node));
|
|
377
|
+
for (const owner of writeOwners) {
|
|
378
|
+
if (!violationNodes.has(owner.exprStatement.expression)) {
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
// Earlier statements stay in place and must not observe the temporary.
|
|
383
|
+
for (let i = declIdx + 1; i < scatterIdx; i++) {
|
|
384
|
+
const stmtPath = stmtPaths[i];
|
|
385
|
+
if (!stmtPath) {
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
if (subtreeReadsBinding(stmtPath, binding)) {
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
const build = isReturn
|
|
393
|
+
? (value) => t.returnStatement(value)
|
|
394
|
+
: (value) => t.throwStatement(value);
|
|
395
|
+
for (const owner of writeOwners) {
|
|
396
|
+
const value = owner.exprStatement.expression.right;
|
|
397
|
+
owner.replaceInBlock(build(value));
|
|
398
|
+
}
|
|
399
|
+
declStmt.remove();
|
|
400
|
+
readParent.remove();
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
// Every branch must end with a matching temporary write.
|
|
404
|
+
function collectScatterWriteOwners(binding, stmt) {
|
|
405
|
+
const owners = [];
|
|
406
|
+
if (!collectFromBranch(binding, stmt, owners)) {
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
return owners.length > 0 ? owners : null;
|
|
410
|
+
}
|
|
411
|
+
function collectFromBranch(binding, stmt, owners) {
|
|
412
|
+
if (t.isIfStatement(stmt)) {
|
|
413
|
+
if (!stmt.alternate) {
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
return (collectFromBranch(binding, stmt.consequent, owners) &&
|
|
417
|
+
collectFromBranch(binding, stmt.alternate, owners));
|
|
418
|
+
}
|
|
419
|
+
if (t.isBlockStatement(stmt)) {
|
|
420
|
+
if (stmt.body.length === 0) {
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
const last = stmt.body.at(-1);
|
|
424
|
+
if (isWriteToBinding(binding, last)) {
|
|
425
|
+
owners.push({
|
|
426
|
+
exprStatement: last,
|
|
427
|
+
replaceInBlock(replacement) {
|
|
428
|
+
stmt.body[stmt.body.length - 1] = replacement;
|
|
429
|
+
},
|
|
430
|
+
});
|
|
431
|
+
return true;
|
|
432
|
+
}
|
|
433
|
+
return collectFromBranch(binding, last, owners);
|
|
434
|
+
}
|
|
435
|
+
if (isWriteToBinding(binding, stmt)) {
|
|
436
|
+
// A bare branch write needs a parent-aware replacement this helper lacks.
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
// Every path to the trailing read must assign first. Any abrupt exit can expose a
|
|
442
|
+
// redirected write elsewhere and is refused.
|
|
443
|
+
function spanDefinitelyAssigns(binding, spanPaths) {
|
|
444
|
+
const flow = analyzeStatementList(binding, spanPaths.map((path) => path.node), false);
|
|
445
|
+
return flow.escapes.length === 0 && flow.fall === true;
|
|
446
|
+
}
|
|
447
|
+
function analyzeStatementList(binding, statements, entry) {
|
|
448
|
+
let cur = entry;
|
|
449
|
+
const escapes = [];
|
|
450
|
+
for (const statement of statements) {
|
|
451
|
+
if (cur === null) {
|
|
452
|
+
// An unconditional exit makes the remaining statements unreachable.
|
|
453
|
+
break;
|
|
454
|
+
}
|
|
455
|
+
const flow = analyzeStatement(binding, statement, cur);
|
|
456
|
+
escapes.push(...flow.escapes);
|
|
457
|
+
cur = flow.fall;
|
|
458
|
+
}
|
|
459
|
+
return { fall: cur, escapes };
|
|
460
|
+
}
|
|
461
|
+
function analyzeStatement(binding, stmt, entry) {
|
|
462
|
+
if (isWriteToBinding(binding, stmt)) {
|
|
463
|
+
return { fall: true, escapes: [] };
|
|
464
|
+
}
|
|
465
|
+
if (t.isEmptyStatement(stmt) ||
|
|
466
|
+
t.isExpressionStatement(stmt) ||
|
|
467
|
+
t.isVariableDeclaration(stmt) ||
|
|
468
|
+
t.isDebuggerStatement(stmt)) {
|
|
469
|
+
return { fall: entry, escapes: [] };
|
|
470
|
+
}
|
|
471
|
+
if (t.isBreakStatement(stmt)) {
|
|
472
|
+
return {
|
|
473
|
+
fall: null,
|
|
474
|
+
escapes: [{ label: stmt.label?.name ?? null, kind: 'break', assigned: entry }],
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
if (t.isContinueStatement(stmt)) {
|
|
478
|
+
return {
|
|
479
|
+
fall: null,
|
|
480
|
+
escapes: [{ label: stmt.label?.name ?? null, kind: 'continue', assigned: entry }],
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
if (t.isReturnStatement(stmt) || t.isThrowStatement(stmt)) {
|
|
484
|
+
const kind = t.isReturnStatement(stmt) ? 'return' : 'throw';
|
|
485
|
+
return { fall: null, escapes: [{ label: null, kind, assigned: entry }] };
|
|
486
|
+
}
|
|
487
|
+
if (t.isBlockStatement(stmt)) {
|
|
488
|
+
return analyzeStatementList(binding, stmt.body, entry);
|
|
489
|
+
}
|
|
490
|
+
if (t.isIfStatement(stmt)) {
|
|
491
|
+
const consequent = analyzeStatement(binding, stmt.consequent, entry);
|
|
492
|
+
const alternate = stmt.alternate
|
|
493
|
+
? analyzeStatement(binding, stmt.alternate, entry)
|
|
494
|
+
: { fall: entry, escapes: [] };
|
|
495
|
+
return {
|
|
496
|
+
fall: combineFall(consequent.fall, alternate.fall),
|
|
497
|
+
escapes: [...consequent.escapes, ...alternate.escapes],
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
if (t.isLabeledStatement(stmt)) {
|
|
501
|
+
const inner = analyzeStatement(binding, stmt.body, entry);
|
|
502
|
+
const label = stmt.label.name;
|
|
503
|
+
const remaining = [];
|
|
504
|
+
const capturedFall = [inner.fall];
|
|
505
|
+
for (const escape of inner.escapes) {
|
|
506
|
+
if (escape.kind === 'break' && escape.label === label) {
|
|
507
|
+
// A `break <thisLabel>` completes the labeled statement normally.
|
|
508
|
+
capturedFall.push(escape.assigned);
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
remaining.push(escape);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
const fall = capturedFall.reduce((acc, state) => combineFall(acc, state), null);
|
|
515
|
+
return { fall, escapes: remaining };
|
|
516
|
+
}
|
|
517
|
+
// These containers are not proven to assign on every path.
|
|
518
|
+
return { fall: false, escapes: [] };
|
|
519
|
+
}
|
|
520
|
+
// A non-falling branch defers to the other. Two falling branches must both assign.
|
|
521
|
+
function combineFall(a, b) {
|
|
522
|
+
if (a === null) {
|
|
523
|
+
return b;
|
|
524
|
+
}
|
|
525
|
+
if (b === null) {
|
|
526
|
+
return a;
|
|
527
|
+
}
|
|
528
|
+
return a && b;
|
|
529
|
+
}
|
|
530
|
+
// Apart from redirected writes, the span must run no user code or observations.
|
|
531
|
+
// Redirected right sides must also be pure.
|
|
532
|
+
function spanStatementIsInert(path, tempBinding) {
|
|
533
|
+
const { node } = path;
|
|
534
|
+
if (t.isEmptyStatement(node) || t.isBreakStatement(node) || t.isContinueStatement(node)) {
|
|
535
|
+
return true;
|
|
536
|
+
}
|
|
537
|
+
if (t.isBlockStatement(node)) {
|
|
538
|
+
return path.get('body').every((inner) => spanStatementIsInert(inner, tempBinding));
|
|
539
|
+
}
|
|
540
|
+
if (t.isLabeledStatement(node)) {
|
|
541
|
+
return spanStatementIsInert(path.get('body'), tempBinding);
|
|
542
|
+
}
|
|
543
|
+
if (t.isIfStatement(node)) {
|
|
544
|
+
const test = path.get('test');
|
|
545
|
+
if (!isPure(test.node, test.scope, test)) {
|
|
546
|
+
return false;
|
|
547
|
+
}
|
|
548
|
+
if (!spanStatementIsInert(path.get('consequent'), tempBinding)) {
|
|
549
|
+
return false;
|
|
550
|
+
}
|
|
551
|
+
const alternate = path.get('alternate');
|
|
552
|
+
return !alternate.node || spanStatementIsInert(alternate, tempBinding);
|
|
553
|
+
}
|
|
554
|
+
if (t.isExpressionStatement(node)) {
|
|
555
|
+
const expr = path.get('expression');
|
|
556
|
+
if (isTempWrite(expr.node, tempBinding)) {
|
|
557
|
+
const rhs = expr.get('right');
|
|
558
|
+
return isPure(rhs.node, rhs.scope, rhs);
|
|
559
|
+
}
|
|
560
|
+
return isPure(expr.node, expr.scope, expr);
|
|
561
|
+
}
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
function isTempWrite(node, tempBinding) {
|
|
565
|
+
return (t.isAssignmentExpression(node) &&
|
|
566
|
+
node.operator === '=' &&
|
|
567
|
+
t.isIdentifier(node.left) &&
|
|
568
|
+
node.left.name === tempBinding.identifier.name);
|
|
569
|
+
}
|
|
570
|
+
function isWriteToBinding(binding, stmt) {
|
|
571
|
+
if (!t.isExpressionStatement(stmt)) {
|
|
572
|
+
return false;
|
|
573
|
+
}
|
|
574
|
+
const expr = stmt.expression;
|
|
575
|
+
if (!t.isAssignmentExpression(expr) || expr.operator !== '=') {
|
|
576
|
+
return false;
|
|
577
|
+
}
|
|
578
|
+
if (!t.isIdentifier(expr.left) || expr.left.name !== binding.identifier.name) {
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
581
|
+
return true;
|
|
582
|
+
}
|
|
583
|
+
// A known single-write value can replace reads only after an unconditional write.
|
|
584
|
+
function tryPropagateConstant(binding, declaratorPath) {
|
|
585
|
+
const declStmt = declaratorPath.parentPath;
|
|
586
|
+
if (!declStmt || !declStmt.isVariableDeclaration()) {
|
|
587
|
+
return false;
|
|
588
|
+
}
|
|
589
|
+
const { init } = declaratorPath.node;
|
|
590
|
+
let writeRhs;
|
|
591
|
+
let writeRhsPath;
|
|
592
|
+
let writeStmtToRemove;
|
|
593
|
+
let evalScope;
|
|
594
|
+
let writeSourcePath;
|
|
595
|
+
if (init) {
|
|
596
|
+
if (binding.constantViolations.length > 0) {
|
|
597
|
+
return false;
|
|
598
|
+
}
|
|
599
|
+
if (!isUnconditionallyExecuted(declStmt, declaratorPath.scope)) {
|
|
600
|
+
return false;
|
|
601
|
+
}
|
|
602
|
+
writeRhs = init;
|
|
603
|
+
writeRhsPath = declaratorPath.get('init');
|
|
604
|
+
writeStmtToRemove = null;
|
|
605
|
+
evalScope = declaratorPath.scope;
|
|
606
|
+
writeSourcePath = declaratorPath;
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
if (binding.constantViolations.length !== 1) {
|
|
610
|
+
return false;
|
|
611
|
+
}
|
|
612
|
+
const violation = binding.constantViolations[0];
|
|
613
|
+
if (!violation.isAssignmentExpression() || violation.node.operator !== '=') {
|
|
614
|
+
return false;
|
|
615
|
+
}
|
|
616
|
+
if (!t.isIdentifier(violation.node.left) ||
|
|
617
|
+
violation.node.left.name !== binding.identifier.name) {
|
|
618
|
+
return false;
|
|
619
|
+
}
|
|
620
|
+
if (!violation.parentPath?.isExpressionStatement()) {
|
|
621
|
+
return false;
|
|
622
|
+
}
|
|
623
|
+
if (violation.parentPath.node.expression !== violation.node) {
|
|
624
|
+
return false;
|
|
625
|
+
}
|
|
626
|
+
if (violation.scope.getFunctionParent() !== declaratorPath.scope.getFunctionParent() ||
|
|
627
|
+
violation.scope.getProgramParent() !== declaratorPath.scope.getProgramParent()) {
|
|
628
|
+
return false;
|
|
629
|
+
}
|
|
630
|
+
if (!isUnconditionallyExecuted(violation.parentPath, declaratorPath.scope)) {
|
|
631
|
+
return false;
|
|
632
|
+
}
|
|
633
|
+
writeRhs = violation.node.right;
|
|
634
|
+
writeRhsPath = violation.get('right');
|
|
635
|
+
writeStmtToRemove = violation.parentPath;
|
|
636
|
+
evalScope = violation.scope;
|
|
637
|
+
writeSourcePath = violation;
|
|
638
|
+
}
|
|
639
|
+
const evaluation = evaluateConstant(writeRhs, evalScope, writeRhsPath);
|
|
640
|
+
if (!evaluation.known) {
|
|
641
|
+
return false;
|
|
642
|
+
}
|
|
643
|
+
const literal = valueToLiteral(evaluation.value);
|
|
644
|
+
if (!literal) {
|
|
645
|
+
return false;
|
|
646
|
+
}
|
|
647
|
+
for (const ref of binding.referencePaths) {
|
|
648
|
+
// A read before the write still sees `undefined`.
|
|
649
|
+
if (!isPathLive(ref) || !initializerReaches(writeSourcePath, ref)) {
|
|
650
|
+
return false;
|
|
651
|
+
}
|
|
652
|
+
// A nested function can run before the declarator despite document order.
|
|
653
|
+
if (readCrossesFunctionBoundary(binding, ref)) {
|
|
654
|
+
return false;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
for (const ref of binding.referencePaths) {
|
|
658
|
+
ref.replaceWith(t.cloneNode(literal));
|
|
659
|
+
}
|
|
660
|
+
if (writeStmtToRemove) {
|
|
661
|
+
writeStmtToRemove.remove();
|
|
662
|
+
}
|
|
663
|
+
if (declStmt.node.declarations.length === 1) {
|
|
664
|
+
declStmt.remove();
|
|
665
|
+
}
|
|
666
|
+
else {
|
|
667
|
+
declaratorPath.remove();
|
|
668
|
+
}
|
|
669
|
+
return true;
|
|
670
|
+
}
|
|
671
|
+
// The write must sit only inside unconditional blocks before reaching its
|
|
672
|
+
// function or program. Any other container may leave the binding `undefined`.
|
|
673
|
+
function isUnconditionallyExecuted(stmtPath, declScope) {
|
|
674
|
+
const declFnScope = declScope.getFunctionParent();
|
|
675
|
+
let current = stmtPath;
|
|
676
|
+
while (current.parentPath) {
|
|
677
|
+
const parent = current.parentPath;
|
|
678
|
+
if (parent.isFunction()) {
|
|
679
|
+
return parent.scope === declFnScope;
|
|
680
|
+
}
|
|
681
|
+
if (parent.isProgram()) {
|
|
682
|
+
return declFnScope === null || declFnScope === undefined;
|
|
683
|
+
}
|
|
684
|
+
if (!parent.isBlockStatement()) {
|
|
685
|
+
return false;
|
|
686
|
+
}
|
|
687
|
+
current = parent;
|
|
688
|
+
}
|
|
689
|
+
return false;
|
|
690
|
+
}
|
|
691
|
+
function ancestorIndexInContainer(path, container, stmtPaths) {
|
|
692
|
+
let current = path;
|
|
693
|
+
while (current.parentPath && current.parentPath !== container) {
|
|
694
|
+
current = current.parentPath;
|
|
695
|
+
}
|
|
696
|
+
if (!current.parentPath || current.parentPath !== container) {
|
|
697
|
+
return -1;
|
|
698
|
+
}
|
|
699
|
+
return stmtPaths.findIndex((stmt) => stmt.node === current.node);
|
|
700
|
+
}
|
|
701
|
+
// Stale paths may have no statement ancestor, where Babel's helper throws.
|
|
702
|
+
function safeGetStatementParent(path) {
|
|
703
|
+
let current = path;
|
|
704
|
+
while (current) {
|
|
705
|
+
if (current.isStatement()) {
|
|
706
|
+
return current;
|
|
707
|
+
}
|
|
708
|
+
if (!current.parentPath) {
|
|
709
|
+
return null;
|
|
710
|
+
}
|
|
711
|
+
current = current.parentPath;
|
|
712
|
+
}
|
|
713
|
+
return null;
|
|
714
|
+
}
|
|
715
|
+
function subtreeReadsBinding(stmtPath, target) {
|
|
716
|
+
let found = false;
|
|
717
|
+
const visitor = {
|
|
718
|
+
Identifier(path) {
|
|
719
|
+
if (path.node.name !== target.identifier.name) {
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
if (!path.isReferencedIdentifier()) {
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
if (path.scope.getBinding(path.node.name) !== target) {
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
found = true;
|
|
729
|
+
path.stop();
|
|
730
|
+
},
|
|
731
|
+
};
|
|
732
|
+
if (stmtPath.isReferencedIdentifier() &&
|
|
733
|
+
stmtPath.node.name === target.identifier.name &&
|
|
734
|
+
stmtPath.scope.getBinding(stmtPath.node.name) === target) {
|
|
735
|
+
return true;
|
|
736
|
+
}
|
|
737
|
+
stmtPath.traverse(visitor);
|
|
738
|
+
return found;
|
|
739
|
+
}
|
|
740
|
+
function isStatementMovementSafe(path) {
|
|
741
|
+
const { node } = path;
|
|
742
|
+
if (t.isEmptyStatement(node)) {
|
|
743
|
+
return true;
|
|
744
|
+
}
|
|
745
|
+
// Crossing a declaration can change name resolution or TDZ.
|
|
746
|
+
if (t.isExpressionStatement(node)) {
|
|
747
|
+
const expr = path.get('expression');
|
|
748
|
+
return isPure(expr.node, expr.scope, expr);
|
|
749
|
+
}
|
|
750
|
+
return false;
|
|
751
|
+
}
|
|
752
|
+
// Callers locate and index statements within this same path array.
|
|
753
|
+
function getContainerStatementPaths(container) {
|
|
754
|
+
if (container.isBlock()) {
|
|
755
|
+
return container.get('body');
|
|
756
|
+
}
|
|
757
|
+
if (container.isSwitchCase()) {
|
|
758
|
+
return container.get('consequent');
|
|
759
|
+
}
|
|
760
|
+
return null;
|
|
761
|
+
}
|
|
762
|
+
// Loop tests, updates, and iterator targets repeat each iteration.
|
|
763
|
+
function readIsInLoopHeader(readPath, readStmt) {
|
|
764
|
+
if (!readStmt.isForStatement() &&
|
|
765
|
+
!readStmt.isWhileStatement() &&
|
|
766
|
+
!readStmt.isDoWhileStatement() &&
|
|
767
|
+
!readStmt.isForInStatement() &&
|
|
768
|
+
!readStmt.isForOfStatement()) {
|
|
769
|
+
return false;
|
|
770
|
+
}
|
|
771
|
+
let ancestor = readPath;
|
|
772
|
+
while (ancestor && ancestor.parentPath !== readStmt) {
|
|
773
|
+
ancestor = ancestor.parentPath;
|
|
774
|
+
}
|
|
775
|
+
if (!ancestor) {
|
|
776
|
+
return false;
|
|
777
|
+
}
|
|
778
|
+
if (readStmt.isForStatement()) {
|
|
779
|
+
return ancestor.parentKey === 'test' || ancestor.parentKey === 'update';
|
|
780
|
+
}
|
|
781
|
+
if (readStmt.isForInStatement() || readStmt.isForOfStatement()) {
|
|
782
|
+
// The iterable runs once. Only the target repeats.
|
|
783
|
+
return ancestor.parentKey === 'left';
|
|
784
|
+
}
|
|
785
|
+
return ancestor.parentKey === 'test';
|
|
786
|
+
}
|
|
787
|
+
// Babel marks only the directly removed path. A live descendant must still reach
|
|
788
|
+
// the Program through parents that were not removed.
|
|
789
|
+
function isPathLive(path) {
|
|
790
|
+
if (path.removed || path.node == null) {
|
|
791
|
+
return false;
|
|
792
|
+
}
|
|
793
|
+
let current = path;
|
|
794
|
+
while (current) {
|
|
795
|
+
if (current.removed) {
|
|
796
|
+
return false;
|
|
797
|
+
}
|
|
798
|
+
if (current.isProgram()) {
|
|
799
|
+
return true;
|
|
800
|
+
}
|
|
801
|
+
current = current.parentPath;
|
|
802
|
+
}
|
|
803
|
+
return false;
|
|
804
|
+
}
|
|
805
|
+
//# sourceMappingURL=collapse-single-use-temps.js.map
|