소스 검색

Merge remote-tracking branch 'origin/dev' into dev

Conflicts:
	package.json
sanex3339 8 년 전
부모
커밋
21f6253cad
100개의 변경된 파일1801개의 추가작업 그리고 1023개의 파일을 삭제
  1. 5 0
      CHANGELOG.md
  2. 9 1
      README.md
  3. 420 290
      dist/index.js
  4. 11 10
      package.json
  5. 4 6
      src/JavaScriptObfuscator.ts
  6. 33 26
      src/JavaScriptObfuscatorInternal.ts
  7. 58 77
      src/Obfuscator.ts
  8. 4 4
      src/SourceMapCorrector.ts
  9. 0 53
      src/StringArray.ts
  10. 73 13
      src/Utils.ts
  11. 2 2
      src/cli/CLIUtils.ts
  12. 10 8
      src/cli/JavaScriptObfuscatorCLI.ts
  13. 10 2
      src/custom-nodes/AbstractCustomNode.ts
  14. 7 24
      src/custom-nodes/console-output-nodes/ConsoleOutputDisableExpressionNode.ts
  15. 49 0
      src/custom-nodes/control-flow-replacers-nodes/binary-expression-control-flow-replacer-nodes/BinaryExpressionFunctionNode.ts
  16. 80 0
      src/custom-nodes/control-flow-replacers-nodes/binary-expression-control-flow-replacer-nodes/ControlFlowStorageCallNode.ts
  17. 64 0
      src/custom-nodes/control-flow-storage-nodes/ControlFlowStorageNode.ts
  18. 6 10
      src/custom-nodes/debug-protection-nodes/DebugProtectionFunctionCallNode.ts
  19. 6 10
      src/custom-nodes/debug-protection-nodes/DebugProtectionFunctionIntervalNode.ts
  20. 6 22
      src/custom-nodes/debug-protection-nodes/DebugProtectionFunctionNode.ts
  21. 9 13
      src/custom-nodes/domain-lock-nodes/DomainLockNode.ts
  22. 13 14
      src/custom-nodes/node-calls-controller-nodes/NodeCallsControllerFunctionNode.ts
  23. 13 13
      src/custom-nodes/self-defending-nodes/SelfDefendingUnicodeNode.ts
  24. 28 28
      src/custom-nodes/string-array-nodes/StringArrayCallsWrapper.ts
  25. 16 32
      src/custom-nodes/string-array-nodes/StringArrayNode.ts
  26. 20 29
      src/custom-nodes/string-array-nodes/StringArrayRotateFunctionNode.ts
  27. 0 2
      src/enums/JSFuck.ts
  28. 6 0
      src/enums/VisitorDirection.ts
  29. 13 0
      src/interfaces/IControlFlowReplacer.d.ts
  30. 0 9
      src/interfaces/INodeObfuscator.d.ts
  31. 5 0
      src/interfaces/INodeTransformer.d.ts
  32. 5 0
      src/interfaces/INodeTransformersFactory.d.ts
  33. 1 1
      src/interfaces/IObfuscator.d.ts
  34. 2 0
      src/interfaces/IObfuscatorOptions.d.ts
  35. 2 0
      src/interfaces/IOptions.d.ts
  36. 8 0
      src/interfaces/IStorage.d.ts
  37. 5 0
      src/interfaces/custom-nodes/ICustomNode.d.ts
  38. 0 5
      src/interfaces/custom-nodes/ICustomNodeWithData.d.ts
  39. 3 1
      src/interfaces/stack-trace-analyzer/IStackTraceAnalyzer.d.ts
  40. 3 3
      src/node-groups/AbstractNodesGroup.ts
  41. 4 6
      src/node-groups/StringArrayNodesGroup.ts
  42. 6 6
      src/node-transformers/AbstractNodeTransformer.ts
  43. 49 0
      src/node-transformers/AbstractNodeTransformersFactory.ts
  44. 98 0
      src/node-transformers/node-control-flow-transformers/FunctionControlFlowTransformer.ts
  45. 53 0
      src/node-transformers/node-control-flow-transformers/control-flow-replacers/AbstractControlFlowReplacer.ts
  46. 47 0
      src/node-transformers/node-control-flow-transformers/control-flow-replacers/BinaryExpressionControlFlowReplacer.ts
  47. 16 0
      src/node-transformers/node-control-flow-transformers/factory/NodeControlFlowTransformersFactory.ts
  48. 10 10
      src/node-transformers/node-obfuscators/CatchClauseObfuscator.ts
  49. 11 11
      src/node-transformers/node-obfuscators/FunctionDeclarationObfuscator.ts
  50. 13 16
      src/node-transformers/node-obfuscators/FunctionObfuscator.ts
  51. 10 10
      src/node-transformers/node-obfuscators/LabeledStatementObfuscator.ts
  52. 5 5
      src/node-transformers/node-obfuscators/LiteralObfuscator.ts
  53. 16 16
      src/node-transformers/node-obfuscators/MemberExpressionObfuscator.ts
  54. 6 6
      src/node-transformers/node-obfuscators/MethodDefinitionObfuscator.ts
  55. 17 17
      src/node-transformers/node-obfuscators/ObjectExpressionObfuscator.ts
  56. 11 11
      src/node-transformers/node-obfuscators/VariableDeclarationObfuscator.ts
  57. 36 0
      src/node-transformers/node-obfuscators/factory/NodeObfuscatorsFactory.ts
  58. 5 5
      src/node-transformers/node-obfuscators/replacers/AbstractReplacer.ts
  59. 1 1
      src/node-transformers/node-obfuscators/replacers/BooleanLiteralReplacer.ts
  60. 2 2
      src/node-transformers/node-obfuscators/replacers/IdentifierReplacer.ts
  61. 1 1
      src/node-transformers/node-obfuscators/replacers/NumberLiteralReplacer.ts
  62. 17 19
      src/node-transformers/node-obfuscators/replacers/StringLiteralReplacer.ts
  63. 1 4
      src/node/NodeAppender.ts
  64. 26 3
      src/node/NodeUtils.ts
  65. 13 1
      src/options/Options.ts
  66. 6 6
      src/options/OptionsNormalizer.ts
  67. 4 3
      src/options/ValidationErrorsFormatter.ts
  68. 2 0
      src/preset-options/DefaultPreset.ts
  69. 2 0
      src/preset-options/NoCustomNodesPreset.ts
  70. 7 18
      src/stack-trace-analyzer/StackTraceAnalyzer.ts
  71. 52 0
      src/storages/ArrayStorage.ts
  72. 54 0
      src/storages/MapStorage.ts
  73. 19 0
      src/storages/control-flow/ControlFlowStorage.ts
  74. 20 0
      src/storages/string-array/StringArrayStorage.ts
  75. 10 0
      src/templates/custom-nodes/control-flow-replacers-nodes/binary-expression-control-flow-replacer-nodes/BinaryExpressionFunctionTemplate.ts
  76. 6 0
      src/templates/custom-nodes/control-flow-replacers-nodes/binary-expression-control-flow-replacer-nodes/ControlFlowStorageCallTemplate.ts
  77. 8 0
      src/templates/custom-nodes/control-flow-storage-nodes/ControlFlowStorageTemplate.ts
  78. 1 3
      src/templates/custom-nodes/debug-protection-nodes/debug-protection-function-node/DebugProtectionFunctionTemplate.ts
  79. 5 0
      src/types/TControlFlowReplacer.d.ts
  80. 0 5
      src/types/TNodeObfuscator.d.ts
  81. 5 0
      src/types/TNodeTransformer.d.ts
  82. 1 0
      src/types/TVisitorDirection.d.ts
  83. 0 3
      src/types/custom-nodes/TStringArrayCallsWrapper.d.ts
  84. 0 4
      src/types/custom-nodes/TStringArrayNode.d.ts
  85. 13 63
      test/dev/dev.ts
  86. 1 1
      test/fixtures/compile-performance.js
  87. 0 0
      test/fixtures/node-transformers/node-obfuscators/catch-clause-obfuscator/catch-clause-obfuscator.js
  88. 0 0
      test/fixtures/node-transformers/node-obfuscators/labeled-statement-obfuscator/labeled-statement-obfuscator.js
  89. 36 0
      test/functional-tests/JavaScriptObfuscator.spec.ts
  90. 17 17
      test/functional-tests/JavaScriptObfuscatorInternal.spec.ts
  91. 8 6
      test/functional-tests/node-transformers/node-obfuscators/CatchClauseObfuscator.spec.ts
  92. 3 3
      test/functional-tests/node-transformers/node-obfuscators/FunctionObfuscator.spec.ts
  93. 8 6
      test/functional-tests/node-transformers/node-obfuscators/LabeledStatementObfuscator.spec.ts
  94. 3 3
      test/functional-tests/node-transformers/node-obfuscators/LiteralObfuscator.spec.ts
  95. 3 3
      test/functional-tests/node-transformers/node-obfuscators/MemberExpressionObfuscator.spec.ts
  96. 3 3
      test/functional-tests/node-transformers/node-obfuscators/MethodDefinitionObfuscator.spec.ts
  97. 3 3
      test/functional-tests/node-transformers/node-obfuscators/ObjectExpressionObfuscator.spec.ts
  98. 3 3
      test/functional-tests/node-transformers/node-obfuscators/VariableDeclarationObfuscator.spec.ts
  99. 13 10
      test/functional-tests/stack-trace-analyzer/StackTraceAnalyzer.spec.ts
  100. 2 2
      test/functional-tests/templates/custom-nodes/domain-lock-nodes/DomainLockNodeTemplate.spec.ts

+ 5 - 0
CHANGELOG.md

@@ -1,5 +1,10 @@
 Change Log
 ===
+v0.8.2
+---
+* New option `seed` sets seed for random generator. This is useful for creating repeatable results.
+* IE8 runtime error fix.
+
 v0.8.1
 ---
 * `disableConsoleOutput` option now replaces `console.xxx` functions on empty function instead of infinity loop.

+ 9 - 1
README.md

@@ -123,6 +123,7 @@ Following options available for the JS Obfuscator:
     disableConsoleOutput: true,
     reservedNames: [],
     rotateStringArray: true,
+    seed: 0,
     selfDefending: true,
     sourceMap: false,
     sourceMapBaseUrl: '',
@@ -131,7 +132,7 @@ Following options available for the JS Obfuscator:
     stringArray: true,
     stringArrayEncoding: false,
     stringArrayThreshold: 0.8,
