瀏覽代碼

custom nodes structure rework

sanex3339 8 年之前
父節點
當前提交
d184298208
共有 35 個文件被更改,包括 1102 次插入701 次删除
  1. 322 240
      dist/index.js
  2. 177 0
      src/NodeAppender.ts
  3. 0 88
      src/NodeUtils.ts
  4. 39 23
      src/Obfuscator.ts
  5. 2 5
      src/custom-nodes/AbstractCustomNode.ts
  6. 0 82
      src/custom-nodes/CustomNodeAppender.ts
  7. 40 7
      src/custom-nodes/console-output-nodes/ConsoleOutputDisableExpressionNode.ts
  8. 2 1
      src/custom-nodes/debug-protection-nodes/DebugProtectionFunctionCallNode.ts
  9. 2 1
      src/custom-nodes/debug-protection-nodes/DebugProtectionFunctionIntervalNode.ts
  10. 2 1
      src/custom-nodes/debug-protection-nodes/DebugProtectionFunctionNode.ts
  11. 42 7
      src/custom-nodes/domain-lock-nodes/DomainLockNode.ts
  12. 83 0
      src/custom-nodes/node-calls-controller-nodes/NodeCallsControllerFunctionNode.ts
  13. 40 6
      src/custom-nodes/self-defending-nodes/SelfDefendingUnicodeNode.ts
  14. 2 1
      src/custom-nodes/unicode-array-nodes/UnicodeArrayCallsWrapper.ts
  15. 2 1
      src/custom-nodes/unicode-array-nodes/UnicodeArrayNode.ts
  16. 2 1
      src/custom-nodes/unicode-array-nodes/UnicodeArrayRotateFunctionNode.ts
  17. 1 4
      src/interfaces/custom-nodes/ICustomNode.d.ts
  18. 13 2
      src/node-groups/AbstractNodesGroup.ts
  19. 24 3
      src/node-groups/ConsoleOutputNodesGroup.ts
  20. 4 2
      src/node-groups/DebugProtectionNodesGroup.ts
  21. 24 3
      src/node-groups/DomainLockNodesGroup.ts
  22. 24 3
      src/node-groups/SelfDefendingNodesGroup.ts
  23. 4 2
      src/node-groups/UnicodeArrayNodesGroup.ts
  24. 24 0
      src/templates/custom-nodes/SingleNodeCallControllerTemplate.ts
  25. 50 53
      src/templates/custom-nodes/domain-lock-nodes/domain-lock-node/DomainLockNodeTemplate.ts
  26. 2 1
      test/dev/dev.ts
  27. 2 2
      test/functional-tests/custom-nodes/console-output-nodes/ConsoleOutputDisableExpressionNode.spec.ts
  28. 2 2
      test/functional-tests/custom-nodes/domain-lock-nodes/DomainLockNode.spec.ts
  29. 1 1
      test/functional-tests/custom-nodes/unicode-array-nodes/UnicodeArrayCallsWrapper.spec.ts
  30. 2 2
      test/functional-tests/custom-nodes/unicode-array-nodes/UnicodeArrayNode.spec.ts
  31. 2 2
      test/functional-tests/custom-nodes/unicode-array-nodes/UnicodeArrayRotateFunctionNode.spec.ts
  32. 25 12
      test/functional-tests/templates/custom-nodes/domain-lock-nodes/DomainLockNodeTemplate.spec.ts
  33. 1 1
      test/index.spec.ts
  34. 0 132
      test/unit-tests/NodeUtils.spec.ts
  35. 140 10
      test/unit-tests/custom-nodes/NodeAppender.spec.ts

File diff suppressed because it is too large
+ 322 - 240
dist/index.js


+ 177 - 0
src/NodeAppender.ts

@@ -0,0 +1,177 @@
+import * as ESTree from 'estree';
+
+import { TNodeWithBlockStatement } from './types/TNodeWithBlockStatement';
+import { TStatement } from './types/TStatement';
+
+import { IStackTraceData } from './interfaces/stack-trace-analyzer/IStackTraceData';
+
+import { Utils } from './Utils';
+
+/**
+ * This class appends node into a first deepest BlockStatement in order of function calls
+ *
+ * For example:
+ *
+ * function Foo () {
+ *     var baz = function () {
+ *
+ *     }
+ *
+ *     baz();
+ * }
+ *
+ * foo();
+ *
+ * Appends node into block statement of `baz` function expression.
+ */
+export class NodeAppender {
+    /**
+     * @param blockScopeNode
+     * @param nodeBodyStatements
+     */
+    public static appendNode (
+        blockScopeNode: TNodeWithBlockStatement,
+        nodeBodyStatements: TStatement[]
+    ): void {
+        if (!NodeAppender.validateBodyStatements(nodeBodyStatements)) {
+            nodeBodyStatements = [];
+        }
+
+        nodeBodyStatements = NodeAppender.parentizeBodyStatementsBeforeAppend(blockScopeNode, nodeBodyStatements);
+
+        blockScopeNode.body = [
+            ...blockScopeNode.body,
+            ...nodeBodyStatements
+        ];
+    }
+
+    /**
+     * @param blockScopeStackTraceData
+     * @param blockScopeNode
+     * @param nodeBodyStatements
+     * @param index
+     */
+    public static appendNodeToOptimalBlockScope (
+        blockScopeStackTraceData: IStackTraceData[],
+        blockScopeNode: TNodeWithBlockStatement,
+        nodeBodyStatements: TStatement[],
+        index: number = 0
+    ): void {
+        let targetBlockScope: TNodeWithBlockStatement;
+
+        if (!blockScopeStackTraceData.length) {
+            targetBlockScope = blockScopeNode;
+        } else {
+            targetBlockScope = NodeAppender.getOptimalBlockScope(
+                blockScopeStackTraceData,
+                index
+            );
+        }
+
+        NodeAppender.prependNode(targetBlockScope, nodeBodyStatements);
+    }
+
+    /**
+     * Returns deepest block scope node at given deep.
+     *
+     * @param blockScopeTraceData
+     * @param index
+     * @param deep
+     * @returns {ESTree.BlockStatement}
+     */
+    public static getOptimalBlockScope (
+        blockScopeTraceData: IStackTraceData[],
+        index: number,
+        deep: number = Infinity
+    ): ESTree.BlockStatement {
+        const firstCall: IStackTraceData = blockScopeTraceData[index];
+
+        if (deep <= 0) {
+            throw new Error(`Invalid \`deep\` argument value. Value should be bigger then 0.`);
+        }
+
+        if (deep > 1 && firstCall.stackTrace.length) {
+            return NodeAppender.getOptimalBlockScope(firstCall.stackTrace, 0, --deep);
+        } else {
+            return firstCall.callee;
+        }
+    }
+
+    /**
+     * @param stackTraceRootLength
+     */
+    public static getRandomStackTraceIndex (stackTraceRootLength: number): number {
+        return Utils.getRandomGenerator().integer({
+            min: 0,
+            max: Math.max(0, Math.round(stackTraceRootLength - 1))
+        });
+    }
+
+    /**
+     * @param blockScopeNode
+     * @param nodeBodyStatements
+     * @param index
+     */
+    public static insertNodeAtIndex (
+        blockScopeNode: TNodeWithBlockStatement,
+        nodeBodyStatements: TStatement[],
+        index: number
+    ): void {
+        if (!NodeAppender.validateBodyStatements(nodeBodyStatements)) {
+            nodeBodyStatements = [];
+        }
+
+        nodeBodyStatements = NodeAppender.parentizeBodyStatementsBeforeAppend(blockScopeNode, nodeBodyStatements);
+
+        blockScopeNode.body = [
+            ...blockScopeNode.body.slice(0, index),
+            ...nodeBodyStatements,
+            ...blockScopeNode.body.slice(index)
+        ];
+    }
+
+    /**
+     * @param blockScopeNode
+     * @param nodeBodyStatements
+     */
+    public static prependNode (
+        blockScopeNode: TNodeWithBlockStatement,
+        nodeBodyStatements: TStatement[]
+    ): void {
+        if (!NodeAppender.validateBodyStatements(nodeBodyStatements)) {
+            nodeBodyStatements = [];
+        }
+
+        nodeBodyStatements = NodeAppender.parentizeBodyStatementsBeforeAppend(blockScopeNode, nodeBodyStatements);
+
+        blockScopeNode.body = [
+            ...nodeBodyStatements,
+            ...blockScopeNode.body,
+        ];
+    }
+
+    /**
+     * @param blockScopeNode
+     * @param nodeBodyStatements
+     */
+    private static parentizeBodyStatementsBeforeAppend (
+        blockScopeNode: TNodeWithBlockStatement,
+        nodeBodyStatements: TStatement[]
+    ): TStatement[] {
+        for (let statement of nodeBodyStatements) {
+            statement.parentNode = blockScopeNode;
+        }
+
+        return nodeBodyStatements;
+    }
+
+    /**
+     * @param nodeBodyStatements
+     * @returns {boolean}
+     */
+    private static validateBodyStatements (nodeBodyStatements: TStatement[]): boolean {
+        return nodeBodyStatements.every(statementNode => {
+            return !!statementNode && statementNode.hasOwnProperty('type');
+        });
+    }
+}

+ 0 - 88
src/NodeUtils.ts

@@ -37,26 +37,6 @@ export class NodeUtils {
         });
     }
 
