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,77 @@
|
|
|
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 { lowerLogicalWriteStatement } from 'src/utils/logical-lowering'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Rewrites `a ?? b` as a guarded assignment or `a != null ? a : b`. A
|
|
12
|
+
* side-effectful left side is cached. Loose inequality checks both nullish
|
|
13
|
+
* values in one comparison.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* // ◀️ before
|
|
17
|
+
* var x = a ?? b;
|
|
18
|
+
* f(get() ?? fallback);
|
|
19
|
+
*
|
|
20
|
+
* // ▶️ after
|
|
21
|
+
* var x = a;
|
|
22
|
+
* if (x == null) x = b;
|
|
23
|
+
* f((_t = get()) != null ? _t : fallback);
|
|
24
|
+
*/
|
|
25
|
+
export function nullishCoalescingToTernary(ast: File): boolean {
|
|
26
|
+
return traverseForChanges(ast, visitor)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const visitor: Visitor<ChangeState> = {
|
|
30
|
+
LogicalExpression(path, state) {
|
|
31
|
+
if (lowerNullishCoalescing(path)) {
|
|
32
|
+
state.changed = true
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function lowerNullishCoalescing(path: NodePath<t.LogicalExpression>): boolean {
|
|
38
|
+
const { node } = path
|
|
39
|
+
if (node.operator !== '??') {
|
|
40
|
+
return false
|
|
41
|
+
}
|
|
42
|
+
// `with` can turn a repeated name into a getter and shadow a cache temp.
|
|
43
|
+
if (isInsideWith(path)) {
|
|
44
|
+
return false
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (lowerLogicalWriteStatement(path, nullishFallbackTest)) {
|
|
48
|
+
return true
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const { scope } = path
|
|
52
|
+
let test: t.Expression
|
|
53
|
+
let consequent: t.Expression
|
|
54
|
+
|
|
55
|
+
if (t.isIdentifier(node.left) && scope.hasBinding(node.left.name)) {
|
|
56
|
+
test = node.left
|
|
57
|
+
consequent = t.cloneNode(node.left)
|
|
58
|
+
} else {
|
|
59
|
+
const tmp = scope.generateUidIdentifierBasedOnNode(node.left)
|
|
60
|
+
scope.push({ id: t.cloneNode(tmp) })
|
|
61
|
+
test = t.assignmentExpression('=', t.cloneNode(tmp), node.left)
|
|
62
|
+
consequent = t.cloneNode(tmp)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
path.replaceWith(
|
|
66
|
+
t.conditionalExpression(
|
|
67
|
+
t.binaryExpression('!=', test, t.nullLiteral()),
|
|
68
|
+
consequent,
|
|
69
|
+
node.right,
|
|
70
|
+
),
|
|
71
|
+
)
|
|
72
|
+
return true
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function nullishFallbackTest(target: t.Identifier): t.Expression {
|
|
76
|
+
return t.binaryExpression('==', target, t.nullLiteral())
|
|
77
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { NodePath, Visitor } from '@babel/traverse'
|
|
2
|
+
import * as t from '@babel/types'
|
|
3
|
+
import type { File } from '@babel/types'
|
|
4
|
+
|
|
5
|
+
import { traverseForChanges } from 'src/utils/change-tracking'
|
|
6
|
+
import type { ChangeState } from 'src/utils/change-tracking'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Rewrites numeric literals as strings coerced with unary plus. Under `+`, `-`,
|
|
10
|
+
* or `~`, the existing operator supplies that coercion. Other unary operators
|
|
11
|
+
* keep the wrapper because their string behavior differs.
|
|
12
|
+
*
|
|
13
|
+
* Preparation has already converted numeric object keys to strings.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* // ◀️ before
|
|
17
|
+
* var x = arr[42] + 0.5 * 1e3;
|
|
18
|
+
* var y = -5 * ~3;
|
|
19
|
+
*
|
|
20
|
+
* // ▶️ after
|
|
21
|
+
* var x = arr[+"42"] + +"0.5" * +"1000";
|
|
22
|
+
* var y = -"5" * ~"3";
|
|
23
|
+
*/
|
|
24
|
+
export function numbersToStrings(ast: File): boolean {
|
|
25
|
+
return traverseForChanges(ast, visitor)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const NUMERIC_UNARIES = new Set(['+', '-', '~'])
|
|
29
|
+
|
|
30
|
+
const visitor: Visitor<ChangeState> = {
|
|
31
|
+
NumericLiteral(path, state) {
|
|
32
|
+
// Non-computed keys are names, and class-field keys can still reach this pass.
|
|
33
|
+
if (isNonComputedKey(path)) {
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
const str = t.stringLiteral(String(path.node.value))
|
|
37
|
+
const { parent } = path
|
|
38
|
+
if (
|
|
39
|
+
t.isUnaryExpression(parent) &&
|
|
40
|
+
parent.argument === path.node &&
|
|
41
|
+
NUMERIC_UNARIES.has(parent.operator)
|
|
42
|
+
) {
|
|
43
|
+
path.replaceWith(str)
|
|
44
|
+
} else {
|
|
45
|
+
path.replaceWith(t.unaryExpression('+', str))
|
|
46
|
+
}
|
|
47
|
+
path.skip()
|
|
48
|
+
state.changed = true
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function isNonComputedKey(path: NodePath<t.NumericLiteral>): boolean {
|
|
53
|
+
const { parentPath } = path
|
|
54
|
+
if (!parentPath) {
|
|
55
|
+
return false
|
|
56
|
+
}
|
|
57
|
+
const parent = parentPath.node
|
|
58
|
+
return (
|
|
59
|
+
'key' in parent && parent.key === path.node && 'computed' in parent && parent.computed === false
|
|
60
|
+
)
|
|
61
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
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 { isProtoKey } 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 generator and async object methods as function-valued properties so
|
|
11
|
+
* later passes can lift and inline them.
|
|
12
|
+
*
|
|
13
|
+
* Plain methods stay concise because a function property would gain construction
|
|
14
|
+
* and `prototype`. Generator and async functions remain non-constructors. Any
|
|
15
|
+
* method using `super` also stays intact because a function has no HomeObject.
|
|
16
|
+
*
|
|
17
|
+
* Non-computed `__proto__` methods stay intact because the property form invokes
|
|
18
|
+
* ECMA-262 B.3.1 prototype-setting behavior. Computed keys create own properties
|
|
19
|
+
* and remain safe.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* // ◀️ before
|
|
23
|
+
* const o = {
|
|
24
|
+
* async *stream() {},
|
|
25
|
+
* plain() {},
|
|
26
|
+
* get x() {
|
|
27
|
+
* return 1;
|
|
28
|
+
* },
|
|
29
|
+
* };
|
|
30
|
+
*
|
|
31
|
+
* // ▶️ after
|
|
32
|
+
* const o = {
|
|
33
|
+
* stream: async function* () {},
|
|
34
|
+
* plain() {},
|
|
35
|
+
* get x() {
|
|
36
|
+
* return 1;
|
|
37
|
+
* },
|
|
38
|
+
* };
|
|
39
|
+
*/
|
|
40
|
+
export function objectMethodToProperty(ast: File): boolean {
|
|
41
|
+
return traverseForChanges(ast, visitor)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const visitor: Visitor<ChangeState> = {
|
|
45
|
+
ObjectMethod(path, state) {
|
|
46
|
+
const { node } = path
|
|
47
|
+
if (node.kind !== 'method' || (!node.generator && !node.async)) {
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
if (referencesSuper(path)) {
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
if (isNonComputedProtoKey(node)) {
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
const fn = t.functionExpression(null, node.params, node.body, node.generator, node.async)
|
|
57
|
+
path.replaceWith(t.objectProperty(node.key, fn, node.computed, false))
|
|
58
|
+
state.changed = true
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isNonComputedProtoKey(node: t.ObjectMethod): boolean {
|
|
63
|
+
return !node.computed && isProtoKey(node.key)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function referencesSuper(path: NodePath<t.ObjectMethod>): boolean {
|
|
67
|
+
const state = { found: false }
|
|
68
|
+
path.traverse(superVisitor, state)
|
|
69
|
+
return state.found
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const superVisitor: Visitor<{ found: boolean }> = {
|
|
73
|
+
Function(path, state) {
|
|
74
|
+
if (path.isArrowFunctionExpression()) {
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
// A nested method's computed key can still use the outer HomeObject.
|
|
78
|
+
if ((path.isObjectMethod() || path.isClassMethod()) && path.node.computed) {
|
|
79
|
+
path.get('key').traverse(superVisitor, state)
|
|
80
|
+
}
|
|
81
|
+
path.skip()
|
|
82
|
+
},
|
|
83
|
+
Super(_path, state) {
|
|
84
|
+
state.found = true
|
|
85
|
+
},
|
|
86
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
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
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Rewrites optional chains as nested ternaries. Side-effectful receivers are
|
|
11
|
+
* cached, and member calls retain their `this`. Optional deletion short-circuits
|
|
12
|
+
* to `true` as required by ECMA-262 13.5.1.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* // ◀️ before
|
|
16
|
+
* obj?.m(arg);
|
|
17
|
+
* delete obj?.x;
|
|
18
|
+
* a?.b?.c;
|
|
19
|
+
*
|
|
20
|
+
* // ▶️ after
|
|
21
|
+
* obj == null ? void 0 : (_m = obj.m).call(obj, arg);
|
|
22
|
+
* obj == null ? true : delete obj.x;
|
|
23
|
+
* a == null ? void 0 : (_b = a.b) == null ? void 0 : _b.c;
|
|
24
|
+
*/
|
|
25
|
+
export function optionalChainingToTernary(ast: File): boolean {
|
|
26
|
+
return traverseForChanges(ast, visitor)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const visitor: Visitor<ChangeState> = {
|
|
30
|
+
OptionalMemberExpression(path, state) {
|
|
31
|
+
if (lowerOptionalChain(path)) {
|
|
32
|
+
state.changed = true
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
OptionalCallExpression(path, state) {
|
|
36
|
+
if (lowerOptionalChain(path)) {
|
|
37
|
+
state.changed = true
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function lowerOptionalChain(path: OptionalPath): boolean {
|
|
43
|
+
// `with` can make a repeated receiver name invoke a getter twice.
|
|
44
|
+
if (isInsideWith(path)) {
|
|
45
|
+
return false
|
|
46
|
+
}
|
|
47
|
+
if (isInsideOptionalChain(path)) {
|
|
48
|
+
return false
|
|
49
|
+
}
|
|
50
|
+
// A tagged optional chain needs explicit parentheses to remain valid and keep
|
|
51
|
+
// the tag receiver.
|
|
52
|
+
if (isBareTaggedTemplateTag(path)) {
|
|
53
|
+
path.replaceWith(t.parenthesizedExpression(path.node))
|
|
54
|
+
path.skip()
|
|
55
|
+
return true
|
|
56
|
+
}
|
|
57
|
+
return transformChain(path)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Wrap a direct tagged-template parent once.
|
|
61
|
+
function isBareTaggedTemplateTag(path: OptionalPath): boolean {
|
|
62
|
+
const parent = path.parentPath
|
|
63
|
+
return parent != null && parent.isTaggedTemplateExpression() && parent.node.tag === path.node
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
type OptionalNode = t.OptionalMemberExpression | t.OptionalCallExpression
|
|
67
|
+
type OptionalPath = NodePath<OptionalNode>
|
|
68
|
+
|
|
69
|
+
function isInsideOptionalChain(path: NodePath): boolean {
|
|
70
|
+
const parent = path.parentPath
|
|
71
|
+
if (!parent) {
|
|
72
|
+
return false
|
|
73
|
+
}
|
|
74
|
+
if (parent.isOptionalMemberExpression()) {
|
|
75
|
+
return parent.node.object === path.node
|
|
76
|
+
}
|
|
77
|
+
if (parent.isOptionalCallExpression()) {
|
|
78
|
+
return parent.node.callee === path.node
|
|
79
|
+
}
|
|
80
|
+
return false
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Refuse member optional calls because `.call` is shadowable, `super` because its
|
|
84
|
+
// HomeObject cannot move, and callee or tag chains because ternaries lose their
|
|
85
|
+
// receiver. Check before mutating the chain.
|
|
86
|
+
function chainIsUnsafe(path: OptionalPath): boolean {
|
|
87
|
+
let node: t.Node = path.node
|
|
88
|
+
for (;;) {
|
|
89
|
+
if (t.isOptionalCallExpression(node)) {
|
|
90
|
+
const callee: t.Node = node.callee
|
|
91
|
+
if (node.optional && (t.isMemberExpression(callee) || t.isOptionalMemberExpression(callee))) {
|
|
92
|
+
return true
|
|
93
|
+
}
|
|
94
|
+
if (t.isSuper(callee)) {
|
|
95
|
+
return true
|
|
96
|
+
}
|
|
97
|
+
node = callee
|
|
98
|
+
} else if (t.isOptionalMemberExpression(node)) {
|
|
99
|
+
const object: t.Node = node.object
|
|
100
|
+
if (t.isSuper(object)) {
|
|
101
|
+
return true
|
|
102
|
+
}
|
|
103
|
+
node = object
|
|
104
|
+
} else {
|
|
105
|
+
break
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
let child: t.Node = path.node
|
|
109
|
+
let parent = path.parentPath
|
|
110
|
+
while (parent?.isParenthesizedExpression() && parent.node.expression === child) {
|
|
111
|
+
child = parent.node
|
|
112
|
+
parent = parent.parentPath
|
|
113
|
+
}
|
|
114
|
+
if (!parent) {
|
|
115
|
+
return false
|
|
116
|
+
}
|
|
117
|
+
if ((parent.isCallExpression() || parent.isNewExpression()) && parent.node.callee === child) {
|
|
118
|
+
return true
|
|
119
|
+
}
|
|
120
|
+
return parent.isTaggedTemplateExpression() && parent.node.tag === child
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function transformChain(path: OptionalPath): boolean {
|
|
124
|
+
if (chainIsUnsafe(path)) {
|
|
125
|
+
return false
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const optionals: (t.MemberExpression | t.CallExpression)[] = []
|
|
129
|
+
|
|
130
|
+
let cur: NodePath = path
|
|
131
|
+
while (cur.isOptionalMemberExpression() || cur.isOptionalCallExpression()) {
|
|
132
|
+
const { node } = cur
|
|
133
|
+
if (node.optional) {
|
|
134
|
+
optionals.push(node as unknown as t.MemberExpression | t.CallExpression)
|
|
135
|
+
}
|
|
136
|
+
// Demotion keeps the same fields and removes `optional`.
|
|
137
|
+
if (cur.isOptionalMemberExpression()) {
|
|
138
|
+
;(node as unknown as { type: string }).type = 'MemberExpression'
|
|
139
|
+
cur = cur.get('object') as NodePath
|
|
140
|
+
} else {
|
|
141
|
+
;(node as unknown as { type: string }).type = 'CallExpression'
|
|
142
|
+
cur = cur.get('callee') as NodePath
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (optionals.length === 0) {
|
|
147
|
+
return false
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const parent = path.parentPath
|
|
151
|
+
const isDelete =
|
|
152
|
+
parent !== null &&
|
|
153
|
+
parent.isUnaryExpression() &&
|
|
154
|
+
parent.node.operator === 'delete' &&
|
|
155
|
+
parent.node.argument === path.node
|
|
156
|
+
|
|
157
|
+
// `void 0` cannot be shadowed like `undefined`.
|
|
158
|
+
const shortCircuit: t.Expression = isDelete
|
|
159
|
+
? t.booleanLiteral(true)
|
|
160
|
+
: t.unaryExpression('void', t.numericLiteral(0))
|
|
161
|
+
|
|
162
|
+
let replacementPath: NodePath = path
|
|
163
|
+
|
|
164
|
+
for (let i = optionals.length - 1; i >= 0; i--) {
|
|
165
|
+
const node = optionals[i]!
|
|
166
|
+
const isCall = t.isCallExpression(node)
|
|
167
|
+
const chain: t.Expression | t.Super = isCall
|
|
168
|
+
? ((node as t.CallExpression).callee as t.Expression | t.Super)
|
|
169
|
+
: (node as t.MemberExpression).object
|
|
170
|
+
|
|
171
|
+
if (t.isSuper(chain)) {
|
|
172
|
+
return false
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
let check: t.Expression
|
|
176
|
+
|
|
177
|
+
if (path.scope.isStatic(chain)) {
|
|
178
|
+
check = chain
|
|
179
|
+
} else {
|
|
180
|
+
const tmp = path.scope.generateUidIdentifierBasedOnNode(chain)
|
|
181
|
+
path.scope.push({ id: t.cloneNode(tmp) })
|
|
182
|
+
check = t.assignmentExpression('=', t.cloneNode(tmp), chain)
|
|
183
|
+
if (isCall) {
|
|
184
|
+
;(node as t.CallExpression).callee = t.cloneNode(tmp)
|
|
185
|
+
} else {
|
|
186
|
+
;(node as t.MemberExpression).object = t.cloneNode(tmp)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// A cached member call needs its original receiver as `this`.
|
|
191
|
+
if (isCall && t.isMemberExpression(chain)) {
|
|
192
|
+
const callNode = node as t.CallExpression
|
|
193
|
+
const { object } = chain
|
|
194
|
+
if (!t.isSuper(object)) {
|
|
195
|
+
let context: t.Expression
|
|
196
|
+
const memoized = path.scope.maybeGenerateMemoised(object)
|
|
197
|
+
if (memoized) {
|
|
198
|
+
context = t.cloneNode(memoized)
|
|
199
|
+
chain.object = t.assignmentExpression('=', memoized, object)
|
|
200
|
+
} else {
|
|
201
|
+
context = t.cloneNode(object) as t.Expression
|
|
202
|
+
}
|
|
203
|
+
callNode.arguments.unshift(t.cloneNode(context))
|
|
204
|
+
callNode.callee = t.memberExpression(callNode.callee as t.Expression, t.identifier('call'))
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const condition = t.binaryExpression('==', check, t.nullLiteral())
|
|
209
|
+
|
|
210
|
+
let alternate = replacementPath.node as t.Expression
|
|
211
|
+
if (isDelete && i === optionals.length - 1) {
|
|
212
|
+
alternate = t.unaryExpression('delete', alternate)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const newTernary = t.conditionalExpression(condition, shortCircuit, alternate)
|
|
216
|
+
const replaced: NodePath<t.ConditionalExpression> = replacementPath.replaceWith(newTernary)[0]
|
|
217
|
+
replacementPath = replaced.get('alternate')
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (isDelete) {
|
|
221
|
+
// The ternary already contains deletion, so replace the parent `delete` too.
|
|
222
|
+
parent!.replaceWith(path.node)
|
|
223
|
+
}
|
|
224
|
+
return true
|
|
225
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
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 { enclosingScopeHasDirectEval, hasDuplicateParamNames } from 'src/utils/ast'
|
|
6
|
+
import { traverseForChanges } from 'src/utils/change-tracking'
|
|
7
|
+
import type { ChangeState } from 'src/utils/change-tracking'
|
|
8
|
+
import { isLiteralShaped } from 'src/utils/literal'
|
|
9
|
+
import { hasAnnexBFunctionAlias } from 'src/utils/paths'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Moves leading literal-valued `var` declarations into trailing default
|
|
13
|
+
* parameters. Arrows, rest parameters, name collisions, and functions with
|
|
14
|
+
* callers that could fill a new slot stay unchanged.
|
|
15
|
+
*
|
|
16
|
+
* Packing also requires unchanged `arguments`, directive, and hoisting behavior.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* // ◀️ before
|
|
20
|
+
* function run() {
|
|
21
|
+
* var x = 1;
|
|
22
|
+
* return x;
|
|
23
|
+
* }
|
|
24
|
+
*
|
|
25
|
+
* // ▶️ after
|
|
26
|
+
* function run(x = 1) {
|
|
27
|
+
* return x;
|
|
28
|
+
* }
|
|
29
|
+
*/
|
|
30
|
+
export function packDeclarationsIntoParameters(ast: File): boolean {
|
|
31
|
+
return traverseForChanges(ast, visitor)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const visitor: Visitor<ChangeState> = {
|
|
35
|
+
Function(path, state) {
|
|
36
|
+
if (packLeadingIntoParams(path)) {
|
|
37
|
+
state.changed = true
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function packLeadingIntoParams(path: NodePath<t.Function>): boolean {
|
|
43
|
+
// Accessors have fixed arity. Method calls are not analyzable, and arrows do
|
|
44
|
+
// not own `arguments`.
|
|
45
|
+
if (
|
|
46
|
+
path.isArrowFunctionExpression() ||
|
|
47
|
+
path.isObjectMethod() ||
|
|
48
|
+
path.isClassMethod() ||
|
|
49
|
+
path.isClassPrivateMethod()
|
|
50
|
+
) {
|
|
51
|
+
return false
|
|
52
|
+
}
|
|
53
|
+
// An Annex B alias splits call sites across bindings Babel does not connect.
|
|
54
|
+
if (hasAnnexBFunctionAlias(path)) {
|
|
55
|
+
return false
|
|
56
|
+
}
|
|
57
|
+
const { params } = path.node
|
|
58
|
+
if (params.some((param) => t.isRestElement(param))) {
|
|
59
|
+
return false
|
|
60
|
+
}
|
|
61
|
+
// Adding a default makes duplicate sloppy parameters a syntax error.
|
|
62
|
+
if (hasDuplicateParamNames(params)) {
|
|
63
|
+
return false
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const { body } = path.node
|
|
67
|
+
if (!t.isBlockStatement(body)) {
|
|
68
|
+
return false
|
|
69
|
+
}
|
|
70
|
+
// A strict directive forbids a non-simple parameter list.
|
|
71
|
+
if (body.directives.length > 0) {
|
|
72
|
+
return false
|
|
73
|
+
}
|
|
74
|
+
// Defaults make `arguments` unmapped.
|
|
75
|
+
if (referencesArguments(path)) {
|
|
76
|
+
return false
|
|
77
|
+
}
|
|
78
|
+
// Direct eval can inspect the newly unmapped `arguments` by name.
|
|
79
|
+
if (enclosingScopeHasDirectEval(path)) {
|
|
80
|
+
return false
|
|
81
|
+
}
|
|
82
|
+
// A caller with extra arguments could fill the new parameter slot.
|
|
83
|
+
if (!allCallSitesRespectArity(path, params.length)) {
|
|
84
|
+
return false
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const existingNames = collectParamNames(params)
|
|
88
|
+
// FunctionDeclarationInstantiation can replace a packed default with a hoisted
|
|
89
|
+
// same-named function.
|
|
90
|
+
const bodyFunctionNames = functionDeclarationNames(body)
|
|
91
|
+
|
|
92
|
+
let packed = false
|
|
93
|
+
while (body.body.length > 0) {
|
|
94
|
+
const stmt = body.body[0]!
|
|
95
|
+
if (!t.isVariableDeclaration(stmt) || stmt.kind !== 'var') {
|
|
96
|
+
break
|
|
97
|
+
}
|
|
98
|
+
if (stmt.declarations.length !== 1) {
|
|
99
|
+
break
|
|
100
|
+
}
|
|
101
|
+
const decl = stmt.declarations[0]!
|
|
102
|
+
if (!t.isIdentifier(decl.id)) {
|
|
103
|
+
break
|
|
104
|
+
}
|
|
105
|
+
if (!decl.init) {
|
|
106
|
+
break
|
|
107
|
+
}
|
|
108
|
+
if (!isLiteralShaped(decl.init)) {
|
|
109
|
+
break
|
|
110
|
+
}
|
|
111
|
+
if (existingNames.has(decl.id.name) || bodyFunctionNames.has(decl.id.name)) {
|
|
112
|
+
break
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
params.push(t.assignmentPattern(t.cloneNode(decl.id), decl.init))
|
|
116
|
+
existingNames.add(decl.id.name)
|
|
117
|
+
body.body.shift()
|
|
118
|
+
packed = true
|
|
119
|
+
}
|
|
120
|
+
return packed
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function referencesArguments(path: NodePath<t.Function>): boolean {
|
|
124
|
+
let found = false
|
|
125
|
+
const argumentsVisitor: Visitor = {
|
|
126
|
+
Function(inner) {
|
|
127
|
+
if (inner.isArrowFunctionExpression()) {
|
|
128
|
+
// Arrows read the enclosing `arguments`.
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
// Non-arrow bodies bind `arguments`, but computed method keys use the
|
|
132
|
+
// enclosing scope.
|
|
133
|
+
const key = methodComputedKey(inner)
|
|
134
|
+
if (key) {
|
|
135
|
+
if (key.isIdentifier() && key.node.name === 'arguments') {
|
|
136
|
+
found = true
|
|
137
|
+
}
|
|
138
|
+
key.traverse(argumentsVisitor)
|
|
139
|
+
}
|
|
140
|
+
inner.skip()
|
|
141
|
+
},
|
|
142
|
+
Identifier(inner) {
|
|
143
|
+
if (inner.node.name === 'arguments' && inner.isReferencedIdentifier()) {
|
|
144
|
+
found = true
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
}
|
|
148
|
+
path.traverse(argumentsVisitor)
|
|
149
|
+
return found
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function methodComputedKey(path: NodePath<t.Function>): NodePath | null {
|
|
153
|
+
if (
|
|
154
|
+
(path.isObjectMethod() || path.isClassMethod() || path.isClassPrivateMethod()) &&
|
|
155
|
+
path.node.computed
|
|
156
|
+
) {
|
|
157
|
+
return path.get('key') as NodePath
|
|
158
|
+
}
|
|
159
|
+
return null
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Every outer and recursive reference must be a direct call within the original
|
|
163
|
+
// arity and without spread.
|
|
164
|
+
function allCallSitesRespectArity(path: NodePath<t.Function>, arity: number): boolean {
|
|
165
|
+
const external = functionBinding(path)
|
|
166
|
+
if (!external || !external.constant || !bindingCallsRespectArity(external, arity)) {
|
|
167
|
+
return false
|
|
168
|
+
}
|
|
169
|
+
if (path.isFunctionExpression() && path.node.id) {
|
|
170
|
+
const inner = path.scope.getBinding(path.node.id.name)
|
|
171
|
+
if (inner && (!inner.constant || !bindingCallsRespectArity(inner, arity))) {
|
|
172
|
+
return false
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return true
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function bindingCallsRespectArity(binding: Binding, arity: number): boolean {
|
|
179
|
+
for (const ref of binding.referencePaths) {
|
|
180
|
+
const parent = ref.parentPath
|
|
181
|
+
if (
|
|
182
|
+
!parent ||
|
|
183
|
+
!(parent.isCallExpression() || parent.isOptionalCallExpression()) ||
|
|
184
|
+
parent.node.callee !== ref.node
|
|
185
|
+
) {
|
|
186
|
+
return false
|
|
187
|
+
}
|
|
188
|
+
const args = parent.node.arguments
|
|
189
|
+
if (args.length > arity || args.some((arg) => t.isSpreadElement(arg))) {
|
|
190
|
+
return false
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return true
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Annex B can hoist function names from nested blocks into this function scope.
|
|
197
|
+
function functionDeclarationNames(body: t.BlockStatement): Set<string> {
|
|
198
|
+
const names = new Set<string>()
|
|
199
|
+
t.traverseFast(body, (node) => {
|
|
200
|
+
if (t.isFunctionDeclaration(node) && node.id) {
|
|
201
|
+
names.add(node.id.name)
|
|
202
|
+
return t.traverseFast.skip
|
|
203
|
+
}
|
|
204
|
+
return node !== body && t.isFunction(node) ? t.traverseFast.skip : undefined
|
|
205
|
+
})
|
|
206
|
+
return names
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function functionBinding(path: NodePath<t.Function>): Binding | null {
|
|
210
|
+
if (path.isFunctionDeclaration() && path.node.id) {
|
|
211
|
+
return path.scope.parent?.getBinding(path.node.id.name) ?? null
|
|
212
|
+
}
|
|
213
|
+
const parent = path.parentPath
|
|
214
|
+
if (
|
|
215
|
+
parent?.isVariableDeclarator() &&
|
|
216
|
+
parent.node.init === path.node &&
|
|
217
|
+
t.isIdentifier(parent.node.id)
|
|
218
|
+
) {
|
|
219
|
+
return parent.scope.getBinding(parent.node.id.name) ?? null
|
|
220
|
+
}
|
|
221
|
+
return null
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Parameter names inside patterns and defaults also block duplicate packing.
|
|
225
|
+
function collectParamNames(params: t.Function['params']): Set<string> {
|
|
226
|
+
const names = new Set<string>()
|
|
227
|
+
for (const param of params) {
|
|
228
|
+
for (const name of Object.keys(t.getBindingIdentifiers(param))) {
|
|
229
|
+
names.add(name)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return names
|
|
233
|
+
}
|