-    unicdeEscapeSequence: true
+    unicodeEscapeSequence: true
 }
 ```
 
@@ -148,6 +149,7 @@ Following options available for the JS Obfuscator:
     --disableConsoleOutput <boolean>
     --reservedNames <list> (comma separated)
     --rotateStringArray <boolean>
+    --seed <number>
     --selfDefending <boolean>
     --sourceMap <boolean>
     --sourceMapBaseUrl <string>
@@ -218,6 +220,12 @@ Shift the `stringArray` array by a fixed and random (generated at the code obfus
 
 This option is recommended if your original source code isn't small, as the helper function can attract attention.
 
+### `seed`
+Type: `number` Default: `0`
+
+This option sets seed for random generator. This is useful for creating repeatable results.
+
+If seed is `0` - random generator will work without seed.
 
 ### `selfDefending`
 Type: `boolean` Default: `true`

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 420 - 290
dist/index.js


+ 11 - 10
package.json

@@ -1,6 +1,6 @@
 {
   "name": "javascript-obfuscator",
-  "version": "0.8.1",
+  "version": "0.8.2",
   "description": "JavaScript obfuscator",
   "keywords": [
     "obfuscator",
@@ -27,27 +27,28 @@
     "escodegen": "1.8.1",
     "esprima": "3.1.1",
     "estraverse": "4.2.0",
-    "format-unicorn": "1.1.0",
+    "is-equal": "^1.5.3",
     "mkdirp": "0.5.1",
-    "source-map-support": "0.4.6"
+    "source-map-support": "0.4.6",
+    "string-template": "^1.0.0"
   },
   "devDependencies": {
     "@types/chai": "3.4.34",
     "@types/chance": "0.7.31",
-    "@types/commander": "2.3.30",
+    "@types/commander": "2.3.31",
     "@types/escodegen": "0.0.6",
     "@types/esprima": "2.1.33",
     "@types/estraverse": "0.0.6",
     "@types/estree": "0.0.34",
-    "@types/format-unicorn": "0.0.29",
     "@types/joi": "9.0.33",
     "@types/mkdirp": "0.3.29",
     "@types/mocha": "2.2.33",
-    "@types/node": "6.0.47",
-    "@types/sinon": "1.16.31",
+    "@types/node": "6.0.49",
+    "@types/sinon": "1.16.32",
+    "@types/string-template": "^1.0.2",
     "awesome-typescript-loader": "2.2.4",
     "babel-cli": "6.18.0",
-    "babel-loader": "6.2.7",
+    "babel-loader": "6.2.8",
     "babel-preset-es2015": "6.18.0",
     "chai": "3.5.0",
     "coveralls": "2.11.15",
@@ -55,9 +56,9 @@
     "mocha": "3.1.2",
     "sinon": "2.0.0-pre.3",
     "ts-node": "1.7.0",
-    "tslint": "3.15.1",
+    "tslint": "4.0.1",
     "typescript": "2.0.10",
-    "webpack": "2.1.0-beta.25",
+    "webpack": "2.1.0-beta.27",
     "webpack-node-externals": "1.5.4"
   },
   "repository": {

+ 4 - 6
src/JavaScriptObfuscator.ts

@@ -3,6 +3,7 @@ import { IObfuscatorOptions } from './interfaces/IObfuscatorOptions';
 
 import { JavaScriptObfuscatorCLI } from './cli/JavaScriptObfuscatorCLI';
 import { JavaScriptObfuscatorInternal } from './JavaScriptObfuscatorInternal';
+import { Options } from './options/Options';
 
 export class JavaScriptObfuscator {
     /**
@@ -11,14 +12,11 @@ export class JavaScriptObfuscator {
      * @returns {string}
      */
     public static obfuscate (sourceCode: string, obfuscatorOptions: IObfuscatorOptions = {}): IObfuscationResult {
-        let javaScriptObfuscator: JavaScriptObfuscatorInternal = new JavaScriptObfuscatorInternal(
-            sourceCode,
-            obfuscatorOptions
+        const javaScriptObfuscator: JavaScriptObfuscatorInternal = new JavaScriptObfuscatorInternal(
+            new Options(obfuscatorOptions)
         );
 
-        javaScriptObfuscator.obfuscate();
-
-        return javaScriptObfuscator.getObfuscationResult();
+        return javaScriptObfuscator.obfuscate(sourceCode);
     }
 
     /**

+ 33 - 26
src/JavaScriptObfuscatorInternal.ts

@@ -2,47 +2,43 @@ import * as esprima from 'esprima';
 import * as escodegen from 'escodegen';
 import * as ESTree from 'estree';
 
-import { IObfuscatorOptions } from './interfaces/IObfuscatorOptions';
+import { Chance } from 'chance';
+
 import { IGeneratorOutput } from './interfaces/IGeneratorOutput';
 import { IObfuscationResult } from './interfaces/IObfuscationResult';
 import { IOptions } from './interfaces/IOptions';
 
 import { ObfuscationResult } from './ObfuscationResult';
 import { Obfuscator } from './Obfuscator';
-import { Options } from './options/Options';
 import { SourceMapCorrector } from './SourceMapCorrector';
+import { Utils } from './Utils';
 
 export class JavaScriptObfuscatorInternal {
     /**
      * @type {GenerateOptions}
      */
-    private static escodegenParams: escodegen.GenerateOptions = {
+    private static readonly escodegenParams: escodegen.GenerateOptions = {
         verbatim: 'x-verbatim-property',
         sourceMapWithCode: true
     };
 
     /**
-     * @type {IGeneratorOutput}
+     * @type {esprima.Options}
      */
-    private generatorOutput: IGeneratorOutput;
+    private static readonly esprimaParams: esprima.Options = {
+        loc: true
+    };
 
     /**
      * @type {IOptions}
      */
-    private options: IOptions;
+    private readonly options: IOptions;
 
     /**
-     * @type {string}
-     */
-    private sourceCode: string;
-
-    /**
-     * @param sourceCode
-     * @param obfuscatorOptions
+     * @param options
      */
-    constructor (sourceCode: string, obfuscatorOptions: IObfuscatorOptions = {}) {
-        this.sourceCode = sourceCode;
-        this.options = new Options(obfuscatorOptions);
+    constructor (options: IOptions) {
+        this.options = options;
     }
 
     /**
@@ -50,7 +46,7 @@ export class JavaScriptObfuscatorInternal {
      * @param astTree
      * @param options
      */
-    private static generateCode (sourceCode: string, astTree: ESTree.Node, options: IOptions): IGeneratorOutput {
+    private static generateCode (sourceCode: string, astTree: ESTree.Program, options: IOptions): IGeneratorOutput {
         const escodegenParams: escodegen.GenerateOptions = Object.assign(
             {},
             JavaScriptObfuscatorInternal.escodegenParams
@@ -73,26 +69,37 @@ export class JavaScriptObfuscatorInternal {
     }
 
     /**
+     * @param generatorOutput
      * @returns {IObfuscationResult}
      */
-    public getObfuscationResult (): IObfuscationResult {
+    public getObfuscationResult (generatorOutput: IGeneratorOutput): IObfuscationResult {
         return new SourceMapCorrector(
             new ObfuscationResult(
-                this.generatorOutput.code,
-                this.generatorOutput.map
+                generatorOutput.code,
+                generatorOutput.map
             ),
             this.options.sourceMapBaseUrl + this.options.sourceMapFileName,
             this.options.sourceMapMode
         ).correct();
     }
 
-    public obfuscate (): void {
-        let astTree: ESTree.Node = esprima.parse(this.sourceCode, {
-            loc: true
-        });
+    /**
+     * @param sourceCode
+     * @returns {IObfuscationResult}
+     */
+    public obfuscate (sourceCode: string): IObfuscationResult {
+        if (this.options.seed !== 0) {
+            Utils.setRandomGenerator(new Chance(this.options.seed));
+        }
 
-        astTree = new Obfuscator(this.options).obfuscateNode(astTree);
+        const astTree: ESTree.Program = esprima.parse(sourceCode, JavaScriptObfuscatorInternal.esprimaParams);
+        const obfuscatedAstTree: ESTree.Program = new Obfuscator(this.options).obfuscateAstTree(astTree);
+        const generatorOutput: IGeneratorOutput = JavaScriptObfuscatorInternal.generateCode(
+            sourceCode,
+            obfuscatedAstTree,
+            this.options
+        );
 
-        this.generatorOutput = JavaScriptObfuscatorInternal.generateCode(this.sourceCode, astTree, this.options);
+        return this.getObfuscationResult(generatorOutput);
     }
 }

+ 58 - 77
src/Obfuscator.ts

@@ -2,39 +2,34 @@ import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 
 import { TNodeGroup } from './types/TNodeGroup';
-import { TNodeObfuscator } from './types/TNodeObfuscator';
+import { TVisitorDirection } from './types/TVisitorDirection';
 
 import { ICustomNode } from './interfaces/custom-nodes/ICustomNode';
 import { IObfuscator } from './interfaces/IObfuscator';
 import { IOptions } from './interfaces/IOptions';
+import { INodeTransformer } from './interfaces/INodeTransformer';
+import { INodeTransformersFactory } from './interfaces/INodeTransformersFactory';
 import { IStackTraceData } from './interfaces/stack-trace-analyzer/IStackTraceData';
 
 import { AppendState } from './enums/AppendState';
-import { NodeType } from './enums/NodeType';
+import { VisitorDirection } from './enums/VisitorDirection';
 
-import { CatchClauseObfuscator } from './node-obfuscators/CatchClauseObfuscator';
 import { ConsoleOutputNodesGroup } from './node-groups/ConsoleOutputNodesGroup';
 import { DebugProtectionNodesGroup } from './node-groups/DebugProtectionNodesGroup';
 import { DomainLockNodesGroup } from './node-groups/DomainLockNodesGroup';
-import { FunctionDeclarationObfuscator } from './node-obfuscators/FunctionDeclarationObfuscator';
-import { FunctionObfuscator } from './node-obfuscators/FunctionObfuscator';
-import { LabeledStatementObfuscator } from './node-obfuscators/LabeledStatementObfuscator';
-import { LiteralObfuscator } from './node-obfuscators/LiteralObfuscator';
-import { MemberExpressionObfuscator } from './node-obfuscators/MemberExpressionObfuscator';
-import { MethodDefinitionObfuscator } from './node-obfuscators/MethodDefinitionObfuscator';
 import { Node } from './node/Node';
+import { NodeControlFlowTransformersFactory } from './node-transformers/node-control-flow-transformers/factory/NodeControlFlowTransformersFactory';
+import { NodeObfuscatorsFactory } from './node-transformers/node-obfuscators/factory/NodeObfuscatorsFactory';
 import { NodeUtils } from './node/NodeUtils';
-import { ObjectExpressionObfuscator } from './node-obfuscators/ObjectExpressionObfuscator';
 import { SelfDefendingNodesGroup } from './node-groups/SelfDefendingNodesGroup';
 import { StackTraceAnalyzer } from './stack-trace-analyzer/StackTraceAnalyzer';
 import { StringArrayNodesGroup } from './node-groups/StringArrayNodesGroup';
-import { VariableDeclarationObfuscator } from './node-obfuscators/VariableDeclarationObfuscator';
 
 export class Obfuscator implements IObfuscator {
     /**
      * @type {TNodeGroup[]}
      */
-    private static nodeGroups: TNodeGroup[] = [
+    private static readonly nodeGroups: TNodeGroup[] = [
         DomainLockNodesGroup,
         SelfDefendingNodesGroup,
         ConsoleOutputNodesGroup,
@@ -42,35 +37,15 @@ export class Obfuscator implements IObfuscator {
         StringArrayNodesGroup
     ];
 
-    /**
-     * @type {Map<string, TNodeObfuscator[]>}
-     */
-    private static nodeObfuscators: Map <string, TNodeObfuscator[]> = new Map <string, TNodeObfuscator[]> ([
-        [NodeType.ArrowFunctionExpression, [FunctionObfuscator]],
-        [NodeType.ClassDeclaration, [FunctionDeclarationObfuscator]],
-        [NodeType.CatchClause, [CatchClauseObfuscator]],
-        [NodeType.FunctionDeclaration, [
-            FunctionDeclarationObfuscator,
-            FunctionObfuscator
-        ]],
-        [NodeType.FunctionExpression, [FunctionObfuscator]],
-        [NodeType.MemberExpression, [MemberExpressionObfuscator]],
-        [NodeType.MethodDefinition, [MethodDefinitionObfuscator]],
-        [NodeType.ObjectExpression, [ObjectExpressionObfuscator]],
-        [NodeType.VariableDeclaration, [VariableDeclarationObfuscator]],
-        [NodeType.LabeledStatement, [LabeledStatementObfuscator]],
-        [NodeType.Literal, [LiteralObfuscator]]
-    ]);
-
     /**
      * @type {Map<string, AbstractCustomNode>}
      */
-    private customNodes: Map <string, ICustomNode> = new Map <string, ICustomNode> ();
+    private customNodes: Map <string, ICustomNode>;
 
     /**
      * @type {IOptions}
      */
-    private options: IOptions;
+    private readonly options: IOptions;
 
     /**
      * @param options
@@ -80,34 +55,47 @@ export class Obfuscator implements IObfuscator {
     }
 
     /**
-     * @param node
-     * @returns {ESTree.Node}
+     * @param astTree
+     * @returns {ESTree.Program}
      */
-    public obfuscateNode (node: ESTree.Program): ESTree.Node {
-        if (Node.isProgramNode(node) && !node.body.length) {
-            return node;
+    public obfuscateAstTree (astTree: ESTree.Program): ESTree.Program {
+        if (Node.isProgramNode(astTree) && !astTree.body.length) {
+            return astTree;
         }
 
-        NodeUtils.parentize(node);
+        NodeUtils.parentize(astTree);
+        this.initializeCustomNodes(new StackTraceAnalyzer().analyze(astTree.body));
 
-        const stackTraceData: IStackTraceData[] = new StackTraceAnalyzer(node.body).analyze();
+        // tasks before nodes transformation
+        this.beforeTransform(astTree);
 
-        this.initializeCustomNodes(stackTraceData);
+        // first pass: control flow flattening
+        if (this.options.controlFlowFlattening) {
+            this.transformAstTree(astTree, VisitorDirection.leave, new NodeControlFlowTransformersFactory(
+                this.customNodes,
+                this.options
+            ));
+        }
+
+        // second pass: nodes obfuscation
+        this.transformAstTree(astTree, VisitorDirection.enter, new NodeObfuscatorsFactory(
+            this.customNodes,
+            this.options
+        ));
 
-        this.beforeObfuscation(node);
-        this.obfuscate(node);
-        this.afterObfuscation(node);
+        // tasks after nodes transformation
+        this.afterTransform(astTree);
 
-        return node;
+        return astTree;
     }
 
     /**
      * @param astTree
      */
-    private afterObfuscation (astTree: ESTree.Node): void {
-        this.customNodes.forEach((node: ICustomNode) => {
-            if (node.getAppendState() === AppendState.AfterObfuscation) {
-                node.appendNode(astTree);
+    private afterTransform (astTree: ESTree.Program): void {
+        this.customNodes.forEach((customNode: ICustomNode) => {
+            if (customNode.getAppendState() === AppendState.AfterObfuscation) {
+                customNode.appendNode(astTree);
             }
         });
     }
@@ -115,10 +103,10 @@ export class Obfuscator implements IObfuscator {
     /**
      * @param astTree
      */
-    private beforeObfuscation (astTree: ESTree.Node): void {
-        this.customNodes.forEach((node: ICustomNode) => {
-            if (node.getAppendState() === AppendState.BeforeObfuscation) {
-                node.appendNode(astTree);
+    private beforeTransform (astTree: ESTree.Program): void {
+        this.customNodes.forEach((customNode: ICustomNode) => {
+            if (customNode.getAppendState() === AppendState.BeforeObfuscation) {
+                customNode.appendNode(astTree);
             }
         });
     };
@@ -127,7 +115,7 @@ export class Obfuscator implements IObfuscator {
      * @param stackTraceData
      */
     private initializeCustomNodes (stackTraceData: IStackTraceData[]): void {
-        let customNodes: [string, ICustomNode][] = [];
+        const customNodes: [string, ICustomNode][] = [];
 
         Obfuscator.nodeGroups.forEach((nodeGroupConstructor: TNodeGroup) => {
             const nodeGroupNodes: Map <string, ICustomNode> | undefined = new nodeGroupConstructor(
@@ -144,30 +132,23 @@ export class Obfuscator implements IObfuscator {
         this.customNodes = new Map <string, ICustomNode> (customNodes);
     }
 
-
-    /**
-     * @param node
-     * @param parentNode
-     */
-    private initializeNodeObfuscators (node: ESTree.Node, parentNode: ESTree.Node): void {
-        let nodeObfuscators: TNodeObfuscator[] | undefined = Obfuscator.nodeObfuscators.get(node.type);
-
-        if (!nodeObfuscators) {
-            return;
-        }
-
-        nodeObfuscators.forEach((obfuscator: TNodeObfuscator) => {
-            new obfuscator(this.customNodes, this.options).obfuscateNode(node, parentNode);
-        });
-    }
-
     /**
-     * @param node
+     * @param astTree
+     * @param direction
+     * @param nodeTransformersFactory
      */
-    private obfuscate (node: ESTree.Node): void {
-        estraverse.traverse(node, {
-            enter: (node: ESTree.Node, parentNode: ESTree.Node): void => {
-                this.initializeNodeObfuscators(node, parentNode);
+    private transformAstTree (
+        astTree: ESTree.Program,
+        direction: TVisitorDirection,
+        nodeTransformersFactory: INodeTransformersFactory
+    ): void {
+        estraverse.traverse(astTree, {
+            [direction]: (node: ESTree.Node, parentNode: ESTree.Node): void => {
+                const nodeTransformers: INodeTransformer[] = nodeTransformersFactory.initializeNodeTransformers(node.type);
+
+                nodeTransformers.forEach((nodeTransformer: INodeTransformer) => {
+                    nodeTransformer.transformNode(node, parentNode);
+                });
             }
         });
     }

+ 4 - 4
src/SourceMapCorrector.ts

@@ -12,22 +12,22 @@ export class SourceMapCorrector implements ISourceMapCorrector {
     /**
      * @type {string}
      */
-    private obfuscatedCode: string;
+    private readonly obfuscatedCode: string;
 
     /**
      * @type {string}
      */
-    private sourceMap: string;
+    private readonly sourceMap: string;
 
     /**
      * @type {TSourceMapMode}
      */
-    private sourceMapMode: TSourceMapMode;
+    private readonly sourceMapMode: TSourceMapMode;
 
     /**
      * @type {string}
      */
-    private sourceMapUrl: string;
+    private readonly sourceMapUrl: string;
 
     /**
      * @param obfuscationResult

+ 0 - 53
src/StringArray.ts

@@ -1,53 +0,0 @@
-import { Utils } from './Utils';
-
-export class StringArray {
-    /**
-     * @type {string[]}
-     */
-    private array: string[] = [];
-
-    /**
-     * @param value
-     */
-    public addToArray (value: string): void {
-        this.array.push(value);
-    }
-
-    /**
-     * @returns {string[]}
-     */
-    public getArray (): string[] {
-        return this.array;
-    }
-
-    /**
-     * @param value
-     * @returns {number}
-     */
-    public getIndexOf(value: string): number {
-        return this.array.indexOf(value);
-    }
-
-    /**
-     * @returns {number}
-     */
-    public getLength (): number {
-        return this.array.length;
-    }
-
-    /**
-     * @param rotationValue
-     */
-    public rotateArray (rotationValue: number): void {
-        this.array = Utils.arrayRotate(this.array, rotationValue);
-    }
-
-    /**
-     * @returns {string}
-     */
-    public toString (): string {
-        return this.array.map((value: string) => {
-            return `'${value}'`;
-        }).toString()
-    }
-}

+ 73 - 13
src/Utils.ts

@@ -2,11 +2,18 @@ import { Chance } from 'chance';
 
 import { JSFuck } from './enums/JSFuck';
 
+const isEqual = require('is-equal');
+
 export class Utils {
     /**
-     * @type {Chance.Chance}
+     * @type {string}
+     */
+    public static readonly randomGeneratorPool: string = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+
+    /**
+     * @type {Chance.Chance | Chance.SeededChance}
      */
-    private static randomGenerator: Chance.Chance = new Chance();
+    private static randomGenerator: Chance.Chance | Chance.SeededChance = new Chance();
 
     /**
      * @param array
@@ -99,13 +106,44 @@ export class Utils {
         return domain;
     }
 
+    /**
+     * @param min
+     * @param max
+     * @returns {number}
+     */
+    public static getRandomFloat (min: number, max: number): number {
+        return Utils.getRandomGenerator().floating({
+            min: min,
+            max: max,
+            fixed: 7
+        });
+    }
+
     /**
      * @returns {Chance.Chance}
      */
     public static getRandomGenerator (): Chance.Chance {
+        const randomGenerator: Chance.Chance = Utils.randomGenerator;
+
+        if (!randomGenerator) {
+            throw new Error(`\`randomGenerator\` static property is undefined`);
+        }
+
         return Utils.randomGenerator;
     }
 
+    /**
+     * @param min
+     * @param max
+     * @returns {number}
+     */
+    public static getRandomInteger (min: number, max: number): number {
+        return Utils.getRandomGenerator().integer({
+            min: min,
+            max: max
+        });
+    }
+
     /**
      * @param length
      * @returns {string}
@@ -117,10 +155,7 @@ export class Utils {
 
         return `${prefix}${(
             Utils.decToHex(
-                Utils.getRandomGenerator().integer({
-                    min: rangeMinInteger,
-                    max: rangeMaxInteger
-                })
+                Utils.getRandomInteger(rangeMinInteger, rangeMaxInteger)
             )
         ).substr(0, length)}`;
     }
@@ -140,7 +175,7 @@ export class Utils {
                 result: string = '';
 
             while (i1 < s1.length || i2 < s2.length) {
-                if (Math.random() < 0.5 && i2 < s2.length) {
+                if (Utils.getRandomFloat(0, 1) < 0.5 && i2 < s2.length) {
                     result += s2.charAt(++i2);
                 } else {
                     result += s1.charAt(++i1);
@@ -150,13 +185,16 @@ export class Utils {
             return result;
         };
 
-        const customPool: string = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
+        const randomString: string = Utils.randomGenerator.string({
+            length: length,
+            pool: Utils.randomGeneratorPool
+        });
+
+        let randomStringDiff: string = randomString.replace(
+            new RegExp('[' + escapeRegExp(str) + ']', 'g'),
+        '');
 
-        let randomString: string = Utils.randomGenerator.string({length: length, pool: customPool}),
-            randomStringDiff: string = randomString.replace(
-                new RegExp('[' + escapeRegExp(str) + ']', 'g'),
-            ''),
-            randomStringDiffArray: string[] = randomStringDiff.split('');
+        const randomStringDiffArray: string[] = randomStringDiff.split('');
 
         Utils.randomGenerator.shuffle(randomStringDiffArray);
         randomStringDiff = randomStringDiffArray.join('');
@@ -173,6 +211,21 @@ export class Utils {
         return number % 1 === 0;
     }
 
+    /**
+     * @param map
+     * @param value
+     * @returns {any}
+     */
+    public static mapGetFirstKeyOf(map: Map <any, any>, value: any): any {
+        for (var [key, storageValue] of map) {
+            if (isEqual(value, storageValue)) {
+                return key;
+            }
+        }
+
+        return null;
+    }
+
     /**
      * RC4 symmetric cipher encryption/decryption
      * https://gist.github.com/farhadi/2185197
@@ -213,6 +266,13 @@ export class Utils {
         return result;
     }
 
+    /**
+     * @param randomGenerator
+     */
+    public static setRandomGenerator (randomGenerator: Chance.Chance | Chance.SeededChance): void {
+        Utils.randomGenerator = randomGenerator;
+    }
+
     /**
      * @param obj
      * @returns {T}

+ 2 - 2
src/cli/CLIUtils.ts

@@ -10,14 +10,14 @@ export class CLIUtils {
     /**
      * @type {string[]}
      */
