js-confuser 2.0.0 → 2.0.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/.github/ISSUE_TEMPLATE/bug_report.md +43 -43
- package/.github/ISSUE_TEMPLATE/feature_request.md +20 -20
- package/.github/workflows/node.js.yml +28 -28
- package/.prettierrc +4 -4
- package/CHANGELOG.md +1015 -987
- package/CODE_OF_CONDUCT.md +131 -131
- package/CONTRIBUTING.md +52 -52
- package/LICENSE +21 -21
- package/Migration.md +72 -72
- package/README.md +86 -86
- package/dist/constants.js +43 -43
- package/dist/index.js +14 -23
- package/dist/obfuscator.js +31 -25
- package/dist/order.js +4 -4
- package/dist/presets.js +31 -31
- package/dist/templates/integrityTemplate.js +4 -4
- package/dist/templates/template.js +1 -2
- package/dist/transforms/astScrambler.js +1 -2
- package/dist/transforms/calculator.js +1 -2
- package/dist/transforms/controlFlowFlattening.js +60 -41
- package/dist/transforms/deadCode.js +1 -2
- package/dist/transforms/dispatcher.js +4 -5
- package/dist/transforms/extraction/duplicateLiteralsRemoval.js +1 -2
- package/dist/transforms/extraction/objectExtraction.js +1 -2
- package/dist/transforms/finalizer.js +1 -2
- package/dist/transforms/flatten.js +1 -2
- package/dist/transforms/identifier/globalConcealing.js +15 -2
- package/dist/transforms/identifier/movedDeclarations.js +8 -7
- package/dist/transforms/identifier/renameVariables.js +7 -7
- package/dist/transforms/lock/integrity.js +8 -9
- package/dist/transforms/lock/lock.js +1 -2
- package/dist/transforms/minify.js +11 -29
- package/dist/transforms/opaquePredicates.js +1 -2
- package/dist/transforms/pack.js +1 -2
- package/dist/transforms/plugin.js +18 -19
- package/dist/transforms/preparation.js +16 -16
- package/dist/transforms/renameLabels.js +1 -2
- package/dist/transforms/rgf.js +8 -9
- package/dist/transforms/shuffle.js +1 -2
- package/dist/transforms/string/encoding.js +1 -2
- package/dist/transforms/string/stringCompression.js +3 -4
- package/dist/transforms/string/stringConcealing.js +1 -2
- package/dist/transforms/string/stringEncoding.js +1 -2
- package/dist/transforms/variableMasking.js +1 -2
- package/dist/utils/NameGen.js +2 -2
- package/dist/utils/PredicateGen.js +1 -2
- package/dist/utils/ast-utils.js +87 -88
- package/dist/utils/function-utils.js +8 -8
- package/dist/utils/node.js +5 -6
- package/dist/utils/object-utils.js +4 -4
- package/dist/utils/random-utils.js +20 -20
- package/dist/utils/static-utils.js +1 -2
- package/dist/validateOptions.js +4 -7
- package/index.d.ts +17 -17
- package/package.json +61 -59
- package/src/constants.ts +168 -168
- package/src/index.ts +118 -118
- package/src/obfuscationResult.ts +49 -49
- package/src/obfuscator.ts +501 -497
- package/src/options.ts +407 -407
- package/src/order.ts +54 -54
- package/src/presets.ts +125 -125
- package/src/templates/bufferToStringTemplate.ts +57 -57
- package/src/templates/deadCodeTemplates.ts +1185 -1185
- package/src/templates/getGlobalTemplate.ts +76 -76
- package/src/templates/integrityTemplate.ts +64 -64
- package/src/templates/setFunctionLengthTemplate.ts +11 -11
- package/src/templates/stringCompressionTemplate.ts +20 -20
- package/src/templates/tamperProtectionTemplates.ts +120 -120
- package/src/templates/template.ts +224 -224
- package/src/transforms/astScrambler.ts +99 -99
- package/src/transforms/calculator.ts +99 -99
- package/src/transforms/controlFlowFlattening.ts +1716 -1680
- package/src/transforms/deadCode.ts +82 -82
- package/src/transforms/dispatcher.ts +450 -450
- package/src/transforms/extraction/duplicateLiteralsRemoval.ts +156 -158
- package/src/transforms/extraction/objectExtraction.ts +186 -186
- package/src/transforms/finalizer.ts +74 -74
- package/src/transforms/flatten.ts +421 -418
- package/src/transforms/identifier/globalConcealing.ts +315 -295
- package/src/transforms/identifier/movedDeclarations.ts +252 -251
- package/src/transforms/identifier/renameVariables.ts +328 -321
- package/src/transforms/lock/integrity.ts +117 -117
- package/src/transforms/lock/lock.ts +418 -418
- package/src/transforms/minify.ts +615 -629
- package/src/transforms/opaquePredicates.ts +100 -100
- package/src/transforms/pack.ts +239 -239
- package/src/transforms/plugin.ts +173 -173
- package/src/transforms/preparation.ts +349 -347
- package/src/transforms/renameLabels.ts +175 -175
- package/src/transforms/rgf.ts +322 -322
- package/src/transforms/shuffle.ts +82 -82
- package/src/transforms/string/encoding.ts +144 -144
- package/src/transforms/string/stringCompression.ts +128 -128
- package/src/transforms/string/stringConcealing.ts +312 -312
- package/src/transforms/string/stringEncoding.ts +80 -80
- package/src/transforms/string/stringSplitting.ts +77 -77
- package/src/transforms/variableMasking.ts +257 -257
- package/src/utils/IntGen.ts +33 -33
- package/src/utils/NameGen.ts +116 -116
- package/src/utils/PredicateGen.ts +61 -61
- package/src/utils/ast-utils.ts +663 -663
- package/src/utils/function-utils.ts +50 -50
- package/src/utils/gen-utils.ts +48 -48
- package/src/utils/node.ts +78 -78
- package/src/utils/object-utils.ts +21 -21
- package/src/utils/random-utils.ts +93 -93
- package/src/utils/static-utils.ts +66 -66
- package/src/validateOptions.ts +256 -259
- package/tsconfig.json +13 -14
- package/dist/probability.js +0 -1
- package/dist/transforms/functionOutlining.js +0 -230
- package/dist/utils/ControlObject.js +0 -125
package/src/utils/ast-utils.ts
CHANGED
|
@@ -1,663 +1,663 @@
|
|
|
1
|
-
import * as t from "@babel/types";
|
|
2
|
-
import { NodePath } from "@babel/traverse";
|
|
3
|
-
import { ok } from "assert";
|
|
4
|
-
import { deepClone } from "./node";
|
|
5
|
-
|
|
6
|
-
export function getPatternIdentifierNames(
|
|
7
|
-
path: NodePath | NodePath[]
|
|
8
|
-
): Set<string> {
|
|
9
|
-
if (Array.isArray(path)) {
|
|
10
|
-
var allNames = new Set<string>();
|
|
11
|
-
for (var p of path) {
|
|
12
|
-
var names = getPatternIdentifierNames(p);
|
|
13
|
-
for (var name of names) {
|
|
14
|
-
allNames.add(name);
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return allNames;
|
|
19
|
-
}
|
|
20
|
-
var names = new Set<string>();
|
|
21
|
-
|
|
22
|
-
var functionParent = path.find((parent) => parent.isFunction());
|
|
23
|
-
|
|
24
|
-
path.traverse({
|
|
25
|
-
BindingIdentifier: (bindingPath) => {
|
|
26
|
-
var bindingFunctionParent = bindingPath.find((parent) =>
|
|
27
|
-
parent.isFunction()
|
|
28
|
-
);
|
|
29
|
-
if (functionParent === bindingFunctionParent) {
|
|
30
|
-
names.add(bindingPath.node.name);
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
// Check if the path itself is a binding identifier
|
|
36
|
-
if (path.isBindingIdentifier()) {
|
|
37
|
-
names.add(path.node.name);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return names;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Ensures a `String Literal` is 'computed' before replacing it with a more complex expression.
|
|
45
|
-
*
|
|
46
|
-
* ```js
|
|
47
|
-
* // Input
|
|
48
|
-
* {
|
|
49
|
-
* "myToBeEncodedString": "value"
|
|
50
|
-
* }
|
|
51
|
-
*
|
|
52
|
-
* // Output
|
|
53
|
-
* {
|
|
54
|
-
* ["myToBeEncodedString"]: "value"
|
|
55
|
-
* }
|
|
56
|
-
* ```
|
|
57
|
-
* @param path
|
|
58
|
-
*/
|
|
59
|
-
export function ensureComputedExpression(path: NodePath<t.Node>) {
|
|
60
|
-
if (
|
|
61
|
-
(t.isObjectMember(path.parent) ||
|
|
62
|
-
t.isClassMethod(path.parent) ||
|
|
63
|
-
t.isClassProperty(path.parent)) &&
|
|
64
|
-
path.parent.key === path.node &&
|
|
65
|
-
!path.parent.computed
|
|
66
|
-
) {
|
|
67
|
-
path.parent.computed = true;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Retrieves a function name from debugging purposes.
|
|
73
|
-
* - Function Declaration / Expression
|
|
74
|
-
* - Variable Declaration
|
|
75
|
-
* - Object property / method
|
|
76
|
-
* - Class property / method
|
|
77
|
-
* - Program returns "[Program]"
|
|
78
|
-
* - Default returns "anonymous"
|
|
79
|
-
* @param path
|
|
80
|
-
* @returns
|
|
81
|
-
*/
|
|
82
|
-
export function getFunctionName(path: NodePath<t.Function>): string {
|
|
83
|
-
if (!path) return "null";
|
|
84
|
-
if (path.isProgram()) return "[Program]";
|
|
85
|
-
|
|
86
|
-
// Check function declaration/expression ID
|
|
87
|
-
if (
|
|
88
|
-
(t.isFunctionDeclaration(path.node) || t.isFunctionExpression(path.node)) &&
|
|
89
|
-
path.node.id
|
|
90
|
-
) {
|
|
91
|
-
return path.node.id.name;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Check for containing variable declaration
|
|
95
|
-
if (
|
|
96
|
-
path.parentPath?.isVariableDeclarator() &&
|
|
97
|
-
t.isIdentifier(path.parentPath.node.id)
|
|
98
|
-
) {
|
|
99
|
-
return path.parentPath.node.id.name;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (path.isObjectMethod() || path.isClassMethod()) {
|
|
103
|
-
var property = getObjectPropertyAsString(path.node);
|
|
104
|
-
if (property) return property;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Check for containing property in an object
|
|
108
|
-
if (
|
|
109
|
-
path.parentPath?.isObjectProperty() ||
|
|
110
|
-
path.parentPath?.isClassProperty()
|
|
111
|
-
) {
|
|
112
|
-
var property = getObjectPropertyAsString(path.parentPath.node);
|
|
113
|
-
if (property) return property;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
var output = "anonymous";
|
|
117
|
-
|
|
118
|
-
if (path.isFunction()) {
|
|
119
|
-
if (path.node.generator) {
|
|
120
|
-
output += "*";
|
|
121
|
-
} else if (path.node.async) {
|
|
122
|
-
output = "async " + output;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return output;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export function isModuleImport(path: NodePath<t.StringLiteral>) {
|
|
130
|
-
// Import Declaration
|
|
131
|
-
if (path.parentPath.isImportDeclaration()) {
|
|
132
|
-
return true;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Dynamic Import / require() call
|
|
136
|
-
if (
|
|
137
|
-
t.isCallExpression(path.parent) &&
|
|
138
|
-
(t.isIdentifier(path.parent.callee, { name: "require" }) ||
|
|
139
|
-
t.isImport(path.parent.callee)) &&
|
|
140
|
-
path.node === path.parent.arguments[0]
|
|
141
|
-
) {
|
|
142
|
-
return true;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return false;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export function getBlock(path: NodePath) {
|
|
149
|
-
return path.find((p) => p.isBlock()) as NodePath<t.Block>;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
export function getParentFunctionOrProgram(
|
|
153
|
-
path: NodePath
|
|
154
|
-
): NodePath<t.Function | t.Program> {
|
|
155
|
-
if (path.isProgram()) return path;
|
|
156
|
-
|
|
157
|
-
// Find the nearest function-like parent
|
|
158
|
-
const functionOrProgramPath = path.findParent(
|
|
159
|
-
(parentPath) => parentPath.isFunction() || parentPath.isProgram()
|
|
160
|
-
) as NodePath<t.Function | t.Program>;
|
|
161
|
-
|
|
162
|
-
ok(functionOrProgramPath);
|
|
163
|
-
return functionOrProgramPath;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export function getObjectPropertyAsString(
|
|
167
|
-
property: t.ObjectMember | t.ClassProperty | t.ClassMethod
|
|
168
|
-
): string {
|
|
169
|
-
ok(
|
|
170
|
-
t.isObjectMember(property) ||
|
|
171
|
-
t.isClassProperty(property) ||
|
|
172
|
-
t.isClassMethod(property)
|
|
173
|
-
);
|
|
174
|
-
|
|
175
|
-
if (!property.computed && t.isIdentifier(property.key)) {
|
|
176
|
-
return property.key.name;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (t.isStringLiteral(property.key)) {
|
|
180
|
-
return property.key.value;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (t.isNumericLiteral(property.key)) {
|
|
184
|
-
return property.key.value.toString();
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
return null;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Gets the property of a MemberExpression as a string.
|
|
192
|
-
*
|
|
193
|
-
* @param memberPath - The path of the MemberExpression node.
|
|
194
|
-
* @returns The property as a string or null if it cannot be determined.
|
|
195
|
-
*/
|
|
196
|
-
export function getMemberExpressionPropertyAsString(
|
|
197
|
-
member: t.MemberExpression
|
|
198
|
-
): string | null {
|
|
199
|
-
t.assertMemberExpression(member);
|
|
200
|
-
|
|
201
|
-
const property = member.property;
|
|
202
|
-
|
|
203
|
-
if (!member.computed && t.isIdentifier(property)) {
|
|
204
|
-
return property.name;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (t.isStringLiteral(property)) {
|
|
208
|
-
return property.value;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (t.isNumericLiteral(property)) {
|
|
212
|
-
return property.value.toString();
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
return null; // If the property cannot be determined
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function nodeListToNodes(nodesIn: (t.Statement | t.Statement[])[]) {
|
|
219
|
-
var nodes: t.Statement[] = [];
|
|
220
|
-
if (Array.isArray(nodesIn[0])) {
|
|
221
|
-
ok(nodesIn.length === 1);
|
|
222
|
-
nodes = nodesIn[0];
|
|
223
|
-
} else {
|
|
224
|
-
nodes = nodesIn as t.Statement[];
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
return nodes;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Appends to the bottom of a block. Preserving last expression for the top level.
|
|
232
|
-
*/
|
|
233
|
-
export function append(
|
|
234
|
-
path: NodePath,
|
|
235
|
-
...nodesIn: (t.Statement | t.Statement[])[]
|
|
236
|
-
) {
|
|
237
|
-
var nodes = nodeListToNodes(nodesIn);
|
|
238
|
-
|
|
239
|
-
var listParent = path.find(
|
|
240
|
-
(p) => p.isFunction() || p.isBlock() || p.isSwitchCase()
|
|
241
|
-
);
|
|
242
|
-
if (!listParent) {
|
|
243
|
-
throw new Error("Could not find a suitable parent to prepend to");
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
if (listParent.isProgram()) {
|
|
247
|
-
var lastExpression = listParent.get("body").at(-1);
|
|
248
|
-
if (lastExpression.isExpressionStatement()) {
|
|
249
|
-
return lastExpression.insertBefore(nodes);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
if (listParent.isSwitchCase()) {
|
|
254
|
-
return listParent.pushContainer("consequent", nodes);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
if (listParent.isFunction()) {
|
|
258
|
-
var body = listParent.get("body");
|
|
259
|
-
|
|
260
|
-
if (listParent.isArrowFunctionExpression() && listParent.node.expression) {
|
|
261
|
-
if (!body.isBlockStatement()) {
|
|
262
|
-
body.replaceWith(
|
|
263
|
-
t.blockStatement([t.returnStatement(body.node as t.Expression)])
|
|
264
|
-
);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
ok(body.isBlockStatement());
|
|
269
|
-
|
|
270
|
-
return body.pushContainer("body", nodes);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
ok(listParent.isBlock());
|
|
274
|
-
return listParent.pushContainer("body", nodes);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Prepends and registers a list of nodes to the beginning of a block.
|
|
279
|
-
*
|
|
280
|
-
* - Preserves import declarations by inserting after the last import declaration.
|
|
281
|
-
* - Handles arrow functions
|
|
282
|
-
* - Handles switch cases
|
|
283
|
-
* @param path
|
|
284
|
-
* @param nodes
|
|
285
|
-
* @returns
|
|
286
|
-
*/
|
|
287
|
-
export function prepend(
|
|
288
|
-
path: NodePath,
|
|
289
|
-
...nodesIn: (t.Statement | t.Statement[])[]
|
|
290
|
-
): NodePath[] {
|
|
291
|
-
var nodes = nodeListToNodes(nodesIn);
|
|
292
|
-
|
|
293
|
-
var listParent = path.find(
|
|
294
|
-
(p) => p.isFunction() || p.isBlock() || p.isSwitchCase()
|
|
295
|
-
);
|
|
296
|
-
if (!listParent) {
|
|
297
|
-
throw new Error("Could not find a suitable parent to prepend to");
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
if (listParent.isProgram()) {
|
|
301
|
-
// Preserve import declarations
|
|
302
|
-
// Filter out import declarations
|
|
303
|
-
const body = listParent.get("body");
|
|
304
|
-
let afterImport = 0;
|
|
305
|
-
for (var stmt of body) {
|
|
306
|
-
if (!stmt.isImportDeclaration()) {
|
|
307
|
-
break;
|
|
308
|
-
}
|
|
309
|
-
afterImport++;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
if (afterImport === 0) {
|
|
313
|
-
// No import declarations, so we can safely unshift everything
|
|
314
|
-
return listParent.unshiftContainer("body", nodes);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// Insert the nodes after the last import declaration
|
|
318
|
-
return body[afterImport - 1].insertAfter(nodes);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
if (listParent.isFunction()) {
|
|
322
|
-
var body = listParent.get("body");
|
|
323
|
-
|
|
324
|
-
if (listParent.isArrowFunctionExpression() && listParent.node.expression) {
|
|
325
|
-
if (!body.isBlockStatement()) {
|
|
326
|
-
body = body.replaceWith(
|
|
327
|
-
t.blockStatement([t.returnStatement(body.node as t.Expression)])
|
|
328
|
-
)[0];
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
ok(body.isBlockStatement());
|
|
333
|
-
|
|
334
|
-
return body.unshiftContainer("body", nodes);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
if (listParent.isBlock()) {
|
|
338
|
-
return listParent.unshiftContainer("body", nodes);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
if (listParent.isSwitchCase()) {
|
|
342
|
-
return listParent.unshiftContainer("consequent", nodes);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
ok(false);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
export function prependProgram(
|
|
349
|
-
path: NodePath,
|
|
350
|
-
...nodes: (t.Statement | t.Statement[])[]
|
|
351
|
-
) {
|
|
352
|
-
var program = path.find((p) => p.isProgram());
|
|
353
|
-
ok(program);
|
|
354
|
-
ok(program.isProgram());
|
|
355
|
-
return prepend(program, ...nodes);
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
/**
|
|
359
|
-
* A referenced or binding identifier, only names that reflect variables.
|
|
360
|
-
*
|
|
361
|
-
* - Excludes labels
|
|
362
|
-
*
|
|
363
|
-
* @param path
|
|
364
|
-
* @returns
|
|
365
|
-
*/
|
|
366
|
-
export function isVariableIdentifier(path: NodePath<t.Identifier>) {
|
|
367
|
-
if (
|
|
368
|
-
!path.isReferencedIdentifier() &&
|
|
369
|
-
!(path as NodePath).isBindingIdentifier()
|
|
370
|
-
)
|
|
371
|
-
return false;
|
|
372
|
-
|
|
373
|
-
// abc: {} // not a variable identifier
|
|
374
|
-
if (path.key === "label" && path.parentPath?.isLabeledStatement())
|
|
375
|
-
return false;
|
|
376
|
-
|
|
377
|
-
return true;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
/**
|
|
381
|
-
* Subset of BindingIdentifier, excluding non-defined assignment expressions.
|
|
382
|
-
*
|
|
383
|
-
* @example
|
|
384
|
-
* var a = 1; // true
|
|
385
|
-
* var {c} = {} // true
|
|
386
|
-
* function b() {} // true
|
|
387
|
-
* function d([e] = [], ...f) {} // true
|
|
388
|
-
*
|
|
389
|
-
* f = 0; // false
|
|
390
|
-
* f(); // false
|
|
391
|
-
* @param path
|
|
392
|
-
* @returns
|
|
393
|
-
*/
|
|
394
|
-
export function isDefiningIdentifier(path: NodePath<t.Identifier>) {
|
|
395
|
-
if (path.key === "id" && path.parentPath.isFunction()) return true;
|
|
396
|
-
if (path.key === "id" && path.parentPath.isClassDeclaration) return true;
|
|
397
|
-
if (
|
|
398
|
-
path.key === "local" &&
|
|
399
|
-
(path.parentPath.isImportSpecifier() ||
|
|
400
|
-
path.parentPath.isImportDefaultSpecifier() ||
|
|
401
|
-
path.parentPath.isImportNamespaceSpecifier())
|
|
402
|
-
)
|
|
403
|
-
return true;
|
|
404
|
-
|
|
405
|
-
var maxTraversalPath = path.find(
|
|
406
|
-
(p) =>
|
|
407
|
-
(p.key === "id" && p.parentPath?.isVariableDeclarator()) ||
|
|
408
|
-
(p.listKey === "params" && p.parentPath?.isFunction()) ||
|
|
409
|
-
(p.key === "param" && p.parentPath?.isCatchClause())
|
|
410
|
-
);
|
|
411
|
-
|
|
412
|
-
if (!maxTraversalPath) return false;
|
|
413
|
-
|
|
414
|
-
var cursor: NodePath = path;
|
|
415
|
-
while (cursor && cursor !== maxTraversalPath) {
|
|
416
|
-
if (
|
|
417
|
-
cursor.parentPath.isObjectProperty() &&
|
|
418
|
-
cursor.parentPath.parentPath?.isObjectPattern()
|
|
419
|
-
) {
|
|
420
|
-
if (cursor.key !== "value") {
|
|
421
|
-
return false;
|
|
422
|
-
}
|
|
423
|
-
} else if (cursor.parentPath.isArrayPattern()) {
|
|
424
|
-
if (cursor.listKey !== "elements") {
|
|
425
|
-
return false;
|
|
426
|
-
}
|
|
427
|
-
} else if (cursor.parentPath.isRestElement()) {
|
|
428
|
-
if (cursor.key !== "argument") {
|
|
429
|
-
return false;
|
|
430
|
-
}
|
|
431
|
-
} else if (cursor.parentPath.isAssignmentPattern()) {
|
|
432
|
-
if (cursor.key !== "left") {
|
|
433
|
-
return false;
|
|
434
|
-
}
|
|
435
|
-
} else if (cursor.parentPath.isObjectPattern()) {
|
|
436
|
-
} else return false;
|
|
437
|
-
|
|
438
|
-
cursor = cursor.parentPath;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
return true;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
/**
|
|
445
|
-
* @example
|
|
446
|
-
* function id() {} // true
|
|
447
|
-
* class id {} // true
|
|
448
|
-
* var id; // false
|
|
449
|
-
* @param path
|
|
450
|
-
* @returns
|
|
451
|
-
*/
|
|
452
|
-
export function isStrictIdentifier(path: NodePath): boolean {
|
|
453
|
-
if (
|
|
454
|
-
path.key === "id" &&
|
|
455
|
-
(path.parentPath.isFunction() || path.parentPath.isClass())
|
|
456
|
-
)
|
|
457
|
-
return true;
|
|
458
|
-
|
|
459
|
-
return false;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
export function isExportedIdentifier(path: NodePath<t.Identifier>) {
|
|
463
|
-
// Check if the identifier is directly inside an ExportNamedDeclaration
|
|
464
|
-
if (path.parentPath.isExportNamedDeclaration()) {
|
|
465
|
-
return true;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// Check if the identifier is in an ExportDefaultDeclaration
|
|
469
|
-
if (path.parentPath.isExportDefaultDeclaration()) {
|
|
470
|
-
return true;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// Check if the identifier is within an ExportSpecifier
|
|
474
|
-
if (
|
|
475
|
-
path.parentPath.isExportSpecifier() &&
|
|
476
|
-
path.parentPath.parentPath.isExportNamedDeclaration()
|
|
477
|
-
) {
|
|
478
|
-
return true;
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
// Check if it's part of an exported variable declaration (e.g., export const a = 1;)
|
|
482
|
-
if (
|
|
483
|
-
path.parentPath.isVariableDeclarator() &&
|
|
484
|
-
path.parentPath.parentPath.parentPath.isExportNamedDeclaration()
|
|
485
|
-
) {
|
|
486
|
-
return true;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
// Check if it's part of an exported function declaration (e.g., export function abc() {})
|
|
490
|
-
if (
|
|
491
|
-
(path.parentPath.isFunctionDeclaration() ||
|
|
492
|
-
path.parentPath.isClassDeclaration()) &&
|
|
493
|
-
path.parentPath.parentPath.isExportNamedDeclaration()
|
|
494
|
-
) {
|
|
495
|
-
return true;
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
return false;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
/**
|
|
502
|
-
* @example
|
|
503
|
-
* function abc() {
|
|
504
|
-
* "use strict";
|
|
505
|
-
* } // true
|
|
506
|
-
* @param path
|
|
507
|
-
* @returns
|
|
508
|
-
*/
|
|
509
|
-
export function isStrictMode(path: NodePath) {
|
|
510
|
-
// Classes are always in strict mode
|
|
511
|
-
if (path.isClass()) return true;
|
|
512
|
-
|
|
513
|
-
if (path.isBlock()) {
|
|
514
|
-
if (path.isTSModuleBlock()) return false;
|
|
515
|
-
return (path.node as t.BlockStatement | t.Program).directives.some(
|
|
516
|
-
(directive) => directive.value.value === "use strict"
|
|
517
|
-
);
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
if (path.isFunction()) {
|
|
521
|
-
const fnBody = path.get("body");
|
|
522
|
-
if (fnBody.isBlock()) {
|
|
523
|
-
return isStrictMode(fnBody);
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
return false;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
/**
|
|
531
|
-
* A modified identifier is an identifier that is assigned to or updated.
|
|
532
|
-
*
|
|
533
|
-
* - Assignment Expression
|
|
534
|
-
* - Update Expression
|
|
535
|
-
*
|
|
536
|
-
* @param identifierPath
|
|
537
|
-
*/
|
|
538
|
-
export function isModifiedIdentifier(identifierPath: NodePath<t.Identifier>) {
|
|
539
|
-
var isModification = false;
|
|
540
|
-
if (identifierPath.parentPath.isUpdateExpression()) {
|
|
541
|
-
isModification = true;
|
|
542
|
-
}
|
|
543
|
-
if (
|
|
544
|
-
identifierPath.find(
|
|
545
|
-
(p) => p.key === "left" && p.parentPath?.isAssignmentExpression()
|
|
546
|
-
)
|
|
547
|
-
) {
|
|
548
|
-
isModification = true;
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
return isModification;
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
export function replaceDefiningIdentifierToMemberExpression(
|
|
555
|
-
path: NodePath<t.Identifier>,
|
|
556
|
-
memberExpression: t.MemberExpression | t.Identifier
|
|
557
|
-
) {
|
|
558
|
-
// function id(){} -> var id = function() {}
|
|
559
|
-
if (path.key === "id" && path.parentPath.isFunctionDeclaration()) {
|
|
560
|
-
var asFunctionExpression = deepClone(
|
|
561
|
-
path.parentPath.node
|
|
562
|
-
) as t.Node as t.FunctionExpression;
|
|
563
|
-
asFunctionExpression.type = "FunctionExpression";
|
|
564
|
-
|
|
565
|
-
path.parentPath.replaceWith(
|
|
566
|
-
t.expressionStatement(
|
|
567
|
-
t.assignmentExpression("=", memberExpression, asFunctionExpression)
|
|
568
|
-
)
|
|
569
|
-
);
|
|
570
|
-
return;
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
// class id{} -> var id = class {}
|
|
574
|
-
if (path.key === "id" && path.parentPath.isClassDeclaration()) {
|
|
575
|
-
var asClassExpression = deepClone(
|
|
576
|
-
path.parentPath.node
|
|
577
|
-
) as t.Node as t.ClassExpression;
|
|
578
|
-
asClassExpression.type = "ClassExpression";
|
|
579
|
-
|
|
580
|
-
path.parentPath.replaceWith(
|
|
581
|
-
t.expressionStatement(
|
|
582
|
-
t.assignmentExpression("=", memberExpression, asClassExpression)
|
|
583
|
-
)
|
|
584
|
-
);
|
|
585
|
-
return;
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
// var id = 1 -> id = 1
|
|
589
|
-
var variableDeclaratorChild = path.find(
|
|
590
|
-
(p) =>
|
|
591
|
-
p.key === "id" &&
|
|
592
|
-
p.parentPath?.isVariableDeclarator() &&
|
|
593
|
-
p.parentPath?.parentPath?.isVariableDeclaration()
|
|
594
|
-
) as NodePath<t.VariableDeclarator["id"]>;
|
|
595
|
-
|
|
596
|
-
if (variableDeclaratorChild) {
|
|
597
|
-
var variableDeclarator =
|
|
598
|
-
variableDeclaratorChild.parentPath as NodePath<t.VariableDeclarator>;
|
|
599
|
-
var variableDeclaration =
|
|
600
|
-
variableDeclarator.parentPath as NodePath<t.VariableDeclaration>;
|
|
601
|
-
|
|
602
|
-
if (variableDeclaration.type === "VariableDeclaration") {
|
|
603
|
-
ok(
|
|
604
|
-
variableDeclaration.node.declarations.length === 1,
|
|
605
|
-
"Multiple declarations not supported"
|
|
606
|
-
);
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
const id = variableDeclarator.get("id");
|
|
610
|
-
const init = variableDeclarator.get("init");
|
|
611
|
-
|
|
612
|
-
var newExpression: t.Node = id.node;
|
|
613
|
-
|
|
614
|
-
var isForInitializer =
|
|
615
|
-
(variableDeclaration.key === "init" ||
|
|
616
|
-
variableDeclaration.key === "left") &&
|
|
617
|
-
variableDeclaration.parentPath.isFor();
|
|
618
|
-
|
|
619
|
-
if (init.node || !isForInitializer) {
|
|
620
|
-
newExpression = t.assignmentExpression(
|
|
621
|
-
"=",
|
|
622
|
-
id.node,
|
|
623
|
-
init.node || t.identifier("undefined")
|
|
624
|
-
);
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
if (!isForInitializer) {
|
|
628
|
-
newExpression = t.expressionStatement(newExpression as t.Expression);
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
path.replaceWith(memberExpression);
|
|
632
|
-
|
|
633
|
-
if (variableDeclaration.isVariableDeclaration()) {
|
|
634
|
-
variableDeclaration.replaceWith(newExpression);
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
return;
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
// Safely replace the identifier with the member expression
|
|
641
|
-
// ensureComputedExpression(path);
|
|
642
|
-
// path.replaceWith(memberExpression);
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
/**
|
|
646
|
-
* @example
|
|
647
|
-
* undefined // true
|
|
648
|
-
* void 0 // true
|
|
649
|
-
*/
|
|
650
|
-
export function isUndefined(path: NodePath) {
|
|
651
|
-
if (path.isIdentifier() && path.node.name === "undefined") {
|
|
652
|
-
return true;
|
|
653
|
-
}
|
|
654
|
-
if (
|
|
655
|
-
path.isUnaryExpression() &&
|
|
656
|
-
path.node.operator === "void" &&
|
|
657
|
-
path.node.argument.type === "NumericLiteral" &&
|
|
658
|
-
path.node.argument.value === 0
|
|
659
|
-
) {
|
|
660
|
-
return true;
|
|
661
|
-
}
|
|
662
|
-
return false;
|
|
663
|
-
}
|
|
1
|
+
import * as t from "@babel/types";
|
|
2
|
+
import { NodePath } from "@babel/traverse";
|
|
3
|
+
import { ok } from "assert";
|
|
4
|
+
import { deepClone } from "./node";
|
|
5
|
+
|
|
6
|
+
export function getPatternIdentifierNames(
|
|
7
|
+
path: NodePath | NodePath[]
|
|
8
|
+
): Set<string> {
|
|
9
|
+
if (Array.isArray(path)) {
|
|
10
|
+
var allNames = new Set<string>();
|
|
11
|
+
for (var p of path) {
|
|
12
|
+
var names = getPatternIdentifierNames(p);
|
|
13
|
+
for (var name of names) {
|
|
14
|
+
allNames.add(name);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return allNames;
|
|
19
|
+
}
|
|
20
|
+
var names = new Set<string>();
|
|
21
|
+
|
|
22
|
+
var functionParent = path.find((parent) => parent.isFunction());
|
|
23
|
+
|
|
24
|
+
path.traverse({
|
|
25
|
+
BindingIdentifier: (bindingPath) => {
|
|
26
|
+
var bindingFunctionParent = bindingPath.find((parent) =>
|
|
27
|
+
parent.isFunction()
|
|
28
|
+
);
|
|
29
|
+
if (functionParent === bindingFunctionParent) {
|
|
30
|
+
names.add(bindingPath.node.name);
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Check if the path itself is a binding identifier
|
|
36
|
+
if (path.isBindingIdentifier()) {
|
|
37
|
+
names.add(path.node.name);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return names;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Ensures a `String Literal` is 'computed' before replacing it with a more complex expression.
|
|
45
|
+
*
|
|
46
|
+
* ```js
|
|
47
|
+
* // Input
|
|
48
|
+
* {
|
|
49
|
+
* "myToBeEncodedString": "value"
|
|
50
|
+
* }
|
|
51
|
+
*
|
|
52
|
+
* // Output
|
|
53
|
+
* {
|
|
54
|
+
* ["myToBeEncodedString"]: "value"
|
|
55
|
+
* }
|
|
56
|
+
* ```
|
|
57
|
+
* @param path
|
|
58
|
+
*/
|
|
59
|
+
export function ensureComputedExpression(path: NodePath<t.Node>) {
|
|
60
|
+
if (
|
|
61
|
+
(t.isObjectMember(path.parent) ||
|
|
62
|
+
t.isClassMethod(path.parent) ||
|
|
63
|
+
t.isClassProperty(path.parent)) &&
|
|
64
|
+
path.parent.key === path.node &&
|
|
65
|
+
!path.parent.computed
|
|
66
|
+
) {
|
|
67
|
+
path.parent.computed = true;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Retrieves a function name from debugging purposes.
|
|
73
|
+
* - Function Declaration / Expression
|
|
74
|
+
* - Variable Declaration
|
|
75
|
+
* - Object property / method
|
|
76
|
+
* - Class property / method
|
|
77
|
+
* - Program returns "[Program]"
|
|
78
|
+
* - Default returns "anonymous"
|
|
79
|
+
* @param path
|
|
80
|
+
* @returns
|
|
81
|
+
*/
|
|
82
|
+
export function getFunctionName(path: NodePath<t.Function>): string {
|
|
83
|
+
if (!path) return "null";
|
|
84
|
+
if (path.isProgram()) return "[Program]";
|
|
85
|
+
|
|
86
|
+
// Check function declaration/expression ID
|
|
87
|
+
if (
|
|
88
|
+
(t.isFunctionDeclaration(path.node) || t.isFunctionExpression(path.node)) &&
|
|
89
|
+
path.node.id
|
|
90
|
+
) {
|
|
91
|
+
return path.node.id.name;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Check for containing variable declaration
|
|
95
|
+
if (
|
|
96
|
+
path.parentPath?.isVariableDeclarator() &&
|
|
97
|
+
t.isIdentifier(path.parentPath.node.id)
|
|
98
|
+
) {
|
|
99
|
+
return path.parentPath.node.id.name;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (path.isObjectMethod() || path.isClassMethod()) {
|
|
103
|
+
var property = getObjectPropertyAsString(path.node);
|
|
104
|
+
if (property) return property;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Check for containing property in an object
|
|
108
|
+
if (
|
|
109
|
+
path.parentPath?.isObjectProperty() ||
|
|
110
|
+
path.parentPath?.isClassProperty()
|
|
111
|
+
) {
|
|
112
|
+
var property = getObjectPropertyAsString(path.parentPath.node);
|
|
113
|
+
if (property) return property;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
var output = "anonymous";
|
|
117
|
+
|
|
118
|
+
if (path.isFunction()) {
|
|
119
|
+
if (path.node.generator) {
|
|
120
|
+
output += "*";
|
|
121
|
+
} else if (path.node.async) {
|
|
122
|
+
output = "async " + output;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return output;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function isModuleImport(path: NodePath<t.StringLiteral>) {
|
|
130
|
+
// Import Declaration
|
|
131
|
+
if (path.parentPath.isImportDeclaration()) {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Dynamic Import / require() call
|
|
136
|
+
if (
|
|
137
|
+
t.isCallExpression(path.parent) &&
|
|
138
|
+
(t.isIdentifier(path.parent.callee, { name: "require" }) ||
|
|
139
|
+
t.isImport(path.parent.callee)) &&
|
|
140
|
+
path.node === path.parent.arguments[0]
|
|
141
|
+
) {
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function getBlock(path: NodePath) {
|
|
149
|
+
return path.find((p) => p.isBlock()) as NodePath<t.Block>;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function getParentFunctionOrProgram(
|
|
153
|
+
path: NodePath
|
|
154
|
+
): NodePath<t.Function | t.Program> {
|
|
155
|
+
if (path.isProgram()) return path;
|
|
156
|
+
|
|
157
|
+
// Find the nearest function-like parent
|
|
158
|
+
const functionOrProgramPath = path.findParent(
|
|
159
|
+
(parentPath) => parentPath.isFunction() || parentPath.isProgram()
|
|
160
|
+
) as NodePath<t.Function | t.Program>;
|
|
161
|
+
|
|
162
|
+
ok(functionOrProgramPath);
|
|
163
|
+
return functionOrProgramPath;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function getObjectPropertyAsString(
|
|
167
|
+
property: t.ObjectMember | t.ClassProperty | t.ClassMethod
|
|
168
|
+
): string {
|
|
169
|
+
ok(
|
|
170
|
+
t.isObjectMember(property) ||
|
|
171
|
+
t.isClassProperty(property) ||
|
|
172
|
+
t.isClassMethod(property)
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
if (!property.computed && t.isIdentifier(property.key)) {
|
|
176
|
+
return property.key.name;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (t.isStringLiteral(property.key)) {
|
|
180
|
+
return property.key.value;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (t.isNumericLiteral(property.key)) {
|
|
184
|
+
return property.key.value.toString();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Gets the property of a MemberExpression as a string.
|
|
192
|
+
*
|
|
193
|
+
* @param memberPath - The path of the MemberExpression node.
|
|
194
|
+
* @returns The property as a string or null if it cannot be determined.
|
|
195
|
+
*/
|
|
196
|
+
export function getMemberExpressionPropertyAsString(
|
|
197
|
+
member: t.MemberExpression
|
|
198
|
+
): string | null {
|
|
199
|
+
t.assertMemberExpression(member);
|
|
200
|
+
|
|
201
|
+
const property = member.property;
|
|
202
|
+
|
|
203
|
+
if (!member.computed && t.isIdentifier(property)) {
|
|
204
|
+
return property.name;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (t.isStringLiteral(property)) {
|
|
208
|
+
return property.value;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (t.isNumericLiteral(property)) {
|
|
212
|
+
return property.value.toString();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return null; // If the property cannot be determined
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function nodeListToNodes(nodesIn: (t.Statement | t.Statement[])[]) {
|
|
219
|
+
var nodes: t.Statement[] = [];
|
|
220
|
+
if (Array.isArray(nodesIn[0])) {
|
|
221
|
+
ok(nodesIn.length === 1);
|
|
222
|
+
nodes = nodesIn[0];
|
|
223
|
+
} else {
|
|
224
|
+
nodes = nodesIn as t.Statement[];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return nodes;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Appends to the bottom of a block. Preserving last expression for the top level.
|
|
232
|
+
*/
|
|
233
|
+
export function append(
|
|
234
|
+
path: NodePath,
|
|
235
|
+
...nodesIn: (t.Statement | t.Statement[])[]
|
|
236
|
+
) {
|
|
237
|
+
var nodes = nodeListToNodes(nodesIn);
|
|
238
|
+
|
|
239
|
+
var listParent = path.find(
|
|
240
|
+
(p) => p.isFunction() || p.isBlock() || p.isSwitchCase()
|
|
241
|
+
);
|
|
242
|
+
if (!listParent) {
|
|
243
|
+
throw new Error("Could not find a suitable parent to prepend to");
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (listParent.isProgram()) {
|
|
247
|
+
var lastExpression = listParent.get("body").at(-1);
|
|
248
|
+
if (lastExpression.isExpressionStatement()) {
|
|
249
|
+
return lastExpression.insertBefore(nodes);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (listParent.isSwitchCase()) {
|
|
254
|
+
return listParent.pushContainer("consequent", nodes);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (listParent.isFunction()) {
|
|
258
|
+
var body = listParent.get("body");
|
|
259
|
+
|
|
260
|
+
if (listParent.isArrowFunctionExpression() && listParent.node.expression) {
|
|
261
|
+
if (!body.isBlockStatement()) {
|
|
262
|
+
body.replaceWith(
|
|
263
|
+
t.blockStatement([t.returnStatement(body.node as t.Expression)])
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
ok(body.isBlockStatement());
|
|
269
|
+
|
|
270
|
+
return body.pushContainer("body", nodes);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
ok(listParent.isBlock());
|
|
274
|
+
return listParent.pushContainer("body", nodes);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Prepends and registers a list of nodes to the beginning of a block.
|
|
279
|
+
*
|
|
280
|
+
* - Preserves import declarations by inserting after the last import declaration.
|
|
281
|
+
* - Handles arrow functions
|
|
282
|
+
* - Handles switch cases
|
|
283
|
+
* @param path
|
|
284
|
+
* @param nodes
|
|
285
|
+
* @returns
|
|
286
|
+
*/
|
|
287
|
+
export function prepend(
|
|
288
|
+
path: NodePath,
|
|
289
|
+
...nodesIn: (t.Statement | t.Statement[])[]
|
|
290
|
+
): NodePath[] {
|
|
291
|
+
var nodes = nodeListToNodes(nodesIn);
|
|
292
|
+
|
|
293
|
+
var listParent = path.find(
|
|
294
|
+
(p) => p.isFunction() || p.isBlock() || p.isSwitchCase()
|
|
295
|
+
);
|
|
296
|
+
if (!listParent) {
|
|
297
|
+
throw new Error("Could not find a suitable parent to prepend to");
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (listParent.isProgram()) {
|
|
301
|
+
// Preserve import declarations
|
|
302
|
+
// Filter out import declarations
|
|
303
|
+
const body = listParent.get("body");
|
|
304
|
+
let afterImport = 0;
|
|
305
|
+
for (var stmt of body) {
|
|
306
|
+
if (!stmt.isImportDeclaration()) {
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
afterImport++;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (afterImport === 0) {
|
|
313
|
+
// No import declarations, so we can safely unshift everything
|
|
314
|
+
return listParent.unshiftContainer("body", nodes);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Insert the nodes after the last import declaration
|
|
318
|
+
return body[afterImport - 1].insertAfter(nodes);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (listParent.isFunction()) {
|
|
322
|
+
var body = listParent.get("body");
|
|
323
|
+
|
|
324
|
+
if (listParent.isArrowFunctionExpression() && listParent.node.expression) {
|
|
325
|
+
if (!body.isBlockStatement()) {
|
|
326
|
+
body = body.replaceWith(
|
|
327
|
+
t.blockStatement([t.returnStatement(body.node as t.Expression)])
|
|
328
|
+
)[0];
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
ok(body.isBlockStatement());
|
|
333
|
+
|
|
334
|
+
return body.unshiftContainer("body", nodes);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (listParent.isBlock()) {
|
|
338
|
+
return listParent.unshiftContainer("body", nodes);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (listParent.isSwitchCase()) {
|
|
342
|
+
return listParent.unshiftContainer("consequent", nodes);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
ok(false);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export function prependProgram(
|
|
349
|
+
path: NodePath,
|
|
350
|
+
...nodes: (t.Statement | t.Statement[])[]
|
|
351
|
+
) {
|
|
352
|
+
var program = path.find((p) => p.isProgram());
|
|
353
|
+
ok(program);
|
|
354
|
+
ok(program.isProgram());
|
|
355
|
+
return prepend(program, ...nodes);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* A referenced or binding identifier, only names that reflect variables.
|
|
360
|
+
*
|
|
361
|
+
* - Excludes labels
|
|
362
|
+
*
|
|
363
|
+
* @param path
|
|
364
|
+
* @returns
|
|
365
|
+
*/
|
|
366
|
+
export function isVariableIdentifier(path: NodePath<t.Identifier>) {
|
|
367
|
+
if (
|
|
368
|
+
!path.isReferencedIdentifier() &&
|
|
369
|
+
!(path as NodePath).isBindingIdentifier()
|
|
370
|
+
)
|
|
371
|
+
return false;
|
|
372
|
+
|
|
373
|
+
// abc: {} // not a variable identifier
|
|
374
|
+
if (path.key === "label" && path.parentPath?.isLabeledStatement())
|
|
375
|
+
return false;
|
|
376
|
+
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Subset of BindingIdentifier, excluding non-defined assignment expressions.
|
|
382
|
+
*
|
|
383
|
+
* @example
|
|
384
|
+
* var a = 1; // true
|
|
385
|
+
* var {c} = {} // true
|
|
386
|
+
* function b() {} // true
|
|
387
|
+
* function d([e] = [], ...f) {} // true
|
|
388
|
+
*
|
|
389
|
+
* f = 0; // false
|
|
390
|
+
* f(); // false
|
|
391
|
+
* @param path
|
|
392
|
+
* @returns
|
|
393
|
+
*/
|
|
394
|
+
export function isDefiningIdentifier(path: NodePath<t.Identifier>) {
|
|
395
|
+
if (path.key === "id" && path.parentPath.isFunction()) return true;
|
|
396
|
+
if (path.key === "id" && path.parentPath.isClassDeclaration) return true;
|
|
397
|
+
if (
|
|
398
|
+
path.key === "local" &&
|
|
399
|
+
(path.parentPath.isImportSpecifier() ||
|
|
400
|
+
path.parentPath.isImportDefaultSpecifier() ||
|
|
401
|
+
path.parentPath.isImportNamespaceSpecifier())
|
|
402
|
+
)
|
|
403
|
+
return true;
|
|
404
|
+
|
|
405
|
+
var maxTraversalPath = path.find(
|
|
406
|
+
(p) =>
|
|
407
|
+
(p.key === "id" && p.parentPath?.isVariableDeclarator()) ||
|
|
408
|
+
(p.listKey === "params" && p.parentPath?.isFunction()) ||
|
|
409
|
+
(p.key === "param" && p.parentPath?.isCatchClause())
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
if (!maxTraversalPath) return false;
|
|
413
|
+
|
|
414
|
+
var cursor: NodePath = path;
|
|
415
|
+
while (cursor && cursor !== maxTraversalPath) {
|
|
416
|
+
if (
|
|
417
|
+
cursor.parentPath.isObjectProperty() &&
|
|
418
|
+
cursor.parentPath.parentPath?.isObjectPattern()
|
|
419
|
+
) {
|
|
420
|
+
if (cursor.key !== "value") {
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
} else if (cursor.parentPath.isArrayPattern()) {
|
|
424
|
+
if (cursor.listKey !== "elements") {
|
|
425
|
+
return false;
|
|
426
|
+
}
|
|
427
|
+
} else if (cursor.parentPath.isRestElement()) {
|
|
428
|
+
if (cursor.key !== "argument") {
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
} else if (cursor.parentPath.isAssignmentPattern()) {
|
|
432
|
+
if (cursor.key !== "left") {
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
} else if (cursor.parentPath.isObjectPattern()) {
|
|
436
|
+
} else return false;
|
|
437
|
+
|
|
438
|
+
cursor = cursor.parentPath;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* @example
|
|
446
|
+
* function id() {} // true
|
|
447
|
+
* class id {} // true
|
|
448
|
+
* var id; // false
|
|
449
|
+
* @param path
|
|
450
|
+
* @returns
|
|
451
|
+
*/
|
|
452
|
+
export function isStrictIdentifier(path: NodePath): boolean {
|
|
453
|
+
if (
|
|
454
|
+
path.key === "id" &&
|
|
455
|
+
(path.parentPath.isFunction() || path.parentPath.isClass())
|
|
456
|
+
)
|
|
457
|
+
return true;
|
|
458
|
+
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
export function isExportedIdentifier(path: NodePath<t.Identifier>) {
|
|
463
|
+
// Check if the identifier is directly inside an ExportNamedDeclaration
|
|
464
|
+
if (path.parentPath.isExportNamedDeclaration()) {
|
|
465
|
+
return true;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Check if the identifier is in an ExportDefaultDeclaration
|
|
469
|
+
if (path.parentPath.isExportDefaultDeclaration()) {
|
|
470
|
+
return true;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Check if the identifier is within an ExportSpecifier
|
|
474
|
+
if (
|
|
475
|
+
path.parentPath.isExportSpecifier() &&
|
|
476
|
+
path.parentPath.parentPath.isExportNamedDeclaration()
|
|
477
|
+
) {
|
|
478
|
+
return true;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Check if it's part of an exported variable declaration (e.g., export const a = 1;)
|
|
482
|
+
if (
|
|
483
|
+
path.parentPath.isVariableDeclarator() &&
|
|
484
|
+
path.parentPath.parentPath.parentPath.isExportNamedDeclaration()
|
|
485
|
+
) {
|
|
486
|
+
return true;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Check if it's part of an exported function declaration (e.g., export function abc() {})
|
|
490
|
+
if (
|
|
491
|
+
(path.parentPath.isFunctionDeclaration() ||
|
|
492
|
+
path.parentPath.isClassDeclaration()) &&
|
|
493
|
+
path.parentPath.parentPath.isExportNamedDeclaration()
|
|
494
|
+
) {
|
|
495
|
+
return true;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* @example
|
|
503
|
+
* function abc() {
|
|
504
|
+
* "use strict";
|
|
505
|
+
* } // true
|
|
506
|
+
* @param path
|
|
507
|
+
* @returns
|
|
508
|
+
*/
|
|
509
|
+
export function isStrictMode(path: NodePath) {
|
|
510
|
+
// Classes are always in strict mode
|
|
511
|
+
if (path.isClass()) return true;
|
|
512
|
+
|
|
513
|
+
if (path.isBlock()) {
|
|
514
|
+
if (path.isTSModuleBlock()) return false;
|
|
515
|
+
return (path.node as t.BlockStatement | t.Program).directives.some(
|
|
516
|
+
(directive) => directive.value.value === "use strict"
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (path.isFunction()) {
|
|
521
|
+
const fnBody = path.get("body");
|
|
522
|
+
if (fnBody.isBlock()) {
|
|
523
|
+
return isStrictMode(fnBody);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* A modified identifier is an identifier that is assigned to or updated.
|
|
532
|
+
*
|
|
533
|
+
* - Assignment Expression
|
|
534
|
+
* - Update Expression
|
|
535
|
+
*
|
|
536
|
+
* @param identifierPath
|
|
537
|
+
*/
|
|
538
|
+
export function isModifiedIdentifier(identifierPath: NodePath<t.Identifier>) {
|
|
539
|
+
var isModification = false;
|
|
540
|
+
if (identifierPath.parentPath.isUpdateExpression()) {
|
|
541
|
+
isModification = true;
|
|
542
|
+
}
|
|
543
|
+
if (
|
|
544
|
+
identifierPath.find(
|
|
545
|
+
(p) => p.key === "left" && p.parentPath?.isAssignmentExpression()
|
|
546
|
+
)
|
|
547
|
+
) {
|
|
548
|
+
isModification = true;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
return isModification;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
export function replaceDefiningIdentifierToMemberExpression(
|
|
555
|
+
path: NodePath<t.Identifier>,
|
|
556
|
+
memberExpression: t.MemberExpression | t.Identifier
|
|
557
|
+
) {
|
|
558
|
+
// function id(){} -> var id = function() {}
|
|
559
|
+
if (path.key === "id" && path.parentPath.isFunctionDeclaration()) {
|
|
560
|
+
var asFunctionExpression = deepClone(
|
|
561
|
+
path.parentPath.node
|
|
562
|
+
) as t.Node as t.FunctionExpression;
|
|
563
|
+
asFunctionExpression.type = "FunctionExpression";
|
|
564
|
+
|
|
565
|
+
path.parentPath.replaceWith(
|
|
566
|
+
t.expressionStatement(
|
|
567
|
+
t.assignmentExpression("=", memberExpression, asFunctionExpression)
|
|
568
|
+
)
|
|
569
|
+
);
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// class id{} -> var id = class {}
|
|
574
|
+
if (path.key === "id" && path.parentPath.isClassDeclaration()) {
|
|
575
|
+
var asClassExpression = deepClone(
|
|
576
|
+
path.parentPath.node
|
|
577
|
+
) as t.Node as t.ClassExpression;
|
|
578
|
+
asClassExpression.type = "ClassExpression";
|
|
579
|
+
|
|
580
|
+
path.parentPath.replaceWith(
|
|
581
|
+
t.expressionStatement(
|
|
582
|
+
t.assignmentExpression("=", memberExpression, asClassExpression)
|
|
583
|
+
)
|
|
584
|
+
);
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// var id = 1 -> id = 1
|
|
589
|
+
var variableDeclaratorChild = path.find(
|
|
590
|
+
(p) =>
|
|
591
|
+
p.key === "id" &&
|
|
592
|
+
p.parentPath?.isVariableDeclarator() &&
|
|
593
|
+
p.parentPath?.parentPath?.isVariableDeclaration()
|
|
594
|
+
) as NodePath<t.VariableDeclarator["id"]>;
|
|
595
|
+
|
|
596
|
+
if (variableDeclaratorChild) {
|
|
597
|
+
var variableDeclarator =
|
|
598
|
+
variableDeclaratorChild.parentPath as NodePath<t.VariableDeclarator>;
|
|
599
|
+
var variableDeclaration =
|
|
600
|
+
variableDeclarator.parentPath as NodePath<t.VariableDeclaration>;
|
|
601
|
+
|
|
602
|
+
if (variableDeclaration.type === "VariableDeclaration") {
|
|
603
|
+
ok(
|
|
604
|
+
variableDeclaration.node.declarations.length === 1,
|
|
605
|
+
"Multiple declarations not supported"
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const id = variableDeclarator.get("id");
|
|
610
|
+
const init = variableDeclarator.get("init");
|
|
611
|
+
|
|
612
|
+
var newExpression: t.Node = id.node;
|
|
613
|
+
|
|
614
|
+
var isForInitializer =
|
|
615
|
+
(variableDeclaration.key === "init" ||
|
|
616
|
+
variableDeclaration.key === "left") &&
|
|
617
|
+
variableDeclaration.parentPath.isFor();
|
|
618
|
+
|
|
619
|
+
if (init.node || !isForInitializer) {
|
|
620
|
+
newExpression = t.assignmentExpression(
|
|
621
|
+
"=",
|
|
622
|
+
id.node as t.LVal,
|
|
623
|
+
init.node || t.identifier("undefined")
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
if (!isForInitializer) {
|
|
628
|
+
newExpression = t.expressionStatement(newExpression as t.Expression);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
path.replaceWith(memberExpression);
|
|
632
|
+
|
|
633
|
+
if (variableDeclaration.isVariableDeclaration()) {
|
|
634
|
+
variableDeclaration.replaceWith(newExpression);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// Safely replace the identifier with the member expression
|
|
641
|
+
// ensureComputedExpression(path);
|
|
642
|
+
// path.replaceWith(memberExpression);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* @example
|
|
647
|
+
* undefined // true
|
|
648
|
+
* void 0 // true
|
|
649
|
+
*/
|
|
650
|
+
export function isUndefined(path: NodePath) {
|
|
651
|
+
if (path.isIdentifier() && path.node.name === "undefined") {
|
|
652
|
+
return true;
|
|
653
|
+
}
|
|
654
|
+
if (
|
|
655
|
+
path.isUnaryExpression() &&
|
|
656
|
+
path.node.operator === "void" &&
|
|
657
|
+
path.node.argument.type === "NumericLiteral" &&
|
|
658
|
+
path.node.argument.value === 0
|
|
659
|
+
) {
|
|
660
|
+
return true;
|
|
661
|
+
}
|
|
662
|
+
return false;
|
|
663
|
+
}
|