-    /**
-     * @param blockScopeNode
-     * @param nodeBodyStatements
-     */
-    public static appendNode (
-        blockScopeNode: TNodeWithBlockStatement,
-        nodeBodyStatements: TStatement[]
-    ): void {
-        if (!NodeUtils.validateBodyStatements(nodeBodyStatements)) {
-            nodeBodyStatements = [];
-        }
-
-        nodeBodyStatements = NodeUtils.parentizeBodyStatementsBeforeAppend(blockScopeNode, nodeBodyStatements);
-
-        blockScopeNode.body = [
-            ...blockScopeNode.body,
-            ...nodeBodyStatements
-        ];
-    }
-
     /**
      * @param code
      * @returns {TStatement[]}
@@ -120,29 +100,6 @@ export class NodeUtils {
         return NodeUtils.getBlockScopeOfNode(parentNode);
     }
 
-    /**
-     * @param blockScopeNode
-     * @param nodeBodyStatements
-     * @param index
-     */
-    public static insertNodeAtIndex (
-        blockScopeNode: TNodeWithBlockStatement,
-        nodeBodyStatements: TStatement[],
-        index: number
-    ): void {
-        if (!NodeUtils.validateBodyStatements(nodeBodyStatements)) {
-            nodeBodyStatements = [];
-        }
-
-        nodeBodyStatements = NodeUtils.parentizeBodyStatementsBeforeAppend(blockScopeNode, nodeBodyStatements);
-
-        blockScopeNode.body = [
-            ...blockScopeNode.body.slice(0, index),
-            ...nodeBodyStatements,
-            ...blockScopeNode.body.slice(index)
-        ];
-    }
-
     /**
      * @param node
      */
@@ -172,26 +129,6 @@ export class NodeUtils {
         });
     }
 
-    /**
-     * @param blockScopeNode
-     * @param nodeBodyStatements
-     */
-    public static prependNode (
-        blockScopeNode: TNodeWithBlockStatement,
-        nodeBodyStatements: TStatement[]
-    ): void {
-        if (!NodeUtils.validateBodyStatements(nodeBodyStatements)) {
-            nodeBodyStatements = [];
-        }
-
-        nodeBodyStatements = NodeUtils.parentizeBodyStatementsBeforeAppend(blockScopeNode, nodeBodyStatements);
-
-        blockScopeNode.body = [
-            ...nodeBodyStatements,
-            ...blockScopeNode.body,
-        ];
-    }
-
     /**
      * @param node
      * @param nodeType
@@ -230,29 +167,4 @@ export class NodeUtils {
             }
         });
     }
-
-    /**
-     * @param blockScopeNode
-     * @param nodeBodyStatements
-     */
-    private static parentizeBodyStatementsBeforeAppend (
-        blockScopeNode: TNodeWithBlockStatement,
-        nodeBodyStatements: TStatement[]
-    ): TStatement[] {
-        for (let statement of nodeBodyStatements) {
-            statement.parentNode = blockScopeNode;
-        }
-
-        return nodeBodyStatements;
-    }
-
-    /**
-     * @param nodeBodyStatements
-     * @returns {boolean}
-     */
-    private static validateBodyStatements (nodeBodyStatements: TStatement[]): boolean {
-        return nodeBodyStatements.every(statementNode => {
-            return !!statementNode && statementNode.hasOwnProperty('type');
-        });
-    }
 }

+ 39 - 23
src/Obfuscator.ts

@@ -3,6 +3,7 @@ import * as ESTree from 'estree';
 
 import { ICustomNode } from './interfaces/custom-nodes/ICustomNode';
 import { IObfuscator } from './interfaces/IObfuscator';
+import { INodesGroup } from './interfaces/INodesGroup';
 import { IOptions } from './interfaces/IOptions';
 import { IStackTraceData } from './interfaces/stack-trace-analyzer/IStackTraceData';
 
