js-confuser 1.6.0 → 1.7.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 (67) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +215 -172
  3. package/dist/constants.js +6 -2
  4. package/dist/obfuscator.js +0 -6
  5. package/dist/options.js +4 -4
  6. package/dist/presets.js +6 -7
  7. package/dist/templates/crash.js +2 -2
  8. package/dist/templates/functionLength.js +16 -0
  9. package/dist/transforms/dispatcher.js +10 -1
  10. package/dist/transforms/extraction/duplicateLiteralsRemoval.js +95 -59
  11. package/dist/transforms/extraction/objectExtraction.js +6 -1
  12. package/dist/transforms/flatten.js +224 -147
  13. package/dist/transforms/identifier/movedDeclarations.js +38 -85
  14. package/dist/transforms/identifier/renameVariables.js +94 -41
  15. package/dist/transforms/lock/lock.js +0 -37
  16. package/dist/transforms/minify.js +2 -2
  17. package/dist/transforms/rgf.js +145 -244
  18. package/dist/transforms/stack.js +42 -1
  19. package/dist/transforms/transform.js +1 -1
  20. package/dist/util/gen.js +2 -1
  21. package/dist/util/identifiers.js +38 -4
  22. package/dist/util/insert.js +24 -3
  23. package/docs/ControlFlowFlattening.md +595 -0
  24. package/{Countermeasures.md → docs/Countermeasures.md} +1 -15
  25. package/docs/ES5.md +197 -0
  26. package/{Integrity.md → docs/Integrity.md} +2 -2
  27. package/docs/RGF.md +419 -0
  28. package/package.json +2 -2
  29. package/src/constants.ts +3 -0
  30. package/src/obfuscator.ts +0 -4
  31. package/src/options.ts +9 -86
  32. package/src/presets.ts +6 -7
  33. package/src/templates/crash.ts +10 -10
  34. package/src/templates/functionLength.ts +14 -0
  35. package/src/transforms/dispatcher.ts +15 -1
  36. package/src/transforms/extraction/duplicateLiteralsRemoval.ts +135 -130
  37. package/src/transforms/extraction/objectExtraction.ts +4 -0
  38. package/src/transforms/flatten.ts +357 -290
  39. package/src/transforms/identifier/movedDeclarations.ts +50 -96
  40. package/src/transforms/identifier/renameVariables.ts +120 -56
  41. package/src/transforms/lock/lock.ts +1 -42
  42. package/src/transforms/minify.ts +11 -2
  43. package/src/transforms/rgf.ts +221 -402
  44. package/src/transforms/stack.ts +62 -0
  45. package/src/transforms/transform.ts +6 -2
  46. package/src/util/gen.ts +7 -2
  47. package/src/util/identifiers.ts +48 -4
  48. package/src/util/insert.ts +26 -2
  49. package/test/code/ES6.src.js +24 -0
  50. package/test/transforms/dispatcher.test.ts +27 -0
  51. package/test/transforms/extraction/duplicateLiteralsRemoval.test.ts +21 -8
  52. package/test/transforms/extraction/objectExtraction.test.ts +35 -15
  53. package/test/transforms/flatten.test.ts +352 -88
  54. package/test/transforms/identifier/globalConcealing.test.ts +23 -2
  55. package/test/transforms/identifier/movedDeclarations.test.ts +37 -9
  56. package/test/transforms/identifier/renameVariables.test.ts +37 -0
  57. package/test/transforms/lock/integrity.test.ts +24 -0
  58. package/test/transforms/lock/lock.test.ts +1 -48
  59. package/test/transforms/minify.test.ts +19 -0
  60. package/test/transforms/rgf.test.ts +262 -353
  61. package/test/transforms/stack.test.ts +52 -0
  62. package/test/util/identifiers.test.ts +134 -1
  63. package/test/util/insert.test.ts +57 -3
  64. package/src/transforms/eval.ts +0 -89
  65. package/src/transforms/identifier/nameRecycling.ts +0 -280
  66. package/test/transforms/eval.test.ts +0 -131
  67. package/test/transforms/identifier/nameRecycling.test.ts +0 -205
package/src/options.ts CHANGED
@@ -141,39 +141,6 @@ export interface ObfuscateOptions {
141
141
  "hexadecimal" | "randomized" | "zeroWidth" | "mangled" | "number"
142
142
  >;
143
143
 
144
- /**
145
- * ### `nameRecycling`
146
- *
147
- * (Experimental feature)
148
- *
149
- * Attempts to reuse released names.
150
- *
151
- * - Potency Medium
152
- * - Resilience High
153
- * - Cost Low
154
- *
155
- * ```js
156
- * // Input
157
- * function percentage(x) {
158
- * var multiplied = x * 100;
159
- * var floored = Math.floor(multiplied);
160
- * var output = floored + "%"
161
- * return output;
162
- * }
163
- *
164
- * // Output
165
- * function percentage(x) {
166
- * var multiplied = x * 100;
167
- * var floored = Math.floor(multiplied);
168
- * multiplied = floored + "%";
169
- * return multiplied;
170
- * }
171
- * ```
172
- *
173
- * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options)
174
- */
175
- nameRecycling?: ProbabilityMap<boolean>;
176
-
177
144
  /**
178
145
  * ### `controlFlowFlattening`
179
146
  *
@@ -286,48 +253,15 @@ export interface ObfuscateOptions {
286
253
  */
287
254
  dispatcher?: ProbabilityMap<boolean>;
288
255
 
289
- /**
290
- * ### `eval`
291
- *
292
- * #### **`Security Warning`**
293
- *
294
- * From [MDN](<(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval)**>): Executing JavaScript from a string is an enormous security risk. It is far too easy
295
- * for a bad actor to run arbitrary code when you use eval(). Never use eval()!
296
- *
297
- * Wraps defined functions within eval statements.
298
- *
299
- * - **`false`** - Avoids using the `eval` function. _Default_.
300
- * - **`true`** - Wraps function's code into an `eval` statement.
301
- *
302
- * ```js
303
- * // Output.js
304
- * var Q4r1__ = {
305
- * Oo$Oz8t: eval(
306
- * "(function(YjVpAp){var gniSBq6=kHmsJrhOO;switch(gniSBq6){case'RW11Hj5x':return console;}});"
307
- * ),
308
- * };
309
- * Q4r1__.Oo$Oz8t("RW11Hj5x");
310
- * ```
311
- *
312
- * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options)
313
- */
314
- eval?: ProbabilityMap<boolean>;
315
-
316
256
  /**
317
257
  * ### `rgf`
318
258
  *
319
259
  * RGF (Runtime-Generated-Functions) uses the [`new Function(code...)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function) syntax to construct executable code from strings. (`"all"/true/false`)
320
260
  *
321
- * - **This can break your code. This is also as dangerous as `eval`.**
261
+ * - **This can break your code.
322
262
  * - **Due to the security concerns of arbitrary code execution, you must enable this yourself.**
323
263
  * - The arbitrary code is also obfuscated.
324
264
  *
325
- * | Mode | Description |
326
- * | --- | --- |
327
- * | `"all"` | Recursively applies to every scope (slow) |
328
- * | `true` | Applies to the top level only |
329
- * | `false` | Feature disabled |
330
- *
331
265
  * ```js
332
266
  * // Input
333
267
  * function log(x){
@@ -342,7 +276,7 @@ export interface ObfuscateOptions {
342
276
  *
343
277
  * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options)
344
278
  */
345
- rgf?: ProbabilityMap<boolean | "all">;
279
+ rgf?: ProbabilityMap<boolean>;
346
280
 
347
281
  /**
348
282
  * ### `stack`
@@ -472,20 +406,6 @@ export interface ObfuscateOptions {
472
406
  */
473
407
  context?: string[];
474
408
 
475
- /**
476
- * ### `lock.nativeFunctions`
477
- *
478
- * Set of global functions that are native. Such as `require`, `fetch`. If these variables are modified the program crashes.
479
- * Set to `true` to use the default set of native functions. (`string[]/true/false`)
480
- *
481
- * - Potency Low
482
- * - Resilience Medium
483
- * - Cost Medium
484
- *
485
- * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options)
486
- */
487
- nativeFunctions?: string[] | Set<string> | boolean;
488
-
489
409
  /**
490
410
  * ### `lock.startDate`
491
411
  *
@@ -678,7 +598,6 @@ const validProperties = new Set([
678
598
  "renameVariables",
679
599
  "renameGlobals",
680
600
  "identifierGenerator",
681
- "nameRecycling",
682
601
  "controlFlowFlattening",
683
602
  "globalConcealing",
684
603
  "stringCompression",
@@ -687,7 +606,6 @@ const validProperties = new Set([
687
606
  "stringSplitting",
688
607
  "duplicateLiteralsRemoval",
689
608
  "dispatcher",
690
- "eval",
691
609
  "rgf",
692
610
  "objectExtraction",
693
611
  "flatten",
@@ -868,6 +786,10 @@ export async function correctOptions(
868
786
  "alert",
869
787
  "confirm",
870
788
  "location",
789
+ "btoa",
790
+ "atob",
791
+ "unescape",
792
+ "encodeURIComponent",
871
793
  ].forEach((x) => options.globalVariables.add(x));
872
794
  } else {
873
795
  // node
@@ -876,6 +798,8 @@ export async function correctOptions(
876
798
  "Buffer",
877
799
  "require",
878
800
  "process",
801
+ "exports",
802
+ "module",
879
803
  "__dirname",
880
804
  "__filename",
881
805
  ].forEach((x) => options.globalVariables.add(x));
@@ -887,6 +811,7 @@ export async function correctOptions(
887
811
  "parseInt",
888
812
  "parseFloat",
889
813
  "Math",
814
+ "JSON",
890
815
  "Promise",
891
816
  "String",
892
817
  "Boolean",
@@ -906,8 +831,6 @@ export async function correctOptions(
906
831
  "setImmediate",
907
832
  "clearImmediate",
908
833
  "queueMicrotask",
909
- "exports",
910
- "module",
911
834
  "isNaN",
912
835
  "isFinite",
913
836
  "Set",
package/src/presets.ts CHANGED
@@ -17,7 +17,7 @@ import { ObfuscateOptions } from "./options";
17
17
  * 10. Minified output
18
18
  *
19
19
  * ## **`Disabled features`**
20
- * - `eval` Use at your own risk!
20
+ * - `rgf` Use at your own risk!
21
21
  *
22
22
  * ### Potential Issues
23
23
  * 1. *String Encoding* can corrupt files. Disable `stringEncoding` manually if this happens.
@@ -51,7 +51,6 @@ const highPreset: ObfuscateOptions = {
51
51
  stringSplitting: 0.75,
52
52
 
53
53
  // Use at own risk
54
- eval: false,
55
54
  rgf: false,
56
55
  };
57
56
 
@@ -66,9 +65,9 @@ const mediumPreset: ObfuscateOptions = {
66
65
  calculator: true,
67
66
  compact: true,
68
67
  hexadecimalNumbers: true,
69
- controlFlowFlattening: 0.5,
68
+ controlFlowFlattening: 0.25,
70
69
  deadCode: 0.025,
71
- dispatcher: 0.75,
70
+ dispatcher: 0.5,
72
71
  duplicateLiteralsRemoval: 0.5,
73
72
  globalConcealing: true,
74
73
  identifierGenerator: "randomized",
@@ -95,10 +94,10 @@ const lowPreset: ObfuscateOptions = {
95
94
  calculator: true,
96
95
  compact: true,
97
96
  hexadecimalNumbers: true,
98
- controlFlowFlattening: 0.25,
97
+ controlFlowFlattening: 0.1,
99
98
  deadCode: 0.01,
100
- dispatcher: 0.5,
101
- duplicateLiteralsRemoval: true,
99
+ dispatcher: 0.25,
100
+ duplicateLiteralsRemoval: 0.5,
102
101
  identifierGenerator: "randomized",
103
102
  minify: true,
104
103
  movedDeclarations: true,
@@ -3,7 +3,7 @@ import Template from "./template";
3
3
  export const CrashTemplate1 = Template(`
4
4
  var {var} = "a";
5
5
  while(1){
6
- {var} = {var} += "a"; //add as much as the browser can handle
6
+ {var} = {var} += "a";
7
7
  }
8
8
  `);
9
9
 
@@ -40,16 +40,16 @@ try {
40
40
  });
41
41
 
42
42
  {$1}();
43
- } catch ( e ) {
44
- while(e ? e : !e){
45
- var b;
46
- var c = 0;
47
- (e ? !e : e) ? (function(e){
48
- c = e ? 0 : !e ? 1 : 0;
49
- })(e) : b = 1;
43
+ } catch ( {$1}e ) {
44
+ while({$1}e ? {$1}e : !{$1}e){
45
+ var {$1}b;
46
+ var {$1}c = 0;
47
+ ({$1}e ? !{$1}e : {$1}e) ? (function({$1}e){
48
+ {$1}c = {$1}e ? 0 : !{$1}e ? 1 : 0;
49
+ })({$1}e) : {$1}b = 1;
50
50
 
51
- if(b&&c){break;}
52
- if(b){continue;}
51
+ if({$1}b&&{$1}c){break;}
52
+ if({$1}b){continue;}
53
53
  }
54
54
  }
55
55
  `);
@@ -0,0 +1,14 @@
1
+ import Template from "./template";
2
+
3
+ /**
4
+ * Helper function to set `function.length` property.
5
+ */
6
+ export const FunctionLengthTemplate = Template(`
7
+ function {name}(functionObject, functionLength){
8
+ Object["defineProperty"](functionObject, "length", {
9
+ "value": functionLength,
10
+ "configurable": true
11
+ });
12
+ return functionObject;
13
+ }
14
+ `);
@@ -68,6 +68,8 @@ import Template from "../templates/template";
68
68
  * 3. using `this`
69
69
  */
70
70
  export default class Dispatcher extends Transform {
71
+ // Debug mode preserves function names
72
+ isDebug = false;
71
73
  count: number;
72
74
 
73
75
  constructor(o) {
@@ -155,6 +157,16 @@ export default class Dispatcher extends Transform {
155
157
  return "EXIT";
156
158
  }
157
159
  }
160
+
161
+ // Avoid functions with function expressions as they have a different scope
162
+ if (
163
+ (oo.type === "FunctionExpression" ||
164
+ oo.type === "ArrowFunctionExpression") &&
165
+ pp.find((x) => x == o.params)
166
+ ) {
167
+ illegalFnNames.add(name);
168
+ return "EXIT";
169
+ }
158
170
  });
159
171
 
160
172
  functionDeclarations[name] = [o, p];
@@ -196,7 +208,9 @@ export default class Dispatcher extends Transform {
196
208
  // map original name->new game
197
209
  var gen = this.getGenerator();
198
210
  Object.keys(functionDeclarations).forEach((name) => {
199
- newFnNames[name] = gen.generate();
211
+ newFnNames[name] = this.isDebug
212
+ ? "_dispatcher_" + this.count + "_" + name
213
+ : gen.generate();
200
214
  });
201
215
  // set containing new name
202
216
  var set = new Set(Object.keys(newFnNames));
@@ -13,15 +13,9 @@ import {
13
13
  BinaryExpression,
14
14
  FunctionDeclaration,
15
15
  ThisExpression,
16
- FunctionExpression,
16
+ ConditionalExpression,
17
17
  } from "../../util/gen";
18
- import {
19
- append,
20
- clone,
21
- getLexContext,
22
- isLexContext,
23
- prepend,
24
- } from "../../util/insert";
18
+ import { append, clone, prepend } from "../../util/insert";
25
19
  import { isDirective, isPrimitive } from "../../util/compare";
26
20
 
27
21
  import { ObfuscateOrder } from "../../order";
@@ -29,6 +23,8 @@ import { isModuleSource } from "../string/stringConcealing";
29
23
  import { ComputeProbabilityMap } from "../../probability";
30
24
  import { ok } from "assert";
31
25
  import { chance, choice, getRandomInteger } from "../../util/random";
26
+ import { getBlock } from "../../traverse";
27
+ import { getIdentifierInfo } from "../../util/identifiers";
32
28
 
33
29
  /**
34
30
  * [Duplicate Literals Removal](https://docs.jscrambler.com/code-integrity/documentation/transformations/duplicate-literals-removal) replaces duplicate literals with a variable name.
@@ -49,20 +45,25 @@ import { chance, choice, getRandomInteger } from "../../util/random";
49
45
  * ```
50
46
  */
51
47
  export default class DuplicateLiteralsRemoval extends Transform {
48
+ // The array holding all the duplicate literals
52
49
  arrayName: string;
50
+ // The array expression node to be inserted into the program
53
51
  arrayExpression: Node;
52
+
53
+ /**
54
+ * Literals in the array
55
+ */
54
56
  map: Map<string, number>;
55
- first: Map<string, Location | null>;
56
57
 
57
58
  /**
58
- * getter fn name -> accumulative shift
59
+ * Literals are saved here the first time they are seen.
59
60
  */
60
- fnShifts: Map<string, number>;
61
+ first: Map<string, Location>;
61
62
 
62
63
  /**
63
- * lex context -> getter fn name
64
+ * Block -> { functionName, indexShift }
64
65
  */
65
- fnGetters: Map<Node, string>;
66
+ functions: Map<Node, { functionName: string; indexShift: number }>;
66
67
 
67
68
  constructor(o) {
68
69
  super(o, ObfuscateOrder.DuplicateLiteralsRemoval);
@@ -70,14 +71,14 @@ export default class DuplicateLiteralsRemoval extends Transform {
70
71
  this.map = new Map();
71
72
  this.first = new Map();
72
73
 
73
- this.fnShifts = new Map();
74
- this.fnGetters = new Map();
74
+ this.functions = new Map();
75
75
  }
76
76
 
77
77
  apply(tree) {
78
78
  super.apply(tree);
79
79
 
80
- if (this.arrayName && this.arrayExpression.elements.length) {
80
+ if (this.arrayName && this.arrayExpression.elements.length > 0) {
81
+ // This function simply returns the array
81
82
  var getArrayFn = this.getPlaceholder();
82
83
  append(
83
84
  tree,
@@ -88,22 +89,79 @@ export default class DuplicateLiteralsRemoval extends Transform {
88
89
  )
89
90
  );
90
91
 
92
+ // This variable holds the array
91
93
  prepend(
92
94
  tree,
93
95
  VariableDeclaration(
94
96
  VariableDeclarator(
95
97
  this.arrayName,
96
98
  CallExpression(
97
- MemberExpression(
98
- Identifier(getArrayFn),
99
- Identifier("call"),
100
- false
101
- ),
99
+ MemberExpression(Identifier(getArrayFn), Literal("call"), true),
102
100
  [ThisExpression()]
103
101
  )
104
102
  )
105
103
  )
106
104
  );
105
+
106
+ // Create all the functions needed
107
+ for (var blockNode of this.functions.keys()) {
108
+ var { functionName, indexShift } = this.functions.get(blockNode);
109
+
110
+ var propertyNode: Node = BinaryExpression(
111
+ "-",
112
+ Identifier("index_param"),
113
+ Literal(indexShift)
114
+ );
115
+
116
+ var indexRangeInclusive = [
117
+ 0 + indexShift - 1,
118
+ this.map.size + indexShift,
119
+ ];
120
+
121
+ // The function uses mangling to hide the index being accessed
122
+ var mangleCount = getRandomInteger(1, 10);
123
+ for (var i = 0; i < mangleCount; i++) {
124
+ var operator = choice([">", "<"]);
125
+ var compareValue = choice(indexRangeInclusive);
126
+
127
+ var test = BinaryExpression(
128
+ operator,
129
+ Identifier("index_param"),
130
+ Literal(compareValue)
131
+ );
132
+
133
+ var alternate = BinaryExpression(
134
+ "-",
135
+ Identifier("index_param"),
136
+ Literal(getRandomInteger(-100, 100))
137
+ );
138
+
139
+ var testValue =
140
+ (operator === ">" && compareValue === indexRangeInclusive[0]) ||
141
+ (operator === "<" && compareValue === indexRangeInclusive[1]);
142
+
143
+ propertyNode = ConditionalExpression(
144
+ test,
145
+ testValue ? propertyNode : alternate,
146
+ !testValue ? propertyNode : alternate
147
+ );
148
+ }
149
+
150
+ var returnArgument = MemberExpression(
151
+ Identifier(this.arrayName),
152
+ propertyNode,
153
+ true
154
+ );
155
+
156
+ prepend(
157
+ blockNode,
158
+ FunctionDeclaration(
159
+ functionName,
160
+ [Identifier("index_param")],
161
+ [ReturnStatement(returnArgument)]
162
+ )
163
+ );
164
+ }
107
165
  }
108
166
  }
109
167
 
@@ -122,109 +180,52 @@ export default class DuplicateLiteralsRemoval extends Transform {
122
180
  * @param parents
123
181
  * @param index
124
182
  */
125
- toCaller(object: Node, parents: Node[], index: number) {
126
- // get all the getters defined here or higher
127
- var getterNames = [object, ...parents]
128
- .map((x) => this.fnGetters.get(x))
129
- .filter((x) => x);
130
-
131
- // use random getter function
132
- var getterName = choice(getterNames);
133
-
134
- // get this literals context
135
- var lexContext = getLexContext(object, parents);
136
-
137
- var hasGetterHere = this.fnGetters.has(lexContext);
138
-
139
- // create one if none are available (or by random chance if none are here locally)
140
- var shouldCreateNew =
141
- !getterName || (!hasGetterHere && Math.random() > 0.9);
142
-
143
- if (shouldCreateNew) {
144
- ok(!this.fnGetters.has(lexContext));
145
-
146
- var lexContextIndex = parents.findIndex(
147
- (x) => x !== lexContext && isLexContext(x)
148
- );
149
- var basedOn =
150
- lexContextIndex !== -1
151
- ? choice(
152
- parents
153
- .slice(lexContextIndex + 1)
154
- .map((x) => this.fnGetters.get(x))
155
- .filter((x) => x)
156
- )
157
- : null;
158
-
159
- var body = [];
160
- var thisShift = getRandomInteger(-250, 250);
161
- // the name of the getter
162
- getterName = this.getPlaceholder() + "_dLR_" + this.fnGetters.size;
163
-
164
- if (basedOn) {
165
- var shift = this.fnShifts.get(basedOn);
166
- ok(typeof shift === "number");
167
-
168
- body = [
169
- ReturnStatement(
170
- CallExpression(Identifier(basedOn), [
171
- BinaryExpression("+", Identifier("index"), Literal(thisShift)),
172
- ])
173
- ),
174
- ];
183
+ transformLiteral(object: Node, parents: Node[], index: number) {
184
+ var blockNode = choice(parents.filter((x) => this.functions.has(x)));
185
+
186
+ // Create initial function if none exist
187
+ if (this.functions.size === 0) {
188
+ var root = parents[parents.length - 1];
189
+ var rootFunctionName = this.getPlaceholder() + "_dLR_0";
190
+ this.functions.set(root, {
191
+ functionName: rootFunctionName,
192
+ indexShift: getRandomInteger(-100, 100),
193
+ });
194
+
195
+ blockNode = root;
196
+ }
175
197
 
176
- this.fnShifts.set(getterName, shift + thisShift);
177
- } else {
178
- // from scratch
179
-
180
- body = [
181
- ReturnStatement(
182
- MemberExpression(
183
- Identifier(this.arrayName),
184
- BinaryExpression("+", Identifier("index"), Literal(thisShift)),
185
- true
186
- )
187
- ),
188
- ];
198
+ // If no function here exist, possibly create new chained function
199
+ var block = getBlock(object, parents);
200
+ if (!this.functions.has(block) && chance(50 - this.functions.size)) {
201
+ var newFunctionName =
202
+ this.getPlaceholder() + "_dLR_" + this.functions.size;
189
203
 
190
- this.fnShifts.set(getterName, thisShift);
191
- }
204
+ this.functions.set(block, {
205
+ functionName: newFunctionName,
206
+ indexShift: getRandomInteger(-100, 100),
207
+ });
192
208
 
193
- this.fnGetters.set(lexContext, getterName);
194
-
195
- prepend(
196
- lexContext,
197
- VariableDeclaration(
198
- VariableDeclarator(
199
- getterName,
200
- CallExpression(
201
- FunctionExpression(
202
- [],
203
- [
204
- ReturnStatement(
205
- FunctionExpression([Identifier("index")], body)
206
- ),
207
- ]
208
- ),
209
- []
210
- )
211
- )
212
- )
213
- );
209
+ blockNode = block;
214
210
  }
215
211
 
216
- var theShift = this.fnShifts.get(getterName);
212
+ // Derive the function to call from the selected blockNode
213
+ var { functionName, indexShift } = this.functions.get(blockNode);
217
214
 
218
- this.replaceIdentifierOrLiteral(
219
- object,
220
- CallExpression(Identifier(getterName), [Literal(index - theShift)]),
221
- parents
222
- );
215
+ // Call the function given it's indexShift
216
+ var callExpression = CallExpression(Identifier(functionName), [
217
+ Literal(index + indexShift),
218
+ ]);
219
+
220
+ this.replaceIdentifierOrLiteral(object, callExpression, parents);
223
221
  }
224
222
 
225
223
  transform(object: Node, parents: Node[]) {
226
224
  return () => {
227
- var value = object.value;
225
+ if (object.type === "Identifier") {
226
+ var info = getIdentifierInfo(object, parents);
227
+ if (info.isLabel || info.spec.isDefined) return;
228
+ }
228
229
  if (object.regex) {
229
230
  return;
230
231
  }
@@ -234,7 +235,7 @@ export default class DuplicateLiteralsRemoval extends Transform {
234
235
  }
235
236
 
236
237
  // HARD CODED LIMIT of 10,000 (after 1,000 elements)
237
- if (this.map.size > 1000 && !chance(this.map.size / 100)) return;
238
+ if (this.map.size > 1000 && chance(this.map.size / 100)) return;
238
239
 
239
240
  if (
240
241
  this.arrayName &&
@@ -244,11 +245,11 @@ export default class DuplicateLiteralsRemoval extends Transform {
244
245
  return;
245
246
  }
246
247
 
247
- var value;
248
+ var stringValue;
248
249
  if (object.type == "Literal") {
249
- value = typeof object.value + ":" + object.value;
250
+ stringValue = typeof object.value + ":" + object.value;
250
251
  if (object.value === null) {
251
- value = "null:null";
252
+ stringValue = "null:null";
252
253
  } else {
253
254
  // Skip empty strings
254
255
  if (typeof object.value === "string" && !object.value) {
@@ -256,42 +257,46 @@ export default class DuplicateLiteralsRemoval extends Transform {
256
257
  }
257
258
  }
258
259
  } else if (object.type == "Identifier") {
259
- value = "identifier:" + object.name;
260
+ stringValue = "identifier:" + object.name;
260
261
  } else {
261
262
  throw new Error("Unsupported primitive type: " + object.type);
262
263
  }
263
264
 
264
- ok(value);
265
+ ok(stringValue);
265
266
 
266
- if (!this.first.has(value) && !this.map.has(value)) {
267
- this.first.set(value, [object, parents]);
268
- } else {
267
+ if (this.map.has(stringValue) || this.first.has(stringValue)) {
268
+ // Create the array if not already made
269
269
  if (!this.arrayName) {
270
270
  this.arrayName = this.getPlaceholder();
271
271
  this.arrayExpression = ArrayExpression([]);
272
272
  }
273
273
 
274
- var firstLocation = this.first.get(value);
274
+ // Delete with first location
275
+ var firstLocation = this.first.get(stringValue);
275
276
  if (firstLocation) {
276
- this.first.set(value, null);
277
277
  var index = this.map.size;
278
278
 
279
- ok(!this.map.has(value));
280
- this.map.set(value, index);
279
+ ok(!this.map.has(stringValue));
280
+ this.map.set(stringValue, index);
281
+ this.first.delete(stringValue);
281
282
 
282
283
  var pushing = clone(object);
283
284
  this.arrayExpression.elements.push(pushing);
284
285
 
285
286
  ok(this.arrayExpression.elements[index] === pushing);
286
287
 
287
- this.toCaller(firstLocation[0], firstLocation[1], index);
288
+ this.transformLiteral(firstLocation[0], firstLocation[1], index);
288
289
  }
289
290
 
290
- var index = this.map.get(value);
291
+ var index = this.map.get(stringValue);
291
292
  ok(typeof index === "number");
292
293
 
293
- this.toCaller(object, parents, index);
294
+ this.transformLiteral(object, parents, index);
295
+ return;
294
296
  }
297
+
298
+ // Save this, maybe a duplicate will be found.
299
+ this.first.set(stringValue, [object, parents]);
295
300
  };
296
301
  }
297
302
  }