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,387 @@
|
|
|
1
|
+
import type { NodePath, Visitor } from '@babel/traverse'
|
|
2
|
+
import * as t from '@babel/types'
|
|
3
|
+
import type { File } from '@babel/types'
|
|
4
|
+
|
|
5
|
+
import { isInsideWith } from 'src/utils/ast'
|
|
6
|
+
import { traverseForChanges } from 'src/utils/change-tracking'
|
|
7
|
+
import type { ChangeState } from 'src/utils/change-tracking'
|
|
8
|
+
import { isConditionalGate, reorderableToStatement } from 'src/utils/evaluation-order'
|
|
9
|
+
import { isStandaloneDeclaration } from 'src/utils/paths'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Lowers conditional expressions to `if` statements. Expression values use a
|
|
13
|
+
* temporary. Parameter defaults and repeating loop tests stay unchanged because
|
|
14
|
+
* hoisting would change when they run.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* // ◀️ before
|
|
18
|
+
* var y = a ? b : c;
|
|
19
|
+
* log(d ? e : f);
|
|
20
|
+
*
|
|
21
|
+
* // ▶️ after
|
|
22
|
+
* var y;
|
|
23
|
+
* if (a) { y = b; } else { y = c; }
|
|
24
|
+
* var _ternaryResult;
|
|
25
|
+
* if (d) { _ternaryResult = e; } else { _ternaryResult = f; }
|
|
26
|
+
* log(_ternaryResult);
|
|
27
|
+
*/
|
|
28
|
+
export function ternaryToIf(ast: File): boolean {
|
|
29
|
+
// Flatten sequences and logical gates before lifting ternaries. Replacements
|
|
30
|
+
// detach queued child paths, so every handler checks liveness.
|
|
31
|
+
const preparationChanged = traverseForChanges(ast, preparationVisitor)
|
|
32
|
+
const loweringChanged = traverseForChanges(ast, visitor)
|
|
33
|
+
return preparationChanged || loweringChanged
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const preparationVisitor: Visitor<ChangeState> = {
|
|
37
|
+
ConditionalExpression(path, state) {
|
|
38
|
+
if (!isPathLive(path) || isInsideWith(path)) {
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
const stmtPath = path.getStatementParent()
|
|
42
|
+
if (!stmtPath) {
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
if (hoistEnclosingSequence(path, stmtPath) || lowerEnclosingLogical(path, stmtPath)) {
|
|
46
|
+
state.changed = true
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const visitor: Visitor<ChangeState> = {
|
|
52
|
+
ConditionalExpression(path, state) {
|
|
53
|
+
// Statement replacement detaches queued siblings. `with` can also shadow the
|
|
54
|
+
// synthesized result temporary.
|
|
55
|
+
if (!isPathLive(path) || isInsideWith(path)) {
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
if (lowerConditional(path)) {
|
|
59
|
+
state.changed = true
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function lowerConditional(path: NodePath<t.ConditionalExpression>): boolean {
|
|
65
|
+
const parent = path.parentPath
|
|
66
|
+
if (!parent) {
|
|
67
|
+
return false
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const { test, consequent, alternate } = path.node
|
|
71
|
+
|
|
72
|
+
if (parent.isExpressionStatement() && parent.node.expression === path.node) {
|
|
73
|
+
parent.replaceWith(
|
|
74
|
+
t.ifStatement(
|
|
75
|
+
test,
|
|
76
|
+
t.blockStatement(seqToExpressionStatements(consequent)),
|
|
77
|
+
t.blockStatement(seqToExpressionStatements(alternate)),
|
|
78
|
+
),
|
|
79
|
+
)
|
|
80
|
+
// Skip the orphaned path after replacing its parent.
|
|
81
|
+
path.skip()
|
|
82
|
+
return true
|
|
83
|
+
}
|
|
84
|
+
if (parent.isReturnStatement() && parent.node.argument === path.node) {
|
|
85
|
+
parent.replaceWith(
|
|
86
|
+
t.ifStatement(
|
|
87
|
+
test,
|
|
88
|
+
t.blockStatement(seqWithFinal(consequent, t.returnStatement)),
|
|
89
|
+
t.blockStatement(seqWithFinal(alternate, t.returnStatement)),
|
|
90
|
+
),
|
|
91
|
+
)
|
|
92
|
+
path.skip()
|
|
93
|
+
return true
|
|
94
|
+
}
|
|
95
|
+
if (parent.isThrowStatement() && parent.node.argument === path.node) {
|
|
96
|
+
parent.replaceWith(
|
|
97
|
+
t.ifStatement(
|
|
98
|
+
test,
|
|
99
|
+
t.blockStatement(seqWithFinal(consequent, t.throwStatement)),
|
|
100
|
+
t.blockStatement(seqWithFinal(alternate, t.throwStatement)),
|
|
101
|
+
),
|
|
102
|
+
)
|
|
103
|
+
path.skip()
|
|
104
|
+
return true
|
|
105
|
+
}
|
|
106
|
+
if (
|
|
107
|
+
parent.isAssignmentExpression() &&
|
|
108
|
+
parent.node.operator === '=' &&
|
|
109
|
+
t.isIdentifier(parent.node.left) &&
|
|
110
|
+
parent.node.right === path.node &&
|
|
111
|
+
parent.parentPath?.isExpressionStatement() &&
|
|
112
|
+
parent.parentPath.node.expression === parent.node
|
|
113
|
+
) {
|
|
114
|
+
const target = parent.node.left
|
|
115
|
+
parent.parentPath.replaceWith(
|
|
116
|
+
t.ifStatement(
|
|
117
|
+
test,
|
|
118
|
+
t.blockStatement(seqWithAssignment(consequent, target)),
|
|
119
|
+
t.blockStatement(seqWithAssignment(alternate, target)),
|
|
120
|
+
),
|
|
121
|
+
)
|
|
122
|
+
path.skip()
|
|
123
|
+
return true
|
|
124
|
+
}
|
|
125
|
+
if (
|
|
126
|
+
parent.isVariableDeclarator() &&
|
|
127
|
+
t.isIdentifier(parent.node.id) &&
|
|
128
|
+
parent.node.init === path.node &&
|
|
129
|
+
parent.parentPath?.isVariableDeclaration() &&
|
|
130
|
+
// Splitting lexical declarations would end TDZ early, and bare `const` is
|
|
131
|
+
// invalid. Keep their initializer behind a result temp.
|
|
132
|
+
parent.parentPath.node.kind === 'var' &&
|
|
133
|
+
parent.parentPath.node.declarations.length === 1 &&
|
|
134
|
+
isStandaloneDeclaration(parent.parentPath)
|
|
135
|
+
) {
|
|
136
|
+
const declPath = parent.parentPath
|
|
137
|
+
const target = parent.node.id
|
|
138
|
+
declPath.replaceWithMultiple([
|
|
139
|
+
t.variableDeclaration(declPath.node.kind, [t.variableDeclarator(t.cloneNode(target))]),
|
|
140
|
+
t.ifStatement(
|
|
141
|
+
test,
|
|
142
|
+
t.blockStatement(seqWithAssignment(consequent, target)),
|
|
143
|
+
t.blockStatement(seqWithAssignment(alternate, target)),
|
|
144
|
+
),
|
|
145
|
+
])
|
|
146
|
+
path.skip()
|
|
147
|
+
return true
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const stmtPath = path.getStatementParent()
|
|
151
|
+
if (!stmtPath) {
|
|
152
|
+
return false
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (isInsideAssignmentPattern(path, stmtPath)) {
|
|
156
|
+
return false
|
|
157
|
+
}
|
|
158
|
+
if (isInsideLoopHeader(path, stmtPath)) {
|
|
159
|
+
return false
|
|
160
|
+
}
|
|
161
|
+
// Lifting requires no short-circuit gate or earlier effect in the statement.
|
|
162
|
+
if (!reorderableToStatement(path, stmtPath)) {
|
|
163
|
+
return false
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const { scope } = path
|
|
167
|
+
const tmpId = scope.generateUidIdentifier('ternaryResult')
|
|
168
|
+
scope.push({ id: t.cloneNode(tmpId) })
|
|
169
|
+
|
|
170
|
+
const ifStmt = t.ifStatement(
|
|
171
|
+
test,
|
|
172
|
+
t.blockStatement([
|
|
173
|
+
t.expressionStatement(t.assignmentExpression('=', t.cloneNode(tmpId), consequent)),
|
|
174
|
+
]),
|
|
175
|
+
t.blockStatement([
|
|
176
|
+
t.expressionStatement(t.assignmentExpression('=', t.cloneNode(tmpId), alternate)),
|
|
177
|
+
]),
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
// Detach the ternary before reusing its children in the new `if`.
|
|
181
|
+
path.replaceWith(t.cloneNode(tmpId))
|
|
182
|
+
stmtPath.insertBefore(ifStmt)
|
|
183
|
+
return true
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function isInsideAssignmentPattern(path: NodePath, stmtPath: NodePath): boolean {
|
|
187
|
+
let ancestor: NodePath | null = path.parentPath
|
|
188
|
+
while (ancestor && ancestor !== stmtPath) {
|
|
189
|
+
if (ancestor.isAssignmentPattern()) {
|
|
190
|
+
return true
|
|
191
|
+
}
|
|
192
|
+
ancestor = ancestor.parentPath
|
|
193
|
+
}
|
|
194
|
+
return false
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Split the innermost sequence so its ternary reaches a safe statement position.
|
|
198
|
+
function hoistEnclosingSequence(path: NodePath, stmtPath: NodePath): boolean {
|
|
199
|
+
const seqPath = findEnclosingSequence(path, stmtPath)
|
|
200
|
+
if (!seqPath) {
|
|
201
|
+
return false
|
|
202
|
+
}
|
|
203
|
+
const elements = seqPath.node.expressions
|
|
204
|
+
if (elements.length < 2) {
|
|
205
|
+
return false
|
|
206
|
+
}
|
|
207
|
+
const seqStmt = seqPath.getStatementParent()
|
|
208
|
+
if (!seqStmt) {
|
|
209
|
+
return false
|
|
210
|
+
}
|
|
211
|
+
// The sequence must run once at statement position without crossing a gate,
|
|
212
|
+
// case test, parameter default, loop header, or earlier effect.
|
|
213
|
+
if (
|
|
214
|
+
isInsideLoopHeader(seqPath, seqStmt) ||
|
|
215
|
+
isInsideAssignmentPattern(seqPath, seqStmt) ||
|
|
216
|
+
isInsideSwitchCaseTest(seqPath, seqStmt) ||
|
|
217
|
+
!reorderableToStatement(seqPath, seqStmt)
|
|
218
|
+
) {
|
|
219
|
+
return false
|
|
220
|
+
}
|
|
221
|
+
const preceding = elements.slice(0, -1).map((e) => t.expressionStatement(t.cloneNode(e)))
|
|
222
|
+
const last = t.cloneNode(elements.at(-1)!)
|
|
223
|
+
seqPath.replaceWith(last)
|
|
224
|
+
seqStmt.insertBefore(preceding)
|
|
225
|
+
path.skip()
|
|
226
|
+
return true
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Hoisting from a case test would run its work before the switch reaches that case.
|
|
230
|
+
function isInsideSwitchCaseTest(path: NodePath, stmtPath: NodePath): boolean {
|
|
231
|
+
let ancestor: NodePath | null = path.parentPath
|
|
232
|
+
while (ancestor && ancestor !== stmtPath) {
|
|
233
|
+
if (ancestor.isSwitchCase()) {
|
|
234
|
+
return true
|
|
235
|
+
}
|
|
236
|
+
ancestor = ancestor.parentPath
|
|
237
|
+
}
|
|
238
|
+
return false
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// A live path must still occupy every recorded parent key up to the Program.
|
|
242
|
+
function isPathLive(path: NodePath): boolean {
|
|
243
|
+
let cur: NodePath | null = path
|
|
244
|
+
while (cur) {
|
|
245
|
+
if (!cur.node) {
|
|
246
|
+
return false
|
|
247
|
+
}
|
|
248
|
+
const parent: NodePath | null = cur.parentPath
|
|
249
|
+
if (!parent) {
|
|
250
|
+
return cur.isProgram() || cur.isFile()
|
|
251
|
+
}
|
|
252
|
+
if (!parent.node) {
|
|
253
|
+
return false
|
|
254
|
+
}
|
|
255
|
+
const parentNode = parent.node as unknown as Record<string, unknown>
|
|
256
|
+
let held: unknown = parentNode[cur.key as string]
|
|
257
|
+
if (cur.listKey != null) {
|
|
258
|
+
const arr = parentNode[cur.listKey]
|
|
259
|
+
held = Array.isArray(arr) ? arr[cur.key as number] : arr
|
|
260
|
+
}
|
|
261
|
+
if (held !== cur.node) {
|
|
262
|
+
return false
|
|
263
|
+
}
|
|
264
|
+
cur = parent
|
|
265
|
+
}
|
|
266
|
+
return false
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function findEnclosingSequence(
|
|
270
|
+
path: NodePath,
|
|
271
|
+
stmtPath: NodePath,
|
|
272
|
+
): NodePath<t.SequenceExpression> | null {
|
|
273
|
+
let cur: NodePath = path
|
|
274
|
+
let parent = cur.parentPath
|
|
275
|
+
let found: NodePath<t.SequenceExpression> | null = null
|
|
276
|
+
while (parent && parent !== stmtPath) {
|
|
277
|
+
if (isConditionalGate(parent, cur)) {
|
|
278
|
+
return null
|
|
279
|
+
}
|
|
280
|
+
if (!found && parent.isSequenceExpression()) {
|
|
281
|
+
found = parent as NodePath<t.SequenceExpression>
|
|
282
|
+
}
|
|
283
|
+
cur = parent
|
|
284
|
+
parent = cur.parentPath
|
|
285
|
+
}
|
|
286
|
+
return found
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Convert an enclosing logical gate to a conditional so this pass remains
|
|
290
|
+
// self-contained and can lower it on the next visit.
|
|
291
|
+
function lowerEnclosingLogical(path: NodePath, stmtPath: NodePath): boolean {
|
|
292
|
+
const logical = findEnclosingShortCircuitLogical(path, stmtPath)
|
|
293
|
+
if (!logical || logical.removed || !logical.container) {
|
|
294
|
+
return false
|
|
295
|
+
}
|
|
296
|
+
const { node } = logical
|
|
297
|
+
const { left, right } = node
|
|
298
|
+
let test: t.Expression
|
|
299
|
+
let valueRef: t.Expression
|
|
300
|
+
if (t.isIdentifier(left) && logical.scope.hasBinding(left.name)) {
|
|
301
|
+
test = left
|
|
302
|
+
valueRef = t.cloneNode(left)
|
|
303
|
+
} else {
|
|
304
|
+
const tmp = logical.scope.generateUidIdentifierBasedOnNode(left)
|
|
305
|
+
logical.scope.push({ id: t.cloneNode(tmp) })
|
|
306
|
+
test = t.assignmentExpression('=', t.cloneNode(tmp), left)
|
|
307
|
+
valueRef = t.cloneNode(tmp)
|
|
308
|
+
}
|
|
309
|
+
const testExpr = node.operator === '??' ? t.binaryExpression('!=', test, t.nullLiteral()) : test
|
|
310
|
+
logical.replaceWith(
|
|
311
|
+
node.operator === '&&'
|
|
312
|
+
? t.conditionalExpression(testExpr, right, valueRef)
|
|
313
|
+
: t.conditionalExpression(testExpr, valueRef, right),
|
|
314
|
+
)
|
|
315
|
+
path.skip()
|
|
316
|
+
return true
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function findEnclosingShortCircuitLogical(
|
|
320
|
+
path: NodePath,
|
|
321
|
+
stmtPath: NodePath,
|
|
322
|
+
): NodePath<t.LogicalExpression> | null {
|
|
323
|
+
let cur: NodePath = path
|
|
324
|
+
let parent = cur.parentPath
|
|
325
|
+
let found: NodePath<t.LogicalExpression> | null = null
|
|
326
|
+
while (parent && parent !== stmtPath) {
|
|
327
|
+
if (!found) {
|
|
328
|
+
if (
|
|
329
|
+
parent.isConditionalExpression() &&
|
|
330
|
+
(parent.node.consequent === cur.node || parent.node.alternate === cur.node)
|
|
331
|
+
) {
|
|
332
|
+
return null
|
|
333
|
+
}
|
|
334
|
+
if (parent.isLogicalExpression() && parent.node.right === cur.node) {
|
|
335
|
+
found = parent as NodePath<t.LogicalExpression>
|
|
336
|
+
}
|
|
337
|
+
} else if (isConditionalGate(parent, cur)) {
|
|
338
|
+
return null
|
|
339
|
+
}
|
|
340
|
+
cur = parent
|
|
341
|
+
parent = cur.parentPath
|
|
342
|
+
}
|
|
343
|
+
return found
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Loop tests and updates repeat, while a `for` initializer runs once.
|
|
347
|
+
function isInsideLoopHeader(path: NodePath, stmtPath: NodePath): boolean {
|
|
348
|
+
let ancestor: NodePath | null = path
|
|
349
|
+
while (ancestor && ancestor.parentPath !== stmtPath) {
|
|
350
|
+
ancestor = ancestor.parentPath
|
|
351
|
+
}
|
|
352
|
+
if (!ancestor) {
|
|
353
|
+
return false
|
|
354
|
+
}
|
|
355
|
+
if (stmtPath.isWhileStatement() || stmtPath.isDoWhileStatement()) {
|
|
356
|
+
return ancestor.parentKey === 'test'
|
|
357
|
+
}
|
|
358
|
+
if (stmtPath.isForStatement()) {
|
|
359
|
+
return ancestor.parentKey === 'test' || ancestor.parentKey === 'update'
|
|
360
|
+
}
|
|
361
|
+
return false
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Split sequences so later lifts target the right statement.
|
|
365
|
+
function seqToExpressionStatements(expr: t.Expression): t.Statement[] {
|
|
366
|
+
if (t.isSequenceExpression(expr)) {
|
|
367
|
+
return expr.expressions.map((e) => t.expressionStatement(e))
|
|
368
|
+
}
|
|
369
|
+
return [t.expressionStatement(expr)]
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// In return and throw, only the final sequence element supplies the value.
|
|
373
|
+
function seqWithFinal(expr: t.Expression, build: (e: t.Expression) => t.Statement): t.Statement[] {
|
|
374
|
+
if (t.isSequenceExpression(expr)) {
|
|
375
|
+
const last = expr.expressions.at(-1)!
|
|
376
|
+
const init = expr.expressions.slice(0, -1).map((e) => t.expressionStatement(e))
|
|
377
|
+
return [...init, build(last)]
|
|
378
|
+
}
|
|
379
|
+
return [build(expr)]
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// In a bare assignment, only the final sequence element supplies the value.
|
|
383
|
+
function seqWithAssignment(expr: t.Expression, target: t.Identifier): t.Statement[] {
|
|
384
|
+
const build = (value: t.Expression): t.Statement =>
|
|
385
|
+
t.expressionStatement(t.assignmentExpression('=', t.cloneNode(target), value))
|
|
386
|
+
return seqWithFinal(expr, build)
|
|
387
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { Binding, NodePath, Visitor } from '@babel/traverse'
|
|
2
|
+
import * as t from '@babel/types'
|
|
3
|
+
import type { File } from '@babel/types'
|
|
4
|
+
|
|
5
|
+
import { isDirectEvalCall, isInsideWith } from 'src/utils/ast'
|
|
6
|
+
import { traverseForChanges } from 'src/utils/change-tracking'
|
|
7
|
+
import type { ChangeState } from 'src/utils/change-tracking'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Rewrites numeric updates as compound assignments for later expansion. Prefix
|
|
11
|
+
* results already match. Postfix forms require a position that discards the old
|
|
12
|
+
* value.
|
|
13
|
+
*
|
|
14
|
+
* BigInt differs because adding the number `1` throws. Only bindings proven to
|
|
15
|
+
* stay numeric are rewritten. Members and parameters stay unchanged.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* // ◀️ before
|
|
19
|
+
* let i = 0;
|
|
20
|
+
* i++;
|
|
21
|
+
* arr[++i] = 1;
|
|
22
|
+
* let n = 5n;
|
|
23
|
+
* n++;
|
|
24
|
+
*
|
|
25
|
+
* // ▶️ after
|
|
26
|
+
* let i = 0;
|
|
27
|
+
* i += 1;
|
|
28
|
+
* arr[i += 1] = 1;
|
|
29
|
+
* let n = 5n;
|
|
30
|
+
* n++;
|
|
31
|
+
*/
|
|
32
|
+
export function updateToAssignment(ast: File): boolean {
|
|
33
|
+
return traverseForChanges(ast, visitor)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const visitor: Visitor<ChangeState> = {
|
|
37
|
+
UpdateExpression(path, state) {
|
|
38
|
+
const { argument, prefix, operator } = path.node
|
|
39
|
+
if (!t.isIdentifier(argument)) {
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
if (!prefix && !valueIsDiscarded(path)) {
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
// `with` can retarget the name and defeat the numeric proof.
|
|
46
|
+
if (isInsideWith(path)) {
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
const binding = path.scope.getBinding(argument.name)
|
|
50
|
+
if (!isNumericBinding(binding)) {
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
// Direct eval anywhere in the binding's scope can change its type.
|
|
54
|
+
if (bindingScopeReachesDirectEval(binding!)) {
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
const compound = operator === '++' ? '+=' : '-='
|
|
58
|
+
path.replaceWith(t.assignmentExpression(compound, argument, t.numericLiteral(1)))
|
|
59
|
+
state.changed = true
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function valueIsDiscarded(path: NodePath<t.UpdateExpression>): boolean {
|
|
64
|
+
const { parentPath } = path
|
|
65
|
+
if (!parentPath) {
|
|
66
|
+
return false
|
|
67
|
+
}
|
|
68
|
+
if (parentPath.isExpressionStatement()) {
|
|
69
|
+
return true
|
|
70
|
+
}
|
|
71
|
+
if (parentPath.isForStatement() && parentPath.node.update === path.node) {
|
|
72
|
+
return true
|
|
73
|
+
}
|
|
74
|
+
if (parentPath.isSequenceExpression()) {
|
|
75
|
+
return parentPath.node.expressions.at(-1) !== path.node
|
|
76
|
+
}
|
|
77
|
+
return false
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// The initializer and every mutation must keep the binding numeric.
|
|
81
|
+
function isNumericBinding(binding: Binding | undefined): boolean {
|
|
82
|
+
if (!binding || (binding.kind !== 'var' && binding.kind !== 'let')) {
|
|
83
|
+
return false
|
|
84
|
+
}
|
|
85
|
+
const declarator = binding.path.node
|
|
86
|
+
if (!t.isVariableDeclarator(declarator) || !t.isNumericLiteral(declarator.init)) {
|
|
87
|
+
return false
|
|
88
|
+
}
|
|
89
|
+
// Babel misses the string writes from an Annex B `for-in` initializer.
|
|
90
|
+
if (isForInOfLoopVariable(binding)) {
|
|
91
|
+
return false
|
|
92
|
+
}
|
|
93
|
+
return binding.constantViolations.every((violation) => keepsNumeric(violation.node))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// A sibling or nested direct eval can still close over and reassign the binding.
|
|
97
|
+
function bindingScopeReachesDirectEval(binding: Binding): boolean {
|
|
98
|
+
let found = false
|
|
99
|
+
t.traverseFast(binding.scope.block, (node) => {
|
|
100
|
+
if (isDirectEvalCall(node)) {
|
|
101
|
+
found = true
|
|
102
|
+
}
|
|
103
|
+
return found ? t.traverseFast.stop : undefined
|
|
104
|
+
})
|
|
105
|
+
return found
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function isForInOfLoopVariable(binding: Binding): boolean {
|
|
109
|
+
const declaration = binding.path.parentPath
|
|
110
|
+
if (!declaration || !declaration.isVariableDeclaration()) {
|
|
111
|
+
return false
|
|
112
|
+
}
|
|
113
|
+
const loop = declaration.parentPath
|
|
114
|
+
return (
|
|
115
|
+
loop != null &&
|
|
116
|
+
(loop.isForInStatement() || loop.isForOfStatement()) &&
|
|
117
|
+
loop.node.left === declaration.node
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function keepsNumeric(node: t.Node): boolean {
|
|
122
|
+
if (t.isUpdateExpression(node)) {
|
|
123
|
+
return true
|
|
124
|
+
}
|
|
125
|
+
if (t.isVariableDeclarator(node)) {
|
|
126
|
+
return t.isNumericLiteral(node.init)
|
|
127
|
+
}
|
|
128
|
+
if (t.isAssignmentExpression(node)) {
|
|
129
|
+
return t.isNumericLiteral(node.right)
|
|
130
|
+
}
|
|
131
|
+
return false
|
|
132
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { Visitor } from '@babel/traverse'
|
|
2
|
+
import * as t from '@babel/types'
|
|
3
|
+
import type { File } from '@babel/types'
|
|
4
|
+
|
|
5
|
+
import { traverseForChanges } from 'src/utils/change-tracking'
|
|
6
|
+
import type { ChangeState } from 'src/utils/change-tracking'
|
|
7
|
+
import { isLiteralShaped } from 'src/utils/literal'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Moves a safe literal-shaped right operand to the left of a comparison.
|
|
11
|
+
* Relational operators are flipped after swapping.
|
|
12
|
+
*
|
|
13
|
+
* Unary operations over literals also count when their coercion cannot act.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* // ◀️ before
|
|
17
|
+
* if (x === 1) {}
|
|
18
|
+
* if (name !== "foo") {}
|
|
19
|
+
* if (count > 5) {}
|
|
20
|
+
* if (idx <= 10) {}
|
|
21
|
+
*
|
|
22
|
+
* // ▶️ after
|
|
23
|
+
* if (1 === x) {}
|
|
24
|
+
* if ("foo" !== name) {}
|
|
25
|
+
* if (5 < count) {}
|
|
26
|
+
* if (10 >= idx) {}
|
|
27
|
+
*/
|
|
28
|
+
export function yodifyConditions(ast: File): boolean {
|
|
29
|
+
return traverseForChanges(ast, visitor)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const visitor: Visitor<ChangeState> = {
|
|
33
|
+
BinaryExpression(path, state) {
|
|
34
|
+
const { node } = path
|
|
35
|
+
const op = node.operator
|
|
36
|
+
if (!isComparisonOp(op)) {
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
if (!t.isExpression(node.left)) {
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
if (isLiteralShaped(node.left)) {
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
// The moved right side must run no user code, including object coercion.
|
|
46
|
+
if (!swappingRightIsEffectFree(node.right)) {
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
// Relational comparison coerces left first, so a regexp operand cannot swap.
|
|
50
|
+
if (RELATIONAL_FLIP[op] !== undefined && containsRegExpLiteral(node.right)) {
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const { left } = node
|
|
55
|
+
const { right } = node
|
|
56
|
+
node.left = right
|
|
57
|
+
node.right = left
|
|
58
|
+
const flipped = RELATIONAL_FLIP[op]
|
|
59
|
+
if (flipped !== undefined) {
|
|
60
|
+
node.operator = flipped
|
|
61
|
+
}
|
|
62
|
+
state.changed = true
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const COMPARISON_OPS = new Set<string>(['==', '!=', '===', '!==', '<', '<=', '>', '>='])
|
|
67
|
+
|
|
68
|
+
function isComparisonOp(op: string): boolean {
|
|
69
|
+
return COMPARISON_OPS.has(op)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const RELATIONAL_FLIP: Record<string, t.BinaryExpression['operator']> = {
|
|
73
|
+
'<': '>',
|
|
74
|
+
'>': '<',
|
|
75
|
+
'<=': '>=',
|
|
76
|
+
'>=': '<=',
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Numeric unary over regexp invokes ToPrimitive and cannot move earlier.
|
|
80
|
+
function swappingRightIsEffectFree(node: t.Expression): boolean {
|
|
81
|
+
return isLiteralShaped(node) && !numericUnaryCoercesRegExp(node)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// `!` only propagates risk because ToBoolean never invokes object coercion.
|
|
85
|
+
function numericUnaryCoercesRegExp(node: t.Node): boolean {
|
|
86
|
+
if (!t.isUnaryExpression(node) || !t.isExpression(node.argument)) {
|
|
87
|
+
return false
|
|
88
|
+
}
|
|
89
|
+
if (node.operator === '!') {
|
|
90
|
+
return numericUnaryCoercesRegExp(node.argument)
|
|
91
|
+
}
|
|
92
|
+
return t.isRegExpLiteral(node.argument) || numericUnaryCoercesRegExp(node.argument)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function containsRegExpLiteral(node: t.Expression): boolean {
|
|
96
|
+
if (t.isRegExpLiteral(node)) {
|
|
97
|
+
return true
|
|
98
|
+
}
|
|
99
|
+
if (t.isUnaryExpression(node) && t.isExpression(node.argument)) {
|
|
100
|
+
return containsRegExpLiteral(node.argument)
|
|
101
|
+
}
|
|
102
|
+
return false
|
|
103
|
+
}
|