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.
Files changed (113) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +43 -43
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +20 -20
  3. package/.github/workflows/node.js.yml +28 -28
  4. package/.prettierrc +4 -4
  5. package/CHANGELOG.md +1015 -987
  6. package/CODE_OF_CONDUCT.md +131 -131
  7. package/CONTRIBUTING.md +52 -52
  8. package/LICENSE +21 -21
  9. package/Migration.md +72 -72
  10. package/README.md +86 -86
  11. package/dist/constants.js +43 -43
  12. package/dist/index.js +14 -23
  13. package/dist/obfuscator.js +31 -25
  14. package/dist/order.js +4 -4
  15. package/dist/presets.js +31 -31
  16. package/dist/templates/integrityTemplate.js +4 -4
  17. package/dist/templates/template.js +1 -2
  18. package/dist/transforms/astScrambler.js +1 -2
  19. package/dist/transforms/calculator.js +1 -2
  20. package/dist/transforms/controlFlowFlattening.js +60 -41
  21. package/dist/transforms/deadCode.js +1 -2
  22. package/dist/transforms/dispatcher.js +4 -5
  23. package/dist/transforms/extraction/duplicateLiteralsRemoval.js +1 -2
  24. package/dist/transforms/extraction/objectExtraction.js +1 -2
  25. package/dist/transforms/finalizer.js +1 -2
  26. package/dist/transforms/flatten.js +1 -2
  27. package/dist/transforms/identifier/globalConcealing.js +15 -2
  28. package/dist/transforms/identifier/movedDeclarations.js +8 -7
  29. package/dist/transforms/identifier/renameVariables.js +7 -7
  30. package/dist/transforms/lock/integrity.js +8 -9
  31. package/dist/transforms/lock/lock.js +1 -2
  32. package/dist/transforms/minify.js +11 -29
  33. package/dist/transforms/opaquePredicates.js +1 -2
  34. package/dist/transforms/pack.js +1 -2
  35. package/dist/transforms/plugin.js +18 -19
  36. package/dist/transforms/preparation.js +16 -16
  37. package/dist/transforms/renameLabels.js +1 -2
  38. package/dist/transforms/rgf.js +8 -9
  39. package/dist/transforms/shuffle.js +1 -2
  40. package/dist/transforms/string/encoding.js +1 -2
  41. package/dist/transforms/string/stringCompression.js +3 -4
  42. package/dist/transforms/string/stringConcealing.js +1 -2
  43. package/dist/transforms/string/stringEncoding.js +1 -2
  44. package/dist/transforms/variableMasking.js +1 -2
  45. package/dist/utils/NameGen.js +2 -2
  46. package/dist/utils/PredicateGen.js +1 -2
  47. package/dist/utils/ast-utils.js +87 -88
  48. package/dist/utils/function-utils.js +8 -8
  49. package/dist/utils/node.js +5 -6
  50. package/dist/utils/object-utils.js +4 -4
  51. package/dist/utils/random-utils.js +20 -20
  52. package/dist/utils/static-utils.js +1 -2
  53. package/dist/validateOptions.js +4 -7
  54. package/index.d.ts +17 -17
  55. package/package.json +61 -59
  56. package/src/constants.ts +168 -168
  57. package/src/index.ts +118 -118
  58. package/src/obfuscationResult.ts +49 -49
  59. package/src/obfuscator.ts +501 -497
  60. package/src/options.ts +407 -407
  61. package/src/order.ts +54 -54
  62. package/src/presets.ts +125 -125
  63. package/src/templates/bufferToStringTemplate.ts +57 -57
  64. package/src/templates/deadCodeTemplates.ts +1185 -1185
  65. package/src/templates/getGlobalTemplate.ts +76 -76
  66. package/src/templates/integrityTemplate.ts +64 -64
  67. package/src/templates/setFunctionLengthTemplate.ts +11 -11
  68. package/src/templates/stringCompressionTemplate.ts +20 -20
  69. package/src/templates/tamperProtectionTemplates.ts +120 -120
  70. package/src/templates/template.ts +224 -224
  71. package/src/transforms/astScrambler.ts +99 -99
  72. package/src/transforms/calculator.ts +99 -99
  73. package/src/transforms/controlFlowFlattening.ts +1716 -1680
  74. package/src/transforms/deadCode.ts +82 -82
  75. package/src/transforms/dispatcher.ts +450 -450
  76. package/src/transforms/extraction/duplicateLiteralsRemoval.ts +156 -158
  77. package/src/transforms/extraction/objectExtraction.ts +186 -186
  78. package/src/transforms/finalizer.ts +74 -74
  79. package/src/transforms/flatten.ts +421 -418
  80. package/src/transforms/identifier/globalConcealing.ts +315 -295
  81. package/src/transforms/identifier/movedDeclarations.ts +252 -251
  82. package/src/transforms/identifier/renameVariables.ts +328 -321
  83. package/src/transforms/lock/integrity.ts +117 -117
  84. package/src/transforms/lock/lock.ts +418 -418
  85. package/src/transforms/minify.ts +615 -629
  86. package/src/transforms/opaquePredicates.ts +100 -100
  87. package/src/transforms/pack.ts +239 -239
  88. package/src/transforms/plugin.ts +173 -173
  89. package/src/transforms/preparation.ts +349 -347
  90. package/src/transforms/renameLabels.ts +175 -175
  91. package/src/transforms/rgf.ts +322 -322
  92. package/src/transforms/shuffle.ts +82 -82
  93. package/src/transforms/string/encoding.ts +144 -144
  94. package/src/transforms/string/stringCompression.ts +128 -128
  95. package/src/transforms/string/stringConcealing.ts +312 -312
  96. package/src/transforms/string/stringEncoding.ts +80 -80
  97. package/src/transforms/string/stringSplitting.ts +77 -77
  98. package/src/transforms/variableMasking.ts +257 -257
  99. package/src/utils/IntGen.ts +33 -33
  100. package/src/utils/NameGen.ts +116 -116
  101. package/src/utils/PredicateGen.ts +61 -61
  102. package/src/utils/ast-utils.ts +663 -663
  103. package/src/utils/function-utils.ts +50 -50
  104. package/src/utils/gen-utils.ts +48 -48
  105. package/src/utils/node.ts +78 -78
  106. package/src/utils/object-utils.ts +21 -21
  107. package/src/utils/random-utils.ts +93 -93
  108. package/src/utils/static-utils.ts +66 -66
  109. package/src/validateOptions.ts +256 -259
  110. package/tsconfig.json +13 -14
  111. package/dist/probability.js +0 -1
  112. package/dist/transforms/functionOutlining.js +0 -230
  113. package/dist/utils/ControlObject.js +0 -125
@@ -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
+ }