Просмотр исходного кода

Merge branch 'dev'

# Conflicts:
#	dist/index.js
sanex3339 8 лет назад
Родитель
Сommit
b2615f16bb
100 измененных файлов с 1431 добавлено и 862 удалено
  1. 8 1
      .babelrc
  2. 23 14
      README.md
  3. 417 336
      dist/index.js
  4. 16 12
      package.json
  5. 12 1
      src/JavaScriptObfuscator.ts
  6. 1 7
      src/JavaScriptObfuscatorInternal.ts
  7. 2 2
      src/Obfuscator.ts
  8. 1 3
      src/cli/CLIUtils.ts
  9. 8 4
      src/cli/JavaScriptObfuscatorCLI.ts
  10. 4 3
      src/container/ServiceIdentifiers.ts
  11. 1 1
      src/container/modules/node-transformers/NodeControlFlowTransformersModule.ts
  12. 11 11
      src/container/modules/node-transformers/NodeObfuscatorsModule.ts
  13. 1 1
      src/container/modules/node-transformers/NodeTransformersModule.ts
  14. 1 1
      src/container/modules/stack-trace-analyzer/StackTraceAnalyzerModule.ts
  15. 5 0
      src/container/modules/storages/StoragesModule.ts
  16. 9 2
      src/custom-nodes/AbstractCustomNode.ts
  17. 1 1
      src/custom-nodes/console-output-nodes/ConsoleOutputDisableExpressionNode.ts
  18. 2 2
      src/custom-nodes/control-flow-replacers-nodes/binary-expression-control-flow-replacer-nodes/BinaryExpressionFunctionNode.ts
  19. 1 1
      src/custom-nodes/control-flow-replacers-nodes/binary-expression-control-flow-replacer-nodes/ControlFlowStorageCallNode.ts
  20. 3 11
      src/custom-nodes/control-flow-storage-nodes/ControlFlowStorageNode.ts
  21. 1 1
      src/custom-nodes/debug-protection-nodes/DebugProtectionFunctionCallNode.ts
  22. 1 1
      src/custom-nodes/debug-protection-nodes/DebugProtectionFunctionIntervalNode.ts
  23. 1 1
      src/custom-nodes/debug-protection-nodes/DebugProtectionFunctionNode.ts
  24. 1 1
      src/custom-nodes/domain-lock-nodes/DomainLockNode.ts
  25. 2 2
      src/custom-nodes/node-calls-controller-nodes/NodeCallsControllerFunctionNode.ts
  26. 2 2
      src/custom-nodes/self-defending-nodes/SelfDefendingUnicodeNode.ts
  27. 27 35
      src/custom-nodes/string-array-nodes/StringArrayCallsWrapper.ts
  28. 15 28
      src/custom-nodes/string-array-nodes/StringArrayNode.ts
  29. 6 6
      src/custom-nodes/string-array-nodes/StringArrayRotateFunctionNode.ts
  30. 13 11
      src/custom-nodes/string-array-nodes/group/StringArrayCustomNodeGroup.ts
  31. 1 1
      src/declarations/ESTree.d.ts
  32. 10 0
      src/declarations/escodegen.d.ts
  33. 26 30
      src/decorators/Initializable.ts
  34. 2 0
      src/enums/NodeType.ts
  35. 0 5
      src/interfaces/IGenerator.d.ts
  36. 4 0
      src/interfaces/IJavaScriptObfsucator.d.ts
  37. 4 0
      src/interfaces/IObfuscator.d.ts
  38. 5 0
      src/interfaces/ISourceMapCorrector.d.ts
  39. 0 8
      src/interfaces/custom-nodes/ICustomNodeWithData.d.ts
  40. 0 5
      src/interfaces/custom-nodes/ICustomNodeWithIdentifier.d.ts
  41. 11 0
      src/interfaces/event-emitters/IObfuscationEventEmitter.d.ts
  42. 8 3
      src/interfaces/node-transformers/IControlFlowReplacer.d.ts
  43. 4 0
      src/interfaces/node-transformers/INodeTransformer.d.ts
  44. 7 0
      src/interfaces/node-transformers/IObfuscatorReplacer.d.ts
  45. 9 0
      src/interfaces/node-transformers/IObfuscatorReplacerWithStorage.d.ts
  46. 0 3
      src/interfaces/node-transformers/IReplacer.d.ts
  47. 1 0
      src/interfaces/options/IOptions.d.ts
  48. 5 0
      src/interfaces/stack-trace-analyzer/ICalleeDataExtractor.d.ts
  49. 4 0
      src/interfaces/stack-trace-analyzer/IStackTraceAnalyzer.d.ts
  50. 41 0
      src/interfaces/storages/IStorage.d.ts
  51. 97 22
      src/node-transformers/node-control-flow-transformers/FunctionControlFlowTransformer.ts
  52. 3 17
      src/node-transformers/node-control-flow-transformers/control-flow-replacers/AbstractControlFlowReplacer.ts
  53. 73 24
      src/node-transformers/node-control-flow-transformers/control-flow-replacers/BinaryExpressionControlFlowReplacer.ts
  54. 15 16
      src/node-transformers/node-obfuscators/CatchClauseObfuscator.ts
  55. 16 18
      src/node-transformers/node-obfuscators/FunctionDeclarationObfuscator.ts
  56. 15 16
      src/node-transformers/node-obfuscators/FunctionObfuscator.ts
  57. 15 16
      src/node-transformers/node-obfuscators/LabeledStatementObfuscator.ts
  58. 4 4
      src/node-transformers/node-obfuscators/LiteralObfuscator.ts
  59. 4 4
      src/node-transformers/node-obfuscators/MemberExpressionObfuscator.ts
  60. 5 6
      src/node-transformers/node-obfuscators/MethodDefinitionObfuscator.ts
  61. 16 18
      src/node-transformers/node-obfuscators/VariableDeclarationObfuscator.ts
  62. 4 3
      src/node-transformers/node-obfuscators/replacers/AbstractReplacer.ts
  63. 9 22
      src/node-transformers/node-obfuscators/replacers/IdentifierReplacer.ts
  64. 1 1
      src/node-transformers/node-obfuscators/replacers/NumberLiteralReplacer.ts
  65. 15 19
      src/node-transformers/node-obfuscators/replacers/StringLiteralReplacer.ts
  66. 8 0
      src/node/Node.ts
  67. 1 1
      src/node/NodeAppender.ts
  68. 31 14
      src/node/NodeUtils.ts
  69. 9 1
      src/options/Options.ts
  70. 29 5
      src/options/OptionsNormalizer.ts
  71. 3 2
      src/options/presets/Default.ts
  72. 3 2
      src/options/presets/NoCustomNodes.ts
  73. 1 1
      src/stack-trace-analyzer/StackTraceAnalyzer.ts
  74. 1 1
      src/stack-trace-analyzer/callee-data-extractors/FunctionDeclarationCalleeDataExtractor.ts
  75. 1 1
      src/stack-trace-analyzer/callee-data-extractors/FunctionExpressionCalleeDataExtractor.ts
  76. 1 1
      src/stack-trace-analyzer/callee-data-extractors/ObjectExpressionCalleeDataExtractor.ts
  77. 31 0
      src/storages/ArrayStorage.ts
  78. 28 1
      src/storages/MapStorage.ts
  79. 2 0
      src/storages/custom-node-group/CustomNodeGroupStorage.ts
  80. 14 1
      src/storages/string-array/StringArrayStorage.ts
  81. 2 2
      src/utils/CryptUtils.ts
  82. 21 10
      src/utils/RandomGeneratorUtils.ts
  83. 19 19
      src/utils/Utils.ts
  84. 66 3
      test/dev/dev.ts
  85. 18 18
      test/fixtures/compile-performance.js
  86. 3 0
      test/fixtures/node-transformers/node-control-flow-transformers/control-flow-replacers/binary-expression-control-flow-replacer-1.js
  87. 4 0
      test/fixtures/node-transformers/node-control-flow-transformers/control-flow-replacers/binary-expression-control-flow-replacer-2.js
  88. 13 1
      test/functional-tests/JavaScriptObfuscator.spec.ts
  89. 1 1
      test/functional-tests/JavaScriptObfuscatorInternal.spec.ts
  90. 1 1
      test/functional-tests/custom-nodes/console-output-nodes/ConsoleOutputDisableExpressionNode.spec.ts
  91. 1 1
      test/functional-tests/custom-nodes/domain-lock-nodes/DomainLockNode.spec.ts
  92. 1 1
      test/functional-tests/custom-nodes/string-array-nodes/StringArrayCallsWrapper.spec.ts
  93. 1 1
      test/functional-tests/custom-nodes/string-array-nodes/StringArrayNode.spec.ts
  94. 1 1
      test/functional-tests/custom-nodes/string-array-nodes/StringArrayRotateFunctionNode.spec.ts
  95. 77 0
      test/functional-tests/node-transformers/node-control-flow-transformers/control-flow-replacers/BinaryExpressionControlFlowReplacer.spec.ts
  96. 5 5
      test/functional-tests/node-transformers/node-obfuscators/CatchClauseObfuscator.spec.ts
  97. 1 1
      test/functional-tests/node-transformers/node-obfuscators/FunctionDeclarationObfuscator.spec.ts
  98. 1 1
      test/functional-tests/node-transformers/node-obfuscators/FunctionObfuscator.spec.ts
  99. 1 1
      test/functional-tests/node-transformers/node-obfuscators/LabeledStatementObfuscator.spec.ts
  100. 1 1
      test/functional-tests/node-transformers/node-obfuscators/LiteralObfuscator.spec.ts

+ 8 - 1
.babelrc

@@ -2,5 +2,12 @@
   "generatorOpts": {
     "retainFunctionParens": true
   },
-  "presets": ["es2015"]
+  "presets": ["es2015"],
+  "plugins": [
+    ["transform-runtime", {
+      "helpers": true,
+      "polyfill": true,
+      "regenerator": false
+    }]
+  ]
 }

+ 23 - 14
README.md

