js-confuser 2.0.0-alpha.2 → 2.0.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/.prettierrc +4 -0
  2. package/CHANGELOG.md +42 -8
  3. package/Migration.md +23 -8
  4. package/README.md +2 -2
  5. package/dist/constants.js +11 -2
  6. package/dist/index.js +49 -6
  7. package/dist/obfuscator.js +121 -10
  8. package/dist/order.js +0 -1
  9. package/dist/probability.js +1 -96
  10. package/dist/templates/getGlobalTemplate.js +4 -1
  11. package/dist/templates/integrityTemplate.js +1 -1
  12. package/dist/templates/stringCompressionTemplate.js +3 -3
  13. package/dist/templates/tamperProtectionTemplates.js +1 -1
  14. package/dist/templates/template.js +17 -12
  15. package/dist/transforms/controlFlowFlattening.js +112 -83
  16. package/dist/transforms/deadCode.js +21 -22
  17. package/dist/transforms/dispatcher.js +62 -37
  18. package/dist/transforms/extraction/duplicateLiteralsRemoval.js +5 -0
  19. package/dist/transforms/extraction/objectExtraction.js +1 -2
  20. package/dist/transforms/finalizer.js +1 -1
  21. package/dist/transforms/flatten.js +2 -19
  22. package/dist/transforms/identifier/globalConcealing.js +3 -4
  23. package/dist/transforms/identifier/movedDeclarations.js +12 -5
  24. package/dist/transforms/identifier/renameVariables.js +40 -6
  25. package/dist/transforms/lock/integrity.js +9 -1
  26. package/dist/transforms/lock/lock.js +16 -9
  27. package/dist/transforms/minify.js +64 -27
  28. package/dist/transforms/opaquePredicates.js +6 -7
  29. package/dist/transforms/pack.js +32 -5
  30. package/dist/transforms/plugin.js +20 -39
  31. package/dist/transforms/preparation.js +25 -36
  32. package/dist/transforms/renameLabels.js +1 -2
  33. package/dist/transforms/rgf.js +36 -16
  34. package/dist/transforms/shuffle.js +10 -11
  35. package/dist/transforms/string/stringCompression.js +14 -10
  36. package/dist/transforms/string/stringConcealing.js +7 -5
  37. package/dist/transforms/string/stringEncoding.js +4 -2
  38. package/dist/transforms/string/stringSplitting.js +4 -2
  39. package/dist/transforms/variableMasking.js +3 -2
  40. package/dist/utils/NameGen.js +5 -2
  41. package/dist/utils/PredicateGen.js +62 -0
  42. package/dist/utils/ast-utils.js +24 -9
  43. package/dist/utils/random-utils.js +10 -0
  44. package/dist/validateOptions.js +2 -2
  45. package/index.d.ts +16 -2
  46. package/package.json +2 -2
  47. package/src/constants.ts +15 -5
  48. package/src/index.ts +15 -5
  49. package/src/obfuscationResult.ts +7 -1
  50. package/src/obfuscator.ts +152 -12
  51. package/src/options.ts +26 -8
  52. package/src/order.ts +0 -2
  53. package/src/templates/getGlobalTemplate.ts +5 -1
  54. package/src/templates/integrityTemplate.ts +14 -19
  55. package/src/templates/stringCompressionTemplate.ts +4 -28
  56. package/src/templates/tamperProtectionTemplates.ts +7 -3
  57. package/src/templates/template.ts +5 -3
  58. package/src/transforms/controlFlowFlattening.ts +139 -83
  59. package/src/transforms/deadCode.ts +27 -30
  60. package/src/transforms/dispatcher.ts +24 -5
  61. package/src/transforms/extraction/duplicateLiteralsRemoval.ts +10 -1
  62. package/src/transforms/extraction/objectExtraction.ts +1 -2
  63. package/src/transforms/finalizer.ts +1 -1
  64. package/src/transforms/flatten.ts +3 -22
  65. package/src/transforms/identifier/globalConcealing.ts +26 -17
  66. package/src/transforms/identifier/movedDeclarations.ts +18 -6
  67. package/src/transforms/identifier/renameVariables.ts +48 -6
  68. package/src/transforms/lock/integrity.ts +11 -1
  69. package/src/transforms/lock/lock.ts +26 -10
  70. package/src/transforms/minify.ts +85 -38
  71. package/src/transforms/opaquePredicates.ts +6 -9
  72. package/src/transforms/pack.ts +41 -5
  73. package/src/transforms/plugin.ts +47 -69
  74. package/src/transforms/preparation.ts +33 -46
  75. package/src/transforms/renameLabels.ts +1 -2
  76. package/src/transforms/rgf.ts +52 -23
  77. package/src/transforms/shuffle.ts +28 -26
  78. package/src/transforms/string/encoding.ts +1 -1
  79. package/src/transforms/string/stringCompression.ts +22 -13
  80. package/src/transforms/string/stringConcealing.ts +13 -7
  81. package/src/transforms/string/stringEncoding.ts +6 -2
  82. package/src/transforms/string/stringSplitting.ts +9 -4
  83. package/src/transforms/variableMasking.ts +2 -2
  84. package/src/utils/NameGen.ts +13 -3
  85. package/src/utils/PredicateGen.ts +61 -0
  86. package/src/utils/ast-utils.ts +16 -9
  87. package/src/utils/random-utils.ts +14 -0
  88. package/src/validateOptions.ts +7 -4
  89. package/src/probability.ts +0 -110
  90. package/src/transforms/functionOutlining.ts +0 -225
  91. package/src/utils/ControlObject.ts +0 -141