-    private static availableInputExtensions: string[] = [
+    private static readonly availableInputExtensions: string[] = [
         '.js'
     ];
 
     /**
      * @type {BufferEncoding}
      */
-    private static encoding: BufferEncoding = 'utf8';
+    private static readonly encoding: BufferEncoding = 'utf8';
 
     /**
      * @param outputPath

+ 10 - 8
src/cli/JavaScriptObfuscatorCLI.ts

@@ -19,7 +19,7 @@ export class JavaScriptObfuscatorCLI {
     /**
      * @type {string[]}
      */
-    private arguments: string[];
+    private readonly arguments: string[];
 
     /**
      * @type {commander.ICommand}
@@ -69,7 +69,7 @@ export class JavaScriptObfuscatorCLI {
      * @returns {string}
      */
     private static parseSourceMapMode (value: string): string {
-        let availableMode: boolean = Object
+        const availableMode: boolean = Object
             .keys(SourceMapMode)
             .some((key: string): boolean => {
                 return SourceMapMode[key] === value;
@@ -121,8 +121,8 @@ export class JavaScriptObfuscatorCLI {
      * @returns {IObfuscatorOptions}
      */
     private buildOptions (): IObfuscatorOptions {
-        let obfuscatorOptions: IObfuscatorOptions = {},
-            availableOptions: string[] = Object.keys(DEFAULT_PRESET);
+        const obfuscatorOptions: IObfuscatorOptions = {};
+        const availableOptions: string[] = Object.keys(DEFAULT_PRESET);
 
         for (const option in this.commands) {
             if (!this.commands.hasOwnProperty(option)) {
@@ -145,12 +145,14 @@ export class JavaScriptObfuscatorCLI {
             .usage('<inputPath> [options]')
             .option('-o, --output <path>', 'Output path for obfuscated code')
             .option('--compact <boolean>', 'Disable one line output code compacting', JavaScriptObfuscatorCLI.parseBoolean)
+            .option('--controlFlowFlattening <boolean>', 'Enables control flow flattening', JavaScriptObfuscatorCLI.parseBoolean)
             .option('--debugProtection <boolean>', 'Disable browser Debug panel (can cause DevTools enabled browser freeze)', JavaScriptObfuscatorCLI.parseBoolean)
             .option('--debugProtectionInterval <boolean>', 'Disable browser Debug panel even after page was loaded (can cause DevTools enabled browser freeze)', JavaScriptObfuscatorCLI.parseBoolean)
             .option('--disableConsoleOutput <boolean>', 'Allow console.log, console.info, console.error and console.warn messages output into browser console', JavaScriptObfuscatorCLI.parseBoolean)
             .option('--domainLock <list>', 'Blocks the execution of the code in domains that do not match the passed RegExp patterns (comma separated)', (val: string) => val.split(','))
             .option('--reservedNames <list>', 'Disable obfuscation of variable names, function names and names of function parameters that match the passed RegExp patterns (comma separated)', (val: string) => val.split(','))
             .option('--rotateStringArray <boolean>', 'Disable rotation of unicode array values during obfuscation', JavaScriptObfuscatorCLI.parseBoolean)
+            .option('--seed <number>', 'Sets seed for random generator. This is useful for creating repeatable results.', parseFloat)
             .option('--selfDefending <boolean>', 'Disables self-defending for obfuscated code', JavaScriptObfuscatorCLI.parseBoolean)
             .option('--sourceMap <boolean>', 'Enables source map generation', JavaScriptObfuscatorCLI.parseBoolean)
             .option('--sourceMapBaseUrl <string>', 'Sets base url to the source map import url when `--sourceMapMode=separate`')
@@ -179,8 +181,8 @@ export class JavaScriptObfuscatorCLI {
     }
 
     private processData (): void {
-        let options: IObfuscatorOptions = this.buildOptions(),
-            outputCodePath: string = CLIUtils.getOutputCodePath((<any>this.commands).output, this.inputPath);
+        const options: IObfuscatorOptions = this.buildOptions();
+        const outputCodePath: string = CLIUtils.getOutputCodePath((<any>this.commands).output, this.inputPath);
 
         if (options.sourceMap) {
             this.processDataWithSourceMap(outputCodePath, options);
@@ -194,7 +196,7 @@ export class JavaScriptObfuscatorCLI {
      * @param options
      */
     private processDataWithoutSourceMap (outputCodePath: string, options: IObfuscatorOptions): void {
-        let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(this.data, options).getObfuscatedCode();
+        const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(this.data, options).getObfuscatedCode();
 
         CLIUtils.writeFile(outputCodePath, obfuscatedCode);
     }
@@ -204,7 +206,7 @@ export class JavaScriptObfuscatorCLI {
      * @param options
      */
     private processDataWithSourceMap (outputCodePath: string, options: IObfuscatorOptions): void {
-        let outputSourceMapPath: string = CLIUtils.getOutputSourceMapPath(
+        const outputSourceMapPath: string = CLIUtils.getOutputSourceMapPath(
             outputCodePath,
             options.sourceMapFileName || ''
         );

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

@@ -5,6 +5,7 @@ import { IOptions } from '../interfaces/IOptions';
 import { TStatement } from '../types/TStatement';
 
 import { AppendState } from '../enums/AppendState';
+import { NodeUtils } from '../node/NodeUtils';
 
 export abstract class AbstractCustomNode implements ICustomNode {
     /**
@@ -15,7 +16,7 @@ export abstract class AbstractCustomNode implements ICustomNode {
     /**
      * @type {IOptions}
      */
-    protected options: IOptions;
+    protected readonly options: IOptions;
 
     /**
      * @param options
@@ -36,6 +37,11 @@ export abstract class AbstractCustomNode implements ICustomNode {
         return this.appendState;
     }
 
+    /**
+     * @returns {string}
+     */
+    public abstract getCode (): string;
+
     /**
      * @returns {TStatement[]}
      */
@@ -53,5 +59,7 @@ export abstract class AbstractCustomNode implements ICustomNode {
     /**
      * @returns {TStatement[]}
      */
-    protected abstract getNodeStructure (): TStatement[];
+    protected getNodeStructure (): TStatement[] {
+        return NodeUtils.convertCodeToStructure(this.getCode());
+    }
 }

+ 7 - 24
src/custom-nodes/console-output-nodes/ConsoleOutputDisableExpressionNode.ts

@@ -1,7 +1,6 @@
-import 'format-unicorn';
+import * as format from 'string-template';
 
 import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
-import { TStatement } from '../../types/TStatement';
 
 import { IOptions } from '../../interfaces/IOptions';
 import { IStackTraceData } from '../../interfaces/stack-trace-analyzer/IStackTraceData';
@@ -12,7 +11,6 @@ import { ConsoleOutputDisableExpressionTemplate } from '../../templates/custom-n
 
 import { AbstractCustomNode } from '../AbstractCustomNode';
 import { NodeAppender } from '../../node/NodeAppender';
-import { NodeUtils } from '../../node/NodeUtils';
 import { Utils } from '../../Utils';
 
 export class ConsoleOutputDisableExpressionNode extends AbstractCustomNode {
@@ -68,27 +66,12 @@ export class ConsoleOutputDisableExpressionNode extends AbstractCustomNode {
     }
 
     /**
-     *  JSCrush version of following code
-     *
-     *  (function () {
-     *      var _console = []["filter"]["constructor"]("return this")().console;
-     *      var _function = function () {};
-     *
-     *      _console.log = _function;
-     *      _console.info = _function;
-     *      _console.warn = _function;
-     *      _console.error = _function;
-     *  _console
-     *  })();
-     *
-     * @returns {TStatement[]}
+     * @returns {string}
      */
-    protected getNodeStructure (): TStatement[] {
-        return NodeUtils.convertCodeToStructure(
-            ConsoleOutputDisableExpressionTemplate().formatUnicorn({
-                consoleLogDisableFunctionName: Utils.getRandomVariableName(),
-                singleNodeCallControllerFunctionName: this.callsControllerFunctionName
-            })
-        );
+    public getCode (): string {
+        return format(ConsoleOutputDisableExpressionTemplate(), {
+            consoleLogDisableFunctionName: Utils.getRandomVariableName(),
+            singleNodeCallControllerFunctionName: this.callsControllerFunctionName
+        });
     }
 }

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

@@ -0,0 +1,49 @@
+import * as format from 'string-template';
+
+import { TNodeWithBlockStatement } from '../../../types/TNodeWithBlockStatement';
+
+import { IOptions } from '../../../interfaces/IOptions';
+
+import { AppendState } from '../../../enums/AppendState';
+
+import { BinaryExpressionFunctionTemplate } from '../../../templates/custom-nodes/control-flow-replacers-nodes/binary-expression-control-flow-replacer-nodes/BinaryExpressionFunctionTemplate';
+
+import { AbstractCustomNode } from '../../AbstractCustomNode';
+import { Utils } from '../../../Utils';
+
+export class BinaryExpressionFunctionNode extends AbstractCustomNode {
+    /**
+     * @type {AppendState}
+     */
+    protected appendState: AppendState = AppendState.BeforeObfuscation;
+
+    /**
+     * @type {string}
+     */
+    private operator: string;
+
+    /**
+     * @param operator
+     * @param options
+     */
+    constructor (operator: string, options: IOptions) {
+        super(options);
+
+        this.operator = operator;
+    }
+
+    /**
+     * @param blockScopeNode
+     */
+    public appendNode (blockScopeNode: TNodeWithBlockStatement): void {}
+
+    /**
+     * @returns {string}
+     */
+    public getCode (): string {
+        return format(BinaryExpressionFunctionTemplate(), {
+            functionName: Utils.getRandomVariableName(1),
+            operator: this.operator
+        });
+    }
+}

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

@@ -0,0 +1,80 @@
+import * as format from 'string-template';
+
+import { TNodeWithBlockStatement } from '../../../types/TNodeWithBlockStatement';
+
+import { IOptions } from '../../../interfaces/IOptions';
+
+import { AppendState } from '../../../enums/AppendState';
+
+import { ControlFlowStorageCallTemplate } from '../../../templates/custom-nodes/control-flow-replacers-nodes/binary-expression-control-flow-replacer-nodes/ControlFlowStorageCallTemplate';
+
+import { AbstractCustomNode } from '../../AbstractCustomNode';
+import { NodeAppender } from '../../../node/NodeAppender';
+
+export class ControlFlowStorageCallNode extends AbstractCustomNode {
+    /**
+     * @type {AppendState}
+     */
+    protected appendState: AppendState = AppendState.AfterObfuscation;
+
+    /**
+     * @type {string}
+     */
+    private controlFlowStorageKey: string;
+
+    /**
+     * @type {string}
+     */
+    private controlFlowStorageName: string;
+
+    /**
+     * @type {string}
+     */
+    private leftValue: string;
+
+    /**
+     * @type {string}
+     */
+    private rightValue: string;
+
+    /**
+     * @param controlFlowStorageName
+     * @param controlFlowStorageKey
+     * @param leftValue
+     * @param rightValue
+     * @param options
+     */
+    constructor (
+        controlFlowStorageName: string,
+        controlFlowStorageKey: string,
+        leftValue: string,
+        rightValue: string,
+        options: IOptions
+    ) {
+        super(options);
+
+        this.controlFlowStorageName = controlFlowStorageName;
+        this.controlFlowStorageKey = controlFlowStorageKey;
+        this.leftValue = leftValue;
+        this.rightValue = rightValue;
+    }
+
+    /**
+     * @param blockScopeNode
+     */
+    public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
+        NodeAppender.prependNode(blockScopeNode, this.getNode());
+    }
+
+    /**
+     * @returns {string}
+     */
+    public getCode (): string {
+        return format(ControlFlowStorageCallTemplate(), {
+            controlFlowStorageKey: this.controlFlowStorageKey,
+            controlFlowStorageName: this.controlFlowStorageName,
+            leftValue: this.leftValue,
+            rightValue: this.rightValue
+        });
+    }
+}

+ 64 - 0
src/custom-nodes/control-flow-storage-nodes/ControlFlowStorageNode.ts

@@ -0,0 +1,64 @@
+import * as format from 'string-template';
+
+import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
+
+import { ICustomNode } from '../../interfaces/custom-nodes/ICustomNode';
+import { IOptions } from '../../interfaces/IOptions';
+import { IStorage } from '../../interfaces/IStorage';
+
+import { AppendState } from '../../enums/AppendState';
+
+import { ControlFlowStorageTemplate } from '../../templates/custom-nodes/control-flow-storage-nodes/ControlFlowStorageTemplate';
+
+import { AbstractCustomNode } from '../AbstractCustomNode';
+import { NodeAppender } from '../../node/NodeAppender';
+
+export class ControlFlowStorageNode extends AbstractCustomNode {
+    /**
+     * @type {AppendState}
+     */
+    protected appendState: AppendState = AppendState.AfterObfuscation;
+
+    /**
+     * @type {IStorage <ICustomNode>}
+     */
+    private controlFlowStorage: IStorage <ICustomNode>;
+
+    /**
+     * @type {string}
+     */
+    private controlFlowStorageName: string;
+
+    /**
+     * @param controlFlowStorage
+     * @param controlFlowStorageName
+     * @param options
+     */
+    constructor (
+        controlFlowStorage: IStorage <ICustomNode>,
+        controlFlowStorageName: string,
+        options: IOptions
+    ) {
+        super(options);
+
+        this.controlFlowStorage = controlFlowStorage;
+        this.controlFlowStorageName = controlFlowStorageName;
+    }
+
+    /**
+     * @param blockScopeNode
+     */
+    public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
+        NodeAppender.prependNode(blockScopeNode, this.getNode());
+    }
+
+    /**
+     * @returns {string}
+     */
+    public getCode (): string {
+        return format(ControlFlowStorageTemplate(), {
+            controlFlowStorage: this.controlFlowStorage.toString(),
+            controlFlowStorageName: this.controlFlowStorageName
+        });
+    }
+}

+ 6 - 10
src/custom-nodes/debug-protection-nodes/DebugProtectionFunctionCallNode.ts

@@ -1,7 +1,6 @@
-import 'format-unicorn';
+import * as format from 'string-template';
 
 import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
-import { TStatement } from '../../types/TStatement';
 
 import { IOptions } from '../../interfaces/IOptions';
 
@@ -11,7 +10,6 @@ import { DebugProtectionFunctionCallTemplate } from '../../templates/custom-node
 
 import { AbstractCustomNode } from '../AbstractCustomNode';
 import { NodeAppender } from '../../node/NodeAppender';
-import { NodeUtils } from '../../node/NodeUtils';
 
 export class DebugProtectionFunctionCallNode extends AbstractCustomNode {
     /**
@@ -42,13 +40,11 @@ export class DebugProtectionFunctionCallNode extends AbstractCustomNode {
     }
 
     /**
-     * @returns {TStatement[]}
+     * @returns {string}
      */
-    protected getNodeStructure (): TStatement[] {
-        return NodeUtils.convertCodeToStructure(
-            DebugProtectionFunctionCallTemplate().formatUnicorn({
-                debugProtectionFunctionName: this.debugProtectionFunctionName
-            })
-        );
+    public getCode (): string {
+        return format(DebugProtectionFunctionCallTemplate(), {
+            debugProtectionFunctionName: this.debugProtectionFunctionName
+        });
     }
 }

+ 6 - 10
src/custom-nodes/debug-protection-nodes/DebugProtectionFunctionIntervalNode.ts

@@ -1,7 +1,6 @@
-import 'format-unicorn';
+import * as format from 'string-template';
 
 import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
-import { TStatement } from '../../types/TStatement';
 
 import { IOptions } from '../../interfaces/IOptions';
 
@@ -11,7 +10,6 @@ import { DebugProtectionFunctionIntervalTemplate } from '../../templates/custom-
 
 import { AbstractCustomNode } from '../AbstractCustomNode';
 import { NodeAppender } from '../../node/NodeAppender';
-import { NodeUtils } from '../../node/NodeUtils';
 
 export class DebugProtectionFunctionIntervalNode extends AbstractCustomNode {
     /**
@@ -42,13 +40,11 @@ export class DebugProtectionFunctionIntervalNode extends AbstractCustomNode {
     }
 
     /**
-     * @returns {TStatement[]}
+     * @returns {string}
      */
-    protected getNodeStructure (): TStatement[] {
-        return NodeUtils.convertCodeToStructure(
-            DebugProtectionFunctionIntervalTemplate().formatUnicorn({
-                debugProtectionFunctionName: this.debugProtectionFunctionName
-            })
-        );
+    public getCode (): string {
+        return format(DebugProtectionFunctionIntervalTemplate(), {
+            debugProtectionFunctionName: this.debugProtectionFunctionName
+        });
     }
 }

+ 6 - 22
src/custom-nodes/debug-protection-nodes/DebugProtectionFunctionNode.ts

@@ -1,7 +1,6 @@
-import 'format-unicorn';
+import * as format from 'string-template';
 
 import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
-import { TStatement } from '../../types/TStatement';
 
 import { IOptions } from '../../interfaces/IOptions';
 
@@ -11,7 +10,6 @@ import { DebugProtectionFunctionTemplate } from '../../templates/custom-nodes/de
 
 import { AbstractCustomNode } from '../AbstractCustomNode';
 import { NodeAppender } from '../../node/NodeAppender';
-import { NodeUtils } from '../../node/NodeUtils';
 import { Utils } from '../../Utils';
 
 export class DebugProtectionFunctionNode extends AbstractCustomNode {
@@ -40,10 +38,7 @@ export class DebugProtectionFunctionNode extends AbstractCustomNode {
      */
     public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
         let programBodyLength: number = blockScopeNode.body.length,
-            randomIndex: number = Utils.getRandomGenerator().integer({
-                min: 0,
-                max: programBodyLength
-            });
+            randomIndex: number = Utils.getRandomInteger(0, programBodyLength);
 
         NodeAppender.insertNodeAtIndex(blockScopeNode, this.getNode(), randomIndex);
     }
@@ -51,20 +46,9 @@ export class DebugProtectionFunctionNode extends AbstractCustomNode {
     /**
      * @returns {string}
      */
-    public getNodeIdentifier (): string {
-        return this.debugProtectionFunctionName;
-    }
-
-    /**
-     * Found this trick in JScrambler
-     *
-     * @returns {TStatement[]}
-     */
-    protected getNodeStructure (): TStatement[] {
-        return NodeUtils.convertCodeToStructure(
-            DebugProtectionFunctionTemplate().formatUnicorn({
-                debugProtectionFunctionName: this.debugProtectionFunctionName
-            })
-        );
+    public getCode (): string {
+        return format(DebugProtectionFunctionTemplate(), {
+            debugProtectionFunctionName: this.debugProtectionFunctionName
+        });
     }
 }

+ 9 - 13
src/custom-nodes/domain-lock-nodes/DomainLockNode.ts

@@ -1,7 +1,6 @@
-import 'format-unicorn';
+import * as format from 'string-template';
 
 import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
-import { TStatement } from '../../types/TStatement';
 
 import { IOptions } from '../../interfaces/IOptions';
 import { IStackTraceData } from '../../interfaces/stack-trace-analyzer/IStackTraceData';
@@ -12,7 +11,6 @@ import { DomainLockNodeTemplate } from '../../templates/custom-nodes/domain-lock
 
 import { AbstractCustomNode } from '../AbstractCustomNode';
 import { NodeAppender } from '../../node/NodeAppender';
-import { NodeUtils } from '../../node/NodeUtils';
 import { Utils } from '../../Utils';
 
 export class DomainLockNode extends AbstractCustomNode {
@@ -68,19 +66,17 @@ export class DomainLockNode extends AbstractCustomNode {
     }
 
     /**
-     * @returns {TStatement[]}
+     * @returns {string}
      */
-    protected getNodeStructure (): TStatement[] {
+    public getCode (): string {
         let domainsString: string = this.options.domainLock.join(';'),
             [hiddenDomainsString, diff]: string[] = Utils.hideString(domainsString, domainsString.length * 3);
 
-        return NodeUtils.convertCodeToStructure(
-            DomainLockNodeTemplate().formatUnicorn({
-                domainLockFunctionName: Utils.getRandomVariableName(),
-                diff: diff,
-                domains: hiddenDomainsString,
-                singleNodeCallControllerFunctionName: this.callsControllerFunctionName
-            })
-        );
+        return format(DomainLockNodeTemplate(), {
+            domainLockFunctionName: Utils.getRandomVariableName(),
+            diff: diff,
+            domains: hiddenDomainsString,
+            singleNodeCallControllerFunctionName: this.callsControllerFunctionName
+        });
     }
 }

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

@@ -1,7 +1,6 @@
-import 'format-unicorn';
+import * as format from 'string-template';
 
 import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
-import { TStatement } from '../../types/TStatement';
 
 import { IOptions } from '../../interfaces/IOptions';
 import { IStackTraceData } from '../../interfaces/stack-trace-analyzer/IStackTraceData';
@@ -15,7 +14,6 @@ import { NO_CUSTOM_NODES_PRESET } from '../../preset-options/NoCustomNodesPreset
 import { AbstractCustomNode } from '../AbstractCustomNode';
 import { JavaScriptObfuscator } from '../../JavaScriptObfuscator';
 import { NodeAppender } from '../../node/NodeAppender';
-import { NodeUtils } from '../../node/NodeUtils';
 
 export class NodeCallsControllerFunctionNode extends AbstractCustomNode {
     /**
@@ -74,21 +72,22 @@ export class NodeCallsControllerFunctionNode extends AbstractCustomNode {
     }
 
     /**
-     * @returns {TStatement[]}
+     * @returns {string}
      */
-    protected getNodeStructure (): TStatement[] {
+    public getCode (): string {
         if (this.appendState === AppendState.AfterObfuscation) {
-            return NodeUtils.convertCodeToStructure(
-                JavaScriptObfuscator.obfuscate(SingleNodeCallControllerTemplate().formatUnicorn({
+            return JavaScriptObfuscator.obfuscate(
+                format(SingleNodeCallControllerTemplate(), {
                     singleNodeCallControllerFunctionName: this.callsControllerFunctionName
-                }), NO_CUSTOM_NODES_PRESET).getObfuscatedCode()
-            );
+                }),
+                Object.assign({}, NO_CUSTOM_NODES_PRESET, {
+                    seed: this.options.seed
+                })
+            ).getObfuscatedCode();
         }
 
-        return NodeUtils.convertCodeToStructure(
-            SingleNodeCallControllerTemplate().formatUnicorn({
-                singleNodeCallControllerFunctionName: this.callsControllerFunctionName
-            })
-        );
+        return format(SingleNodeCallControllerTemplate(), {
+            singleNodeCallControllerFunctionName: this.callsControllerFunctionName
+        });
     }
 }

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

@@ -1,5 +1,6 @@
+import * as format from 'string-template';
+
 import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
-import { TStatement } from '../../types/TStatement';
 
 import { IOptions } from '../../interfaces/IOptions';
 import { IStackTraceData } from '../../interfaces/stack-trace-analyzer/IStackTraceData';
@@ -13,7 +14,6 @@ import { SelfDefendingTemplate } from '../../templates/custom-nodes/self-defendi
 import { AbstractCustomNode } from '../AbstractCustomNode';
 import { NodeAppender } from '../../node/NodeAppender';
 import { JavaScriptObfuscator } from '../../JavaScriptObfuscator';
-import { NodeUtils } from '../../node/NodeUtils';
 import { Utils } from '../../Utils';
 
 export class SelfDefendingUnicodeNode extends AbstractCustomNode {
@@ -69,17 +69,17 @@ export class SelfDefendingUnicodeNode extends AbstractCustomNode {
     }
 
     /**
-     * @returns {TStatement[]}
+     * @returns {string}
      */
-    protected getNodeStructure (): TStatement[] {
-        return NodeUtils.convertCodeToStructure(
-            JavaScriptObfuscator.obfuscate(
-                SelfDefendingTemplate().formatUnicorn({
-                    selfDefendingFunctionName: Utils.getRandomVariableName(),
-                    singleNodeCallControllerFunctionName: this.callsControllerFunctionName
-                }),
-                NO_CUSTOM_NODES_PRESET
-            ).getObfuscatedCode()
-        );
+    public getCode (): string {
+        return JavaScriptObfuscator.obfuscate(
+            format(SelfDefendingTemplate(), {
+                selfDefendingFunctionName: Utils.getRandomVariableName(),
+                singleNodeCallControllerFunctionName: this.callsControllerFunctionName
+            }),
+            Object.assign({},  NO_CUSTOM_NODES_PRESET, {
+                seed: this.options.seed
+            })
+        ).getObfuscatedCode();
     }
 }

+ 28 - 28
src/custom-nodes/string-array-nodes/StringArrayCallsWrapper.ts

@@ -1,9 +1,11 @@
-import 'format-unicorn';
+import * as format from 'string-template';
 
 import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
 import { TStatement } from '../../types/TStatement';
 
+import { ICustomNodeWithIdentifier } from '../../interfaces/custom-nodes/ICustomNodeWithIdentifier';
 import { IOptions } from '../../interfaces/IOptions';
+import { IStorage } from '../../interfaces/IStorage';
 
 import { AppendState } from '../../enums/AppendState';
 import { StringArrayEncoding } from '../../enums/StringArrayEncoding';
@@ -20,19 +22,17 @@ import { StringArrayRc4DecodeNodeTemplate } from '../../templates/custom-nodes/s
 import { AbstractCustomNode } from '../AbstractCustomNode';
 import { JavaScriptObfuscator } from '../../JavaScriptObfuscator';
 import { NodeAppender } from '../../node/NodeAppender';
-import { NodeUtils } from '../../node/NodeUtils';
-import { StringArray } from '../../StringArray';
 
-export class StringArrayCallsWrapper extends AbstractCustomNode {
+export class StringArrayCallsWrapper extends AbstractCustomNode implements ICustomNodeWithIdentifier {
     /**
      * @type {AppendState}
      */
     protected appendState: AppendState = AppendState.AfterObfuscation;
 
     /**
-     * @type {StringArray}
+     * @type {IStorage <string>}
      */
-    private stringArray: StringArray;
+    private stringArray: IStorage <string>;
 
     /**
      * @type {string}
@@ -53,7 +53,7 @@ export class StringArrayCallsWrapper extends AbstractCustomNode {
     constructor (
         stringArrayCallsWrapperName: string,
         stringArrayName: string,
-        stringArray: StringArray,
+        stringArray: IStorage <string>,
         options: IOptions
     ) {
         super(options);
@@ -74,6 +74,24 @@ export class StringArrayCallsWrapper extends AbstractCustomNode {
         NodeAppender.insertNodeAtIndex(blockScopeNode, this.getNode(), 1);
     }
 
+    /**
+     * @returns {string}
+     */
+    public getCode (): string {
+        const decodeNodeTemplate: string = this.getDecodeStringArrayTemplate();
+
+        return JavaScriptObfuscator.obfuscate(
+            format(StringArrayCallsWrapperTemplate(), {
+                decodeNodeTemplate,
+                stringArrayCallsWrapperName: this.stringArrayCallsWrapperName,
+                stringArrayName: this.stringArrayName
+            }),
+            Object.assign({}, NO_CUSTOM_NODES_PRESET, {
+                seed: this.options.seed
+            })
+        ).getObfuscatedCode();
+    }
+
     /**
      * @returns {string}
      */
@@ -96,7 +114,7 @@ export class StringArrayCallsWrapper extends AbstractCustomNode {
             selfDefendingCode: string = '';
 
         if (this.options.selfDefending) {
-            selfDefendingCode = SelfDefendingTemplate().formatUnicorn({
+            selfDefendingCode = format(SelfDefendingTemplate(), {
                 stringArrayCallsWrapperName: this.stringArrayCallsWrapperName,
                 stringArrayName: this.stringArrayName
             });
@@ -104,7 +122,7 @@ export class StringArrayCallsWrapper extends AbstractCustomNode {
 
         switch (this.options.stringArrayEncoding) {
             case StringArrayEncoding.base64:
-                decodeStringArrayTemplate = StringArrayBase64DecodeNodeTemplate().formatUnicorn({
+                decodeStringArrayTemplate = format(StringArrayBase64DecodeNodeTemplate(), {
                     atobPolyfill: AtobTemplate(),
                     selfDefendingCode,
                     stringArrayCallsWrapperName: this.stringArrayCallsWrapperName
@@ -113,7 +131,7 @@ export class StringArrayCallsWrapper extends AbstractCustomNode {
                 break;
 
             case StringArrayEncoding.rc4:
-                decodeStringArrayTemplate = StringArrayRc4DecodeNodeTemplate().formatUnicorn({
+                decodeStringArrayTemplate = format(StringArrayRc4DecodeNodeTemplate(), {
                     atobPolyfill: AtobTemplate(),
                     rc4Polyfill: Rc4Template(),
                     selfDefendingCode,
@@ -125,22 +143,4 @@ export class StringArrayCallsWrapper extends AbstractCustomNode {
 
         return decodeStringArrayTemplate;
     }
-
-    /**
-     * @returns {TStatement[]}
-     */
-    protected getNodeStructure (): TStatement[] {
-        const decodeNodeTemplate: string = this.getDecodeStringArrayTemplate();
-
-        return NodeUtils.convertCodeToStructure(
-            JavaScriptObfuscator.obfuscate(
-                StringArrayCallsWrapperTemplate().formatUnicorn({
-                    decodeNodeTemplate,
-                    stringArrayCallsWrapperName: this.stringArrayCallsWrapperName,
-                    stringArrayName: this.stringArrayName
-                }),
-                NO_CUSTOM_NODES_PRESET
-            ).getObfuscatedCode()
-        );
-    }
 }

+ 16 - 32
src/custom-nodes/string-array-nodes/StringArrayNode.ts

@@ -1,21 +1,21 @@
-import 'format-unicorn';
+import * as format from 'string-template';
 
 import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
 import { TStatement } from '../../types/TStatement';
 
+import { ICustomNodeWithData } from '../../interfaces/custom-nodes/ICustomNodeWithData';
 import { IOptions } from '../../interfaces/IOptions';
+import { IStorage } from '../../interfaces/IStorage';
 
 import { AppendState } from '../../enums/AppendState';
 
-import { StringArray } from '../../StringArray';
-
 import { StringArrayTemplate } from '../../templates/custom-nodes/string-array-nodes/string-array-node/StringArrayTemplate';
 
 import { AbstractCustomNode } from '../AbstractCustomNode';
 import { NodeAppender } from '../../node/NodeAppender';
-import { NodeUtils } from '../../node/NodeUtils';
+import { StringArrayStorage } from '../../storages/string-array/StringArrayStorage';
 
-export class StringArrayNode extends AbstractCustomNode {
+export class StringArrayNode extends AbstractCustomNode implements ICustomNodeWithData {
     /**
      * @type {number}
      */
@@ -27,9 +27,9 @@ export class StringArrayNode extends AbstractCustomNode {
     protected appendState: AppendState = AppendState.AfterObfuscation;
 
     /**
-     * @type {StringArray}
+     * @type {IStorage <string>}
      */
-    private stringArray: StringArray;
+    private stringArray: IStorage <string>;
 
     /**
      * @type {string}
@@ -48,7 +48,7 @@ export class StringArrayNode extends AbstractCustomNode {
      * @param options
      */
     constructor (
-        stringArray: StringArray,
+        stringArray: IStorage <string>,
         stringArrayName: string,
         stringArrayRotateValue: number = 0,
         options: IOptions
@@ -74,14 +74,17 @@ export class StringArrayNode extends AbstractCustomNode {
     /**
      * @returns {string}
      */
-    public getNodeIdentifier (): string {
-        return this.stringArrayName;
+    public getCode (): string {
+        return format(StringArrayTemplate(), {
+            stringArrayName: this.stringArrayName,
+            stringArray: this.stringArray.toString()
+        });
     }
 
     /**
-     * @returns {StringArray}
+     * @returns {IStorage <string>}
      */
-    public getNodeData (): StringArray {
+    public getNodeData (): IStorage <string> {
         return this.stringArray;
     }
 
@@ -89,27 +92,8 @@ export class StringArrayNode extends AbstractCustomNode {
      * @returns {TStatement[]}
      */
     public getNode (): TStatement[] {
-        this.stringArray.rotateArray(this.stringArrayRotateValue);
+        (<StringArrayStorage>this.stringArray).rotateArray(this.stringArrayRotateValue);
 
         return super.getNode();
     }
-
-    /**
-     * @param data
-     */
-    public updateNodeData (data: string): void {
-        this.stringArray.addToArray(data);
-    }
-
-    /**
-     * @returns {TStatement[]}
-     */
-    protected getNodeStructure (): TStatement[] {
-        return NodeUtils.convertCodeToStructure(
-            StringArrayTemplate().formatUnicorn({
-                stringArrayName: this.stringArrayName,
-                stringArray: this.stringArray.toString()
-            })
-        );
-    }
 }

+ 20 - 29
src/custom-nodes/string-array-nodes/StringArrayRotateFunctionNode.ts

@@ -1,9 +1,9 @@
-import 'format-unicorn';
+import * as format from 'string-template';
 
 import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
-import { TStatement } from '../../types/TStatement';
 
 import { IOptions } from '../../interfaces/IOptions';
+import { IStorage } from '../../interfaces/IStorage';
 
 import { AppendState } from '../../enums/AppendState';
 
@@ -15,8 +15,6 @@ import { StringArrayRotateFunctionTemplate } from '../../templates/custom-nodes/
 import { AbstractCustomNode } from '../AbstractCustomNode';
 import { JavaScriptObfuscator } from '../../JavaScriptObfuscator';
 import { NodeAppender } from '../../node/NodeAppender';
-import { NodeUtils } from '../../node/NodeUtils';
-import { StringArray } from '../../StringArray';
 import { Utils } from '../../Utils';
 
 export class StringArrayRotateFunctionNode extends AbstractCustomNode {
@@ -26,9 +24,9 @@ export class StringArrayRotateFunctionNode extends AbstractCustomNode {
     protected appendState: AppendState = AppendState.AfterObfuscation;
 
     /**
-     * @type {StringArray}
+     * @type {IStorage <string>}
      */
-    private stringArray: StringArray;
+    private stringArray: IStorage <string>;
 
     /**
      * @type {string}
@@ -48,7 +46,7 @@ export class StringArrayRotateFunctionNode extends AbstractCustomNode {
      */
     constructor (
         stringArrayName: string,
-        stringArray: StringArray,
+        stringArray: IStorage <string>,
         stringArrayRotateValue: number,
         options: IOptions
     ) {
@@ -71,22 +69,15 @@ export class StringArrayRotateFunctionNode extends AbstractCustomNode {
     }
 
     /**
-     * @returns {TStatement[]}
+     * @returns {string}
      */
-    public getNode (): TStatement[] {
-        return super.getNode();
-    }
-
-    /**
-     * @returns {TStatement[]}
-     */
-    protected getNodeStructure (): TStatement[] {
+    public getCode (): string {
         let code: string = '',
             timesName: string = Utils.getRandomVariableName(),
             whileFunctionName: string = Utils.getRandomVariableName();
 
         if (this.options.selfDefending) {
-            code = SelfDefendingTemplate().formatUnicorn({
+            code = format(SelfDefendingTemplate(), {
                 timesName,
                 whileFunctionName
             });
@@ -94,17 +85,17 @@ export class StringArrayRotateFunctionNode extends AbstractCustomNode {
             code = `${whileFunctionName}(++${timesName})`;
         }
 
-        return NodeUtils.convertCodeToStructure(
-            JavaScriptObfuscator.obfuscate(
-                StringArrayRotateFunctionTemplate().formatUnicorn({
-                    code,
-                    timesName,
-                    stringArrayName: this.stringArrayName,
-                    stringArrayRotateValue: Utils.decToHex(this.stringArrayRotateValue),
-                    whileFunctionName
-                }),
-                NO_CUSTOM_NODES_PRESET
-            ).getObfuscatedCode()
-        );
+        return JavaScriptObfuscator.obfuscate(
+            format(StringArrayRotateFunctionTemplate(), {
+                code,
+                timesName,
+                stringArrayName: this.stringArrayName,
+                stringArrayRotateValue: Utils.decToHex(this.stringArrayRotateValue),
+                whileFunctionName
+            }),
+            Object.assign({}, NO_CUSTOM_NODES_PRESET, {
+                seed: this.options.seed
+            })
+        ).getObfuscatedCode();
     }
 }

+ 0 - 2
src/enums/JSFuck.ts

@@ -1,6 +1,4 @@
 export const JSFuck: any = {
-    Window: '[]["filter"]["constructor"]("return this")()',
-
     False: '![]',
     True: '!![]',
 

+ 6 - 0
src/enums/VisitorDirection.ts

@@ -0,0 +1,6 @@
+import { Utils } from '../Utils';
+
+export const VisitorDirection: any = Utils.strEnumify({
+    enter: 'enter',
+    leave: 'leave'
+});

+ 13 - 0
src/interfaces/IControlFlowReplacer.d.ts

@@ -0,0 +1,13 @@
+import * as ESTree from 'estree';
+
+import { ICustomNode } from './custom-nodes/ICustomNode';
+import { IStorage } from './IStorage';
+
+export interface IControlFlowReplacer {
+    replace (
+        node: ESTree.Node,
+        parentNode: ESTree.Node,
+        controlFlowStorage: IStorage <ICustomNode>,
+        controlFlowStorageCustomNodeName: string
+    ): ICustomNode | undefined;
+}

+ 0 - 9
src/interfaces/INodeObfuscator.d.ts

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

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

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

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

@@ -0,0 +1,5 @@
+import { INodeTransformer } from './INodeTransformer';
+
+export interface INodeTransformersFactory {
+    initializeNodeTransformers (nodeType: string): INodeTransformer[];
+}

+ 1 - 1
src/interfaces/IObfuscator.d.ts

@@ -1,5 +1,5 @@
 import * as ESTree from 'estree';
 
 export interface IObfuscator {
-    obfuscateNode (node: ESTree.Node): ESTree.Node;
+    obfuscateAstTree (node: ESTree.Program): ESTree.Program;
 }

+ 2 - 0
src/interfaces/IObfuscatorOptions.d.ts

@@ -3,12 +3,14 @@ import { TStringArrayEncoding } from '../types/TStringArrayEncoding';
 
 export interface IObfuscatorOptions {
     compact?: boolean;
+    controlFlowFlattening?: boolean;
     debugProtection?: boolean;
     debugProtectionInterval?: boolean;
     disableConsoleOutput?: boolean;
     domainLock?: string[];
     reservedNames?: string[];
     rotateStringArray?: boolean;
+    seed?: number;
     selfDefending?: boolean;
     sourceMap?: boolean;
     sourceMapBaseUrl?: string;

+ 2 - 0
src/interfaces/IOptions.d.ts

@@ -3,12 +3,14 @@ import { TStringArrayEncoding } from '../types/TStringArrayEncoding';
 
 export interface IOptions {
     readonly compact: boolean;
+    readonly controlFlowFlattening: boolean;
     readonly debugProtection: boolean;
     readonly debugProtectionInterval: boolean;
     readonly disableConsoleOutput: boolean;
     readonly domainLock: string[];
     readonly reservedNames: string[];
     readonly rotateStringArray: boolean;
+    readonly seed: number;
     readonly selfDefending: boolean;
     readonly sourceMap: boolean;
     readonly sourceMapBaseUrl: string;

+ 8 - 0
src/interfaces/IStorage.d.ts

@@ -0,0 +1,8 @@
+export interface IStorage <T> {
+    get (key: string | number): T;
+    getKeyOf (value: T): string | number | null;
+    getLength (): number;
+    getStorage (): any;
+    set (key: string | number | null, value: T): void;
+    toString (): string;
+}

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

@@ -15,6 +15,11 @@ export interface ICustomNode {
      */
     getAppendState (): AppendState;
 
+    /**
+     * @returns {string}
+     */
+    getCode (): string;
+
     /**
      * @returns ESTree.Node[]
      */

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

@@ -5,9 +5,4 @@ export interface ICustomNodeWithData extends ICustomNode {
      * @returns any
      */
     getNodeData (): any;
-
-    /**
-     * @param data
-     */
-    updateNodeData (data: any): void;
 }

+ 3 - 1
src/interfaces/stack-trace-analyzer/IStackTraceAnalyzer.d.ts

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

+ 3 - 3
src/node-groups/AbstractNodesGroup.ts

@@ -9,17 +9,17 @@ export abstract class AbstractNodesGroup implements INodesGroup {
     /**
      * @type {AppendState}
      */
-    protected appendState: AppendState = AppendState.BeforeObfuscation;
+    protected readonly appendState: AppendState = AppendState.BeforeObfuscation;
 
     /**
      * @type {IStackTraceData[]}
      */
-    protected stackTraceData: IStackTraceData[];
+    protected readonly stackTraceData: IStackTraceData[];
 
     /**
      * @type {IOptions}
      */
-    protected options: IOptions;
+    protected readonly options: IOptions;
 
     /**
      * @param stackTraceData

+ 4 - 6
src/node-groups/StringArrayNodesGroup.ts

@@ -7,8 +7,9 @@ import { StringArrayNode } from '../custom-nodes/string-array-nodes/StringArrayN
 import { StringArrayRotateFunctionNode } from '../custom-nodes/string-array-nodes/StringArrayRotateFunctionNode';
 
 import { AbstractNodesGroup } from './AbstractNodesGroup';
-import { StringArray } from '../StringArray';
+import { StringArrayStorage } from '../storages/string-array/StringArrayStorage';
 import { Utils } from '../Utils';
+import { IStorage } from '../interfaces/IStorage';
 
 export class StringArrayNodesGroup extends AbstractNodesGroup {
     /**
@@ -40,15 +41,12 @@ export class StringArrayNodesGroup extends AbstractNodesGroup {
         }
 
         if (this.options.rotateStringArray) {
-            this.stringArrayRotateValue = Utils.getRandomGenerator().integer({
-                min: 100,
-                max: 500
-            });
+            this.stringArrayRotateValue = Utils.getRandomInteger(100, 500);
         } else {
             this.stringArrayRotateValue = 0;
         }
 
-        const stringArray: StringArray = new StringArray();
+        const stringArray: IStorage <string> = new StringArrayStorage();
         const stringArrayNode: ICustomNode = new StringArrayNode(
             stringArray,
             this.stringArrayName,

+ 6 - 6
src/node-obfuscators/AbstractNodeObfuscator.ts → src/node-transformers/AbstractNodeTransformer.ts

@@ -1,25 +1,25 @@
 import * as ESTree from 'estree';
 
 import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
-import { INodeObfuscator } from '../interfaces/INodeObfuscator';
+import { INodeTransformer } from '../interfaces/INodeTransformer';
 import { IOptions } from '../interfaces/IOptions';
 
-export abstract class AbstractNodeObfuscator implements INodeObfuscator {
+export abstract class AbstractNodeTransformer implements INodeTransformer {
     /**
      * @type Map <string, AbstractCustomNode>
      */
-    protected nodes: Map <string, ICustomNode>;
+    protected readonly nodes: Map <string, ICustomNode>;
 
     /**
      * @type {IOptions}
      */
-    protected options: IOptions;
+    protected readonly options: IOptions;
 
     /**
      * @param nodes
      * @param options
      */
-    constructor(nodes: Map <string, ICustomNode>, options: IOptions) {
+    constructor (nodes: Map <string, ICustomNode>, options: IOptions) {
         this.nodes = nodes;
         this.options = options;
     }
@@ -28,5 +28,5 @@ export abstract class AbstractNodeObfuscator implements INodeObfuscator {
      * @param node
      * @param parentNode
      */
-    public abstract obfuscateNode (node: ESTree.Node, parentNode?: ESTree.Node): void;
+    public abstract transformNode (node: ESTree.Node, parentNode?: ESTree.Node): void;
 }

+ 49 - 0
src/node-transformers/AbstractNodeTransformersFactory.ts

@@ -0,0 +1,49 @@
+import { TNodeTransformer } from '../types/TNodeTransformer';
+
+import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
+import { INodeTransformer } from '../interfaces/INodeTransformer';
+import { INodeTransformersFactory } from '../interfaces/INodeTransformersFactory';
+import { IOptions } from '../interfaces/IOptions';
+
+export abstract class AbstractNodeTransformersFactory implements INodeTransformersFactory {
+    /**
+     * @type {Map<string, TNodeTransformer[]>}
+     */
+    protected abstract readonly nodeTransformers: Map <string, TNodeTransformer[]>;
+
+    /**
+     * @type Map <string, AbstractCustomNode>
+     */
+    protected readonly customNodes: Map <string, ICustomNode>;
+
+    /**
+     * @type {IOptions}
+     */
+    protected readonly options: IOptions;
+
+    /**
+     * @param customNodes
+     * @param options
+     */
+    constructor (customNodes: Map <string, ICustomNode>, options: IOptions) {
+        this.customNodes = customNodes;
+        this.options = options;
+    }
+
+    /**
+     * @param nodeType
+     * @returns {INodeTransformer[]}
+     */
+    public initializeNodeTransformers (nodeType: string): INodeTransformer[] {
+        const nodeTransformers: TNodeTransformer[] = this.nodeTransformers.get(nodeType) || [];
+        const instancesArray: INodeTransformer[] = [];
+
+        nodeTransformers.forEach((transformer: TNodeTransformer) => {
+            instancesArray.push(
+                new transformer(this.customNodes, this.options)
+            );
+        });
+
+        return instancesArray;
+    }
+}

+ 98 - 0
src/node-transformers/node-control-flow-transformers/FunctionControlFlowTransformer.ts

@@ -0,0 +1,98 @@
+import * as estraverse from 'estraverse';
+import * as ESTree from 'estree';
+
+import { TControlFlowReplacer } from '../../types/TControlFlowReplacer';
+import { TStatement } from '../../types/TStatement';
+
+import { ICustomNode } from '../../interfaces/custom-nodes/ICustomNode';
+import { IOptions } from '../../interfaces/IOptions';
+import { IStorage } from '../../interfaces/IStorage';
+
+import { NodeType } from '../../enums/NodeType';
+
+import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
+import { BinaryExpressionControlFlowReplacer } from './control-flow-replacers/BinaryExpressionControlFlowReplacer';
+import { ControlFlowStorage } from '../../storages/control-flow/ControlFlowStorage';
+import { ControlFlowStorageNode } from '../../custom-nodes/control-flow-storage-nodes/ControlFlowStorageNode';
+import { Node } from '../../node/Node';
+import { NodeAppender } from '../../node/NodeAppender';
+import { Utils } from '../../Utils';
+import { NodeUtils } from '../../node/NodeUtils';
+
+export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
+    /**
+     * @type {Map <string, IReplacer>}
+     */
+    private static readonly controlFlowReplacers: Map <string, TControlFlowReplacer> = new Map <string, TControlFlowReplacer> ([
+        [NodeType.BinaryExpression, BinaryExpressionControlFlowReplacer]
+    ]);
+
+    /**
+     * @param nodes
+     * @param options
+     */
+    constructor (nodes: Map <string, ICustomNode>, options: IOptions) {
+        super(nodes, options);
+    }
+
+    /**
+     * @param functionNode
+     */
+    public transformNode (functionNode: ESTree.Function): void {
+        this.changeFunctionBodyControlFlow(functionNode);
+    }
+
+    /**
+     * @param functionNode
+     */
+    private changeFunctionBodyControlFlow (functionNode: ESTree.Function): void {
+        if (Node.isArrowFunctionExpressionNode(functionNode)) {
+            return;
+        }
+
+        const controlFlowStorage: IStorage <ICustomNode> = new ControlFlowStorage();
+        const controlFlowStorageCustomNodeName: string = Utils.getRandomVariableName(6);
+
+        console.log(NodeUtils.getNodeBlockScopeDepth(functionNode.body));
+
+        estraverse.replace(functionNode.body, {
+            enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
+                const controlFlowReplacer: TControlFlowReplacer | undefined = FunctionControlFlowTransformer
+                    .controlFlowReplacers.get(node.type);
+
+                if (!controlFlowReplacer) {
+                    return;
+                }
+
+                const controlFlowStorageCallCustomNode: ICustomNode | undefined = new controlFlowReplacer(
+                    this.nodes,
+                    this.options
+                ).replace(node, parentNode, controlFlowStorage, controlFlowStorageCustomNodeName);
+
+                if (!controlFlowStorageCallCustomNode) {
+                    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 | undefined = controlFlowStorageCallCustomNode.getNode()[0];
+
+                if (!statementNode || !Node.isExpressionStatementNode(statementNode)) {
+                    throw new Error(`\`controlFlowStorageCallCustomNode.getNode()\` should returns array with \`ExpressionStatement\` node`);
+                }
+
+                return statementNode.expression;
+            }
+        });
+
+        const controlFlowStorageCustomNode: ICustomNode = new ControlFlowStorageNode(
+            controlFlowStorage,
+            controlFlowStorageCustomNodeName,
+            this.options
+        );
+
+        NodeAppender.prependNode(functionNode.body, controlFlowStorageCustomNode.getNode());
+    }
+}

+ 53 - 0
src/node-transformers/node-control-flow-transformers/control-flow-replacers/AbstractControlFlowReplacer.ts

@@ -0,0 +1,53 @@
+import * as ESTree from 'estree';
+
+import { IControlFlowReplacer } from '../../../interfaces/IControlFlowReplacer';
+import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
+import { IOptions } from '../../../interfaces/IOptions';
+import { IStorage } from '../../../interfaces/IStorage';
+
+import { Utils } from '../../../Utils';
+
+export abstract class AbstractControlFlowReplacer implements IControlFlowReplacer {
+    /**
+     * @type Map <string, AbstractCustomNode>
+     */
+    protected readonly nodes: Map <string, ICustomNode>;
+
+    /**
+     * @type {IOptions}
+     */
+    protected readonly options : IOptions;
+
+    /**
+     * @param nodes
+     * @param options
+     */
+    constructor (nodes: Map <string, ICustomNode>, options: IOptions) {
+        this.nodes = nodes;
+        this.options = options;
+    }
+
+    /**
+     * @returns {string}
+     */
+    protected static getStorageKey (): string {
+        return Utils.getRandomGenerator().string({
+            length: 3,
+            pool: Utils.randomGeneratorPool
+        });
+    }
+
+    /**
+     * @param node
+     * @param parentNode
+     * @param controlFlowStorage
+     * @param controlFlowStorageCustomNodeName
+     * @returns {ICustomNode | undefined}
+     */
+    public abstract replace (
+        node: ESTree.Node,
+        parentNode: ESTree.Node,
+        controlFlowStorage: IStorage <ICustomNode>,
+        controlFlowStorageCustomNodeName: string
+    ): ICustomNode | undefined;
+}

+ 47 - 0
src/node-transformers/node-control-flow-transformers/control-flow-replacers/BinaryExpressionControlFlowReplacer.ts

@@ -0,0 +1,47 @@
+import * as escodegen from 'escodegen';
+import * as ESTree from 'estree';
+
+import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
+import { IStorage } from '../../../interfaces/IStorage';
+
+import { AbstractControlFlowReplacer } from './AbstractControlFlowReplacer';
+import { BinaryExpressionFunctionNode } from '../../../custom-nodes/control-flow-replacers-nodes/binary-expression-control-flow-replacer-nodes/BinaryExpressionFunctionNode';
+import { ControlFlowStorageCallNode } from '../../../custom-nodes/control-flow-replacers-nodes/binary-expression-control-flow-replacer-nodes/ControlFlowStorageCallNode';
+
+export class BinaryExpressionControlFlowReplacer extends AbstractControlFlowReplacer {
+    /**
+     * @param expressionNode
+     * @returns {string}
+     */
+    private static getExpressionValue (expressionNode: ESTree.Expression): string {
+        return escodegen.generate(expressionNode, {
+            sourceMapWithCode: true
+        }).code;
+    }
+
+    /**
+     * @param binaryExpressionNode
+     * @param parentNode
+     * @param controlFlowStorage
+     * @param controlFlowStorageCustomNodeName
+     * @returns {ICustomNode | undefined}
+     */
+    public replace (
+        binaryExpressionNode: ESTree.BinaryExpression,
+        parentNode: ESTree.Node,
+        controlFlowStorage: IStorage <ICustomNode>,
+        controlFlowStorageCustomNodeName: string
+    ): ICustomNode | undefined {
+        const key: string = AbstractControlFlowReplacer.getStorageKey();
+
+        controlFlowStorage.set(key, new BinaryExpressionFunctionNode(binaryExpressionNode.operator, this.options));
+
+        return new ControlFlowStorageCallNode(
+            controlFlowStorageCustomNodeName,
+            key,
+            BinaryExpressionControlFlowReplacer.getExpressionValue(binaryExpressionNode.left),
+            BinaryExpressionControlFlowReplacer.getExpressionValue(binaryExpressionNode.right),
+            this.options
+        );
+    }
+}

+ 16 - 0
src/node-transformers/node-control-flow-transformers/factory/NodeControlFlowTransformersFactory.ts

@@ -0,0 +1,16 @@
+import { TNodeTransformer } from '../../../types/TNodeTransformer';
+
+import { NodeType } from '../../../enums/NodeType';
+
+import { FunctionControlFlowTransformer } from '../FunctionControlFlowTransformer';
+import { AbstractNodeTransformersFactory } from '../../AbstractNodeTransformersFactory';
+
+export class NodeControlFlowTransformersFactory extends AbstractNodeTransformersFactory {
+    /**
+     * @type {Map<string, TNodeTransformer[]>}
+     */
+    protected readonly nodeTransformers: Map <string, TNodeTransformer[]> = new Map <string, TNodeTransformer[]> ([
+        [NodeType.FunctionDeclaration, [FunctionControlFlowTransformer]],
+        [NodeType.FunctionExpression, [FunctionControlFlowTransformer]]
+    ]);
+}

+ 10 - 10
src/node-obfuscators/CatchClauseObfuscator.ts → src/node-transformers/node-obfuscators/CatchClauseObfuscator.ts

@@ -1,15 +1,15 @@
 import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 
-import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
-import { IOptions } from '../interfaces/IOptions';
+import { ICustomNode } from '../../interfaces/custom-nodes/ICustomNode';
+import { IOptions } from '../../interfaces/IOptions';
 
-import { NodeType } from '../enums/NodeType';
+import { NodeType } from '../../enums/NodeType';
 
-import { AbstractNodeObfuscator } from './AbstractNodeObfuscator';
+import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { IdentifierReplacer } from './replacers/IdentifierReplacer';
-import { Node } from '../node/Node';
-import { NodeUtils } from '../node/NodeUtils';
+import { Node } from '../../node/Node';
+import { NodeUtils } from '../../node/NodeUtils';
 
 /**
  * replaces:
@@ -19,11 +19,11 @@ import { NodeUtils } from '../node/NodeUtils';
  *     try {} catch (_0x12d45f) { console.log(_0x12d45f); };
  *
  */
-export class CatchClauseObfuscator extends AbstractNodeObfuscator {
+export class CatchClauseObfuscator extends AbstractNodeTransformer {
     /**
      * @type {IdentifierReplacer}
      */
-    private identifierReplacer: IdentifierReplacer;
+    private readonly identifierReplacer: IdentifierReplacer;
 
     /**
      * @param nodes
@@ -38,7 +38,7 @@ export class CatchClauseObfuscator extends AbstractNodeObfuscator {
     /**
      * @param catchClauseNode
      */
-    public obfuscateNode (catchClauseNode: ESTree.CatchClause): void {
+    public transformNode (catchClauseNode: ESTree.CatchClause): void {
         this.storeCatchClauseParam(catchClauseNode);
         this.replaceCatchClauseParam(catchClauseNode);
     }
@@ -47,7 +47,7 @@ export class CatchClauseObfuscator extends AbstractNodeObfuscator {
      * @param catchClauseNode
      */
     private storeCatchClauseParam (catchClauseNode: ESTree.CatchClause): void {
-        NodeUtils.typedReplace(catchClauseNode.param, NodeType.Identifier, {
+        NodeUtils.typedTraverse(catchClauseNode.param, NodeType.Identifier, {
             enter: (node: ESTree.Identifier) => this.identifierReplacer.storeNames(node.name)
         });
     }

+ 11 - 11
src/node-obfuscators/FunctionDeclarationObfuscator.ts → src/node-transformers/node-obfuscators/FunctionDeclarationObfuscator.ts

@@ -1,17 +1,17 @@
 import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 
-import { TNodeWithBlockStatement } from '../types/TNodeWithBlockStatement';
+import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
 
-import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
-import { IOptions } from '../interfaces/IOptions';
+import { ICustomNode } from '../../interfaces/custom-nodes/ICustomNode';
+import { IOptions } from '../../interfaces/IOptions';
 
-import { NodeType } from '../enums/NodeType';
+import { NodeType } from '../../enums/NodeType';
 
-import { AbstractNodeObfuscator } from './AbstractNodeObfuscator';
+import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { IdentifierReplacer } from './replacers/IdentifierReplacer';
-import { Node } from '../node/Node';
-import { NodeUtils } from '../node/NodeUtils';
+import { Node } from '../../node/Node';
+import { NodeUtils } from '../../node/NodeUtils';
 
 /**
  * replaces:
@@ -22,11 +22,11 @@ import { NodeUtils } from '../node/NodeUtils';
  *     function _0x12d45f () { //... };
  *     _0x12d45f();
  */
-export class FunctionDeclarationObfuscator extends AbstractNodeObfuscator {
+export class FunctionDeclarationObfuscator extends AbstractNodeTransformer {
     /**
      * @type {IdentifierReplacer}
      */
-    private identifierReplacer: IdentifierReplacer;
+    private readonly identifierReplacer: IdentifierReplacer;
 
     /**
      * @param nodes
@@ -42,7 +42,7 @@ export class FunctionDeclarationObfuscator extends AbstractNodeObfuscator {
      * @param functionDeclarationNode
      * @param parentNode
      */
-    public obfuscateNode (functionDeclarationNode: ESTree.FunctionDeclaration, parentNode: ESTree.Node): void {
+    public transformNode (functionDeclarationNode: ESTree.FunctionDeclaration, parentNode: ESTree.Node): void {
         const blockScopeOfFunctionDeclarationNode: TNodeWithBlockStatement = NodeUtils
             .getBlockScopeOfNode(functionDeclarationNode);
 
@@ -58,7 +58,7 @@ export class FunctionDeclarationObfuscator extends AbstractNodeObfuscator {
      * @param functionDeclarationNode
      */
     private storeFunctionName (functionDeclarationNode: ESTree.FunctionDeclaration): void {
-        NodeUtils.typedReplace(functionDeclarationNode.id, NodeType.Identifier, {
+        NodeUtils.typedTraverse(functionDeclarationNode.id, NodeType.Identifier, {
             enter: (node: ESTree.Identifier) => this.identifierReplacer.storeNames(node.name)
         });
     }

+ 13 - 16
src/node-obfuscators/FunctionObfuscator.ts → src/node-transformers/node-obfuscators/FunctionObfuscator.ts

@@ -1,15 +1,15 @@
 import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 
-import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
-import { IOptions } from '../interfaces/IOptions';
+import { ICustomNode } from '../../interfaces/custom-nodes/ICustomNode';
+import { IOptions } from '../../interfaces/IOptions';
 
-import { NodeType } from '../enums/NodeType';
+import { NodeType } from '../../enums/NodeType';
 
-import { AbstractNodeObfuscator } from './AbstractNodeObfuscator';
+import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { IdentifierReplacer } from './replacers/IdentifierReplacer';
-import { Node } from '../node/Node';
-import { NodeUtils } from '../node/NodeUtils';
+import { Node } from '../../node/Node';
+import { NodeUtils } from '../../node/NodeUtils';
 
 /**
  * replaces:
@@ -19,11 +19,11 @@ import { NodeUtils } from '../node/NodeUtils';
  *     function foo (_0x12d45f) { return _0x12d45f; };
  *
  */
-export class FunctionObfuscator extends AbstractNodeObfuscator {
+export class FunctionObfuscator extends AbstractNodeTransformer {
     /**
      * @type {IdentifierReplacer}
      */
-    private identifierReplacer: IdentifierReplacer;
+    private readonly identifierReplacer: IdentifierReplacer;
 
     /**
      * @param nodes
@@ -38,7 +38,7 @@ export class FunctionObfuscator extends AbstractNodeObfuscator {
     /**
      * @param functionNode
      */
-    public obfuscateNode (functionNode: ESTree.Function): void {
+    public transformNode (functionNode: ESTree.Function): void {
         this.storeFunctionParams(functionNode);
         this.replaceFunctionParams(functionNode);
     }
@@ -49,7 +49,7 @@ export class FunctionObfuscator extends AbstractNodeObfuscator {
     private storeFunctionParams (functionNode: ESTree.Function): void {
         functionNode.params
             .forEach((paramsNode: ESTree.Node) => {
-                NodeUtils.typedReplace(paramsNode, NodeType.Identifier, {
+                NodeUtils.typedTraverse(paramsNode, NodeType.Identifier, {
                     enter: (node: ESTree.Identifier) => this.identifierReplacer.storeNames(node.name)
                 });
             });
@@ -59,7 +59,7 @@ export class FunctionObfuscator extends AbstractNodeObfuscator {
      * @param functionNode
      */
     private replaceFunctionParams (functionNode: ESTree.Function): void {
-        let replaceVisitor: estraverse.Visitor = {
+        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);
@@ -72,11 +72,8 @@ export class FunctionObfuscator extends AbstractNodeObfuscator {
             }
         };
 
-        functionNode.params
-            .forEach((paramsNode: ESTree.Node) => {
-                estraverse.replace(paramsNode, replaceVisitor);
-            });
+        functionNode.params.forEach((paramsNode: ESTree.Node) => estraverse.replace(paramsNode, traverseVisitor));
 
-        estraverse.replace(functionNode.body, replaceVisitor);
+        estraverse.replace(functionNode.body, traverseVisitor);
     }
 }

+ 10 - 10
src/node-obfuscators/LabeledStatementObfuscator.ts → src/node-transformers/node-obfuscators/LabeledStatementObfuscator.ts

@@ -1,15 +1,15 @@
 import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 
-import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
-import { IOptions } from '../interfaces/IOptions';
+import { ICustomNode } from '../../interfaces/custom-nodes/ICustomNode';
+import { IOptions } from '../../interfaces/IOptions';
 
-import { NodeType } from '../enums/NodeType';
+import { NodeType } from '../../enums/NodeType';
 
-import { AbstractNodeObfuscator } from './AbstractNodeObfuscator';
+import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { IdentifierReplacer } from './replacers/IdentifierReplacer';
-import { Node } from '../node/Node';
-import { NodeUtils } from '../node/NodeUtils';
+import { Node } from '../../node/Node';
+import { NodeUtils } from '../../node/NodeUtils';
 
 /**
  * replaces:
@@ -27,11 +27,11 @@ import { NodeUtils } from '../node/NodeUtils';
  *     }
  *
  */
-export class LabeledStatementObfuscator extends AbstractNodeObfuscator {
+export class LabeledStatementObfuscator extends AbstractNodeTransformer {
     /**
      * @type {IdentifierReplacer}
      */
-    private identifierReplacer: IdentifierReplacer;
+    private readonly identifierReplacer: IdentifierReplacer;
 
     /**
      * @param nodes
@@ -46,7 +46,7 @@ export class LabeledStatementObfuscator extends AbstractNodeObfuscator {
     /**
      * @param labeledStatementNode
      */
-    public obfuscateNode (labeledStatementNode: ESTree.LabeledStatement): void {
+    public transformNode (labeledStatementNode: ESTree.LabeledStatement): void {
         this.storeLabeledStatementName(labeledStatementNode);
         this.replaceLabeledStatementName(labeledStatementNode);
     }
@@ -55,7 +55,7 @@ export class LabeledStatementObfuscator extends AbstractNodeObfuscator {
      * @param labeledStatementNode
      */
     private storeLabeledStatementName (labeledStatementNode: ESTree.LabeledStatement): void {
-        NodeUtils.typedReplace(labeledStatementNode.label, NodeType.Identifier, {
+        NodeUtils.typedTraverse(labeledStatementNode.label, NodeType.Identifier, {
             enter: (node: ESTree.Identifier) => this.identifierReplacer.storeNames(node.name)
         });
     }

+ 5 - 5
src/node-obfuscators/LiteralObfuscator.ts → src/node-transformers/node-obfuscators/LiteralObfuscator.ts

@@ -1,18 +1,18 @@
 import * as escodegen from 'escodegen';
 import * as ESTree from 'estree';
 
-import { AbstractNodeObfuscator } from './AbstractNodeObfuscator';
+import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { BooleanLiteralReplacer } from './replacers/BooleanLiteralReplacer';
-import { Node } from '../node/Node';
+import { Node } from '../../node/Node';
 import { NumberLiteralReplacer } from './replacers/NumberLiteralReplacer';
 import { StringLiteralReplacer } from './replacers/StringLiteralReplacer';
 
-export class LiteralObfuscator extends AbstractNodeObfuscator {
+export class LiteralObfuscator extends AbstractNodeTransformer {
     /**
      * @param literalNode
      * @param parentNode
      */
-    public obfuscateNode (literalNode: ESTree.Literal, parentNode: ESTree.Node): void {
+    public transformNode (literalNode: ESTree.Literal, parentNode: ESTree.Node): void {
         if (Node.isPropertyNode(parentNode) && parentNode.key === literalNode) {
             return;
         }
@@ -34,7 +34,7 @@ export class LiteralObfuscator extends AbstractNodeObfuscator {
 
             case 'string':
                 content = new StringLiteralReplacer(this.nodes, this.options)
-                        .replace(<string>literalNode.value);
+                    .replace(<string>literalNode.value);
 
                 break;
 

+ 16 - 16
src/node-obfuscators/MemberExpressionObfuscator.ts → src/node-transformers/node-obfuscators/MemberExpressionObfuscator.ts

@@ -2,18 +2,18 @@ import * as escodegen from 'escodegen';
 import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 
-import { NodeType } from '../enums/NodeType';
+import { NodeType } from '../../enums/NodeType';
 
-import { AbstractNodeObfuscator } from './AbstractNodeObfuscator';
-import { Node } from '../node/Node';
+import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
+import { Node } from '../../node/Node';
 import { StringLiteralReplacer } from './replacers/StringLiteralReplacer';
 
-export class MemberExpressionObfuscator extends AbstractNodeObfuscator {
+export class MemberExpressionObfuscator extends AbstractNodeTransformer {
     /**
      * @param memberExpressionNode
      */
-    public obfuscateNode (memberExpressionNode: ESTree.MemberExpression): void {
-        estraverse.replace(memberExpressionNode.property, {
+    public transformNode (memberExpressionNode: ESTree.MemberExpression): void {
+        estraverse.traverse(memberExpressionNode.property, {
             enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
                 if (Node.isLiteralNode(node)) {
                     this.obfuscateLiteralProperty(node);
@@ -46,16 +46,16 @@ export class MemberExpressionObfuscator extends AbstractNodeObfuscator {
      * @param node
      */
     private obfuscateIdentifierProperty (node: ESTree.Identifier): void {
-        let nodeValue: string = node.name,
-            literalNode: ESTree.Literal = {
-                raw: `'${nodeValue}'`,
-                'x-verbatim-property': {
-                    content : new StringLiteralReplacer(this.nodes, this.options).replace(nodeValue),
-                    precedence: escodegen.Precedence.Primary
-                },
-                type: NodeType.Literal,
-                value: nodeValue
-            };
+        const nodeValue: string = node.name;
+        const literalNode: ESTree.Literal = {
+            raw: `'${nodeValue}'`,
+            'x-verbatim-property': {
+                content : new StringLiteralReplacer(this.nodes, this.options).replace(nodeValue),
+                precedence: escodegen.Precedence.Primary
+            },
+            type: NodeType.Literal,
+            value: nodeValue
+        };
 
         delete node.name;
 

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

@@ -1,9 +1,9 @@
 import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 
-import { AbstractNodeObfuscator } from './AbstractNodeObfuscator';
-import { Node } from '../node/Node';
-import { Utils } from '../Utils';
+import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
+import { Node } from '../../node/Node';
+import { Utils } from '../../Utils';
 import { StringLiteralReplacer } from './replacers/StringLiteralReplacer';
 
 /**
@@ -13,17 +13,17 @@ import { StringLiteralReplacer } from './replacers/StringLiteralReplacer';
  * on:
  *     [_0x9a4e('0x0')] { //... };
  */
-export class MethodDefinitionObfuscator extends AbstractNodeObfuscator {
+export class MethodDefinitionObfuscator extends AbstractNodeTransformer {
     /**
      * @type {string[]}
      */
-    private static ignoredNames: string[] = ['constructor'];
+    private static readonly ignoredNames: string[] = ['constructor'];
 
     /**
      * @param methodDefinitionNode
      * @param parentNode
      */
-    public obfuscateNode (methodDefinitionNode: ESTree.MethodDefinition, parentNode: ESTree.Node): void {
+    public transformNode (methodDefinitionNode: ESTree.MethodDefinition, parentNode: ESTree.Node): void {
         this.replaceMethodName(methodDefinitionNode);
     }
 

+ 17 - 17
src/node-obfuscators/ObjectExpressionObfuscator.ts → src/node-transformers/node-obfuscators/ObjectExpressionObfuscator.ts

@@ -2,11 +2,11 @@ import * as escodegen from 'escodegen';
 import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 
-import { NodeType } from '../enums/NodeType';
+import { NodeType } from '../../enums/NodeType';
 
-import { AbstractNodeObfuscator } from './AbstractNodeObfuscator';
-import { Node } from '../node/Node';
-import { Utils } from '../Utils';
+import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
+import { Node } from '../../node/Node';
+import { Utils } from '../../Utils';
 
 /**
  * replaces:
@@ -18,18 +18,18 @@ import { Utils } from '../Utils';
  * on:
  *     var object = { '\u0050\u0053\u0045\u0055\u0044\u004f': 1 };
  */
-export class ObjectExpressionObfuscator extends AbstractNodeObfuscator {
+export class ObjectExpressionObfuscator extends AbstractNodeTransformer {
     /**
      * @param objectExpressionNode
      */
-    public obfuscateNode (objectExpressionNode: ESTree.ObjectExpression): void {
+    public transformNode (objectExpressionNode: ESTree.ObjectExpression): void {
         objectExpressionNode.properties
             .forEach((property: ESTree.Property) => {
                 if (property.shorthand) {
                     property.shorthand = false;
                 }
 
-                estraverse.replace(property.key, {
+                estraverse.traverse(property.key, {
                     enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
                         if (Node.isLiteralNode(node)) {
                             this.obfuscateLiteralPropertyKey(node);
@@ -61,16 +61,16 @@ export class ObjectExpressionObfuscator extends AbstractNodeObfuscator {
      * @param node
      */
     private obfuscateIdentifierPropertyKey (node: ESTree.Identifier): void {
-        let nodeValue: string = node.name,
-            literalNode: ESTree.Literal = {
-                raw: `'${nodeValue}'`,
-                'x-verbatim-property': {
-                    content : `'${Utils.stringToUnicodeEscapeSequence(nodeValue)}'`,
-                    precedence: escodegen.Precedence.Primary
-                },
-                type: NodeType.Literal,
-                value: nodeValue
-            };
+        const nodeValue: string = node.name;
+        const literalNode: ESTree.Literal = {
+            raw: `'${nodeValue}'`,
+            'x-verbatim-property': {
+                content : `'${Utils.stringToUnicodeEscapeSequence(nodeValue)}'`,
+                precedence: escodegen.Precedence.Primary
+            },
+            type: NodeType.Literal,
+            value: nodeValue
+        };
 
         delete node.name;
 

+ 11 - 11
src/node-obfuscators/VariableDeclarationObfuscator.ts → src/node-transformers/node-obfuscators/VariableDeclarationObfuscator.ts

@@ -1,17 +1,17 @@
 import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 
-import { TNodeWithBlockStatement } from '../types/TNodeWithBlockStatement';
+import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
 
-import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
-import { IOptions } from '../interfaces/IOptions';
+import { ICustomNode } from '../../interfaces/custom-nodes/ICustomNode';
+import { IOptions } from '../../interfaces/IOptions';
 
-import { NodeType } from '../enums/NodeType';
+import { NodeType } from '../../enums/NodeType';
 
-import { AbstractNodeObfuscator } from './AbstractNodeObfuscator';
+import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { IdentifierReplacer } from './replacers/IdentifierReplacer';
-import { Node } from '../node/Node';
-import { NodeUtils } from '../node/NodeUtils';
+import { Node } from '../../node/Node';
+import { NodeUtils } from '../../node/NodeUtils';
 
 /**
  * replaces:
@@ -23,11 +23,11 @@ import { NodeUtils } from '../node/NodeUtils';
  *     _0x12d45f++;
  *
  */
-export class VariableDeclarationObfuscator extends AbstractNodeObfuscator {
+export class VariableDeclarationObfuscator extends AbstractNodeTransformer {
     /**
      * @type {IdentifierReplacer}
      */
-    private identifierReplacer: IdentifierReplacer;
+    private readonly identifierReplacer: IdentifierReplacer;
 
     /**
      * @param nodes
@@ -43,7 +43,7 @@ export class VariableDeclarationObfuscator extends AbstractNodeObfuscator {
      * @param variableDeclarationNode
      * @param parentNode
      */
-    public obfuscateNode (variableDeclarationNode: ESTree.VariableDeclaration, parentNode: ESTree.Node): void {
+    public transformNode (variableDeclarationNode: ESTree.VariableDeclaration, parentNode: ESTree.Node): void {
         const blockScopeOfVariableDeclarationNode: TNodeWithBlockStatement = NodeUtils
             .getBlockScopeOfNode(variableDeclarationNode);
 
@@ -65,7 +65,7 @@ export class VariableDeclarationObfuscator extends AbstractNodeObfuscator {
     private storeVariableNames (variableDeclarationNode: ESTree.VariableDeclaration): void {
         variableDeclarationNode.declarations
             .forEach((declarationNode: ESTree.VariableDeclarator) => {
-                NodeUtils.typedReplace(declarationNode.id, NodeType.Identifier, {
+                NodeUtils.typedTraverse(declarationNode.id, NodeType.Identifier, {
                     enter: (node: ESTree.Identifier) => this.identifierReplacer.storeNames(node.name)
                 });
             });

+ 36 - 0
src/node-transformers/node-obfuscators/factory/NodeObfuscatorsFactory.ts

@@ -0,0 +1,36 @@
+import { TNodeTransformer } from '../../../types/TNodeTransformer';
+
+import { NodeType } from '../../../enums/NodeType';
+
+import { CatchClauseObfuscator } from '../CatchClauseObfuscator';
+import { FunctionDeclarationObfuscator } from '../FunctionDeclarationObfuscator';
+import { FunctionObfuscator } from '../FunctionObfuscator';
+import { LabeledStatementObfuscator } from '../LabeledStatementObfuscator';
+import { LiteralObfuscator } from '../LiteralObfuscator';
+import { MemberExpressionObfuscator } from '../MemberExpressionObfuscator';
+import { MethodDefinitionObfuscator } from '../MethodDefinitionObfuscator';
+import { ObjectExpressionObfuscator } from '../ObjectExpressionObfuscator';
+import { VariableDeclarationObfuscator } from '../VariableDeclarationObfuscator';
+import { AbstractNodeTransformersFactory } from '../../AbstractNodeTransformersFactory';
+
+export class NodeObfuscatorsFactory extends AbstractNodeTransformersFactory {
+    /**
+     * @type {Map<string, TNodeTransformer[]>}
+     */
+    protected readonly nodeTransformers: Map <string, TNodeTransformer[]> = new Map <string, TNodeTransformer[]> ([
+        [NodeType.ArrowFunctionExpression, [FunctionObfuscator]],
+        [NodeType.ClassDeclaration, [FunctionDeclarationObfuscator]],
+        [NodeType.CatchClause, [CatchClauseObfuscator]],
+        [NodeType.FunctionDeclaration, [
+            FunctionDeclarationObfuscator,
+            FunctionObfuscator
+        ]],
+        [NodeType.FunctionExpression, [FunctionObfuscator]],
+        [NodeType.MemberExpression, [MemberExpressionObfuscator]],
+        [NodeType.MethodDefinition, [MethodDefinitionObfuscator]],
+        [NodeType.ObjectExpression, [ObjectExpressionObfuscator]],
+        [NodeType.VariableDeclaration, [VariableDeclarationObfuscator]],
+        [NodeType.LabeledStatement, [LabeledStatementObfuscator]],
+        [NodeType.Literal, [LiteralObfuscator]]
+    ]);
+}

+ 5 - 5
src/node-obfuscators/replacers/AbstractReplacer.ts → src/node-transformers/node-obfuscators/replacers/AbstractReplacer.ts

@@ -1,17 +1,17 @@
-import { ICustomNode } from '../../interfaces/custom-nodes/ICustomNode';
-import { IOptions } from '../../interfaces/IOptions';
-import { IReplacer } from '../../interfaces/IReplacer';
+import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
+import { IOptions } from '../../../interfaces/IOptions';
+import { IReplacer } from '../../../interfaces/IReplacer';
 
 export abstract class AbstractReplacer implements IReplacer {
     /**
      * @type Map <string, AbstractCustomNode>
      */
-    protected nodes: Map <string, ICustomNode>;
+    protected readonly nodes: Map <string, ICustomNode>;
 
     /**
      * @type {IOptions}
      */
-    protected options : IOptions;
+    protected readonly options : IOptions;
 
     /**
      * @param nodes

+ 1 - 1
src/node-obfuscators/replacers/BooleanLiteralReplacer.ts → src/node-transformers/node-obfuscators/replacers/BooleanLiteralReplacer.ts

@@ -1,4 +1,4 @@
-import { JSFuck } from '../../enums/JSFuck';
+import { JSFuck } from '../../../enums/JSFuck';
 
 import { AbstractReplacer } from './AbstractReplacer';
 

+ 2 - 2
src/node-obfuscators/replacers/IdentifierReplacer.ts → src/node-transformers/node-obfuscators/replacers/IdentifierReplacer.ts

@@ -1,11 +1,11 @@
 import { AbstractReplacer } from './AbstractReplacer';
-import { Utils } from '../../Utils';
+import { Utils } from '../../../Utils';
 
 export class IdentifierReplacer extends AbstractReplacer {
     /**
      * @type {Map<string, string>}
      */
-    private namesMap: Map<string, string> = new Map<string, string>();
+    private readonly namesMap: Map<string, string> = new Map<string, string>();
 
     /**
      * @param nodeValue

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

@@ -1,5 +1,5 @@
 import { AbstractReplacer } from './AbstractReplacer';
-import { Utils } from '../../Utils';
+import { Utils } from '../../../Utils';
 
 export class NumberLiteralReplacer extends AbstractReplacer {
     /**

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

@@ -1,23 +1,23 @@
-import { TStringArrayCallsWrapper } from '../../types/custom-nodes/TStringArrayCallsWrapper';
-import { TStringArrayNode } from '../../types/custom-nodes/TStringArrayNode';
+import { ICustomNodeWithData } from '../../../interfaces/custom-nodes/ICustomNodeWithData';
+import { IStorage } from '../../../interfaces/IStorage';
 
-import { StringArrayEncoding } from '../../enums/StringArrayEncoding';
+import { StringArrayEncoding } from '../../../enums/StringArrayEncoding';
 
 import { AbstractReplacer } from './AbstractReplacer';
 import { NumberLiteralReplacer } from './NumberLiteralReplacer';
-import { StringArray } from '../../StringArray';
-import { Utils } from '../../Utils';
+import { Utils } from '../../../Utils';
+import { ICustomNodeWithIdentifier } from '../../../interfaces/custom-nodes/ICustomNodeWithIdentifier';
 
 export class StringLiteralReplacer extends AbstractReplacer {
     /**
      * @type {number}
      */
-    private static minimumLengthForStringArray: number = 3;
+    private static readonly minimumLengthForStringArray: number = 3;
 
     /**
      * @type {string[]}
      */
-    private static rc4Keys: string[] = Utils.getRandomGenerator()
+    private static readonly rc4Keys: string[] = Utils.getRandomGenerator()
         .n(() => Utils.getRandomGenerator().string({length: 4}), 50);
 
     /**
@@ -27,7 +27,7 @@ export class StringLiteralReplacer extends AbstractReplacer {
     public replace (nodeValue: string): string {
         const replaceWithStringArrayFlag: boolean = (
             nodeValue.length >= StringLiteralReplacer.minimumLengthForStringArray
-            && Math.random() <= this.options.stringArrayThreshold
+            && Utils.getRandomFloat(0, 1) <= this.options.stringArrayThreshold
         );
 
         if (this.options.stringArray && replaceWithStringArrayFlag) {
@@ -42,10 +42,10 @@ export class StringLiteralReplacer extends AbstractReplacer {
      * @returns {string}
      */
     private replaceStringLiteralWithStringArrayCall (value: string): string {
-        const stringArrayNode: TStringArrayNode = <TStringArrayNode>this.nodes.get('stringArrayNode');
+        const stringArrayNode: ICustomNodeWithData = <ICustomNodeWithData>this.nodes.get('stringArrayNode');
 
         if (!stringArrayNode) {
-            throw new ReferenceError('`stringArrayNode` node is not found in Map with custom node.');
+            throw new ReferenceError('`stringArrayNode` node is not found in Map with custom nodes.');
         }
 
         let rc4Key: string = '';
@@ -67,22 +67,20 @@ export class StringLiteralReplacer extends AbstractReplacer {
             value = Utils.stringToUnicodeEscapeSequence(value);
         }
 
-        let stringArray: StringArray = stringArrayNode.getNodeData(),
-            indexOfExistingValue: number = stringArray.getIndexOf(value),
-            indexOfValue: number,
-            hexadecimalIndex: string;
+        const stringArray: IStorage <string> = stringArrayNode.getNodeData();
+        const indexOfExistingValue: number = <number>stringArray.getKeyOf(value);
+
+        let indexOfValue: number;
 
         if (indexOfExistingValue >= 0) {
             indexOfValue = indexOfExistingValue;
         } else {
             indexOfValue = stringArray.getLength();
-            stringArrayNode.updateNodeData(value);
+            stringArray.set(null, value);
         }
 
-        hexadecimalIndex = new NumberLiteralReplacer(this.nodes, this.options)
-            .replace(indexOfValue);
-
-        const stringArrayCallsWrapper: TStringArrayCallsWrapper = <TStringArrayCallsWrapper>this.nodes.get('stringArrayCallsWrapper');
+        const stringArrayCallsWrapper: ICustomNodeWithIdentifier = <ICustomNodeWithIdentifier>this.nodes.get('stringArrayCallsWrapper');
+        const hexadecimalIndex: string = new NumberLiteralReplacer(this.nodes, this.options).replace(indexOfValue);
 
         if (!stringArrayCallsWrapper) {
             throw new ReferenceError('`stringArrayCallsWrapper` node is not found in Map with custom node.');

+ 1 - 4
src/node/NodeAppender.ts

@@ -101,10 +101,7 @@ export class NodeAppender {
      * @param stackTraceRootLength
      */
     public static getRandomStackTraceIndex (stackTraceRootLength: number): number {
-        return Utils.getRandomGenerator().integer({
-            min: 0,
-            max: Math.max(0, Math.round(stackTraceRootLength - 1))
-        });
+        return Utils.getRandomInteger(0, Math.max(0, Math.round(stackTraceRootLength - 1)));
     }
 
     /**

+ 26 - 3
src/node/NodeUtils.ts

@@ -15,7 +15,7 @@ export class NodeUtils {
     /**
      * @type {string[]}
      */
-    private static nodesWithBlockScope: string[] = [
+    private static readonly nodesWithBlockScope: string[] = [
         NodeType.ArrowFunctionExpression,
         NodeType.FunctionDeclaration,
         NodeType.FunctionExpression,
@@ -42,7 +42,7 @@ export class NodeUtils {
      * @returns {TStatement[]}
      */
     public static convertCodeToStructure (code: string): TStatement[] {
-        let structure: ESTree.Program = esprima.parse(code);
+        const structure: ESTree.Program = esprima.parse(code);
 
         NodeUtils.addXVerbatimPropertyToLiterals(structure);
         NodeUtils.parentize(structure);
@@ -73,7 +73,7 @@ export class NodeUtils {
      * @returns {ESTree.Node}
      */
     public static getBlockScopeOfNode (node: ESTree.Node, depth: number = 0): TNodeWithBlockStatement {
-        let parentNode: ESTree.Node | undefined = node.parentNode;
+        const parentNode: ESTree.Node | undefined = node.parentNode;
 
         if (!parentNode) {
             throw new ReferenceError('`parentNode` property of given node is `undefined`');
@@ -100,6 +100,29 @@ export class NodeUtils {
         return NodeUtils.getBlockScopeOfNode(parentNode);
     }
 
+    /**
+     * @param node
+     * @param depth
+     * @returns {number}
+     */
+    public static getNodeBlockScopeDepth (node: ESTree.Node, depth: number = 0): number {
+        const parentNode: ESTree.Node | undefined = node.parentNode;
+
+        if (!parentNode) {
+            throw new ReferenceError('`parentNode` property of given node is `undefined`');
+        }
+
+        if (Node.isProgramNode(parentNode)) {
+            return depth;
+        }
+
+        if (Node.isBlockStatementNode(node)) {
+            return NodeUtils.getNodeBlockScopeDepth(parentNode, ++depth);
+        }
+
+        return NodeUtils.getNodeBlockScopeDepth(parentNode, depth);
+    }
+
     /**
      * @param node
      */

+ 13 - 1
src/options/Options.ts

@@ -41,6 +41,12 @@ export class Options implements IOptions {
     @IsBoolean()
     public readonly compact: boolean;
 
+    /**
+     * @type {boolean}
+     */
+    @IsBoolean()
+    public readonly controlFlowFlattening: boolean;
+
     /**
      * @type {boolean}
      */
@@ -85,6 +91,12 @@ export class Options implements IOptions {
     @IsBoolean()
     public readonly rotateStringArray: boolean;
 
+    /**
+     * @type {number}
+     */
+    @IsNumber()
+    public readonly seed: number;
+
     /**
      * @type {boolean}
      */
@@ -152,7 +164,7 @@ export class Options implements IOptions {
     constructor (obfuscatorOptions: IObfuscatorOptions) {
         Object.assign(this, DEFAULT_PRESET, obfuscatorOptions);
 
-        let errors: ValidationError[] = validateSync(this, Options.validatorOptions);
+        const errors: ValidationError[] = validateSync(this, Options.validatorOptions);
 
         if (errors.length) {
             throw new ReferenceError(`Validation failed. errors:\n${ValidationErrorsFormatter.format(errors)}`);

+ 6 - 6
src/options/OptionsNormalizer.ts

@@ -9,7 +9,7 @@ export class OptionsNormalizer {
     /**
      * @type {IObfuscatorOptions}
      */
-    private static DISABLED_UNICODE_ARRAY_OPTIONS: IObfuscatorOptions = {
+    private static readonly DISABLED_UNICODE_ARRAY_OPTIONS: IObfuscatorOptions = {
         rotateStringArray: false,
         stringArray: false,
         stringArrayEncoding: false,
@@ -19,7 +19,7 @@ export class OptionsNormalizer {
     /**
      * @type {IObfuscatorOptions}
      */
-    private static SELF_DEFENDING_OPTIONS: IObfuscatorOptions = {
+    private static readonly SELF_DEFENDING_OPTIONS: IObfuscatorOptions = {
         compact: true,
         selfDefending: true
     };
@@ -27,14 +27,14 @@ export class OptionsNormalizer {
     /**
      * @type {IObfuscatorOptions}
      */
-    private static UNICODE_ARRAY_ENCODING_OPTIONS: IObfuscatorOptions = {
+    private static readonly UNICODE_ARRAY_ENCODING_OPTIONS: IObfuscatorOptions = {
         stringArrayEncoding: 'base64'
     };
 
     /**
      * @type {TOptionsNormalizerRule[]}
      */
-    private static normalizerRules: TOptionsNormalizerRule[] = [
+    private static readonly normalizerRules: TOptionsNormalizerRule[] = [
         OptionsNormalizer.domainLockRule,
         OptionsNormalizer.selfDefendingRule,
         OptionsNormalizer.sourceMapBaseUrlRule,
@@ -64,7 +64,7 @@ export class OptionsNormalizer {
      */
     private static domainLockRule (options: IOptions): IOptions {
         if (options.domainLock.length) {
-            let normalizedDomains: string[] = [];
+            const normalizedDomains: string[] = [];
 
             for (const domain of options.domainLock) {
                 normalizedDomains.push(Utils.extractDomainFromUrl(domain));
@@ -95,7 +95,7 @@ export class OptionsNormalizer {
      * @returns {IOptions}
      */
     private static sourceMapBaseUrlRule (options: IOptions): IOptions {
-        let sourceMapBaseUrl: string = options.sourceMapBaseUrl;
+        const sourceMapBaseUrl: string = options.sourceMapBaseUrl;
 
         if (!options.sourceMapFileName) {
             Object.assign(options, {

+ 4 - 3
src/options/ValidationErrorsFormatter.ts

@@ -6,7 +6,7 @@ export class ValidationErrorsFormatter {
      * @returns {string}
      */
     public static format (validationErrors: ValidationError[]): string {
-        let errorsArray: string[] = [];
+        const errorsArray: string[] = [];
 
         for (const error of validationErrors) {
             errorsArray.push(ValidationErrorsFormatter.formatError(error));
@@ -20,8 +20,9 @@ export class ValidationErrorsFormatter {
      * @returns {string}
      */
     private static formatError (validationError: ValidationError): string {
-        let errorString: string = `\`${validationError.property}\` errors:\n`,
-            constraints: {[type: string]: string} = validationError.constraints;
+        const constraints: {[type: string]: string} = validationError.constraints;
+
+        let errorString: string = `\`${validationError.property}\` errors:\n`;
 
         for (const constraint in constraints) {
             if (!constraints.hasOwnProperty(constraint)) {

+ 2 - 0
src/preset-options/DefaultPreset.ts

@@ -4,12 +4,14 @@ import { SourceMapMode } from '../enums/SourceMapMode';
 
 export const DEFAULT_PRESET: IObfuscatorOptions = Object.freeze({
     compact: true,
+    controlFlowFlattening: false,
     debugProtection: false,
     debugProtectionInterval: false,
     disableConsoleOutput: true,
     domainLock: [],
     reservedNames: [],
     rotateStringArray: true,
+    seed: 0,
     selfDefending: true,
     sourceMap: false,
     sourceMapBaseUrl: '',

+ 2 - 0
src/preset-options/NoCustomNodesPreset.ts

@@ -4,12 +4,14 @@ import { SourceMapMode } from '../enums/SourceMapMode';
 
 export const NO_CUSTOM_NODES_PRESET: IObfuscatorOptions = Object.freeze({
     compact: true,
+    controlFlowFlattening: false,
     debugProtection: false,
     debugProtectionInterval: false,
     disableConsoleOutput: false,
     domainLock: [],
     reservedNames: [],
     rotateStringArray: false,
+    seed: 0,
     selfDefending: false,
     sourceMap: false,
     sourceMapBaseUrl: '',

+ 7 - 18
src/stack-trace-analyzer/StackTraceAnalyzer.ts

@@ -51,34 +51,22 @@ export class StackTraceAnalyzer implements IStackTraceAnalyzer {
     /**
      * @type {number}
      */
-    private static limitThresholdActivationLength: number = 25;
+    private static readonly limitThresholdActivationLength: number = 25;
 
     /**
      * @type {number}
      */
-    private static limitThreshold: number = 0.002;
-
-    /**
-     * @type {ESTree.Node[]}
-     */
-    private blockScopeBody: ESTree.Node[];
+    private static readonly limitThreshold: number = 0.002;
 
     /**
      * @type {Map<string, TCalleeDataExtractor>}
      */
-    private calleeDataExtractors: Map <string, TCalleeDataExtractor> = new Map <string, TCalleeDataExtractor> ([
+    private readonly calleeDataExtractors: Map <string, TCalleeDataExtractor> = new Map <string, TCalleeDataExtractor> ([
         [NodeType.FunctionDeclaration, FunctionDeclarationCalleeDataExtractor],
         [NodeType.FunctionExpression, FunctionExpressionCalleeDataExtractor],
         [NodeType.ObjectExpression, ObjectExpressionCalleeDataExtractor]
     ]);
 
-    /**
-     * @param blockScopeBody
-     */
-    constructor (blockScopeBody: ESTree.Node[]) {
-        this.blockScopeBody = blockScopeBody;
-    }
-
     /**
      * @param blockScopeBodyLength
      * @returns {number}
@@ -103,10 +91,11 @@ export class StackTraceAnalyzer implements IStackTraceAnalyzer {
     }
 
     /**
+     * @param blockScopeBody
      * @returns {IStackTraceData[]}
      */
-    public analyze (): IStackTraceData[] {
-        return this.analyzeRecursive(this.blockScopeBody);
+    public analyze (blockScopeBody: ESTree.Node[]): IStackTraceData[] {
+        return this.analyzeRecursive(blockScopeBody);
     }
 
     /**
@@ -122,7 +111,7 @@ export class StackTraceAnalyzer implements IStackTraceAnalyzer {
             index < blockScopeBodyLength;
             index++
         ) {
-            let rootNode: ESTree.Node = blockScopeBody[index];
+            const rootNode: ESTree.Node = blockScopeBody[index];
 
             if (index > limitIndex) {
                 break;

+ 52 - 0
src/storages/ArrayStorage.ts

@@ -0,0 +1,52 @@
+import { IStorage } from '../interfaces/IStorage';
+
+export abstract class ArrayStorage <T> implements IStorage <T> {
+    /**
+     * @type {T[]}
+     */
+    protected storage: T[] = [];
+
+    /**
+     * @param key
+     * @returns {T}
+     */
+    public get (key: number): T {
+        const value: T | undefined = this.storage[key];
+
+        if (!value) {
+            throw new Error(`No value found in array storage with key \`${key}\``);
+        }
+
+        return value;
+    }
+
+    /**
+     * @param value
+     * @returns {string | number}
+     */
+    public getKeyOf (value: T): string | number {
+        return this.storage.indexOf(value);
+    }
+
+    /**
+     * @returns {number}
+     */
+    public getLength (): number {
+        return this.storage.length;
+    }
+
+    /**
+     * @returns {T[]}
+     */
+    public getStorage (): T[] {
+        return this.storage;
+    }
+
+    /**
+     * @param key
+     * @param value
+     */
+    public set (key: string | null, value: T): void {
+        this.storage.push(value);
+    }
+}

+ 54 - 0
src/storages/MapStorage.ts

@@ -0,0 +1,54 @@
+import { IStorage } from '../interfaces/IStorage';
+
+import { Utils } from '../Utils';
+
+export abstract class MapStorage <T> implements IStorage <T> {
+    /**
+     * @type {Map <string | number, T>}
+     */
+    protected storage: Map <string | number, T> = new Map <string | number, T> ();
+
+    /**
+     * @param key
+     * @returns {T}
+     */
+    public get (key: string | number): T {
+        const value: T | undefined = this.storage.get(key);
+
+        if (!value) {
+            throw new Error(`No value found in map storage with key \`${key}\``);
+        }
+
+        return value;
+    }
+
+    /**
+     * @param value
+     * @returns {string | number | null}
+     */
+    public getKeyOf (value: T): string | number | null {
+        return Utils.mapGetFirstKeyOf(this.storage, value);
+    }
+
+    /**
+     * @returns {number}
+     */
+    public getLength (): number {
+        return Array.from(this.storage).length;
+    }
+
+    /**
+     * @returns {Map <string | number, T>}
+     */
+    public getStorage (): Map <string | number, T> {
+        return this.storage;
+    }
+
+    /**
+     * @param key
+     * @param value
+     */
+    public set (key: string | number, value: T): void {
+        this.storage.set(key, value);
+    }
+}

+ 19 - 0
src/storages/control-flow/ControlFlowStorage.ts

@@ -0,0 +1,19 @@
+import { ICustomNode } from '../../interfaces/custom-nodes/ICustomNode';
+
+import { MapStorage } from '../MapStorage';
+
+export class ControlFlowStorage extends MapStorage <ICustomNode> {
+    /**
+     * @returns {string}
+     */
+    public toString (): string {
+        return Array
+            .from(this.storage)
+            .reduce((controlFlowStorageItems: string[], [key, value]: [string, ICustomNode]) => {
+                controlFlowStorageItems.push(`${key}: ${value.getCode()}`);
+
+                return controlFlowStorageItems;
+            }, [])
+            .join(',');
+    }
+}

+ 20 - 0
src/storages/string-array/StringArrayStorage.ts

@@ -0,0 +1,20 @@
+import { ArrayStorage } from '../ArrayStorage';
+import { Utils } from '../../Utils';
+
+export class StringArrayStorage extends ArrayStorage <string> {
+    /**
+     * @param rotationValue
+     */
+    public rotateArray (rotationValue: number): void {
+        this.storage = Utils.arrayRotate(this.storage, rotationValue);
+    }
+
+    /**
+     * @returns {string}
+     */
+    public toString (): string {
+        return this.storage.map((value: string) => {
+            return `'${value}'`;
+        }).toString();
+    }
+}

+ 10 - 0
src/templates/custom-nodes/control-flow-replacers-nodes/binary-expression-control-flow-replacer-nodes/BinaryExpressionFunctionTemplate.ts

@@ -0,0 +1,10 @@
+/**
+ * @returns {string}
+ */
+export function BinaryExpressionFunctionTemplate (): string {
+    return `
+        function {functionName} (x, y) {
+            return x {operator} y;
+        }
+    `;
+}

+ 6 - 0
src/templates/custom-nodes/control-flow-replacers-nodes/binary-expression-control-flow-replacer-nodes/ControlFlowStorageCallTemplate.ts

@@ -0,0 +1,6 @@
+/**
+ * @returns {string}
+ */
+export function ControlFlowStorageCallTemplate (): string {
+    return '{controlFlowStorageName}.{controlFlowStorageKey}({leftValue}, {rightValue})';
+}

+ 8 - 0
src/templates/custom-nodes/control-flow-storage-nodes/ControlFlowStorageTemplate.ts

@@ -0,0 +1,8 @@
+/**
+ * @returns {string}
+ */
+export function ControlFlowStorageTemplate (): string {
+    return `
+        var {controlFlowStorageName} = { {controlFlowStorage} };
+    `;
+}

+ 1 - 3
src/templates/custom-nodes/debug-protection-nodes/debug-protection-function-node/DebugProtectionFunctionTemplate.ts

@@ -1,5 +1,3 @@
-import { Utils } from '../../../../Utils';
-
 /**
  * @returns {string}
  */
@@ -10,7 +8,7 @@ export function DebugProtectionFunctionTemplate (): string {
                 if (('' + counter / counter)['length'] !== 1 || counter % 20 === 0) {
                     (function () {}.constructor('debugger')());
                 } else {
-                    [].filter.constructor(${Utils.stringToJSFuck('debugger')})();
+                    (function () {}.constructor('debugger')());
                 }
                 
                 debuggerProtection(++counter);

+ 5 - 0
src/types/TControlFlowReplacer.d.ts

@@ -0,0 +1,5 @@
+import { IControlFlowReplacer } from '../interfaces/IControlFlowReplacer';
+import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
+import { IOptions } from '../interfaces/IOptions';
+
+export type TControlFlowReplacer = (new (nodes: Map <string, ICustomNode>, options: IOptions) => IControlFlowReplacer);

+ 0 - 5
src/types/TNodeObfuscator.d.ts

@@ -1,5 +0,0 @@
-import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
-import { INodeObfuscator } from '../interfaces/INodeObfuscator';
-import { IOptions } from '../interfaces/IOptions';
-
-export type TNodeObfuscator = (new (nodes: Map <string, ICustomNode>, options: IOptions) => INodeObfuscator);

+ 5 - 0
src/types/TNodeTransformer.d.ts

@@ -0,0 +1,5 @@
+import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
+import { INodeTransformer } from '../interfaces/INodeTransformer';
+import { IOptions } from '../interfaces/IOptions';
+
+export type TNodeTransformer = (new (nodes: Map <string, ICustomNode>, options: IOptions) => INodeTransformer);

+ 1 - 0
src/types/TVisitorDirection.d.ts

@@ -0,0 +1 @@
+export type TVisitorDirection = 'enter' | 'leave';

+ 0 - 3
src/types/custom-nodes/TStringArrayCallsWrapper.d.ts

@@ -1,3 +0,0 @@
-import { ICustomNodeWithIdentifier } from '../../interfaces/custom-nodes/ICustomNodeWithIdentifier';
-
-export type TStringArrayCallsWrapper = ICustomNodeWithIdentifier;

+ 0 - 4
src/types/custom-nodes/TStringArrayNode.d.ts

@@ -1,4 +0,0 @@
-import { ICustomNodeWithData } from '../../interfaces/custom-nodes/ICustomNodeWithData';
-import { ICustomNodeWithIdentifier } from '../../interfaces/custom-nodes/ICustomNodeWithIdentifier';
-
-export type TStringArrayNode = ICustomNodeWithData & ICustomNodeWithIdentifier;

+ 13 - 63
test/dev/dev.ts

@@ -1,4 +1,5 @@
 'use strict';
+import { NO_CUSTOM_NODES_PRESET } from '../../src/preset-options/NoCustomNodesPreset';
 
 if (!(<any>global)._babelPolyfill) {
     require('babel-polyfill');
@@ -7,72 +8,21 @@ if (!(<any>global)._babelPolyfill) {
 (function () {
     const JavaScriptObfuscator: any = require("../../index");
 
-    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);
+    let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(`
+        (function(){
+            function t () {
+                return function () {
+                    var t = 1 * 2;
+                }
             }
             
-            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);
-        }
-    })();
+            var s = 1 - 3;
+        })();
     `,
-        {
-            disableConsoleOutput: false,
-            selfDefending: true,
-            stringArrayEncoding: 'rc4'
-        }
+        Object.assign({}, NO_CUSTOM_NODES_PRESET,  {
+            controlFlowFlattening: true,
+            disableConsoleOutput: false
+        })
     ).getObfuscatedCode();
 
     console.log(obfuscatedCode);

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

@@ -6371,7 +6371,7 @@
     /* 42 */
     /***/ function(module, exports, __webpack_require__) {
 
-// to indexed object, toObject with fallback for non-array-like ES3 strings
+// to indexed object, toString with fallback for non-array-like ES3 strings
         var IObject = __webpack_require__(118)
             , defined = __webpack_require__(59);
         module.exports = function(it){

+ 0 - 0
test/fixtures/node-obfuscators/catch-clause-obfuscator/catch-clause-obfuscator.js → test/fixtures/node-transformers/node-obfuscators/catch-clause-obfuscator/catch-clause-obfuscator.js


+ 0 - 0
test/fixtures/node-obfuscators/labeled-statement-obfuscator/labeled-statement-obfuscator.js → test/fixtures/node-transformers/node-obfuscators/labeled-statement-obfuscator/labeled-statement-obfuscator.js


+ 36 - 0
test/functional-tests/JavaScriptObfuscator.spec.ts

@@ -3,6 +3,7 @@ import { IObfuscationResult } from '../../src/interfaces/IObfuscationResult';
 import { JavaScriptObfuscator } from '../../src/JavaScriptObfuscator';
 
 import { NO_CUSTOM_NODES_PRESET } from '../../src/preset-options/NoCustomNodesPreset';
+import { readFileAsString } from '../helpers/readFileAsString';
 
 const assert: Chai.AssertStatic = require('chai').assert;
 
@@ -125,5 +126,40 @@ describe('JavaScriptObfuscator', () => {
             assert.match(obfuscatedCode2, pattern1);
             assert.match(obfuscatedCode2, pattern2);
         });
+
+        it('should returns same code every time with same `seed`', () => {
+            const code: string = readFileAsString('./test/fixtures/sample.js');
+            const seed: number = 12345;
+
+            const obfuscationResult1: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code, { seed: seed }
+            );
+            const obfuscationResult2: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code, { seed: seed }
+            );
+
+            assert.equal(obfuscationResult1.getObfuscatedCode(), obfuscationResult2.getObfuscatedCode());
+        });
+
+        it('should returns different code with different `seed` option value', () => {
+            const code: string = readFileAsString('./test/fixtures/sample.js');
+
+            const obfuscationResult1: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code, { seed: 12345 }
+            );
+            const obfuscationResult2: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code, { seed: 12346 }
+            );
+
+            const obfuscationResult3: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code, { seed: 0 }
+            );
+            const obfuscationResult4: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code, { seed: 0 }
+            );
+
+            assert.notEqual(obfuscationResult1.getObfuscatedCode(), obfuscationResult2.getObfuscatedCode());
+            assert.notEqual(obfuscationResult3.getObfuscatedCode(), obfuscationResult4.getObfuscatedCode());
+        });
     });
 });

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

@@ -4,6 +4,8 @@ import { JavaScriptObfuscatorInternal } from '../../src/JavaScriptObfuscatorInte
 
 import { NO_CUSTOM_NODES_PRESET } from '../../src/preset-options/NoCustomNodesPreset';
 
+import { Options } from '../../src/options/Options';
+
 const assert: Chai.AssertStatic = require('chai').assert;
 
 describe('JavaScriptObfuscatorInternal', () => {
@@ -14,16 +16,15 @@ describe('JavaScriptObfuscatorInternal', () => {
 
         it('should link obfuscated code with source map', () => {
             javaScriptObfuscator = new JavaScriptObfuscatorInternal(
-                `var test = 1;`,
-                Object.assign({}, NO_CUSTOM_NODES_PRESET, {
-                    sourceMap: true,
-                    sourceMapFileName: sourceMapUrl
-                })
+                new Options(
+                    Object.assign({}, NO_CUSTOM_NODES_PRESET, {
+                        sourceMap: true,
+                        sourceMapFileName: sourceMapUrl
+                    })
+                )
             );
 
-            javaScriptObfuscator.obfuscate();
-
-            obfuscationResult = javaScriptObfuscator.getObfuscationResult();
+            obfuscationResult = javaScriptObfuscator.obfuscate('var test = 1;');
 
             assert.match(
                 obfuscationResult.getObfuscatedCode(),
@@ -36,17 +37,16 @@ describe('JavaScriptObfuscatorInternal', () => {
             let sourceMapBaseUrl: string = 'http://localhost:9000';
 
             javaScriptObfuscator = new JavaScriptObfuscatorInternal(
-                `var test = 1;`,
-                Object.assign({}, NO_CUSTOM_NODES_PRESET, {
-                    sourceMap: true,
-                    sourceMapBaseUrl: sourceMapBaseUrl,
-                    sourceMapFileName: sourceMapUrl
-                })
+                new Options(
+                    Object.assign({}, NO_CUSTOM_NODES_PRESET, {
+                        sourceMap: true,
+                        sourceMapBaseUrl: sourceMapBaseUrl,
+                        sourceMapFileName: sourceMapUrl
+                    })
+                )
             );
 
-            javaScriptObfuscator.obfuscate();
-
-            obfuscationResult = javaScriptObfuscator.getObfuscationResult();
+            obfuscationResult = javaScriptObfuscator.obfuscate('var test = 1;');
 
             assert.match(
                 obfuscationResult.getObfuscatedCode(),

+ 8 - 6
test/functional-tests/node-obfuscators/CatchClauseObfuscator.spec.ts → test/functional-tests/node-transformers/node-obfuscators/CatchClauseObfuscator.spec.ts

@@ -1,17 +1,19 @@
-import { IObfuscationResult } from '../../../src/interfaces/IObfuscationResult';
+import { IObfuscationResult } from '../../../../src/interfaces/IObfuscationResult';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../../src/preset-options/NoCustomNodesPreset';
+import { NO_CUSTOM_NODES_PRESET } from '../../../../src/preset-options/NoCustomNodesPreset';
 
-import { readFileAsString } from '../../helpers/readFileAsString';
+import { readFileAsString } from '../../../helpers/readFileAsString';
 
-import { JavaScriptObfuscator } from '../../../src/JavaScriptObfuscator';
+import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
 
 const assert: Chai.AssertStatic = require('chai').assert;
 
 describe('CatchClauseObfuscator', () => {
-    describe('obfuscateNode (catchClauseNode: ESTree.CatchClause): void', () => {
+    describe('changeControlFlow (catchClauseNode: ESTree.CatchClause): void', () => {
         const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString('./test/fixtures/node-obfuscators/catch-clause-obfuscator/catch-clause-obfuscator.js'),
+            readFileAsString(
+                './test/fixtures/node-transformers/node-obfuscators/catch-clause-obfuscator/catch-clause-obfuscator.js'
+            ),
             Object.assign({}, NO_CUSTOM_NODES_PRESET)
         );
         const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();

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

@@ -1,8 +1,8 @@
-import { IObfuscationResult } from '../../../src/interfaces/IObfuscationResult';
+import { IObfuscationResult } from '../../../../src/interfaces/IObfuscationResult';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../../src/preset-options/NoCustomNodesPreset';
+import { NO_CUSTOM_NODES_PRESET } from '../../../../src/preset-options/NoCustomNodesPreset';
 
-import { JavaScriptObfuscator } from '../../../src/JavaScriptObfuscator';
+import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
 
 const assert: Chai.AssertStatic = require('chai').assert;
 

+ 8 - 6
test/functional-tests/node-obfuscators/LabeledStatementObfuscator.spec.ts → test/functional-tests/node-transformers/node-obfuscators/LabeledStatementObfuscator.spec.ts

@@ -1,17 +1,19 @@
-import { IObfuscationResult } from '../../../src/interfaces/IObfuscationResult';
+import { IObfuscationResult } from '../../../../src/interfaces/IObfuscationResult';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../../src/preset-options/NoCustomNodesPreset';
+import { NO_CUSTOM_NODES_PRESET } from '../../../../src/preset-options/NoCustomNodesPreset';
 
-import { readFileAsString } from '../../helpers/readFileAsString';
+import { readFileAsString } from '../../../helpers/readFileAsString';
 
-import { JavaScriptObfuscator } from '../../../src/JavaScriptObfuscator';
+import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
 
 const assert: Chai.AssertStatic = require('chai').assert;
 
 describe('LabeledStatementObfuscator', () => {
-    describe('obfuscateNode (labeledStatementNode: ESTree.LabeledStatement): void', () => {
+    describe('changeControlFlow (labeledStatementNode: ESTree.LabeledStatement): void', () => {
         const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
-            readFileAsString('./test/fixtures/node-obfuscators/labeled-statement-obfuscator/labeled-statement-obfuscator.js'),
+            readFileAsString(
+                './test/fixtures/node-transformers/node-obfuscators/labeled-statement-obfuscator/labeled-statement-obfuscator.js'
+            ),
             Object.assign({}, NO_CUSTOM_NODES_PRESET)
         );
         const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();

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

@@ -1,8 +1,8 @@
-import { IObfuscationResult } from '../../../src/interfaces/IObfuscationResult';
+import { IObfuscationResult } from '../../../../src/interfaces/IObfuscationResult';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../../src/preset-options/NoCustomNodesPreset';
+import { NO_CUSTOM_NODES_PRESET } from '../../../../src/preset-options/NoCustomNodesPreset';
 
-import { JavaScriptObfuscator } from '../../../src/JavaScriptObfuscator';
+import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
 
 const assert: Chai.AssertStatic = require('chai').assert;
 

+ 3 - 3
test/functional-tests/node-obfuscators/MemberExpressionObfuscator.spec.ts → test/functional-tests/node-transformers/node-obfuscators/MemberExpressionObfuscator.spec.ts

@@ -1,8 +1,8 @@
-import { IObfuscationResult } from '../../../src/interfaces/IObfuscationResult';
+import { IObfuscationResult } from '../../../../src/interfaces/IObfuscationResult';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../../src/preset-options/NoCustomNodesPreset';
+import { NO_CUSTOM_NODES_PRESET } from '../../../../src/preset-options/NoCustomNodesPreset';
 
-import { JavaScriptObfuscator } from '../../../src/JavaScriptObfuscator';
+import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
 
 const assert: Chai.AssertStatic = require('chai').assert;
 

+ 3 - 3
test/functional-tests/node-obfuscators/MethodDefinitionObfuscator.spec.ts → test/functional-tests/node-transformers/node-obfuscators/MethodDefinitionObfuscator.spec.ts

@@ -1,8 +1,8 @@
-import { IObfuscationResult } from '../../../src/interfaces/IObfuscationResult';
+import { IObfuscationResult } from '../../../../src/interfaces/IObfuscationResult';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../../src/preset-options/NoCustomNodesPreset';
+import { NO_CUSTOM_NODES_PRESET } from '../../../../src/preset-options/NoCustomNodesPreset';
 
-import { JavaScriptObfuscator } from '../../../src/JavaScriptObfuscator';
+import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
 
 const assert: Chai.AssertStatic = require('chai').assert;
 

+ 3 - 3
test/functional-tests/node-obfuscators/ObjectExpressionObfuscator.spec.ts → test/functional-tests/node-transformers/node-obfuscators/ObjectExpressionObfuscator.spec.ts

@@ -1,8 +1,8 @@
-import { IObfuscationResult } from '../../../src/interfaces/IObfuscationResult';
+import { IObfuscationResult } from '../../../../src/interfaces/IObfuscationResult';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../../src/preset-options/NoCustomNodesPreset';
+import { NO_CUSTOM_NODES_PRESET } from '../../../../src/preset-options/NoCustomNodesPreset';
 
-import { JavaScriptObfuscator } from '../../../src/JavaScriptObfuscator';
+import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
 
 const assert: Chai.AssertStatic = require('chai').assert;
 

+ 3 - 3
test/functional-tests/node-obfuscators/VariableDeclarationObfuscator.spec.ts → test/functional-tests/node-transformers/node-obfuscators/VariableDeclarationObfuscator.spec.ts

@@ -1,8 +1,8 @@
-import { IObfuscationResult } from '../../../src/interfaces/IObfuscationResult';
+import { IObfuscationResult } from '../../../../src/interfaces/IObfuscationResult';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../../src/preset-options/NoCustomNodesPreset';
+import { NO_CUSTOM_NODES_PRESET } from '../../../../src/preset-options/NoCustomNodesPreset';
 
-import { JavaScriptObfuscator } from '../../../src/JavaScriptObfuscator';
+import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
 
 const assert: Chai.AssertStatic = require('chai').assert;
 

+ 13 - 10
test/functional-tests/stack-trace-analyzer/StackTraceAnalyzer.spec.ts

@@ -4,6 +4,7 @@ import * as ESTree from 'estree';
 
 import { TNodeWithBlockStatement } from '../../../src/types/TNodeWithBlockStatement';
 
+import { IStackTraceAnalyzer } from '../../../src/interfaces/stack-trace-analyzer/IStackTraceAnalyzer';
 import { IStackTraceData } from '../../../src/interfaces/stack-trace-analyzer/IStackTraceData';
 
 import { readFileAsString } from '../../helpers/readFileAsString';
@@ -145,6 +146,8 @@ function getObjectFunctionExpressionByName (astTree: ESTree.Node, objectName: st
 
 describe('StackTraceAnalyzer', () => {
     describe('extract (): IStackTraceData[]', () => {
+        const stackTraceAnalyzer: IStackTraceAnalyzer = new StackTraceAnalyzer();
+
         let astTree: TNodeWithBlockStatement,
             stackTraceData: IStackTraceData[],
             expectedStackTraceData: IStackTraceData[];
@@ -191,7 +194,7 @@ describe('StackTraceAnalyzer', () => {
                 }
             ];
 
-            stackTraceData = new StackTraceAnalyzer(astTree.body).analyze();
+            stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
 
             assert.deepEqual(stackTraceData, expectedStackTraceData);
         });
@@ -227,7 +230,7 @@ describe('StackTraceAnalyzer', () => {
                 }
             ];
 
-            stackTraceData = new StackTraceAnalyzer(astTree.body).analyze();
+            stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
 
             assert.deepEqual(stackTraceData, expectedStackTraceData);
         });
@@ -263,7 +266,7 @@ describe('StackTraceAnalyzer', () => {
                 }
             ];
 
-            stackTraceData = new StackTraceAnalyzer(astTree.body).analyze();
+            stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
 
             assert.deepEqual(stackTraceData, expectedStackTraceData);
         });
@@ -283,7 +286,7 @@ describe('StackTraceAnalyzer', () => {
                 }
             ];
 
-            stackTraceData = new StackTraceAnalyzer(astTree.body).analyze();
+            stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
 
             assert.deepEqual(stackTraceData, expectedStackTraceData);
         });
@@ -339,7 +342,7 @@ describe('StackTraceAnalyzer', () => {
                 }
             ];
 
-            stackTraceData = new StackTraceAnalyzer(astTree.body).analyze();
+            stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
 
             assert.deepEqual(stackTraceData, expectedStackTraceData);
         });
@@ -364,7 +367,7 @@ describe('StackTraceAnalyzer', () => {
                 },
             ];
 
-            stackTraceData = new StackTraceAnalyzer(astTree.body).analyze();
+            stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
 
             assert.deepEqual(stackTraceData, expectedStackTraceData);
         });
@@ -378,7 +381,7 @@ describe('StackTraceAnalyzer', () => {
 
             expectedStackTraceData = [];
 
-            stackTraceData = new StackTraceAnalyzer(astTree.body).analyze();
+            stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
 
             assert.deepEqual(stackTraceData, expectedStackTraceData);
         });
@@ -392,7 +395,7 @@ describe('StackTraceAnalyzer', () => {
 
             expectedStackTraceData = [];
 
-            stackTraceData = new StackTraceAnalyzer(astTree.body).analyze();
+            stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
 
             assert.deepEqual(stackTraceData, expectedStackTraceData);
         });
@@ -424,7 +427,7 @@ describe('StackTraceAnalyzer', () => {
                 }
             ];
 
-            stackTraceData = new StackTraceAnalyzer(astTree.body).analyze();
+            stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
 
             assert.deepEqual(stackTraceData, expectedStackTraceData);
         });
@@ -444,7 +447,7 @@ describe('StackTraceAnalyzer', () => {
                 }
             ];
 
-            stackTraceData = new StackTraceAnalyzer(astTree.body).analyze();
+            stackTraceData = stackTraceAnalyzer.analyze(astTree.body);
 
             assert.deepEqual(stackTraceData, expectedStackTraceData);
         });

+ 2 - 2
test/functional-tests/templates/custom-nodes/domain-lock-nodes/DomainLockNodeTemplate.spec.ts

@@ -1,4 +1,4 @@
-import 'format-unicorn';
+import * as format from 'string-template';
 
 import { DomainLockNodeTemplate } from '../../../../../src/templates/custom-nodes/domain-lock-nodes/domain-lock-node/DomainLockNodeTemplate';
 
@@ -14,7 +14,7 @@ const assert: Chai.AssertStatic = require('chai').assert;
  * @returns {Function}
  */
 function getFunctionFromTemplate (templateData: any, callsControllerFunctionName: string,  currentDomain: string) {
-    let domainLockTemplate: string = DomainLockNodeTemplate().formatUnicorn(templateData);
+    let domainLockTemplate: string = format(DomainLockNodeTemplate(), templateData);
 
     return Function(`
         document = {

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.