webfuscator 1.2.1 → 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/README.md +3 -3
- package/dist/analysis/constant.d.ts.map +1 -1
- package/dist/analysis/constant.js +10 -27
- package/dist/analysis/constant.js.map +1 -1
- package/dist/analysis/inlineability.d.ts.map +1 -1
- package/dist/analysis/inlineability.js +5 -13
- 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/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/obfuscator.d.ts +2 -1
- package/dist/obfuscator.d.ts.map +1 -1
- package/dist/obfuscator.js +47 -12
- package/dist/obfuscator.js.map +1 -1
- package/dist/options.d.ts +17 -4
- package/dist/options.d.ts.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 -10
- 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 +56 -0
- package/dist/transforms/conceal-strings.d.ts.map +1 -0
- package/dist/transforms/conceal-strings.js +356 -0
- package/dist/transforms/conceal-strings.js.map +1 -0
- 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 -67
- package/dist/transforms/const-let-to-var.js.map +1 -1
- package/dist/transforms/const-to-let.d.ts +1 -3
- package/dist/transforms/const-to-let.d.ts.map +1 -1
- package/dist/transforms/const-to-let.js +1 -8
- 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.d.ts.map +1 -1
- package/dist/transforms/fold-builtin-methods.js +44 -14
- package/dist/transforms/fold-builtin-methods.js.map +1 -1
- package/dist/transforms/fold-constants.js +5 -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 +29 -0
- package/dist/transforms/hoist-variables.d.ts.map +1 -0
- package/dist/transforms/hoist-variables.js +215 -0
- package/dist/transforms/hoist-variables.js.map +1 -0
- package/dist/transforms/inline-functions.js +11 -40
- 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 +9 -9
- package/dist/transforms/mangle-properties.js.map +1 -1
- package/dist/transforms/merge-variable-declarations.d.ts +17 -0
- package/dist/transforms/merge-variable-declarations.d.ts.map +1 -0
- package/dist/transforms/merge-variable-declarations.js +68 -0
- package/dist/transforms/merge-variable-declarations.js.map +1 -0
- 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/numbers-to-strings.d.ts.map +1 -1
- package/dist/transforms/numbers-to-strings.js +1 -8
- package/dist/transforms/numbers-to-strings.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 +5 -21
- package/dist/transforms/pack-declarations-into-parameters.js.map +1 -1
- package/dist/transforms/pack.d.ts +4 -13
- package/dist/transforms/pack.d.ts.map +1 -1
- package/dist/transforms/pack.js +9 -29
- 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 +12 -49
- 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 +31 -35
- package/dist/transforms/rename-identifiers.js.map +1 -1
- package/dist/transforms/reorder-function-declarations.d.ts +24 -0
- package/dist/transforms/reorder-function-declarations.d.ts.map +1 -0
- package/dist/transforms/reorder-function-declarations.js +62 -0
- package/dist/transforms/reorder-function-declarations.js.map +1 -0
- package/dist/transforms/specials-to-strings.d.ts.map +1 -1
- package/dist/transforms/specials-to-strings.js +2 -3
- 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 -23
- 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 +1 -3
- package/dist/utils/ast.d.ts.map +1 -1
- package/dist/utils/ast.js +4 -60
- 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/literal.d.ts.map +1 -1
- package/dist/utils/literal.js +74 -2
- package/dist/utils/literal.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/dist/utils/statement-lists.d.ts +9 -0
- package/dist/utils/statement-lists.d.ts.map +1 -0
- package/dist/utils/statement-lists.js +27 -0
- package/dist/utils/statement-lists.js.map +1 -0
- package/package.json +6 -2
- package/src/analysis/constant.ts +12 -33
- package/src/analysis/inlineability.ts +5 -13
- package/src/analysis/purity.ts +29 -92
- package/src/index.ts +1 -0
- package/src/obfuscator.ts +51 -14
- package/src/options.ts +21 -3
- 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 -10
- package/src/transforms/collapse-single-use-temps.ts +12 -23
- package/src/transforms/conceal-strings.ts +525 -0
- package/src/transforms/const-let-to-var.ts +9 -72
- package/src/transforms/const-to-let.ts +1 -8
- 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 +63 -17
- package/src/transforms/fold-constants.ts +5 -2
- package/src/transforms/function-declaration-to-expression.ts +5 -6
- package/src/transforms/hoist-variables.ts +262 -0
- package/src/transforms/inline-functions.ts +16 -47
- package/src/transforms/logical-to-ternary.ts +5 -10
- package/src/transforms/mangle-properties.ts +9 -12
- package/src/transforms/merge-variable-declarations.ts +91 -0
- package/src/transforms/nullish-coalescing-to-ternary.ts +7 -13
- package/src/transforms/numbers-to-strings.ts +2 -12
- 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 +11 -21
- package/src/transforms/pack.ts +9 -30
- 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 +14 -74
- package/src/transforms/rename-identifiers.ts +44 -35
- package/src/transforms/reorder-function-declarations.ts +78 -0
- package/src/transforms/specials-to-strings.ts +2 -3
- 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 -24
- package/src/transforms/yodify-conditions.ts +3 -4
- package/src/utils/ast.ts +4 -69
- package/src/utils/evaluation-order.ts +1 -1
- package/src/utils/literal.ts +91 -2
- package/src/utils/logical-lowering.ts +7 -11
- package/src/utils/nullish.ts +23 -0
- package/src/utils/paths.ts +0 -32
- package/src/utils/statement-lists.ts +45 -0
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
import traverse from '@babel/traverse'
|
|
2
|
+
import type { NodePath, Visitor } from '@babel/traverse'
|
|
3
|
+
import * as t from '@babel/types'
|
|
4
|
+
import type { File } from '@babel/types'
|
|
5
|
+
|
|
6
|
+
import type { ConcealStringsOptions, TransformContext } from '../options'
|
|
7
|
+
import { isNonComputedKey } from '../utils/ast'
|
|
8
|
+
import type { ChangeState } from '../utils/change-tracking'
|
|
9
|
+
import { mulberry32, shuffleInPlace } from '../utils/random'
|
|
10
|
+
|
|
11
|
+
const RECORD_WIDTH = 4
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Replaces runtime string literals with calls to a generated decoder. Each vault
|
|
15
|
+
* stores masked positions in a shuffled UTF-16 pool. The decoder handles lone
|
|
16
|
+
* surrogates without relying on mutable built-ins. The default `regional` pool
|
|
17
|
+
* covers U+0000-00FF and every 256-unit page used by the program. `full` covers
|
|
18
|
+
* all 65,536 code units.
|
|
19
|
+
*
|
|
20
|
+
* `scope: 'program'` creates one vault. `scope: 'function'` creates a vault per
|
|
21
|
+
* function but repeats the program-wide pool in each one. Parameter defaults and
|
|
22
|
+
* computed keys use the enclosing vault because they run before the function
|
|
23
|
+
* body.
|
|
24
|
+
*
|
|
25
|
+
* Directives, module specifiers, import attributes, and non-computed keys are not
|
|
26
|
+
* runtime expressions, so they stay unchanged.
|
|
27
|
+
*
|
|
28
|
+
* This is obfuscation, not encryption. The output contains the decoder and all
|
|
29
|
+
* data needed to recover each string. `cover: 'used'` also leaves one-unit
|
|
30
|
+
* strings in the pool and may reproduce longer text by chance.
|
|
31
|
+
*
|
|
32
|
+
* The example uses `{ cover: 'used' }` to keep the pool short.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* // ◀️ before
|
|
36
|
+
* function greet(name) {
|
|
37
|
+
* return "Hello, " + name;
|
|
38
|
+
* }
|
|
39
|
+
*
|
|
40
|
+
* // ▶️ after
|
|
41
|
+
* const _stringPool = " lo,He",
|
|
42
|
+
* _stringPayload = [0, 4, 7, 2, 2, 6, 2],
|
|
43
|
+
* _stringMetadata = [0, 7, 4, 5],
|
|
44
|
+
* _stringCache = [null],
|
|
45
|
+
* _stringDecoder = function (_entry) {
|
|
46
|
+
* let _cached = _stringCache[_entry];
|
|
47
|
+
* if (_cached !== null) return _cached;
|
|
48
|
+
* let _result = "",
|
|
49
|
+
* _record = _entry * 4,
|
|
50
|
+
* _start = _stringMetadata[_record],
|
|
51
|
+
* _length = _stringMetadata[_record + 1],
|
|
52
|
+
* _stringKey = _stringMetadata[_record + 2],
|
|
53
|
+
* _stringStep = _stringMetadata[_record + 3];
|
|
54
|
+
* for (let _position = 0; _position < _length; _position++) {
|
|
55
|
+
* let _stringMask = _stringKey + _position * _stringStep & 7;
|
|
56
|
+
* _result += _stringPool[_stringPayload[_start + _position] ^ _stringMask];
|
|
57
|
+
* }
|
|
58
|
+
* _stringCache[_entry] = _result;
|
|
59
|
+
* return _result;
|
|
60
|
+
* };
|
|
61
|
+
* function greet(name) {
|
|
62
|
+
* return _stringDecoder(0) + name;
|
|
63
|
+
* }
|
|
64
|
+
*/
|
|
65
|
+
export function concealStrings(ast: File, ctx: TransformContext): boolean {
|
|
66
|
+
const options = resolveOptions(ctx.concealStrings)
|
|
67
|
+
const rng = mulberry32(ctx.seed)
|
|
68
|
+
const state: State = { changed: false, groups: new Map(), options }
|
|
69
|
+
|
|
70
|
+
if (options.scope === 'function') {
|
|
71
|
+
normalizeConciseArrows(ast, options)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
traverse(ast, collectVisitor, undefined, state)
|
|
75
|
+
if (state.groups.size === 0) {
|
|
76
|
+
return false
|
|
77
|
+
}
|
|
78
|
+
const pool = buildSharedPool(state.groups.values(), options.cover, rng)
|
|
79
|
+
for (const group of state.groups.values()) {
|
|
80
|
+
concealGroup(group, options, pool, rng)
|
|
81
|
+
state.changed = true
|
|
82
|
+
}
|
|
83
|
+
return state.changed
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
interface ResolvedOptions {
|
|
87
|
+
cache: boolean
|
|
88
|
+
cover: 'full' | 'regional' | 'used'
|
|
89
|
+
deduplicate: boolean
|
|
90
|
+
scope: 'function' | 'program'
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
type VaultOwner = t.Program | t.Function
|
|
94
|
+
type VaultOwnerPath = NodePath<VaultOwner>
|
|
95
|
+
|
|
96
|
+
interface Occurrence {
|
|
97
|
+
path: NodePath<t.StringLiteral>
|
|
98
|
+
value: string
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
interface VaultGroup {
|
|
102
|
+
occurrences: Occurrence[]
|
|
103
|
+
owner: VaultOwnerPath
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
interface State extends ChangeState {
|
|
107
|
+
groups: Map<VaultOwner, VaultGroup>
|
|
108
|
+
options: ResolvedOptions
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
interface Entry {
|
|
112
|
+
occurrences: Occurrence[]
|
|
113
|
+
value: string
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
interface VaultData {
|
|
117
|
+
indexMask: number
|
|
118
|
+
metadata: number[]
|
|
119
|
+
payload: number[]
|
|
120
|
+
pool: string
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
interface SharedPool {
|
|
124
|
+
indexMask: number
|
|
125
|
+
positions: ReadonlyMap<string, number>
|
|
126
|
+
value: string
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
interface VaultIdentifiers {
|
|
130
|
+
cache: t.Identifier | null
|
|
131
|
+
decoder: t.Identifier
|
|
132
|
+
metadata: t.Identifier
|
|
133
|
+
payload: t.Identifier
|
|
134
|
+
pool: t.Identifier
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const collectVisitor: Visitor<State> = {
|
|
138
|
+
StringLiteral(path, state) {
|
|
139
|
+
if (!isRuntimeString(path)) {
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
const owner = findVaultOwner(path, state.options.scope)
|
|
143
|
+
if (!owner) {
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
let group = state.groups.get(owner.node)
|
|
147
|
+
if (!group) {
|
|
148
|
+
group = { occurrences: [], owner }
|
|
149
|
+
state.groups.set(owner.node, group)
|
|
150
|
+
}
|
|
151
|
+
group.occurrences.push({ path, value: path.node.value })
|
|
152
|
+
},
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function resolveOptions(options: ConcealStringsOptions | undefined): ResolvedOptions {
|
|
156
|
+
const cache = options?.cache ?? true
|
|
157
|
+
const cover = options?.cover ?? 'regional'
|
|
158
|
+
const deduplicate = options?.deduplicate ?? true
|
|
159
|
+
const scope = options?.scope ?? 'program'
|
|
160
|
+
if (typeof cache !== 'boolean') {
|
|
161
|
+
throw new TypeError('concealStrings cache must be a boolean')
|
|
162
|
+
}
|
|
163
|
+
if (cover !== 'full' && cover !== 'regional' && cover !== 'used') {
|
|
164
|
+
throw new TypeError("concealStrings cover must be 'full', 'regional', or 'used'")
|
|
165
|
+
}
|
|
166
|
+
if (typeof deduplicate !== 'boolean') {
|
|
167
|
+
throw new TypeError('concealStrings deduplicate must be a boolean')
|
|
168
|
+
}
|
|
169
|
+
if (scope !== 'function' && scope !== 'program') {
|
|
170
|
+
throw new TypeError("concealStrings scope must be 'function' or 'program'")
|
|
171
|
+
}
|
|
172
|
+
return { cache, cover, deduplicate, scope }
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function normalizeConciseArrows(ast: File, options: ResolvedOptions): void {
|
|
176
|
+
const marked = new WeakSet<t.ArrowFunctionExpression>()
|
|
177
|
+
traverse(ast, {
|
|
178
|
+
StringLiteral(path) {
|
|
179
|
+
if (!isRuntimeString(path)) {
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
const owner = findVaultOwner(path, options.scope)
|
|
183
|
+
if (owner?.isArrowFunctionExpression() && !t.isBlockStatement(owner.node.body)) {
|
|
184
|
+
marked.add(owner.node)
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
ArrowFunctionExpression: {
|
|
188
|
+
exit(path) {
|
|
189
|
+
if (!marked.has(path.node) || t.isBlockStatement(path.node.body)) {
|
|
190
|
+
return
|
|
191
|
+
}
|
|
192
|
+
const expression = path.node.body
|
|
193
|
+
path.get('body').replaceWith(t.blockStatement([t.returnStatement(expression)]))
|
|
194
|
+
path.node.expression = false
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function findVaultOwner(
|
|
201
|
+
path: NodePath<t.StringLiteral>,
|
|
202
|
+
scope: ResolvedOptions['scope'],
|
|
203
|
+
): VaultOwnerPath | null {
|
|
204
|
+
let child: NodePath = path
|
|
205
|
+
let current: NodePath | null = path.parentPath
|
|
206
|
+
while (current) {
|
|
207
|
+
if (current.isFunction() && child.key === 'body' && scope === 'function') {
|
|
208
|
+
return current as VaultOwnerPath
|
|
209
|
+
}
|
|
210
|
+
if (current.isProgram()) {
|
|
211
|
+
return current
|
|
212
|
+
}
|
|
213
|
+
child = current
|
|
214
|
+
current = current.parentPath
|
|
215
|
+
}
|
|
216
|
+
return null
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function isRuntimeString(path: NodePath<t.StringLiteral>): boolean {
|
|
220
|
+
const parent = path.parent
|
|
221
|
+
const node = path.node
|
|
222
|
+
if (
|
|
223
|
+
(t.isImportDeclaration(parent) && parent.source === node) ||
|
|
224
|
+
(t.isExportNamedDeclaration(parent) && parent.source === node) ||
|
|
225
|
+
(t.isExportAllDeclaration(parent) && parent.source === node) ||
|
|
226
|
+
t.isImportAttribute(parent) ||
|
|
227
|
+
(t.isImportSpecifier(parent) && parent.imported === node) ||
|
|
228
|
+
(t.isExportSpecifier(parent) && parent.exported === node) ||
|
|
229
|
+
(t.isExportNamespaceSpecifier(parent) && parent.exported === node)
|
|
230
|
+
) {
|
|
231
|
+
return false
|
|
232
|
+
}
|
|
233
|
+
return !isNonComputedKey(path)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function concealGroup(
|
|
237
|
+
group: VaultGroup,
|
|
238
|
+
options: ResolvedOptions,
|
|
239
|
+
pool: SharedPool,
|
|
240
|
+
rng: () => number,
|
|
241
|
+
): void {
|
|
242
|
+
const entries = makeEntries(group.occurrences, options.deduplicate)
|
|
243
|
+
shuffleInPlace(entries, rng)
|
|
244
|
+
const data = encodeEntries(entries, pool, rng)
|
|
245
|
+
const identifiers = insertVault(statementBody(group.owner), entries.length, data, options.cache)
|
|
246
|
+
|
|
247
|
+
for (let index = 0; index < entries.length; index++) {
|
|
248
|
+
for (const occurrence of entries[index]!.occurrences) {
|
|
249
|
+
occurrence.path.replaceWith(
|
|
250
|
+
t.callExpression(t.identifier(identifiers.decoder.name), [t.numericLiteral(index)]),
|
|
251
|
+
)
|
|
252
|
+
occurrence.path.skip()
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function makeEntries(occurrences: Occurrence[], deduplicate: boolean): Entry[] {
|
|
258
|
+
if (!deduplicate) {
|
|
259
|
+
return occurrences.map((occurrence) => ({ occurrences: [occurrence], value: occurrence.value }))
|
|
260
|
+
}
|
|
261
|
+
const byValue = new Map<string, Entry>()
|
|
262
|
+
for (const occurrence of occurrences) {
|
|
263
|
+
let entry = byValue.get(occurrence.value)
|
|
264
|
+
if (!entry) {
|
|
265
|
+
entry = { occurrences: [], value: occurrence.value }
|
|
266
|
+
byValue.set(occurrence.value, entry)
|
|
267
|
+
}
|
|
268
|
+
entry.occurrences.push(occurrence)
|
|
269
|
+
}
|
|
270
|
+
return [...byValue.values()]
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function buildSharedPool(
|
|
274
|
+
groups: Iterable<VaultGroup>,
|
|
275
|
+
cover: ResolvedOptions['cover'],
|
|
276
|
+
rng: () => number,
|
|
277
|
+
): SharedPool {
|
|
278
|
+
const usedUnits = new Set<string>()
|
|
279
|
+
for (const group of groups) {
|
|
280
|
+
for (const occurrence of group.occurrences) {
|
|
281
|
+
let index = 0
|
|
282
|
+
while (index < occurrence.value.length) {
|
|
283
|
+
usedUnits.add(occurrence.value[index]!)
|
|
284
|
+
index++
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const poolUnits = makePoolUnits(usedUnits, cover)
|
|
290
|
+
shuffleInPlace(poolUnits, rng)
|
|
291
|
+
return {
|
|
292
|
+
indexMask: coveringBitMask(poolUnits.length),
|
|
293
|
+
positions: indexPoolPositions(poolUnits),
|
|
294
|
+
value: poolUnits.join(''),
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function encodeEntries(entries: Entry[], pool: SharedPool, rng: () => number): VaultData {
|
|
299
|
+
const metadata: number[] = []
|
|
300
|
+
const payload: number[] = []
|
|
301
|
+
|
|
302
|
+
for (const entry of entries) {
|
|
303
|
+
const key = randomInteger(pool.indexMask + 1, rng)
|
|
304
|
+
const step = randomOddInteger(pool.indexMask, rng)
|
|
305
|
+
metadata.push(payload.length, entry.value.length, key, step)
|
|
306
|
+
for (let position = 0; position < entry.value.length; position++) {
|
|
307
|
+
const truePosition = pool.positions.get(entry.value[position]!)!
|
|
308
|
+
payload.push(truePosition ^ maskPosition(position, key, step, pool.indexMask))
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return { indexMask: pool.indexMask, metadata, payload, pool: pool.value }
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function makePoolUnits(usedUnits: ReadonlySet<string>, cover: ResolvedOptions['cover']): string[] {
|
|
315
|
+
return cover === 'used' ? [...usedUnits] : coveredPageUnits(usedUnits, cover)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function coveredPageUnits(
|
|
319
|
+
usedUnits: ReadonlySet<string>,
|
|
320
|
+
cover: Exclude<ResolvedOptions['cover'], 'used'>,
|
|
321
|
+
): string[] {
|
|
322
|
+
const pages = new Set<number>()
|
|
323
|
+
if (cover === 'full') {
|
|
324
|
+
for (let page = 0; page < 0x01_00; page++) {
|
|
325
|
+
pages.add(page)
|
|
326
|
+
}
|
|
327
|
+
} else {
|
|
328
|
+
pages.add(0)
|
|
329
|
+
for (const unit of usedUnits) {
|
|
330
|
+
pages.add(unit.codePointAt(0)! >>> 8)
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const units: string[] = []
|
|
335
|
+
for (const page of pages) {
|
|
336
|
+
const start = page << 8
|
|
337
|
+
for (let offset = 0; offset < 0x01_00; offset++) {
|
|
338
|
+
units.push(String.fromCodePoint(start + offset))
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return units
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function indexPoolPositions(pool: readonly string[]): Map<string, number> {
|
|
345
|
+
const positions = new Map<string, number>()
|
|
346
|
+
for (let index = 0; index < pool.length; index++) {
|
|
347
|
+
positions.set(pool[index]!, index)
|
|
348
|
+
}
|
|
349
|
+
return positions
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function coveringBitMask(length: number): number {
|
|
353
|
+
let mask = 0
|
|
354
|
+
while (mask < length - 1) {
|
|
355
|
+
mask = (mask << 1) | 1
|
|
356
|
+
}
|
|
357
|
+
return mask
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function randomInteger(limit: number, rng: () => number): number {
|
|
361
|
+
return limit === 0 ? 0 : Math.floor(rng() * limit)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function randomOddInteger(mask: number, rng: () => number): number {
|
|
365
|
+
return mask === 0 ? 0 : randomInteger((mask + 1) / 2, rng) * 2 + 1
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function maskPosition(position: number, key: number, step: number, indexMask: number): number {
|
|
369
|
+
return (key + position * step) & indexMask
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function statementBody(owner: VaultOwnerPath): NodePath<t.Program | t.BlockStatement> {
|
|
373
|
+
if (owner.isProgram()) {
|
|
374
|
+
return owner
|
|
375
|
+
}
|
|
376
|
+
const body = owner.get('body') as NodePath
|
|
377
|
+
if (!body.isBlockStatement()) {
|
|
378
|
+
throw new Error('concealStrings expected a normalized function body')
|
|
379
|
+
}
|
|
380
|
+
return body
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function insertVault(
|
|
384
|
+
body: NodePath<t.Program | t.BlockStatement>,
|
|
385
|
+
entryCount: number,
|
|
386
|
+
data: VaultData,
|
|
387
|
+
cache: boolean,
|
|
388
|
+
): VaultIdentifiers {
|
|
389
|
+
const fresh = (purpose: string): t.Identifier => t.identifier(body.scope.generateUid(purpose))
|
|
390
|
+
const identifiers: VaultIdentifiers = {
|
|
391
|
+
cache: cache ? fresh('stringCache') : null,
|
|
392
|
+
decoder: fresh('stringDecoder'),
|
|
393
|
+
metadata: fresh('stringMetadata'),
|
|
394
|
+
payload: fresh('stringPayload'),
|
|
395
|
+
pool: fresh('stringPool'),
|
|
396
|
+
}
|
|
397
|
+
const decoder = t.functionExpression(null, [], t.blockStatement([]))
|
|
398
|
+
const declarations = [
|
|
399
|
+
t.variableDeclarator(identifiers.pool, t.stringLiteral(data.pool)),
|
|
400
|
+
t.variableDeclarator(identifiers.payload, numericArray(data.payload)),
|
|
401
|
+
t.variableDeclarator(identifiers.metadata, numericArray(data.metadata)),
|
|
402
|
+
]
|
|
403
|
+
if (identifiers.cache) {
|
|
404
|
+
declarations.push(
|
|
405
|
+
t.variableDeclarator(
|
|
406
|
+
identifiers.cache,
|
|
407
|
+
t.arrayExpression(Array.from({ length: entryCount }, () => t.nullLiteral())),
|
|
408
|
+
),
|
|
409
|
+
)
|
|
410
|
+
}
|
|
411
|
+
declarations.push(t.variableDeclarator(identifiers.decoder, decoder))
|
|
412
|
+
|
|
413
|
+
const declaration = t.variableDeclaration('const', declarations)
|
|
414
|
+
const [declarationPath] = body.unshiftContainer('body', declaration)
|
|
415
|
+
const declaratorPaths = declarationPath!.get('declarations') as NodePath<t.VariableDeclarator>[]
|
|
416
|
+
const decoderPath = declaratorPaths
|
|
417
|
+
.find((path) => path.node.init === decoder)!
|
|
418
|
+
.get('init') as NodePath<t.FunctionExpression>
|
|
419
|
+
populateDecoder(decoderPath, identifiers, data.indexMask)
|
|
420
|
+
return identifiers
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function populateDecoder(
|
|
424
|
+
decoderPath: NodePath<t.FunctionExpression>,
|
|
425
|
+
vault: VaultIdentifiers,
|
|
426
|
+
indexMask: number,
|
|
427
|
+
): void {
|
|
428
|
+
const fresh = (purpose: string): t.Identifier =>
|
|
429
|
+
t.identifier(decoderPath.scope.generateUid(purpose))
|
|
430
|
+
const entry = fresh('entry')
|
|
431
|
+
const result = fresh('result')
|
|
432
|
+
const record = fresh('record')
|
|
433
|
+
const start = fresh('start')
|
|
434
|
+
const length = fresh('length')
|
|
435
|
+
const key = fresh('stringKey')
|
|
436
|
+
const step = fresh('stringStep')
|
|
437
|
+
const position = fresh('position')
|
|
438
|
+
const mask = fresh('stringMask')
|
|
439
|
+
const statements: t.Statement[] = []
|
|
440
|
+
|
|
441
|
+
if (vault.cache) {
|
|
442
|
+
const cached = fresh('cached')
|
|
443
|
+
statements.push(
|
|
444
|
+
t.variableDeclaration('let', [t.variableDeclarator(cached, indexed(vault.cache, entry))]),
|
|
445
|
+
t.ifStatement(t.binaryExpression('!==', cached, t.nullLiteral()), t.returnStatement(cached)),
|
|
446
|
+
)
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
statements.push(
|
|
450
|
+
t.variableDeclaration('let', [
|
|
451
|
+
t.variableDeclarator(result, t.stringLiteral('')),
|
|
452
|
+
t.variableDeclarator(record, t.binaryExpression('*', entry, t.numericLiteral(RECORD_WIDTH))),
|
|
453
|
+
t.variableDeclarator(start, indexed(vault.metadata, record)),
|
|
454
|
+
t.variableDeclarator(
|
|
455
|
+
length,
|
|
456
|
+
indexed(vault.metadata, t.binaryExpression('+', record, t.numericLiteral(1))),
|
|
457
|
+
),
|
|
458
|
+
t.variableDeclarator(
|
|
459
|
+
key,
|
|
460
|
+
indexed(vault.metadata, t.binaryExpression('+', record, t.numericLiteral(2))),
|
|
461
|
+
),
|
|
462
|
+
t.variableDeclarator(
|
|
463
|
+
step,
|
|
464
|
+
indexed(vault.metadata, t.binaryExpression('+', record, t.numericLiteral(3))),
|
|
465
|
+
),
|
|
466
|
+
]),
|
|
467
|
+
t.forStatement(
|
|
468
|
+
t.variableDeclaration('let', [t.variableDeclarator(position, t.numericLiteral(0))]),
|
|
469
|
+
t.binaryExpression('<', position, length),
|
|
470
|
+
t.updateExpression('++', position),
|
|
471
|
+
t.blockStatement([
|
|
472
|
+
t.variableDeclaration('let', [
|
|
473
|
+
t.variableDeclarator(mask, maskExpression(position, key, step, indexMask)),
|
|
474
|
+
]),
|
|
475
|
+
t.expressionStatement(
|
|
476
|
+
t.assignmentExpression(
|
|
477
|
+
'+=',
|
|
478
|
+
result,
|
|
479
|
+
indexed(
|
|
480
|
+
vault.pool,
|
|
481
|
+
t.binaryExpression(
|
|
482
|
+
'^',
|
|
483
|
+
indexed(vault.payload, t.binaryExpression('+', start, position)),
|
|
484
|
+
mask,
|
|
485
|
+
),
|
|
486
|
+
),
|
|
487
|
+
),
|
|
488
|
+
),
|
|
489
|
+
]),
|
|
490
|
+
),
|
|
491
|
+
)
|
|
492
|
+
if (vault.cache) {
|
|
493
|
+
statements.push(
|
|
494
|
+
t.expressionStatement(t.assignmentExpression('=', indexed(vault.cache, entry), result)),
|
|
495
|
+
)
|
|
496
|
+
}
|
|
497
|
+
statements.push(t.returnStatement(result))
|
|
498
|
+
decoderPath.node.params = [entry]
|
|
499
|
+
decoderPath.node.body.body = statements
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function maskExpression(
|
|
503
|
+
position: t.Identifier,
|
|
504
|
+
key: t.Identifier,
|
|
505
|
+
step: t.Identifier,
|
|
506
|
+
indexMask: number,
|
|
507
|
+
): t.Expression {
|
|
508
|
+
return t.binaryExpression(
|
|
509
|
+
'&',
|
|
510
|
+
t.binaryExpression(
|
|
511
|
+
'+',
|
|
512
|
+
t.cloneNode(key),
|
|
513
|
+
t.binaryExpression('*', t.cloneNode(position), t.cloneNode(step)),
|
|
514
|
+
),
|
|
515
|
+
t.numericLiteral(indexMask),
|
|
516
|
+
)
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function indexed(object: t.Identifier, property: t.Expression): t.MemberExpression {
|
|
520
|
+
return t.memberExpression(t.cloneNode(object), t.cloneNode(property), true)
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function numericArray(values: readonly number[]): t.ArrayExpression {
|
|
524
|
+
return t.arrayExpression(values.map((value) => t.numericLiteral(value)))
|
|
525
|
+
}
|
|
@@ -3,14 +3,11 @@ import type { NodePath, Scope, Visitor } from '@babel/traverse'
|
|
|
3
3
|
import * as t from '@babel/types'
|
|
4
4
|
import type { File } from '@babel/types'
|
|
5
5
|
|
|
6
|
-
import { initializerReaches } from '../analysis/document-order'
|
|
7
|
-
import { enclosingScopeHasDirectEval, isInsideWith } from '../utils/ast'
|
|
8
|
-
import { hasConstantViolation } from '../utils/paths'
|
|
9
|
-
|
|
10
6
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
7
|
+
* Converts `let` and `const` declarations to `var` and renames bindings that
|
|
8
|
+
* would conflict after hoisting. It leaves loop declarations unchanged when
|
|
9
|
+
* `var` would lose a captured per-iteration binding or reuse the previous
|
|
10
|
+
* iteration's value.
|
|
14
11
|
*
|
|
15
12
|
* @example
|
|
16
13
|
* // ◀️ before
|
|
@@ -33,28 +30,12 @@ export function constLetToVar(ast: File): boolean {
|
|
|
33
30
|
if (kind !== 'let' && kind !== 'const') {
|
|
34
31
|
return
|
|
35
32
|
}
|
|
36
|
-
// Babel cannot see names supplied by `with` or direct eval.
|
|
37
|
-
if (isInsideWith(path) || enclosingScopeHasDirectEval(path)) {
|
|
38
|
-
return
|
|
39
|
-
}
|
|
40
|
-
// A `var` write would lose the `const` TypeError.
|
|
41
|
-
if (kind === 'const' && hasConstantViolation(path)) {
|
|
42
|
-
return
|
|
43
|
-
}
|
|
44
|
-
// Hoisted `var` reads `undefined` where a lexical binding throws in TDZ.
|
|
45
|
-
if (hasTdzRiskyReference(path)) {
|
|
46
|
-
return
|
|
47
|
-
}
|
|
48
33
|
// An uninitialized lexical loop head resets on each entry. `var` does not.
|
|
49
34
|
if (forHeadMissingInitializer(path)) {
|
|
50
35
|
return
|
|
51
36
|
}
|
|
52
|
-
// Top-level `var` creates a global property. Lexical declarations do not.
|
|
53
37
|
const blockScope = path.scope
|
|
54
38
|
const varScope = blockScope.getFunctionParent() ?? blockScope.getProgramParent()
|
|
55
|
-
if (varScope.path.isProgram()) {
|
|
56
|
-
return
|
|
57
|
-
}
|
|
58
39
|
// One `var` cannot preserve per-iteration bindings captured by closures.
|
|
59
40
|
if (capturedInEnclosingLoop(path)) {
|
|
60
41
|
return
|
|
@@ -68,10 +49,6 @@ export function constLetToVar(ast: File): boolean {
|
|
|
68
49
|
if (!id) {
|
|
69
50
|
return
|
|
70
51
|
}
|
|
71
|
-
if (isInsideWith(path) || enclosingScopeHasDirectEval(path)) {
|
|
72
|
-
return
|
|
73
|
-
}
|
|
74
|
-
|
|
75
52
|
const scope = path.parentPath.scope
|
|
76
53
|
if (!isVarScope(scope) && scope.parent!.hasBinding(id.name, { noUids: true })) {
|
|
77
54
|
path.scope.rename(id.name)
|
|
@@ -99,35 +76,6 @@ const conflictingFunctionsVisitor: Visitor<{ names: string[] }> = {
|
|
|
99
76
|
},
|
|
100
77
|
}
|
|
101
78
|
|
|
102
|
-
// Same-scope uses need a reachable initializer. Nested uses also need their
|
|
103
|
-
// function value created after initialization.
|
|
104
|
-
function hasTdzRiskyReference(path: NodePath<t.VariableDeclaration>): boolean {
|
|
105
|
-
const declHome = nearestFunctionOrProgram(path)
|
|
106
|
-
for (const name of Object.keys(path.getBindingIdentifiers())) {
|
|
107
|
-
const binding = path.scope.getBinding(name)
|
|
108
|
-
if (!binding) {
|
|
109
|
-
continue
|
|
110
|
-
}
|
|
111
|
-
for (const usage of [...binding.referencePaths, ...binding.constantViolations]) {
|
|
112
|
-
if (usage.findParent((ancestor) => ancestor === path)) {
|
|
113
|
-
return true
|
|
114
|
-
}
|
|
115
|
-
const usageHome = nearestFunctionOrProgram(usage)
|
|
116
|
-
if (usageHome === declHome) {
|
|
117
|
-
if (!initializerReaches(path, usage)) {
|
|
118
|
-
return true
|
|
119
|
-
}
|
|
120
|
-
continue
|
|
121
|
-
}
|
|
122
|
-
const nested = outermostNestedFunction(usage, declHome)
|
|
123
|
-
if (!nested || nested.isFunctionDeclaration() || !initializerReaches(path, nested)) {
|
|
124
|
-
return true
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
return false
|
|
129
|
-
}
|
|
130
|
-
|
|
131
79
|
function forHeadMissingInitializer(path: NodePath<t.VariableDeclaration>): boolean {
|
|
132
80
|
if (!t.isForStatement(path.parent) || path.key !== 'init') {
|
|
133
81
|
return false
|
|
@@ -146,7 +94,11 @@ function capturedInEnclosingLoop(path: NodePath<t.VariableDeclaration>): boolean
|
|
|
146
94
|
if (!binding) {
|
|
147
95
|
continue
|
|
148
96
|
}
|
|
149
|
-
|
|
97
|
+
const usages =
|
|
98
|
+
binding.kind === 'const'
|
|
99
|
+
? binding.referencePaths
|
|
100
|
+
: [...binding.referencePaths, ...binding.constantViolations]
|
|
101
|
+
for (const usage of usages) {
|
|
150
102
|
for (
|
|
151
103
|
let current = usage.parentPath;
|
|
152
104
|
current && current !== loop;
|
|
@@ -204,21 +156,6 @@ function transformBlockScopedVariable(
|
|
|
204
156
|
})
|
|
205
157
|
}
|
|
206
158
|
|
|
207
|
-
function nearestFunctionOrProgram(path: NodePath): NodePath {
|
|
208
|
-
return path.findParent((ancestor) => ancestor.isFunction() || ancestor.isProgram())!
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// The shallowest nested function decides when this use can run.
|
|
212
|
-
function outermostNestedFunction(usage: NodePath, home: NodePath): NodePath | null {
|
|
213
|
-
let result: NodePath | null = null
|
|
214
|
-
for (let current = usage.parentPath; current && current !== home; current = current.parentPath) {
|
|
215
|
-
if (current.isFunction()) {
|
|
216
|
-
result = current
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
return result
|
|
220
|
-
}
|
|
221
|
-
|
|
222
159
|
function enclosingLoop(path: NodePath): NodePath<t.Loop> | null {
|
|
223
160
|
for (let current = path.parentPath; current; current = current.parentPath) {
|
|
224
161
|
if (current.isFunction()) {
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
import type { Visitor } from '@babel/traverse'
|
|
2
2
|
import type { File } from '@babel/types'
|
|
3
3
|
|
|
4
|
-
import { enclosingScopeHasDirectEval } from '../utils/ast'
|
|
5
4
|
import { traverseForChanges } from '../utils/change-tracking'
|
|
6
5
|
import type { ChangeState } from '../utils/change-tracking'
|
|
7
|
-
import { hasConstantViolation } from '../utils/paths'
|
|
8
6
|
|
|
9
7
|
/**
|
|
10
|
-
* Changes `const` to `let
|
|
11
|
-
* eval and recorded constant violations keep the declaration unchanged. Scope
|
|
12
|
-
* and TDZ behavior are otherwise identical.
|
|
8
|
+
* Changes `const` declarations to `let`.
|
|
13
9
|
*
|
|
14
10
|
* @example
|
|
15
11
|
* // ◀️ before
|
|
@@ -29,9 +25,6 @@ const visitor: Visitor<ChangeState> = {
|
|
|
29
25
|
if (path.node.kind !== 'const') {
|
|
30
26
|
return
|
|
31
27
|
}
|
|
32
|
-
if (enclosingScopeHasDirectEval(path) || hasConstantViolation(path)) {
|
|
33
|
-
return
|
|
34
|
-
}
|
|
35
28
|
path.node.kind = 'let'
|
|
36
29
|
state.changed = true
|
|
37
30
|
},
|