@@ -3,7 +3,6 @@ import { NodePath } from "@babel/traverse";
3
3
  import Template from "../../templates/template";
4
4
  import { PluginArg, PluginObject } from "../plugin";
5
5
  import { Order } from "../../order";
6
- import { computeProbabilityMap } from "../../probability";
7
6
  import { ok } from "assert";
8
7
  import { BufferToStringTemplate } from "../../templates/bufferToStringTemplate";
9
8
  import { createGetGlobalTemplate } from "../../templates/getGlobalTemplate";
@@ -22,6 +21,7 @@ import {
22
21
  import { CustomStringEncoding } from "../../options";
23
22
  import { createDefaultStringEncoding } from "./encoding";
24
23
  import { numericLiteral } from "../../utils/node";
24
+ import { NO_REMOVE } from "../../constants";
25
25
 
26
26
  interface StringConcealingInterface {
27
27
  encodingImplementation: CustomStringEncoding;
@@ -38,6 +38,7 @@ export default ({ Plugin }: PluginArg): PluginObject => {
38
38
  const me = Plugin(Order.StringConcealing, {
39
39
  changeData: {
40
40
  strings: 0,
41
+ decryptionFunctions: 0,
41
42
  },
42
43
  });
43
44
 
@@ -93,6 +94,7 @@ export default ({ Plugin }: PluginArg): PluginObject => {
93
94
  encoding.code = new Template(encoding.code);
94
95
  }
95
96
 
97
+ me.changeData.decryptionFunctions++;
96
98
  encodingImplementations[encoding.identity] = encoding;
97
99
 
98
100
  return encoding;
@@ -139,7 +141,7 @@ export default ({ Plugin }: PluginArg): PluginObject => {
139
141
 
140
142
  // Check user setting
141
143
  if (
142
- !computeProbabilityMap(
144
+ !me.computeProbabilityMap(
143
145
  me.options.stringConcealing,
144
146
  originalValue
145
147
  )
@@ -271,17 +273,21 @@ export default ({ Plugin }: PluginArg): PluginObject => {
271
273
  ok(encodingImplementation.code instanceof Template);
272
274
 
273
275
  // The decoder function
274
- const decoder = encodingImplementation.code.compile({
275
- fnName: decodeFnName,
276
- __bufferToStringFunction__: bufferToStringName,
277
- });
276
+ const decoder = encodingImplementation.code
277
+ .addSymbols(NO_REMOVE)
278
+ .compile({
279
+ fnName: decodeFnName,
280
+ __bufferToStringFunction__: bufferToStringName,
281
+ });
278
282
 
279
283
  // The main function to get the string value
280
284
  const retrieveFunctionDeclaration = new Template(`
281
285
  function ${fnName}(index) {
282
286
  return ${decodeFnName}(${stringArrayName}[index]);
283
287
  }
284
- `).single<t.FunctionDeclaration>();
288
+ `)
289
+ .addSymbols(NO_REMOVE)
290
+ .single<t.FunctionDeclaration>();
285
291
 
286
292
  prepend(block, [...decoder, retrieveFunctionDeclaration]);
287
293
  }
@@ -1,8 +1,8 @@
1
1
  import { PluginInstance, PluginObject } from "../plugin";
2
2
  import * as t from "@babel/types";
3
3
  import { choice } from "../../utils/random-utils";
4
- import { computeProbabilityMap } from "../../probability";
5
4
  import { GEN_NODE, NodeSymbol } from "../../constants";
5
+ import { isModuleImport } from "../../utils/ast-utils";
6
6
 
7
7
  function pad(x: string, len: number): string {
8
8
  while (x.length < len) {
@@ -51,10 +51,14 @@ export default (me: PluginInstance): PluginObject => {
51
51
  visitor: {
52
52
  StringLiteral: {
53
53
  exit(path) {
54
+ // Ignore module imports
55
+ if (isModuleImport(path)) return;
56
+
54
57
  const { value } = path.node;
55
58
 
56
59
  // Allow percentages
57
- if (!computeProbabilityMap(me.options.stringEncoding, value)) return;
60
+ if (!me.computeProbabilityMap(me.options.stringEncoding, value))
61
+ return;
58
62
 
59
63
  var type = choice(["hexadecimal", "unicode"]);
60
64
 
@@ -1,10 +1,12 @@
1
- import { PluginArg, PluginInstance, PluginObject } from "../plugin";
1
+ import { PluginArg, PluginObject } from "../plugin";
2
2
  import { getRandomInteger, splitIntoChunks } from "../../utils/random-utils";
3
- import { computeProbabilityMap } from "../../probability";
4
3
  import { binaryExpression, stringLiteral } from "@babel/types";
5
4
  import { ok } from "assert";
6
5
  import { Order } from "../../order";
7
- import { ensureComputedExpression } from "../../utils/ast-utils";
6
+ import {
7
+ ensureComputedExpression,
8
+ isModuleImport,
9
+ } from "../../utils/ast-utils";
8
10
 
9
11
  export default ({ Plugin }: PluginArg): PluginObject => {
10
12
  const me = Plugin(Order.StringSplitting, {
@@ -19,6 +21,9 @@ export default ({ Plugin }: PluginArg): PluginObject => {
19
21
  exit(path) {
20
22
  var object = path.node;
21
23
 
24
+ // Don't change module imports
25
+ if (isModuleImport(path)) return;
26
+
22
27
  var size = Math.round(
23
28
  Math.max(6, object.value.length / getRandomInteger(3, 8))
24
29
  );
@@ -32,7 +37,7 @@ export default ({ Plugin }: PluginArg): PluginObject => {
32
37
  }
33
38
 
34
39
  if (
35
- !computeProbabilityMap(me.options.stringSplitting, object.value)
40
+ !me.computeProbabilityMap(me.options.stringSplitting, object.value)
36
41
  ) {
37
42
  return;
38
43
  }
@@ -2,7 +2,6 @@ import { Binding, NodePath } from "@babel/traverse";
2
2
  import { PluginArg, PluginObject } from "./plugin";
3
3
  import * as t from "@babel/types";
4
4
  import Template from "../templates/template";
5
- import { computeProbabilityMap } from "../probability";
6
5
  import { Order } from "../order";
7
6
  import {
8
7
  NodeSymbol,
@@ -65,7 +64,7 @@ export default ({ Plugin }: PluginArg): PluginObject => {
65
64
 
66
65
  const functionName = getFunctionName(fnPath);
67
66
 
68
- if (!computeProbabilityMap(me.options.variableMasking, functionName)) {
67
+ if (!me.computeProbabilityMap(me.options.variableMasking, functionName)) {
69
68
  return;
70
69
  }
71
70
 
@@ -155,6 +154,7 @@ export default ({ Plugin }: PluginArg): PluginObject => {
155
154
  fnPath.traverse({
156
155
  Identifier(path) {
157
156
  if (!isVariableIdentifier(path)) return;
157
+ if (fnPath.get("id") === path) return; // Skip this function's name (Test #21)
158
158
 
159
159
  if (reservedIdentifiers.has(path.node.name)) return;
160
160
  if (me.options.globalVariables.has(path.node.name)) return;
@@ -1,9 +1,14 @@
1
1
  import { ok } from "assert";
2
2
  import { ObfuscateOptions } from "../options";
3
3
  import { alphabeticalGenerator, createZeroWidthGenerator } from "./gen-utils";
4
- import { choice, getRandomHexString, getRandomInteger } from "./random-utils";
5
- import { computeProbabilityMap } from "../probability";
4
+ import {
5
+ choice,
6
+ getRandomChineseString,
7
+ getRandomHexString,
8
+ getRandomInteger,
9
+ } from "./random-utils";
6
10
  import { reservedKeywords, reservedObjectPrototype } from "../constants";
11
+ import Obfuscator from "../obfuscator";
7
12
 
8
13
  /**
9
14
  * Generate random names for variables and properties.
@@ -33,7 +38,9 @@ export class NameGen {
33
38
  return value;
34
39
  }
35
40
 
36
- var mode = computeProbabilityMap(this.identifierGenerator);
41
+ var mode = Obfuscator.prototype.computeProbabilityMap(
42
+ this.identifierGenerator
43
+ );
37
44
 
38
45
  const randomizedLength = getRandomInteger(6, 8);
39
46
 
@@ -68,6 +75,9 @@ export class NameGen {
68
75
  case "zeroWidth":
69
76
  return this.zeroWidthGenerator.generate();
70
77
 
78
+ case "chinese":
79
+ return getRandomChineseString(randomizedLength);
80
+
71
81
  default:
72
82
  throw new Error(
73
83
  "Invalid identifier generator mode: " + this.identifierGenerator
@@ -0,0 +1,61 @@
1
+ import { NodePath } from "@babel/traverse";
2
+ import { PluginInstance } from "../transforms/plugin";
3
+ import * as t from "@babel/types";
4
+ import { prepend } from "./ast-utils";
5
+ import { NameGen } from "./NameGen";
6
+ import Template from "../templates/template";
7
+
8
+ export default class PredicateGen {
9
+ constructor(public plugin: PluginInstance) {}
10
+
11
+ dummyFunctionName: string | null = null;
12
+ programPath: NodePath<t.Program> | null = null;
13
+
14
+ ensureCreated() {
15
+ if (this.dummyFunctionName) return;
16
+
17
+ this.dummyFunctionName = this.plugin.getPlaceholder("dummyFunction");
18
+
19
+ // Insert dummy function
20
+ prepend(
21
+ this.programPath,
22
+
23
+ this.plugin.skip(
24
+ t.functionDeclaration(
25
+ t.identifier(this.dummyFunctionName),
26
+ [],
27
+ t.blockStatement([])
28
+ )
29
+ )
30
+ );
31
+ }
32
+
33
+ generateTrueExpression(path: NodePath): t.Expression {
34
+ return t.unaryExpression("!", this.generateFalseExpression(path));
35
+ }
36
+
37
+ generateFalseExpression(path: NodePath): t.Expression {
38
+ this.programPath = path.find((p) => p.isProgram()) as NodePath<t.Program>;
39
+ this.ensureCreated();
40
+
41
+ // Overcomplicated way to get a random property name that doesn't exist on the Function
42
+ var randomProperty: string;
43
+ var nameGen = new NameGen("randomized");
44
+
45
+ function PrototypeCollision() {}
46
+ PrototypeCollision(); // Call it for code coverage :D
47
+
48
+ do {
49
+ randomProperty = nameGen.generate();
50
+ } while (
51
+ !randomProperty ||
52
+ PrototypeCollision[randomProperty] !== undefined
53
+ );
54
+
55
+ return this.plugin.skip(
56
+ new Template(
57
+ `"${randomProperty}" in ${this.dummyFunctionName}`
58
+ ).expression()
59
+ );
60
+ }
61
+ }
@@ -312,17 +312,21 @@ export function prepend(
312
312
  // Preserve import declarations
313
313
  // Filter out import declarations
314
314
  const body = listParent.get("body");
315
- const lastImportIndex = body.findIndex(
316
- (path) => !path.isImportDeclaration()
317
- );
315
+ let afterImport = 0;
316
+ for (var stmt of body) {
317
+ if (!stmt.isImportDeclaration()) {
318
+ break;
319
+ }
320
+ afterImport++;
321
+ }
318
322
 
319
- if (lastImportIndex === 0 || lastImportIndex === -1) {
320
- // No non-import declarations, so we can safely unshift everything
323
+ if (afterImport === 0) {
324
+ // No import declarations, so we can safely unshift everything
321
325
  return registerPaths(listParent.unshiftContainer("body", nodes));
322
- } else {
323
- // Insert the nodes after the last import declaration
324
- return registerPaths(body[lastImportIndex - 1].insertAfter(nodes));
325
326
  }
327
+
328
+ // Insert the nodes after the last import declaration
329
+ return registerPaths(body[afterImport - 1].insertAfter(nodes));
326
330
  }
327
331
 
328
332
  if (listParent.isFunction()) {
@@ -343,7 +347,9 @@ export function prepend(
343
347
 
344
348
  if (listParent.isBlock()) {
345
349
  return registerPaths(listParent.unshiftContainer("body", nodes));
346
- } else if (listParent.isSwitchCase()) {
350
+ }
351
+
352
+ if (listParent.isSwitchCase()) {
347
353
  return registerPaths(listParent.unshiftContainer("consequent", nodes));
348
354
  }
349
355
 
@@ -356,6 +362,7 @@ export function prependProgram(
356
362
  ) {
357
363
  var program = path.find((p) => p.isProgram());
358
364
  ok(program);
365
+ ok(program.isProgram());
359
366
  return prepend(program, ...nodes);
360
367
  }
361
368
 
@@ -41,6 +41,20 @@ export function getRandomHexString(length: number) {
41
41
  .toUpperCase();
42
42
  }
43
43
 
44
+ /**
45
+ * @see https://github.com/MichaelXF/js-confuser/issues/150#issuecomment-2466159582
46
+ */
47
+ export function getRandomChineseString(length: number) {
48
+ const characters: string[] = [];
49
+ for (let i = 0; i < length; i++)
50
+ characters.push(
51
+ String.fromCharCode(
52
+ Math.floor(Math.random() * (0x9fff - 0x4e00)) + 0x4e00
53
+ )
54
+ );
55
+ return characters.join("");
56
+ }
57
+
44
58
  /**
45
59
  * Returns a random string.
46
60
  */
@@ -190,10 +190,6 @@ export function applyDefaultsToOptions(
190
190
  "alert",
191
191
  "confirm",
192
192
  "location",
193
- "btoa",
194
- "atob",
195
- "unescape",
196
- "encodeURIComponent",
197
193
  ].forEach((x) => options.globalVariables.add(x));
198
194
  } else {
199
195
  // node
@@ -248,7 +244,14 @@ export function applyDefaultsToOptions(
248
244
  "Uint8Array",
249
245
  "Uint16Array",
250
246
  "Uint32Array",
247
+ "Int8Array",
248
+ "Int16Array",
249
+ "Int32Array",
251
250
  "ArrayBuffer",
251
+ "btoa",
252
+ "atob",
253
+ "unescape",
254
+ "encodeURIComponent",
252
255
  ].forEach((x) => options.globalVariables.add(x));
253
256
  }
254
257
 
@@ -1,110 +0,0 @@
1
- import { ok } from "assert";
2
- import { createObject } from "./utils/object-utils";
3
- import { ProbabilityMap } from "./options";
4
-
5
- /**
6
- * Evaluates a ProbabilityMap.
7
- * @param map The setting object.
8
- * @param customFnArgs Args given to user-implemented function, such as a variable name.
9
- */
10
- export function computeProbabilityMap<
11
- T,
12
- F extends (...args: any[]) => any = (...args: any[]) => any
13
- >(
14
- map: ProbabilityMap<T, F>,
15
- ...customImplementationArgs: F extends (...args: infer P) => any ? P : never
16
- ): boolean | string {
17
- if (!map) {
18
- return false;
19
- }
20
- if (map === true || map === 1) {
21
- return true;
22
- }
23
- if (typeof map === "number") {
24
- return Math.random() < map;
25
- }
26
-
27
- if (typeof map === "function") {
28
- return (map as Function)(...customImplementationArgs);
29
- }
30
-
31
- if (typeof map === "string") {
32
- return map;
33
- }
34
-
35
- var asObject: { [mode: string]: number } = {};
36
- if (Array.isArray(map)) {
37
- map.forEach((x: any) => {
38
- asObject[x.toString()] = 1;
39
- });
40
- } else {
41
- asObject = map as any;
42
- }
43
-
44
- var total = Object.values(asObject).reduce((a, b) => a + b);
45
- var percentages = createObject(
46
- Object.keys(asObject),
47
- Object.values(asObject).map((x) => x / total)
48
- );
49
-
50
- var ticket = Math.random();
51
-
52
- var count = 0;
53
- var winner = null;
54
- Object.keys(percentages).forEach((key) => {
55
- var x = Number(percentages[key]);
56
-
57
- if (ticket >= count && ticket < count + x) {
58
- winner = key;
59
- }
60
- count += x;
61
- });
62
-
63
- return winner;
64
- }
65
-
66
- /**
67
- * Determines if a probability map can return a positive result (true, or some string mode).
68
- * - Negative probability maps are used to remove transformations from running entirely.
69
- * @param map
70
- */
71
- export function isProbabilityMapProbable<T>(map: ProbabilityMap<T>): boolean {
72
- ok(!Number.isNaN(map), "Numbers cannot be NaN");
73
-
74
- if (!map || typeof map === "undefined") {
75
- return false;
76
- }
77
- if (typeof map === "function") {
78
- return true;
79
- }
80
- if (typeof map === "number") {
81
- if (map > 1 || map < 0) {
82
- throw new Error(`Numbers must be between 0 and 1 for 0% - 100%`);
83
- }
84
- }
85
- if (Array.isArray(map)) {
86
- ok(
87
- map.length != 0,
88
- "Empty arrays are not allowed for options. Use false instead."
89
- );
90
-
91
- if (map.length == 1) {
92
- return !!map[0];
93
- }
94
- }
95
- if (typeof map === "object") {
96
- if (map instanceof Date) return true;
97
- if (map instanceof RegExp) return true;
98
-
99
- var keys = Object.keys(map);
100
- ok(
101
- keys.length != 0,
102
- "Empty objects are not allowed for options. Use false instead."
103
- );
104
-
105
- if (keys.length == 1) {
106
- return !!keys[0];
107
- }
108
- }
109
- return true;
110
- }
@@ -1,225 +0,0 @@
1
- import { NodePath } from "@babel/traverse";
2
- import { PluginArg, PluginObject } from "./plugin";
3
- import { Order } from "../order";
4
- import { ensureComputedExpression, prepend } from "../utils/ast-utils";
5
- import * as t from "@babel/types";
6
- import { NameGen } from "../utils/NameGen";
7
- import { chance, getRandomInteger } from "../utils/random-utils";
8
- import { Binding, Visitor } from "@babel/traverse";
9
- import { computeProbabilityMap } from "../probability";
10
-
11
- function isSafeForOutlining(path: NodePath): {
12
- isSafe: boolean;
13
- bindings?: Binding[];
14
- } {
15
- if (path.isIdentifier() || path.isLiteral()) return { isSafe: false };
16
-
17
- // Skip direct invocations ('this' will be different)
18
- if (path.key === "callee" && path.parentPath.isCallExpression()) {
19
- return { isSafe: false };
20
- }
21
-
22
- // Skip typeof and delete expressions (identifier behavior is different)
23
- if (path.key === "argument" && path.parentPath.isUnaryExpression()) {
24
- return { isSafe: false };
25
- }
26
-
27
- if (
28
- path.isReturnStatement() ||
29
- path.isYieldExpression() ||
30
- path.isAwaitExpression() ||
31
- path.isContinueStatement() ||
32
- path.isBreakStatement() ||
33
- path.isThrowStatement() ||
34
- path.isDebuggerStatement() ||
35
- path.isImportDeclaration() ||
36
- path.isExportDeclaration()
37
- ) {
38
- return { isSafe: false };
39
- }
40
-
41
- var isSafe = true;
42
- var bindings: Binding[] = [];
43
- var fnPath = path.getFunctionParent();
44
-
45
- var visitor: Visitor = {
46
- ThisExpression(path) {
47
- isSafe = false;
48
- path.stop();
49
- },
50
- Identifier(path) {
51
- if (["arguments", "eval"].includes(path.node.name)) {
52
- isSafe = false;
53
- path.stop();
54
- }
55
- },
56
- BindingIdentifier(path) {
57
- var binding = path.scope.getBinding(path.node.name);
58
- if (binding) {
59
- bindings.push(binding);
60
- }
61
- },
62
- // Function flow guard
63
- "ReturnStatement|YieldExpression|AwaitExpression"(path) {
64
- if (path.getFunctionParent() === fnPath) {
65
- isSafe = false;
66
- path.stop();
67
- }
68
- },
69
- };
70
-
71
- // Exclude 'ThisExpression' and semantic 'Identifier' nodes
72
- if (visitor[path.type]) return { isSafe: false };
73
-
74
- path.traverse(visitor);
75
-
76
- return { isSafe, bindings };
77
- }
78
-
79
- export default ({ Plugin }: PluginArg): PluginObject => {
80
- const me = Plugin(Order.FunctionOutlining, {
81
- changeData: {
82
- functionsMade: 0,
83
- },
84
- });
85
-
86
- var changesMade = 0;
87
-
88
- function checkProbability() {
89
- if (!computeProbabilityMap(me.options.functionOutlining)) return false;
90
-
91
- if (changesMade > 100 && chance(changesMade - 100)) return false;
92
-
93
- return true;
94
- }
95
-
96
- return {
97
- visitor: {
98
- Block: {
99
- exit(blockPath) {
100
- if (blockPath.isProgram()) {
101
- blockPath.scope.crawl();
102
- }
103
-
104
- if (blockPath.find((p) => me.isSkipped(p))) return;
105
-
106
- if (!checkProbability()) return;
107
-
108
- // Extract a random number of statements
109
-
110
- var statements = blockPath.get("body");
111
- // var startIndex = getRandomInteger(0, statements.length);
112
- // var endIndex = getRandomInteger(startIndex, statements.length);
113
-
114
- var startIndex = 0;
115
- var endIndex = statements.length;
116
-
117
- var extractedStatements = statements.slice(startIndex, endIndex);
118
- if (!extractedStatements.length) return;
119
-
120
- var bindings: Binding[] = [];
121
-
122
- for (var statement of extractedStatements) {
123
- // Don't override the control node
124
- if (me.isSkipped(statement)) return;
125
-
126
- var result = isSafeForOutlining(statement);
127
- if (!result.isSafe) {
128
- return;
129
- }
130
-
131
- bindings.push(...result.bindings);
132
- }
133
-
134
- const extractedStatementSet = new Set<NodePath>(extractedStatements);
135
-
136
- for (var binding of bindings) {
137
- for (var referencePath of binding.referencePaths) {
138
- var found = referencePath.find((p) =>
139
- extractedStatementSet.has(p)
140
- );
141
- if (!found) {
142
- return;
143
- }
144
- }
145
- for (var constantViolation of binding.constantViolations) {
146
- var found = constantViolation.find((p) =>
147
- extractedStatementSet.has(p)
148
- );
149
- if (!found) {
150
- return;
151
- }
152
- }
153
- }
154
-
155
- changesMade++;
156
-
157
- var isFirst = true;
158
- for (var statement of extractedStatements) {
159
- if (isFirst) {
160
- isFirst = false;
161
- var memberExpression = me
162
- .getControlObject(blockPath)
163
- .addProperty(
164
- t.functionExpression(
165
- null,
166
- [],
167
- t.blockStatement(extractedStatements.map((x) => x.node))
168
- )
169
- );
170
-
171
- me.changeData.functionsMade++;
172
-
173
- var callExpression = t.callExpression(memberExpression, []);
174
-
175
- statement.replaceWith(callExpression);
176
- continue;
177
- }
178
- statement.remove();
179
- }
180
- },
181
- },
182
- Expression: {
183
- exit(path) {
184
- // Skip assignment left
185
- if (
186
- path.find(
187
- (p) =>
188
- p.key === "left" &&
189
- p.parentPath?.type === "AssignmentExpression"
190
- )
191
- ) {
192
- return;
193
- }
194
-
195
- if (!checkProbability()) return;
196
-
197
- if (path.find((p) => me.isSkipped(p))) return;
198
- if (!isSafeForOutlining(path).isSafe) return;
199
-
200
- changesMade++;
201
-
202
- var blockPath = path.find((p) => p.isBlock()) as NodePath<t.Block>;
203
-
204
- var memberExpression = me
205
- .getControlObject(blockPath)
206
- .addProperty(
207
- t.functionExpression(
208
- null,
209
- [],
210
- t.blockStatement([t.returnStatement(t.cloneNode(path.node))])
211
- )
212
- );
213
-
214
- me.changeData.functionsMade++;
215
-
216
- var callExpression = t.callExpression(memberExpression, []);
217
-
218
- ensureComputedExpression(path);
219
- path.replaceWith(callExpression);
220
- me.skip(path);
221
- },
222
- },
223
- },
224
- };
225
- };