webfuscator 1.3.0 → 1.3.1
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/README.md +67 -0
- package/dist/analysis/constant.d.ts.map +1 -1
- package/dist/analysis/constant.js +10 -26
- package/dist/analysis/constant.js.map +1 -1
- package/dist/analysis/inlineability.d.ts.map +1 -1
- package/dist/analysis/inlineability.js +4 -12
- package/dist/analysis/inlineability.js.map +1 -1
- package/dist/analysis/purity.d.ts +3 -3
- package/dist/analysis/purity.d.ts.map +1 -1
- package/dist/analysis/purity.js +28 -75
- package/dist/analysis/purity.js.map +1 -1
- package/dist/preparation/expand-destructuring.d.ts +2 -3
- package/dist/preparation/expand-destructuring.d.ts.map +1 -1
- package/dist/preparation/expand-destructuring.js +2 -8
- package/dist/preparation/expand-destructuring.js.map +1 -1
- package/dist/transforms/arguments-to-parameters.d.ts +5 -4
- package/dist/transforms/arguments-to-parameters.d.ts.map +1 -1
- package/dist/transforms/arguments-to-parameters.js +5 -4
- package/dist/transforms/arguments-to-parameters.js.map +1 -1
- package/dist/transforms/arrow-to-function.d.ts +4 -3
- package/dist/transforms/arrow-to-function.d.ts.map +1 -1
- package/dist/transforms/arrow-to-function.js +6 -8
- package/dist/transforms/arrow-to-function.js.map +1 -1
- package/dist/transforms/collapse-single-use-temps.d.ts +2 -1
- package/dist/transforms/collapse-single-use-temps.d.ts.map +1 -1
- package/dist/transforms/collapse-single-use-temps.js +12 -20
- package/dist/transforms/collapse-single-use-temps.js.map +1 -1
- package/dist/transforms/conceal-strings.d.ts +1 -2
- package/dist/transforms/conceal-strings.d.ts.map +1 -1
- package/dist/transforms/conceal-strings.js +2 -7
- package/dist/transforms/conceal-strings.js.map +1 -1
- package/dist/transforms/const-let-to-var.d.ts +4 -3
- package/dist/transforms/const-let-to-var.d.ts.map +1 -1
- package/dist/transforms/const-let-to-var.js +8 -94
- package/dist/transforms/const-let-to-var.js.map +1 -1
- package/dist/transforms/const-to-let.d.ts +1 -2
- package/dist/transforms/const-to-let.d.ts.map +1 -1
- package/dist/transforms/const-to-let.js +1 -6
- package/dist/transforms/const-to-let.js.map +1 -1
- package/dist/transforms/drop-console.d.ts.map +1 -1
- package/dist/transforms/drop-console.js +3 -5
- package/dist/transforms/drop-console.js.map +1 -1
- package/dist/transforms/expand-binary-assignment.d.ts.map +1 -1
- package/dist/transforms/expand-binary-assignment.js +0 -5
- package/dist/transforms/expand-binary-assignment.js.map +1 -1
- package/dist/transforms/expand-logical-assignment.d.ts.map +1 -1
- package/dist/transforms/expand-logical-assignment.js +0 -5
- package/dist/transforms/expand-logical-assignment.js.map +1 -1
- package/dist/transforms/extract-object-properties.js +5 -12
- package/dist/transforms/extract-object-properties.js.map +1 -1
- package/dist/transforms/fold-builtin-methods.js +3 -11
- package/dist/transforms/fold-builtin-methods.js.map +1 -1
- package/dist/transforms/fold-constants.js +2 -2
- package/dist/transforms/fold-constants.js.map +1 -1
- package/dist/transforms/function-declaration-to-expression.d.ts +5 -6
- package/dist/transforms/function-declaration-to-expression.d.ts.map +1 -1
- package/dist/transforms/function-declaration-to-expression.js +5 -6
- package/dist/transforms/function-declaration-to-expression.js.map +1 -1
- package/dist/transforms/hoist-variables.d.ts +1 -2
- package/dist/transforms/hoist-variables.d.ts.map +1 -1
- package/dist/transforms/hoist-variables.js +2 -11
- package/dist/transforms/hoist-variables.js.map +1 -1
- package/dist/transforms/inline-functions.js +11 -39
- package/dist/transforms/inline-functions.js.map +1 -1
- package/dist/transforms/logical-to-ternary.d.ts +1 -1
- package/dist/transforms/logical-to-ternary.js +5 -9
- package/dist/transforms/logical-to-ternary.js.map +1 -1
- package/dist/transforms/mangle-properties.d.ts +2 -5
- package/dist/transforms/mangle-properties.d.ts.map +1 -1
- package/dist/transforms/mangle-properties.js +2 -5
- package/dist/transforms/mangle-properties.js.map +1 -1
- package/dist/transforms/nullish-coalescing-to-ternary.d.ts +4 -5
- package/dist/transforms/nullish-coalescing-to-ternary.d.ts.map +1 -1
- package/dist/transforms/nullish-coalescing-to-ternary.js +7 -12
- package/dist/transforms/nullish-coalescing-to-ternary.js.map +1 -1
- package/dist/transforms/object-method-to-property.d.ts +1 -1
- package/dist/transforms/object-method-to-property.js +1 -1
- package/dist/transforms/optional-chaining-to-ternary.d.ts +4 -3
- package/dist/transforms/optional-chaining-to-ternary.d.ts.map +1 -1
- package/dist/transforms/optional-chaining-to-ternary.js +9 -9
- package/dist/transforms/optional-chaining-to-ternary.js.map +1 -1
- package/dist/transforms/pack-declarations-into-parameters.d.ts.map +1 -1
- package/dist/transforms/pack-declarations-into-parameters.js +4 -16
- package/dist/transforms/pack-declarations-into-parameters.js.map +1 -1
- package/dist/transforms/pack.d.ts +4 -11
- package/dist/transforms/pack.d.ts.map +1 -1
- package/dist/transforms/pack.js +4 -17
- package/dist/transforms/pack.js.map +1 -1
- package/dist/transforms/remove-anonymous-function-names.d.ts +2 -4
- package/dist/transforms/remove-anonymous-function-names.d.ts.map +1 -1
- package/dist/transforms/remove-anonymous-function-names.js +2 -40
- package/dist/transforms/remove-anonymous-function-names.js.map +1 -1
- package/dist/transforms/remove-unreachable-code.js +2 -21
- package/dist/transforms/remove-unreachable-code.js.map +1 -1
- package/dist/transforms/remove-unused-code.d.ts.map +1 -1
- package/dist/transforms/remove-unused-code.js +5 -10
- package/dist/transforms/remove-unused-code.js.map +1 -1
- package/dist/transforms/rename-identifiers.d.ts +3 -3
- package/dist/transforms/rename-identifiers.d.ts.map +1 -1
- package/dist/transforms/rename-identifiers.js +4 -20
- package/dist/transforms/rename-identifiers.js.map +1 -1
- package/dist/transforms/reorder-function-declarations.d.ts +1 -2
- package/dist/transforms/reorder-function-declarations.d.ts.map +1 -1
- package/dist/transforms/reorder-function-declarations.js +2 -4
- package/dist/transforms/reorder-function-declarations.js.map +1 -1
- package/dist/transforms/specials-to-strings.d.ts.map +1 -1
- package/dist/transforms/specials-to-strings.js +2 -4
- package/dist/transforms/specials-to-strings.js.map +1 -1
- package/dist/transforms/switch-to-if.js +5 -15
- package/dist/transforms/switch-to-if.js.map +1 -1
- package/dist/transforms/ternary-to-if.d.ts.map +1 -1
- package/dist/transforms/ternary-to-if.js +5 -9
- package/dist/transforms/ternary-to-if.js.map +1 -1
- package/dist/transforms/update-to-assignment.d.ts.map +1 -1
- package/dist/transforms/update-to-assignment.js +6 -8
- package/dist/transforms/update-to-assignment.js.map +1 -1
- package/dist/transforms/yodify-conditions.d.ts +3 -4
- package/dist/transforms/yodify-conditions.d.ts.map +1 -1
- package/dist/transforms/yodify-conditions.js +3 -4
- package/dist/transforms/yodify-conditions.js.map +1 -1
- package/dist/utils/ast.d.ts +0 -1
- package/dist/utils/ast.d.ts.map +1 -1
- package/dist/utils/ast.js +0 -26
- package/dist/utils/ast.js.map +1 -1
- package/dist/utils/evaluation-order.js +1 -1
- package/dist/utils/evaluation-order.js.map +1 -1
- package/dist/utils/logical-lowering.d.ts.map +1 -1
- package/dist/utils/logical-lowering.js +7 -9
- package/dist/utils/logical-lowering.js.map +1 -1
- package/dist/utils/nullish.d.ts +4 -0
- package/dist/utils/nullish.d.ts.map +1 -0
- package/dist/utils/nullish.js +8 -0
- package/dist/utils/nullish.js.map +1 -0
- package/dist/utils/paths.d.ts +0 -2
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +0 -28
- package/dist/utils/paths.js.map +1 -1
- package/package.json +16 -10
- package/src/analysis/constant.ts +12 -30
- package/src/analysis/inlineability.ts +4 -12
- package/src/analysis/purity.ts +29 -92
- package/src/preparation/expand-destructuring.ts +2 -10
- package/src/transforms/arguments-to-parameters.ts +5 -4
- package/src/transforms/arrow-to-function.ts +6 -8
- package/src/transforms/collapse-single-use-temps.ts +12 -23
- package/src/transforms/conceal-strings.ts +3 -8
- package/src/transforms/const-let-to-var.ts +9 -100
- package/src/transforms/const-to-let.ts +1 -6
- package/src/transforms/drop-console.ts +3 -5
- package/src/transforms/expand-binary-assignment.ts +0 -5
- package/src/transforms/expand-logical-assignment.ts +0 -5
- package/src/transforms/extract-object-properties.ts +5 -14
- package/src/transforms/fold-builtin-methods.ts +3 -12
- package/src/transforms/fold-constants.ts +2 -2
- package/src/transforms/function-declaration-to-expression.ts +5 -6
- package/src/transforms/hoist-variables.ts +2 -12
- package/src/transforms/inline-functions.ts +16 -46
- package/src/transforms/logical-to-ternary.ts +5 -10
- package/src/transforms/mangle-properties.ts +2 -5
- package/src/transforms/nullish-coalescing-to-ternary.ts +7 -13
- package/src/transforms/object-method-to-property.ts +1 -1
- package/src/transforms/optional-chaining-to-ternary.ts +9 -9
- package/src/transforms/pack-declarations-into-parameters.ts +10 -16
- package/src/transforms/pack.ts +4 -17
- package/src/transforms/remove-anonymous-function-names.ts +3 -46
- package/src/transforms/remove-unreachable-code.ts +2 -22
- package/src/transforms/remove-unused-code.ts +5 -10
- package/src/transforms/rename-identifiers.ts +4 -20
- package/src/transforms/reorder-function-declarations.ts +2 -4
- package/src/transforms/specials-to-strings.ts +2 -4
- package/src/transforms/switch-to-if.ts +5 -22
- package/src/transforms/ternary-to-if.ts +5 -9
- package/src/transforms/update-to-assignment.ts +6 -8
- package/src/transforms/yodify-conditions.ts +3 -4
- package/src/utils/ast.ts +0 -29
- package/src/utils/evaluation-order.ts +1 -1
- package/src/utils/logical-lowering.ts +7 -11
- package/src/utils/nullish.ts +23 -0
- package/src/utils/paths.ts +0 -32
|
@@ -2,15 +2,14 @@ import type { NodePath, Visitor } from '@babel/traverse'
|
|
|
2
2
|
import * as t from '@babel/types'
|
|
3
3
|
import type { File } from '@babel/types'
|
|
4
4
|
|
|
5
|
-
import { isInsideWith } from '../utils/ast'
|
|
6
5
|
import { traverseForChanges } from '../utils/change-tracking'
|
|
7
6
|
import type { ChangeState } from '../utils/change-tracking'
|
|
8
7
|
import { lowerLogicalWriteStatement } from '../utils/logical-lowering'
|
|
8
|
+
import { buildNonNullishCheck, buildNullishCheck } from '../utils/nullish'
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* Rewrites `a ?? b` as a guarded assignment or
|
|
12
|
-
* side-effectful left side is cached.
|
|
13
|
-
* values in one comparison.
|
|
11
|
+
* Rewrites `a ?? b` as a guarded assignment or strict null and undefined
|
|
12
|
+
* checks. A side-effectful left side is cached.
|
|
14
13
|
*
|
|
15
14
|
* @example
|
|
16
15
|
* // ◀️ before
|
|
@@ -19,8 +18,8 @@ import { lowerLogicalWriteStatement } from '../utils/logical-lowering'
|
|
|
19
18
|
*
|
|
20
19
|
* // ▶️ after
|
|
21
20
|
* var x = a;
|
|
22
|
-
* if (x
|
|
23
|
-
* f((_t = get())
|
|
21
|
+
* if (x === null ? true : x === void 0) x = b;
|
|
22
|
+
* f(((_t = get()) !== null ? _t !== void 0 : false) ? _t : fallback);
|
|
24
23
|
*/
|
|
25
24
|
export function nullishCoalescingToTernary(ast: File): boolean {
|
|
26
25
|
return traverseForChanges(ast, visitor)
|
|
@@ -39,11 +38,6 @@ function lowerNullishCoalescing(path: NodePath<t.LogicalExpression>): boolean {
|
|
|
39
38
|
if (node.operator !== '??') {
|
|
40
39
|
return false
|
|
41
40
|
}
|
|
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
41
|
if (lowerLogicalWriteStatement(path, nullishFallbackTest)) {
|
|
48
42
|
return true
|
|
49
43
|
}
|
|
@@ -64,7 +58,7 @@ function lowerNullishCoalescing(path: NodePath<t.LogicalExpression>): boolean {
|
|
|
64
58
|
|
|
65
59
|
path.replaceWith(
|
|
66
60
|
t.conditionalExpression(
|
|
67
|
-
|
|
61
|
+
buildNonNullishCheck(test, t.cloneNode(consequent)),
|
|
68
62
|
consequent,
|
|
69
63
|
node.right,
|
|
70
64
|
),
|
|
@@ -73,5 +67,5 @@ function lowerNullishCoalescing(path: NodePath<t.LogicalExpression>): boolean {
|
|
|
73
67
|
}
|
|
74
68
|
|
|
75
69
|
function nullishFallbackTest(target: t.Identifier): t.Expression {
|
|
76
|
-
return
|
|
70
|
+
return buildNullishCheck(target, t.cloneNode(target))
|
|
77
71
|
}
|
|
@@ -16,7 +16,7 @@ import type { ChangeState } from '../utils/change-tracking'
|
|
|
16
16
|
*
|
|
17
17
|
* Non-computed `__proto__` methods stay intact because the property form invokes
|
|
18
18
|
* ECMA-262 B.3.1 prototype-setting behavior. Computed keys create own properties
|
|
19
|
-
* and
|
|
19
|
+
* and can be rewritten.
|
|
20
20
|
*
|
|
21
21
|
* @example
|
|
22
22
|
* // ◀️ before
|
|
@@ -2,9 +2,9 @@ import type { NodePath, Visitor } from '@babel/traverse'
|
|
|
2
2
|
import * as t from '@babel/types'
|
|
3
3
|
import type { File } from '@babel/types'
|
|
4
4
|
|
|
5
|
-
import { isInsideWith } from '../utils/ast'
|
|
6
5
|
import { traverseForChanges } from '../utils/change-tracking'
|
|
7
6
|
import type { ChangeState } from '../utils/change-tracking'
|
|
7
|
+
import { buildNullishCheck } from '../utils/nullish'
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Rewrites optional chains as nested ternaries. Side-effectful receivers are
|
|
@@ -18,9 +18,10 @@ import type { ChangeState } from '../utils/change-tracking'
|
|
|
18
18
|
* a?.b?.c;
|
|
19
19
|
*
|
|
20
20
|
* // ▶️ after
|
|
21
|
-
* obj
|
|
22
|
-
* obj
|
|
23
|
-
* a
|
|
21
|
+
* (obj === null ? true : obj === void 0) ? void 0 : (_m = obj.m).call(obj, arg);
|
|
22
|
+
* (obj === null ? true : obj === void 0) ? true : delete obj.x;
|
|
23
|
+
* (a === null ? true : a === void 0) ? void 0 :
|
|
24
|
+
* ((_b = a.b) === null ? true : _b === void 0) ? void 0 : _b.c;
|
|
24
25
|
*/
|
|
25
26
|
export function optionalChainingToTernary(ast: File): boolean {
|
|
26
27
|
return traverseForChanges(ast, visitor)
|
|
@@ -40,10 +41,6 @@ const visitor: Visitor<ChangeState> = {
|
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
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
44
|
if (isInsideOptionalChain(path)) {
|
|
48
45
|
return false
|
|
49
46
|
}
|
|
@@ -173,16 +170,19 @@ function transformChain(path: OptionalPath): boolean {
|
|
|
173
170
|
}
|
|
174
171
|
|
|
175
172
|
let check: t.Expression
|
|
173
|
+
let repeatedCheck: t.Expression
|
|
176
174
|
|
|
177
175
|
if (path.scope.isStatic(chain)) {
|
|
178
176
|
// The receiver stays in the member expression, so the check needs its own
|
|
179
177
|
// node. One node under two parents is visited twice, and a later in-place
|
|
180
178
|
// rewrite such as renameIdentifiers then applies itself twice to it.
|
|
181
179
|
check = t.cloneNode(chain)
|
|
180
|
+
repeatedCheck = t.cloneNode(chain)
|
|
182
181
|
} else {
|
|
183
182
|
const tmp = path.scope.generateUidIdentifierBasedOnNode(chain)
|
|
184
183
|
path.scope.push({ id: t.cloneNode(tmp) })
|
|
185
184
|
check = t.assignmentExpression('=', t.cloneNode(tmp), chain)
|
|
185
|
+
repeatedCheck = t.cloneNode(tmp)
|
|
186
186
|
if (isCall) {
|
|
187
187
|
;(node as t.CallExpression).callee = t.cloneNode(tmp)
|
|
188
188
|
} else {
|
|
@@ -208,7 +208,7 @@ function transformChain(path: OptionalPath): boolean {
|
|
|
208
208
|
}
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
-
const condition =
|
|
211
|
+
const condition = buildNullishCheck(check, repeatedCheck)
|
|
212
212
|
|
|
213
213
|
let alternate = replacementPath.node as t.Expression
|
|
214
214
|
if (isDelete && i === optionals.length - 1) {
|
|
@@ -6,7 +6,6 @@ import { hasDuplicateParamNames } from '../utils/ast'
|
|
|
6
6
|
import { traverseForChanges } from '../utils/change-tracking'
|
|
7
7
|
import type { ChangeState } from '../utils/change-tracking'
|
|
8
8
|
import { isLiteralShaped } from '../utils/literal'
|
|
9
|
-
import { hasAnnexBFunctionAlias } from '../utils/paths'
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
11
|
* Moves leading literal-valued `var` declarations into trailing default
|
|
@@ -50,10 +49,6 @@ function packLeadingIntoParams(path: NodePath<t.Function>): boolean {
|
|
|
50
49
|
) {
|
|
51
50
|
return false
|
|
52
51
|
}
|
|
53
|
-
// An Annex B alias splits call sites across bindings Babel does not connect.
|
|
54
|
-
if (hasAnnexBFunctionAlias(path)) {
|
|
55
|
-
return false
|
|
56
|
-
}
|
|
57
52
|
const params = path.node.params
|
|
58
53
|
if (params.some((param) => t.isRestElement(param))) {
|
|
59
54
|
return false
|
|
@@ -159,7 +154,11 @@ function methodComputedKey(path: NodePath<t.Function>): NodePath | null {
|
|
|
159
154
|
// arity and without spread.
|
|
160
155
|
function allCallSitesRespectArity(path: NodePath<t.Function>, arity: number): boolean {
|
|
161
156
|
const external = functionBinding(path)
|
|
162
|
-
if (
|
|
157
|
+
if (
|
|
158
|
+
!external ||
|
|
159
|
+
(external.kind !== 'const' && !external.constant) ||
|
|
160
|
+
!bindingCallsRespectArity(external, arity)
|
|
161
|
+
) {
|
|
163
162
|
return false
|
|
164
163
|
}
|
|
165
164
|
if (path.isFunctionExpression() && path.node.id) {
|
|
@@ -189,17 +188,12 @@ function bindingCallsRespectArity(binding: Binding, arity: number): boolean {
|
|
|
189
188
|
return true
|
|
190
189
|
}
|
|
191
190
|
|
|
192
|
-
// Annex B can hoist function names from nested blocks into this function scope.
|
|
193
191
|
function functionDeclarationNames(body: t.BlockStatement): Set<string> {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
return node !== body && t.isFunction(node) ? t.traverseFast.skip : undefined
|
|
201
|
-
})
|
|
202
|
-
return names
|
|
192
|
+
return new Set(
|
|
193
|
+
body.body.flatMap((statement) =>
|
|
194
|
+
t.isFunctionDeclaration(statement) && statement.id ? [statement.id.name] : [],
|
|
195
|
+
),
|
|
196
|
+
)
|
|
203
197
|
}
|
|
204
198
|
|
|
205
199
|
function functionBinding(path: NodePath<t.Function>): Binding | null {
|
package/src/transforms/pack.ts
CHANGED
|
@@ -5,7 +5,6 @@ import * as t from '@babel/types'
|
|
|
5
5
|
import type { File } from '@babel/types'
|
|
6
6
|
|
|
7
7
|
import type { TransformContext } from '../options'
|
|
8
|
-
import { isInsideWith } from '../utils/ast'
|
|
9
8
|
import { isCalleeOrTagOf, isInStrictContext, referencesOrWritesVariable } from '../utils/paths'
|
|
10
9
|
import { mulberry32 } from '../utils/random'
|
|
11
10
|
import { StringGenerator } from '../utils/string-generator'
|
|
@@ -108,17 +107,10 @@ const STANDARD_GLOBALS: readonly string[] = [
|
|
|
108
107
|
]
|
|
109
108
|
|
|
110
109
|
/**
|
|
111
|
-
* Serializes the whole program
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
* none distinguishable from a global at build time, so free names are routed
|
|
116
|
-
* through a parameter object whose accessors run in the original top-level scope
|
|
117
|
-
* and resolve each name there. Imports remain above the call. Names inside
|
|
118
|
-
* `with` stay bare so the object still controls lookup. A Function body cannot
|
|
119
|
-
* contain exports, so pack throws when it finds one. Code that needs
|
|
120
|
-
* `import.meta`, top-level await, dynamic `import()`, or top-level `arguments`
|
|
121
|
-
* will not run after the move.
|
|
110
|
+
* Serializes the whole program and runs it through the `Function` constructor.
|
|
111
|
+
* Imports stay outside the generated function. Reads and writes to other free
|
|
112
|
+
* names go through accessors created in the original top-level scope. Export
|
|
113
|
+
* statements are rejected because they cannot appear in a `Function` body.
|
|
122
114
|
*
|
|
123
115
|
* @example
|
|
124
116
|
* // ◀️ before
|
|
@@ -233,11 +225,6 @@ function packProgram(programPath: NodePath<t.Program>, ctx: TransformContext): b
|
|
|
233
225
|
if (name === objectName || name === 'arguments') {
|
|
234
226
|
return false
|
|
235
227
|
}
|
|
236
|
-
// Inside `with`, a free name may resolve to a property of the with object at
|
|
237
|
-
// runtime, so routing it to the top-level accessor would change behavior.
|
|
238
|
-
if (isInsideWith(path)) {
|
|
239
|
-
return false
|
|
240
|
-
}
|
|
241
228
|
if (!referencesOrWritesVariable(path)) {
|
|
242
229
|
return false
|
|
243
230
|
}
|
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import * as t from '@babel/types'
|
|
1
|
+
import type { Visitor } from '@babel/traverse'
|
|
3
2
|
import type { File } from '@babel/types'
|
|
4
3
|
|
|
5
4
|
import { traverseForChanges } from '../utils/change-tracking'
|
|
6
5
|
import type { ChangeState } from '../utils/change-tracking'
|
|
7
6
|
|
|
8
7
|
/**
|
|
9
|
-
* Removes a function expression's name when
|
|
10
|
-
* and NamedEvaluation restores the same `.name`. Other positions could infer no
|
|
11
|
-
* name or a different one.
|
|
8
|
+
* Removes a function expression's name when its body does not reference it.
|
|
12
9
|
*
|
|
13
10
|
* @example
|
|
14
11
|
* // ◀️ before
|
|
@@ -17,7 +14,7 @@ import type { ChangeState } from '../utils/change-tracking'
|
|
|
17
14
|
*
|
|
18
15
|
* // ▶️ after
|
|
19
16
|
* var greet = function () {};
|
|
20
|
-
* var a = function
|
|
17
|
+
* var a = function () {};
|
|
21
18
|
*/
|
|
22
19
|
export function removeAnonymousFunctionNames(ast: File): boolean {
|
|
23
20
|
return traverseForChanges(ast, visitor)
|
|
@@ -38,47 +35,7 @@ const visitor: Visitor<ChangeState> = {
|
|
|
38
35
|
) {
|
|
39
36
|
return
|
|
40
37
|
}
|
|
41
|
-
if (inferredName(path) !== id.name) {
|
|
42
|
-
return
|
|
43
|
-
}
|
|
44
38
|
path.node.id = null
|
|
45
39
|
state.changed = true
|
|
46
40
|
},
|
|
47
41
|
}
|
|
48
|
-
|
|
49
|
-
// No inferred name means `.name` would become empty.
|
|
50
|
-
function inferredName(path: NodePath<t.FunctionExpression>): string | null {
|
|
51
|
-
const node = path.node
|
|
52
|
-
const parentPath = path.parentPath
|
|
53
|
-
if (!parentPath) {
|
|
54
|
-
return null
|
|
55
|
-
}
|
|
56
|
-
const parent = parentPath.node
|
|
57
|
-
if (t.isVariableDeclarator(parent) && parent.init === node && t.isIdentifier(parent.id)) {
|
|
58
|
-
return parent.id.name
|
|
59
|
-
}
|
|
60
|
-
if (
|
|
61
|
-
t.isAssignmentExpression(parent) &&
|
|
62
|
-
parent.operator === '=' &&
|
|
63
|
-
parent.right === node &&
|
|
64
|
-
t.isIdentifier(parent.left)
|
|
65
|
-
) {
|
|
66
|
-
return parent.left.name
|
|
67
|
-
}
|
|
68
|
-
if (t.isAssignmentPattern(parent) && parent.right === node && t.isIdentifier(parent.left)) {
|
|
69
|
-
return parent.left.name
|
|
70
|
-
}
|
|
71
|
-
if (
|
|
72
|
-
(t.isObjectProperty(parent) || t.isClassProperty(parent)) &&
|
|
73
|
-
parent.value === node &&
|
|
74
|
-
!parent.computed
|
|
75
|
-
) {
|
|
76
|
-
if (t.isIdentifier(parent.key)) {
|
|
77
|
-
return parent.key.name
|
|
78
|
-
}
|
|
79
|
-
if (t.isStringLiteral(parent.key)) {
|
|
80
|
-
return parent.key.value
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return null
|
|
84
|
-
}
|
|
@@ -69,41 +69,21 @@ function isCompletion(node: t.Node): boolean {
|
|
|
69
69
|
)
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
// Hoisted bindings and referenced lexical TDZ remain observable from reachable
|
|
73
|
-
// code. An unreferenced lexical declaration is inert.
|
|
74
72
|
function isSafeToRemoveAfterCompletion(path: NodePath<t.Statement>): boolean {
|
|
75
73
|
const node = path.node
|
|
76
74
|
if (t.isFunctionDeclaration(node) || (t.isVariableDeclaration(node) && node.kind === 'var')) {
|
|
77
75
|
return false
|
|
78
76
|
}
|
|
79
|
-
|
|
80
|
-
return !declaresReferencedBinding(path)
|
|
81
|
-
}
|
|
82
|
-
return !nestsHoistedDeclaration(node)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// A write before `let x` also observes its TDZ.
|
|
86
|
-
function declaresReferencedBinding(path: NodePath<t.Statement>): boolean {
|
|
87
|
-
for (const name of Object.keys(path.getBindingIdentifiers())) {
|
|
88
|
-
const binding = path.scope.getBinding(name)
|
|
89
|
-
if (binding && (binding.references > 0 || binding.constantViolations.length > 0)) {
|
|
90
|
-
return true
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return false
|
|
77
|
+
return !nestsHoistedVar(node)
|
|
94
78
|
}
|
|
95
79
|
|
|
96
|
-
function
|
|
80
|
+
function nestsHoistedVar(node: t.Node): boolean {
|
|
97
81
|
let found = false
|
|
98
82
|
t.traverseFast(node, (child) => {
|
|
99
83
|
if (found) {
|
|
100
84
|
return t.traverseFast.skip
|
|
101
85
|
}
|
|
102
86
|
if (child !== node && t.isFunction(child)) {
|
|
103
|
-
// Function declarations may add Annex B aliases. Function bodies do not.
|
|
104
|
-
if (t.isFunctionDeclaration(child)) {
|
|
105
|
-
found = true
|
|
106
|
-
}
|
|
107
87
|
return t.traverseFast.skip
|
|
108
88
|
}
|
|
109
89
|
if (t.isVariableDeclaration(child) && child.kind === 'var') {
|
|
@@ -5,7 +5,6 @@ import type { File } from '@babel/types'
|
|
|
5
5
|
|
|
6
6
|
import { isSideEffectFree } from '../analysis/purity'
|
|
7
7
|
import type { ChangeState } from '../utils/change-tracking'
|
|
8
|
-
import { hasAnnexBFunctionAlias } from '../utils/paths'
|
|
9
8
|
|
|
10
9
|
/**
|
|
11
10
|
* Removes unused declarations and expression statements that have no effects.
|
|
@@ -119,7 +118,7 @@ function tryRemoveDeclarator(path: NodePath<t.VariableDeclarator>): boolean {
|
|
|
119
118
|
}
|
|
120
119
|
|
|
121
120
|
const init = path.node.init
|
|
122
|
-
if (init && !isSideEffectFree(init, path.scope
|
|
121
|
+
if (init && !isSideEffectFree(init, path.scope)) {
|
|
123
122
|
return false
|
|
124
123
|
}
|
|
125
124
|
|
|
@@ -130,7 +129,7 @@ function tryRemoveDeclarator(path: NodePath<t.VariableDeclarator>): boolean {
|
|
|
130
129
|
if (binding.references > 0) {
|
|
131
130
|
return false
|
|
132
131
|
}
|
|
133
|
-
if (binding.constantViolations.length > 0) {
|
|
132
|
+
if (declaration.node.kind !== 'const' && binding.constantViolations.length > 0) {
|
|
134
133
|
return false
|
|
135
134
|
}
|
|
136
135
|
if (path.node.init) {
|
|
@@ -148,10 +147,6 @@ function tryRemoveFunctionDecl(path: NodePath<t.FunctionDeclaration>): boolean {
|
|
|
148
147
|
if (isExported(path)) {
|
|
149
148
|
return false
|
|
150
149
|
}
|
|
151
|
-
// Babel misses the sloppy Annex B alias of a block-level function.
|
|
152
|
-
if (hasAnnexBFunctionAlias(path)) {
|
|
153
|
-
return false
|
|
154
|
-
}
|
|
155
150
|
const id = path.node.id
|
|
156
151
|
if (!id) {
|
|
157
152
|
return false
|
|
@@ -183,7 +178,7 @@ function tryRemoveClassDecl(path: NodePath<t.ClassDeclaration>): boolean {
|
|
|
183
178
|
}
|
|
184
179
|
|
|
185
180
|
// `extends`, computed keys, static fields, and static blocks run on evaluation.
|
|
186
|
-
if (path.node.superClass && !isSideEffectFree(path.node.superClass, path.scope
|
|
181
|
+
if (path.node.superClass && !isSideEffectFree(path.node.superClass, path.scope)) {
|
|
187
182
|
return false
|
|
188
183
|
}
|
|
189
184
|
for (const member of path.node.body.body) {
|
|
@@ -198,7 +193,7 @@ function tryRemoveClassDecl(path: NodePath<t.ClassDeclaration>): boolean {
|
|
|
198
193
|
'computed' in member &&
|
|
199
194
|
member.computed &&
|
|
200
195
|
'key' in member &&
|
|
201
|
-
!isSideEffectFree(member.key, path.scope
|
|
196
|
+
!isSideEffectFree(member.key, path.scope)
|
|
202
197
|
) {
|
|
203
198
|
return false
|
|
204
199
|
}
|
|
@@ -224,7 +219,7 @@ function tryRemoveClassDecl(path: NodePath<t.ClassDeclaration>): boolean {
|
|
|
224
219
|
}
|
|
225
220
|
|
|
226
221
|
function tryRemoveExpressionStatement(path: NodePath<t.ExpressionStatement>): boolean {
|
|
227
|
-
if (!isSideEffectFree(path.node.expression, path.scope
|
|
222
|
+
if (!isSideEffectFree(path.node.expression, path.scope)) {
|
|
228
223
|
return false
|
|
229
224
|
}
|
|
230
225
|
dereferenceSubtree(path.get('expression') as NodePath)
|
|
@@ -4,15 +4,14 @@ import * as t from '@babel/types'
|
|
|
4
4
|
import type { File } from '@babel/types'
|
|
5
5
|
|
|
6
6
|
import type { TransformContext } from '../options'
|
|
7
|
-
import {
|
|
8
|
-
import { hasAnnexBFunctionAlias, referencesOrWritesVariable } from '../utils/paths'
|
|
7
|
+
import { referencesOrWritesVariable } from '../utils/paths'
|
|
9
8
|
import { mulberry32 } from '../utils/random'
|
|
10
9
|
import { StringGenerator } from '../utils/string-generator'
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
|
-
* Renames bindings and labels with
|
|
14
|
-
*
|
|
15
|
-
* sibling scopes.
|
|
12
|
+
* Renames bindings and labels with the configured string generator. Generated
|
|
13
|
+
* names do not capture unresolved names or bindings still used from an outer
|
|
14
|
+
* scope. Separate sibling scopes may reuse the same names.
|
|
16
15
|
*
|
|
17
16
|
* @example
|
|
18
17
|
* // ◀️ before
|
|
@@ -48,7 +47,6 @@ export function renameIdentifiers(ast: File, ctx: TransformContext): boolean {
|
|
|
48
47
|
const liveAncestors = new Map<Scope, Set<Binding>>()
|
|
49
48
|
// Reserve unbound names so generated bindings cannot capture them.
|
|
50
49
|
const freeNames = new Set<string>()
|
|
51
|
-
// A reference inside `with` pins its binding to the source name.
|
|
52
50
|
const unrenamable = new Set<Binding>()
|
|
53
51
|
// Reserve pinned source names globally, including before their binding is seen.
|
|
54
52
|
const pinnedNames = new Set<string>()
|
|
@@ -69,9 +67,6 @@ export function renameIdentifiers(ast: File, ctx: TransformContext): boolean {
|
|
|
69
67
|
}
|
|
70
68
|
return
|
|
71
69
|
}
|
|
72
|
-
if (isInsideWith(path)) {
|
|
73
|
-
pin(binding, path.node.name)
|
|
74
|
-
}
|
|
75
70
|
let scope: Scope | undefined = path.scope
|
|
76
71
|
while (scope && scope !== binding.scope) {
|
|
77
72
|
let live = liveAncestors.get(scope)
|
|
@@ -83,17 +78,6 @@ export function renameIdentifiers(ast: File, ctx: TransformContext): boolean {
|
|
|
83
78
|
scope = scope.parent
|
|
84
79
|
}
|
|
85
80
|
},
|
|
86
|
-
FunctionDeclaration(path) {
|
|
87
|
-
// ECMA-262 B.3.3 gives sloppy block functions an alias Babel does not model.
|
|
88
|
-
if (!hasAnnexBFunctionAlias(path)) {
|
|
89
|
-
return
|
|
90
|
-
}
|
|
91
|
-
const name = path.node.id?.name
|
|
92
|
-
const binding = name === undefined ? undefined : path.scope.getBinding(name)
|
|
93
|
-
if (binding && name !== undefined) {
|
|
94
|
-
pin(binding, name)
|
|
95
|
-
}
|
|
96
|
-
},
|
|
97
81
|
CatchClause(path) {
|
|
98
82
|
// Babel splits a catch parameter and same-named `var` that share one runtime
|
|
99
83
|
// variable. They must keep the same name.
|
|
@@ -2,14 +2,12 @@ import type { NodePath } from '@babel/traverse'
|
|
|
2
2
|
import type * as t from '@babel/types'
|
|
3
3
|
import type { File } from '@babel/types'
|
|
4
4
|
|
|
5
|
-
import { hasAnnexBFunctionAlias } from '../utils/paths'
|
|
6
5
|
import { rewriteStatementLists } from '../utils/statement-lists'
|
|
7
6
|
import type { StatementListContainer } from '../utils/statement-lists'
|
|
8
7
|
|
|
9
8
|
/**
|
|
10
9
|
* Moves hoisted function declarations to the end of each program, block, switch
|
|
11
|
-
* case, or static block. It keeps declaration order, including duplicate names
|
|
12
|
-
* and leaves sloppy Annex B block functions in place.
|
|
10
|
+
* case, or static block. It keeps declaration order, including duplicate names.
|
|
13
11
|
*
|
|
14
12
|
* If moving every declaration would turn a string expression into a directive,
|
|
15
13
|
* one declaration stays before the string. Direct exports stay in place.
|
|
@@ -76,5 +74,5 @@ function createsDirective(statements: readonly t.Statement[]): boolean {
|
|
|
76
74
|
}
|
|
77
75
|
|
|
78
76
|
function isMovable(child: NodePath<t.Statement>): boolean {
|
|
79
|
-
return child.isFunctionDeclaration() && child.node.id !== null
|
|
77
|
+
return child.isFunctionDeclaration() && child.node.id !== null
|
|
80
78
|
}
|
|
@@ -4,7 +4,6 @@ import * as t from '@babel/types'
|
|
|
4
4
|
import type { File } from '@babel/types'
|
|
5
5
|
|
|
6
6
|
import type { TransformContext } from '../options'
|
|
7
|
-
import { isInsideWith } from '../utils/ast'
|
|
8
7
|
import type { ChangeState } from '../utils/change-tracking'
|
|
9
8
|
import { mulberry32 } from '../utils/random'
|
|
10
9
|
import { StringGenerator } from '../utils/string-generator'
|
|
@@ -69,9 +68,8 @@ const visitor: Visitor<State> = {
|
|
|
69
68
|
if (path.scope.getBinding(name)) {
|
|
70
69
|
return
|
|
71
70
|
}
|
|
72
|
-
// A replacement cannot occupy a write target.
|
|
73
|
-
|
|
74
|
-
if (isWriteTarget(path) || isInsideWith(path)) {
|
|
71
|
+
// A replacement cannot occupy a write target.
|
|
72
|
+
if (isWriteTarget(path)) {
|
|
75
73
|
return
|
|
76
74
|
}
|
|
77
75
|
const str = t.stringLiteral(state.generator.next())
|
|
@@ -47,8 +47,7 @@ const visitor: Visitor<ChangeState> = {
|
|
|
47
47
|
|
|
48
48
|
function transformSwitch(path: NodePath<t.SwitchStatement>): boolean {
|
|
49
49
|
const node = path.node
|
|
50
|
-
// Separate `if` blocks cannot preserve cross-case lexical scope
|
|
51
|
-
// function hoisting.
|
|
50
|
+
// Separate `if` blocks cannot preserve cross-case lexical scope.
|
|
52
51
|
if (hasBlockScopedDeclaration(node)) {
|
|
53
52
|
return false
|
|
54
53
|
}
|
|
@@ -56,7 +55,7 @@ function transformSwitch(path: NodePath<t.SwitchStatement>): boolean {
|
|
|
56
55
|
lowerToIfElseChain(path)
|
|
57
56
|
return true
|
|
58
57
|
}
|
|
59
|
-
if (!labeledLoweringIsSafe(node, path.scope
|
|
58
|
+
if (!labeledLoweringIsSafe(node, path.scope)) {
|
|
60
59
|
return false
|
|
61
60
|
}
|
|
62
61
|
lowerToLabeledBlock(path)
|
|
@@ -69,8 +68,7 @@ function hasBlockScopedDeclaration(node: t.SwitchStatement): boolean {
|
|
|
69
68
|
if (
|
|
70
69
|
(t.isVariableDeclaration(stmt) && stmt.kind !== 'var') ||
|
|
71
70
|
t.isClassDeclaration(stmt) ||
|
|
72
|
-
t.isFunctionDeclaration(stmt)
|
|
73
|
-
isLabeledFunctionDeclaration(stmt)
|
|
71
|
+
t.isFunctionDeclaration(stmt)
|
|
74
72
|
) {
|
|
75
73
|
return true
|
|
76
74
|
}
|
|
@@ -79,29 +77,14 @@ function hasBlockScopedDeclaration(node: t.SwitchStatement): boolean {
|
|
|
79
77
|
return false
|
|
80
78
|
}
|
|
81
79
|
|
|
82
|
-
// Babel hides an Annex B.3.4 labeled function under LabeledStatement wrappers.
|
|
83
|
-
function isLabeledFunctionDeclaration(stmt: t.Statement): boolean {
|
|
84
|
-
let current: t.Statement = stmt
|
|
85
|
-
while (t.isLabeledStatement(current)) {
|
|
86
|
-
current = current.body
|
|
87
|
-
}
|
|
88
|
-
return t.isFunctionDeclaration(current)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
80
|
// A non-final default needs match results from later cases. Precomputing that
|
|
92
81
|
// result rereads tests, so every test must be pure in this shape.
|
|
93
|
-
function labeledLoweringIsSafe(
|
|
94
|
-
node: t.SwitchStatement,
|
|
95
|
-
scope: Scope,
|
|
96
|
-
referencePath: NodePath,
|
|
97
|
-
): boolean {
|
|
82
|
+
function labeledLoweringIsSafe(node: t.SwitchStatement, scope: Scope): boolean {
|
|
98
83
|
const defaultIndex = node.cases.findIndex((caseClause) => caseClause.test === null)
|
|
99
84
|
if (defaultIndex === -1 || defaultIndex === node.cases.length - 1) {
|
|
100
85
|
return true
|
|
101
86
|
}
|
|
102
|
-
return node.cases.every(
|
|
103
|
-
(caseClause) => caseClause.test == null || isPure(caseClause.test, scope, referencePath),
|
|
104
|
-
)
|
|
87
|
+
return node.cases.every((caseClause) => caseClause.test == null || isPure(caseClause.test, scope))
|
|
105
88
|
}
|
|
106
89
|
|
|
107
90
|
function canLowerToIfElseChain(node: t.SwitchStatement): boolean {
|
|
@@ -2,7 +2,6 @@ import type { NodePath, Visitor } from '@babel/traverse'
|
|
|
2
2
|
import * as t from '@babel/types'
|
|
3
3
|
import type { File } from '@babel/types'
|
|
4
4
|
|
|
5
|
-
import { isInsideWith } from '../utils/ast'
|
|
6
5
|
import { traverseForChanges } from '../utils/change-tracking'
|
|
7
6
|
import type { ChangeState } from '../utils/change-tracking'
|
|
8
7
|
import { isConditionalGate, reorderableToStatement } from '../utils/evaluation-order'
|
|
@@ -35,7 +34,7 @@ export function ternaryToIf(ast: File): boolean {
|
|
|
35
34
|
|
|
36
35
|
const preparationVisitor: Visitor<ChangeState> = {
|
|
37
36
|
ConditionalExpression(path, state) {
|
|
38
|
-
if (!isPathLive(path)
|
|
37
|
+
if (!isPathLive(path)) {
|
|
39
38
|
return
|
|
40
39
|
}
|
|
41
40
|
const stmtPath = path.getStatementParent()
|
|
@@ -50,9 +49,8 @@ const preparationVisitor: Visitor<ChangeState> = {
|
|
|
50
49
|
|
|
51
50
|
const visitor: Visitor<ChangeState> = {
|
|
52
51
|
ConditionalExpression(path, state) {
|
|
53
|
-
// Statement replacement detaches queued siblings.
|
|
54
|
-
|
|
55
|
-
if (!isPathLive(path) || isInsideWith(path)) {
|
|
52
|
+
// Statement replacement detaches queued siblings.
|
|
53
|
+
if (!isPathLive(path)) {
|
|
56
54
|
return
|
|
57
55
|
}
|
|
58
56
|
if (lowerConditional(path)) {
|
|
@@ -129,16 +127,14 @@ function lowerConditional(path: NodePath<t.ConditionalExpression>): boolean {
|
|
|
129
127
|
t.isIdentifier(parent.node.id) &&
|
|
130
128
|
parent.node.init === path.node &&
|
|
131
129
|
parent.parentPath?.isVariableDeclaration() &&
|
|
132
|
-
// Splitting lexical declarations would end TDZ early, and bare `const` is
|
|
133
|
-
// invalid. Keep their initializer behind a result temp.
|
|
134
|
-
parent.parentPath.node.kind === 'var' &&
|
|
135
130
|
parent.parentPath.node.declarations.length === 1 &&
|
|
136
131
|
isStandaloneDeclaration(parent.parentPath)
|
|
137
132
|
) {
|
|
138
133
|
const declPath = parent.parentPath
|
|
139
134
|
const target = parent.node.id
|
|
135
|
+
const declarationKind = declPath.node.kind === 'const' ? 'let' : declPath.node.kind
|
|
140
136
|
declPath.replaceWithMultiple([
|
|
141
|
-
t.variableDeclaration(
|
|
137
|
+
t.variableDeclaration(declarationKind, [t.variableDeclarator(t.cloneNode(target))]),
|
|
142
138
|
t.ifStatement(
|
|
143
139
|
test,
|
|
144
140
|
t.blockStatement(seqWithAssignment(consequent, target)),
|
|
@@ -2,7 +2,6 @@ import type { Binding, NodePath, Visitor } from '@babel/traverse'
|
|
|
2
2
|
import * as t from '@babel/types'
|
|
3
3
|
import type { File } from '@babel/types'
|
|
4
4
|
|
|
5
|
-
import { isInsideWith } from '../utils/ast'
|
|
6
5
|
import { traverseForChanges } from '../utils/change-tracking'
|
|
7
6
|
import type { ChangeState } from '../utils/change-tracking'
|
|
8
7
|
|
|
@@ -44,10 +43,6 @@ const visitor: Visitor<ChangeState> = {
|
|
|
44
43
|
if (!prefix && !valueIsDiscarded(path)) {
|
|
45
44
|
return
|
|
46
45
|
}
|
|
47
|
-
// `with` can retarget the name and defeat the numeric proof.
|
|
48
|
-
if (isInsideWith(path)) {
|
|
49
|
-
return
|
|
50
|
-
}
|
|
51
46
|
const binding = path.scope.getBinding(argument.name)
|
|
52
47
|
if (!isNumericBinding(binding)) {
|
|
53
48
|
return
|
|
@@ -75,19 +70,22 @@ function valueIsDiscarded(path: NodePath<t.UpdateExpression>): boolean {
|
|
|
75
70
|
return false
|
|
76
71
|
}
|
|
77
72
|
|
|
78
|
-
//
|
|
73
|
+
// Mutable bindings must stay numeric. A const write cannot change its value.
|
|
79
74
|
function isNumericBinding(binding: Binding | undefined): boolean {
|
|
80
|
-
if (!binding || (binding.kind !== 'var' && binding.kind !== 'let')) {
|
|
75
|
+
if (!binding || (binding.kind !== 'var' && binding.kind !== 'let' && binding.kind !== 'const')) {
|
|
81
76
|
return false
|
|
82
77
|
}
|
|
83
78
|
const declarator = binding.path.node
|
|
84
79
|
if (!t.isVariableDeclarator(declarator) || !t.isNumericLiteral(declarator.init)) {
|
|
85
80
|
return false
|
|
86
81
|
}
|
|
87
|
-
// Babel
|
|
82
|
+
// Babel does not record each value assigned by a `for-in` or `for-of` head.
|
|
88
83
|
if (isForInOfLoopVariable(binding)) {
|
|
89
84
|
return false
|
|
90
85
|
}
|
|
86
|
+
if (binding.kind === 'const') {
|
|
87
|
+
return true
|
|
88
|
+
}
|
|
91
89
|
return binding.constantViolations.every((violation) => keepsNumeric(violation.node))
|
|
92
90
|
}
|
|
93
91
|
|