Explorar el Código

large rework of node appending mechanics

sanex3339 hace 8 años
padre
commit
2968f1417a
Se han modificado 31 ficheros con 704 adiciones y 473 borrados
  1. 79 46
      dist/index.js
  2. 69 30
      src/NodeUtils.ts
  3. 2 1
      src/Nodes.ts
  4. 6 4
      src/custom-nodes/AbstractCustomNode.ts
  5. 21 12
      src/custom-nodes/CustomNodeAppender.ts
  6. 5 5
      src/custom-nodes/console-output-nodes/ConsoleOutputDisableExpressionNode.ts
  7. 6 7
      src/custom-nodes/debug-protection-nodes/DebugProtectionFunctionCallNode.ts
  8. 6 7
      src/custom-nodes/debug-protection-nodes/DebugProtectionFunctionIntervalNode.ts
  9. 6 7
      src/custom-nodes/debug-protection-nodes/DebugProtectionFunctionNode.ts
  10. 4 5
      src/custom-nodes/domain-lock-nodes/DomainLockNode.ts
  11. 9 7
      src/custom-nodes/self-defending-nodes/SelfDefendingUnicodeNode.ts
  12. 8 9
      src/custom-nodes/unicode-array-nodes/UnicodeArrayCallsWrapper.ts
  13. 8 9
      src/custom-nodes/unicode-array-nodes/UnicodeArrayNode.ts
  14. 8 9
      src/custom-nodes/unicode-array-nodes/UnicodeArrayRotateFunctionNode.ts
  15. 4 2
      src/interfaces/custom-nodes/ICustomNode.d.ts
  16. 2 2
      src/interfaces/stack-trace-analyzer/ICalleeData.d.ts
  17. 4 6
      src/stack-trace-analyzer/callee-data-extractors/FunctionDeclarationCalleeDataExtractor.ts
  18. 4 6
      src/stack-trace-analyzer/callee-data-extractors/FunctionExpressionCalleeDataExtractor.ts
  19. 10 7
      src/stack-trace-analyzer/callee-data-extractors/ObjectExpressionCalleeDataExtractor.ts
  20. 26 10
      src/templates/custom-nodes/self-defending-nodes/self-defending-unicode-node/SelfDefendingTemplate.ts
  21. 1 6
      src/templates/custom-nodes/unicode-array-nodes/unicode-array-calls-wrapper/SelfDefendingTemplate.ts
  22. 72 5
      src/templates/custom-nodes/unicode-array-nodes/unicode-array-rotate-function-node/SelfDefendingTemplate.ts
  23. 3 0
      src/types/TStatement.d.ts
  24. 7 5
      test/functional-tests/custom-nodes/console-output-nodes/ConsoleOutputDisableExpressionNode.spec.ts
  25. 6 4
      test/functional-tests/custom-nodes/domain-lock-nodes/DomainLockNode.spec.ts
  26. 6 2
      test/functional-tests/custom-nodes/unicode-array-nodes/UnicodeArrayCallsWrapper.spec.ts
  27. 8 5
      test/functional-tests/custom-nodes/unicode-array-nodes/UnicodeArrayNode.spec.ts
  28. 12 6
      test/functional-tests/custom-nodes/unicode-array-nodes/UnicodeArrayRotateFunctionNode.spec.ts
  29. 37 27
      test/functional-tests/stack-trace-analyzer/StackTraceAnalyzer.spec.ts
  30. 65 33
      test/unit-tests/NodeUtils.spec.ts
  31. 200 189
      test/unit-tests/custom-nodes/CustomNodeAppender.spec.ts

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 79 - 46
dist/index.js


+ 69 - 30
src/NodeUtils.ts

@@ -4,6 +4,7 @@ import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 
 import { TNodeWithBlockStatement } from './types/TNodeWithBlockStatement';
+import { TStatement } from './types/TStatement';
 
 import { NodeType } from './enums/NodeType';
 
@@ -37,33 +38,36 @@ export class NodeUtils {
     }
 
     /**
-     * @param blockScopeBody
-     * @param node
+     * @param blockScopeNode
+     * @param nodeBodyStatements
      */