@@ -30,14 +31,20 @@ import { StackTraceAnalyzer } from './stack-trace-analyzer/StackTraceAnalyzer';
 
 export class Obfuscator implements IObfuscator {
     /**
-     * @type {Map<string, AbstractCustomNode>}
+     * @type {(new (stackTraceData: IStackTraceData[], options: IOptions) => INodesGroup)[]}
      */
-    private nodes: Map <string, ICustomNode>;
+    private static nodeGroups: (new (stackTraceData: IStackTraceData[], options: IOptions) => INodesGroup)[] = [
+        DomainLockNodesGroup,
+        SelfDefendingNodesGroup,
+        ConsoleOutputNodesGroup,
+        DebugProtectionNodesGroup,
+        UnicodeArrayNodesGroup
+    ];
 
     /**
      * @type {Map<string, TNodeObfuscator[]>}
      */
-    private nodeObfuscators: Map <string, TNodeObfuscator[]> = new Map <string, TNodeObfuscator[]> ([
+    private static nodeObfuscators: Map <string, TNodeObfuscator[]> = new Map <string, TNodeObfuscator[]> ([
         [NodeType.ArrowFunctionExpression, [FunctionObfuscator]],
         [NodeType.ClassDeclaration, [FunctionDeclarationObfuscator]],
         [NodeType.CatchClause, [CatchClauseObfuscator]],
@@ -53,6 +60,11 @@ export class Obfuscator implements IObfuscator {
         [NodeType.Literal, [LiteralObfuscator]]
     ]);
 
+    /**
+     * @type {Map<string, AbstractCustomNode>}
+     */
+    private customNodes: Map <string, ICustomNode> = new Map <string, ICustomNode> ();
+
     /**
      * @type {IOptions}
      */
@@ -63,14 +75,6 @@ export class Obfuscator implements IObfuscator {
      */
     constructor (options: IOptions) {
         this.options = options;
-
-        this.nodes = new Map <string, ICustomNode> ([
-            ...new DomainLockNodesGroup(this.options).getNodes(),
-            ...new SelfDefendingNodesGroup(this.options).getNodes(),
-            ...new ConsoleOutputNodesGroup(this.options).getNodes(),
-            ...new DebugProtectionNodesGroup(this.options).getNodes(),
-            ...new UnicodeArrayNodesGroup(this.options).getNodes()
-        ]);
     }
 
     /**
@@ -86,51 +90,63 @@ export class Obfuscator implements IObfuscator {
 
         const stackTraceData: IStackTraceData[] = new StackTraceAnalyzer(node.body).analyze();
 
-        this.beforeObfuscation(node, stackTraceData);
+        this.initializeCustomNodes(stackTraceData);
+
+        this.beforeObfuscation(node);
         this.obfuscate(node);
-        this.afterObfuscation(node, stackTraceData);
+        this.afterObfuscation(node);
 
         return node;
     }
 
     /**
      * @param astTree
-     * @param stackTraceData
      */
-    private afterObfuscation (astTree: ESTree.Node, stackTraceData: IStackTraceData[]): void {
-        this.nodes.forEach((node: ICustomNode) => {
+    private afterObfuscation (astTree: ESTree.Node): void {
+        this.customNodes.forEach((node: ICustomNode) => {
             if (node.getAppendState() === AppendState.AfterObfuscation) {
-                node.appendNode(astTree, stackTraceData);
+                node.appendNode(astTree);
             }
         });
     }
 
     /**
      * @param astTree
-     * @param stackTraceData
      */
-    private beforeObfuscation (astTree: ESTree.Node, stackTraceData: IStackTraceData[]): void {
-        this.nodes.forEach((node: ICustomNode) => {
+    private beforeObfuscation (astTree: ESTree.Node): void {
+        this.customNodes.forEach((node: ICustomNode) => {
             if (node.getAppendState() === AppendState.BeforeObfuscation) {
-                node.appendNode(astTree, stackTraceData);
+                node.appendNode(astTree);
             }
         });
     };
 
+    /**
+     * @param stackTraceData
+     */
+    private initializeCustomNodes (stackTraceData: IStackTraceData[]): void {
+        Obfuscator.nodeGroups.map((nodeGroupConstructor) => {
+            this.customNodes = new Map <string, ICustomNode> ([
+                ...this.customNodes,
+                ...new nodeGroupConstructor(stackTraceData, this.options).getNodes()
+            ]);
+        });
+    }
+
 
     /**
      * @param node
      * @param parentNode
      */
     private initializeNodeObfuscators (node: ESTree.Node, parentNode: ESTree.Node): void {
-        let nodeObfuscators: TNodeObfuscator[] | undefined = this.nodeObfuscators.get(node.type);
+        let nodeObfuscators: TNodeObfuscator[] | undefined = Obfuscator.nodeObfuscators.get(node.type);
 
         if (!nodeObfuscators) {
             return;
         }
 
         nodeObfuscators.forEach((obfuscator: TNodeObfuscator) => {
-            new obfuscator(this.nodes, this.options).obfuscateNode(node, parentNode);
+            new obfuscator(this.customNodes, this.options).obfuscateNode(node, parentNode);
         });
     }
 

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

@@ -1,10 +1,8 @@
 import * as ESTree from 'estree';
 
-import { TStatement } from '../types/TStatement';
-
 import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
 import { IOptions } from '../interfaces/IOptions';
-import { IStackTraceData } from '../interfaces/stack-trace-analyzer/IStackTraceData';
+import { TStatement } from '../types/TStatement';
 
 import { AppendState } from '../enums/AppendState';
 
@@ -28,9 +26,8 @@ export abstract class AbstractCustomNode implements ICustomNode {
 
     /**
      * @param astTree
-     * @param stackTraceData
      */
-    public abstract appendNode (astTree: ESTree.Node, stackTraceData?: IStackTraceData[]): void;
+    public abstract appendNode (astTree: ESTree.Node): void;
 
     /**
      * @returns {AppendState}

+ 0 - 82
src/custom-nodes/CustomNodeAppender.ts

@@ -1,82 +0,0 @@
-import * as ESTree from 'estree';
-
-import { TNodeWithBlockStatement } from '../types/TNodeWithBlockStatement';
-import { TStatement } from '../types/TStatement';
-
-import { IStackTraceData } from '../interfaces/stack-trace-analyzer/IStackTraceData';
-
-import { NodeUtils } from '../NodeUtils';
-import { Utils } from '../Utils';
-
-/**
- * This class appends node into a first deepest BlockStatement in order of function calls
- *
- * For example:
- *
- * function Foo () {
- *     var baz = function () {
- *
- *     }
- *
- *     baz();
- * }
- *
- * foo();
- *
- * Appends node into block statement of `baz` function expression.
- */
-export class CustomNodeAppender {
-    /**
-     * @param blockScopeStackTraceData
-     * @param blockScopeNode
-     * @param nodeBodyStatements
-     * @param index
-     */
-    public static appendNode (
-        blockScopeStackTraceData: IStackTraceData[],
-        blockScopeNode: TNodeWithBlockStatement,
-        nodeBodyStatements: TStatement[],
-        index: number = 0
-    ): void {
-        let targetBlockScope: TNodeWithBlockStatement;
-
-        if (!blockScopeStackTraceData.length) {
-            targetBlockScope = blockScopeNode;
-        } else {
-            targetBlockScope = CustomNodeAppender.getOptimalBlockScope(
-                blockScopeStackTraceData,
-                index
-            );
-        }
-
-        NodeUtils.prependNode(targetBlockScope, nodeBodyStatements);
-    }
-
-    /**
-     * @param stackTraceRootLength
-     */
-    public static getRandomStackTraceIndex (stackTraceRootLength: number): number {
-        return Utils.getRandomGenerator().integer({
-            min: 0,
-            max: Math.max(0, Math.round(stackTraceRootLength - 1))
-        });
-    }
-
-    /**
-     * @param blockScopeTraceData
-     * @param index
-     * @returns {ESTree.BlockStatement}
-     */
-    private static getOptimalBlockScope (
-        blockScopeTraceData: IStackTraceData[],
-        index: number
-    ): ESTree.BlockStatement {
-        const firstCall: IStackTraceData = blockScopeTraceData[index];
-
-        if (firstCall.stackTrace.length) {
-            return CustomNodeAppender.getOptimalBlockScope(firstCall.stackTrace, 0);
-        } else {
-            return firstCall.callee;
-        }
-    }
-}

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

@@ -1,9 +1,9 @@
 import 'format-unicorn';
 
 import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
-
 import { TStatement } from '../../types/TStatement';
 
+import { IOptions } from '../../interfaces/IOptions';
 import { IStackTraceData } from '../../interfaces/stack-trace-analyzer/IStackTraceData';
 
 import { AppendState } from '../../enums/AppendState';
@@ -11,7 +11,7 @@ import { AppendState } from '../../enums/AppendState';
 import { ConsoleOutputDisableExpressionTemplate } from '../../templates/custom-nodes/console-output-nodes/console-output-disable-expression-node/ConsoleOutputDisableExpressionTemplate';
 
 import { AbstractCustomNode } from '../AbstractCustomNode';
-import { CustomNodeAppender } from '../CustomNodeAppender';
+import { NodeAppender } from '../../NodeAppender';
 import { NodeUtils } from '../../NodeUtils';
 import { Utils } from '../../Utils';
 
@@ -22,15 +22,48 @@ export class ConsoleOutputDisableExpressionNode extends AbstractCustomNode {
     protected appendState: AppendState = AppendState.BeforeObfuscation;
 
     /**
-     * @param blockScopeNode
+     * @type {string}
+     */
+    protected callsControllerFunctionName: string;
+
+    /**
+     * @type {number}
+     */
+    protected randomStackTraceIndex: number;
+
+    /**
+     * @type {IStackTraceData[]}
+     */
+    protected stackTraceData: IStackTraceData[];
+
+    /**
      * @param stackTraceData
+     * @param callsControllerFunctionName
+     * @param randomStackTraceIndex
+     * @param options
+     */
+    constructor (
+        stackTraceData: IStackTraceData[],
+        callsControllerFunctionName: string,
+        randomStackTraceIndex: number,
+        options: IOptions
+    ) {
+        super(options);
+
+        this.stackTraceData = stackTraceData;
+        this.callsControllerFunctionName = callsControllerFunctionName;
+        this.randomStackTraceIndex = randomStackTraceIndex;
+    }
+
+    /**
+     * @param blockScopeNode
      */
-    public appendNode (blockScopeNode: TNodeWithBlockStatement, stackTraceData: IStackTraceData[]): void {
-        CustomNodeAppender.appendNode(
-            stackTraceData,
+    public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
+        NodeAppender.appendNodeToOptimalBlockScope(
+            this.stackTraceData,
             blockScopeNode,
             this.getNode(),
-            CustomNodeAppender.getRandomStackTraceIndex(stackTraceData.length)
+            this.randomStackTraceIndex
         );
     }
 

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

@@ -10,6 +10,7 @@ import { AppendState } from '../../enums/AppendState';
 import { DebugProtectionFunctionCallTemplate } from '../../templates/custom-nodes/debug-protection-nodes/debug-protection-function-call-node/DebufProtectionFunctionCallTemplate';
 
 import { AbstractCustomNode } from '../AbstractCustomNode';
+import { NodeAppender } from '../../NodeAppender';
 import { NodeUtils } from '../../NodeUtils';
 
 export class DebugProtectionFunctionCallNode extends AbstractCustomNode {
@@ -37,7 +38,7 @@ export class DebugProtectionFunctionCallNode extends AbstractCustomNode {
      * @param blockScopeNode
      */
     public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
-        NodeUtils.appendNode(blockScopeNode, this.getNode());
+        NodeAppender.appendNode(blockScopeNode, this.getNode());
     }
 
     /**

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

@@ -10,6 +10,7 @@ import { AppendState } from '../../enums/AppendState';
 import { DebugProtectionFunctionIntervalTemplate } from '../../templates/custom-nodes/debug-protection-nodes/debug-protection-function-interval-node/DebugProtectionFunctionIntervalTemplate';
 
 import { AbstractCustomNode } from '../AbstractCustomNode';
+import { NodeAppender } from '../../NodeAppender';
 import { NodeUtils } from '../../NodeUtils';
 
 export class DebugProtectionFunctionIntervalNode extends AbstractCustomNode {
@@ -37,7 +38,7 @@ export class DebugProtectionFunctionIntervalNode extends AbstractCustomNode {
      * @param blockScopeNode
      */
     public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
-        NodeUtils.appendNode(blockScopeNode, this.getNode());
+        NodeAppender.appendNode(blockScopeNode, this.getNode());
     }
 
     /**

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

@@ -10,6 +10,7 @@ import { AppendState } from '../../enums/AppendState';
 import { DebugProtectionFunctionTemplate } from '../../templates/custom-nodes/debug-protection-nodes/debug-protection-function-node/DebugProtectionFunctionTemplate';
 
 import { AbstractCustomNode } from '../AbstractCustomNode';
+import { NodeAppender } from '../../NodeAppender';
 import { NodeUtils } from '../../NodeUtils';
 import { Utils } from '../../Utils';
 
@@ -44,7 +45,7 @@ export class DebugProtectionFunctionNode extends AbstractCustomNode {
                 max: programBodyLength
             });
 
-        NodeUtils.insertNodeAtIndex(blockScopeNode, this.getNode(), randomIndex);
+        NodeAppender.insertNodeAtIndex(blockScopeNode, this.getNode(), randomIndex);
     }
 
     /**

+ 42 - 7
src/custom-nodes/domain-lock-nodes/DomainLockNode.ts

@@ -3,6 +3,7 @@ import 'format-unicorn';
 import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
 import { TStatement } from '../../types/TStatement';
 
+import { IOptions } from '../../interfaces/IOptions';
 import { IStackTraceData } from '../../interfaces/stack-trace-analyzer/IStackTraceData';
 
 import { AppendState } from '../../enums/AppendState';
@@ -10,7 +11,7 @@ import { AppendState } from '../../enums/AppendState';
 import { DomainLockNodeTemplate } from '../../templates/custom-nodes/domain-lock-nodes/domain-lock-node/DomainLockNodeTemplate';
 
 import { AbstractCustomNode } from '../AbstractCustomNode';
-import { CustomNodeAppender } from '../CustomNodeAppender';
+import { NodeAppender } from '../../NodeAppender';
 import { NodeUtils } from '../../NodeUtils';
 import { Utils } from '../../Utils';
 
@@ -21,15 +22,48 @@ export class DomainLockNode extends AbstractCustomNode {
     protected appendState: AppendState = AppendState.BeforeObfuscation;
 
     /**
-     * @param blockScopeNode
+     * @type {string}
+     */
+    protected callsControllerFunctionName: string;
+
+    /**
+     * @type {number}
+     */
+    protected randomStackTraceIndex: number;
+
+    /**
+     * @type {IStackTraceData[]}
+     */
+    protected stackTraceData: IStackTraceData[];
+
+    /**
      * @param stackTraceData
+     * @param callsControllerFunctionName
+     * @param randomStackTraceIndex
+     * @param options
+     */
+    constructor (
+        stackTraceData: IStackTraceData[],
+        callsControllerFunctionName: string,
+        randomStackTraceIndex: number,
+        options: IOptions
+    ) {
+        super(options);
+
+        this.stackTraceData = stackTraceData;
+        this.callsControllerFunctionName = callsControllerFunctionName;
+        this.randomStackTraceIndex = randomStackTraceIndex;
+    }
+
+    /**
+     * @param blockScopeNode
      */
-    public appendNode (blockScopeNode: TNodeWithBlockStatement, stackTraceData: IStackTraceData[]): void {
-        CustomNodeAppender.appendNode(
-            stackTraceData,
+    public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
+        NodeAppender.appendNodeToOptimalBlockScope(
+            this.stackTraceData,
             blockScopeNode,
             this.getNode(),
-            CustomNodeAppender.getRandomStackTraceIndex(stackTraceData.length)
+            this.randomStackTraceIndex
         );
     }
 
@@ -44,7 +78,8 @@ export class DomainLockNode extends AbstractCustomNode {
             DomainLockNodeTemplate().formatUnicorn({
                 domainLockFunctionName: Utils.getRandomVariableName(),
                 diff: diff,
-                domains: hiddenDomainsString
+                domains: hiddenDomainsString,
+                singleNodeCallControllerFunctionName: this.callsControllerFunctionName
             })
         );
     }

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

@@ -0,0 +1,83 @@
+import 'format-unicorn';
+
+import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
+import { TStatement } from '../../types/TStatement';
+
+import { IOptions } from '../../interfaces/IOptions';
+import { IStackTraceData } from '../../interfaces/stack-trace-analyzer/IStackTraceData';
+
+import { AppendState } from '../../enums/AppendState';
+
+import { SingleNodeCallControllerTemplate } from '../../templates/custom-nodes/SingleNodeCallControllerTemplate';
+
+import { AbstractCustomNode } from '../AbstractCustomNode';
+import { NodeAppender } from '../../NodeAppender';
+import { NodeUtils } from '../../NodeUtils';
+
+export class NodeCallsControllerFunctionNode extends AbstractCustomNode {
+    /**
+     * @type {AppendState}
+     */
+    protected appendState: AppendState = AppendState.BeforeObfuscation;
+
+    /**
+     * @type {string}
+     */
+    protected callsControllerFunctionName: string;
+
+    /**
+     * @type {number}
+     */
+    protected randomStackTraceIndex: number;
+
+    /**
+     * @type {IStackTraceData[]}
+     */
+    protected stackTraceData: IStackTraceData[];
+
+    /**
+     * @param stackTraceData
+     * @param callsControllerFunctionName
+     * @param randomStackTraceIndex
+     * @param options
+     */
+    constructor (
+        stackTraceData: IStackTraceData[],
+        callsControllerFunctionName: string,
+        randomStackTraceIndex: number,
+        options: IOptions
+    ) {
+        super(options);
+
+        this.stackTraceData = stackTraceData;
+        this.callsControllerFunctionName = callsControllerFunctionName;
+        this.randomStackTraceIndex = randomStackTraceIndex;
+    }
+
+    /**
+     * @param blockScopeNode
+     */
+    public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
+        let targetBlockScope: TNodeWithBlockStatement;
+
+        if (this.stackTraceData.length) {
+            targetBlockScope = NodeAppender
+                .getOptimalBlockScope(this.stackTraceData, this.randomStackTraceIndex, 1);
+        } else {
+            targetBlockScope = blockScopeNode;
+        }
+
+        NodeAppender.prependNode(targetBlockScope, this.getNode());
+    }
+
+    /**
+     * @returns {TStatement[]}
+     */
+    protected getNodeStructure (): TStatement[] {
+        return NodeUtils.convertCodeToStructure(
+            SingleNodeCallControllerTemplate().formatUnicorn({
+                singleNodeCallControllerFunctionName: this.callsControllerFunctionName
+            })
+        );
+    }
+}

+ 40 - 6
src/custom-nodes/self-defending-nodes/SelfDefendingUnicodeNode.ts

@@ -1,6 +1,7 @@
 import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
 import { TStatement } from '../../types/TStatement';
 
+import { IOptions } from '../../interfaces/IOptions';
 import { IStackTraceData } from '../../interfaces/stack-trace-analyzer/IStackTraceData';
 
 import { AppendState } from '../../enums/AppendState';
@@ -10,7 +11,7 @@ import { NO_CUSTOM_NODES_PRESET } from '../../preset-options/NoCustomNodesPreset
 import { SelfDefendingTemplate } from '../../templates/custom-nodes/self-defending-nodes/self-defending-unicode-node/SelfDefendingTemplate';
 
 import { AbstractCustomNode } from '../AbstractCustomNode';
-import { CustomNodeAppender } from '../CustomNodeAppender';
+import { NodeAppender } from '../../NodeAppender';
 import { JavaScriptObfuscator } from '../../JavaScriptObfuscator';
 import { NodeUtils } from '../../NodeUtils';
 import { Utils } from '../../Utils';
@@ -22,15 +23,48 @@ export class SelfDefendingUnicodeNode extends AbstractCustomNode {
     protected appendState: AppendState = AppendState.AfterObfuscation;
 
     /**
-     * @param blockScopeNode
+     * @type {string}
+     */
+    protected callsControllerFunctionName: string;
+
+    /**
+     * @type {number}
+     */
+    protected randomStackTraceIndex: number;
+
+    /**
+     * @type {IStackTraceData[]}
+     */
+    protected stackTraceData: IStackTraceData[];
+
+    /**
      * @param stackTraceData
+     * @param callsControllerFunctionName
+     * @param randomStackTraceIndex
+     * @param options
+     */
+    constructor (
+        stackTraceData: IStackTraceData[],
+        callsControllerFunctionName: string,
+        randomStackTraceIndex: number,
+        options: IOptions
+    ) {
+        super(options);
+
+        this.stackTraceData = stackTraceData;
+        this.callsControllerFunctionName = callsControllerFunctionName;
+        this.randomStackTraceIndex = randomStackTraceIndex;
+    }
+
+    /**
+     * @param blockScopeNode
      */
-    public appendNode (blockScopeNode: TNodeWithBlockStatement, stackTraceData: IStackTraceData[]): void {
-        CustomNodeAppender.appendNode(
-            stackTraceData,
+    public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
+        NodeAppender.appendNodeToOptimalBlockScope(
+            this.stackTraceData,
             blockScopeNode,
             this.getNode(),
-            CustomNodeAppender.getRandomStackTraceIndex(stackTraceData.length)
+            this.randomStackTraceIndex
         );
     }
 

+ 2 - 1
src/custom-nodes/unicode-array-nodes/UnicodeArrayCallsWrapper.ts

@@ -19,6 +19,7 @@ import { UnicodeArrayRc4DecodeNodeTemplate } from '../../templates/custom-nodes/
 
 import { AbstractCustomNode } from '../AbstractCustomNode';
 import { JavaScriptObfuscator } from '../../JavaScriptObfuscator';
+import { NodeAppender } from '../../NodeAppender';
 import { NodeUtils } from '../../NodeUtils';
 import { UnicodeArray } from '../../UnicodeArray';
 
@@ -70,7 +71,7 @@ export class UnicodeArrayCallsWrapper extends AbstractCustomNode {
             return;
         }
 
-        NodeUtils.insertNodeAtIndex(blockScopeNode, this.getNode(), 1);
+        NodeAppender.insertNodeAtIndex(blockScopeNode, this.getNode(), 1);
     }
 
     /**

+ 2 - 1
src/custom-nodes/unicode-array-nodes/UnicodeArrayNode.ts

@@ -12,6 +12,7 @@ import { UnicodeArray } from '../../UnicodeArray';
 import { UnicodeArrayTemplate } from '../../templates/custom-nodes/unicode-array-nodes/unicode-array-node/UnicodeArrayTemplate';
 
 import { AbstractCustomNode } from '../AbstractCustomNode';
+import { NodeAppender } from '../../NodeAppender';
 import { NodeUtils } from '../../NodeUtils';
 
 export class UnicodeArrayNode extends AbstractCustomNode {
@@ -67,7 +68,7 @@ export class UnicodeArrayNode extends AbstractCustomNode {
             return;
         }
 
-        NodeUtils.prependNode(blockScopeNode, this.getNode());
+        NodeAppender.prependNode(blockScopeNode, this.getNode());
     }
 
     /**

+ 2 - 1
src/custom-nodes/unicode-array-nodes/UnicodeArrayRotateFunctionNode.ts

@@ -14,6 +14,7 @@ import { UnicodeArrayRotateFunctionTemplate } from '../../templates/custom-nodes
 
 import { AbstractCustomNode } from '../AbstractCustomNode';
 import { JavaScriptObfuscator } from '../../JavaScriptObfuscator';
+import { NodeAppender } from '../../NodeAppender';
 import { NodeUtils } from '../../NodeUtils';
 import { UnicodeArray } from '../../UnicodeArray';
 import { Utils } from '../../Utils';
@@ -66,7 +67,7 @@ export class UnicodeArrayRotateFunctionNode extends AbstractCustomNode {
             return;
         }
 
-        NodeUtils.insertNodeAtIndex(blockScopeNode, this.getNode(), 1);
+        NodeAppender.insertNodeAtIndex(blockScopeNode, this.getNode(), 1);
     }
 
     /**

+ 1 - 4
src/interfaces/custom-nodes/ICustomNode.d.ts

@@ -2,16 +2,13 @@ import * as ESTree from 'estree';
 
 import { TStatement } from '../../types/TStatement';
 
-import { IStackTraceData } from '../stack-trace-analyzer/IStackTraceData';
-
 import { AppendState } from '../../enums/AppendState';
 
 export interface ICustomNode {
     /**
      * @param astTree
-     * @param stackTraceData
      */
-    appendNode (astTree: ESTree.Node, stackTraceData?: IStackTraceData[]): void;
+    appendNode (astTree: ESTree.Node): void;
 
     /**
      * @returns {AppendState}

+ 13 - 2
src/node-groups/AbstractNodesGroup.ts

@@ -2,6 +2,7 @@ import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
 
 import { INodesGroup } from '../interfaces/INodesGroup';
 import { IOptions } from '../interfaces/IOptions';
+import { IStackTraceData } from '../interfaces/stack-trace-analyzer/IStackTraceData';
 
 export abstract class AbstractNodesGroup implements INodesGroup {
     /**
@@ -9,17 +10,27 @@ export abstract class AbstractNodesGroup implements INodesGroup {
      */
     protected nodes: Map <string, ICustomNode> = new Map <string, ICustomNode> ();
 
+    /**
+     * @type {IStackTraceData[]}
+     */
+    protected stackTraceData: IStackTraceData[];
+
     /**
      * @type {IOptions}
      */
     protected options: IOptions;
 
-    constructor (options: IOptions) {
+    /**
+     * @param stackTraceData
+     * @param options
+     */
+    constructor (stackTraceData: IStackTraceData[], options: IOptions) {
+        this.stackTraceData = stackTraceData;
         this.options = options;
     }
 
     /**
-     * @returns {Map<string, INode>}
+     * @returns {Map<string, ICustomNode>}
      */
     public getNodes (): Map <string, ICustomNode> {
         return this.nodes;

+ 24 - 3
src/node-groups/ConsoleOutputNodesGroup.ts

@@ -1,22 +1,43 @@
 import { IOptions } from '../interfaces/IOptions';
+import { IStackTraceData } from '../interfaces/stack-trace-analyzer/IStackTraceData';
 
 import { AbstractNodesGroup } from './AbstractNodesGroup';
 import { ConsoleOutputDisableExpressionNode } from '../custom-nodes/console-output-nodes/ConsoleOutputDisableExpressionNode';
+import { NodeAppender } from '../NodeAppender';
+import { NodeCallsControllerFunctionNode } from '../custom-nodes/node-calls-controller-nodes/NodeCallsControllerFunctionNode';
 
 export class ConsoleOutputNodesGroup extends AbstractNodesGroup {
     /**
+     * @param stackTraceData
      * @param options
      */
-    constructor (options: IOptions) {
-        super(options);
+    constructor (stackTraceData: IStackTraceData[], options: IOptions) {
+        super(stackTraceData, options);
 
         if (!this.options.disableConsoleOutput) {
             return;
         }
 
+        const callsControllerFunctionName: string = 'domainLockCallsControllerFunction';
+        const randomStackTraceIndex: number = NodeAppender.getRandomStackTraceIndex(this.stackTraceData.length);
+
         this.nodes.set(
             'consoleOutputDisableExpressionNode',
-            new ConsoleOutputDisableExpressionNode(this.options)
+            new ConsoleOutputDisableExpressionNode(
+                this.stackTraceData,
+                callsControllerFunctionName,
+                randomStackTraceIndex,
+                this.options
+            )
+        );
+        this.nodes.set(
+            'ConsoleOutputNodeCallsControllerFunctionNode',
+            new NodeCallsControllerFunctionNode(
+                this.stackTraceData,
+                callsControllerFunctionName,
+                randomStackTraceIndex,
+                this.options
+            )
         );
     }
 }

+ 4 - 2
src/node-groups/DebugProtectionNodesGroup.ts

@@ -1,4 +1,5 @@
 import { IOptions } from '../interfaces/IOptions';
+import { IStackTraceData } from '../interfaces/stack-trace-analyzer/IStackTraceData';
 
 import { DebugProtectionFunctionCallNode } from '../custom-nodes/debug-protection-nodes/DebugProtectionFunctionCallNode';
 import { DebugProtectionFunctionIntervalNode } from '../custom-nodes/debug-protection-nodes/DebugProtectionFunctionIntervalNode';
@@ -14,10 +15,11 @@ export class DebugProtectionNodesGroup extends AbstractNodesGroup {
     private debugProtectionFunctionIdentifier: string = Utils.getRandomVariableName();
 
     /**
+     * @param stackTraceData
      * @param options
      */
-    constructor (options: IOptions) {
-        super(options);
+    constructor (stackTraceData: IStackTraceData[], options: IOptions) {
+        super(stackTraceData, options);
 
         if (!this.options.debugProtection) {
             return;

+ 24 - 3
src/node-groups/DomainLockNodesGroup.ts

@@ -1,22 +1,43 @@
 import { IOptions } from '../interfaces/IOptions';
+import { IStackTraceData } from '../interfaces/stack-trace-analyzer/IStackTraceData';
 
 import { AbstractNodesGroup } from './AbstractNodesGroup';
+import { NodeAppender } from '../NodeAppender';
 import { DomainLockNode } from '../custom-nodes/domain-lock-nodes/DomainLockNode';
+import { NodeCallsControllerFunctionNode } from '../custom-nodes/node-calls-controller-nodes/NodeCallsControllerFunctionNode';
 
 export class DomainLockNodesGroup extends AbstractNodesGroup {
     /**
+     * @param stackTraceData
      * @param options
      */
-    constructor (options: IOptions) {
-        super(options);
+    constructor (stackTraceData: IStackTraceData[], options: IOptions) {
+        super(stackTraceData, options);
 
         if (!this.options.domainLock.length) {
             return;
         }
 
+        const callsControllerFunctionName: string = 'domainLockCallsControllerFunction';
+        const randomStackTraceIndex: number = NodeAppender.getRandomStackTraceIndex(this.stackTraceData.length);
+
         this.nodes.set(
             'DomainLockNode',
-            new DomainLockNode(this.options)
+            new DomainLockNode(
+                this.stackTraceData,
+                callsControllerFunctionName,
+                randomStackTraceIndex,
+                this.options
+            )
+        );
+        this.nodes.set(
+            'DomainLockNodeCallsControllerFunctionNode',
+            new NodeCallsControllerFunctionNode(
+                this.stackTraceData,
+                callsControllerFunctionName,
+                randomStackTraceIndex,
+                this.options
+            )
         );
     }
 }

+ 24 - 3
src/node-groups/SelfDefendingNodesGroup.ts

@@ -1,22 +1,43 @@
 import { IOptions } from '../interfaces/IOptions';
+import { IStackTraceData } from '../interfaces/stack-trace-analyzer/IStackTraceData';
 
 import { AbstractNodesGroup } from './AbstractNodesGroup';
+import { NodeAppender } from '../NodeAppender';
+import { NodeCallsControllerFunctionNode } from '../custom-nodes/node-calls-controller-nodes/NodeCallsControllerFunctionNode';
 import { SelfDefendingUnicodeNode } from '../custom-nodes/self-defending-nodes/SelfDefendingUnicodeNode';
 
 export class SelfDefendingNodesGroup extends AbstractNodesGroup {
     /**
+     * @param stackTraceData
      * @param options
      */
-    constructor (options: IOptions) {
-        super(options);
+    constructor (stackTraceData: IStackTraceData[], options: IOptions) {
+        super(stackTraceData, options);
 
         if (!this.options.selfDefending) {
             return;
         }
 
+        const callsControllerFunctionName: string = 'domainLockCallsControllerFunction';
+        const randomStackTraceIndex: number = NodeAppender.getRandomStackTraceIndex(this.stackTraceData.length);
+
         this.nodes.set(
             'selfDefendingUnicodeNode',
-            new SelfDefendingUnicodeNode(this.options)
+            new SelfDefendingUnicodeNode(
+                this.stackTraceData,
+                callsControllerFunctionName,
+                randomStackTraceIndex,
+                this.options
+            )
+        );
+        this.nodes.set(
+            'SelfDefendingNodeCallsControllerFunctionNode',
+            new NodeCallsControllerFunctionNode(
+                this.stackTraceData,
+                callsControllerFunctionName,
+                randomStackTraceIndex,
+                this.options
+            )
         );
     }
 }

+ 4 - 2
src/node-groups/UnicodeArrayNodesGroup.ts

@@ -1,4 +1,5 @@
 import { IOptions } from '../interfaces/IOptions';
+import { IStackTraceData } from '../interfaces/stack-trace-analyzer/IStackTraceData';
 
 import { AbstractNodesGroup } from './AbstractNodesGroup';
 import { UnicodeArray } from '../UnicodeArray';
@@ -24,10 +25,11 @@ export class UnicodeArrayNodesGroup extends AbstractNodesGroup {
     private unicodeArrayTranslatorName: string = Utils.getRandomVariableName(UnicodeArrayNode.UNICODE_ARRAY_RANDOM_LENGTH);
 
     /**
+     * @param stackTraceData
      * @param options
      */
-    constructor (options: IOptions) {
-        super(options);
+    constructor (stackTraceData: IStackTraceData[], options: IOptions) {
+        super(stackTraceData, options);
 
         if (!this.options.unicodeArray) {
             return;

+ 24 - 0
src/templates/custom-nodes/SingleNodeCallControllerTemplate.ts

@@ -0,0 +1,24 @@
+/**
+ * @returns {string}
+ */
+export function SingleNodeCallControllerTemplate (): string {
+    return `
+        var {singleNodeCallControllerFunctionName} = (function(){
+            var firstCall = true;
+            
+            return function (context, fn){	
+                var rfn = firstCall ? function(){
+                    if(fn){
+                        var res = fn.apply(context, arguments);
+                        fn = null;
+                        return res;
+                    }
+                } : function(){}
+                
+                firstCall = false;
+                
+                return rfn;
+            }
+        })();
+    `;
+}

+ 50 - 53
src/templates/custom-nodes/domain-lock-nodes/domain-lock-node/DomainLockNodeTemplate.ts

@@ -3,7 +3,7 @@
  */
 export function DomainLockNodeTemplate (): string {
     return `
-        var {domainLockFunctionName} = function () {
+        var {domainLockFunctionName} = {singleNodeCallControllerFunctionName}(this, function () {
             var getGlobal = function () {
                 if (typeof self !== 'undefined') { return self; }
                 if (typeof window !== 'undefined') { return window; }
@@ -19,65 +19,62 @@ export function DomainLockNodeTemplate (): string {
                     }()
                 };
             };
-  
-            if (
-                !getGlobal().promisePolyfillActivationFlag ||
-                !getGlobal().promisePolyfillActivationFlag.isActive
-            ) {            
-                getGlobal().promisePolyfillActivationFlag = {
-                    isActive: true
-                };
-                
-                var regExp = new RegExp("[{diff}]", "g");
-                var domains = "{domains}".replace(regExp, "").split(";");
-                var eval = []["forEach"]["constructor"];
-                var window = eval("return this")();
-                
-                for (var d in window) {
-                    if (d.length == 8 && d.charCodeAt(7) == 116 && d.charCodeAt(5) == 101 && d.charCodeAt(3) == 117 && d.charCodeAt(0) == 100) {
-                        break;
-                    }
-                }
-    
-                for (var d1 in window[d]) {
-                    if (d1.length == 6 && d1.charCodeAt(5) == 110 && d1.charCodeAt(0) == 100) {
-                        break;
-                    }
-                }
-    
-                var currentDomain = window[d][d1];
+                        
+            var regExp = new RegExp("[{diff}]", "g");
+            var domains = "{domains}".replace(regExp, "").split(";");
+            var eval = []["forEach"]["constructor"];
+            var windowObject = eval("return this")();
+            var document;
+            var domain;
+                        
+            for (var d in windowObject) {
+                if (d.length == 8 && d.charCodeAt(7) == 116 && d.charCodeAt(5) == 101 && d.charCodeAt(3) == 117 && d.charCodeAt(0) == 100) {
+                    document = d;
                 
-                if (!currentDomain) {
-                    return;
+                    break;
                 }
-                
-                var ok = false;
-                            
-                for (var i = 0; i < domains.length; i++) {
-                    var domain = domains[i];
-                    var position = currentDomain.length - domain.length;
-                    var lastIndex = currentDomain.indexOf(domain, position);
-                    var endsWith = lastIndex !== -1 && lastIndex === position;
+            }
+
+            for (var d1 in windowObject[document]) {
+                if (d1.length == 6 && d1.charCodeAt(5) == 110 && d1.charCodeAt(0) == 100) {
+                    domain = d1;
                     
-                    if (endsWith) {
-                        if (currentDomain.length == domain.length || domain.indexOf(".") === 0) {
-                            ok = true;
-                        }
+                    break;
+                }
+            }
+            
+            if ((!document && !domain) || (!windowObject[document] && !windowObject[document][domain])) {
+                return;
+            }
+            
+            var currentDomain = windowObject[document][domain];
+
+            var ok = false;
                         
-                        break;
+            for (var i = 0; i < domains.length; i++) {
+                var domain = domains[i];
+                var position = currentDomain.length - domain.length;
+                var lastIndex = currentDomain.indexOf(domain, position);
+                var endsWith = lastIndex !== -1 && lastIndex === position;
+                
+                if (endsWith) {
+                    if (currentDomain.length == domain.length || domain.indexOf(".") === 0) {
+                        ok = true;
                     }
+                    
+                    break;
                 }
-                   
-                if (!ok) {
-                    data;
-                } else {
-                    return;
-                }
-                
-                func();
             }
-        };
-        
+               
+            if (!ok) {
+                data;
+            } else {
+                return;
+            }
+            
+            func();
+        });
+
         {domainLockFunctionName}();
     `;
 }

+ 2 - 1
test/dev/dev.ts

@@ -70,7 +70,8 @@ if (!(<any>global)._babelPolyfill) {
     `,
         {
             disableConsoleOutput: false,
-            unicodeArrayEncoding: 'rc4'
+            unicodeArrayEncoding: 'rc4',
+            domainLock: ['google.ru']
         }
     ).getObfuscatedCode();
 

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

@@ -7,7 +7,7 @@ import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
 const assert: Chai.AssertStatic = require('chai').assert;
 
 describe('ConsoleOutputDisableExpressionNode', () => {
-    it('should correctly appendNode `ConsoleOutputDisableExpressionNode` custom node into the obfuscated code if `disableConsoleOutput` option is set', () => {
+    it('should correctly appendNodeToOptimalBlockScope `ConsoleOutputDisableExpressionNode` custom node into the obfuscated code if `disableConsoleOutput` option is set', () => {
         let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
             `var test = 'test';`,
             Object.assign({}, NO_CUSTOM_NODES_PRESET, {
@@ -19,7 +19,7 @@ describe('ConsoleOutputDisableExpressionNode', () => {
         assert.match(obfuscationResult.getObfuscatedCode(), /for *\(_0x([a-z0-9]){4,6} in _0x([a-z0-9]){4,6} *= *'(\\x[a-f0-9]*)*'\)/);
     });
 
-    it('should\'t appendNode `ConsoleOutputDisableExpressionNode` custom node into the obfuscated code if `disableConsoleOutput` option is not set', () => {
+    it('should\'t appendNodeToOptimalBlockScope `ConsoleOutputDisableExpressionNode` custom node into the obfuscated code if `disableConsoleOutput` option is not set', () => {
         let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
             `var test = 'test';`,
             Object.assign({}, NO_CUSTOM_NODES_PRESET, {

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

@@ -7,7 +7,7 @@ import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
 const assert: Chai.AssertStatic = require('chai').assert;
 
 describe('DomainLockNode', () => {
-    it('should correctly appendNode `DomainLockNode` custom node into the obfuscated code if `domainLock` option is set', () => {
+    it('should correctly appendNodeToOptimalBlockScope `DomainLockNode` custom node into the obfuscated code if `domainLock` option is set', () => {
         let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
             `var test = 'test';`,
             Object.assign({}, NO_CUSTOM_NODES_PRESET, {
@@ -18,7 +18,7 @@ describe('DomainLockNode', () => {
         assert.match(obfuscationResult.getObfuscatedCode(), /var _0x([a-z0-9]){4,6} *= *new RegExp/);
     });
 
-    it('should\'t appendNode `DomainLockNode` custom node into the obfuscated code if `domainLock` option is not set', () => {
+    it('should\'t appendNodeToOptimalBlockScope `DomainLockNode` custom node into the obfuscated code if `domainLock` option is not set', () => {
         let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
             `var test = 'test';`,
             Object.assign({}, NO_CUSTOM_NODES_PRESET, {

+ 1 - 1
test/functional-tests/custom-nodes/unicode-array-nodes/UnicodeArrayCallsWrapper.spec.ts

@@ -7,7 +7,7 @@ import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
 const assert: Chai.AssertStatic = require('chai').assert;
 
 describe('UnicodeArrayCallsWrapper', () => {
-    it('should correctly appendNode `UnicodeArrayCallsWrapper` custom node into the obfuscated code', () => {
+    it('should correctly appendNodeToOptimalBlockScope `UnicodeArrayCallsWrapper` custom node into the obfuscated code', () => {
         let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
             `var test = 'test';`,
             Object.assign({}, NO_CUSTOM_NODES_PRESET, {

+ 2 - 2
test/functional-tests/custom-nodes/unicode-array-nodes/UnicodeArrayNode.spec.ts

@@ -7,7 +7,7 @@ import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
 const assert: Chai.AssertStatic = require('chai').assert;
 
 describe('UnicodeArrayNode', () => {
-    it('should correctly appendNode `UnicodeArrayNode` custom node into the obfuscated code if `unicodeArray` option is set', () => {
+    it('should correctly appendNodeToOptimalBlockScope `UnicodeArrayNode` custom node into the obfuscated code if `unicodeArray` option is set', () => {
         let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
             `var test = 'test';`,
             Object.assign({}, NO_CUSTOM_NODES_PRESET, {
@@ -19,7 +19,7 @@ describe('UnicodeArrayNode', () => {
         assert.match(obfuscationResult.getObfuscatedCode(), /^var _0x([a-z0-9]){4} *= *\[/);
     });
 
-    it('should\'t appendNode `UnicodeArrayNode` custom node into the obfuscated code if `unicodeArray` option is not set', () => {
+    it('should\'t appendNodeToOptimalBlockScope `UnicodeArrayNode` custom node into the obfuscated code if `unicodeArray` option is not set', () => {
         let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
             `var test = 'test';`,
             Object.assign({}, NO_CUSTOM_NODES_PRESET, {

+ 2 - 2
test/functional-tests/custom-nodes/unicode-array-nodes/UnicodeArrayRotateFunctionNode.spec.ts

@@ -7,7 +7,7 @@ import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
 const assert: Chai.AssertStatic = require('chai').assert;
 
 describe('UnicodeArrayRotateFunctionNode', () => {
-    it('should correctly appendNode `UnicodeArrayRotateFunctionNode` custom node into the obfuscated code if `rotateUnicodeArray` option is set', () => {
+    it('should correctly appendNodeToOptimalBlockScope `UnicodeArrayRotateFunctionNode` custom node into the obfuscated code if `rotateUnicodeArray` option is set', () => {
         let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
             `var test = 'test';`,
             Object.assign({}, NO_CUSTOM_NODES_PRESET, {
@@ -20,7 +20,7 @@ describe('UnicodeArrayRotateFunctionNode', () => {
         assert.match(obfuscationResult.getObfuscatedCode(), /while *\(-- *_0x([a-z0-9]){4,6}\) *\{/);
     });
 
-    it('should\'t appendNode `UnicodeArrayRotateFunctionNode` custom node into the obfuscated code if `rotateUnicodeArray` option is not set', () => {
+    it('should\'t appendNodeToOptimalBlockScope `UnicodeArrayRotateFunctionNode` custom node into the obfuscated code if `rotateUnicodeArray` option is not set', () => {
         let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
             `var test = 'test';`,
             Object.assign({}, NO_CUSTOM_NODES_PRESET, {

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

@@ -9,17 +9,26 @@ const assert: Chai.AssertStatic = require('chai').assert;
 
 /**
  * @param templateData
+ * @param callsControllerFunctionName
  * @param currentDomain
  * @returns {Function}
  */
-function getFunctionFromTemplate (templateData: any, currentDomain: string) {
+function getFunctionFromTemplate (templateData: any, callsControllerFunctionName: string,  currentDomain: string) {
     let domainLockTemplate: string = DomainLockNodeTemplate().formatUnicorn(templateData);
 
     return Function(`
         document = {
             domain: '${currentDomain}'
         };
-        
+
+        var ${callsControllerFunctionName} = (function(){            
+            return function (context, fn){	
+                return function () {
+                    return fn.apply(context, arguments);
+                };
+            }
+        })();
+
         ${domainLockTemplate}
     `)();
 }
@@ -28,7 +37,8 @@ describe('DomainLockNodeTemplate (): string', () => {
     let domainsString: string,
         currentDomain: string,
         hiddenDomainsString: string,
-        diff: string;
+        diff: string,
+        singleNodeCallControllerFunctionName: string = 'callsController';
 
     it('should correctly runs code inside template if current domain matches with `domainsString`', () => {
         domainsString = ['www.example.com'].join(';');
@@ -39,10 +49,11 @@ describe('DomainLockNodeTemplate (): string', () => {
         ] = Utils.hideString(domainsString, domainsString.length * 3);
 
         assert.doesNotThrow(() => getFunctionFromTemplate({
-            domainLockFunctionName: 'func',
+            domainLockFunctionName: 'domainLockFunction',
             diff: diff,
-            domains: hiddenDomainsString
-        }, currentDomain));
+            domains: hiddenDomainsString,
+            singleNodeCallControllerFunctionName
+        }, singleNodeCallControllerFunctionName, currentDomain));
     });
 
     it('should correctly runs code inside template if current domain matches with base domain of `domainsString` item', () => {
@@ -54,10 +65,11 @@ describe('DomainLockNodeTemplate (): string', () => {
         ] = Utils.hideString(domainsString, domainsString.length * 3);
 
         assert.doesNotThrow(() => getFunctionFromTemplate({
-            domainLockFunctionName: 'func',
+            domainLockFunctionName: 'domainLockFunction',
             diff: diff,
-            domains: hiddenDomainsString
-        }, currentDomain));
+            domains: hiddenDomainsString,
+            singleNodeCallControllerFunctionName
+        }, singleNodeCallControllerFunctionName, currentDomain));
     });
 
     it('should throw an error if current domain doesn\'t match with `domainsString`', () => {
@@ -69,10 +81,11 @@ describe('DomainLockNodeTemplate (): string', () => {
         ] = Utils.hideString(domainsString, domainsString.length * 3);
 
         assert.throws(() => getFunctionFromTemplate({
-            domainLockFunctionName: 'func',
+            domainLockFunctionName: 'domainLockFunction',
             diff: diff,
-            domains: hiddenDomainsString
-        }, currentDomain));
+            domains: hiddenDomainsString,
+            singleNodeCallControllerFunctionName
+        }, singleNodeCallControllerFunctionName, currentDomain));
     });
 
     afterEach(() => {

+ 1 - 1
test/index.spec.ts

@@ -13,7 +13,7 @@ import './unit-tests/OptionsNormalizer.spec';
 import './unit-tests/SourceMapCorrector.spec';
 import './unit-tests/Utils.spec';
 import './unit-tests/cli/CLIUtils.spec';
-import './unit-tests/custom-nodes/CustomNodeAppender.spec';
+import './unit-tests/custom-nodes/NodeAppender.spec';
 import './unit-tests/node-obfuscators/CatchClauseObfuscator.spec';
 import './unit-tests/node-obfuscators/FunctionDeclarationObfuscator.spec';
 import './unit-tests/node-obfuscators/FunctionObfuscator.spec';

+ 0 - 132
test/unit-tests/NodeUtils.spec.ts

@@ -1,8 +1,6 @@
 import * as chai from 'chai';
 import * as ESTree from 'estree';
 
-import { TStatement } from '../../src/types/TStatement';
-
 import { NodeMocks } from '../mocks/NodeMocks';
 import { NodeUtils } from '../../src/NodeUtils';
 
@@ -27,43 +25,6 @@ describe('NodeUtils', () => {
         });
     });
 
-    describe('appendNode (blockScopeNode: TNodeWithBlockStatement[], nodeBodyStatements: TStatement[]): void', () => {
-        let blockStatementNode: ESTree.BlockStatement,
-            expectedBlockStatementNode: ESTree.BlockStatement,
-            expectedExpressionStatementNode: ESTree.ExpressionStatement,
-            expressionStatementNode: ESTree.ExpressionStatement;
-
-        beforeEach(() => {
-            expressionStatementNode = NodeMocks.getExpressionStatementNode();
-            expectedExpressionStatementNode = NodeMocks.getExpressionStatementNode();
-
-            blockStatementNode = NodeMocks.getBlockStatementNode();
-
-            expectedExpressionStatementNode['parentNode'] = blockStatementNode;
-
-            expectedBlockStatementNode = NodeMocks.getBlockStatementNode([
-                expectedExpressionStatementNode
-            ]);
-
-            NodeUtils.appendNode(
-                blockStatementNode,
-                [expressionStatementNode]
-            );
-            NodeUtils.appendNode(
-                blockStatementNode,
-                <TStatement[]>[{}]
-            );
-        });
-
-        it('should appendNode given node to a `BlockStatement` node body', () => {
-            assert.deepEqual(blockStatementNode, expectedBlockStatementNode);
-        });
-
-        it('should does not change `BlockStatement` node body if given node is not a valid Node', () => {
-            assert.deepEqual(blockStatementNode, expectedBlockStatementNode);
-        });
-    });
-
     describe('convertCodeToStructure (code: string): ESTree.Node[]', () => {
         let code: string,
             identifierNode: ESTree.Identifier,
@@ -198,56 +159,6 @@ describe('NodeUtils', () => {
         });
     });
 
-    describe('insertNodeAtIndex (blockScopeNode: TNodeWithBlockStatement[], nodeBodyStatements: TStatement[], index: number): void', () => {
-        let blockStatementNode: ESTree.BlockStatement,
-            expectedBlockStatementNode: ESTree.BlockStatement,
-            expressionStatementNode1: ESTree.ExpressionStatement,
-            expressionStatementNode2: ESTree.ExpressionStatement,
-            expressionStatementNode3: ESTree.ExpressionStatement,
-            expressionStatementNode4: ESTree.ExpressionStatement;
-
-        beforeEach(() => {
-            expressionStatementNode1 = NodeMocks.getExpressionStatementNode(NodeMocks.getLiteralNode(1));
-            expressionStatementNode2 = NodeMocks.getExpressionStatementNode(NodeMocks.getLiteralNode(2));
-            expressionStatementNode3 = NodeMocks.getExpressionStatementNode(NodeMocks.getLiteralNode(3));
-            expressionStatementNode4 = NodeMocks.getExpressionStatementNode(NodeMocks.getLiteralNode(2));
-
-            blockStatementNode = NodeMocks.getBlockStatementNode([
-                expressionStatementNode1,
-                expressionStatementNode3
-            ]);
-
-            expressionStatementNode1['parentNode'] = blockStatementNode;
-            expressionStatementNode2['parentNode'] = blockStatementNode;
-            expressionStatementNode3['parentNode'] = blockStatementNode;
-
-            expectedBlockStatementNode = NodeMocks.getBlockStatementNode([
-                expressionStatementNode1,
-                expressionStatementNode2,
-                expressionStatementNode3
-            ]);
-
-            NodeUtils.insertNodeAtIndex(
-                blockStatementNode,
-                [expressionStatementNode4],
-                1
-            );
-            NodeUtils.insertNodeAtIndex(
-                blockStatementNode,
-                <TStatement[]>[{}],
-                1
-            );
-        });
-
-        it('should insert given node in `BlockStatement` node body at index', () => {
-            assert.deepEqual(blockStatementNode, expectedBlockStatementNode);
-        });
-
-        it('should does not change `BlockStatement` node body if given node is not a valid Node', () => {
-            assert.deepEqual(blockStatementNode, expectedBlockStatementNode);
-        });
-    });
-
     describe('parentize (node: ESTree.Node): void', () => {
         let ifStatementNode: ESTree.IfStatement,
             ifStatementBlockStatementNode: ESTree.BlockStatement,
@@ -295,47 +206,4 @@ describe('NodeUtils', () => {
             assert.deepEqual(expressionStatementNode2['parentNode'], ifStatementBlockStatementNode);
         });
     });
-
-    describe('prependNode (blockScopeNode: TNodeWithBlockStatement[], nodeBodyStatements: TStatement[]): void', () => {
-        let blockStatementNode: ESTree.BlockStatement,
-            expectedBlockStatementNode: ESTree.BlockStatement,
-            expressionStatementNode1: ESTree.ExpressionStatement,
-            expressionStatementNode2: ESTree.ExpressionStatement,
-            expressionStatementNode3: ESTree.ExpressionStatement;
-
-        beforeEach(() => {
-            expressionStatementNode1 = NodeMocks.getExpressionStatementNode(NodeMocks.getLiteralNode(1));
-            expressionStatementNode2 = NodeMocks.getExpressionStatementNode(NodeMocks.getLiteralNode(2));
-            expressionStatementNode3 = NodeMocks.getExpressionStatementNode(NodeMocks.getLiteralNode(2));
-
-            blockStatementNode = NodeMocks.getBlockStatementNode([
-                expressionStatementNode1
-            ]);
-
-            expressionStatementNode1['parentNode'] = blockStatementNode;
-            expressionStatementNode2['parentNode'] = blockStatementNode;
-
-            expectedBlockStatementNode = NodeMocks.getBlockStatementNode([
-                expressionStatementNode2,
-                expressionStatementNode1
-            ]);
-
-            NodeUtils.prependNode(
-                blockStatementNode,
-                [Object.assign({}, expressionStatementNode3)]
-            );
-            NodeUtils.prependNode(
-                blockStatementNode,
-                <TStatement[]>[{}]
-            )
-        });
-
-        it('should prepend given node to a `BlockStatement` node body', () => {
-            assert.deepEqual(blockStatementNode, expectedBlockStatementNode);
-        });
-
-        it('should does not change `BlockStatement` node body if given node is not a valid Node', () => {
-            assert.deepEqual(blockStatementNode, expectedBlockStatementNode);
-        });
-    });
 });

+ 140 - 10
test/unit-tests/custom-nodes/CustomNodeAppender.spec.ts → test/unit-tests/custom-nodes/NodeAppender.spec.ts

@@ -5,15 +5,52 @@ import { TStatement } from '../../../src/types/TStatement';
 
 import { IStackTraceData } from '../../../src/interfaces/stack-trace-analyzer/IStackTraceData';
 
-import { CustomNodeAppender } from '../../../src/custom-nodes/CustomNodeAppender';
+import { NodeAppender } from '../../../src/NodeAppender';
 import { NodeMocks } from '../../mocks/NodeMocks';
 import { NodeUtils } from '../../../src/NodeUtils';
 import { StackTraceAnalyzer } from '../../../src/stack-trace-analyzer/StackTraceAnalyzer';
 
 const assert: any = chai.assert;
 
-describe('CustomNodeAppender', () => {
-    describe('appendNode (blockScopeStackTraceData: IStackTraceData[], blockScopeNode: TNodeWithBlockStatement, nodeBodyStatements: TStatement[], index: number = 0): void', () => {
+describe('NodeAppender', () => {
+    describe('appendNode (blockScopeNode: TNodeWithBlockStatement[], nodeBodyStatements: TStatement[]): void', () => {
+        let blockStatementNode: ESTree.BlockStatement,
+            expectedBlockStatementNode: ESTree.BlockStatement,
+            expectedExpressionStatementNode: ESTree.ExpressionStatement,
+            expressionStatementNode: ESTree.ExpressionStatement;
+
+        beforeEach(() => {
+            expressionStatementNode = NodeMocks.getExpressionStatementNode();
+            expectedExpressionStatementNode = NodeMocks.getExpressionStatementNode();
+
+            blockStatementNode = NodeMocks.getBlockStatementNode();
+
+            expectedExpressionStatementNode['parentNode'] = blockStatementNode;
+
+            expectedBlockStatementNode = NodeMocks.getBlockStatementNode([
+                expectedExpressionStatementNode
+            ]);
+
+            NodeAppender.appendNode(
+                blockStatementNode,
+                [expressionStatementNode]
+            );
+            NodeAppender.appendNode(
+                blockStatementNode,
+                <TStatement[]>[{}]
+            );
+        });
+
+        it('should append given node to a `BlockStatement` node body', () => {
+            assert.deepEqual(blockStatementNode, expectedBlockStatementNode);
+        });
+
+        it('should does not change `BlockStatement` node body if given node is not a valid Node', () => {
+            assert.deepEqual(blockStatementNode, expectedBlockStatementNode);
+        });
+    });
+
+    describe('appendNodeToOptimalBlockScope (blockScopeStackTraceData: IStackTraceData[], blockScopeNode: TNodeWithBlockStatement, nodeBodyStatements: TStatement[], index: number = 0): void', () => {
         let astTree: ESTree.Program,
             expectedAstTree: ESTree.Program,
             node: TStatement[],
@@ -92,7 +129,7 @@ describe('CustomNodeAppender', () => {
             );
 
             stackTraceData = new StackTraceAnalyzer(astTree.body).analyze();
-            CustomNodeAppender.appendNode(stackTraceData, astTree, node);
+            NodeAppender.appendNodeToOptimalBlockScope(stackTraceData, astTree, node);
 
             assert.deepEqual(astTree, expectedAstTree);
         });
@@ -165,7 +202,7 @@ describe('CustomNodeAppender', () => {
             );
 
             stackTraceData = new StackTraceAnalyzer(astTree.body).analyze();
-            CustomNodeAppender.appendNode(stackTraceData, astTree, node);
+            NodeAppender.appendNodeToOptimalBlockScope(stackTraceData, astTree, node);
 
             assert.deepEqual(astTree, expectedAstTree);
         });
@@ -243,7 +280,7 @@ describe('CustomNodeAppender', () => {
                 );
 
                 stackTraceData = new StackTraceAnalyzer(astTree.body).analyze();
-                CustomNodeAppender.appendNode(stackTraceData, astTree, node, 2);
+                NodeAppender.appendNodeToOptimalBlockScope(stackTraceData, astTree, node, 2);
 
                 assert.deepEqual(astTree, expectedAstTree);
             });
@@ -283,7 +320,7 @@ describe('CustomNodeAppender', () => {
                 );
 
                 stackTraceData = new StackTraceAnalyzer(astTree.body).analyze();
-                CustomNodeAppender.appendNode(stackTraceData, astTree, node, 1);
+                NodeAppender.appendNodeToOptimalBlockScope(stackTraceData, astTree, node, 1);
 
                 assert.deepEqual(astTree, expectedAstTree);
             });
@@ -353,11 +390,11 @@ describe('CustomNodeAppender', () => {
                 );
 
                 stackTraceData = new StackTraceAnalyzer(astTree.body).analyze();
-                CustomNodeAppender.appendNode(
+                NodeAppender.appendNodeToOptimalBlockScope(
                     stackTraceData,
                     astTree,
                     node,
-                    CustomNodeAppender.getRandomStackTraceIndex(stackTraceData.length)
+                    NodeAppender.getRandomStackTraceIndex(stackTraceData.length)
                 );
 
                 assert.deepEqual(astTree, expectedAstTree);
@@ -370,11 +407,104 @@ describe('CustomNodeAppender', () => {
             let index: number;
 
             for (let i: number = 0; i < 100; i++) {
-                index = CustomNodeAppender.getRandomStackTraceIndex(100);
+                index = NodeAppender.getRandomStackTraceIndex(100);
 
                 assert.isAtLeast(index, 0);
                 assert.isAtMost(index, 100);
             }
         });
     });
+
+    describe('insertNodeAtIndex (blockScopeNode: TNodeWithBlockStatement[], nodeBodyStatements: TStatement[], index: number): void', () => {
+        let blockStatementNode: ESTree.BlockStatement,
+            expectedBlockStatementNode: ESTree.BlockStatement,
+            expressionStatementNode1: ESTree.ExpressionStatement,
+            expressionStatementNode2: ESTree.ExpressionStatement,
+            expressionStatementNode3: ESTree.ExpressionStatement,
+            expressionStatementNode4: ESTree.ExpressionStatement;
+
+        beforeEach(() => {
+            expressionStatementNode1 = NodeMocks.getExpressionStatementNode(NodeMocks.getLiteralNode(1));
+            expressionStatementNode2 = NodeMocks.getExpressionStatementNode(NodeMocks.getLiteralNode(2));
+            expressionStatementNode3 = NodeMocks.getExpressionStatementNode(NodeMocks.getLiteralNode(3));
+            expressionStatementNode4 = NodeMocks.getExpressionStatementNode(NodeMocks.getLiteralNode(2));
+
+            blockStatementNode = NodeMocks.getBlockStatementNode([
+                expressionStatementNode1,
+                expressionStatementNode3
+            ]);
+
+            expressionStatementNode1['parentNode'] = blockStatementNode;
+            expressionStatementNode2['parentNode'] = blockStatementNode;
+            expressionStatementNode3['parentNode'] = blockStatementNode;
+
+            expectedBlockStatementNode = NodeMocks.getBlockStatementNode([
+                expressionStatementNode1,
+                expressionStatementNode2,
+                expressionStatementNode3
+            ]);
+
+            NodeAppender.insertNodeAtIndex(
+                blockStatementNode,
+                [expressionStatementNode4],
+                1
+            );
+            NodeAppender.insertNodeAtIndex(
+                blockStatementNode,
+                <TStatement[]>[{}],
+                1
+            );
+        });
+
+        it('should insert given node in `BlockStatement` node body at index', () => {
+            assert.deepEqual(blockStatementNode, expectedBlockStatementNode);
+        });
+
+        it('should does not change `BlockStatement` node body if given node is not a valid Node', () => {
+            assert.deepEqual(blockStatementNode, expectedBlockStatementNode);
+        });
+    });
+
+    describe('prependNode (blockScopeNode: TNodeWithBlockStatement[], nodeBodyStatements: TStatement[]): void', () => {
+        let blockStatementNode: ESTree.BlockStatement,
+            expectedBlockStatementNode: ESTree.BlockStatement,
+            expressionStatementNode1: ESTree.ExpressionStatement,
+            expressionStatementNode2: ESTree.ExpressionStatement,
+            expressionStatementNode3: ESTree.ExpressionStatement;
+
+        beforeEach(() => {
+            expressionStatementNode1 = NodeMocks.getExpressionStatementNode(NodeMocks.getLiteralNode(1));
+            expressionStatementNode2 = NodeMocks.getExpressionStatementNode(NodeMocks.getLiteralNode(2));
+            expressionStatementNode3 = NodeMocks.getExpressionStatementNode(NodeMocks.getLiteralNode(2));
+
+            blockStatementNode = NodeMocks.getBlockStatementNode([
+                expressionStatementNode1
+            ]);
+
+            expressionStatementNode1['parentNode'] = blockStatementNode;
+            expressionStatementNode2['parentNode'] = blockStatementNode;
+
+            expectedBlockStatementNode = NodeMocks.getBlockStatementNode([
+                expressionStatementNode2,
+                expressionStatementNode1
+            ]);
+
+            NodeAppender.prependNode(
+                blockStatementNode,
+                [Object.assign({}, expressionStatementNode3)]
+            );
+            NodeAppender.prependNode(
+                blockStatementNode,
+                <TStatement[]>[{}]
+            )
+        });
+
+        it('should prepend given node to a `BlockStatement` node body', () => {
+            assert.deepEqual(blockStatementNode, expectedBlockStatementNode);
+        });
+
+        it('should does not change `BlockStatement` node body if given node is not a valid Node', () => {
+            assert.deepEqual(blockStatementNode, expectedBlockStatementNode);
+        });
+    });
 });

Some files were not shown because too many files changed in this diff