@@ -78,7 +78,7 @@ var _0xabf1 = [
 Returns `ObfuscationResult` object which contains two public methods:
 
 * `getObfuscatedCode()` - returns `string` with obfuscated code;
-* `getSourceMap()` - if `sourceMap` options is enable - returns `string` with source map or an empty string if `sourceMapMode` option is set as `inline`.
+* `getSourceMap()` - if [`sourceMap`](#sourcemap) option is enabled - returns `string` with source map or an empty string if [`sourceMapMode`](#sourcemapmode) option is set as `inline`.
 
 Calling `toString()` for `ObfuscationResult` object will return `string` with obfuscated code.
 
@@ -171,7 +171,7 @@ Compact code output on one line.
 ### `controlFlowFlattening`
 Type: `boolean` Default: `false`
 
-Enables code control flow flattening. Control flow flattening is a structure transformation of the source code that hindering the comprehension of the program.
+Enables code control flow flattening. Control flow flattening is a structure transformation of the source code that hinders program comprehension.
 
 Example:
 ```ts
@@ -210,6 +210,15 @@ Example:
 }());
 ```
 
+### `controlFlowFlatteningThreshold`
+Type: `number` Default: `0.75` Min: `0` Max: `1`
+
+The probability that the [`controlFlowFlattening`](#controlflowflattening) transformation will be applied to the node.
+
+This setting is especially useful for large code size because large amount of control flow transformations can slow down your code and increase code size.
+
+`controlFlowFlatteningThreshold: 0` equals to `controlFlowFlattening: false`.
+
 ### `debugProtection`
 Type: `boolean` Default: `false`
 
@@ -225,7 +234,7 @@ Type: `boolean` Default: `false`
 
 ##### :warning: Can freeze your browser! Use at own risk.
 
-If checked, an interval is used to force the debug mode on the Console tab, making it harder to use other features of the Developer Tools. Works if `debugProtection` is enabled.
+If checked, an interval is used to force the debug mode on the Console tab, making it harder to use other features of the Developer Tools. Works if [`debugProtection`](#debugprotection) is enabled.
 
 ### `disableConsoleOutput`
 Type: `boolean` Default: `true`
@@ -258,7 +267,7 @@ Example:
 ### `rotateStringArray`
 Type: `boolean` Default: `true`
 
-##### :warning: `stringArray` must be enabled
+##### :warning: [`stringArray`](#stringarray) must be enabled
 
 Shift the `stringArray` array by a fixed and random (generated at the code obfuscation) places. This makes it harder to match the order of the removed strings to their original place.
 
@@ -275,7 +284,7 @@ If seed is `0` - random generator will work without seed.
 Type: `boolean` Default: `false`
 
 ##### :warning: Don't change obfuscated code in any way after obfuscation with this option, because any change like uglifying of code can trigger self defending and code wont work anymore!
-##### :warning: this option forcibly set `compact` value to `true`
+##### :warning: This option forcibly sets `compact` value to `true`
 
 This option makes the output code resilient against formatting and variable renaming. If one tries to use a JavaScript beautifier on the obfuscated code, the code won't work anymore, making it harder to understand and modify it.
 
@@ -284,12 +293,12 @@ Type: `boolean` Default: `false`
 
 Enables source map generation for obfuscated code.
 
-Source maps can be useful to help you debug your obfuscated Java Script source code. If you want or need to debug in production, you can upload the separate source map file to a secret location and then point your browser there. 
+Source maps can be useful to help you debug your obfuscated JavaScript source code. If you want or need to debug in production, you can upload the separate source map file to a secret location and then point your browser there. 
 
 ### `sourceMapBaseUrl`
 Type: `string` Default: ``
 
-Sets base url to the source map import url when `sourceMapMode: 'separate'`.
+Sets base url to the source map import url when [`sourceMapMode: 'separate'`](#sourcemapmode).
  
 CLI example:
 ```
@@ -321,12 +330,12 @@ Type: `string` Default: `separate`
 
 Specifies source map generation mode:
 * `inline` - emit a single file with source maps instead of having a separate file;
-* `separate` - generates corresponding '.map' file with source map. If obfuscator run through CLI - adds link to source map file to the end of file with obfuscated code `//# sourceMappingUrl=file.js.map`.
+* `separate` - generates corresponding '.map' file with source map. In case you run obfuscator through CLI - adds link to source map file to the end of file with obfuscated code `//# sourceMappingUrl=file.js.map`.
 
 ### `stringArray`
 Type: `boolean` Default: `true`
 
-Removes string literals and place them in a special array. For instance the string `"Hello World"` in `var m = "Hello World";` will be replaced with something like `var m = _0x12c456[0x1];`
+Removes string literals and place them in a special array. For instance, the string `"Hello World"` in `var m = "Hello World";` will be replaced with something like `var m = _0x12c456[0x1];`
     
 ### `stringArrayEncoding`
 Type: `boolean|string` Default: `false`
@@ -335,7 +344,7 @@ Type: `boolean|string` Default: `false`
 
 This option can slightly slow down your script.
 
-Encode all string literals of the `stringArray` using `base64` or `rc4` and inserts a special code that used to decode it back at runtime.
+Encode all string literals of the [`stringArray`](#stringarray) using `base64` or `rc4` and inserts a special code that used to decode it back at runtime.
 
 Available values:
 * `true` (`boolean`): encode `stringArray` values using `base64`
@@ -346,11 +355,11 @@ Available values:
 ### `stringArrayThreshold`
 Type: `number` Default: `0.8` Min: `0` Max: `1`
 
-##### :warning: `stringArray` option must be enabled
+##### :warning: [`stringArray`](#stringarray) option must be enabled
 
 You can use this setting to adjust the probability (from 0 to 1) that a string literal will be inserted into the `stringArray`.
 
-This setting is useful with large code size because repeatdely calls to the `stringArray` array can slightly slow down your code.
+This setting is especially useful for large code size because it repeatedly calls to the `string array` and can slow down your code.
 
 `stringArrayThreshold: 0` equals to `stringArray: false`.
 
@@ -359,7 +368,7 @@ Type: `boolean` Default: `true`
 
 Allows to enable/disable string conversion to unicode escape sequence.
 
-Unicode escape sequence greatly increases code size. Recommend to disable this option when using `stringArrayEncoding` (especially with `rc4` encoding).
+Unicode escape sequence increases code size greatly. It is recommended to disable this option when using [`stringArrayEncoding`](#stringarrayencoding) (especially with `rc4` encoding).
 
 ## License
 Copyright (C) 2016 [Timofey Kachalov](http://github.com/sanex3339).
@@ -382,4 +391,4 @@ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
-THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Разница между файлами не показана из-за своего большого размера
+ 417 - 336
dist/index.js


+ 16 - 12
package.json

@@ -1,6 +1,6 @@
 {
   "name": "javascript-obfuscator",
-  "version": "0.9.0-dev.1",
+  "version": "0.9.0-dev.2",
   "description": "JavaScript obfuscator",
   "keywords": [
     "obfuscator",
@@ -20,19 +20,21 @@
     "javascript-obfuscator": "./bin/javascript-obfuscator.js"
   },
   "dependencies": {
-    "babel-polyfill": "6.20.0",
+    "babel-polyfill": "^6.20.0",
+    "babel-runtime": "^6.20.0",
     "chance": "1.0.4",
     "class-validator": "0.6.6",
     "commander": "2.9.0",
     "escodegen": "1.8.1",
     "esprima": "3.1.2",
     "estraverse": "4.2.0",
-    "inversify": "^3.0.0-rc.1",
-    "is-equal": "^1.5.3",
+    "inversify": "^3.0.0-rc.2",
+    "lodash": "^4.17.2",
     "mkdirp": "0.5.1",
     "reflect-metadata": "^0.1.8",
     "source-map-support": "0.4.6",
-    "string-template": "^1.0.0"
+    "string-template": "^1.0.0",
+    "tslib": "^1.2.0"
   },
   "devDependencies": {
     "@types/chai": "3.4.34",
@@ -43,22 +45,24 @@
     "@types/estraverse": "0.0.6",
     "@types/estree": "0.0.34",
     "@types/joi": "9.0.33",
+    "@types/lodash": "^4.14.43",
     "@types/mkdirp": "0.3.29",
     "@types/mocha": "2.2.33",
-    "@types/node": "6.0.51",
-    "@types/sinon": "1.16.32",
-    "@types/string-template": "^1.0.2",
-    "awesome-typescript-loader": "^3.0.0-beta.9",
+    "@types/node": "6.0.52",
+    "@types/sinon": "1.16.33",
+    "@types/string-template": "1.0.2",
+    "awesome-typescript-loader": "3.0.0-beta.17",
     "babel-cli": "6.18.0",
-    "babel-loader": "6.2.9",
+    "babel-loader": "6.2.10",
+    "babel-plugin-transform-runtime": "^6.15.0",
     "babel-preset-es2015": "6.18.0",
     "chai": "3.5.0",
     "coveralls": "2.11.15",
     "istanbul": "1.1.0-alpha.1",
     "mocha": "3.2.0",
     "sinon": "2.0.0-pre.3",
-    "ts-node": "1.7.0",
-    "tslint": "4.0.2",
+    "ts-node": "1.7.2",
+    "tslint": "4.1.1",
     "typescript": "2.1.4",
     "webpack": "2.1.0-beta.27",
     "webpack-node-externals": "1.5.4"

+ 12 - 1
src/JavaScriptObfuscator.ts

@@ -4,15 +4,20 @@ if (!(<any>global)._babelPolyfill) {
     require('babel-polyfill');
 }
 
+import { ServiceIdentifiers } from './container/ServiceIdentifiers';
+
+import { Chance } from 'chance';
+
 import { TInputOptions } from './types/options/TInputOptions';
 
 import { IInversifyContainerFacade } from './interfaces/container/IInversifyContainerFacade';
 import { IJavaScriptObfuscator } from './interfaces/IJavaScriptObfsucator';
 import { IObfuscationResult } from './interfaces/IObfuscationResult';
+import { IOptions } from './interfaces/options/IOptions';
 
 import { InversifyContainerFacade } from './container/InversifyContainerFacade';
 import { JavaScriptObfuscatorCLI } from './cli/JavaScriptObfuscatorCLI';
-import { ServiceIdentifiers } from './container/ServiceIdentifiers';
+import { RandomGeneratorUtils } from './utils/RandomGeneratorUtils';
 
 export class JavaScriptObfuscator {
     /**
@@ -22,6 +27,12 @@ export class JavaScriptObfuscator {
      */
     public static obfuscate (sourceCode: string, inputOptions: TInputOptions = {}): IObfuscationResult {
         const inversifyContainerFacade: IInversifyContainerFacade = new InversifyContainerFacade(inputOptions);
+        const options: IOptions = inversifyContainerFacade.get<IOptions>(ServiceIdentifiers.IOptions);
+
+        if (options.seed !== 0) {
+            RandomGeneratorUtils.setRandomGenerator(new Chance(options.seed));
+        }
+
         const javaScriptObfuscator: IJavaScriptObfuscator = inversifyContainerFacade
             .get<IJavaScriptObfuscator>(ServiceIdentifiers.IJavaScriptObfuscator);
 

+ 1 - 7
src/JavaScriptObfuscatorInternal.ts

@@ -5,15 +5,13 @@ import * as esprima from 'esprima';
 import * as escodegen from 'escodegen';
 import * as ESTree from 'estree';
 
+import { IGeneratorOutput } from './interfaces/IGeneratorOutput';
 import { IJavaScriptObfuscator } from './interfaces/IJavaScriptObfsucator';
 import { IObfuscationResult } from './interfaces/IObfuscationResult';
 import { IObfuscator } from './interfaces/IObfuscator';
-import { IGeneratorOutput } from './interfaces/IGeneratorOutput';
 import { IOptions } from './interfaces/options/IOptions';
 import { ISourceMapCorrector } from './interfaces/ISourceMapCorrector';
 
-import { RandomGeneratorUtils } from './utils/RandomGeneratorUtils';
-
 @injectable()
 export class JavaScriptObfuscatorInternal implements IJavaScriptObfuscator {
     /**
@@ -102,10 +100,6 @@ export class JavaScriptObfuscatorInternal implements IJavaScriptObfuscator {
      * @returns {IObfuscationResult}
      */
     public obfuscate (sourceCode: string): IObfuscationResult {
-        if (this.options.seed !== 0) {
-            RandomGeneratorUtils.setRandomGeneratorSeed(this.options.seed);
-        }
-
         // parse AST tree
         const astTree: ESTree.Program = esprima.parse(sourceCode, JavaScriptObfuscatorInternal.esprimaParams);
 

+ 2 - 2
src/Obfuscator.ts

@@ -29,7 +29,7 @@ export class Obfuscator implements IObfuscator {
     /**
      * @type {Map<string, NodeTransformers[]>}
      */
-    private static readonly nodeControlFlowTransformersMap: Map <string, NodeTransformers[]> = new Map <string, NodeTransformers[]> ([
+    private static readonly nodeControlFlowTransformersMap: Map <string, NodeTransformers[]> = new Map([
         [NodeType.FunctionDeclaration, [NodeTransformers.FunctionControlFlowTransformer]],
         [NodeType.FunctionExpression, [NodeTransformers.FunctionControlFlowTransformer]]
     ]);
@@ -37,7 +37,7 @@ export class Obfuscator implements IObfuscator {
     /**
      * @type {Map<string, NodeTransformers[]>}
      */
-    private static readonly nodeObfuscatorsMap: Map <string, NodeTransformers[]> = new Map <string, NodeTransformers[]> ([
+    private static readonly nodeObfuscatorsMap: Map <string, NodeTransformers[]> = new Map([
         [NodeType.ArrowFunctionExpression, [NodeTransformers.FunctionObfuscator]],
         [NodeType.ClassDeclaration, [NodeTransformers.FunctionDeclarationObfuscator]],
         [NodeType.CatchClause, [NodeTransformers.CatchClauseObfuscator]],

+ 1 - 3
src/cli/CLIUtils.ts

@@ -4,8 +4,6 @@ import * as path from 'path';
 
 import { IPackageConfig } from '../interfaces/IPackageConfig';
 
-import { Utils } from '../utils/Utils';
-
 export class CLIUtils {
     /**
      * @type {string[]}
@@ -102,7 +100,7 @@ export class CLIUtils {
             throw new ReferenceError(`Given input path must be a valid file path`);
         }
 
-        if (!Utils.arrayContains(CLIUtils.availableInputExtensions, path.extname(inputPath))) {
+        if (!CLIUtils.availableInputExtensions.includes(path.extname(inputPath))) {
             throw new ReferenceError(`Input file must have .js extension`);
         }
     }

+ 8 - 4
src/cli/JavaScriptObfuscatorCLI.ts

@@ -9,11 +9,10 @@ import { IObfuscationResult } from '../interfaces/IObfuscationResult';
 import { SourceMapMode } from '../enums/SourceMapMode';
 import { StringArrayEncoding } from '../enums/StringArrayEncoding';
 
-import { DEFAULT_PRESET } from '../preset-options/DefaultPreset';
+import { DEFAULT_PRESET } from '../options/presets/Default';
 
 import { CLIUtils } from './CLIUtils';
 import { JavaScriptObfuscator } from '../JavaScriptObfuscator';
-import { Utils } from '../utils/Utils';
 
 export class JavaScriptObfuscatorCLI {
     /**
@@ -104,7 +103,7 @@ export class JavaScriptObfuscatorCLI {
     public run (): void {
         this.configureCommands();
 
-        if (!this.arguments.length || Utils.arrayContains(this.arguments, '--help')) {
+        if (!this.arguments.length || this.arguments.includes('--help')) {
             this.commands.outputHelp();
 
             return;
@@ -129,7 +128,7 @@ export class JavaScriptObfuscatorCLI {
                 continue;
             }
 
-            if (!Utils.arrayContains(availableOptions, option)) {
+            if (!availableOptions.includes(option)) {
                 continue;
             }
 
@@ -160,6 +159,11 @@ export class JavaScriptObfuscatorCLI {
                 'Enables control flow flattening',
                 JavaScriptObfuscatorCLI.parseBoolean
             )
+            .option(
+                '--controlFlowFlatteningThreshold <number>',
+                'The probability that the control flow flattening transformation will be applied to the node',
+                parseFloat
+            )
             .option(
                 '--debugProtection <boolean>',
                 'Disable browser Debug panel (can cause DevTools enabled browser freeze)',

+ 4 - 3
src/container/ServiceIdentifiers.ts

@@ -5,7 +5,7 @@ export const ServiceIdentifiers: any = {
     'Factory<ICustomNodeGroup>': Symbol('Factory<ICustomNodeGroup>'),
     'Factory<INodeTransformer[]>': Symbol('Factory<INodeTransformer[]>'),
     'Factory<IObfuscationResult>': Symbol('Factory<IObfuscationResult>'),
-    'Factory<IReplacer>': Symbol('Factory<IReplacer>'),
+    'Factory<IObfuscatorReplacer>': Symbol('Factory<IObfuscatorReplacer>'),
     'Factory<IStorage<ICustomNode>>': Symbol('Factory<IStorage<ICustomNode>>'),
     ICalleeDataExtractor: Symbol('ICalleeDataExtractor'),
     ICustomNode: Symbol('ICustomNode'),
@@ -17,9 +17,10 @@ export const ServiceIdentifiers: any = {
     IObfuscationResult: Symbol('IObfuscationResult'),
     IObfuscator: Symbol('IObfuscator'),
     IOptions: Symbol('IOptions'),
-    IReplacer: Symbol('IReplacer'),
+    IObfuscatorReplacer: Symbol('IObfuscatorReplacer'),
     ISourceMapCorrector: Symbol('ISourceMapCorrector'),
     IStackTraceAnalyzer: Symbol('IStackTraceAnalyzer'),
     'IStorage<ICustomNode>': Symbol('IStorage<ICustomNode>'),
-    'IStorage<ICustomNodeGroup>': Symbol('IStorage<ICustomNodeGroup>')
+    'IStorage<ICustomNodeGroup>': Symbol('IStorage<ICustomNodeGroup>'),
+    'IStorage<string>': Symbol('IStorage<string>')
 };

+ 1 - 1
src/container/modules/node-transformers/NodeControlFlowTransformersModule.ts

@@ -14,7 +14,7 @@ export const nodeControlFlowTransformersModule: interfaces.ContainerModule = new
 
     bind<IControlFlowReplacer>(ServiceIdentifiers['Factory<IControlFlowReplacer>'])
         .toFactory<IControlFlowReplacer>((context: interfaces.Context) => {
-            const cache: Map <NodeControlFlowReplacers, IControlFlowReplacer> = new Map <NodeControlFlowReplacers, IControlFlowReplacer> ();
+            const cache: Map <NodeControlFlowReplacers, IControlFlowReplacer> = new Map();
 
             return (replacerName: NodeControlFlowReplacers) => {
                 if (cache.has(replacerName)) {

+ 11 - 11
src/container/modules/node-transformers/NodeObfuscatorsModule.ts

@@ -1,7 +1,7 @@
 import { ContainerModule, interfaces } from 'inversify';
 import { ServiceIdentifiers } from '../../ServiceIdentifiers';
 
-import { IReplacer } from '../../../interfaces/node-transformers/IReplacer';
+import { IObfuscatorReplacer } from '../../../interfaces/node-transformers/IObfuscatorReplacer';
 
 import { NodeObfuscatorsReplacers } from '../../../enums/container/NodeObfuscatorsReplacers';
 
@@ -11,33 +11,33 @@ import { NumberLiteralReplacer } from '../../../node-transformers/node-obfuscato
 import { StringLiteralReplacer } from '../../../node-transformers/node-obfuscators/replacers/StringLiteralReplacer';
 
 export const nodeObfuscatorsModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
-    bind<IReplacer>(ServiceIdentifiers.IReplacer)
+    bind<IObfuscatorReplacer>(ServiceIdentifiers.IObfuscatorReplacer)
         .to(BooleanLiteralReplacer)
         .whenTargetNamed(NodeObfuscatorsReplacers.BooleanReplacer);
 
-    bind<IReplacer>(ServiceIdentifiers.IReplacer)
+    bind<IObfuscatorReplacer>(ServiceIdentifiers.IObfuscatorReplacer)
         .to(IdentifierReplacer)
         .whenTargetNamed(NodeObfuscatorsReplacers.IdentifierReplacer);
 
-    bind<IReplacer>(ServiceIdentifiers.IReplacer)
+    bind<IObfuscatorReplacer>(ServiceIdentifiers.IObfuscatorReplacer)
         .to(NumberLiteralReplacer)
         .whenTargetNamed(NodeObfuscatorsReplacers.NumberLiteralReplacer);
 
-    bind<IReplacer>(ServiceIdentifiers.IReplacer)
+    bind<IObfuscatorReplacer>(ServiceIdentifiers.IObfuscatorReplacer)
         .to(StringLiteralReplacer)
         .whenTargetNamed(NodeObfuscatorsReplacers.StringLiteralReplacer);
 
-    bind<IReplacer>(ServiceIdentifiers['Factory<IReplacer>'])
-        .toFactory<IReplacer>((context: interfaces.Context) => {
-            const cache: Map <NodeObfuscatorsReplacers, IReplacer> = new Map <NodeObfuscatorsReplacers, IReplacer> ();
+    bind<IObfuscatorReplacer>(ServiceIdentifiers['Factory<IObfuscatorReplacer>'])
+        .toFactory<IObfuscatorReplacer>((context: interfaces.Context) => {
+            const cache: Map <NodeObfuscatorsReplacers, IObfuscatorReplacer> = new Map();
 
             return (replacerName: NodeObfuscatorsReplacers) => {
                 if (cache.has(replacerName)) {
-                    return <IReplacer>cache.get(replacerName);
+                    return <IObfuscatorReplacer>cache.get(replacerName);
                 }
 
-                const obfuscationReplacer: IReplacer = context.container.getNamed<IReplacer>(
-                    ServiceIdentifiers.IReplacer,
+                const obfuscationReplacer: IObfuscatorReplacer = context.container.getNamed<IObfuscatorReplacer>(
+                    ServiceIdentifiers.IObfuscatorReplacer,
                     replacerName
                 );
 

+ 1 - 1
src/container/modules/node-transformers/NodeTransformersModule.ts

@@ -63,7 +63,7 @@ export const nodeTransformersModule: interfaces.ContainerModule = new ContainerM
     // node transformers factory
     bind<INodeTransformer[]>(ServiceIdentifiers['Factory<INodeTransformer[]>'])
         .toFactory<INodeTransformer[]>((context: interfaces.Context) => {
-            const cache: Map <NodeTransformers, INodeTransformer> = new Map <NodeTransformers, INodeTransformer> ();
+            const cache: Map <NodeTransformers, INodeTransformer> = new Map();
 
             return (nodeTransformersMap: Map<string, NodeTransformers[]>) => (nodeType: string) => {
                 const nodeTransformers: NodeTransformers[] = nodeTransformersMap.get(nodeType) || [];

+ 1 - 1
src/container/modules/stack-trace-analyzer/StackTraceAnalyzerModule.ts

@@ -32,7 +32,7 @@ export const stackTraceAnalyzerModule: interfaces.ContainerModule = new Containe
     // node transformers factory
     bind<ICalleeDataExtractor>(ServiceIdentifiers['Factory<ICalleeDataExtractor>'])
         .toFactory<ICalleeDataExtractor>((context: interfaces.Context) => {
-            const cache: Map <CalleeDataExtractors, ICalleeDataExtractor> = new Map <CalleeDataExtractors, ICalleeDataExtractor> ();
+            const cache: Map <CalleeDataExtractors, ICalleeDataExtractor> = new Map();
 
             return (calleeDataExtractorName: CalleeDataExtractors) => {
                 if (cache.has(calleeDataExtractorName)) {

+ 5 - 0
src/container/modules/storages/StoragesModule.ts

@@ -7,6 +7,7 @@ import { IStorage } from '../../../interfaces/storages/IStorage';
 
 import { ControlFlowStorage } from '../../../storages/control-flow/ControlFlowStorage';
 import { CustomNodeGroupStorage } from '../../../storages/custom-node-group/CustomNodeGroupStorage';
+import { StringArrayStorage } from '../../../storages/string-array/StringArrayStorage';
 
 export const storagesModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
     // storages
@@ -17,6 +18,10 @@ export const storagesModule: interfaces.ContainerModule = new ContainerModule((b
     bind<IStorage<ICustomNode>>(ServiceIdentifiers['IStorage<ICustomNode>'])
         .to(ControlFlowStorage);
 
+    bind<IStorage<string>>(ServiceIdentifiers['IStorage<string>'])
+        .to(StringArrayStorage)
+        .inSingletonScope();
+
     // controlFlowStorage factory
     bind<IStorage<ICustomNode>>(ServiceIdentifiers['Factory<IStorage<ICustomNode>>'])
         .toFactory<IStorage<ICustomNode>>((context: interfaces.Context) => {

+ 9 - 2
src/custom-nodes/AbstractCustomNode.ts

@@ -31,7 +31,9 @@ export abstract class AbstractCustomNode implements ICustomNode {
     /**
      * @returns {string}
      */
-    public abstract getCode (): string;
+    public getCode (): string {
+        return NodeUtils.convertStructureToCode(this.getNode());
+    }
 
     /**
      * @returns {TStatement[]}
@@ -44,6 +46,11 @@ export abstract class AbstractCustomNode implements ICustomNode {
      * @returns {TStatement[]}
      */
     protected getNodeStructure (): TStatement[] {
-        return NodeUtils.convertCodeToStructure(this.getCode());
+        return NodeUtils.convertCodeToStructure(this.getTemplate());
     }
+
+    /**
+     * @returns {string}
+     */
+    protected abstract getTemplate (): string;
 }

+ 1 - 1
src/custom-nodes/console-output-nodes/ConsoleOutputDisableExpressionNode.ts

@@ -39,7 +39,7 @@ export class ConsoleOutputDisableExpressionNode extends AbstractCustomNode {
     /**
      * @returns {string}
      */
-    public getCode (): string {
+    protected getTemplate (): string {
         return format(ConsoleOutputDisableExpressionTemplate(), {
             consoleLogDisableFunctionName: RandomGeneratorUtils.getRandomVariableName(),
             singleNodeCallControllerFunctionName: this.callsControllerFunctionName

+ 2 - 2
src/custom-nodes/control-flow-replacers-nodes/binary-expression-control-flow-replacer-nodes/BinaryExpressionFunctionNode.ts

@@ -32,14 +32,14 @@ export class BinaryExpressionFunctionNode extends AbstractCustomNode {
     /**
      * @param operator
      */
-    initialize (operator: string): void {
+    public initialize (operator: string): void {
         this.operator = operator;
     }
 
     /**
      * @returns {string}
      */
-    public getCode (): string {
+    protected getTemplate (): string {
         return format(BinaryExpressionFunctionTemplate(), {
             functionName: RandomGeneratorUtils.getRandomVariableName(1),
             operator: this.operator

+ 1 - 1
src/custom-nodes/control-flow-replacers-nodes/binary-expression-control-flow-replacer-nodes/ControlFlowStorageCallNode.ts

@@ -67,7 +67,7 @@ export class ControlFlowStorageCallNode extends AbstractCustomNode {
     /**
      * @returns {string}
      */
-    public getCode (): string {
+    protected getTemplate (): string {
         return format(ControlFlowStorageCallTemplate(), {
             controlFlowStorageKey: this.controlFlowStorageKey,
             controlFlowStorageName: this.controlFlowStorageName,

+ 3 - 11
src/custom-nodes/control-flow-storage-nodes/ControlFlowStorageNode.ts

@@ -21,12 +21,6 @@ export class ControlFlowStorageNode extends AbstractCustomNode {
     @initializable()
     private controlFlowStorage: IStorage <ICustomNode>;
 
-    /**
-     * @type {string}
-     */
-    @initializable()
-    private controlFlowStorageName: string;
-
     /**
      * @param options
      */
@@ -38,20 +32,18 @@ export class ControlFlowStorageNode extends AbstractCustomNode {
 
     /**
      * @param controlFlowStorage
-     * @param controlFlowStorageName
      */
-    public initialize (controlFlowStorage: IStorage <ICustomNode>, controlFlowStorageName: string): void {
+    public initialize (controlFlowStorage: IStorage <ICustomNode>): void {
         this.controlFlowStorage = controlFlowStorage;
-        this.controlFlowStorageName = controlFlowStorageName;
     }
 
     /**
      * @returns {string}
      */
-    public getCode (): string {
+    protected getTemplate (): string {
         return format(ControlFlowStorageTemplate(), {
             controlFlowStorage: this.controlFlowStorage.toString(),
-            controlFlowStorageName: this.controlFlowStorageName
+            controlFlowStorageName: this.controlFlowStorage.getStorageId()
         });
     }
 }

+ 1 - 1
src/custom-nodes/debug-protection-nodes/DebugProtectionFunctionCallNode.ts

@@ -38,7 +38,7 @@ export class DebugProtectionFunctionCallNode extends AbstractCustomNode {
     /**
      * @returns {string}
      */
-    public getCode (): string {
+    protected getTemplate (): string {
         return format(DebugProtectionFunctionCallTemplate(), {
             debugProtectionFunctionName: this.debugProtectionFunctionName
         });

+ 1 - 1
src/custom-nodes/debug-protection-nodes/DebugProtectionFunctionIntervalNode.ts

@@ -38,7 +38,7 @@ export class DebugProtectionFunctionIntervalNode extends AbstractCustomNode {
     /**
      * @returns {string}
      */
-    public getCode (): string {
+    protected getTemplate (): string {
         return format(DebugProtectionFunctionIntervalTemplate(), {
             debugProtectionFunctionName: this.debugProtectionFunctionName
         });

+ 1 - 1
src/custom-nodes/debug-protection-nodes/DebugProtectionFunctionNode.ts

@@ -38,7 +38,7 @@ export class DebugProtectionFunctionNode extends AbstractCustomNode {
     /**
      * @returns {string}
      */
-    public getCode (): string {
+    protected getTemplate (): string {
         return format(DebugProtectionFunctionTemplate(), {
             debugProtectionFunctionName: this.debugProtectionFunctionName
         });

+ 1 - 1
src/custom-nodes/domain-lock-nodes/DomainLockNode.ts

@@ -40,7 +40,7 @@ export class DomainLockNode extends AbstractCustomNode {
     /**
      * @returns {string}
      */
-    public getCode (): string {
+    protected getTemplate (): string {
         let domainsString: string = this.options.domainLock.join(';'),
             [hiddenDomainsString, diff]: string[] = CryptUtils.hideString(domainsString, domainsString.length * 3);
 

+ 2 - 2
src/custom-nodes/node-calls-controller-nodes/NodeCallsControllerFunctionNode.ts

@@ -13,7 +13,7 @@ import { initializable } from '../../decorators/Initializable';
 
 import { SingleNodeCallControllerTemplate } from '../../templates/custom-nodes/SingleNodeCallControllerTemplate';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../preset-options/NoCustomNodesPreset';
+import { NO_CUSTOM_NODES_PRESET } from '../../options/presets/NoCustomNodes';
 
 import { AbstractCustomNode } from '../AbstractCustomNode';
 import { JavaScriptObfuscator } from '../../JavaScriptObfuscator';
@@ -53,7 +53,7 @@ export class NodeCallsControllerFunctionNode extends AbstractCustomNode {
     /**
      * @returns {string}
      */
-    public getCode (): string {
+    protected getTemplate (): string {
         if (this.appendEvent === ObfuscationEvents.AfterObfuscation) {
             return JavaScriptObfuscator.obfuscate(
                 format(SingleNodeCallControllerTemplate(), {

+ 2 - 2
src/custom-nodes/self-defending-nodes/SelfDefendingUnicodeNode.ts

@@ -7,7 +7,7 @@ import { IOptions } from '../../interfaces/options/IOptions';
 
 import { initializable } from '../../decorators/Initializable';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../preset-options/NoCustomNodesPreset';
+import { NO_CUSTOM_NODES_PRESET } from '../../options/presets/NoCustomNodes';
 
 import { SelfDefendingTemplate } from '../../templates/custom-nodes/self-defending-nodes/self-defending-unicode-node/SelfDefendingTemplate';
 
@@ -42,7 +42,7 @@ export class SelfDefendingUnicodeNode extends AbstractCustomNode {
     /**
      * @returns {string}
      */
-    public getCode (): string {
+    protected getTemplate (): string {
         return JavaScriptObfuscator.obfuscate(
             format(SelfDefendingTemplate(), {
                 selfDefendingFunctionName: RandomGeneratorUtils.getRandomVariableName(),

+ 27 - 35
src/custom-nodes/string-array-nodes/StringArrayCallsWrapper.ts

@@ -3,7 +3,6 @@ import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
 import * as format from 'string-template';
 
-import { ICustomNodeWithIdentifier } from '../../interfaces/custom-nodes/ICustomNodeWithIdentifier';
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IStorage } from '../../interfaces/storages/IStorage';
 
@@ -11,7 +10,7 @@ import { StringArrayEncoding } from '../../enums/StringArrayEncoding';
 
 import { initializable } from '../../decorators/Initializable';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../preset-options/NoCustomNodesPreset';
+import { NO_CUSTOM_NODES_PRESET } from '../../options/presets/NoCustomNodes';
 
 import { AtobTemplate } from '../../templates/custom-nodes/AtobTemplate';
 import { Rc4Template } from '../../templates/custom-nodes/Rc4Template';
@@ -24,24 +23,24 @@ import { AbstractCustomNode } from '../AbstractCustomNode';
 import { JavaScriptObfuscator } from '../../JavaScriptObfuscator';
 
 @injectable()
-export class StringArrayCallsWrapper extends AbstractCustomNode implements ICustomNodeWithIdentifier {
+export class StringArrayCallsWrapper extends AbstractCustomNode {
     /**
      * @type {IStorage <string>}
      */
     @initializable()
-    private stringArray: IStorage <string>;
+    private stringArrayStorage: IStorage <string>;
 
     /**
      * @type {string}
      */
     @initializable()
-    private stringArrayCallsWrapperName: string;
+    private stringArrayName: string;
 
     /**
      * @type {string}
      */
     @initializable()
-    private stringArrayName: string;
+    private stringArrayCallsWrapperName: string;
 
     /**
      * @param options
@@ -53,46 +52,20 @@ export class StringArrayCallsWrapper extends AbstractCustomNode implements ICust
     }
 
     /**
-     * @param stringArray
+     * @param stringArrayStorage
      * @param stringArrayName
      * @param stringArrayCallsWrapperName
      */
     public initialize (
-        stringArray: IStorage <string>,
+        stringArrayStorage: IStorage <string>,
         stringArrayName: string,
         stringArrayCallsWrapperName: string
     ): void {
-        this.stringArray = stringArray;
+        this.stringArrayStorage = stringArrayStorage;
         this.stringArrayName = stringArrayName;
         this.stringArrayCallsWrapperName = stringArrayCallsWrapperName;
     }
 
-    /**
-     * @returns {string}
-     */
-    public getCode (): string {
-        const decodeNodeTemplate: string = this.getDecodeStringArrayTemplate();
-
-        return JavaScriptObfuscator.obfuscate(
-            format(StringArrayCallsWrapperTemplate(), {
-                decodeNodeTemplate,
-                stringArrayCallsWrapperName: this.stringArrayCallsWrapperName,
-                stringArrayName: this.stringArrayName
-            }),
-            {
-                ...NO_CUSTOM_NODES_PRESET,
-                seed: this.options.seed
-            }
-        ).getObfuscatedCode();
-    }
-
-    /**
-     * @returns {string}
-     */
-    public getNodeIdentifier (): string {
-        return this.stringArrayCallsWrapperName;
-    };
-
     /**
      * @returns {string}
      */
@@ -130,4 +103,23 @@ export class StringArrayCallsWrapper extends AbstractCustomNode implements ICust
 
         return decodeStringArrayTemplate;
     }
+
+    /**
+     * @returns {string}
+     */
+    protected getTemplate (): string {
+        const decodeNodeTemplate: string = this.getDecodeStringArrayTemplate();
+
+        return JavaScriptObfuscator.obfuscate(
+            format(StringArrayCallsWrapperTemplate(), {
+                decodeNodeTemplate,
+                stringArrayCallsWrapperName: this.stringArrayCallsWrapperName,
+                stringArrayName: this.stringArrayName
+            }),
+            {
+                ...NO_CUSTOM_NODES_PRESET,
+                seed: this.options.seed
+            }
+        ).getObfuscatedCode();
+    }
 }

+ 15 - 28
src/custom-nodes/string-array-nodes/StringArrayNode.ts

@@ -5,7 +5,6 @@ import * as format from 'string-template';
 
 import { TStatement } from '../../types/node/TStatement';
 
-import { ICustomNodeWithData } from '../../interfaces/custom-nodes/ICustomNodeWithData';
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IStorage } from '../../interfaces/storages/IStorage';
 
@@ -17,17 +16,12 @@ import { AbstractCustomNode } from '../AbstractCustomNode';
 import { StringArrayStorage } from '../../storages/string-array/StringArrayStorage';
 
 @injectable()
-export class StringArrayNode extends AbstractCustomNode implements ICustomNodeWithData {
-    /**
-     * @type {number}
-     */
-    public static ARRAY_RANDOM_LENGTH: number = 4;
-
+export class StringArrayNode extends AbstractCustomNode {
     /**
      * @type {IStorage <string>}
      */
     @initializable()
-    private stringArray: IStorage <string>;
+    private stringArrayStorage: IStorage <string>;
 
     /**
      * @type {string}
@@ -51,43 +45,36 @@ export class StringArrayNode extends AbstractCustomNode implements ICustomNodeWi
     }
 
     /**
-     * @param stringArray
+     * @param stringArrayStorage
      * @param stringArrayName
      * @param stringArrayRotateValue
      */
     public initialize (
-        stringArray: IStorage <string>,
+        stringArrayStorage: IStorage <string>,
         stringArrayName: string,
         stringArrayRotateValue: number
     ): void {
-        this.stringArray = stringArray;
+        this.stringArrayStorage = stringArrayStorage;
         this.stringArrayName = stringArrayName;
         this.stringArrayRotateValue = stringArrayRotateValue;
     }
 
     /**
-     * @returns {string}
+     * @returns {TStatement[]}
      */
-    public getCode (): string {
-        return format(StringArrayTemplate(), {
-            stringArrayName: this.stringArrayName,
-            stringArray: this.stringArray.toString()
-        });
-    }
+    public getNode (): TStatement[] {
+        (<StringArrayStorage>this.stringArrayStorage).rotateArray(this.stringArrayRotateValue);
 
-    /**
-     * @returns {IStorage <string>}
-     */
-    public getNodeData (): IStorage <string> {
-        return this.stringArray;
+        return super.getNode();
     }
 
     /**
-     * @returns {TStatement[]}
+     * @returns {string}
      */
-    public getNode (): TStatement[] {
-        (<StringArrayStorage>this.stringArray).rotateArray(this.stringArrayRotateValue);
-
-        return super.getNode();
+    protected getTemplate (): string {
+        return format(StringArrayTemplate(), {
+            stringArrayName: this.stringArrayName,
+            stringArray: this.stringArrayStorage.toString()
+        });
     }
 }

+ 6 - 6
src/custom-nodes/string-array-nodes/StringArrayRotateFunctionNode.ts

@@ -8,7 +8,7 @@ import { IStorage } from '../../interfaces/storages/IStorage';
 
 import { initializable } from '../../decorators/Initializable';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../preset-options/NoCustomNodesPreset';
+import { NO_CUSTOM_NODES_PRESET } from '../../options/presets/NoCustomNodes';
 
 import { SelfDefendingTemplate } from '../../templates/custom-nodes/string-array-nodes/string-array-rotate-function-node/SelfDefendingTemplate';
 import { StringArrayRotateFunctionTemplate } from '../../templates/custom-nodes/string-array-nodes/string-array-rotate-function-node/StringArrayRotateFunctionTemplate';
@@ -24,7 +24,7 @@ export class StringArrayRotateFunctionNode extends AbstractCustomNode {
      * @type {IStorage <string>}
      */
     @initializable()
-    private stringArray: IStorage <string>;
+    private stringArrayStorage: IStorage <string>;
 
     /**
      * @type {string}
@@ -48,16 +48,16 @@ export class StringArrayRotateFunctionNode extends AbstractCustomNode {
     }
 
     /**
-     * @param stringArray
+     * @param stringArrayStorage
      * @param stringArrayName
      * @param stringArrayRotateValue
      */
     public initialize (
-        stringArray: IStorage <string>,
+        stringArrayStorage: IStorage <string>,
         stringArrayName: string,
         stringArrayRotateValue: number
     ): void {
-        this.stringArray = stringArray;
+        this.stringArrayStorage = stringArrayStorage;
         this.stringArrayName = stringArrayName;
         this.stringArrayRotateValue = stringArrayRotateValue;
     }
@@ -65,7 +65,7 @@ export class StringArrayRotateFunctionNode extends AbstractCustomNode {
     /**
      * @returns {string}
      */
-    public getCode (): string {
+    protected getTemplate (): string {
         let code: string = '',
             timesName: string = RandomGeneratorUtils.getRandomVariableName(),
             whileFunctionName: string = RandomGeneratorUtils.getRandomVariableName();

+ 13 - 11
src/custom-nodes/string-array-nodes/group/StringArrayCustomNodeGroup.ts

@@ -16,12 +16,10 @@ import { initializable } from '../../../decorators/Initializable';
 import { CustomNodes } from '../../../enums/container/CustomNodes';
 import { ObfuscationEvents } from '../../../enums/ObfuscationEvents';
 
-import { StringArrayNode } from '../StringArrayNode';
-
 import { AbstractCustomNodeGroup } from '../../AbstractCustomNodeGroup';
 import { NodeAppender } from '../../../node/NodeAppender';
 import { RandomGeneratorUtils } from '../../../utils/RandomGeneratorUtils';
-import { StringArrayStorage } from '../../../storages/string-array/StringArrayStorage';
+import { Utils } from '../../../utils/Utils';
 
 @injectable()
 export class StringArrayCustomNodeGroup extends AbstractCustomNodeGroup {
@@ -50,22 +48,25 @@ export class StringArrayCustomNodeGroup extends AbstractCustomNodeGroup {
      * @type {IStorage <string>}
      */
     @initializable()
-    private stringArray: IStorage <string>;
+    private stringArrayStorage: IStorage <string>;
 
     /**
      * @param customNodeFactory
      * @param obfuscationEventEmitter
+     * @param stringArrayStorage
      * @param options
      */
     constructor (
         @inject(ServiceIdentifiers['Factory<ICustomNode>']) customNodeFactory: TCustomNodeFactory,
         @inject(ServiceIdentifiers.IObfuscationEventEmitter) obfuscationEventEmitter: IObfuscationEventEmitter,
+        @inject(ServiceIdentifiers['IStorage<string>']) stringArrayStorage: IStorage<string>,
         @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
         super(options);
 
         this.customNodeFactory = customNodeFactory;
         this.obfuscationEventEmitter = obfuscationEventEmitter;
+        this.stringArrayStorage = stringArrayStorage;
     }
 
     /**
@@ -73,7 +74,7 @@ export class StringArrayCustomNodeGroup extends AbstractCustomNodeGroup {
      * @param stackTraceData
      */
     public appendCustomNodes (blockScopeNode: TNodeWithBlockStatement, stackTraceData: IStackTraceData[]): void {
-        if (!this.stringArray.getLength()) {
+        if (!this.stringArrayStorage.getLength()) {
             return;
         }
 
@@ -95,7 +96,6 @@ export class StringArrayCustomNodeGroup extends AbstractCustomNodeGroup {
 
     public initialize (): void {
         this.customNodes = new Map <CustomNodes, ICustomNode> ();
-        this.stringArray = new StringArrayStorage();
 
         if (!this.options.stringArray) {
             return;
@@ -105,8 +105,10 @@ export class StringArrayCustomNodeGroup extends AbstractCustomNodeGroup {
         const stringArrayCallsWrapper: ICustomNode = this.customNodeFactory(CustomNodes.StringArrayCallsWrapper);
         const stringArrayRotateFunctionNode: ICustomNode = this.customNodeFactory(CustomNodes.StringArrayRotateFunctionNode);
 
-        const stringArrayName: string = RandomGeneratorUtils.getRandomVariableName(StringArrayNode.ARRAY_RANDOM_LENGTH);
-        const stringArrayCallsWrapperName: string = RandomGeneratorUtils.getRandomVariableName(StringArrayNode.ARRAY_RANDOM_LENGTH);
+        const stringArrayStorageId: string = this.stringArrayStorage.getStorageId();
+
+        const stringArrayName: string = `_${Utils.hexadecimalPrefix}${stringArrayStorageId}`;
+        const stringArrayCallsWrapperName: string = `_${Utils.hexadecimalPrefix}${Utils.stringRotate(stringArrayStorageId, 2)}`;
 
         let stringArrayRotateValue: number;
 
@@ -116,9 +118,9 @@ export class StringArrayCustomNodeGroup extends AbstractCustomNodeGroup {
             stringArrayRotateValue = 0;
         }
 
-        stringArrayNode.initialize(this.stringArray, stringArrayName, stringArrayRotateValue);
-        stringArrayCallsWrapper.initialize(this.stringArray, stringArrayName, stringArrayCallsWrapperName);
-        stringArrayRotateFunctionNode.initialize(this.stringArray, stringArrayName, stringArrayRotateValue);
+        stringArrayNode.initialize(this.stringArrayStorage, stringArrayName, stringArrayRotateValue);
+        stringArrayCallsWrapper.initialize(this.stringArrayStorage, stringArrayName, stringArrayCallsWrapperName);
+        stringArrayRotateFunctionNode.initialize(this.stringArrayStorage, stringArrayName, stringArrayRotateValue);
 
         this.customNodes.set(CustomNodes.StringArrayNode, stringArrayNode);
         this.customNodes.set(CustomNodes.StringArrayCallsWrapper, stringArrayCallsWrapper);

+ 1 - 1
src/declarations/ESTree.d.ts

@@ -4,8 +4,8 @@ import * as ESTree from 'estree';
 
 declare module 'estree' {
     interface BaseNode {
-        parentNode?: ESTree.Node;
         obfuscated?: boolean;
+        parentNode?: ESTree.Node;
     }
 
     interface SimpleLiteral extends ESTree.BaseNode, ESTree.BaseExpression {

+ 10 - 0
src/declarations/escodegen.d.ts

@@ -0,0 +1,10 @@
+import { IGeneratorOutput } from '../interfaces/IGeneratorOutput';
+
+declare module 'escodegen' {
+    /**
+     * @param ast
+     * @param options
+     * @returns IGeneratorOutput
+     */
+    export function generate(ast: any, options?: GenerateOptions): IGeneratorOutput;
+}

+ 26 - 30
src/decorators/Initializable.ts

@@ -12,46 +12,42 @@ export function initializable (
     const decoratorName: string = Object.keys(this)[0];
 
     return (target: IInitializable, propertyKey: string | symbol): any => {
+        const descriptor: PropertyDescriptor = {
+            configurable: true,
+            enumerable: true
+        };
         const initializeMethod: any = (<any>target)[initializeMethodKey];
 
         if (!initializeMethod || typeof initializeMethod !== 'function') {
            throw new Error(`\`${initializeMethodKey}\` method with initialization logic not found. \`@${decoratorName}\` decorator requires \`${initializeMethodKey}\` method`);
         }
 
-        const methodDescriptor: PropertyDescriptor = Object.getOwnPropertyDescriptor(target, initializeMethodKey) || {
-            configurable: true,
-            enumerable: true
-        };
-        const originalMethod: Function = methodDescriptor.value;
-
-        methodDescriptor.value = function (): void {
-            originalMethod.apply(this, arguments);
-
-            // call property getter to activate initialization check inside it
-            if (this[propertyKey]) {}
-        };
-
-        Object.defineProperty(target, initializeMethodKey, methodDescriptor);
-
         const metadataPropertyKey: string = `_${propertyKey}`;
-        const propertyDescriptor: PropertyDescriptor = Object.getOwnPropertyDescriptor(target, metadataPropertyKey) || {
-            configurable: true,
-            enumerable: true
-        };
+        const propertyDescriptor: PropertyDescriptor = Object.getOwnPropertyDescriptor(target, metadataPropertyKey) || descriptor;
+        const methodDescriptor: PropertyDescriptor = Object.getOwnPropertyDescriptor(target, initializeMethodKey) || descriptor;
+        const originalMethod: Function = methodDescriptor.value;
 
-        propertyDescriptor.get = function(): any {
-            if (this[metadataPropertyKey] === undefined) {
-                throw new Error(`Property \`${propertyKey}\` is not initialized! Initialize it first!`);
+        Object.defineProperty(target, propertyKey, {
+            ...propertyDescriptor,
+            get: function (): any {
+                if (this[metadataPropertyKey] === undefined) {
+                    throw new Error(`Property \`${propertyKey}\` is not initialized! Initialize it first!`);
+                }
+
+                return this[metadataPropertyKey];
+            },
+            set: function (newVal: any): void {
+                this[metadataPropertyKey] = newVal;
             }
+        });
+        Object.defineProperty(target, initializeMethodKey, {
+            ...methodDescriptor,
+            value: function (): void {
+                originalMethod.apply(this, arguments);
 
-            return this[metadataPropertyKey];
-        };
-
-        propertyDescriptor.set = function (newVal: any): void {
-            this[metadataPropertyKey] = newVal;
-        };
-
-        Object.defineProperty(target, propertyKey, propertyDescriptor);
+                if (this[propertyKey]) {}
+            }
+        });
 
         return propertyDescriptor;
     };

+ 2 - 0
src/enums/NodeType.ts

@@ -25,6 +25,8 @@ export const NodeType: any = Utils.strEnumify({
     Program: 'Program',
     Property: 'Property',
     ReturnStatement: 'ReturnStatement',
+    SwitchCase: 'SwitchCase',
+    SwitchStatement: 'SwitchStatement',
     TryStatement: 'TryStatement',
     UnaryExpression: 'UnaryExpression',
     UpdateExpression: 'UpdateExpression',

+ 0 - 5
src/interfaces/IGenerator.d.ts

@@ -1,5 +0,0 @@
-import { IGeneratorOutput } from './IGeneratorOutput';
-
-declare module 'escodegen' {
-    export function generate(ast: any, options?: GenerateOptions): IGeneratorOutput;
-}

+ 4 - 0
src/interfaces/IJavaScriptObfsucator.d.ts

@@ -1,5 +1,9 @@
 import { IObfuscationResult } from './IObfuscationResult';
 
 export interface IJavaScriptObfuscator {
+    /**
+     * @param sourceCode
+     * @returns IObfuscationResult
+     */
     obfuscate (sourceCode: string): IObfuscationResult;
 }

+ 4 - 0
src/interfaces/IObfuscator.d.ts

@@ -1,5 +1,9 @@
 import * as ESTree from 'estree';
 
 export interface IObfuscator {
+    /**
+     * @param astTree
+     * @returns ESTree.Program
+     */
     obfuscateAstTree (astTree: ESTree.Program): ESTree.Program;
 }

+ 5 - 0
src/interfaces/ISourceMapCorrector.d.ts

@@ -1,5 +1,10 @@
 import { IObfuscationResult } from './IObfuscationResult';
 
 export interface ISourceMapCorrector {
+    /**
+     * @param obfuscatedCode
+     * @param sourceMap
+     * @returns IObfuscationResult
+     */
     correct (obfuscatedCode: string, sourceMap: string): IObfuscationResult;
 }

+ 0 - 8
src/interfaces/custom-nodes/ICustomNodeWithData.d.ts

@@ -1,8 +0,0 @@
-import { ICustomNode } from './ICustomNode';
-
-export interface ICustomNodeWithData extends ICustomNode {
-    /**
-     * @returns any
-     */
-    getNodeData (): any;
-}

+ 0 - 5
src/interfaces/custom-nodes/ICustomNodeWithIdentifier.d.ts

@@ -1,5 +0,0 @@
-import { ICustomNode } from './ICustomNode';
-
-export interface ICustomNodeWithIdentifier extends ICustomNode {
-    getNodeIdentifier (): string;
-}

+ 11 - 0
src/interfaces/event-emitters/IObfuscationEventEmitter.d.ts

@@ -3,6 +3,17 @@ import Events = NodeJS.Events;
 import { TObfuscationEvent } from '../../types/event-emitters/TObfuscationEvent';
 
 export interface IObfuscationEventEmitter extends Events {
+    /**
+     * @param event
+     * @param listener
+     * @returns this
+     */
     on(event: TObfuscationEvent, listener: Function): this;
+
+    /**
+     * @param event
+     * @param listener
+     * @returns this
+     */
     once(event: TObfuscationEvent, listener: Function): this;
 }

+ 8 - 3
src/interfaces/node-transformers/IControlFlowReplacer.d.ts

@@ -4,10 +4,15 @@ import { ICustomNode } from '../custom-nodes/ICustomNode';
 import { IStorage } from '../storages/IStorage';
 
 export interface IControlFlowReplacer {
+    /**
+     * @param node
+     * @param parentNode
+     * @param controlFlowStorage
+     * @returns ESTree.Node
+     */
     replace (
         node: ESTree.Node,
         parentNode: ESTree.Node,
-        controlFlowStorage: IStorage <ICustomNode>,
-        controlFlowStorageCustomNodeName: string
-    ): ICustomNode | undefined;
+        controlFlowStorage: IStorage <ICustomNode>
+    ): ESTree.Node;
 }

+ 4 - 0
src/interfaces/node-transformers/INodeTransformer.d.ts

@@ -1,5 +1,9 @@
 import * as ESTree from 'estree';
 
 export interface INodeTransformer {
+    /**
+     * @param node
+     * @param parentNode
+     */
     transformNode (node: ESTree.Node, parentNode?: ESTree.Node): void;
 }

+ 7 - 0
src/interfaces/node-transformers/IObfuscatorReplacer.d.ts

@@ -0,0 +1,7 @@
+export interface IObfuscatorReplacer {
+    /**
+     * @param nodeValue
+     * @param nodeIdentifier
+     */
+    replace (nodeValue: any, nodeIdentifier?: string): string;
+}

+ 9 - 0
src/interfaces/node-transformers/IObfuscatorReplacerWithStorage.d.ts

@@ -0,0 +1,9 @@
+import { IObfuscatorReplacer } from './IObfuscatorReplacer';
+
+export interface IObfuscatorReplacerWithStorage extends IObfuscatorReplacer {
+    /**
+     * @param nodeValue
+     * @param nodeIdentifier
+     */
+    storeNames (nodeValue: any, nodeIdentifier: string): void;
+}

+ 0 - 3
src/interfaces/node-transformers/IReplacer.d.ts

@@ -1,3 +0,0 @@
-export interface IReplacer {
-    replace (nodeValue: any): string;
-}

+ 1 - 0
src/interfaces/options/IOptions.d.ts

@@ -4,6 +4,7 @@ import { TStringArrayEncoding } from '../../types/options/TStringArrayEncoding';
 export interface IOptions {
     readonly compact: boolean;
     readonly controlFlowFlattening: boolean;
+    readonly controlFlowFlatteningThreshold: number;
     readonly debugProtection: boolean;
     readonly debugProtectionInterval: boolean;
     readonly disableConsoleOutput: boolean;

+ 5 - 0
src/interfaces/stack-trace-analyzer/ICalleeDataExtractor.d.ts

@@ -3,5 +3,10 @@ import * as ESTree from 'estree';
 import { ICalleeData } from './ICalleeData';
 
 export interface ICalleeDataExtractor {
+    /**
+     * @param blockScopeBody
+     * @param callee
+     * @returns ICalleeData|null
+     */
     extract (blockScopeBody: ESTree.Node[], callee: ESTree.Node): ICalleeData|null;
 }

+ 4 - 0
src/interfaces/stack-trace-analyzer/IStackTraceAnalyzer.d.ts

@@ -3,5 +3,9 @@ import * as ESTree from 'estree';
 import { IStackTraceData } from './IStackTraceData';
 
 export interface IStackTraceAnalyzer {
+    /**
+     * @param blockScopeBody
+     * @returns IStackTraceData[]
+     */
     analyze (blockScopeBody: ESTree.Node[]): IStackTraceData[];
 }

+ 41 - 0
src/interfaces/storages/IStorage.d.ts

@@ -1,11 +1,52 @@
 import { IInitializable } from '../IInitializable';
 
 export interface IStorage <T> extends IInitializable {
+    /**
+     * @param key
+     * @returns T
+     */
     get (key: string | number): T;
+
+    /**
+     * @param value
+     * @returns string | number | null
+     */
     getKeyOf (value: T): string | number | null;
+
+    /**
+     * @returns number
+     */
     getLength (): number;
+
+    /**
+     * @returns any
+     */
     getStorage (): any;
+
+    /**
+     * @returns string
+     */
+    getStorageId (): string;
+
+    /**
+     * @param args
+     */
     initialize (...args: any[]): void;
+
+    /**
+     * @param storage
+     * @param mergeId
+     */
+    mergeWith (storage: this, mergeId: boolean): void;
+
+    /**
+     * @param key
+     * @param value
+     */
     set (key: string | number | null, value: T): void;
+
+    /**
+     * @returns string
+     */
     toString (): string;
 }

+ 97 - 22
src/node-transformers/node-control-flow-transformers/FunctionControlFlowTransformer.ts

@@ -3,10 +3,12 @@ import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
 import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
+import * as _ from 'lodash';
 
 import { TControlFlowReplacerFactory } from '../../types/container/TControlFlowReplacerFactory';
 import { TControlFlowStorageFactory } from '../../types/container/TControlFlowStorageFactory';
 import { TCustomNodeFactory } from '../../types/container/TCustomNodeFactory';
+import { TNodeWithBlockStatement } from '../../types/node/TNodeWithBlockStatement';
 import { TStatement } from '../../types/node/TStatement';
 
 import { ICustomNode } from '../../interfaces/custom-nodes/ICustomNode';
@@ -20,6 +22,7 @@ import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { Node } from '../../node/Node';
 import { NodeAppender } from '../../node/NodeAppender';
 import { NodeControlFlowReplacers } from '../../enums/container/NodeControlFlowReplacers';
+import { NodeUtils } from '../../node/NodeUtils';
 import { RandomGeneratorUtils } from '../../utils/RandomGeneratorUtils';
 
 @injectable()
@@ -27,10 +30,30 @@ export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
     /**
      * @type {Map <string, NodeControlFlowReplacers>}
      */
-    private static readonly controlFlowReplacersMap: Map <string, NodeControlFlowReplacers> = new Map <string, NodeControlFlowReplacers> ([
+    private static readonly controlFlowReplacersMap: Map <string, NodeControlFlowReplacers> = new Map([
         [NodeType.BinaryExpression, NodeControlFlowReplacers.BinaryExpressionControlFlowReplacer]
     ]);
 
+    /**
+     * @type {number}
+     */
+    private static readonly hostNodeSearchMinDepth: number = 2;
+
+    /**
+     * @type {number}
+     */
+    private static readonly hostNodeSearchMaxDepth: number = 10;
+
+    /**
+     * @type {Map<ESTree.Node, IStorage<ICustomNode>>}
+     */
+    private controlFlowData: Map <ESTree.Node, IStorage<ICustomNode>> = new Map();
+
+    /**
+     * @type {TStatement[][]}
+     */
+    private readonly controlFlowNodesList: TStatement[][] = [];
+
     /**
      * @type {TControlFlowReplacerFactory}
      */
@@ -65,6 +88,51 @@ export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
         this.customNodeFactory = customNodeFactory;
     }
 
+    /**
+     * @param functionNode
+     * @returns {TNodeWithBlockStatement}
+     */
+    private static getHostNode (functionNode: ESTree.FunctionDeclaration | ESTree.FunctionExpression): TNodeWithBlockStatement {
+        const blockScopesOfNode: TNodeWithBlockStatement[] = NodeUtils.getBlockScopesOfNode(functionNode);
+
+        if (blockScopesOfNode.length === 1) {
+            return functionNode.body;
+        } else {
+            blockScopesOfNode.pop();
+        }
+
+        if (blockScopesOfNode.length > FunctionControlFlowTransformer.hostNodeSearchMinDepth) {
+            blockScopesOfNode.splice(0, FunctionControlFlowTransformer.hostNodeSearchMinDepth);
+        }
+
+        if (blockScopesOfNode.length > FunctionControlFlowTransformer.hostNodeSearchMaxDepth) {
+            blockScopesOfNode.length = FunctionControlFlowTransformer.hostNodeSearchMaxDepth;
+        }
+
+        return RandomGeneratorUtils.getRandomGenerator().pickone(blockScopesOfNode);
+    }
+
+    /**
+     * @param hostNodeBody
+     * @param controlFlowNodesList
+     */
+    private static removeOldControlFlowNodeFromHostNodeBody (
+        hostNodeBody: TStatement[],
+        controlFlowNodesList: TStatement[][]
+    ): TStatement[] {
+        for (let controlFlowNode of controlFlowNodesList) {
+            const firstIndexOfNode: number = hostNodeBody.indexOf(controlFlowNode[0]);
+
+            if (firstIndexOfNode === -1) {
+                continue;
+            }
+
+            return _.difference(hostNodeBody, controlFlowNode);
+        }
+
+        return hostNodeBody;
+    }
+
     /**
      * @param functionNode
      */
@@ -81,35 +149,39 @@ export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
         }
 
         const controlFlowStorage: IStorage <ICustomNode> = this.controlFlowStorageFactory();
-        const controlFlowStorageCustomNodeName: string = RandomGeneratorUtils.getRandomVariableName(6);
+        const hostNode: TNodeWithBlockStatement = FunctionControlFlowTransformer.getHostNode(functionNode);
 
-        estraverse.replace(functionNode.body, {
-            enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
-                const controlFlowReplacerName: NodeControlFlowReplacers | undefined = FunctionControlFlowTransformer
-                    .controlFlowReplacersMap.get(node.type);
+        if (!this.controlFlowData.has(hostNode)) {
+            this.controlFlowData.set(hostNode, controlFlowStorage);
+        } else {
+            hostNode.body = <ESTree.Statement[]>FunctionControlFlowTransformer
+                .removeOldControlFlowNodeFromHostNodeBody(hostNode.body, this.controlFlowNodesList);
 
-                if (controlFlowReplacerName === undefined) {
-                    return;
-                }
+            const hostControlFlowStorage: IStorage<ICustomNode> = <IStorage<ICustomNode>>this.controlFlowData.get(hostNode);
 
-                const controlFlowStorageCallCustomNode: ICustomNode | undefined = this.controlFlowReplacerFactory(controlFlowReplacerName)
-                    .replace(node, parentNode, controlFlowStorage, controlFlowStorageCustomNodeName);
+            controlFlowStorage.mergeWith(hostControlFlowStorage, true);
 
-                if (!controlFlowStorageCallCustomNode) {
+            this.controlFlowData.set(hostNode, controlFlowStorage);
+        }
+
+        estraverse.replace(functionNode.body, {
+            enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
+                if (RandomGeneratorUtils.getRandomFloat(0, 1) > this.options.controlFlowFlatteningThreshold) {
                     return;
                 }
 
-                // controlFlowStorageCallCustomNode will always have only one TStatement node,
-                // so we can get it by index `0`
-                // also we need to return `expression` property of `ExpressionStatement` node because bug:
-                // https://github.com/estools/escodegen/issues/289
-                const statementNode: TStatement = controlFlowStorageCallCustomNode.getNode()[0];
+                const controlFlowReplacerName: NodeControlFlowReplacers | undefined = FunctionControlFlowTransformer
+                    .controlFlowReplacersMap.get(node.type);
 
-                if (!statementNode || !Node.isExpressionStatementNode(statementNode)) {
-                    throw new Error(`\`controlFlowStorageCallCustomNode.getNode()\` should returns array with \`ExpressionStatement\` node`);
+                if (controlFlowReplacerName === undefined) {
+                    return;
                 }
 
-                return statementNode.expression;
+                return {
+                    ...this.controlFlowReplacerFactory(controlFlowReplacerName)
+                        .replace(node, parentNode, controlFlowStorage),
+                    parentNode
+                };
             }
         });
 
@@ -119,8 +191,11 @@ export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
 
         const controlFlowStorageCustomNode: ICustomNode = this.customNodeFactory(CustomNodes.ControlFlowStorageNode);
 
-        controlFlowStorageCustomNode.initialize(controlFlowStorage, controlFlowStorageCustomNodeName);
+        controlFlowStorageCustomNode.initialize(controlFlowStorage);
+
+        const controlFlowStorageNode: TStatement[] = controlFlowStorageCustomNode.getNode();
 
-        NodeAppender.prependNode(functionNode.body, controlFlowStorageCustomNode.getNode());
+        this.controlFlowNodesList.push(controlFlowStorageNode);
+        NodeAppender.prependNode(hostNode, controlFlowStorageNode);
     }
 }

+ 3 - 17
src/node-transformers/node-control-flow-transformers/control-flow-replacers/AbstractControlFlowReplacer.ts

@@ -8,8 +8,6 @@ import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
 import { IOptions } from '../../../interfaces/options/IOptions';
 import { IStorage } from '../../../interfaces/storages/IStorage';
 
-import { RandomGeneratorUtils } from '../../../utils/RandomGeneratorUtils';
-
 @injectable()
 export abstract class AbstractControlFlowReplacer implements IControlFlowReplacer {
     /**
@@ -26,27 +24,15 @@ export abstract class AbstractControlFlowReplacer implements IControlFlowReplace
         this.options = options;
     }
 
-    /**
-     * @returns {string}
-     */
-    protected static getStorageKey (): string {
-        return RandomGeneratorUtils.getRandomGenerator().string({
-            length: 3,
-            pool: RandomGeneratorUtils.randomGeneratorPool
-        });
-    }
-
     /**
      * @param node
      * @param parentNode
      * @param controlFlowStorage
-     * @param controlFlowStorageCustomNodeName
-     * @returns {ICustomNode | undefined}
+     * @returns {ESTree.Node}
      */
     public abstract replace (
         node: ESTree.Node,
         parentNode: ESTree.Node,
-        controlFlowStorage: IStorage <ICustomNode>,
-        controlFlowStorageCustomNodeName: string
-    ): ICustomNode | undefined;
+        controlFlowStorage: IStorage <ICustomNode>
+    ): ESTree.Node;
 }

+ 73 - 24
src/node-transformers/node-control-flow-transformers/control-flow-replacers/BinaryExpressionControlFlowReplacer.ts

@@ -1,10 +1,10 @@
 import { injectable, inject } from 'inversify';
 import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
 
-import * as escodegen from 'escodegen';
 import * as ESTree from 'estree';
 
 import { TCustomNodeFactory } from '../../../types/container/TCustomNodeFactory';
+import { TStatement } from '../../../types/node/TStatement';
 
 import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
 import { IOptions } from '../../../interfaces/options/IOptions';
@@ -13,9 +13,22 @@ import { IStorage } from '../../../interfaces/storages/IStorage';
 import { CustomNodes } from '../../../enums/container/CustomNodes';
 
 import { AbstractControlFlowReplacer } from './AbstractControlFlowReplacer';
+import { Node } from '../../../node/Node';
+import { NodeUtils } from '../../../node/NodeUtils';
+import { RandomGeneratorUtils } from '../../../utils/RandomGeneratorUtils';
 
 @injectable()
 export class BinaryExpressionControlFlowReplacer extends AbstractControlFlowReplacer {
+    /**
+     * @type {number}
+     */
+    private static readonly useExistingOperatorKeyThreshold: number = 0.5;
+
+    /**
+     * @type {Map<string, Map<ESTree.BinaryOperator, string[]>>}
+     */
+    private readonly binaryOperatorsDataByControlFlowStorageId: Map <string, Map<ESTree.BinaryOperator, string[]>> = new Map();
+
     /**
      * @type {TCustomNodeFactory}
      */
@@ -35,42 +48,78 @@ export class BinaryExpressionControlFlowReplacer extends AbstractControlFlowRepl
     }
 
     /**
-     * @param expressionNode
-     * @returns {string}
+     * @param binaryOperatorsDataByControlFlowStorageId
+     * @param controlFlowStorageId
+     * @returns {Map<ESTree.BinaryOperator, string[]>}
      */
-    private static getExpressionValue (expressionNode: ESTree.Expression): string {
-        return escodegen.generate(expressionNode, {
-            sourceMapWithCode: true
-        }).code;
+    private static getStorageKeysByBinaryOperatorForCurrentStorage (
+        binaryOperatorsDataByControlFlowStorageId: Map<string, Map<ESTree.BinaryOperator, string[]>>,
+        controlFlowStorageId: string
+    ): Map<ESTree.BinaryOperator, string[]> {
+        let storageKeysByBinaryOperator: Map<ESTree.BinaryOperator, string[]>;
+
+        if (binaryOperatorsDataByControlFlowStorageId.has(controlFlowStorageId)) {
+            storageKeysByBinaryOperator = <Map<ESTree.BinaryOperator, string[]>>binaryOperatorsDataByControlFlowStorageId
+                .get(controlFlowStorageId);
+        } else {
+            storageKeysByBinaryOperator = new Map <ESTree.BinaryOperator, string[]> ();
+        }
+
+        return storageKeysByBinaryOperator;
     }
 
     /**
      * @param binaryExpressionNode
      * @param parentNode
      * @param controlFlowStorage
-     * @param controlFlowStorageCustomNodeName
-     * @returns {ICustomNode}
+     * @returns {ESTree.Node}
      */
     public replace (
         binaryExpressionNode: ESTree.BinaryExpression,
         parentNode: ESTree.Node,
-        controlFlowStorage: IStorage <ICustomNode>,
-        controlFlowStorageCustomNodeName: string
-    ): ICustomNode {
-        const key: string = AbstractControlFlowReplacer.getStorageKey();
-        const binaryExpressionFunctionNode: ICustomNode = this.customNodeFactory(CustomNodes.BinaryExpressionFunctionNode);
-        const controlFlowStorageCallNode: ICustomNode = this.customNodeFactory(CustomNodes.ControlFlowStorageCallNode);
-
-        binaryExpressionFunctionNode.initialize(binaryExpressionNode.operator);
-        controlFlowStorageCallNode.initialize(
-            controlFlowStorageCustomNodeName,
-            key,
-            BinaryExpressionControlFlowReplacer.getExpressionValue(binaryExpressionNode.left),
-            BinaryExpressionControlFlowReplacer.getExpressionValue(binaryExpressionNode.right)
+        controlFlowStorage: IStorage <ICustomNode>
+    ): ESTree.Node {
+        const controlFlowStorageId: string = controlFlowStorage.getStorageId();
+        const controlFlowStorageCallCustomNode: ICustomNode = this.customNodeFactory(CustomNodes.ControlFlowStorageCallNode);
+        const storageKeysByBinaryOperator: Map<ESTree.BinaryOperator, string[]> = BinaryExpressionControlFlowReplacer
+            .getStorageKeysByBinaryOperatorForCurrentStorage(
+                this.binaryOperatorsDataByControlFlowStorageId,
+                controlFlowStorageId
+            );
+
+        let storageKeysForCurrentOperator: string[] | undefined = storageKeysByBinaryOperator.get(binaryExpressionNode.operator);
+        let storageKey: string;
+
+        if (
+            RandomGeneratorUtils.getRandomFloat(0, 1) > BinaryExpressionControlFlowReplacer.useExistingOperatorKeyThreshold &&
+            storageKeysForCurrentOperator &&
+            storageKeysForCurrentOperator.length
+        ) {
+            storageKey = RandomGeneratorUtils.getRandomGenerator().pickone(storageKeysForCurrentOperator);
+        } else {
+            const binaryExpressionFunctionCustomNode: ICustomNode = this.customNodeFactory(CustomNodes.BinaryExpressionFunctionNode);
+
+            binaryExpressionFunctionCustomNode.initialize(binaryExpressionNode.operator);
+
+            storageKey = RandomGeneratorUtils.getRandomString(3);
+            storageKeysByBinaryOperator.set(binaryExpressionNode.operator, [storageKey]);
+            this.binaryOperatorsDataByControlFlowStorageId.set(controlFlowStorageId, storageKeysByBinaryOperator);
+            controlFlowStorage.set(storageKey, binaryExpressionFunctionCustomNode);
+        }
+
+        controlFlowStorageCallCustomNode.initialize(
+            controlFlowStorageId,
+            storageKey,
+            NodeUtils.convertStructureToCode([binaryExpressionNode.left]),
+            NodeUtils.convertStructureToCode([binaryExpressionNode.right])
         );
 
-        controlFlowStorage.set(key, binaryExpressionFunctionNode);
+        const statementNode: TStatement = controlFlowStorageCallCustomNode.getNode()[0];
+
+        if (!statementNode || !Node.isExpressionStatementNode(statementNode)) {
+            throw new Error(`\`controlFlowStorageCallNode.getNode()[0]\` should returns array with \`ExpressionStatement\` node`);
+        }
 
-        return controlFlowStorageCallNode;
+        return statementNode.expression;
     }
 }

+ 15 - 16
src/node-transformers/node-obfuscators/CatchClauseObfuscator.ts

@@ -5,13 +5,13 @@ import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 
 import { IOptions } from '../../interfaces/options/IOptions';
-import { IReplacer } from '../../interfaces/node-transformers/IReplacer';
+import { IObfuscatorReplacer } from '../../interfaces/node-transformers/IObfuscatorReplacer';
+import { IObfuscatorReplacerWithStorage } from '../../interfaces/node-transformers/IObfuscatorReplacerWithStorage';
 
 import { NodeObfuscatorsReplacers } from '../../enums/container/NodeObfuscatorsReplacers';
 import { NodeType } from '../../enums/NodeType';
 
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
-import { IdentifierReplacer } from './replacers/IdentifierReplacer';
 import { Node } from '../../node/Node';
 import { NodeUtils } from '../../node/NodeUtils';
 import { RandomGeneratorUtils } from '../../utils/RandomGeneratorUtils';
@@ -27,53 +27,52 @@ import { RandomGeneratorUtils } from '../../utils/RandomGeneratorUtils';
 @injectable()
 export class CatchClauseObfuscator extends AbstractNodeTransformer {
     /**
-     * @type {IdentifierReplacer}
+     * @type {IObfuscatorReplacerWithStorage}
      */
-    private readonly identifierReplacer: IReplacer & IdentifierReplacer;
+    private readonly identifierReplacer: IObfuscatorReplacerWithStorage;
 
     /**
      * @param replacersFactory
      * @param options
      */
     constructor(
-        @inject(ServiceIdentifiers['Factory<IReplacer>']) replacersFactory: (replacer: NodeObfuscatorsReplacers) => IReplacer,
+        @inject(ServiceIdentifiers['Factory<IObfuscatorReplacer>']) replacersFactory: (replacer: NodeObfuscatorsReplacers) => IObfuscatorReplacer,
         @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
         super(options);
 
-        this.identifierReplacer = <IdentifierReplacer>replacersFactory(NodeObfuscatorsReplacers.IdentifierReplacer);
+        this.identifierReplacer = <IObfuscatorReplacerWithStorage>replacersFactory(NodeObfuscatorsReplacers.IdentifierReplacer);
     }
 
     /**
      * @param catchClauseNode
      */
     public transformNode (catchClauseNode: ESTree.CatchClause): void {
-        this.identifierReplacer.setPrefix(RandomGeneratorUtils.getRandomGenerator().string({
-            length: 5,
-            pool: RandomGeneratorUtils.randomGeneratorPool
-        }));
+        const nodeIdentifier: string = RandomGeneratorUtils.getRandomString(7);
 
-        this.storeCatchClauseParam(catchClauseNode);
-        this.replaceCatchClauseParam(catchClauseNode);
+        this.storeCatchClauseParam(catchClauseNode, nodeIdentifier);
+        this.replaceCatchClauseParam(catchClauseNode, nodeIdentifier);
     }
 
     /**
      * @param catchClauseNode
+     * @param nodeIdentifier
      */
-    private storeCatchClauseParam (catchClauseNode: ESTree.CatchClause): void {
+    private storeCatchClauseParam (catchClauseNode: ESTree.CatchClause, nodeIdentifier: string): void {
         NodeUtils.typedTraverse(catchClauseNode.param, NodeType.Identifier, {
-            enter: (node: ESTree.Identifier) => this.identifierReplacer.storeNames(node.name)
+            enter: (node: ESTree.Identifier) => this.identifierReplacer.storeNames(node.name, nodeIdentifier)
         });
     }
 
     /**
      * @param catchClauseNode
+     * @param nodeIdentifier
      */
-    private replaceCatchClauseParam (catchClauseNode: ESTree.CatchClause): void {
+    private replaceCatchClauseParam (catchClauseNode: ESTree.CatchClause, nodeIdentifier: string): void {
         estraverse.replace(catchClauseNode, {
             enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
                 if (Node.isReplaceableIdentifierNode(node, parentNode)) {
-                    node.name = this.identifierReplacer.replace(node.name);
+                    node.name = this.identifierReplacer.replace(node.name, nodeIdentifier);
                 }
             }
         });

+ 16 - 18
src/node-transformers/node-obfuscators/FunctionDeclarationObfuscator.ts

@@ -7,13 +7,13 @@ import * as ESTree from 'estree';
 import { TNodeWithBlockStatement } from '../../types/node/TNodeWithBlockStatement';
 
 import { IOptions } from '../../interfaces/options/IOptions';
-import { IReplacer } from '../../interfaces/node-transformers/IReplacer';
+import { IObfuscatorReplacer } from '../../interfaces/node-transformers/IObfuscatorReplacer';
+import { IObfuscatorReplacerWithStorage } from '../../interfaces/node-transformers/IObfuscatorReplacerWithStorage';
 
 import { NodeObfuscatorsReplacers } from '../../enums/container/NodeObfuscatorsReplacers';
 import { NodeType } from '../../enums/NodeType';
 
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
-import { IdentifierReplacer } from './replacers/IdentifierReplacer';
 import { Node } from '../../node/Node';
 import { NodeUtils } from '../../node/NodeUtils';
 import { RandomGeneratorUtils } from '../../utils/RandomGeneratorUtils';
@@ -30,21 +30,21 @@ import { RandomGeneratorUtils } from '../../utils/RandomGeneratorUtils';
 @injectable()
 export class FunctionDeclarationObfuscator extends AbstractNodeTransformer {
     /**
-     * @type {IdentifierReplacer}
+     * @type {IObfuscatorReplacerWithStorage}
      */
-    private readonly identifierReplacer: IdentifierReplacer;
+    private readonly identifierReplacer: IObfuscatorReplacerWithStorage;
 
     /**
      * @param nodeObfuscatorsReplacersFactory
      * @param options
      */
     constructor(
-        @inject(ServiceIdentifiers['Factory<IReplacer>']) nodeObfuscatorsReplacersFactory: (replacer: NodeObfuscatorsReplacers) => IReplacer,
+        @inject(ServiceIdentifiers['Factory<IObfuscatorReplacer>']) nodeObfuscatorsReplacersFactory: (replacer: NodeObfuscatorsReplacers) => IObfuscatorReplacer,
         @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
         super(options);
 
-        this.identifierReplacer = <IdentifierReplacer>nodeObfuscatorsReplacersFactory(NodeObfuscatorsReplacers.IdentifierReplacer);
+        this.identifierReplacer = <IObfuscatorReplacerWithStorage>nodeObfuscatorsReplacersFactory(NodeObfuscatorsReplacers.IdentifierReplacer);
     }
 
     /**
@@ -52,39 +52,37 @@ export class FunctionDeclarationObfuscator extends AbstractNodeTransformer {
      * @param parentNode
      */
     public transformNode (functionDeclarationNode: ESTree.FunctionDeclaration, parentNode: ESTree.Node): void {
-        this.identifierReplacer.setPrefix(RandomGeneratorUtils.getRandomGenerator().string({
-            length: 5,
-            pool: RandomGeneratorUtils.randomGeneratorPool
-        }));
-
+        const nodeIdentifier: string = RandomGeneratorUtils.getRandomString(7);
         const blockScopeOfFunctionDeclarationNode: TNodeWithBlockStatement = NodeUtils
-            .getBlockScopeOfNode(functionDeclarationNode);
+            .getBlockScopesOfNode(functionDeclarationNode)[0];
 
         if (blockScopeOfFunctionDeclarationNode.type === NodeType.Program) {
             return;
         }
 
-        this.storeFunctionName(functionDeclarationNode);
-        this.replaceFunctionName(blockScopeOfFunctionDeclarationNode);
+        this.storeFunctionName(functionDeclarationNode, nodeIdentifier);
+        this.replaceFunctionName(blockScopeOfFunctionDeclarationNode, nodeIdentifier);
     }
 
     /**
      * @param functionDeclarationNode
+     * @param nodeIdentifier
      */
-    private storeFunctionName (functionDeclarationNode: ESTree.FunctionDeclaration): void {
+    private storeFunctionName (functionDeclarationNode: ESTree.FunctionDeclaration, nodeIdentifier: string): void {
         NodeUtils.typedTraverse(functionDeclarationNode.id, NodeType.Identifier, {
-            enter: (node: ESTree.Identifier) => this.identifierReplacer.storeNames(node.name)
+            enter: (node: ESTree.Identifier) => this.identifierReplacer.storeNames(node.name, nodeIdentifier)
         });
     }
 
     /**
      * @param scopeNode
+     * @param nodeIdentifier
      */
-    private replaceFunctionName (scopeNode: ESTree.Node): void {
+    private replaceFunctionName (scopeNode: ESTree.Node, nodeIdentifier: string): void {
         estraverse.replace(scopeNode, {
             enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
                 if (Node.isReplaceableIdentifierNode(node, parentNode)) {
-                    node.name = this.identifierReplacer.replace(node.name);
+                    node.name = this.identifierReplacer.replace(node.name, nodeIdentifier);
                 }
             }
         });

+ 15 - 16
src/node-transformers/node-obfuscators/FunctionObfuscator.ts

@@ -5,13 +5,13 @@ import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 
 import { IOptions } from '../../interfaces/options/IOptions';
-import { IReplacer } from '../../interfaces/node-transformers/IReplacer';
+import { IObfuscatorReplacer } from '../../interfaces/node-transformers/IObfuscatorReplacer';
+import { IObfuscatorReplacerWithStorage } from '../../interfaces/node-transformers/IObfuscatorReplacerWithStorage';
 
 import { NodeObfuscatorsReplacers } from '../../enums/container/NodeObfuscatorsReplacers';
 import { NodeType } from '../../enums/NodeType';
 
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
-import { IdentifierReplacer } from './replacers/IdentifierReplacer';
 import { Node } from '../../node/Node';
 import { NodeUtils } from '../../node/NodeUtils';
 import { RandomGeneratorUtils } from '../../utils/RandomGeneratorUtils';
@@ -27,56 +27,55 @@ import { RandomGeneratorUtils } from '../../utils/RandomGeneratorUtils';
 @injectable()
 export class FunctionObfuscator extends AbstractNodeTransformer {
     /**
-     * @type {IdentifierReplacer}
+     * @type {IObfuscatorReplacerWithStorage}
      */
-    private readonly identifierReplacer: IdentifierReplacer;
+    private readonly identifierReplacer: IObfuscatorReplacerWithStorage;
 
     /**
      * @param nodeObfuscatorsReplacersFactory
      * @param options
      */
     constructor(
-        @inject(ServiceIdentifiers['Factory<IReplacer>']) nodeObfuscatorsReplacersFactory: (replacer: NodeObfuscatorsReplacers) => IReplacer,
+        @inject(ServiceIdentifiers['Factory<IObfuscatorReplacer>']) nodeObfuscatorsReplacersFactory: (replacer: NodeObfuscatorsReplacers) => IObfuscatorReplacer,
         @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
         super(options);
 
-        this.identifierReplacer = <IdentifierReplacer>nodeObfuscatorsReplacersFactory(NodeObfuscatorsReplacers.IdentifierReplacer);
+        this.identifierReplacer = <IObfuscatorReplacerWithStorage>nodeObfuscatorsReplacersFactory(NodeObfuscatorsReplacers.IdentifierReplacer);
     }
 
     /**
      * @param functionNode
      */
     public transformNode (functionNode: ESTree.Function): void {
-        this.identifierReplacer.setPrefix(RandomGeneratorUtils.getRandomGenerator().string({
-            length: 5,
-            pool: RandomGeneratorUtils.randomGeneratorPool
-        }));
+        const nodeIdentifier: string = RandomGeneratorUtils.getRandomString(7);
 
-        this.storeFunctionParams(functionNode);
-        this.replaceFunctionParams(functionNode);
+        this.storeFunctionParams(functionNode, nodeIdentifier);
+        this.replaceFunctionParams(functionNode, nodeIdentifier);
     }
 
     /**
      * @param functionNode
+     * @param nodeIdentifier
      */
-    private storeFunctionParams (functionNode: ESTree.Function): void {
+    private storeFunctionParams (functionNode: ESTree.Function, nodeIdentifier: string): void {
         functionNode.params
             .forEach((paramsNode: ESTree.Node) => {
                 NodeUtils.typedTraverse(paramsNode, NodeType.Identifier, {
-                    enter: (node: ESTree.Identifier) => this.identifierReplacer.storeNames(node.name)
+                    enter: (node: ESTree.Identifier) => this.identifierReplacer.storeNames(node.name, nodeIdentifier)
                 });
             });
     }
 
     /**
      * @param functionNode
+     * @param nodeIdentifier
      */
-    private replaceFunctionParams (functionNode: ESTree.Function): void {
+    private replaceFunctionParams (functionNode: ESTree.Function, nodeIdentifier: string): void {
         let traverseVisitor: estraverse.Visitor = {
             enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
                 if (Node.isReplaceableIdentifierNode(node, parentNode)) {
-                    const newNodeName: string = this.identifierReplacer.replace(node.name);
+                    const newNodeName: string = this.identifierReplacer.replace(node.name, nodeIdentifier);
 
                     if (node.name !== newNodeName) {
                         node.name = newNodeName;

+ 15 - 16
src/node-transformers/node-obfuscators/LabeledStatementObfuscator.ts

@@ -5,13 +5,13 @@ import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 
 import { IOptions } from '../../interfaces/options/IOptions';
-import { IReplacer } from '../../interfaces/node-transformers/IReplacer';
+import { IObfuscatorReplacer } from '../../interfaces/node-transformers/IObfuscatorReplacer';
+import { IObfuscatorReplacerWithStorage } from '../../interfaces/node-transformers/IObfuscatorReplacerWithStorage';
 
 import { NodeObfuscatorsReplacers } from '../../enums/container/NodeObfuscatorsReplacers';
 import { NodeType } from '../../enums/NodeType';
 
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
-import { IdentifierReplacer } from './replacers/IdentifierReplacer';
 import { Node } from '../../node/Node';
 import { NodeUtils } from '../../node/NodeUtils';
 import { RandomGeneratorUtils } from '../../utils/RandomGeneratorUtils';
@@ -35,53 +35,52 @@ import { RandomGeneratorUtils } from '../../utils/RandomGeneratorUtils';
 @injectable()
 export class LabeledStatementObfuscator extends AbstractNodeTransformer {
     /**
-     * @type {IdentifierReplacer}
+     * @type {IObfuscatorReplacerWithStorage}
      */
-    private readonly identifierReplacer: IdentifierReplacer;
+    private readonly identifierReplacer: IObfuscatorReplacerWithStorage;
 
     /**
      * @param nodeObfuscatorsReplacersFactory
      * @param options
      */
     constructor(
-        @inject(ServiceIdentifiers['Factory<IReplacer>']) nodeObfuscatorsReplacersFactory: (replacer: NodeObfuscatorsReplacers) => IReplacer,
+        @inject(ServiceIdentifiers['Factory<IObfuscatorReplacer>']) nodeObfuscatorsReplacersFactory: (replacer: NodeObfuscatorsReplacers) => IObfuscatorReplacer,
         @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
         super(options);
 
-        this.identifierReplacer = <IdentifierReplacer>nodeObfuscatorsReplacersFactory(NodeObfuscatorsReplacers.IdentifierReplacer);
+        this.identifierReplacer = <IObfuscatorReplacerWithStorage>nodeObfuscatorsReplacersFactory(NodeObfuscatorsReplacers.IdentifierReplacer);
     }
 
     /**
      * @param labeledStatementNode
      */
     public transformNode (labeledStatementNode: ESTree.LabeledStatement): void {
-        this.identifierReplacer.setPrefix(RandomGeneratorUtils.getRandomGenerator().string({
-            length: 5,
-            pool: RandomGeneratorUtils.randomGeneratorPool
-        }));
+        const nodeIdentifier: string = RandomGeneratorUtils.getRandomString(7);
 
-        this.storeLabeledStatementName(labeledStatementNode);
-        this.replaceLabeledStatementName(labeledStatementNode);
+        this.storeLabeledStatementName(labeledStatementNode, nodeIdentifier);
+        this.replaceLabeledStatementName(labeledStatementNode, nodeIdentifier);
     }
 
     /**
      * @param labeledStatementNode
+     * @param nodeIdentifier
      */
-    private storeLabeledStatementName (labeledStatementNode: ESTree.LabeledStatement): void {
+    private storeLabeledStatementName (labeledStatementNode: ESTree.LabeledStatement, nodeIdentifier: string): void {
         NodeUtils.typedTraverse(labeledStatementNode.label, NodeType.Identifier, {
-            enter: (node: ESTree.Identifier) => this.identifierReplacer.storeNames(node.name)
+            enter: (node: ESTree.Identifier) => this.identifierReplacer.storeNames(node.name, nodeIdentifier)
         });
     }
 
     /**
      * @param labeledStatementNode
+     * @param nodeIdentifier
      */
-    private replaceLabeledStatementName (labeledStatementNode: ESTree.LabeledStatement): void {
+    private replaceLabeledStatementName (labeledStatementNode: ESTree.LabeledStatement, nodeIdentifier: string): void {
         estraverse.replace(labeledStatementNode, {
             enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
                 if (Node.isLabelIdentifierNode(node, parentNode)) {
-                    node.name = this.identifierReplacer.replace(node.name);
+                    node.name = this.identifierReplacer.replace(node.name, nodeIdentifier);
                 }
             }
         });

+ 4 - 4
src/node-transformers/node-obfuscators/LiteralObfuscator.ts

@@ -5,7 +5,7 @@ import * as escodegen from 'escodegen';
 import * as ESTree from 'estree';
 
 import { IOptions } from '../../interfaces/options/IOptions';
-import { IReplacer } from '../../interfaces/node-transformers/IReplacer';
+import { IObfuscatorReplacer } from '../../interfaces/node-transformers/IObfuscatorReplacer';
 
 import { NodeObfuscatorsReplacers } from '../../enums/container/NodeObfuscatorsReplacers';
 
@@ -15,16 +15,16 @@ import { Node } from '../../node/Node';
 @injectable()
 export class LiteralObfuscator extends AbstractNodeTransformer {
     /**
-     * @type {(replacer: NodeObfuscatorsReplacers) => IReplacer}
+     * @type {(replacer: NodeObfuscatorsReplacers) => IObfuscatorReplacer}
      */
-    private readonly replacersFactory: (replacer: NodeObfuscatorsReplacers) => IReplacer;
+    private readonly replacersFactory: (replacer: NodeObfuscatorsReplacers) => IObfuscatorReplacer;
 
     /**
      * @param replacersFactory
      * @param options
      */
     constructor(
-        @inject(ServiceIdentifiers['Factory<IReplacer>']) replacersFactory: (replacer: NodeObfuscatorsReplacers) => IReplacer,
+        @inject(ServiceIdentifiers['Factory<IObfuscatorReplacer>']) replacersFactory: (replacer: NodeObfuscatorsReplacers) => IObfuscatorReplacer,
         @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
         super(options);

+ 4 - 4
src/node-transformers/node-obfuscators/MemberExpressionObfuscator.ts

@@ -6,7 +6,7 @@ import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 
 import { IOptions } from '../../interfaces/options/IOptions';
-import { IReplacer } from '../../interfaces/node-transformers/IReplacer';
+import { IObfuscatorReplacer } from '../../interfaces/node-transformers/IObfuscatorReplacer';
 
 import { NodeObfuscatorsReplacers } from '../../enums/container/NodeObfuscatorsReplacers';
 import { NodeType } from '../../enums/NodeType';
@@ -17,16 +17,16 @@ import { Node } from '../../node/Node';
 @injectable()
 export class MemberExpressionObfuscator extends AbstractNodeTransformer {
     /**
-     * @type {IReplacer}
+     * @type {IObfuscatorReplacer}
      */
-    private readonly stringLiteralReplacer: IReplacer;
+    private readonly stringLiteralReplacer: IObfuscatorReplacer;
 
     /**
      * @param replacersFactory
      * @param options
      */
     constructor(
-        @inject(ServiceIdentifiers['Factory<IReplacer>']) replacersFactory: (replacer: NodeObfuscatorsReplacers) => IReplacer,
+        @inject(ServiceIdentifiers['Factory<IObfuscatorReplacer>']) replacersFactory: (replacer: NodeObfuscatorsReplacers) => IObfuscatorReplacer,
         @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
         super(options);

+ 5 - 6
src/node-transformers/node-obfuscators/MethodDefinitionObfuscator.ts

@@ -5,13 +5,12 @@ import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 
 import { IOptions } from '../../interfaces/options/IOptions';
-import { IReplacer } from '../../interfaces/node-transformers/IReplacer';
+import { IObfuscatorReplacer } from '../../interfaces/node-transformers/IObfuscatorReplacer';
 
 import { NodeObfuscatorsReplacers } from '../../enums/container/NodeObfuscatorsReplacers';
 
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { Node } from '../../node/Node';
-import { Utils } from '../../utils/Utils';
 
 /**
  * replaces:
@@ -23,9 +22,9 @@ import { Utils } from '../../utils/Utils';
 @injectable()
 export class MethodDefinitionObfuscator extends AbstractNodeTransformer {
     /**
-     * @type {IReplacer}
+     * @type {IObfuscatorReplacer}
      */
-    private readonly stringLiteralReplacer: IReplacer;
+    private readonly stringLiteralReplacer: IObfuscatorReplacer;
 
     /**
      * @type {string[]}
@@ -37,7 +36,7 @@ export class MethodDefinitionObfuscator extends AbstractNodeTransformer {
      * @param options
      */
     constructor(
-        @inject(ServiceIdentifiers['Factory<IReplacer>']) replacersFactory: (replacer: NodeObfuscatorsReplacers) => IReplacer,
+        @inject(ServiceIdentifiers['Factory<IObfuscatorReplacer>']) replacersFactory: (replacer: NodeObfuscatorsReplacers) => IObfuscatorReplacer,
         @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
         super(options);
@@ -61,7 +60,7 @@ export class MethodDefinitionObfuscator extends AbstractNodeTransformer {
             enter: (node: ESTree.Node): any => {
                 if (
                     Node.isIdentifierNode(node) &&
-                    !Utils.arrayContains(MethodDefinitionObfuscator.ignoredNames, node.name) &&
+                    !MethodDefinitionObfuscator.ignoredNames.includes(node.name) &&
                     methodDefinitionNode.computed === false
                 ) {
                     methodDefinitionNode.computed = true;

+ 16 - 18
src/node-transformers/node-obfuscators/VariableDeclarationObfuscator.ts

@@ -7,13 +7,13 @@ import * as ESTree from 'estree';
 import { TNodeWithBlockStatement } from '../../types/node/TNodeWithBlockStatement';
 
 import { IOptions } from '../../interfaces/options/IOptions';
-import { IReplacer } from '../../interfaces/node-transformers/IReplacer';
+import { IObfuscatorReplacer } from '../../interfaces/node-transformers/IObfuscatorReplacer';
+import { IObfuscatorReplacerWithStorage } from '../../interfaces/node-transformers/IObfuscatorReplacerWithStorage';
 
 import { NodeObfuscatorsReplacers } from '../../enums/container/NodeObfuscatorsReplacers';
 import { NodeType } from '../../enums/NodeType';
 
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
-import { IdentifierReplacer } from './replacers/IdentifierReplacer';
 import { Node } from '../../node/Node';
 import { NodeUtils } from '../../node/NodeUtils';
 import { RandomGeneratorUtils } from '../../utils/RandomGeneratorUtils';
@@ -31,21 +31,21 @@ import { RandomGeneratorUtils } from '../../utils/RandomGeneratorUtils';
 @injectable()
 export class VariableDeclarationObfuscator extends AbstractNodeTransformer {
     /**
-     * @type {IdentifierReplacer}
+     * @type {IObfuscatorReplacerWithStorage}
      */
-    private readonly identifierReplacer: IReplacer & IdentifierReplacer;
+    private readonly identifierReplacer: IObfuscatorReplacerWithStorage;
 
     /**
      * @param replacersFactory
      * @param options
      */
     constructor(
-        @inject(ServiceIdentifiers['Factory<IReplacer>']) replacersFactory: (replacer: NodeObfuscatorsReplacers) => IReplacer,
+        @inject(ServiceIdentifiers['Factory<IObfuscatorReplacer>']) replacersFactory: (replacer: NodeObfuscatorsReplacers) => IObfuscatorReplacer,
         @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
         super(options);
 
-        this.identifierReplacer = <IdentifierReplacer>replacersFactory(NodeObfuscatorsReplacers.IdentifierReplacer);
+        this.identifierReplacer = <IObfuscatorReplacerWithStorage>replacersFactory(NodeObfuscatorsReplacers.IdentifierReplacer);
     }
 
     /**
@@ -53,46 +53,44 @@ export class VariableDeclarationObfuscator extends AbstractNodeTransformer {
      * @param parentNode
      */
     public transformNode (variableDeclarationNode: ESTree.VariableDeclaration, parentNode: ESTree.Node): void {
-        this.identifierReplacer.setPrefix(RandomGeneratorUtils.getRandomGenerator().string({
-            length: 5,
-            pool: RandomGeneratorUtils.randomGeneratorPool
-        }));
-
         const blockScopeOfVariableDeclarationNode: TNodeWithBlockStatement = NodeUtils
-            .getBlockScopeOfNode(variableDeclarationNode);
+            .getBlockScopesOfNode(variableDeclarationNode)[0];
 
         if (blockScopeOfVariableDeclarationNode.type === NodeType.Program) {
             return;
         }
 
+        const nodeIdentifier: string = RandomGeneratorUtils.getRandomString(7);
         const scopeNode: ESTree.Node = variableDeclarationNode.kind === 'var'
             ? blockScopeOfVariableDeclarationNode
             : parentNode;
 
-        this.storeVariableNames(variableDeclarationNode);
-        this.replaceVariableNames(scopeNode);
+        this.storeVariableNames(variableDeclarationNode, nodeIdentifier);
+        this.replaceVariableNames(scopeNode, nodeIdentifier);
     }
 
     /**
      * @param variableDeclarationNode
+     * @param nodeIdentifier
      */
-    private storeVariableNames (variableDeclarationNode: ESTree.VariableDeclaration): void {
+    private storeVariableNames (variableDeclarationNode: ESTree.VariableDeclaration, nodeIdentifier: string): void {
         variableDeclarationNode.declarations
             .forEach((declarationNode: ESTree.VariableDeclarator) => {
                 NodeUtils.typedTraverse(declarationNode.id, NodeType.Identifier, {
-                    enter: (node: ESTree.Identifier) => this.identifierReplacer.storeNames(node.name)
+                    enter: (node: ESTree.Identifier) => this.identifierReplacer.storeNames(node.name, nodeIdentifier)
                 });
             });
     }
 
     /**
      * @param scopeNode
+     * @param nodeIdentifier
      */
-    private replaceVariableNames (scopeNode: ESTree.Node): void {
+    private replaceVariableNames (scopeNode: ESTree.Node, nodeIdentifier: string): void {
         estraverse.replace(scopeNode, {
             enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
                 if (!node.obfuscated && Node.isReplaceableIdentifierNode(node, parentNode)) {
-                    node.name = this.identifierReplacer.replace(node.name);
+                    node.name = this.identifierReplacer.replace(node.name, nodeIdentifier);
                 }
             }
         });

+ 4 - 3
src/node-transformers/node-obfuscators/replacers/AbstractReplacer.ts

@@ -2,10 +2,10 @@ import { injectable, inject } from 'inversify';
 import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
 
 import { IOptions } from '../../../interfaces/options/IOptions';
-import { IReplacer } from '../../../interfaces/node-transformers/IReplacer';
+import { IObfuscatorReplacer } from '../../../interfaces/node-transformers/IObfuscatorReplacer';
 
 @injectable()
-export abstract class AbstractReplacer implements IReplacer {
+export abstract class AbstractReplacer implements IObfuscatorReplacer {
     /**
      * @type {IOptions}
      */
@@ -22,7 +22,8 @@ export abstract class AbstractReplacer implements IReplacer {
 
     /**
      * @param nodeValue
+     * @param nodeIdentifier
      * @returns {string}
      */
-    public abstract replace (nodeValue: any): string;
+    public abstract replace (nodeValue: any, nodeIdentifier?: string): string;
 }

+ 9 - 22
src/node-transformers/node-obfuscators/replacers/IdentifierReplacer.ts

@@ -1,22 +1,18 @@
 import { injectable, inject } from 'inversify';
 import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
 
+import { IObfuscatorReplacerWithStorage } from '../../../interfaces/node-transformers/IObfuscatorReplacerWithStorage';
 import { IOptions } from '../../../interfaces/options/IOptions';
 
 import { AbstractReplacer } from './AbstractReplacer';
 import { RandomGeneratorUtils } from '../../../utils/RandomGeneratorUtils';
 
 @injectable()
-export class IdentifierReplacer extends AbstractReplacer {
+export class IdentifierReplacer extends AbstractReplacer implements IObfuscatorReplacerWithStorage {
     /**
      * @type {Map<string, string>}
      */
-    private readonly namesMap: Map<string, string> = new Map<string, string>();
-
-    /**
-     * @type {string}
-     */
-    private uniquePrefix: string;
+    private readonly namesMap: Map<string, string> = new Map();
 
     /**
      * @param options
@@ -29,10 +25,11 @@ export class IdentifierReplacer extends AbstractReplacer {
 
     /**
      * @param nodeValue
+     * @param nodeIdentifier
      * @returns {string}
      */
-    public replace (nodeValue: string): string {
-        const obfuscatedIdentifierName: string|undefined = this.namesMap.get(`${nodeValue}-${this.uniquePrefix}`);
+    public replace (nodeValue: string, nodeIdentifier: string): string {
+        const obfuscatedIdentifierName: string|undefined = this.namesMap.get(`${nodeValue}-${nodeIdentifier}`);
 
         if (!obfuscatedIdentifierName) {
             return nodeValue;
@@ -41,26 +38,16 @@ export class IdentifierReplacer extends AbstractReplacer {
         return obfuscatedIdentifierName;
     }
 
-    /**
-     * @param uniquePrefix
-     */
-    public setPrefix (uniquePrefix: string): void {
-        this.uniquePrefix = uniquePrefix
-    }
-
     /**
      * Store all identifiers names as keys in given `namesMap` with random names as value.
      * Reserved names will be ignored.
      *
      * @param nodeName
+     * @param nodeIdentifier
      */
-    public storeNames (nodeName: string): void {
-        if (!this.uniquePrefix) {
-            throw new Error('`uniquePrefix` is `undefined`. Set it before `storeNames`');
-        }
-
+    public storeNames (nodeName: string, nodeIdentifier: string): void {
         if (!this.isReservedName(nodeName)) {
-            this.namesMap.set(`${nodeName}-${this.uniquePrefix}`, RandomGeneratorUtils.getRandomVariableName());
+            this.namesMap.set(`${nodeName}-${nodeIdentifier}`, RandomGeneratorUtils.getRandomVariableName());
         }
     }
 

+ 1 - 1
src/node-transformers/node-obfuscators/replacers/NumberLiteralReplacer.ts

@@ -22,7 +22,7 @@ export class NumberLiteralReplacer extends AbstractReplacer {
      * @returns {string}
      */
     public replace (nodeValue: number): string {
-        if (!Utils.isInteger(nodeValue)) {
+        if (!Utils.isCeilNumber(nodeValue)) {
             return String(nodeValue);
         }
 

+ 15 - 19
src/node-transformers/node-obfuscators/replacers/StringLiteralReplacer.ts

@@ -1,10 +1,7 @@
 import { injectable, inject } from 'inversify';
 import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
 
-import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
 import { ICustomNodeGroup } from '../../../interfaces/custom-nodes/ICustomNodeGroup';
-import { ICustomNodeWithData } from '../../../interfaces/custom-nodes/ICustomNodeWithData';
-import { ICustomNodeWithIdentifier } from '../../../interfaces/custom-nodes/ICustomNodeWithIdentifier';
 import { IOptions } from '../../../interfaces/options/IOptions';
 import { IStorage } from '../../../interfaces/storages/IStorage';
 
@@ -12,8 +9,6 @@ import { StringArrayEncoding } from '../../../enums/StringArrayEncoding';
 
 import { AbstractReplacer } from './AbstractReplacer';
 import { CryptUtils } from '../../../utils/CryptUtils';
-import { CustomNodes } from '../../../enums/container/CustomNodes';
-import { CustomNodeGroups } from '../../../enums/container/CustomNodeGroups';
 import { RandomGeneratorUtils } from '../../../utils/RandomGeneratorUtils';
 import { Utils } from '../../../utils/Utils';
 
@@ -35,17 +30,25 @@ export class StringLiteralReplacer extends AbstractReplacer {
      */
     private readonly customNodeGroupStorage: IStorage<ICustomNodeGroup>;
 
+    /**
+     * @type {IStorage<string>}
+     */
+    private readonly stringArrayStorage: IStorage<string>;
+
     /**
      * @param customNodeGroupStorage
+     * @param stringArrayStorage
      * @param options
      */
     constructor (
         @inject(ServiceIdentifiers['IStorage<ICustomNodeGroup>']) customNodeGroupStorage: IStorage<ICustomNodeGroup>,
+        @inject(ServiceIdentifiers['IStorage<string>']) stringArrayStorage: IStorage<string>,
         @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
         super(options);
 
         this.customNodeGroupStorage = customNodeGroupStorage;
+        this.stringArrayStorage = stringArrayStorage;
     }
 
     /**
@@ -70,12 +73,6 @@ export class StringLiteralReplacer extends AbstractReplacer {
      * @returns {string}
      */
     private replaceStringLiteralWithStringArrayCall (value: string): string {
-        const stringArrayCustomNodeGroupNodes: Map <CustomNodes, ICustomNode> = this.customNodeGroupStorage
-            .get(CustomNodeGroups.StringArrayCustomNodeGroup)
-            .getCustomNodes();
-        const stringArrayNode: ICustomNodeWithData = <ICustomNodeWithData>stringArrayCustomNodeGroupNodes
-            .get(CustomNodes.StringArrayNode);
-
         let rc4Key: string = '';
 
         switch (this.options.stringArrayEncoding) {
@@ -95,26 +92,25 @@ export class StringLiteralReplacer extends AbstractReplacer {
             value = Utils.stringToUnicodeEscapeSequence(value);
         }
 
-        const stringArray: IStorage <string> = stringArrayNode.getNodeData();
-        const indexOfExistingValue: number = <number>stringArray.getKeyOf(value);
+        const indexOfExistingValue: number = <number>this.stringArrayStorage.getKeyOf(value);
 
         let indexOfValue: number;
 
         if (indexOfExistingValue >= 0) {
             indexOfValue = indexOfExistingValue;
         } else {
-            indexOfValue = stringArray.getLength();
-            stringArray.set(null, value);
+            indexOfValue = this.stringArrayStorage.getLength();
+            this.stringArrayStorage.set(null, value);
         }
 
-        const stringArrayCallsWrapper: ICustomNodeWithIdentifier = <ICustomNodeWithIdentifier>stringArrayCustomNodeGroupNodes
-            .get(CustomNodes.StringArrayCallsWrapper);
+        const rotatedStringArrayStorageId: string = Utils.stringRotate(this.stringArrayStorage.getStorageId(), 2);
+        const stringArrayStorageCallsWrapperName: string = `_${Utils.hexadecimalPrefix}${rotatedStringArrayStorageId}`;
         const hexadecimalIndex: string = `${Utils.hexadecimalPrefix}${Utils.decToHex(indexOfValue)}`;
 
         if (this.options.stringArrayEncoding === StringArrayEncoding.rc4) {
-            return `${stringArrayCallsWrapper.getNodeIdentifier()}('${hexadecimalIndex}', '${Utils.stringToUnicodeEscapeSequence(rc4Key)}')`;
+            return `${stringArrayStorageCallsWrapperName}('${hexadecimalIndex}', '${Utils.stringToUnicodeEscapeSequence(rc4Key)}')`;
         }
 
-        return `${stringArrayCallsWrapper.getNodeIdentifier()}('${hexadecimalIndex}')`;
+        return `${stringArrayStorageCallsWrapperName}('${hexadecimalIndex}')`;
     }
 }

+ 8 - 0
src/node/Node.ts

@@ -91,6 +91,14 @@ export class Node {
         return node.type === NodeType.Identifier;
     }
 
+    /**
+     * @param node
+     * @returns {boolean}
+     */
+    public static isIfStatementNode (node: ESTree.Node): node is ESTree.IfStatement {
+        return node.type === NodeType.IfStatement;
+    }
+
     /**
      * @param node
      * @param parentNode

+ 1 - 1
src/node/NodeAppender.ts

@@ -155,7 +155,7 @@ export class NodeAppender {
         blockScopeNode: TNodeWithBlockStatement,
         nodeBodyStatements: TStatement[]
     ): TStatement[] {
-        for (let statement of nodeBodyStatements) {
+        for (const statement of nodeBodyStatements) {
             statement.parentNode = blockScopeNode;
         }
 

+ 31 - 14
src/node/NodeUtils.ts

@@ -9,7 +9,6 @@ import { TStatement } from '../types/node/TStatement';
 import { NodeType } from '../enums/NodeType';
 
 import { Node } from './Node';
-import { Utils } from '../utils/Utils';
 
 export class NodeUtils {
     /**
@@ -50,6 +49,22 @@ export class NodeUtils {
         return <TStatement[]>structure.body;
     }
 
+    /**
+     * @param structure
+     * @returns {string}
+     */
+    public static convertStructureToCode (structure: ESTree.Node[]): string {
+        let code: string = '';
+
+        for (const node of structure) {
+            code += escodegen.generate(node, {
+                sourceMapWithCode: true
+            }).code;
+        }
+
+        return code;
+    }
+
     /**
      * @param node
      * @param index
@@ -69,10 +84,10 @@ export class NodeUtils {
 
     /**
      * @param node
-     * @param depth
+     * @param blockScopes
      * @returns {ESTree.Node}
      */
-    public static getBlockScopeOfNode (node: ESTree.Node, depth: number = 0): TNodeWithBlockStatement {
+    public static getBlockScopesOfNode (node: ESTree.Node, blockScopes: TNodeWithBlockStatement[] = []): TNodeWithBlockStatement[] {
         const parentNode: ESTree.Node | undefined = node.parentNode;
 
         if (!parentNode) {
@@ -84,20 +99,22 @@ export class NodeUtils {
                 throw new ReferenceError('`parentNode` property of `parentNode` of given node is `undefined`');
             }
 
-            if (!Utils.arrayContains(NodeUtils.nodesWithBlockScope, parentNode.parentNode.type)) {
-                return NodeUtils.getBlockScopeOfNode(parentNode, depth);
-            } else if (depth > 0) {
-                return NodeUtils.getBlockScopeOfNode(parentNode, --depth);
+            if (!NodeUtils.nodesWithBlockScope.includes(parentNode.parentNode.type)) {
+                return NodeUtils.getBlockScopesOfNode(parentNode, blockScopes);
             }
 
-            return parentNode;
+            blockScopes.push(parentNode);
+
+            return NodeUtils.getBlockScopesOfNode(parentNode, blockScopes);
         }
 
-        if (Node.isProgramNode(parentNode)) {
-            return parentNode;
+        if (!Node.isProgramNode(parentNode)) {
+            return NodeUtils.getBlockScopesOfNode(parentNode, blockScopes);
         }
 
-        return NodeUtils.getBlockScopeOfNode(parentNode);
+        blockScopes.push(parentNode);
+
+        return blockScopes;
     }
 
     /**
@@ -116,7 +133,7 @@ export class NodeUtils {
             return depth;
         }
 
-        if (Node.isBlockStatementNode(node) && Utils.arrayContains(NodeUtils.nodesWithBlockScope, parentNode.type)) {
+        if (Node.isBlockStatementNode(node) && NodeUtils.nodesWithBlockScope.includes(parentNode.type)) {
             return NodeUtils.getNodeBlockScopeDepth(parentNode, ++depth);
         }
 
@@ -180,12 +197,12 @@ export class NodeUtils {
         (<any>estraverse)[traverseType](node, {
             enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
                 if (node.type === nodeType && visitor.enter) {
-                    visitor.enter(node, parentNode);
+                    return visitor.enter(node, parentNode);
                 }
             },
             leave: (node: ESTree.Node, parentNode: ESTree.Node): any => {
                 if (node.type === nodeType && visitor.leave) {
-                    visitor.leave(node, parentNode);
+                    return visitor.leave(node, parentNode);
                 }
             }
         });

+ 9 - 1
src/options/Options.ts

@@ -23,7 +23,7 @@ import { IOptions } from '../interfaces/options/IOptions';
 import { TSourceMapMode } from '../types/TSourceMapMode';
 import { TStringArrayEncoding } from '../types/options/TStringArrayEncoding';
 
-import { DEFAULT_PRESET } from '../preset-options/DefaultPreset';
+import { DEFAULT_PRESET } from './presets/Default';
 
 import { OptionsNormalizer } from './OptionsNormalizer';
 import { ValidationErrorsFormatter } from './ValidationErrorsFormatter';
@@ -51,6 +51,14 @@ export class Options implements IOptions {
     @IsBoolean()
     public readonly controlFlowFlattening: boolean;
 
+    /**
+     * @type {boolean}
+     */
+    @IsNumber()
+    @Min(0)
+    @Max(1)
+    public readonly controlFlowFlatteningThreshold: number;
+
     /**
      * @type {boolean}
      */

+ 29 - 5
src/options/OptionsNormalizer.ts

@@ -10,7 +10,15 @@ export class OptionsNormalizer {
     /**
      * @type {TInputOptions}
      */
-    private static readonly DISABLED_UNICODE_ARRAY_OPTIONS: TInputOptions = {
+    private static readonly DISABLED_CONTROL_FLOW_FLATTENING_OPTIONS: TInputOptions = {
+        controlFlowFlattening: false,
+        controlFlowFlatteningThreshold: 0
+    };
+
+    /**
+     * @type {TInputOptions}
+     */
+    private static readonly DISABLED_STRING_ARRAY_OPTIONS: TInputOptions = {
         rotateStringArray: false,
         stringArray: false,
         stringArrayEncoding: false,
@@ -28,7 +36,7 @@ export class OptionsNormalizer {
     /**
      * @type {TInputOptions}
      */
-    private static readonly UNICODE_ARRAY_ENCODING_OPTIONS: TInputOptions = {
+    private static readonly STRING_ARRAY_ENCODING_OPTIONS: TInputOptions = {
         stringArrayEncoding: 'base64'
     };
 
@@ -36,6 +44,7 @@ export class OptionsNormalizer {
      * @type {TOptionsNormalizerRule[]}
      */
     private static readonly normalizerRules: TOptionsNormalizerRule[] = [
+        OptionsNormalizer.controlFlowFlatteningThresholdRule,
         OptionsNormalizer.domainLockRule,
         OptionsNormalizer.selfDefendingRule,
         OptionsNormalizer.sourceMapBaseUrlRule,
@@ -61,6 +70,21 @@ export class OptionsNormalizer {
         return normalizedOptions;
     }
 
+    /**
+     * @param options
+     * @returns {IOptions}
+     */
+    private static controlFlowFlatteningThresholdRule (options: IOptions): IOptions {
+        if (options.controlFlowFlatteningThreshold === 0) {
+            options = {
+                ...options,
+                ...OptionsNormalizer.DISABLED_CONTROL_FLOW_FLATTENING_OPTIONS
+            };
+        }
+
+        return options;
+    }
+
     /**
      * @param options
      * @returns {IOptions}
@@ -152,7 +176,7 @@ export class OptionsNormalizer {
         if (!options.stringArray) {
             options = {
                 ...options,
-                ...OptionsNormalizer.DISABLED_UNICODE_ARRAY_OPTIONS
+                ...OptionsNormalizer.DISABLED_STRING_ARRAY_OPTIONS
             };
         }
 
@@ -167,7 +191,7 @@ export class OptionsNormalizer {
         if (options.stringArrayEncoding === true) {
             options = {
                 ...options,
-                ...OptionsNormalizer.UNICODE_ARRAY_ENCODING_OPTIONS
+                ...OptionsNormalizer.STRING_ARRAY_ENCODING_OPTIONS
             };
         }
 
@@ -182,7 +206,7 @@ export class OptionsNormalizer {
         if (options.stringArrayThreshold === 0) {
             options = {
                 ...options,
-                ...OptionsNormalizer.DISABLED_UNICODE_ARRAY_OPTIONS
+                ...OptionsNormalizer.DISABLED_STRING_ARRAY_OPTIONS
             };
         }
 

+ 3 - 2
src/preset-options/DefaultPreset.ts → src/options/presets/Default.ts

@@ -1,10 +1,11 @@
-import { TInputOptions } from '../types/options/TInputOptions';
+import { TInputOptions } from '../../types/options/TInputOptions';
 
-import { SourceMapMode } from '../enums/SourceMapMode';
+import { SourceMapMode } from '../../enums/SourceMapMode';
 
 export const DEFAULT_PRESET: TInputOptions = Object.freeze({
     compact: true,
     controlFlowFlattening: false,
+    controlFlowFlatteningThreshold: 0.75,
     debugProtection: false,
     debugProtectionInterval: false,
     disableConsoleOutput: true,

+ 3 - 2
src/preset-options/NoCustomNodesPreset.ts → src/options/presets/NoCustomNodes.ts

@@ -1,10 +1,11 @@
-import { TInputOptions } from '../types/options/TInputOptions';
+import { TInputOptions } from '../../types/options/TInputOptions';
 
-import { SourceMapMode } from '../enums/SourceMapMode';
+import { SourceMapMode } from '../../enums/SourceMapMode';
 
 export const NO_CUSTOM_NODES_PRESET: TInputOptions = Object.freeze({
     compact: true,
     controlFlowFlattening: false,
+    controlFlowFlatteningThreshold: 0,
     debugProtection: false,
     debugProtectionInterval: false,
     disableConsoleOutput: false,

+ 1 - 1
src/stack-trace-analyzer/StackTraceAnalyzer.ts

@@ -130,7 +130,7 @@ export class StackTraceAnalyzer implements IStackTraceAnalyzer {
                 enter: (node: ESTree.Node): void => {
                     if (
                         !Node.isCallExpressionNode(node) ||
-                        blockScopeBodyNode.parentNode !== NodeUtils.getBlockScopeOfNode(node)
+                        blockScopeBodyNode.parentNode !== NodeUtils.getBlockScopesOfNode(node)[0]
                     ) {
                         return;
                     }

+ 1 - 1
src/stack-trace-analyzer/callee-data-extractors/FunctionDeclarationCalleeDataExtractor.ts

@@ -21,7 +21,7 @@ export class FunctionDeclarationCalleeDataExtractor extends AbstractCalleeDataEx
 
         if (Node.isIdentifierNode(callee)) {
             calleeBlockStatement = this.getCalleeBlockStatement(
-                NodeUtils.getBlockScopeOfNode(blockScopeBody[0]),
+                NodeUtils.getBlockScopesOfNode(blockScopeBody[0])[0],
                 callee.name
             );
         }

+ 1 - 1
src/stack-trace-analyzer/callee-data-extractors/FunctionExpressionCalleeDataExtractor.ts

@@ -21,7 +21,7 @@ export class FunctionExpressionCalleeDataExtractor extends AbstractCalleeDataExt
 
         if (Node.isIdentifierNode(callee)) {
             calleeBlockStatement = this.getCalleeBlockStatement(
-                NodeUtils.getBlockScopeOfNode(blockScopeBody[0]),
+                NodeUtils.getBlockScopesOfNode(blockScopeBody[0])[0],
                 callee.name
             );
         }

+ 1 - 1
src/stack-trace-analyzer/callee-data-extractors/ObjectExpressionCalleeDataExtractor.ts

@@ -34,7 +34,7 @@ export class ObjectExpressionCalleeDataExtractor extends AbstractCalleeDataExtra
 
             functionExpressionName = objectMembersCallsChain[objectMembersCallsChain.length - 1];
             calleeBlockStatement = this.getCalleeBlockStatement(
-                NodeUtils.getBlockScopeOfNode(blockScopeBody[0]),
+                NodeUtils.getBlockScopesOfNode(blockScopeBody[0])[0],
                 objectMembersCallsChain
             );
         }

+ 31 - 0
src/storages/ArrayStorage.ts

@@ -1,8 +1,19 @@
+import { injectable } from 'inversify';
+
 import { IStorage } from '../interfaces/storages/IStorage';
 
 import { initializable } from '../decorators/Initializable';
 
+import { RandomGeneratorUtils } from '../utils/RandomGeneratorUtils';
+
+@injectable()
 export abstract class ArrayStorage <T> implements IStorage <T> {
+    /**
+     * @type {string}
+     */
+    @initializable()
+    protected storageId: string;
+
     /**
      * @type {T[]}
      */
@@ -45,11 +56,31 @@ export abstract class ArrayStorage <T> implements IStorage <T> {
         return this.storage;
     }
 
+    /**
+     * @returns {string}
+     */
+    public getStorageId (): string {
+        return this.storageId;
+    }
+
     /**
      * @param args
      */
     public initialize (...args: any[]): void {
         this.storage = [];
+        this.storageId = RandomGeneratorUtils.getRandomString(6);
+    }
+
+    /**
+     * @param storage
+     * @param mergeId
+     */
+    public mergeWith (storage: this, mergeId: boolean = false): void {
+        this.storage = [...this.storage, ...storage.getStorage()];
+
+        if (mergeId) {
+            this.storageId = storage.getStorageId();
+        }
     }
 
     /**

+ 28 - 1
src/storages/MapStorage.ts

@@ -4,10 +4,17 @@ import { IStorage } from '../interfaces/storages/IStorage';
 
 import { initializable } from '../decorators/Initializable';
 
+import { RandomGeneratorUtils } from '../utils/RandomGeneratorUtils';
 import { Utils } from '../utils/Utils';
 
 @injectable()
 export abstract class MapStorage <T> implements IStorage <T> {
+    /**
+     * @type {string}
+     */
+    @initializable()
+    protected storageId: string;
+
     /**
      * @type {Map <string | number, T>}
      */
@@ -33,7 +40,7 @@ export abstract class MapStorage <T> implements IStorage <T> {
      * @returns {string | number | null}
      */
     public getKeyOf (value: T): string | number | null {
-        return Utils.mapGetFirstKeyOf(this.storage, value);
+        return Utils.mapGetFirstKeyOf <string | number, T> (this.storage, value);
     }
 
     /**
@@ -50,11 +57,31 @@ export abstract class MapStorage <T> implements IStorage <T> {
         return this.storage;
     }
 
+    /**
+     * @returns {string}
+     */
+    public getStorageId (): string {
+        return this.storageId;
+    }
+
     /**
      * @param args
      */
     public initialize (...args: any[]): void {
         this.storage = new Map <string | number, T> ();
+        this.storageId = RandomGeneratorUtils.getRandomString(6);
+    }
+
+    /**
+     * @param storage
+     * @param mergeId
+     */
+    public mergeWith (storage: this, mergeId: boolean = false): void {
+        this.storage = new Map <string | number, T> ([...this.storage, ...storage.getStorage()]);
+
+        if (mergeId) {
+            this.storageId = storage.getStorageId();
+        }
     }
 
     /**

+ 2 - 0
src/storages/custom-node-group/CustomNodeGroupStorage.ts

@@ -9,6 +9,7 @@ import { IOptions } from '../../interfaces/options/IOptions';
 import { CustomNodeGroups } from '../../enums/container/CustomNodeGroups';
 
 import { MapStorage } from '../MapStorage';
+import { RandomGeneratorUtils } from '../../utils/RandomGeneratorUtils';
 
 @injectable()
 export class CustomNodeGroupStorage extends MapStorage <ICustomNodeGroup> {
@@ -51,6 +52,7 @@ export class CustomNodeGroupStorage extends MapStorage <ICustomNodeGroup> {
 
     public initialize (): void {
         this.storage = new Map <string, ICustomNodeGroup> ();
+        this.storageId = RandomGeneratorUtils.getRandomString(6);
 
         CustomNodeGroupStorage.customNodeGroupsList.forEach((customNodeGroupName: CustomNodeGroups) => {
             const customNodeGroup: ICustomNodeGroup = this.customNodeGroupFactory(

+ 14 - 1
src/storages/string-array/StringArrayStorage.ts

@@ -1,6 +1,10 @@
+import { injectable } from 'inversify';
+
 import { ArrayStorage } from '../ArrayStorage';
+import { RandomGeneratorUtils } from '../../utils/RandomGeneratorUtils';
 import { Utils } from '../../utils/Utils';
 
+@injectable()
 export class StringArrayStorage extends ArrayStorage <string> {
     constructor () {
         super();
@@ -8,6 +12,15 @@ export class StringArrayStorage extends ArrayStorage <string> {
         this.initialize();
     }
 
+    /**
+     * @param args
+     */
+    public initialize (...args: any[]): void {
+        super.initialize(args);
+
+        this.storageId = RandomGeneratorUtils.getRandomVariableName(4, false);
+    }
+
     /**
      * @param rotationValue
      */
@@ -23,4 +36,4 @@ export class StringArrayStorage extends ArrayStorage <string> {
             return `'${value}'`;
         }).toString();
     }
-}
+}

+ 2 - 2
src/utils/CryptUtils.ts

@@ -58,7 +58,7 @@ export class CryptUtils {
             return result;
         };
 
-        const randomString: string = RandomGeneratorUtils.randomGenerator.string({
+        const randomString: string = RandomGeneratorUtils.getRandomGenerator().string({
             length: length,
             pool: RandomGeneratorUtils.randomGeneratorPool
         });
@@ -69,7 +69,7 @@ export class CryptUtils {
 
         const randomStringDiffArray: string[] = randomStringDiff.split('');
 
-        RandomGeneratorUtils.randomGenerator.shuffle(randomStringDiffArray);
+        RandomGeneratorUtils.getRandomGenerator().shuffle(randomStringDiffArray);
         randomStringDiff = randomStringDiffArray.join('');
 
         return [randomMerge(str, randomStringDiff), randomStringDiff];

+ 21 - 10
src/utils/RandomGeneratorUtils.ts

@@ -16,7 +16,7 @@ export class RandomGeneratorUtils {
     /**
      * @type {Chance.Chance | Chance.SeededChance}
      */
-    public static randomGenerator: Chance.Chance | Chance.SeededChance = new Chance();
+    private static randomGenerator: Chance.Chance | Chance.SeededChance = new Chance();
 
     /**
      * @param min
@@ -24,7 +24,7 @@ export class RandomGeneratorUtils {
      * @returns {number}
      */
     public static getRandomFloat (min: number, max: number): number {
-        return RandomGeneratorUtils.randomGenerator.floating({
+        return RandomGeneratorUtils.getRandomGenerator().floating({
             min: min,
             max: max,
             fixed: 7
@@ -50,7 +50,7 @@ export class RandomGeneratorUtils {
      * @returns {number}
      */
     public static getRandomInteger (min: number, max: number): number {
-        return RandomGeneratorUtils.randomGenerator.integer({
+        return RandomGeneratorUtils.getRandomGenerator().integer({
             min: min,
             max: max
         });
@@ -58,13 +58,24 @@ export class RandomGeneratorUtils {
 
     /**
      * @param length
+     * @param pool
      * @returns {string}
      */
-    public static getRandomVariableName (length: number = 6): string {
-        const rangeMinInteger: number = 10000,
-            rangeMaxInteger: number = 99999999;
+    public static getRandomString (length: number, pool: string = RandomGeneratorUtils.randomGeneratorPool): string {
+        return RandomGeneratorUtils.getRandomGenerator().string({ length, pool });
+    }
+
+    /**
+     * @param length
+     * @param withPrefix
+     * @returns {string}
+     */
+    public static getRandomVariableName (length: number = 6, withPrefix: boolean = true): string {
+        const prefix: string = withPrefix ? `_${Utils.hexadecimalPrefix}` : '';
+        const rangeMinInteger: number = 10000;
+        const rangeMaxInteger: number = 99999999;
 
-        return `_${Utils.hexadecimalPrefix}${(
+        return `${prefix}${(
             Utils.decToHex(
                 RandomGeneratorUtils.getRandomInteger(rangeMinInteger, rangeMaxInteger)
             )
@@ -72,9 +83,9 @@ export class RandomGeneratorUtils {
     }
 
     /**
-     * @param randomGeneratorSeed
+     * @param randomGenerator
      */
-    public static setRandomGeneratorSeed (randomGeneratorSeed: number): void {
-        RandomGeneratorUtils.randomGenerator = new Chance(randomGeneratorSeed);
+    public static setRandomGenerator (randomGenerator: Chance.Chance | Chance.SeededChance): void {
+        RandomGeneratorUtils.randomGenerator = randomGenerator;
     }
 }

+ 19 - 19
src/utils/Utils.ts

@@ -1,22 +1,12 @@
+import * as _ from 'lodash';
 import { JSFuck } from '../enums/JSFuck';
 
-const isEqual: any = require('is-equal');
-
 export class Utils {
     /**
      * @type {string}
      */
     public static readonly hexadecimalPrefix: string = '0x';
 
-    /**
-     * @param array
-     * @param searchElement
-     * @returns {boolean}
-     */
-    public static arrayContains (array: any[], searchElement: any): boolean {
-        return array.indexOf(searchElement) >= 0;
-    }
-
     /**
      * @param array
      * @param times
@@ -31,8 +21,9 @@ export class Utils {
             return array;
         }
 
-        let newArray: T[] = array,
-            temp: T | undefined;
+        const newArray: T[] = array;
+
+        let temp: T | undefined;
 
         while (times--) {
             temp = newArray.pop()!;
@@ -74,18 +65,18 @@ export class Utils {
      * @param number
      * @returns {boolean}
      */
-    public static isInteger (number: number): boolean {
+    public static isCeilNumber (number: number): boolean {
         return number % 1 === 0;
     }
 
     /**
      * @param map
      * @param value
-     * @returns {any}
+     * @returns {T | null}
      */
-    public static mapGetFirstKeyOf(map: Map <any, any>, value: any): any {
-        for (let [key, storageValue] of map) {
-            if (isEqual(value, storageValue)) {
+    public static mapGetFirstKeyOf <T, U> (map: Map <T, U>, value: U): T | null {
+        for (const [key, storageValue] of map) {
+            if (_.isEqual(value, storageValue)) {
                 return key;
             }
         }
@@ -101,6 +92,15 @@ export class Utils {
         return obj;
     }
 
+    /**
+     * @param string
+     * @param times
+     * @returns {string}
+     */
+    public static stringRotate (string: string, times: number): string {
+        return Utils.arrayRotate(Array.from(string), times).join('');
+    }
+
     /**
      * @param string
      * @returns {string}
@@ -120,9 +120,9 @@ export class Utils {
      */
     public static stringToUnicodeEscapeSequence (string: string): string {
         const radix: number = 16;
+        const regexp: RegExp = new RegExp('[\x00-\x7F]');
 
         let prefix: string,
-            regexp: RegExp = new RegExp('[\x00-\x7F]'),
             template: string;
 
         return `${string.replace(/[\s\S]/g, (escape: string): string => {

+ 66 - 3
test/dev/dev.ts

@@ -1,5 +1,4 @@
 'use strict';
-import { NO_CUSTOM_NODES_PRESET } from '../../src/preset-options/NoCustomNodesPreset';
 
 if (!(<any>global)._babelPolyfill) {
     require('babel-polyfill');
@@ -11,15 +10,79 @@ if (!(<any>global)._babelPolyfill) {
     let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
         `
             (function(){
+                var result = 1,
+                    term1 = 0,
+                    term2 = 1,
+                    i = 1;
+                while(i < 10)
+                {
+                    var test = 10;
+                    result = term1 + term2;
+                    console.log(result);
+                    term1 = term2;
+                    term2 = result;
+                    i++;
+                }
+        
+                console.log(test);
+                
+                var test = function (test) {
+                    console.log(test);
+                    
+                    if (true) {
+                        var test = 5
+                    }
+                    
+                    return test;
+                }
+                
+                console.log(test(1));
+                
+                function test2 (abc) {
+                    function test1 () {
+                      console.log('inside', abc.item);
+                    }
+                    
+                    console.log('тест', abc);
+                    
+                    var abc = {};
+                    
+                    return abc.item = 15, test1();
+                };
+                
+                var regexptest = /version\\/(\\d+)/i;
+                console.log(regexptest);
+                
+                test2(22);
+                console.log(105.4);
+                console.log(true, false);
+                
+                var sA = 'shorthand1';
+                var sB = 'shorthand2';
+                
+                console.log({sA, sB});
+                
+                try {
+                } catch (error) {
+                    console.log(error);
+                }
+                
                 function foo () {
                     return function () {
-                        var sum = 1 + 2;
+                        var sum1 = 10 + 20;
+                        var sum2 = 20 + 30;
+                        var sum3 = 30 + 50;
+                        var sub = sum3 - sum2;
+                        
+                        return sum1 + sub;
                     }
                 }
+                
+                console.log(foo()());
             })();
         `,
         {
-            ...NO_CUSTOM_NODES_PRESET,
+            compact: false,
             controlFlowFlattening: true,
             disableConsoleOutput: false
         }

+ 18 - 18
test/fixtures/compile-performance.js

@@ -19813,8 +19813,8 @@
                 else {
                     mergedOutputs = outputs;
                 }
-                var mergedHost = __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_2__facade_lang__["a" /* isPresent */])(dm.host) ? __WEBPACK_IMPORTED_MODULE_1__facade_collection__["b" /* StringMapWrapper */].merge(dm.host, host) : host;
-                var mergedQueries = __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_2__facade_lang__["a" /* isPresent */])(dm.queries) ? __WEBPACK_IMPORTED_MODULE_1__facade_collection__["b" /* StringMapWrapper */].merge(dm.queries, queries) : queries;
+                var mergedHost = __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_2__facade_lang__["a" /* isPresent */])(dm.host) ? __WEBPACK_IMPORTED_MODULE_1__facade_collection__["b" /* StringMapWrapper */].mergeWith(dm.host, host) : host;
+                var mergedQueries = __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_2__facade_lang__["a" /* isPresent */])(dm.queries) ? __WEBPACK_IMPORTED_MODULE_1__facade_collection__["b" /* StringMapWrapper */].mergeWith(dm.queries, queries) : queries;
                 if (dm instanceof __WEBPACK_IMPORTED_MODULE_0__angular_core__["Component"]) {
                     return new __WEBPACK_IMPORTED_MODULE_0__angular_core__["Component"]({
                         selector: dm.selector,
@@ -25918,7 +25918,7 @@
         }
         function _createRootRenderer(rootRenderer /** TODO #9100 */, extraTokens) {
             __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_3__dom_adapter__["a" /* getDOM */])().setGlobalVar(INSPECT_GLOBAL_NAME, inspectNativeElement);
-            __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_3__dom_adapter__["a" /* getDOM */])().setGlobalVar(CORE_TOKENS_GLOBAL_NAME, __WEBPACK_IMPORTED_MODULE_1__facade_collection__["a" /* StringMapWrapper */].merge(CORE_TOKENS, _ngProbeTokensToMap(extraTokens || [])));
+            __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_3__dom_adapter__["a" /* getDOM */])().setGlobalVar(CORE_TOKENS_GLOBAL_NAME, __WEBPACK_IMPORTED_MODULE_1__facade_collection__["a" /* StringMapWrapper */].mergeWith(CORE_TOKENS, _ngProbeTokensToMap(extraTokens || [])));
             return new __WEBPACK_IMPORTED_MODULE_2__private_import_core__["b" /* DebugDomRootRenderer */](rootRenderer);
         }
         function _ngProbeTokensToMap(tokens) {
@@ -38913,14 +38913,14 @@
                         if (!_this._finished) {
                             var responseOptions_1 = new __WEBPACK_IMPORTED_MODULE_2__base_response_options__["a" /* ResponseOptions */]({ body: JSONP_ERR_NO_CALLBACK, type: __WEBPACK_IMPORTED_MODULE_3__enums__["a" /* ResponseType */].Error, url: url });
                             if (__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_4__facade_lang__["a" /* isPresent */])(baseResponseOptions)) {
-                                responseOptions_1 = baseResponseOptions.merge(responseOptions_1);
+                                responseOptions_1 = baseResponseOptions.mergeWith(responseOptions_1);
                             }
                             responseObserver.error(new __WEBPACK_IMPORTED_MODULE_6__static_response__["a" /* Response */](responseOptions_1));
                             return;
                         }
                         var responseOptions = new __WEBPACK_IMPORTED_MODULE_2__base_response_options__["a" /* ResponseOptions */]({ body: _this._responseData, url: url });
                         if (__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_4__facade_lang__["a" /* isPresent */])(_this.baseResponseOptions)) {
-                            responseOptions = _this.baseResponseOptions.merge(responseOptions);
+                            responseOptions = _this.baseResponseOptions.mergeWith(responseOptions);
                         }
                         responseObserver.next(new __WEBPACK_IMPORTED_MODULE_6__static_response__["a" /* Response */](responseOptions));
                         responseObserver.complete();
@@ -38932,7 +38932,7 @@
                         _dom.cleanup(script);
                         var responseOptions = new __WEBPACK_IMPORTED_MODULE_2__base_response_options__["a" /* ResponseOptions */]({ body: error.message, type: __WEBPACK_IMPORTED_MODULE_3__enums__["a" /* ResponseType */].Error });
                         if (__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_4__facade_lang__["a" /* isPresent */])(baseResponseOptions)) {
-                            responseOptions = baseResponseOptions.merge(responseOptions);
+                            responseOptions = baseResponseOptions.mergeWith(responseOptions);
                         }
                         responseObserver.error(new __WEBPACK_IMPORTED_MODULE_6__static_response__["a" /* Response */](responseOptions));
                     };
@@ -39075,7 +39075,7 @@
                         var statusText = _xhr.statusText || 'OK';
                         var responseOptions = new __WEBPACK_IMPORTED_MODULE_3__base_response_options__["a" /* ResponseOptions */]({ body: body, status: status, headers: headers, statusText: statusText, url: url });
                         if (__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_5__facade_lang__["a" /* isPresent */])(baseResponseOptions)) {
-                            responseOptions = baseResponseOptions.merge(responseOptions);
+                            responseOptions = baseResponseOptions.mergeWith(responseOptions);
                         }
                         var response = new __WEBPACK_IMPORTED_MODULE_9__static_response__["a" /* Response */](responseOptions);
                         response.ok = __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_7__http_utils__["d" /* isSuccess */])(status);
@@ -39096,7 +39096,7 @@
                             statusText: _xhr.statusText,
                         });
                         if (__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_5__facade_lang__["a" /* isPresent */])(baseResponseOptions)) {
-                            responseOptions = baseResponseOptions.merge(responseOptions);
+                            responseOptions = baseResponseOptions.mergeWith(responseOptions);
                         }
                         responseObserver.error(new __WEBPACK_IMPORTED_MODULE_9__static_response__["a" /* Response */](responseOptions));
                     };
@@ -39355,7 +39355,7 @@
             var newOptions = defaultOpts;
             if (__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_1__src_facade_lang__["a" /* isPresent */])(providedOpts)) {
                 // Hack so Dart can used named parameters
-                return newOptions.merge(new __WEBPACK_IMPORTED_MODULE_2__base_request_options__["a" /* RequestOptions */]({
+                return newOptions.mergeWith(new __WEBPACK_IMPORTED_MODULE_2__base_request_options__["a" /* RequestOptions */]({
                     method: providedOpts.method || method,
                     url: providedOpts.url || url,
                     search: providedOpts.search,
@@ -39366,10 +39366,10 @@
                 }));
             }
             if (__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_1__src_facade_lang__["a" /* isPresent */])(method)) {
-                return newOptions.merge(new __WEBPACK_IMPORTED_MODULE_2__base_request_options__["a" /* RequestOptions */]({ method: method, url: url }));
+                return newOptions.mergeWith(new __WEBPACK_IMPORTED_MODULE_2__base_request_options__["a" /* RequestOptions */]({ method: method, url: url }));
             }
             else {
-                return newOptions.merge(new __WEBPACK_IMPORTED_MODULE_2__base_request_options__["a" /* RequestOptions */]({ url: url }));
+                return newOptions.mergeWith(new __WEBPACK_IMPORTED_MODULE_2__base_request_options__["a" /* RequestOptions */]({ url: url }));
             }
         }
         /**
@@ -39466,13 +39466,13 @@
              * Performs a request with `post` http method.
              */
             Http.prototype.post = function (url, body, options) {
-                return httpRequest(this._backend, new __WEBPACK_IMPORTED_MODULE_5__static_request__["a" /* Request */](mergeOptions(this._defaultOptions.merge(new __WEBPACK_IMPORTED_MODULE_2__base_request_options__["a" /* RequestOptions */]({ body: body })), options, __WEBPACK_IMPORTED_MODULE_3__enums__["b" /* RequestMethod */].Post, url)));
+                return httpRequest(this._backend, new __WEBPACK_IMPORTED_MODULE_5__static_request__["a" /* Request */](mergeOptions(this._defaultOptions.mergeWith(new __WEBPACK_IMPORTED_MODULE_2__base_request_options__["a" /* RequestOptions */]({ body: body })), options, __WEBPACK_IMPORTED_MODULE_3__enums__["b" /* RequestMethod */].Post, url)));
             };
             /**
              * Performs a request with `put` http method.
              */
             Http.prototype.put = function (url, body, options) {
-                return httpRequest(this._backend, new __WEBPACK_IMPORTED_MODULE_5__static_request__["a" /* Request */](mergeOptions(this._defaultOptions.merge(new __WEBPACK_IMPORTED_MODULE_2__base_request_options__["a" /* RequestOptions */]({ body: body })), options, __WEBPACK_IMPORTED_MODULE_3__enums__["b" /* RequestMethod */].Put, url)));
+                return httpRequest(this._backend, new __WEBPACK_IMPORTED_MODULE_5__static_request__["a" /* Request */](mergeOptions(this._defaultOptions.mergeWith(new __WEBPACK_IMPORTED_MODULE_2__base_request_options__["a" /* RequestOptions */]({ body: body })), options, __WEBPACK_IMPORTED_MODULE_3__enums__["b" /* RequestMethod */].Put, url)));
             };
             /**
              * Performs a request with `delete` http method.
@@ -39484,7 +39484,7 @@
              * Performs a request with `patch` http method.
              */
             Http.prototype.patch = function (url, body, options) {
-                return httpRequest(this._backend, new __WEBPACK_IMPORTED_MODULE_5__static_request__["a" /* Request */](mergeOptions(this._defaultOptions.merge(new __WEBPACK_IMPORTED_MODULE_2__base_request_options__["a" /* RequestOptions */]({ body: body })), options, __WEBPACK_IMPORTED_MODULE_3__enums__["b" /* RequestMethod */].Patch, url)));
+                return httpRequest(this._backend, new __WEBPACK_IMPORTED_MODULE_5__static_request__["a" /* Request */](mergeOptions(this._defaultOptions.mergeWith(new __WEBPACK_IMPORTED_MODULE_2__base_request_options__["a" /* RequestOptions */]({ body: body })), options, __WEBPACK_IMPORTED_MODULE_3__enums__["b" /* RequestMethod */].Patch, url)));
             };
             /**
              * Performs a request with `head` http method.
@@ -49300,7 +49300,7 @@
                 var lastIndex = stylesList.length - 1;
                 var lastItem = stylesList[lastIndex];
                 if (__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_2__facade_lang__["l" /* isStringMap */])(lastItem)) {
-                    stylesList[lastIndex] = __WEBPACK_IMPORTED_MODULE_1__facade_collection__["b" /* StringMapWrapper */].merge(lastItem, newItem);
+                    stylesList[lastIndex] = __WEBPACK_IMPORTED_MODULE_1__facade_collection__["b" /* StringMapWrapper */].mergeWith(lastItem, newItem);
                     return;
                 }
             }
@@ -53616,7 +53616,7 @@
                     hasExtraFirstStyles = true;
                 }
             });
-            var keyframeCollectedStyles = __WEBPACK_IMPORTED_MODULE_0__facade_collection__["d" /* StringMapWrapper */].merge({}, flatenedFirstKeyframeStyles);
+            var keyframeCollectedStyles = __WEBPACK_IMPORTED_MODULE_0__facade_collection__["d" /* StringMapWrapper */].mergeWith({}, flatenedFirstKeyframeStyles);
             // phase 2: normalize the final keyframe
             var finalKeyframe = keyframes[limit];
             __WEBPACK_IMPORTED_MODULE_0__facade_collection__["a" /* ListWrapper */].insert(finalKeyframe.styles.styles, 0, finalStateStyles);
@@ -64367,7 +64367,7 @@
         "use strict";
         var Observable_1 = __webpack_require__(0);
         var merge_1 = __webpack_require__(857);
-        Observable_1.Observable.merge = merge_1.merge;
+        Observable_1.Observable.merge = merge_1.mergeWith;
 //# sourceMappingURL=merge.js.map
 
         /***/ },
@@ -64975,7 +64975,7 @@
         "use strict";
         var Observable_1 = __webpack_require__(0);
         var merge_1 = __webpack_require__(404);
-        Observable_1.Observable.prototype.merge = merge_1.merge;
+        Observable_1.Observable.prototype.merge = merge_1.mergeWith;
 //# sourceMappingURL=merge.js.map
 
         /***/ },

+ 3 - 0
test/fixtures/node-transformers/node-control-flow-transformers/control-flow-replacers/binary-expression-control-flow-replacer-1.js

@@ -0,0 +1,3 @@
+(function () {
+    var variable = 1 + 2;
+})();

+ 4 - 0
test/fixtures/node-transformers/node-control-flow-transformers/control-flow-replacers/binary-expression-control-flow-replacer-2.js

@@ -0,0 +1,4 @@
+(function () {
+    var variable1 = 1 + 2;
+    var variable2 = 2 + 3;
+})();

+ 13 - 1
test/functional-tests/JavaScriptObfuscator.spec.ts

@@ -1,14 +1,22 @@
 import { assert } from 'chai';
 
+import { Chance } from 'chance';
+
 import { IObfuscationResult } from '../../src/interfaces/IObfuscationResult';
 
 import { JavaScriptObfuscator } from '../../src/JavaScriptObfuscator';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../src/preset-options/NoCustomNodesPreset';
+import { NO_CUSTOM_NODES_PRESET } from '../../src/options/presets/NoCustomNodes';
+
 import { readFileAsString } from '../helpers/readFileAsString';
+import { RandomGeneratorUtils } from '../../src/utils/RandomGeneratorUtils';
 
 describe('JavaScriptObfuscator', () => {
     describe('obfuscate (sourceCode: string, customOptions?: IObfuscatorOptions): IObfuscationResult', () => {
+        beforeEach(() => {
+            RandomGeneratorUtils.setRandomGenerator(new Chance());
+        });
+
         describe('if `sourceMap` option is `false`', () => {
             it('should returns object with obfuscated code and empty source map', () => {
                 let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
@@ -171,5 +179,9 @@ describe('JavaScriptObfuscator', () => {
             assert.notEqual(obfuscationResult1.getObfuscatedCode(), obfuscationResult2.getObfuscatedCode());
             assert.notEqual(obfuscationResult3.getObfuscatedCode(), obfuscationResult4.getObfuscatedCode());
         });
+
+        afterEach(() => {
+            RandomGeneratorUtils.setRandomGenerator(new Chance());
+        });
     });
 });

+ 1 - 1
test/functional-tests/JavaScriptObfuscatorInternal.spec.ts

@@ -6,7 +6,7 @@ import { IInversifyContainerFacade } from '../../src/interfaces/container/IInver
 import { IJavaScriptObfuscator } from '../../src/interfaces/IJavaScriptObfsucator';
 import { IObfuscationResult } from '../../src/interfaces/IObfuscationResult';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../src/preset-options/NoCustomNodesPreset';
+import { NO_CUSTOM_NODES_PRESET } from '../../src/options/presets/NoCustomNodes';
 
 import { InversifyContainerFacade } from '../../src/container/InversifyContainerFacade';
 

+ 1 - 1
test/functional-tests/custom-nodes/console-output-nodes/ConsoleOutputDisableExpressionNode.spec.ts

@@ -2,7 +2,7 @@ import { assert } from 'chai';
 
 import { IObfuscationResult } from '../../../../src/interfaces/IObfuscationResult';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../../../src/preset-options/NoCustomNodesPreset';
+import { NO_CUSTOM_NODES_PRESET } from '../../../../src/options/presets/NoCustomNodes';
 
 import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
 

+ 1 - 1
test/functional-tests/custom-nodes/domain-lock-nodes/DomainLockNode.spec.ts

@@ -2,7 +2,7 @@ import { assert } from 'chai';
 
 import { IObfuscationResult } from '../../../../src/interfaces/IObfuscationResult';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../../../src/preset-options/NoCustomNodesPreset';
+import { NO_CUSTOM_NODES_PRESET } from '../../../../src/options/presets/NoCustomNodes';
 
 import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
 

+ 1 - 1
test/functional-tests/custom-nodes/string-array-nodes/StringArrayCallsWrapper.spec.ts

@@ -2,7 +2,7 @@ import { assert } from 'chai';
 
 import { IObfuscationResult } from '../../../../src/interfaces/IObfuscationResult';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../../../src/preset-options/NoCustomNodesPreset';
+import { NO_CUSTOM_NODES_PRESET } from '../../../../src/options/presets/NoCustomNodes';
 
 import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
 

+ 1 - 1
test/functional-tests/custom-nodes/string-array-nodes/StringArrayNode.spec.ts

@@ -2,7 +2,7 @@ import { assert } from 'chai';
 
 import { IObfuscationResult } from '../../../../src/interfaces/IObfuscationResult';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../../../src/preset-options/NoCustomNodesPreset';
+import { NO_CUSTOM_NODES_PRESET } from '../../../../src/options/presets/NoCustomNodes';
 
 import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
 

+ 1 - 1
test/functional-tests/custom-nodes/string-array-nodes/StringArrayRotateFunctionNode.spec.ts

@@ -2,7 +2,7 @@ import { assert } from 'chai';
 
 import { IObfuscationResult } from '../../../../src/interfaces/IObfuscationResult';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../../../src/preset-options/NoCustomNodesPreset';
+import { NO_CUSTOM_NODES_PRESET } from '../../../../src/options/presets/NoCustomNodes';
 
 import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
 

+ 77 - 0
test/functional-tests/node-transformers/node-control-flow-transformers/control-flow-replacers/BinaryExpressionControlFlowReplacer.spec.ts

@@ -0,0 +1,77 @@
+import { assert } from 'chai';
+
+import { IObfuscationResult } from '../../../../../src/interfaces/IObfuscationResult';
+
+import { NO_CUSTOM_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
+
+import { readFileAsString } from '../../../../helpers/readFileAsString';
+
+import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscator';
+
+describe('BinaryExpressionControlFlowReplacer', () => {
+    describe('replace (binaryExpressionNode: ESTree.BinaryExpression,parentNode: ESTree.Node,controlFlowStorage: IStorage <ICustomNode>)', () => {
+        describe('variant #1 - single binary expression', () => {
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                readFileAsString(
+                    './test/fixtures/node-transformers/node-control-flow-transformers/control-flow-replacers/binary-expression-control-flow-replacer-1.js'
+                ),
+                {
+                    ...NO_CUSTOM_NODES_PRESET,
+                    controlFlowFlattening: true,
+                    controlFlowFlatteningThreshold: 1
+                }
+            );
+            const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
+            const controlFlowStorageCallRegExp: RegExp = /var *_0x([a-z0-9]){4,6} *= *_0x([a-z0-9]){4,6}\['(\\x[a-f0-9]*){3}'\]\(0x1, *0x2\);/;
+
+            it('should replace binary expression node by call to control flow storage node', () => {
+                assert.match(obfuscatedCode, controlFlowStorageCallRegExp);
+            });
+        });
+
+        describe('variant #2 - multiple binary expressions with threshold = 1', () => {
+            it('should replace binary expression node by call to control flow storage node', function () {
+                this.timeout(4000);
+
+                const samplesCount: number = 200;
+                const expectedValue: number = 0.5;
+                const delta: number = 0.1;
+
+                let equalsValue: number = 0;
+
+                for (let i = 0; i < samplesCount; i++) {
+                    const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                        readFileAsString(
+                            './test/fixtures/node-transformers/node-control-flow-transformers/control-flow-replacers/binary-expression-control-flow-replacer-2.js'
+                        ),
+                        {
+                            ...NO_CUSTOM_NODES_PRESET,
+                            controlFlowFlattening: true,
+                            controlFlowFlatteningThreshold: 1
+                        }
+                    );
+                    const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
+                    const controlFlowStorageCallRegExp1: RegExp = /var *_0x([a-z0-9]){4,6} *= *(_0x([a-z0-9]){4,6}\['(\\x[a-f0-9]*){3}'\])\(0x1, *0x2\);/;
+                    const controlFlowStorageCallRegExp2: RegExp = /var *_0x([a-z0-9]){4,6} *= *(_0x([a-z0-9]){4,6}\['(\\x[a-f0-9]*){3}'\])\(0x2, *0x3\);/;
+
+                    const firstMatchArray: RegExpMatchArray | null = obfuscatedCode.match(controlFlowStorageCallRegExp1);
+                    const secondMatchArray: RegExpMatchArray | null = obfuscatedCode.match(controlFlowStorageCallRegExp2);
+
+                    const firstMatch: string | undefined = firstMatchArray ? firstMatchArray[2] : undefined;
+                    const secondMatch: string | undefined = secondMatchArray ? secondMatchArray[2] : undefined;
+
+                    assert.match(obfuscatedCode, controlFlowStorageCallRegExp1);
+                    assert.match(obfuscatedCode, controlFlowStorageCallRegExp2);
+                    assert.isOk(firstMatch);
+                    assert.isOk(secondMatch);
+
+                    if (firstMatch === secondMatch) {
+                        equalsValue++;
+                    }
+                }
+
+                assert.closeTo(equalsValue / samplesCount, expectedValue, delta);
+            });
+        });
+    });
+});

+ 5 - 5
test/functional-tests/node-transformers/node-obfuscators/CatchClauseObfuscator.spec.ts

@@ -2,7 +2,7 @@ import { assert } from 'chai';
 
 import { IObfuscationResult } from '../../../../src/interfaces/IObfuscationResult';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../../../src/preset-options/NoCustomNodesPreset';
+import { NO_CUSTOM_NODES_PRESET } from '../../../../src/options/presets/NoCustomNodes';
 
 import { readFileAsString } from '../../../helpers/readFileAsString';
 
@@ -28,11 +28,11 @@ describe('CatchClauseObfuscator', () => {
         });
 
         it('catch clause arguments param name and param name in body should be same', () => {
-            const firstMatchArray: RegExpMatchArray|null = obfuscatedCode.match(paramNameRegExp);
-            const secondMatchArray: RegExpMatchArray|null = obfuscatedCode.match(bodyParamNameRegExp);
+            const firstMatchArray: RegExpMatchArray | null = obfuscatedCode.match(paramNameRegExp);
+            const secondMatchArray: RegExpMatchArray | null = obfuscatedCode.match(bodyParamNameRegExp);
 
-            const firstMatch: string|undefined = firstMatchArray ? firstMatchArray[1] : undefined;
-            const secondMatch: string|undefined = secondMatchArray ? secondMatchArray[1] : undefined;
+            const firstMatch: string | undefined = firstMatchArray ? firstMatchArray[1] : undefined;
+            const secondMatch: string | undefined = secondMatchArray ? secondMatchArray[1] : undefined;
 
             assert.isOk(firstMatch);
             assert.isOk(secondMatch);

+ 1 - 1
test/functional-tests/node-transformers/node-obfuscators/FunctionDeclarationObfuscator.spec.ts

@@ -2,7 +2,7 @@ import { assert } from 'chai';
 
 import { IObfuscationResult } from '../../../../src/interfaces/IObfuscationResult';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../../../src/preset-options/NoCustomNodesPreset';
+import { NO_CUSTOM_NODES_PRESET } from '../../../../src/options/presets/NoCustomNodes';
 
 import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
 

+ 1 - 1
test/functional-tests/node-transformers/node-obfuscators/FunctionObfuscator.spec.ts

@@ -2,7 +2,7 @@ import { assert } from 'chai';
 
 import { IObfuscationResult } from '../../../../src/interfaces/IObfuscationResult';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../../../src/preset-options/NoCustomNodesPreset';
+import { NO_CUSTOM_NODES_PRESET } from '../../../../src/options/presets/NoCustomNodes';
 
 import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
 

+ 1 - 1
test/functional-tests/node-transformers/node-obfuscators/LabeledStatementObfuscator.spec.ts

@@ -2,7 +2,7 @@ import { assert } from 'chai';
 
 import { IObfuscationResult } from '../../../../src/interfaces/IObfuscationResult';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../../../src/preset-options/NoCustomNodesPreset';
+import { NO_CUSTOM_NODES_PRESET } from '../../../../src/options/presets/NoCustomNodes';
 
 import { readFileAsString } from '../../../helpers/readFileAsString';
 

+ 1 - 1
test/functional-tests/node-transformers/node-obfuscators/LiteralObfuscator.spec.ts

@@ -2,7 +2,7 @@ import { assert } from 'chai';
 
 import { IObfuscationResult } from '../../../../src/interfaces/IObfuscationResult';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../../../src/preset-options/NoCustomNodesPreset';
+import { NO_CUSTOM_NODES_PRESET } from '../../../../src/options/presets/NoCustomNodes';
 
 import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
 

Некоторые файлы не были показаны из-за большого количества измененных файлов