-    public static appendNode (blockScopeBody: ESTree.Node[], node: ESTree.Node): void {
-        if (!NodeUtils.validateNode(node)) {
-            return;
+    public static appendNode (
+        blockScopeNode: TNodeWithBlockStatement,
+        nodeBodyStatements: TStatement[]
+    ): void {
+        if (!NodeUtils.validateBodyStatements(nodeBodyStatements)) {
+            nodeBodyStatements = [];
         }
 
-        blockScopeBody.push(node);
+        nodeBodyStatements = NodeUtils.parentizeBodyStatementsBeforeAppend(blockScopeNode, nodeBodyStatements);
+
+        blockScopeNode.body = [
+            ...blockScopeNode.body,
+            ...nodeBodyStatements
+        ];
     }
 
     /**
      * @param code
-     * @param getBlockStatementNodeByIndex
-     * @returns {ESTree.Program|ESTree.Node}
+     * @returns {TStatement[]}
      */
-    public static convertCodeToStructure (code: string, getBlockStatementNodeByIndex: boolean = true): ESTree.Program|ESTree.Node {
+    public static convertCodeToStructure (code: string): TStatement[] {
         let structure: ESTree.Program = esprima.parse(code);
 
         NodeUtils.addXVerbatimPropertyToLiterals(structure);
         NodeUtils.parentize(structure);
 
-        if (!getBlockStatementNodeByIndex) {
-            return structure;
-        }
-
-        return NodeUtils.getBlockStatementNodeByIndex(structure);
+        return <TStatement[]>structure.body;
     }
 
     /**
@@ -117,16 +121,26 @@ export class NodeUtils {
     }
 
     /**
-     * @param blockScopeBody
-     * @param node
+     * @param blockScopeNode
+     * @param nodeBodyStatements
      * @param index
      */
-    public static insertNodeAtIndex (blockScopeBody: ESTree.Node[], node: ESTree.Node, index: number): void {
-        if (!NodeUtils.validateNode(node)) {
-            return;
+    public static insertNodeAtIndex (
+        blockScopeNode: TNodeWithBlockStatement,
+        nodeBodyStatements: TStatement[],
+        index: number
+    ): void {
+        if (!NodeUtils.validateBodyStatements(nodeBodyStatements)) {
+            nodeBodyStatements = [];
         }
 
-        blockScopeBody.splice(index, 0, node);
+        nodeBodyStatements = NodeUtils.parentizeBodyStatementsBeforeAppend(blockScopeNode, nodeBodyStatements);
+
+        blockScopeNode.body = [
+            ...blockScopeNode.body.slice(0, index),
+            ...nodeBodyStatements,
+            ...blockScopeNode.body.slice(index)
+        ];
     }
 
     /**
@@ -143,7 +157,7 @@ export class NodeUtils {
                     if (node.type === NodeType.Program) {
                         value = node;
                     } else {
-                        value = Nodes.getProgramNode(<ESTree.Statement[]>[node]);
+                        value = Nodes.getProgramNode(<TStatement[]>[node]);
                         value['parentNode'] = value;
                     }
 
@@ -159,15 +173,23 @@ export class NodeUtils {
     }
 
     /**
-     * @param blockScopeBody
-     * @param node
+     * @param blockScopeNode
+     * @param nodeBodyStatements
      */
-    public static prependNode (blockScopeBody: ESTree.Node[], node: ESTree.Node): void {
-        if (!NodeUtils.validateNode(node)) {
-            return;
+    public static prependNode (
+        blockScopeNode: TNodeWithBlockStatement,
+        nodeBodyStatements: TStatement[]
+    ): void {
+        if (!NodeUtils.validateBodyStatements(nodeBodyStatements)) {
+            nodeBodyStatements = [];
         }
 
-        blockScopeBody.unshift(node);
+        nodeBodyStatements = NodeUtils.parentizeBodyStatementsBeforeAppend(blockScopeNode, nodeBodyStatements);
+
+        blockScopeNode.body = [
+            ...nodeBodyStatements,
+            ...blockScopeNode.body,
+        ];
     }
 
     /**
@@ -210,10 +232,27 @@ export class NodeUtils {
     }
 
     /**
-     * @param node
+     * @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 validateNode (node: ESTree.Node): boolean {
-        return !!node && node.hasOwnProperty('type');
+    private static validateBodyStatements (nodeBodyStatements: TStatement[]): boolean {
+        return nodeBodyStatements.every(statementNode => {
+            return !!statementNode && statementNode.hasOwnProperty('type');
+        });
     }
 }

+ 2 - 1
src/Nodes.ts

@@ -1,6 +1,7 @@
 import * as ESTree from 'estree';
 
 import { TNodeWithBlockStatement } from './types/TNodeWithBlockStatement';
+import { TStatement } from './types/TStatement';
 
 import { NodeType } from './enums/NodeType';
 
@@ -9,7 +10,7 @@ export class Nodes {
      * @param bodyNode
      * @returns ESTree.Program
      */
-    public static getProgramNode (bodyNode: ESTree.Statement[]): ESTree.Program {
+    public static getProgramNode (bodyNode: TStatement[]): ESTree.Program {
         return {
             'type': NodeType.Program,
             'body': bodyNode,

+ 6 - 4
src/custom-nodes/AbstractCustomNode.ts

@@ -1,5 +1,7 @@
 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';
@@ -38,14 +40,14 @@ export abstract class AbstractCustomNode implements ICustomNode {
     }
 
     /**
-     * @returns {ESTree.Node}
+     * @returns {TStatement[]}
      */
-    public getNode (): ESTree.Node {
+    public getNode (): TStatement[] {
         return this.getNodeStructure();
     }
 
     /**
-     * @returns {ESTree.Node}
+     * @returns {TStatement[]}
      */
-    protected abstract getNodeStructure (): ESTree.Node;
+    protected abstract getNodeStructure (): TStatement[];
 }

+ 21 - 12
src/custom-nodes/CustomNodeAppender.ts

@@ -1,5 +1,8 @@
 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';
@@ -25,25 +28,28 @@ import { Utils } from '../Utils';
 export class CustomNodeAppender {
     /**
      * @param blockScopeStackTraceData
-     * @param blockScopeBody
-     * @param node
+     * @param blockScopeNode
+     * @param nodeBodyStatements
      * @param index
      */
     public static appendNode (
         blockScopeStackTraceData: IStackTraceData[],
-        blockScopeBody: ESTree.Node[],
-        node: ESTree.Node,
+        blockScopeNode: TNodeWithBlockStatement,
+        nodeBodyStatements: TStatement[],
         index: number = 0
     ): void {
-        let targetBlockScopeBody: ESTree.Node[];
+        let targetBlockScope: TNodeWithBlockStatement;
 
         if (!blockScopeStackTraceData.length) {
-            targetBlockScopeBody = blockScopeBody;
+            targetBlockScope = blockScopeNode;
         } else {
-            targetBlockScopeBody = CustomNodeAppender.getOptimalBlockScopeBody(blockScopeStackTraceData, index);
+            targetBlockScope = CustomNodeAppender.getOptimalBlockScope(
+                blockScopeStackTraceData,
+                index
+            );
         }
 
-        NodeUtils.prependNode(targetBlockScopeBody, node);
+        NodeUtils.prependNode(targetBlockScope, nodeBodyStatements);
     }
 
     /**
@@ -59,15 +65,18 @@ export class CustomNodeAppender {
     /**
      * @param blockScopeTraceData
      * @param index
-     * @returns {ESTree.Node[]}
+     * @returns {ESTree.BlockStatement}
      */
-    private static getOptimalBlockScopeBody (blockScopeTraceData: IStackTraceData[], index: number): ESTree.Node[] {
+    private static getOptimalBlockScope (
+        blockScopeTraceData: IStackTraceData[],
+        index: number
+    ): ESTree.BlockStatement {
         const firstCall: IStackTraceData = blockScopeTraceData[index];
 
         if (firstCall.stackTrace.length) {
-            return CustomNodeAppender.getOptimalBlockScopeBody(firstCall.stackTrace, 0);
+            return CustomNodeAppender.getOptimalBlockScope(firstCall.stackTrace, 0);
         } else {
-            return firstCall.callee.body;
+            return firstCall.callee;
         }
     }
 }

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

@@ -1,7 +1,7 @@
-import * as ESTree from 'estree';
-
 import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
 
+import { TStatement } from '../../types/TStatement';
+
 import { IStackTraceData } from '../../interfaces/stack-trace-analyzer/IStackTraceData';
 
 import { AppendState } from '../../enums/AppendState';
@@ -25,7 +25,7 @@ export class ConsoleOutputDisableExpressionNode extends AbstractCustomNode {
     public appendNode (blockScopeNode: TNodeWithBlockStatement, stackTraceData: IStackTraceData[]): void {
         CustomNodeAppender.appendNode(
             stackTraceData,
-            blockScopeNode.body,
+            blockScopeNode,
             this.getNode(),
             CustomNodeAppender.getRandomStackTraceIndex(stackTraceData.length)
         );
@@ -45,9 +45,9 @@ export class ConsoleOutputDisableExpressionNode extends AbstractCustomNode {
      *  _console
      *  })();
      *
-     * @returns {ESTree.Node}
+     * @returns {TStatement[]}
      */
-    protected getNodeStructure (): ESTree.Node {
+    protected getNodeStructure (): TStatement[] {
         return NodeUtils.convertCodeToStructure(
             ConsoleOutputDisableExpressionTemplate()
         );

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

@@ -1,10 +1,9 @@
-import * as ESTree from 'estree';
-
 import 'format-unicorn';
 
-import { IOptions } from '../../interfaces/IOptions';
-
 import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
+import { TStatement } from '../../types/TStatement';
+
+import { IOptions } from '../../interfaces/IOptions';
 
 import { AppendState } from '../../enums/AppendState';
 
@@ -38,13 +37,13 @@ export class DebugProtectionFunctionCallNode extends AbstractCustomNode {
      * @param blockScopeNode
      */
     public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
-        NodeUtils.appendNode(blockScopeNode.body, this.getNode());
+        NodeUtils.appendNode(blockScopeNode, this.getNode());
     }
 
     /**
-     * @returns {ESTree.Node}
+     * @returns {TStatement[]}
      */
-    protected getNodeStructure (): ESTree.Node {
+    protected getNodeStructure (): TStatement[] {
         return NodeUtils.convertCodeToStructure(
             DebugProtectionFunctionCallTemplate().formatUnicorn({
                 debugProtectionFunctionName: this.debugProtectionFunctionName

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

@@ -1,10 +1,9 @@
-import * as ESTree from 'estree';
-
 import 'format-unicorn';
 
-import { IOptions } from '../../interfaces/IOptions';
-
 import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
+import { TStatement } from '../../types/TStatement';
+
+import { IOptions } from '../../interfaces/IOptions';
 
 import { AppendState } from '../../enums/AppendState';
 
@@ -38,13 +37,13 @@ export class DebugProtectionFunctionIntervalNode extends AbstractCustomNode {
      * @param blockScopeNode
      */
     public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
-        NodeUtils.appendNode(blockScopeNode.body, this.getNode());
+        NodeUtils.appendNode(blockScopeNode, this.getNode());
     }
 
     /**
-     * @returns {ESTree.Node}
+     * @returns {TStatement[]}
      */
-    protected getNodeStructure (): ESTree.Node {
+    protected getNodeStructure (): TStatement[] {
         return NodeUtils.convertCodeToStructure(
             DebugProtectionFunctionIntervalTemplate().formatUnicorn({
                 debugProtectionFunctionName: this.debugProtectionFunctionName

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

@@ -1,10 +1,9 @@
-import * as ESTree from 'estree';
-
 import 'format-unicorn';
 
-import { IOptions } from '../../interfaces/IOptions';
-
 import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
+import { TStatement } from '../../types/TStatement';
+
+import { IOptions } from '../../interfaces/IOptions';
 
 import { AppendState } from '../../enums/AppendState';
 
@@ -45,7 +44,7 @@ export class DebugProtectionFunctionNode extends AbstractCustomNode {
                 max: programBodyLength
             });
 
-        NodeUtils.insertNodeAtIndex(blockScopeNode.body, this.getNode(), randomIndex);
+        NodeUtils.insertNodeAtIndex(blockScopeNode, this.getNode(), randomIndex);
     }
 
     /**
@@ -58,9 +57,9 @@ export class DebugProtectionFunctionNode extends AbstractCustomNode {
     /**
      * Found this trick in JScrambler
      *
-     * @returns {ESTree.Node}
+     * @returns {TStatement[]}
      */
-    protected getNodeStructure (): ESTree.Node {
+    protected getNodeStructure (): TStatement[] {
         return NodeUtils.convertCodeToStructure(
             DebugProtectionFunctionTemplate().formatUnicorn({
                 debugProtectionFunctionName: this.debugProtectionFunctionName

+ 4 - 5
src/custom-nodes/domain-lock-nodes/DomainLockNode.ts

@@ -1,8 +1,7 @@
-import * as ESTree from 'estree';
-
 import 'format-unicorn';
 
 import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
+import { TStatement } from '../../types/TStatement';
 
 import { IStackTraceData } from '../../interfaces/stack-trace-analyzer/IStackTraceData';
 
@@ -28,16 +27,16 @@ export class DomainLockNode extends AbstractCustomNode {
     public appendNode (blockScopeNode: TNodeWithBlockStatement, stackTraceData: IStackTraceData[]): void {
         CustomNodeAppender.appendNode(
             stackTraceData,
-            blockScopeNode.body,
+            blockScopeNode,
             this.getNode(),
             CustomNodeAppender.getRandomStackTraceIndex(stackTraceData.length)
         );
     }
 
     /**
-     * @returns {ESTree.Node}
+     * @returns {TStatement[]}
      */
-    protected getNodeStructure (): ESTree.Node {
+    protected getNodeStructure (): TStatement[] {
         let domainsString: string = this.options.domainLock.join(';'),
             [hiddenDomainsString, diff]: string[] = Utils.hideString(domainsString, domainsString.length * 3);
 

+ 9 - 7
src/custom-nodes/self-defending-nodes/SelfDefendingUnicodeNode.ts

@@ -1,6 +1,5 @@
-import * as ESTree from 'estree';
-
 import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
+import { TStatement } from '../../types/TStatement';
 
 import { IStackTraceData } from '../../interfaces/stack-trace-analyzer/IStackTraceData';
 
@@ -11,9 +10,10 @@ 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 { JavaScriptObfuscator } from '../../JavaScriptObfuscator';
 import { NodeUtils } from '../../NodeUtils';
-import { CustomNodeAppender } from '../CustomNodeAppender';
+import { Utils } from '../../Utils';
 
 export class SelfDefendingUnicodeNode extends AbstractCustomNode {
     /**
@@ -28,19 +28,21 @@ export class SelfDefendingUnicodeNode extends AbstractCustomNode {
     public appendNode (blockScopeNode: TNodeWithBlockStatement, stackTraceData: IStackTraceData[]): void {
         CustomNodeAppender.appendNode(
             stackTraceData,
-            blockScopeNode.body,
+            blockScopeNode,
             this.getNode(),
             CustomNodeAppender.getRandomStackTraceIndex(stackTraceData.length)
         );
     }
 
     /**
-     * @returns {ESTree.Node}
+     * @returns {TStatement[]}
      */
-    protected getNodeStructure (): ESTree.Node {
+    protected getNodeStructure (): TStatement[] {
         return NodeUtils.convertCodeToStructure(
             JavaScriptObfuscator.obfuscate(
-                SelfDefendingTemplate(),
+                SelfDefendingTemplate().formatUnicorn({
+                    selfDefendingFunctionName: Utils.getRandomVariableName()
+                }),
                 NO_CUSTOM_NODES_PRESET
             ).getObfuscatedCode()
         );

+ 8 - 9
src/custom-nodes/unicode-array-nodes/UnicodeArrayCallsWrapper.ts

@@ -1,10 +1,9 @@
-import * as ESTree from 'estree';
-
 import 'format-unicorn';
 
-import { IOptions } from '../../interfaces/IOptions';
-
 import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
+import { TStatement } from '../../types/TStatement';
+
+import { IOptions } from '../../interfaces/IOptions';
 
 import { AppendState } from '../../enums/AppendState';
 import { UnicodeArrayEncoding } from '../../enums/UnicodeArrayEncoding';
@@ -71,7 +70,7 @@ export class UnicodeArrayCallsWrapper extends AbstractCustomNode {
             return;
         }
 
-        NodeUtils.insertNodeAtIndex(blockScopeNode.body, this.getNode(), 1);
+        NodeUtils.insertNodeAtIndex(blockScopeNode, this.getNode(), 1);
     }
 
     /**
@@ -82,9 +81,9 @@ export class UnicodeArrayCallsWrapper extends AbstractCustomNode {
     };
 
     /**
-     * @returns {ESTree.Node}
+     * @returns {TStatement[]}
      */
-    public getNode (): ESTree.Node {
+    public getNode (): TStatement[] {
         return super.getNode();
     }
 
@@ -127,9 +126,9 @@ export class UnicodeArrayCallsWrapper extends AbstractCustomNode {
     }
 
     /**
-     * @returns {ESTree.Node}
+     * @returns {TStatement[]}
      */
-    protected getNodeStructure (): ESTree.Node {
+    protected getNodeStructure (): TStatement[] {
         const decodeNodeTemplate: string = this.getDecodeUnicodeArrayTemplate();
 
         return NodeUtils.convertCodeToStructure(

+ 8 - 9
src/custom-nodes/unicode-array-nodes/UnicodeArrayNode.ts

@@ -1,10 +1,9 @@
-import * as ESTree from 'estree';
-
 import 'format-unicorn';
 
-import { IOptions } from '../../interfaces/IOptions';
-
 import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
+import { TStatement } from '../../types/TStatement';
+
+import { IOptions } from '../../interfaces/IOptions';
 
 import { AppendState } from '../../enums/AppendState';
 
@@ -68,7 +67,7 @@ export class UnicodeArrayNode extends AbstractCustomNode {
             return;
         }
 
-        NodeUtils.prependNode(blockScopeNode.body, this.getNode());
+        NodeUtils.prependNode(blockScopeNode, this.getNode());
     }
 
     /**
@@ -86,9 +85,9 @@ export class UnicodeArrayNode extends AbstractCustomNode {
     }
 
     /**
-     * @returns {ESTree.Node}
+     * @returns {TStatement[]}
      */
-    public getNode (): ESTree.Node {
+    public getNode (): TStatement[] {
         this.unicodeArray.rotateArray(this.unicodeArrayRotateValue);
 
         return super.getNode();
@@ -102,9 +101,9 @@ export class UnicodeArrayNode extends AbstractCustomNode {
     }
 
     /**
-     * @returns {ESTree.Node}
+     * @returns {TStatement[]}
      */
-    protected getNodeStructure (): ESTree.Node {
+    protected getNodeStructure (): TStatement[] {
         return NodeUtils.convertCodeToStructure(
             UnicodeArrayTemplate().formatUnicorn({
                 unicodeArrayName: this.unicodeArrayName,

+ 8 - 9
src/custom-nodes/unicode-array-nodes/UnicodeArrayRotateFunctionNode.ts

@@ -1,10 +1,9 @@
-import * as ESTree from 'estree';
-
 import 'format-unicorn';
 
-import { IOptions } from '../../interfaces/IOptions';
-
 import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
+import { TStatement } from '../../types/TStatement';
+
+import { IOptions } from '../../interfaces/IOptions';
 
 import { AppendState } from '../../enums/AppendState';
 
@@ -67,20 +66,20 @@ export class UnicodeArrayRotateFunctionNode extends AbstractCustomNode {
             return;
         }
 
-        NodeUtils.insertNodeAtIndex(blockScopeNode.body, this.getNode(), 1);
+        NodeUtils.insertNodeAtIndex(blockScopeNode, this.getNode(), 1);
     }
 
     /**
-     * @returns {ESTree.Node}
+     * @returns {TStatement[]}
      */
-    public getNode (): ESTree.Node {
+    public getNode (): TStatement[] {
         return super.getNode();
     }
 
     /**
-     * @returns {ESTree.Node}
+     * @returns {TStatement[]}
      */
-    protected getNodeStructure (): ESTree.Node {
+    protected getNodeStructure (): TStatement[] {
         let code: string = '',
             timesName: string = Utils.getRandomVariableName(),
             whileFunctionName: string = Utils.getRandomVariableName();

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

@@ -1,5 +1,7 @@
 import * as ESTree from 'estree';
 
+import { TStatement } from '../../types/TStatement';
+
 import { IStackTraceData } from '../stack-trace-analyzer/IStackTraceData';
 
 import { AppendState } from '../../enums/AppendState';
@@ -17,7 +19,7 @@ export interface ICustomNode {
     getAppendState (): AppendState;
 
     /**
-     * @returns INode
+     * @returns ESTree.Node[]
      */
-    getNode (): ESTree.Node;
+    getNode (): TStatement[];
 }

+ 2 - 2
src/interfaces/stack-trace-analyzer/ICalleeData.d.ts

@@ -1,6 +1,6 @@
-import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
+import * as ESTree from 'estree';
 
 export interface ICalleeData {
-    callee: TNodeWithBlockStatement;
+    callee: ESTree.BlockStatement;
     name: string | null;
 }

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

@@ -1,8 +1,6 @@
 import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 
-import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
-
 import { ICalleeData } from '../../interfaces/stack-trace-analyzer/ICalleeData';
 import { ICalleeDataExtractor } from '../../interfaces/stack-trace-analyzer/ICalleeDataExtractor';
 
@@ -33,7 +31,7 @@ export class FunctionDeclarationCalleeDataExtractor implements ICalleeDataExtrac
      * @returns {ICalleeData|null}
      */
     public extract (): ICalleeData|null {
-        let calleeBlockStatement: TNodeWithBlockStatement|null = null;
+        let calleeBlockStatement: ESTree.BlockStatement|null = null;
 
         if (Nodes.isIdentifierNode(this.callee)) {
             calleeBlockStatement = this.getCalleeBlockStatement(
@@ -55,10 +53,10 @@ export class FunctionDeclarationCalleeDataExtractor implements ICalleeDataExtrac
     /**
      * @param node
      * @param name
-     * @returns {TNodeWithBlockStatement|null}
+     * @returns {ESTree.BlockStatement|null}
      */
-    private getCalleeBlockStatement (node: ESTree.Node, name: string): TNodeWithBlockStatement|null {
-        let calleeBlockStatement: TNodeWithBlockStatement|null = null;
+    private getCalleeBlockStatement (node: ESTree.Node, name: string): ESTree.BlockStatement|null {
+        let calleeBlockStatement: ESTree.BlockStatement|null = null;
 
         estraverse.traverse(node, {
             enter: (node: ESTree.Node): any => {

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

@@ -1,8 +1,6 @@
 import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 
-import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
-
 import { ICalleeData } from '../../interfaces/stack-trace-analyzer/ICalleeData';
 import { ICalleeDataExtractor } from '../../interfaces/stack-trace-analyzer/ICalleeDataExtractor';
 
@@ -33,7 +31,7 @@ export class FunctionExpressionCalleeDataExtractor implements ICalleeDataExtract
      * @returns {ICalleeData|null}
      */
     public extract (): ICalleeData|null {
-        let calleeBlockStatement: TNodeWithBlockStatement|null = null;
+        let calleeBlockStatement: ESTree.BlockStatement|null = null;
 
         if (Nodes.isIdentifierNode(this.callee)) {
             calleeBlockStatement = this.getCalleeBlockStatement(
@@ -59,10 +57,10 @@ export class FunctionExpressionCalleeDataExtractor implements ICalleeDataExtract
     /**
      * @param node
      * @param name
-     * @returns {TNodeWithBlockStatement|null}
+     * @returns {ESTree.BlockStatement|null}
      */
-    private getCalleeBlockStatement (node: ESTree.Node, name: string): TNodeWithBlockStatement|null {
-        let calleeBlockStatement: TNodeWithBlockStatement|null = null;
+    private getCalleeBlockStatement (node: ESTree.Node, name: string): ESTree.BlockStatement|null {
+        let calleeBlockStatement: ESTree.BlockStatement|null = null;
 
         estraverse.traverse(node, {
             enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {

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

@@ -1,8 +1,6 @@
 import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 
-import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
-
 import { ICalleeData } from '../../interfaces/stack-trace-analyzer/ICalleeData';
 import { ICalleeDataExtractor } from '../../interfaces/stack-trace-analyzer/ICalleeDataExtractor';
 
@@ -38,7 +36,7 @@ export class ObjectExpressionCalleeDataExtractor implements ICalleeDataExtractor
      * @returns {ICalleeData|null}
      */
     public extract (): ICalleeData|null {
-        let calleeBlockStatement: TNodeWithBlockStatement|null = null,
+        let calleeBlockStatement: ESTree.BlockStatement|null = null,
             functionExpressionName: string|null = null;
 
         if (Nodes.isMemberExpressionNode(this.callee)) {
@@ -98,12 +96,12 @@ export class ObjectExpressionCalleeDataExtractor implements ICalleeDataExtractor
     /**
      * @param node
      * @param objectMembersCallsChain
-     * @returns {TNodeWithBlockStatement|null}
+     * @returns {ESTree.BlockStatement|null}
      */
-    private getCalleeBlockStatement (node: ESTree.Node, objectMembersCallsChain: string[]): TNodeWithBlockStatement|null {
+    private getCalleeBlockStatement (node: ESTree.Node, objectMembersCallsChain: string[]): ESTree.BlockStatement|null {
         const objectName: string = <string>objectMembersCallsChain.shift();
 
-        let calleeBlockStatement: TNodeWithBlockStatement|null = null;
+        let calleeBlockStatement: ESTree.BlockStatement|null = null;
 
         estraverse.traverse(node, {
             enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
@@ -124,10 +122,15 @@ export class ObjectExpressionCalleeDataExtractor implements ICalleeDataExtractor
         return calleeBlockStatement;
     }
 
+    /**
+     * @param objectExpressionProperties
+     * @param objectMembersCallsChain
+     * @returns {ESTree.BlockStatement|null}
+     */
     private findCalleeBlockStatement (
         objectExpressionProperties: ESTree.Property[],
         objectMembersCallsChain: string[]
-    ): TNodeWithBlockStatement|null {
+    ): ESTree.BlockStatement|null {
         const nextItemInCallsChain: string|undefined = objectMembersCallsChain.shift();
 
         if (!nextItemInCallsChain) {

+ 26 - 10
src/templates/custom-nodes/self-defending-nodes/self-defending-unicode-node/SelfDefendingTemplate.ts

@@ -1,35 +1,41 @@
 import { Utils } from '../../../../Utils';
 
 /**
- * SelfDefendingTemplate. Enter code in infinity loop.
- * Notice, that second call to recursiveFunc1('indexOf') has cyrillic `е` character instead latin
+ * SelfDefendingTemplate. Enters code in infinity loop.
+ * Notice, that second and third call to recursiveFunc1('indexOf') has cyrillic `е` character instead latin
  *
  * @returns {string}
  */
 export function SelfDefendingTemplate (): string {
     return `
-        (function () {                                
+        function {selfDefendingFunctionName} () {
+            if ({selfDefendingFunctionName}.firstRun) {
+                return false;
+            }
+                                        
+            {selfDefendingFunctionName}.firstRun = true;
+            
             var func1 = function(){return 'dev';},
                 func2 = function () {
                     return 'window';
                 };
                 
             var test1 = function () {
-                var regExp = new RegExp(${Utils.stringToUnicode(`return/\\w+ *\\(\\) *{\\w+ *['|"].+['|"];? *}/`)});
+                var regExp = new RegExp(${Utils.stringToUnicode(`\\w+ *\\(\\) *{\\w+ *['|"].+['|"];? *}`)});
                 
                 return !regExp.test(func1.toString());
             };
             
             var test2 = function () {
-                var regExp = new RegExp(${Utils.stringToUnicode(`return/(\\\\[x|u](\\w){2,4})+/`)});
+                var regExp = new RegExp(${Utils.stringToUnicode(`(\\\\[x|u](\\w){2,4})+`)});
                 
                 return regExp.test(func2.toString());
             };
             
             var recursiveFunc1 = function (string) {
-                var i = ~1 >> 1 + 255 % 0;
-                
-                if (string.indexOf(([false]+undefined)[10]) === i) {
+                var i = ~-1 >> 1 + 255 % 0;
+                                
+                if (string.indexOf('i' === i)) {
                     recursiveFunc2(string)
                 }
             };
@@ -42,7 +48,17 @@ export function SelfDefendingTemplate (): string {
                 }
             };
             
-            !test1() ? test2() ? (function () { recursiveFunc1('indexOf') })() : (function () { recursiveFunc1('indеxOf') })() : (function () { recursiveFunc1('indexOf') })();
-        })();
+            if (!test1()) {
+                if (!test2()) {
+                    recursiveFunc1('indеxOf');
+                } else {
+                    recursiveFunc1('indexOf');
+                }
+            } else {
+                recursiveFunc1('indеxOf');
+            }
+        }
+        
+        {selfDefendingFunctionName}();
     `;
 }

+ 1 - 6
src/templates/custom-nodes/unicode-array-nodes/unicode-array-calls-wrapper/SelfDefendingTemplate.ts

@@ -1,13 +1,8 @@
-import { Utils } from '../../../../Utils';
-
 /**
  * @returns {string}
  */
 export function SelfDefendingTemplate (): string {
     return `
-        var func = function(){return 'dev';};
-        var object = []['filter']['constructor'];
-                           
-        !{unicodeArrayCallsWrapperName}.flag ? ({unicodeArrayCallsWrapperName}.flag = true, Function(${Utils.stringToUnicode(`return/\\w+ *\\(\\) *{\\w+ *['|"].+['|"];? *}/`)})()['test'](func['toString']()) !== true && !{unicodeArrayName}++ ? object(${Utils.stringToJSFuck('while')} + '(true){}')() : false ? object(${Utils.stringToJSFuck('while')} + '(false){}')() : object(${Utils.stringToJSFuck('while')} + '(false){}')()) : false;
+        
     `;
 }

+ 72 - 5
src/templates/custom-nodes/unicode-array-nodes/unicode-array-rotate-function-node/SelfDefendingTemplate.ts

@@ -1,12 +1,79 @@
 import { Utils } from '../../../../Utils';
 
 /**
+ * SelfDefendingTemplate. Enter code in infinity loop.
+ *
  * @returns {string}
  */
 export function SelfDefendingTemplate (): string {
-    return `(function () {
-        var func = function(){return 'dev';};
-                            
-        !Function(${Utils.stringToUnicode(`return/\\w+ *\\(\\) *{\\w+ *['|"].+['|"];? *}/`)})().test(func.toString()) ? []['filter']['constructor'](${Utils.stringToJSFuck('while')} + '(true){}')() : Function('a', 'b', 'a(++b)')({whileFunctionName}, {timesName}) ? []['filter']['constructor'](${Utils.stringToJSFuck('while')} + '(false){}')() : []['filter']['constructor'](${Utils.stringToJSFuck('while')} + '(false){}')();
-    })();`;
+    return `
+        var selfDefendingFunc = function () {            
+            var object = {
+                data: {
+                    key: 'cookie',
+                    value: 'timeout'
+                },
+                setCookie: function (options, name, value, document) {
+                    document = document || {};
+                    
+                    var updatedCookie = name + "=" + value;
+
+                    var i = 0;
+                                                            
+                    for (var i = 0, len = options.length; i < len; i++) {                          
+                        var propName = options[i];
+                                     
+                        updatedCookie += "; " + propName;
+                        
+                        var propValue = options[propName];
+                        
+                        options.push(propValue);
+                        len = options.length;
+                                                                        
+                        if (propValue !== true) {
+                            updatedCookie += "=" + propValue;
+                        }
+                    }
+
+                    document['cookie'] = updatedCookie;
+                },
+                removeCookie: function(){return 'dev';},
+                getCookie: function (document, name) {    
+                    document = document || function (value) { return value };
+                    var matches = document(new RegExp(
+                        "(?:^|; )" + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + "=([^;]*)"
+                    ));
+                    
+                    var func = function (param1, param2) {
+                        param1(++param2);
+                    };
+                    
+                    func({whileFunctionName}, {timesName});
+                                        
+                    return matches ? decodeURIComponent(matches[1]) : undefined;
+                }
+            };
+            
+            var test1 = function () {
+                var regExp = new RegExp(${Utils.stringToUnicode(`\\w+ *\\(\\) *{\\w+ *['|"].+['|"];? *}`)});
+                
+                return regExp.test(object.removeCookie.toString());
+            };
+            
+            object['updateCookie'] = test1;
+            
+            var cookie = '';
+            var result = object['updateCookie']();
+                                    
+            if (!result) {
+                object['setCookie'](['*'], 'counter', 1);
+            } else if (result) {
+                cookie = object['getCookie'](null, 'counter');     
+            } else {
+                object['removeCookie']();
+            }
+        };
+        
+        selfDefendingFunc();
+    `;
 }

+ 3 - 0
src/types/TStatement.d.ts

@@ -0,0 +1,3 @@
+import * as ESTree from 'estree';
+
+export type TStatement = ESTree.Statement|ESTree.ModuleDeclaration;

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

@@ -1,5 +1,7 @@
 import { IObfuscationResult } from '../../../../src/interfaces/IObfuscationResult';
 
+import { NO_CUSTOM_NODES_PRESET } from '../../../../src/preset-options/NoCustomNodesPreset';
+
 import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
 
 const assert: Chai.AssertStatic = require('chai').assert;
@@ -8,22 +10,22 @@ describe('ConsoleOutputDisableExpressionNode', () => {
     it('should correctly appendNode `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, {
                 disableConsoleOutput: true,
                 unicodeArrayThreshold: 1
-            }
+            })
         );
 
-        assert.match(obfuscationResult.getObfuscatedCode(), /for *\(_0x([a-z0-9]){4,6} in _0x([a-z0-9]){4,6} *= *_0x([a-z0-9]){4}\('0x.*'\)\)/);
+        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', () => {
         let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
             `var test = 'test';`,
-            {
+            Object.assign({}, NO_CUSTOM_NODES_PRESET, {
                 disableConsoleOutput: false,
                 unicodeArrayThreshold: 1
-            }
+            })
         );
 
         assert.notMatch(obfuscationResult.getObfuscatedCode(), /for *\(_0x([a-z0-9]){4,6} in _0x([a-z0-9]){4,6} *= *_0x([a-z0-9]){4}\('0x.*'\)\)/);

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

@@ -1,5 +1,7 @@
 import { IObfuscationResult } from '../../../../src/interfaces/IObfuscationResult';
 
+import { NO_CUSTOM_NODES_PRESET } from '../../../../src/preset-options/NoCustomNodesPreset';
+
 import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
 
 const assert: Chai.AssertStatic = require('chai').assert;
@@ -8,9 +10,9 @@ describe('DomainLockNode', () => {
     it('should correctly appendNode `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, {
                 domainLock: ['.example.com']
-            }
+            })
         );
 
         assert.match(obfuscationResult.getObfuscatedCode(), /var _0x([a-z0-9]){4,6} *= *new RegExp/);
@@ -19,9 +21,9 @@ describe('DomainLockNode', () => {
     it('should\'t appendNode `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, {
                 domainLock: []
-            }
+            })
         );
 
         assert.notMatch(obfuscationResult.getObfuscatedCode(), /var _0x([a-z0-9]){4,6} *= *new RegExp/);

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

@@ -1,5 +1,7 @@
 import { IObfuscationResult } from '../../../../src/interfaces/IObfuscationResult';
 
+import { NO_CUSTOM_NODES_PRESET } from '../../../../src/preset-options/NoCustomNodesPreset';
+
 import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
 
 const assert: Chai.AssertStatic = require('chai').assert;
@@ -8,9 +10,11 @@ describe('UnicodeArrayCallsWrapper', () => {
     it('should correctly appendNode `UnicodeArrayCallsWrapper` custom node into the obfuscated code', () => {
         let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
             `var test = 'test';`,
-            {
+            Object.assign({}, NO_CUSTOM_NODES_PRESET, {
+                unicodeArray: true,
+                unicodeArrayThreshold: 1,
                 wrapUnicodeArrayCalls: true
-            }
+            })
         );
 
         assert.match(

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

@@ -1,5 +1,7 @@
 import { IObfuscationResult } from '../../../../src/interfaces/IObfuscationResult';
 
+import { NO_CUSTOM_NODES_PRESET } from '../../../../src/preset-options/NoCustomNodesPreset';
+
 import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
 
 const assert: Chai.AssertStatic = require('chai').assert;
@@ -8,9 +10,10 @@ describe('UnicodeArrayNode', () => {
     it('should correctly appendNode `UnicodeArrayNode` custom node into the obfuscated code if `unicodeArray` option is set', () => {
         let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
             `var test = 'test';`,
-            {
-                unicodeArray: true
-            }
+            Object.assign({}, NO_CUSTOM_NODES_PRESET, {
+                unicodeArray: true,
+                unicodeArrayThreshold: 1
+            })
         );
 
         assert.match(obfuscationResult.getObfuscatedCode(), /^var _0x([a-z0-9]){4} *= *\[/);
@@ -19,9 +22,9 @@ describe('UnicodeArrayNode', () => {
     it('should\'t appendNode `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, {
                 unicodeArray: false
-            }
+            })
         );
 
         assert.notMatch(obfuscationResult.getObfuscatedCode(), /^var _0x([a-z0-9]){4} *= *\[/);

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

@@ -1,5 +1,7 @@
 import { IObfuscationResult } from '../../../../src/interfaces/IObfuscationResult';
 
+import { NO_CUSTOM_NODES_PRESET } from '../../../../src/preset-options/NoCustomNodesPreset';
+
 import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
 
 const assert: Chai.AssertStatic = require('chai').assert;
@@ -8,9 +10,11 @@ describe('UnicodeArrayRotateFunctionNode', () => {
     it('should correctly appendNode `UnicodeArrayRotateFunctionNode` custom node into the obfuscated code if `rotateUnicodeArray` option is set', () => {
         let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
             `var test = 'test';`,
-            {
-                rotateUnicodeArray: true
-            }
+            Object.assign({}, NO_CUSTOM_NODES_PRESET, {
+                rotateUnicodeArray: true,
+                unicodeArray: true,
+                unicodeArrayThreshold: 1
+            })
         );
 
         assert.match(obfuscationResult.getObfuscatedCode(), /while *\(-- *_0x([a-z0-9]){4,6}\) *\{/);
@@ -19,9 +23,11 @@ describe('UnicodeArrayRotateFunctionNode', () => {
     it('should\'t appendNode `UnicodeArrayRotateFunctionNode` custom node into the obfuscated code if `rotateUnicodeArray` option is not set', () => {
         let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
             `var test = 'test';`,
-            {
-                rotateUnicodeArray: false
-            }
+            Object.assign({}, NO_CUSTOM_NODES_PRESET, {
+                rotateUnicodeArray: false,
+                unicodeArray: true,
+                unicodeArrayThreshold: 1
+            })
         );
 
         assert.notMatch(obfuscationResult.getObfuscatedCode(), /while *\(-- *_0x([a-z0-9]){4,6}\) *\{/);

+ 37 - 27
test/functional-tests/stack-trace-analyzer/StackTraceAnalyzer.spec.ts

@@ -9,6 +9,7 @@ import { IStackTraceData } from '../../../src/interfaces/stack-trace-analyzer/IS
 import { readFileAsString } from '../../helpers/readFileAsString';
 
 import { Nodes } from '../../../src/Nodes';
+import { NodeMocks } from '../../mocks/NodeMocks';
 import { NodeUtils } from '../../../src/NodeUtils';
 import { StackTraceAnalyzer } from '../../../src/stack-trace-analyzer/StackTraceAnalyzer';
 
@@ -149,9 +150,10 @@ describe('StackTraceAnalyzer', () => {
             expectedStackTraceData: IStackTraceData[];
 
         it('should returns correct IStackTraceData - variant #1: basic-1', () => {
-            astTree = <ESTree.Program>NodeUtils.convertCodeToStructure(
-                readFileAsString('./test/fixtures/stack-trace-analyzer/basic-1.js'),
-                false
+            astTree = NodeMocks.getProgramNode(
+                <ESTree.Statement[]>NodeUtils.convertCodeToStructure(
+                    readFileAsString('./test/fixtures/stack-trace-analyzer/basic-1.js')
+                )
             );
 
             expectedStackTraceData = [
@@ -195,9 +197,10 @@ describe('StackTraceAnalyzer', () => {
         });
 
         it('should returns correct BlockScopeTraceData - variant #2: basic-2', () => {
-            astTree = <ESTree.Program>NodeUtils.convertCodeToStructure(
-                readFileAsString('./test/fixtures/stack-trace-analyzer/basic-2.js'),
-                false
+            astTree = NodeMocks.getProgramNode(
+                <ESTree.Statement[]>NodeUtils.convertCodeToStructure(
+                    readFileAsString('./test/fixtures/stack-trace-analyzer/basic-2.js')
+                )
             );
 
             expectedStackTraceData = [
@@ -230,9 +233,10 @@ describe('StackTraceAnalyzer', () => {
         });
 
         it('should returns correct BlockScopeTraceData - variant #3: deep conditions nesting', () => {
-            astTree = <ESTree.Program>NodeUtils.convertCodeToStructure(
-                readFileAsString('./test/fixtures/stack-trace-analyzer/deep-conditions-nesting.js'),
-                false
+            astTree = NodeMocks.getProgramNode(
+                <ESTree.Statement[]>NodeUtils.convertCodeToStructure(
+                    readFileAsString('./test/fixtures/stack-trace-analyzer/deep-conditions-nesting.js')
+                )
             );
 
             expectedStackTraceData = [
@@ -265,9 +269,10 @@ describe('StackTraceAnalyzer', () => {
         });
 
         it('should returns correct BlockScopeTraceData - variant #4: call before declaration', () => {
-            astTree = <ESTree.Program>NodeUtils.convertCodeToStructure(
-                readFileAsString('./test/fixtures/stack-trace-analyzer/call-before-declaration.js'),
-                false
+            astTree = NodeMocks.getProgramNode(
+                <ESTree.Statement[]>NodeUtils.convertCodeToStructure(
+                    readFileAsString('./test/fixtures/stack-trace-analyzer/call-before-declaration.js')
+                )
             );
 
             expectedStackTraceData = [
@@ -284,9 +289,10 @@ describe('StackTraceAnalyzer', () => {
         });
 
         it('should returns correct BlockScopeTraceData - variant #5: call expression of object member', () => {
-            astTree = <ESTree.Program>NodeUtils.convertCodeToStructure(
-                readFileAsString('./test/fixtures/stack-trace-analyzer/call-expression-of-object-member.js'),
-                false
+            astTree = NodeMocks.getProgramNode(
+                <ESTree.Statement[]>NodeUtils.convertCodeToStructure(
+                    readFileAsString('./test/fixtures/stack-trace-analyzer/call-expression-of-object-member.js')
+                )
             );
 
             expectedStackTraceData = [
@@ -339,9 +345,10 @@ describe('StackTraceAnalyzer', () => {
         });
 
         it('should returns correct BlockScopeTraceData - variant #6: no call expressions', () => {
-            astTree = <ESTree.Program>NodeUtils.convertCodeToStructure(
-                readFileAsString('./test/fixtures/stack-trace-analyzer/no-call-expressions.js'),
-                false
+            astTree = NodeMocks.getProgramNode(
+                <ESTree.Statement[]>NodeUtils.convertCodeToStructure(
+                    readFileAsString('./test/fixtures/stack-trace-analyzer/no-call-expressions.js')
+                )
             );
 
             expectedStackTraceData = [];
@@ -352,9 +359,10 @@ describe('StackTraceAnalyzer', () => {
         });
 
         it('should returns correct BlockScopeTraceData - variant #7: only call expression', () => {
-            astTree = <ESTree.Program>NodeUtils.convertCodeToStructure(
-                readFileAsString('./test/fixtures/stack-trace-analyzer/only-call-expression.js'),
-                false
+            astTree = NodeMocks.getProgramNode(
+                <ESTree.Statement[]>NodeUtils.convertCodeToStructure(
+                    readFileAsString('./test/fixtures/stack-trace-analyzer/only-call-expression.js')
+                )
             );
 
             expectedStackTraceData = [];
@@ -365,9 +373,10 @@ describe('StackTraceAnalyzer', () => {
         });
 
         it('should returns correct BlockScopeTraceData - variant #8: self-invoking functions', () => {
-            astTree = <ESTree.Program>NodeUtils.convertCodeToStructure(
-                readFileAsString('./test/fixtures/stack-trace-analyzer/self-invoking-functions.js'),
-                false
+            astTree = NodeMocks.getProgramNode(
+                <ESTree.Statement[]>NodeUtils.convertCodeToStructure(
+                    readFileAsString('./test/fixtures/stack-trace-analyzer/self-invoking-functions.js')
+                )
             );
 
             expectedStackTraceData = [
@@ -396,9 +405,10 @@ describe('StackTraceAnalyzer', () => {
         });
 
         it('should returns correct BlockScopeTraceData - variant #9: no recursion', () => {
-            astTree = <ESTree.Program>NodeUtils.convertCodeToStructure(
-                readFileAsString('./test/fixtures/stack-trace-analyzer/no-recursion.js'),
-                false
+            astTree = NodeMocks.getProgramNode(
+                <ESTree.Statement[]>NodeUtils.convertCodeToStructure(
+                    readFileAsString('./test/fixtures/stack-trace-analyzer/no-recursion.js')
+                )
             );
 
             expectedStackTraceData = [

+ 65 - 33
test/unit-tests/NodeUtils.spec.ts

@@ -1,6 +1,8 @@
 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';
 
@@ -25,21 +27,32 @@ describe('NodeUtils', () => {
         });
     });
 
-    describe('appendNode (blockScopeBody: ESTree.Node[], node: ESTree.Node): void', () => {
+    describe('appendNode (blockScopeNode: TNodeWithBlockStatement[], nodeBodyStatements: TStatement[])', () => {
         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([
-                expressionStatementNode
+                expectedExpressionStatementNode
             ]);
 
-            NodeUtils.appendNode(blockStatementNode.body, expressionStatementNode);
+            NodeUtils.appendNode(
+                blockStatementNode,
+                [expressionStatementNode]
+            );
+            NodeUtils.appendNode(
+                blockStatementNode,
+                <TStatement[]>[{}]
+            );
         });
 
         it('should appendNode given node to a `BlockStatement` node body', () => {
@@ -47,15 +60,11 @@ describe('NodeUtils', () => {
         });
 
         it('should does not change `BlockStatement` node body if given node is not a valid Node', () => {
-            assert.doesNotChange(
-                () => NodeUtils.appendNode(blockStatementNode.body, <ESTree.Node>{}),
-                blockStatementNode,
-                'body'
-            );
+            assert.deepEqual(blockStatementNode, expectedBlockStatementNode);
         });
     });
 
-    describe('convertCodeToStructure (code: string): ESTree.Node', () => {
+    describe('convertCodeToStructure (code: string): ESTree.Node[]', () => {
         let code: string,
             identifierNode: ESTree.Identifier,
             literalNode: ESTree.Literal,
@@ -89,8 +98,8 @@ describe('NodeUtils', () => {
             literalNode['parentNode'] = variableDeclaratorNode;
         });
 
-        it('should convert code to `ESTree.Node` structure', () => {
-            assert.deepEqual(NodeUtils.convertCodeToStructure(code), variableDeclarationNode);
+        it('should convert code to `ESTree.Node[]` structure array', () => {
+            assert.deepEqual(NodeUtils.convertCodeToStructure(code), [variableDeclarationNode]);
         });
     });
 
@@ -189,26 +198,45 @@ describe('NodeUtils', () => {
         });
     });
 
-    describe('insertNodeAtIndex (blockScopeBody: ESTree.Node[], node: ESTree.Node, index: number): void', () => {
+    describe('insertNodeAtIndex (blockScopeNode: TNodeWithBlockStatement[], nodeBodyStatements: TStatement[], index: number): TNodeWithBlockStatement[]', () => {
         let blockStatementNode: ESTree.BlockStatement,
             expectedBlockStatementNode: ESTree.BlockStatement,
             expressionStatementNode1: ESTree.ExpressionStatement,
-            expressionStatementNode2: ESTree.ExpressionStatement;
+            expressionStatementNode2: ESTree.ExpressionStatement,
+            expressionStatementNode3: ESTree.ExpressionStatement,
+            expressionStatementNode4: ESTree.ExpressionStatement;
 
         beforeEach(() => {
-            expressionStatementNode1 = NodeMocks.getExpressionStatementNode();
-            expressionStatementNode2 = NodeMocks.getExpressionStatementNode();
+            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
+                expressionStatementNode1,
+                expressionStatementNode3
             ]);
 
+            expressionStatementNode1['parentNode'] = blockStatementNode;
+            expressionStatementNode2['parentNode'] = blockStatementNode;
+            expressionStatementNode3['parentNode'] = blockStatementNode;
+
             expectedBlockStatementNode = NodeMocks.getBlockStatementNode([
                 expressionStatementNode1,
-                expressionStatementNode1
+                expressionStatementNode2,
+                expressionStatementNode3
             ]);
 
-            NodeUtils.insertNodeAtIndex(blockStatementNode.body, expressionStatementNode2, 1);
+            NodeUtils.insertNodeAtIndex(
+                blockStatementNode,
+                [expressionStatementNode4],
+                1
+            );
+            NodeUtils.insertNodeAtIndex(
+                blockStatementNode,
+                <TStatement[]>[{}],
+                1
+            );
         });
 
         it('should insert given node in `BlockStatement` node body at index', () => {
@@ -216,11 +244,7 @@ describe('NodeUtils', () => {
         });
 
         it('should does not change `BlockStatement` node body if given node is not a valid Node', () => {
-            assert.doesNotChange(
-                () => NodeUtils.insertNodeAtIndex(blockStatementNode.body, <ESTree.Node>{}, 1),
-                blockStatementNode,
-                'body'
-            );
+            assert.deepEqual(blockStatementNode, expectedBlockStatementNode);
         });
     });
 
@@ -272,26 +296,38 @@ describe('NodeUtils', () => {
         });
     });
 
-    describe('prependNode (blockScopeBody: ESTree.Node[], node: ESTree.Node): void', () => {
+    describe('prependNode (blockScopeNode: TNodeWithBlockStatement[], nodeBodyStatements: TStatement[]): TNodeWithBlockStatement[]', () => {
         let blockStatementNode: ESTree.BlockStatement,
             expectedBlockStatementNode: ESTree.BlockStatement,
             expressionStatementNode1: ESTree.ExpressionStatement,
-            expressionStatementNode2: ESTree.ExpressionStatement;
+            expressionStatementNode2: ESTree.ExpressionStatement,
+            expressionStatementNode3: ESTree.ExpressionStatement;
 
         beforeEach(() => {
-            expressionStatementNode1 = NodeMocks.getExpressionStatementNode();
-            expressionStatementNode2 = NodeMocks.getExpressionStatementNode();
+            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.body, expressionStatementNode2);
+            NodeUtils.prependNode(
+                blockStatementNode,
+                [Object.assign({}, expressionStatementNode3)]
+            );
+            NodeUtils.prependNode(
+                blockStatementNode,
+                <TStatement[]>[{}]
+            )
         });
 
         it('should prepend given node to a `BlockStatement` node body', () => {
@@ -299,11 +335,7 @@ describe('NodeUtils', () => {
         });
 
         it('should does not change `BlockStatement` node body if given node is not a valid Node', () => {
-            assert.doesNotChange(
-                () => NodeUtils.prependNode(blockStatementNode.body, <ESTree.Node>{}),
-                blockStatementNode,
-                'body'
-            );
+            assert.deepEqual(blockStatementNode, expectedBlockStatementNode);
         });
     });
 });

+ 200 - 189
test/unit-tests/custom-nodes/CustomNodeAppender.spec.ts

@@ -1,19 +1,22 @@
 import * as chai from 'chai';
 import * as ESTree from 'estree';
 
+import { TStatement } from '../../../src/types/TStatement';
+
 import { IStackTraceData } from '../../../src/interfaces/stack-trace-analyzer/IStackTraceData';
 
 import { CustomNodeAppender } from '../../../src/custom-nodes/CustomNodeAppender';
+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 (blockScopeNode: TNodeWithBlockStatement, node: ESTree.Node): void', () => {
+    describe('appendNode (blockScopeStackTraceData: IStackTraceData[], blockScopeNode: TNodeWithBlockStatement, nodeBodyStatements: TStatement[], index: number = 0)', () => {
         let astTree: ESTree.Program,
             expectedAstTree: ESTree.Program,
-            node: ESTree.Node,
+            node: TStatement[],
             stackTraceData: IStackTraceData[];
 
         beforeEach(() => {
@@ -23,151 +26,41 @@ describe('CustomNodeAppender', () => {
         });
 
         it('should append node into first and deepest function call in calls trace - variant #1', () => {
-            astTree = <ESTree.Program>NodeUtils.convertCodeToStructure(`
-                function foo () {
-                
-                }
-                
-                function bar () {
-                    function inner1 () {
+            astTree = NodeMocks.getProgramNode(
+                <ESTree.Statement[]>NodeUtils.convertCodeToStructure(`
+                    function foo () {
                     
                     }
-                
-                    function inner2 () {
-                        var inner3 = function () {
-                            
-                        }
-                        
-                        inner3();
-                    }
                     
-                    inner2();
-                    inner1();
-                }
-                
-                function baz () {
-                
-                }
-                
-                baz();
-                foo();
-                bar();
-            `, false);
-
-            expectedAstTree = <ESTree.Program>NodeUtils.convertCodeToStructure(`
-                function foo () {
-                
-                }
-                
-                function bar () {
-                    function inner1 () {
-                    
-                    }
-                
-                    function inner2 () {
-                        var inner3 = function () {
-                        }
+                    function bar () {
+                        function inner1 () {
                         
-                        inner3();
-                    }
-                    
-                    inner2();
-                    inner1();
-                }
-                
-                function baz () {
-                    var test = 1;
-                }
-                
-                baz();
-                foo();
-                bar();
-            `, false);
-
-            stackTraceData = new StackTraceAnalyzer(astTree.body).analyze();
-            CustomNodeAppender.appendNode(stackTraceData, astTree.body, node);
-
-            NodeUtils.parentize(astTree);
-
-            assert.deepEqual(astTree, expectedAstTree);
-        });
-
-        it('should append node into first and deepest function call in calls trace - variant #2', () => {
-            astTree = <ESTree.Program>NodeUtils.convertCodeToStructure(`
-                function foo () {
-                
-                }
-                
-                function bar () {
-                    function inner1 () {
+                        }
                     
-                    }
-                
-                    function inner2 () {
-                        var inner3 = function () {
+                        function inner2 () {
+                            var inner3 = function () {
+                                
+                            }
                             
+                            inner3();
                         }
                         
-                        inner3();
+                        inner2();
+                        inner1();
                     }
                     
-                    inner2();
-                    inner1();
-                }
-                
-                function baz () {
-                
-                }
-                
-                bar();
-                baz();
-                foo();
-            `, false);
-
-            expectedAstTree = <ESTree.Program>NodeUtils.convertCodeToStructure(`
-                function foo () {
-                
-                }
-                
-                function bar () {
-                    function inner1 () {
+                    function baz () {
                     
                     }
-                
-                    function inner2 () {
-                        var inner3 = function () {
-                            var test = 1;
-                        }
-                        
-                        inner3();
-                    }
                     
-                    inner2();
-                    inner1();
-                }
-                
-                function baz () {
-                
-                }
-                
-                bar();
-                baz();
-                foo();
-            `, false);
-
-            stackTraceData = new StackTraceAnalyzer(astTree.body).analyze();
-            CustomNodeAppender.appendNode(stackTraceData, astTree.body, node);
-
-            NodeUtils.parentize(astTree);
-
-            assert.deepEqual(astTree, expectedAstTree);
-        });
-
-        describe('append by specific index', () => {
-            let astTree: ESTree.Program;
+                    baz();
+                    foo();
+                    bar();
+                `)
+            );
 
-            beforeEach(() => {
-                astTree = <ESTree.Program>NodeUtils.convertCodeToStructure(`
+            expectedAstTree = NodeMocks.getProgramNode(
+                <ESTree.Statement[]>NodeUtils.convertCodeToStructure(`
                     function foo () {
                     
                     }
@@ -179,7 +72,6 @@ describe('CustomNodeAppender', () => {
                     
                         function inner2 () {
                             var inner3 = function () {
-                               
                             }
                             
                             inner3();
@@ -190,19 +82,26 @@ describe('CustomNodeAppender', () => {
                     }
                     
                     function baz () {
-                    
+                        var test = 1;
                     }
                     
-                    bar();
                     baz();
                     foo();
-                `, false);
-            });
+                    bar();
+                `)
+            );
 
-            it('should append node into deepest function call by specified index in calls trace - variant #1', () => {
-                expectedAstTree = <ESTree.Program>NodeUtils.convertCodeToStructure(`
+            stackTraceData = new StackTraceAnalyzer(astTree.body).analyze();
+            CustomNodeAppender.appendNode(stackTraceData, astTree, node);
+
+            assert.deepEqual(astTree, expectedAstTree);
+        });
+
+        it('should append node into first and deepest function call in calls trace - variant #2', () => {
+            astTree = NodeMocks.getProgramNode(
+                <ESTree.Statement[]>NodeUtils.convertCodeToStructure(`
                     function foo () {
-                        var test = 1;
+                    
                     }
                     
                     function bar () {
@@ -229,20 +128,13 @@ describe('CustomNodeAppender', () => {
                     bar();
                     baz();
                     foo();
-                `, false);
-
-                stackTraceData = new StackTraceAnalyzer(astTree.body).analyze();
-                CustomNodeAppender.appendNode(stackTraceData, astTree.body, node, 2);
-
-                NodeUtils.parentize(astTree);
+                `)
+            );
 
-                assert.deepEqual(astTree, expectedAstTree);
-            });
-
-            it('should append node into deepest function call by specified index in calls trace - variant #2', () => {
-                expectedAstTree = <ESTree.Program>NodeUtils.convertCodeToStructure(`
+            expectedAstTree = NodeMocks.getProgramNode(
+                <ESTree.Statement[]>NodeUtils.convertCodeToStructure(`
                     function foo () {
-                        
+                    
                     }
                     
                     function bar () {
@@ -252,7 +144,7 @@ describe('CustomNodeAppender', () => {
                     
                         function inner2 () {
                             var inner3 = function () {
-                                
+                                var test = 1;
                             }
                             
                             inner3();
@@ -263,30 +155,31 @@ describe('CustomNodeAppender', () => {
                     }
                     
                     function baz () {
-                        var test = 1;
+                    
                     }
                     
                     bar();
                     baz();
                     foo();
-                `, false);
+                `)
+            );
 
-                stackTraceData = new StackTraceAnalyzer(astTree.body).analyze();
-                CustomNodeAppender.appendNode(stackTraceData, astTree.body, node, 1);
+            stackTraceData = new StackTraceAnalyzer(astTree.body).analyze();
+            CustomNodeAppender.appendNode(stackTraceData, astTree, node);
 
-                NodeUtils.parentize(astTree);
+            assert.deepEqual(astTree, expectedAstTree);
+        });
 
-                assert.deepEqual(astTree, expectedAstTree);
-            });
+        describe('append by specific index', () => {
+            let astTree: ESTree.Program;
 
-            it('should append node into deepest function call by specified index in calls trace - variant #3', () => {
-                astTree = <ESTree.Program>NodeUtils.convertCodeToStructure(`
-                    var start = new Date();
-                    var log = console.log;
-                    
-                    console.log = function () {};
-                
-                    (function () {
+            beforeEach(() => {
+                astTree = NodeMocks.getProgramNode(
+                    <ESTree.Statement[]>NodeUtils.convertCodeToStructure(`
+                        function foo () {
+                        
+                        }
+                        
                         function bar () {
                             function inner1 () {
                             
@@ -294,7 +187,42 @@ describe('CustomNodeAppender', () => {
                         
                             function inner2 () {
                                 var inner3 = function () {
+                                   
+                                }
                                 
+                                inner3();
+                            }
+                            
+                            inner2();
+                            inner1();
+                        }
+                        
+                        function baz () {
+                        
+                        }
+                        
+                        bar();
+                        baz();
+                        foo();
+                    `)
+                );
+            });
+
+            it('should append node into deepest function call by specified index in calls trace - variant #1', () => {
+                expectedAstTree = NodeMocks.getProgramNode(
+                    <ESTree.Statement[]>NodeUtils.convertCodeToStructure(`
+                        function foo () {
+                            var test = 1;
+                        }
+                        
+                        function bar () {
+                            function inner1 () {
+                            
+                            }
+                        
+                            function inner2 () {
+                                var inner3 = function () {
+                                    
                                 }
                                 
                                 inner3();
@@ -304,18 +232,29 @@ describe('CustomNodeAppender', () => {
                             inner1();
                         }
                         
+                        function baz () {
+                        
+                        }
+                        
                         bar();
-                    })();
-                    console.log = log;
-                    console.log(new Date() - start);
-                `, false);
-                expectedAstTree = <ESTree.Program>NodeUtils.convertCodeToStructure(`
-                    var start = new Date();
-                    var log = console.log;
-                    
-                    console.log = function () {};
-                
-                    (function () {
+                        baz();
+                        foo();
+                    `)
+                );
+
+                stackTraceData = new StackTraceAnalyzer(astTree.body).analyze();
+                CustomNodeAppender.appendNode(stackTraceData, astTree, node, 2);
+
+                assert.deepEqual(astTree, expectedAstTree);
+            });
+
+            it('should append node into deepest function call by specified index in calls trace - variant #2', () => {
+                expectedAstTree = NodeMocks.getProgramNode(
+                    <ESTree.Statement[]>NodeUtils.convertCodeToStructure(`
+                        function foo () {
+                            
+                        }
+                        
                         function bar () {
                             function inner1 () {
                             
@@ -323,7 +262,7 @@ describe('CustomNodeAppender', () => {
                         
                             function inner2 () {
                                 var inner3 = function () {
-                                    var test = 1;
+                                    
                                 }
                                 
                                 inner3();
@@ -333,29 +272,101 @@ describe('CustomNodeAppender', () => {
                             inner1();
                         }
                         
+                        function baz () {
+                            var test = 1;
+                        }
+                        
                         bar();
-                    })();
-                    console.log = log;
-                    console.log(new Date() - start);
-                `, false);
+                        baz();
+                        foo();
+                    `)
+                );
+
+                stackTraceData = new StackTraceAnalyzer(astTree.body).analyze();
+                CustomNodeAppender.appendNode(stackTraceData, astTree, node, 1);
+
+                assert.deepEqual(astTree, expectedAstTree);
+            });
+
+            it('should append node into deepest function call by specified index in calls trace - variant #3', () => {
+                astTree = NodeMocks.getProgramNode(
+                    <ESTree.Statement[]>NodeUtils.convertCodeToStructure(`
+                        var start = new Date();
+                        var log = console.log;
+                        
+                        console.log = function () {};
+                    
+                        (function () {
+                            function bar () {
+                                function inner1 () {
+                                
+                                }
+                            
+                                function inner2 () {
+                                    var inner3 = function () {
+                                    
+                                    }
+                                    
+                                    inner3();
+                                }
+                                
+                                inner2();
+                                inner1();
+                            }
+                            
+                            bar();
+                        })();
+                        console.log = log;
+                        console.log(new Date() - start);
+                    `)
+                );
+                expectedAstTree = NodeMocks.getProgramNode(
+                    <ESTree.Statement[]>NodeUtils.convertCodeToStructure(`
+                        var start = new Date();
+                        var log = console.log;
+                        
+                        console.log = function () {};
+                    
+                        (function () {
+                            function bar () {
+                                function inner1 () {
+                                
+                                }
+                            
+                                function inner2 () {
+                                    var inner3 = function () {
+                                        var test = 1;
+                                    }
+                                    
+                                    inner3();
+                                }
+                                
+                                inner2();
+                                inner1();
+                            }
+                            
+                            bar();
+                        })();
+                        console.log = log;
+                        console.log(new Date() - start);
+                    `)
+                );
 
                 stackTraceData = new StackTraceAnalyzer(astTree.body).analyze();
                 CustomNodeAppender.appendNode(
                     stackTraceData,
-                    astTree.body,
+                    astTree,
                     node,
                     CustomNodeAppender.getRandomStackTraceIndex(stackTraceData.length)
                 );
 
-                NodeUtils.parentize(astTree);
-
                 assert.deepEqual(astTree, expectedAstTree);
             });
         });
     });
 
     describe('getRandomStackTraceIndex (stackTraceRootLength: number): number', () => {
-        it('should returns random index between 0 and stack trace deata root length', () => {
+        it('should returns random index between 0 and stack trace data root length', () => {
             let index: number;
 
             for (let i: number = 0; i < 100; i++) {

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio