浏览代码

Hidden node appender

sanex3339 8 年之前
父节点
当前提交
8571cef294

+ 327 - 144
dist/index.js

@@ -88,7 +88,7 @@ module.exports =
 /******/ 	__webpack_require__.p = "";
 /******/
 /******/ 	// Load entry module and return exports
-/******/ 	return __webpack_require__(__webpack_require__.s = 77);
+/******/ 	return __webpack_require__(__webpack_require__.s = 79);
 /******/ })
 /************************************************************************/
 /******/ ([
@@ -102,7 +102,7 @@ var _createClass = function () { function defineProperties(target, props) { for
 
 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
 
-var chance_1 = __webpack_require__(71);
+var chance_1 = __webpack_require__(73);
 var JSFuck_1 = __webpack_require__(18);
 
 var Utils = function () {
@@ -258,9 +258,9 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons
 
 var escodegen = __webpack_require__(11);
 var esprima = __webpack_require__(21);
-var estraverse = __webpack_require__(6);
-var NodeType_1 = __webpack_require__(5);
-var Nodes_1 = __webpack_require__(3);
+var estraverse = __webpack_require__(5);
+var NodeType_1 = __webpack_require__(6);
+var Nodes_1 = __webpack_require__(2);
 var Utils_1 = __webpack_require__(0);
 
 var NodeUtils = function () {
@@ -291,9 +291,14 @@ var NodeUtils = function () {
     }, {
         key: 'convertCodeToStructure',
         value: function convertCodeToStructure(code) {
+            var getFirstBlockStatementNodeByIndex = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
+
             var structure = esprima.parse(code);
             NodeUtils.addXVerbatimPropertyToLiterals(structure);
             NodeUtils.parentize(structure);
+            if (!getFirstBlockStatementNodeByIndex) {
+                return structure;
+            }
             return NodeUtils.getBlockStatementNodeByIndex(structure);
         }
     }, {
@@ -411,19 +416,6 @@ exports.NodeUtils = NodeUtils;
 
 /***/ },
 /* 2 */
-/***/ function(module, exports) {
-
-"use strict";
-"use strict";
-
-(function (AppendState) {
-    AppendState[AppendState["AfterObfuscation"] = 0] = "AfterObfuscation";
-    AppendState[AppendState["BeforeObfuscation"] = 1] = "BeforeObfuscation";
-})(exports.AppendState || (exports.AppendState = {}));
-var AppendState = exports.AppendState;
-
-/***/ },
-/* 3 */
 /***/ function(module, exports, __webpack_require__) {
 
 "use strict";
@@ -433,7 +425,7 @@ var _createClass = function () { function defineProperties(target, props) { for
 
 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
 
-var NodeType_1 = __webpack_require__(5);
+var NodeType_1 = __webpack_require__(6);
 
 var Nodes = function () {
     function Nodes() {
@@ -465,6 +457,11 @@ var Nodes = function () {
         value: function isCallExpressionNode(node) {
             return node.type === NodeType_1.NodeType.CallExpression;
         }
+    }, {
+        key: 'isExpressionStatementNode',
+        value: function isExpressionStatementNode(node) {
+            return node.type === NodeType_1.NodeType.ExpressionStatement;
+        }
     }, {
         key: 'isFunctionDeclarationNode',
         value: function isFunctionDeclarationNode(node) {
@@ -532,6 +529,19 @@ var Nodes = function () {
 
 exports.Nodes = Nodes;
 
+/***/ },
+/* 3 */
+/***/ function(module, exports) {
+
+"use strict";
+"use strict";
+
+(function (AppendState) {
+    AppendState[AppendState["AfterObfuscation"] = 0] = "AfterObfuscation";
+    AppendState[AppendState["BeforeObfuscation"] = 1] = "BeforeObfuscation";
+})(exports.AppendState || (exports.AppendState = {}));
+var AppendState = exports.AppendState;
+
 /***/ },
 /* 4 */
 /***/ function(module, exports) {
@@ -569,6 +579,12 @@ exports.AbstractCustomNode = AbstractCustomNode;
 
 /***/ },
 /* 5 */
+/***/ function(module, exports) {
+
+module.exports = require("estraverse");
+
+/***/ },
+/* 6 */
 /***/ function(module, exports, __webpack_require__) {
 
 "use strict";
@@ -605,12 +621,6 @@ exports.NodeType = Utils_1.Utils.strEnumify({
     WhileStatement: 'WhileStatement'
 });
 
-/***/ },
-/* 6 */
-/***/ function(module, exports) {
-
-module.exports = require("estraverse");
-
 /***/ },
 /* 7 */
 /***/ function(module, exports) {
@@ -1113,7 +1123,7 @@ var esprima = __webpack_require__(21);
 var escodegen = __webpack_require__(11);
 var ObfuscationResult_1 = __webpack_require__(17);
 var Obfuscator_1 = __webpack_require__(25);
-var Options_1 = __webpack_require__(54);
+var Options_1 = __webpack_require__(56);
 var SourceMapCorrector_1 = __webpack_require__(26);
 
 var JavaScriptObfuscatorInternal = function () {
@@ -1179,24 +1189,24 @@ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr
 
 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
 
-var estraverse = __webpack_require__(6);
-var AppendState_1 = __webpack_require__(2);
-var NodeType_1 = __webpack_require__(5);
-var CatchClauseObfuscator_1 = __webpack_require__(45);
-var ConsoleOutputNodesGroup_1 = __webpack_require__(40);
-var DebugProtectionNodesGroup_1 = __webpack_require__(41);
-var DomainLockNodesGroup_1 = __webpack_require__(42);
-var FunctionDeclarationObfuscator_1 = __webpack_require__(46);
-var FunctionObfuscator_1 = __webpack_require__(47);
-var LiteralObfuscator_1 = __webpack_require__(48);
-var MemberExpressionObfuscator_1 = __webpack_require__(49);
-var MethodDefinitionObfuscator_1 = __webpack_require__(50);
-var Nodes_1 = __webpack_require__(3);
+var estraverse = __webpack_require__(5);
+var AppendState_1 = __webpack_require__(3);
+var NodeType_1 = __webpack_require__(6);
+var CatchClauseObfuscator_1 = __webpack_require__(47);
+var ConsoleOutputNodesGroup_1 = __webpack_require__(42);
+var DebugProtectionNodesGroup_1 = __webpack_require__(43);
+var DomainLockNodesGroup_1 = __webpack_require__(44);
+var FunctionDeclarationObfuscator_1 = __webpack_require__(48);
+var FunctionObfuscator_1 = __webpack_require__(49);
+var LiteralObfuscator_1 = __webpack_require__(50);
+var MemberExpressionObfuscator_1 = __webpack_require__(51);
+var MethodDefinitionObfuscator_1 = __webpack_require__(52);
+var Nodes_1 = __webpack_require__(2);
 var NodeUtils_1 = __webpack_require__(1);
-var ObjectExpressionObfuscator_1 = __webpack_require__(51);
-var SelfDefendingNodesGroup_1 = __webpack_require__(43);
-var UnicodeArrayNodesGroup_1 = __webpack_require__(44);
-var VariableDeclarationObfuscator_1 = __webpack_require__(52);
+var ObjectExpressionObfuscator_1 = __webpack_require__(53);
+var SelfDefendingNodesGroup_1 = __webpack_require__(45);
+var UnicodeArrayNodesGroup_1 = __webpack_require__(46);
+var VariableDeclarationObfuscator_1 = __webpack_require__(54);
 
 var Obfuscator = function () {
     function Obfuscator(options) {
@@ -1394,8 +1404,8 @@ var _createClass = function () { function defineProperties(target, props) { for
 
 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
 
-var fs = __webpack_require__(74);
-var mkdirp = __webpack_require__(75);
+var fs = __webpack_require__(76);
+var mkdirp = __webpack_require__(77);
 var path = __webpack_require__(22);
 var Utils_1 = __webpack_require__(0);
 
@@ -1486,7 +1496,7 @@ var _createClass = function () { function defineProperties(target, props) { for
 
 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
 
-var commander = __webpack_require__(73);
+var commander = __webpack_require__(75);
 var path = __webpack_require__(22);
 var SourceMapMode_1 = __webpack_require__(12);
 var DefaultPreset_1 = __webpack_require__(20);
@@ -1623,9 +1633,10 @@ function _possibleConstructorReturn(self, call) { if (!self) { throw new Referen
 
 function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
 
-var AppendState_1 = __webpack_require__(2);
-var ConsoleOutputDisableExpressionTemplate_1 = __webpack_require__(58);
+var AppendState_1 = __webpack_require__(3);
+var ConsoleOutputDisableExpressionTemplate_1 = __webpack_require__(60);
 var AbstractCustomNode_1 = __webpack_require__(4);
+var HiddenNodeAppender_1 = __webpack_require__(41);
 var NodeUtils_1 = __webpack_require__(1);
 
 var ConsoleOutputDisableExpressionNode = function (_AbstractCustomNode_) {
@@ -1643,7 +1654,7 @@ var ConsoleOutputDisableExpressionNode = function (_AbstractCustomNode_) {
     _createClass(ConsoleOutputDisableExpressionNode, [{
         key: 'appendNode',
         value: function appendNode(blockScopeNode) {
-            NodeUtils_1.NodeUtils.prependNode(blockScopeNode.body, this.getNode());
+            HiddenNodeAppender_1.HiddenNodeAppender.appendNode(blockScopeNode.body, this.getNode(), HiddenNodeAppender_1.HiddenNodeAppender.getIndexByThreshold(blockScopeNode.body.length));
         }
     }, {
         key: 'getNodeStructure',
@@ -1673,8 +1684,8 @@ function _possibleConstructorReturn(self, call) { if (!self) { throw new Referen
 function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
 
 __webpack_require__(8);
-var AppendState_1 = __webpack_require__(2);
-var DebufProtectionFunctionCallTemplate_1 = __webpack_require__(59);
+var AppendState_1 = __webpack_require__(3);
+var DebufProtectionFunctionCallTemplate_1 = __webpack_require__(61);
 var AbstractCustomNode_1 = __webpack_require__(4);
 var NodeUtils_1 = __webpack_require__(1);
 
@@ -1726,8 +1737,8 @@ function _possibleConstructorReturn(self, call) { if (!self) { throw new Referen
 function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
 
 __webpack_require__(8);
-var AppendState_1 = __webpack_require__(2);
-var DebugProtectionFunctionIntervalTemplate_1 = __webpack_require__(60);
+var AppendState_1 = __webpack_require__(3);
+var DebugProtectionFunctionIntervalTemplate_1 = __webpack_require__(62);
 var AbstractCustomNode_1 = __webpack_require__(4);
 var NodeUtils_1 = __webpack_require__(1);
 
@@ -1779,8 +1790,8 @@ function _possibleConstructorReturn(self, call) { if (!self) { throw new Referen
 function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
 
 __webpack_require__(8);
-var AppendState_1 = __webpack_require__(2);
-var DebugProtectionFunctionTemplate_1 = __webpack_require__(61);
+var AppendState_1 = __webpack_require__(3);
+var DebugProtectionFunctionTemplate_1 = __webpack_require__(63);
 var AbstractCustomNode_1 = __webpack_require__(4);
 var NodeUtils_1 = __webpack_require__(1);
 var Utils_1 = __webpack_require__(0);
@@ -1845,9 +1856,10 @@ function _possibleConstructorReturn(self, call) { if (!self) { throw new Referen
 function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
 
 __webpack_require__(8);
-var AppendState_1 = __webpack_require__(2);
-var DomainLockNodeTemplate_1 = __webpack_require__(62);
+var AppendState_1 = __webpack_require__(3);
+var DomainLockNodeTemplate_1 = __webpack_require__(64);
 var AbstractCustomNode_1 = __webpack_require__(4);
+var HiddenNodeAppender_1 = __webpack_require__(41);
 var NodeUtils_1 = __webpack_require__(1);
 var Utils_1 = __webpack_require__(0);
 
@@ -1866,7 +1878,7 @@ var DomainLockNode = function (_AbstractCustomNode_) {
     _createClass(DomainLockNode, [{
         key: 'appendNode',
         value: function appendNode(blockScopeNode) {
-            NodeUtils_1.NodeUtils.prependNode(blockScopeNode.body, this.getNode());
+            HiddenNodeAppender_1.HiddenNodeAppender.appendNode(blockScopeNode.body, this.getNode(), HiddenNodeAppender_1.HiddenNodeAppender.getIndexByThreshold(blockScopeNode.body.length));
         }
     }, {
         key: 'getNodeStructure',
@@ -1906,13 +1918,13 @@ function _possibleConstructorReturn(self, call) { if (!self) { throw new Referen
 
 function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
 
-var AppendState_1 = __webpack_require__(2);
+var AppendState_1 = __webpack_require__(3);
 var NoCustomNodesPreset_1 = __webpack_require__(16);
-var SelfDefendingTemplate_1 = __webpack_require__(63);
+var SelfDefendingTemplate_1 = __webpack_require__(65);
 var AbstractCustomNode_1 = __webpack_require__(4);
 var JavaScriptObfuscator_1 = __webpack_require__(9);
 var NodeUtils_1 = __webpack_require__(1);
-var Utils_1 = __webpack_require__(0);
+var HiddenNodeAppender_1 = __webpack_require__(41);
 
 var SelfDefendingUnicodeNode = function (_AbstractCustomNode_) {
     _inherits(SelfDefendingUnicodeNode, _AbstractCustomNode_);
@@ -1929,15 +1941,7 @@ var SelfDefendingUnicodeNode = function (_AbstractCustomNode_) {
     _createClass(SelfDefendingUnicodeNode, [{
         key: 'appendNode',
         value: function appendNode(blockScopeNode) {
-            var programBodyLength = blockScopeNode.body.length,
-                randomIndex = 0;
-            if (programBodyLength > 2) {
-                randomIndex = Utils_1.Utils.getRandomGenerator().integer({
-                    min: programBodyLength / 2,
-                    max: programBodyLength - 1
-                });
-            }
-            NodeUtils_1.NodeUtils.insertNodeAtIndex(blockScopeNode.body, this.getNode(), randomIndex);
+            HiddenNodeAppender_1.HiddenNodeAppender.appendNode(blockScopeNode.body, this.getNode(), HiddenNodeAppender_1.HiddenNodeAppender.getIndexByThreshold(blockScopeNode.body.length));
         }
     }, {
         key: 'getNodeStructure',
@@ -1969,8 +1973,8 @@ function _possibleConstructorReturn(self, call) { if (!self) { throw new Referen
 function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
 
 __webpack_require__(8);
-var AppendState_1 = __webpack_require__(2);
-var UnicodeArrayCallsWrapperTemplate_1 = __webpack_require__(64);
+var AppendState_1 = __webpack_require__(3);
+var UnicodeArrayCallsWrapperTemplate_1 = __webpack_require__(66);
 var AbstractCustomNode_1 = __webpack_require__(4);
 var NodeUtils_1 = __webpack_require__(1);
 var Utils_1 = __webpack_require__(0);
@@ -2043,11 +2047,11 @@ function _possibleConstructorReturn(self, call) { if (!self) { throw new Referen
 function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
 
 __webpack_require__(8);
-var AppendState_1 = __webpack_require__(2);
+var AppendState_1 = __webpack_require__(3);
 var NoCustomNodesPreset_1 = __webpack_require__(16);
-var AtobTemplate_1 = __webpack_require__(57);
-var SelfDefendingTemplate_1 = __webpack_require__(65);
-var UnicodeArrayDecodeNodeTemplate_1 = __webpack_require__(66);
+var AtobTemplate_1 = __webpack_require__(59);
+var SelfDefendingTemplate_1 = __webpack_require__(67);
+var UnicodeArrayDecodeNodeTemplate_1 = __webpack_require__(68);
 var AbstractCustomNode_1 = __webpack_require__(4);
 var JavaScriptObfuscator_1 = __webpack_require__(9);
 var NodeUtils_1 = __webpack_require__(1);
@@ -2124,8 +2128,8 @@ function _possibleConstructorReturn(self, call) { if (!self) { throw new Referen
 function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
 
 __webpack_require__(8);
-var AppendState_1 = __webpack_require__(2);
-var UnicodeArrayTemplate_1 = __webpack_require__(67);
+var AppendState_1 = __webpack_require__(3);
+var UnicodeArrayTemplate_1 = __webpack_require__(69);
 var AbstractCustomNode_1 = __webpack_require__(4);
 var NodeUtils_1 = __webpack_require__(1);
 
@@ -2210,10 +2214,10 @@ function _possibleConstructorReturn(self, call) { if (!self) { throw new Referen
 function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
 
 __webpack_require__(8);
-var AppendState_1 = __webpack_require__(2);
+var AppendState_1 = __webpack_require__(3);
 var NoCustomNodesPreset_1 = __webpack_require__(16);
-var SelfDefendingTemplate_1 = __webpack_require__(68);
-var UnicodeArrayRotateFunctionTemplate_1 = __webpack_require__(69);
+var SelfDefendingTemplate_1 = __webpack_require__(70);
+var UnicodeArrayRotateFunctionTemplate_1 = __webpack_require__(71);
 var AbstractCustomNode_1 = __webpack_require__(4);
 var JavaScriptObfuscator_1 = __webpack_require__(9);
 var NodeUtils_1 = __webpack_require__(1);
@@ -2283,6 +2287,185 @@ exports.UnicodeArrayRotateFunctionNode = UnicodeArrayRotateFunctionNode;
 "use strict";
 "use strict";
 
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var estraverse = __webpack_require__(5);
+var Nodes_1 = __webpack_require__(2);
+var NodeUtils_1 = __webpack_require__(1);
+
+var ASTTreeBlockScopeAnalyzer = function () {
+    function ASTTreeBlockScopeAnalyzer(blockScopeBody) {
+        _classCallCheck(this, ASTTreeBlockScopeAnalyzer);
+
+        this.blockScopeTraceData = [];
+        this.blockScopeBody = blockScopeBody;
+    }
+
+    _createClass(ASTTreeBlockScopeAnalyzer, [{
+        key: 'analyze',
+        value: function analyze() {
+            var _this = this;
+
+            if (this.blockScopeBody.length === 1) {
+                estraverse.traverse(this.blockScopeBody[0], {
+                    enter: function enter(node) {
+                        if (Nodes_1.Nodes.isBlockStatementNode(node)) {
+                            _this.analyzeRecursive(node.body, _this.blockScopeTraceData);
+                            return estraverse.VisitorOption.Skip;
+                        }
+                    }
+                });
+            } else {
+                this.analyzeRecursive(this.blockScopeBody, this.blockScopeTraceData);
+            }
+            return this.blockScopeTraceData;
+        }
+    }, {
+        key: 'analyzeRecursive',
+        value: function analyzeRecursive(blockScopeBody, dataTree) {
+            var _this2 = this;
+
+            var _iteratorNormalCompletion = true;
+            var _didIteratorError = false;
+            var _iteratorError = undefined;
+
+            try {
+                var _loop = function _loop() {
+                    var rootNode = _step.value;
+
+                    estraverse.traverse(rootNode, {
+                        enter: function enter(node) {
+                            if (Nodes_1.Nodes.isCallExpressionNode(node) && Nodes_1.Nodes.isIdentifierNode(node.callee) && rootNode.parentNode === NodeUtils_1.NodeUtils.getBlockScopeOfNode(node)) {
+                                var calleeNode = _this2.getCalleeBlockStatement(NodeUtils_1.NodeUtils.getBlockScopeOfNode(blockScopeBody[0]), node.callee.name);
+                                if (!calleeNode) {
+                                    return estraverse.VisitorOption.Break;
+                                }
+                                var data = {
+                                    callee: calleeNode,
+                                    name: node.callee.name,
+                                    trace: []
+                                };
+                                dataTree.push(data);
+                                _this2.analyzeRecursive(calleeNode.body, data.trace);
+                            }
+                        }
+                    });
+                };
+
+                for (var _iterator = blockScopeBody[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
+                    _loop();
+                }
+            } catch (err) {
+                _didIteratorError = true;
+                _iteratorError = err;
+            } finally {
+                try {
+                    if (!_iteratorNormalCompletion && _iterator.return) {
+                        _iterator.return();
+                    }
+                } finally {
+                    if (_didIteratorError) {
+                        throw _iteratorError;
+                    }
+                }
+            }
+        }
+    }, {
+        key: 'getCalleeBlockStatement',
+        value: function getCalleeBlockStatement(node, name) {
+            var calleeBlockStatement = null;
+            estraverse.traverse(node, {
+                enter: function enter(node, parentNode) {
+                    if (Nodes_1.Nodes.isFunctionDeclarationNode(node) && node.id.name === name) {
+                        calleeBlockStatement = node.body;
+                        return estraverse.VisitorOption.Break;
+                    }
+                    if (Nodes_1.Nodes.isFunctionExpressionNode(node) && Nodes_1.Nodes.isVariableDeclaratorNode(parentNode) && Nodes_1.Nodes.isIdentifierNode(parentNode.id) && parentNode.id.name === name) {
+                        calleeBlockStatement = node.body;
+                        return estraverse.VisitorOption.Break;
+                    }
+                }
+            });
+            return calleeBlockStatement;
+        }
+    }]);
+
+    return ASTTreeBlockScopeAnalyzer;
+}();
+
+exports.ASTTreeBlockScopeAnalyzer = ASTTreeBlockScopeAnalyzer;
+
+/***/ },
+/* 41 */
+/***/ function(module, exports, __webpack_require__) {
+
+"use strict";
+"use strict";
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var ASTTreeBlockScopeAnalyzer_1 = __webpack_require__(40);
+var NodeUtils_1 = __webpack_require__(1);
+var Utils_1 = __webpack_require__(0);
+
+var HiddenNodeAppender = function () {
+    function HiddenNodeAppender() {
+        _classCallCheck(this, HiddenNodeAppender);
+    }
+
+    _createClass(HiddenNodeAppender, null, [{
+        key: 'appendNode',
+        value: function appendNode(blockScopeBody, node) {
+            var index = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
+
+            var blockScopeTraceData = new ASTTreeBlockScopeAnalyzer_1.ASTTreeBlockScopeAnalyzer(blockScopeBody).analyze();
+            if (!blockScopeTraceData.length) {
+                NodeUtils_1.NodeUtils.prependNode(blockScopeBody, node);
+                return;
+            }
+            NodeUtils_1.NodeUtils.prependNode(HiddenNodeAppender.getOptimalBlockScope(blockScopeTraceData, index).body, node);
+        }
+    }, {
+        key: 'getIndexByThreshold',
+        value: function getIndexByThreshold(blockStatementBodyLength) {
+            var threshold = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0.1;
+
+            if (threshold < 0 || threshold > 1) {
+                throw new RangeError('`threshold` parameter should has value between 0 and 1');
+            }
+            return Utils_1.Utils.getRandomGenerator().integer({
+                min: 0,
+                max: Math.round(blockStatementBodyLength * threshold)
+            });
+        }
+    }, {
+        key: 'getOptimalBlockScope',
+        value: function getOptimalBlockScope(blockScopeTraceData, index) {
+            var firstCall = blockScopeTraceData[index];
+            if (firstCall.trace.length) {
+                return HiddenNodeAppender.getOptimalBlockScope(firstCall.trace, 0);
+            } else {
+                return firstCall.callee;
+            }
+        }
+    }]);
+
+    return HiddenNodeAppender;
+}();
+
+exports.HiddenNodeAppender = HiddenNodeAppender;
+
+/***/ },
+/* 42 */
+/***/ function(module, exports, __webpack_require__) {
+
+"use strict";
+"use strict";
+
 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
 
 function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
@@ -2313,7 +2496,7 @@ var ConsoleOutputNodesGroup = function (_AbstractNodesGroup_) {
 exports.ConsoleOutputNodesGroup = ConsoleOutputNodesGroup;
 
 /***/ },
-/* 41 */
+/* 43 */
 /***/ function(module, exports, __webpack_require__) {
 
 "use strict";
@@ -2357,7 +2540,7 @@ var DebugProtectionNodesGroup = function (_AbstractNodesGroup_) {
 exports.DebugProtectionNodesGroup = DebugProtectionNodesGroup;
 
 /***/ },
-/* 42 */
+/* 44 */
 /***/ function(module, exports, __webpack_require__) {
 
 "use strict";
@@ -2393,7 +2576,7 @@ var DomainLockNodesGroup = function (_AbstractNodesGroup_) {
 exports.DomainLockNodesGroup = DomainLockNodesGroup;
 
 /***/ },
-/* 43 */
+/* 45 */
 /***/ function(module, exports, __webpack_require__) {
 
 "use strict";
@@ -2429,7 +2612,7 @@ var SelfDefendingNodesGroup = function (_AbstractNodesGroup_) {
 exports.SelfDefendingNodesGroup = SelfDefendingNodesGroup;
 
 /***/ },
-/* 44 */
+/* 46 */
 /***/ function(module, exports, __webpack_require__) {
 
 "use strict";
@@ -2491,7 +2674,7 @@ var UnicodeArrayNodesGroup = function (_AbstractNodesGroup_) {
 exports.UnicodeArrayNodesGroup = UnicodeArrayNodesGroup;
 
 /***/ },
-/* 45 */
+/* 47 */
 /***/ function(module, exports, __webpack_require__) {
 
 "use strict";
@@ -2505,11 +2688,11 @@ function _possibleConstructorReturn(self, call) { if (!self) { throw new Referen
 
 function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
 
-var estraverse = __webpack_require__(6);
-var NodeType_1 = __webpack_require__(5);
+var estraverse = __webpack_require__(5);
+var NodeType_1 = __webpack_require__(6);
 var AbstractNodeObfuscator_1 = __webpack_require__(7);
 var IdentifierReplacer_1 = __webpack_require__(14);
-var Nodes_1 = __webpack_require__(3);
+var Nodes_1 = __webpack_require__(2);
 var NodeUtils_1 = __webpack_require__(1);
 
 var CatchClauseObfuscator = function (_AbstractNodeObfuscat) {
@@ -2562,7 +2745,7 @@ var CatchClauseObfuscator = function (_AbstractNodeObfuscat) {
 exports.CatchClauseObfuscator = CatchClauseObfuscator;
 
 /***/ },
-/* 46 */
+/* 48 */
 /***/ function(module, exports, __webpack_require__) {
 
 "use strict";
@@ -2576,11 +2759,11 @@ function _possibleConstructorReturn(self, call) { if (!self) { throw new Referen
 
 function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
 
-var estraverse = __webpack_require__(6);
-var NodeType_1 = __webpack_require__(5);
+var estraverse = __webpack_require__(5);
+var NodeType_1 = __webpack_require__(6);
 var AbstractNodeObfuscator_1 = __webpack_require__(7);
 var IdentifierReplacer_1 = __webpack_require__(14);
-var Nodes_1 = __webpack_require__(3);
+var Nodes_1 = __webpack_require__(2);
 var NodeUtils_1 = __webpack_require__(1);
 
 var FunctionDeclarationObfuscator = function (_AbstractNodeObfuscat) {
@@ -2637,7 +2820,7 @@ var FunctionDeclarationObfuscator = function (_AbstractNodeObfuscat) {
 exports.FunctionDeclarationObfuscator = FunctionDeclarationObfuscator;
 
 /***/ },
-/* 47 */
+/* 49 */
 /***/ function(module, exports, __webpack_require__) {
 
 "use strict";
@@ -2651,11 +2834,11 @@ function _possibleConstructorReturn(self, call) { if (!self) { throw new Referen
 
 function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
 
-var estraverse = __webpack_require__(6);
-var NodeType_1 = __webpack_require__(5);
+var estraverse = __webpack_require__(5);
+var NodeType_1 = __webpack_require__(6);
 var AbstractNodeObfuscator_1 = __webpack_require__(7);
 var IdentifierReplacer_1 = __webpack_require__(14);
-var Nodes_1 = __webpack_require__(3);
+var Nodes_1 = __webpack_require__(2);
 var NodeUtils_1 = __webpack_require__(1);
 
 var FunctionObfuscator = function (_AbstractNodeObfuscat) {
@@ -2718,7 +2901,7 @@ var FunctionObfuscator = function (_AbstractNodeObfuscat) {
 exports.FunctionObfuscator = FunctionObfuscator;
 
 /***/ },
-/* 48 */
+/* 50 */
 /***/ function(module, exports, __webpack_require__) {
 
 "use strict";
@@ -2736,8 +2919,8 @@ function _inherits(subClass, superClass) { if (typeof superClass !== "function"
 
 var escodegen = __webpack_require__(11);
 var AbstractNodeObfuscator_1 = __webpack_require__(7);
-var BooleanLiteralReplacer_1 = __webpack_require__(53);
-var Nodes_1 = __webpack_require__(3);
+var BooleanLiteralReplacer_1 = __webpack_require__(55);
+var Nodes_1 = __webpack_require__(2);
 var NumberLiteralReplacer_1 = __webpack_require__(19);
 var StringLiteralReplacer_1 = __webpack_require__(15);
 
@@ -2783,7 +2966,7 @@ var LiteralObfuscator = function (_AbstractNodeObfuscat) {
 exports.LiteralObfuscator = LiteralObfuscator;
 
 /***/ },
-/* 49 */
+/* 51 */
 /***/ function(module, exports, __webpack_require__) {
 
 "use strict";
@@ -2798,10 +2981,10 @@ function _possibleConstructorReturn(self, call) { if (!self) { throw new Referen
 function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
 
 var escodegen = __webpack_require__(11);
-var estraverse = __webpack_require__(6);
-var NodeType_1 = __webpack_require__(5);
+var estraverse = __webpack_require__(5);
+var NodeType_1 = __webpack_require__(6);
 var AbstractNodeObfuscator_1 = __webpack_require__(7);
-var Nodes_1 = __webpack_require__(3);
+var Nodes_1 = __webpack_require__(2);
 var StringLiteralReplacer_1 = __webpack_require__(15);
 
 var MemberExpressionObfuscator = function (_AbstractNodeObfuscat) {
@@ -2868,7 +3051,7 @@ var MemberExpressionObfuscator = function (_AbstractNodeObfuscat) {
 exports.MemberExpressionObfuscator = MemberExpressionObfuscator;
 
 /***/ },
-/* 50 */
+/* 52 */
 /***/ function(module, exports, __webpack_require__) {
 
 "use strict";
@@ -2882,9 +3065,9 @@ function _possibleConstructorReturn(self, call) { if (!self) { throw new Referen
 
 function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
 
-var estraverse = __webpack_require__(6);
+var estraverse = __webpack_require__(5);
 var AbstractNodeObfuscator_1 = __webpack_require__(7);
-var Nodes_1 = __webpack_require__(3);
+var Nodes_1 = __webpack_require__(2);
 var Utils_1 = __webpack_require__(0);
 var StringLiteralReplacer_1 = __webpack_require__(15);
 
@@ -2929,7 +3112,7 @@ var MethodDefinitionObfuscator = function (_AbstractNodeObfuscat) {
 exports.MethodDefinitionObfuscator = MethodDefinitionObfuscator;
 
 /***/ },
-/* 51 */
+/* 53 */
 /***/ function(module, exports, __webpack_require__) {
 
 "use strict";
@@ -2944,10 +3127,10 @@ function _possibleConstructorReturn(self, call) { if (!self) { throw new Referen
 function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
 
 var escodegen = __webpack_require__(11);
-var estraverse = __webpack_require__(6);
-var NodeType_1 = __webpack_require__(5);
+var estraverse = __webpack_require__(5);
+var NodeType_1 = __webpack_require__(6);
 var AbstractNodeObfuscator_1 = __webpack_require__(7);
-var Nodes_1 = __webpack_require__(3);
+var Nodes_1 = __webpack_require__(2);
 var Utils_1 = __webpack_require__(0);
 
 var ObjectExpressionObfuscator = function (_AbstractNodeObfuscat) {
@@ -3015,7 +3198,7 @@ var ObjectExpressionObfuscator = function (_AbstractNodeObfuscat) {
 exports.ObjectExpressionObfuscator = ObjectExpressionObfuscator;
 
 /***/ },
-/* 52 */
+/* 54 */
 /***/ function(module, exports, __webpack_require__) {
 
 "use strict";
@@ -3029,11 +3212,11 @@ function _possibleConstructorReturn(self, call) { if (!self) { throw new Referen
 
 function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
 
-var estraverse = __webpack_require__(6);
-var NodeType_1 = __webpack_require__(5);
+var estraverse = __webpack_require__(5);
+var NodeType_1 = __webpack_require__(6);
 var AbstractNodeObfuscator_1 = __webpack_require__(7);
 var IdentifierReplacer_1 = __webpack_require__(14);
-var Nodes_1 = __webpack_require__(3);
+var Nodes_1 = __webpack_require__(2);
 var NodeUtils_1 = __webpack_require__(1);
 
 var VariableDeclarationObfuscator = function (_AbstractNodeObfuscat) {
@@ -3092,7 +3275,7 @@ var VariableDeclarationObfuscator = function (_AbstractNodeObfuscat) {
 exports.VariableDeclarationObfuscator = VariableDeclarationObfuscator;
 
 /***/ },
-/* 53 */
+/* 55 */
 /***/ function(module, exports, __webpack_require__) {
 
 "use strict";
@@ -3131,7 +3314,7 @@ var BooleanLiteralReplacer = function (_AbstractReplacer_1$A) {
 exports.BooleanLiteralReplacer = BooleanLiteralReplacer;
 
 /***/ },
-/* 54 */
+/* 56 */
 /***/ function(module, exports, __webpack_require__) {
 
 "use strict";
@@ -3152,11 +3335,11 @@ var __decorate = undefined && undefined.__decorate || function (decorators, targ
 var __metadata = undefined && undefined.__metadata || function (k, v) {
     if ((typeof Reflect === "undefined" ? "undefined" : _typeof(Reflect)) === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
 };
-var class_validator_1 = __webpack_require__(72);
-var TSourceMapMode_1 = __webpack_require__(70);
+var class_validator_1 = __webpack_require__(74);
+var TSourceMapMode_1 = __webpack_require__(72);
 var DefaultPreset_1 = __webpack_require__(20);
-var OptionsNormalizer_1 = __webpack_require__(55);
-var ValidationErrorsFormatter_1 = __webpack_require__(56);
+var OptionsNormalizer_1 = __webpack_require__(57);
+var ValidationErrorsFormatter_1 = __webpack_require__(58);
 
 var Options = function Options(obfuscatorOptions) {
     _classCallCheck(this, Options);
@@ -3203,7 +3386,7 @@ exports.Options = Options;
 var _a;
 
 /***/ },
-/* 55 */
+/* 57 */
 /***/ function(module, exports, __webpack_require__) {
 
 "use strict";
@@ -3372,7 +3555,7 @@ OptionsNormalizer.normalizerRules = [OptionsNormalizer.domainLockRule, OptionsNo
 exports.OptionsNormalizer = OptionsNormalizer;
 
 /***/ },
-/* 56 */
+/* 58 */
 /***/ function(module, exports) {
 
 "use strict";
@@ -3439,7 +3622,7 @@ var ValidationErrorsFormatter = function () {
 exports.ValidationErrorsFormatter = ValidationErrorsFormatter;
 
 /***/ },
-/* 57 */
+/* 59 */
 /***/ function(module, exports) {
 
 "use strict";
@@ -3451,7 +3634,7 @@ function AtobTemplate() {
 exports.AtobTemplate = AtobTemplate;
 
 /***/ },
-/* 58 */
+/* 60 */
 /***/ function(module, exports) {
 
 "use strict";
@@ -3463,7 +3646,7 @@ function ConsoleOutputDisableExpressionTemplate() {
 exports.ConsoleOutputDisableExpressionTemplate = ConsoleOutputDisableExpressionTemplate;
 
 /***/ },
-/* 59 */
+/* 61 */
 /***/ function(module, exports) {
 
 "use strict";
@@ -3475,7 +3658,7 @@ function DebugProtectionFunctionCallTemplate() {
 exports.DebugProtectionFunctionCallTemplate = DebugProtectionFunctionCallTemplate;
 
 /***/ },
-/* 60 */
+/* 62 */
 /***/ function(module, exports) {
 
 "use strict";
@@ -3487,7 +3670,7 @@ function DebugProtectionFunctionIntervalTemplate() {
 exports.DebugProtectionFunctionIntervalTemplate = DebugProtectionFunctionIntervalTemplate;
 
 /***/ },
-/* 61 */
+/* 63 */
 /***/ function(module, exports, __webpack_require__) {
 
 "use strict";
@@ -3500,7 +3683,7 @@ function DebugProtectionFunctionTemplate() {
 exports.DebugProtectionFunctionTemplate = DebugProtectionFunctionTemplate;
 
 /***/ },
-/* 62 */
+/* 64 */
 /***/ function(module, exports) {
 
 "use strict";
@@ -3512,7 +3695,7 @@ function DomainLockNodeTemplate() {
 exports.DomainLockNodeTemplate = DomainLockNodeTemplate;
 
 /***/ },
-/* 63 */
+/* 65 */
 /***/ function(module, exports, __webpack_require__) {
 
 "use strict";
@@ -3525,7 +3708,7 @@ function SelfDefendingTemplate() {
 exports.SelfDefendingTemplate = SelfDefendingTemplate;
 
 /***/ },
-/* 64 */
+/* 66 */
 /***/ function(module, exports) {
 
 "use strict";
@@ -3537,7 +3720,7 @@ function UnicodeArrayCallsWrapperTemplate() {
 exports.UnicodeArrayCallsWrapperTemplate = UnicodeArrayCallsWrapperTemplate;
 
 /***/ },
-/* 65 */
+/* 67 */
 /***/ function(module, exports, __webpack_require__) {
 
 "use strict";
@@ -3550,7 +3733,7 @@ function SelfDefendingTemplate() {
 exports.SelfDefendingTemplate = SelfDefendingTemplate;
 
 /***/ },
-/* 66 */
+/* 68 */
 /***/ function(module, exports) {
 
 "use strict";
@@ -3562,7 +3745,7 @@ function UnicodeArrayDecodeNodeTemplate() {
 exports.UnicodeArrayDecodeNodeTemplate = UnicodeArrayDecodeNodeTemplate;
 
 /***/ },
-/* 67 */
+/* 69 */
 /***/ function(module, exports) {
 
 "use strict";
@@ -3574,7 +3757,7 @@ function UnicodeArrayTemplate() {
 exports.UnicodeArrayTemplate = UnicodeArrayTemplate;
 
 /***/ },
-/* 68 */
+/* 70 */
 /***/ function(module, exports, __webpack_require__) {
 
 "use strict";
@@ -3587,7 +3770,7 @@ function SelfDefendingTemplate() {
 exports.SelfDefendingTemplate = SelfDefendingTemplate;
 
 /***/ },
-/* 69 */
+/* 71 */
 /***/ function(module, exports) {
 
 "use strict";
@@ -3599,45 +3782,45 @@ function UnicodeArrayRotateFunctionTemplate() {
 exports.UnicodeArrayRotateFunctionTemplate = UnicodeArrayRotateFunctionTemplate;
 
 /***/ },
-/* 70 */
+/* 72 */
 /***/ function(module, exports) {
 
 "use strict";
 "use strict";
 
 /***/ },
-/* 71 */
+/* 73 */
 /***/ function(module, exports) {
 
 module.exports = require("chance");
 
 /***/ },
-/* 72 */
+/* 74 */
 /***/ function(module, exports) {
 
 module.exports = require("class-validator");
 
 /***/ },
-/* 73 */
+/* 75 */
 /***/ function(module, exports) {
 
 module.exports = require("commander");
 
 /***/ },
-/* 74 */
+/* 76 */
 /***/ function(module, exports) {
 
 module.exports = require("fs");
 
 /***/ },
-/* 75 */
+/* 77 */
 /***/ function(module, exports) {
 
 module.exports = require("mkdirp");
 
 /***/ },
-/* 76 */,
-/* 77 */
+/* 78 */,
+/* 79 */
 /***/ function(module, exports, __webpack_require__) {
 
 "use strict";

+ 6 - 1
src/NodeUtils.ts

@@ -50,14 +50,19 @@ export class NodeUtils {
 
     /**
      * @param code
+     * @param getFirstBlockStatementNodeByIndex
      * @returns {ESTree.Node}
      */
-    public static convertCodeToStructure (code: string): ESTree.Node {
+    public static convertCodeToStructure (code: string, getFirstBlockStatementNodeByIndex: boolean = true): ESTree.Node {
         let structure: ESTree.Node = esprima.parse(code);
 
         NodeUtils.addXVerbatimPropertyToLiterals(structure);
         NodeUtils.parentize(structure);
 
+        if (!getFirstBlockStatementNodeByIndex) {
+            return structure;
+        }
+
         return NodeUtils.getBlockStatementNodeByIndex(structure);
     }
 

+ 8 - 0
src/Nodes.ts

@@ -42,6 +42,14 @@ export class Nodes {
         return node.type === NodeType.CallExpression;
     }
 
+    /**
+     * @param node
+     * @returns {boolean}
+     */
+    public static isExpressionStatementNode (node: ESTree.Node): node is ESTree.ExpressionStatement {
+        return node.type === NodeType.ExpressionStatement;
+    }
+
     /**
      * @param node
      * @returns {boolean}

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

@@ -7,6 +7,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 { HiddenNodeAppender } from '../../hidden-node-appender/HiddenNodeAppender';
 import { NodeUtils } from '../../NodeUtils';
 
 export class ConsoleOutputDisableExpressionNode extends AbstractCustomNode {
@@ -19,7 +20,11 @@ export class ConsoleOutputDisableExpressionNode extends AbstractCustomNode {
      * @param blockScopeNode
      */
     public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
-        NodeUtils.prependNode(blockScopeNode.body, this.getNode());
+        HiddenNodeAppender.appendNode(
+            blockScopeNode.body,
+            this.getNode(),
+            HiddenNodeAppender.getIndexByThreshold(blockScopeNode.body.length)
+        );
     }
 
     /**

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

@@ -9,6 +9,7 @@ import { AppendState } from '../../enums/AppendState';
 import { DomainLockNodeTemplate } from '../../templates/custom-nodes/domain-lock-nodes/domain-lock-node/DomainLockNodeTemplate';
 
 import { AbstractCustomNode } from '../AbstractCustomNode';
+import { HiddenNodeAppender } from '../../hidden-node-appender/HiddenNodeAppender';
 import { NodeUtils } from '../../NodeUtils';
 import { Utils } from '../../Utils';
 
@@ -22,7 +23,11 @@ export class DomainLockNode extends AbstractCustomNode {
      * @param blockScopeNode
      */
     public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
-        NodeUtils.prependNode(blockScopeNode.body, this.getNode());
+        HiddenNodeAppender.appendNode(
+            blockScopeNode.body,
+            this.getNode(),
+            HiddenNodeAppender.getIndexByThreshold(blockScopeNode.body.length)
+        );
     }
 
     /**

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

@@ -11,7 +11,7 @@ import { SelfDefendingTemplate } from '../../templates/custom-nodes/self-defendi
 import { AbstractCustomNode } from '../AbstractCustomNode';
 import { JavaScriptObfuscator } from '../../JavaScriptObfuscator';
 import { NodeUtils } from '../../NodeUtils';
-import { Utils } from '../../Utils';
+import { HiddenNodeAppender } from '../../hidden-node-appender/HiddenNodeAppender';
 
 export class SelfDefendingUnicodeNode extends AbstractCustomNode {
     /**
@@ -23,17 +23,11 @@ export class SelfDefendingUnicodeNode extends AbstractCustomNode {
      * @param blockScopeNode
      */
     public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
-        let programBodyLength: number = blockScopeNode.body.length,
-            randomIndex: number = 0;
-
-        if (programBodyLength > 2) {
-            randomIndex = Utils.getRandomGenerator().integer({
-                min: programBodyLength / 2,
-                max: programBodyLength - 1
-            });
-        }
-
-        NodeUtils.insertNodeAtIndex(blockScopeNode.body, this.getNode(), randomIndex);
+        HiddenNodeAppender.appendNode(
+            blockScopeNode.body,
+            this.getNode(),
+            HiddenNodeAppender.getIndexByThreshold(blockScopeNode.body.length)
+        );
     }
 
     /**

+ 145 - 0
src/hidden-node-appender/ASTTreeBlockScopeAnalyzer.ts

@@ -0,0 +1,145 @@
+import * as estraverse from 'estraverse';
+import * as ESTree from 'estree';
+
+import { TNodeWithBlockStatement } from '../types/TNodeWithBlockStatement';
+
+import { IBlockScopeTraceData } from '../interfaces/IBlockScopeTraceData';
+
+import { Nodes } from '../Nodes';
+import { NodeUtils } from '../NodeUtils';
+import { IAnalyzer } from '../interfaces/IAnalyzer';
+
+/**
+ * This class generates a structure with order of function trace calls
+ *
+ * For example:
+ *
+ * function Foo () {
+ *     var baz = function () {
+ *
+ *     }
+ *
+ *     baz();
+ * }
+ *
+ * foo();
+ *
+ * Will generate a structure like:
+ *
+ * [
+ *      {
+ *          callee: FOO_FUNCTION_NODE
+ *          name: 'Foo',
+ *          trace: [
+ *              {
+ *                  callee: BAZ_FUNCTION_NODE,
+ *                  name: 'baz,
+ *                  trace: []
+ *              }
+ *          ]
+ *      }
+ * ]
+ */
+export class ASTTreeBlockScopeAnalyzer <T> implements IAnalyzer<T> {
+    /**
+     * @type {ESTree.Node[]}
+     */
+    private blockScopeBody: ESTree.Node[];
+
+    /**
+     * @type {T[]}
+     */
+    private blockScopeTraceData: T[] = [];
+
+    /**
+     * @param blockScopeBody
+     */
+    constructor (blockScopeBody: ESTree.Node[]) {
+        this.blockScopeBody = blockScopeBody;
+    }
+
+    /**
+     * @returns {T}
+     */
+    public analyze (): T[] {
+        if (this.blockScopeBody.length === 1) {
+            estraverse.traverse(this.blockScopeBody[0], {
+                enter: (node: ESTree.Node): any => {
+                    if (Nodes.isBlockStatementNode(node)) {
+                        this.analyzeRecursive(node.body, this.blockScopeTraceData);
+
+                        return estraverse.VisitorOption.Skip;
+                    }
+                }
+            });
+        } else {
+            this.analyzeRecursive(this.blockScopeBody, this.blockScopeTraceData);
+        }
+
+        return this.blockScopeTraceData;
+    }
+
+    /**
+     * @param blockScopeBody
+     * @param dataTree
+     */
+    private analyzeRecursive (blockScopeBody: ESTree.Node[], dataTree: any): void {
+        for (let rootNode of blockScopeBody) {
+            estraverse.traverse(rootNode, {
+                enter: (node: ESTree.Node): any => {
+                    if (
+                        Nodes.isCallExpressionNode(node) &&
+                        Nodes.isIdentifierNode(node.callee) &&
+                        rootNode.parentNode === NodeUtils.getBlockScopeOfNode(node)
+                    ) {
+                        const calleeNode: TNodeWithBlockStatement|null = this.getCalleeBlockStatement(
+                            NodeUtils.getBlockScopeOfNode(blockScopeBody[0]),
+                            node.callee.name
+                        );
+
+                        if (!calleeNode) {
+                            return estraverse.VisitorOption.Break;
+                        }
+
+                        const data: IBlockScopeTraceData = {
+                            callee: calleeNode,
+                            name: node.callee.name,
+                            trace: []
+                        };
+
+                        dataTree.push(data);
+
+                        this.analyzeRecursive(calleeNode.body, data.trace)
+                    }
+                }
+            });
+        }
+    }
+
+    private getCalleeBlockStatement (node: ESTree.Node, name: string): TNodeWithBlockStatement|null {
+        let calleeBlockStatement: TNodeWithBlockStatement|null = null;
+
+        estraverse.traverse(node, {
+            enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
+                if (Nodes.isFunctionDeclarationNode(node) && node.id.name === name) {
+                    calleeBlockStatement = node.body;
+
+                    return estraverse.VisitorOption.Break;
+                }
+
+                if (
+                    Nodes.isFunctionExpressionNode(node) &&
+                    Nodes.isVariableDeclaratorNode(parentNode) &&
+                    Nodes.isIdentifierNode(parentNode.id) &&
+                    parentNode.id.name === name
+                ) {
+                    calleeBlockStatement = node.body;
+
+                    return estraverse.VisitorOption.Break;
+                }
+            }
+        });
+
+        return calleeBlockStatement;
+    }
+}

+ 78 - 0
src/hidden-node-appender/HiddenNodeAppender.ts

@@ -0,0 +1,78 @@
+import * as ESTree from 'estree';
+
+import { TNodeWithBlockStatement } from '../types/TNodeWithBlockStatement';
+
+import { IBlockScopeTraceData } from '../interfaces/IBlockScopeTraceData';
+
+import { ASTTreeBlockScopeAnalyzer } from './ASTTreeBlockScopeAnalyzer';
+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 HiddenNodeAppender {
+    /**
+     * @param blockScopeBody
+     * @param node
+     * @param index
+     */
+    public static appendNode (blockScopeBody: ESTree.Node[], node: ESTree.Node, index: number = 0): void {
+        const blockScopeTraceData: IBlockScopeTraceData[] = new ASTTreeBlockScopeAnalyzer<IBlockScopeTraceData>(blockScopeBody).analyze();
+
+        if (!blockScopeTraceData.length) {
+            NodeUtils.prependNode(blockScopeBody, node);
+
+            return;
+        }
+
+        NodeUtils.prependNode(
+            HiddenNodeAppender.getOptimalBlockScope(blockScopeTraceData, index).body,
+            node
+        );
+    }
+
+    /**
+     * @param blockStatementBodyLength
+     * @param threshold
+     */
+    public static getIndexByThreshold (blockStatementBodyLength: number, threshold: number = 0.1): number {
+        if (threshold < 0 || threshold > 1) {
+            throw new RangeError('`threshold` parameter should has value between 0 and 1');
+        }
+
+        return Utils.getRandomGenerator().integer({
+            min: 0,
+            max: Math.round(blockStatementBodyLength * threshold)
+        })
+    }
+
+    /**
+     * @param blockScopeTraceData
+     * @param index
+     * @returns {TNodeWithBlockStatement}
+     */
+    private static getOptimalBlockScope (blockScopeTraceData: IBlockScopeTraceData[], index: number): TNodeWithBlockStatement {
+        const firstCall: IBlockScopeTraceData = blockScopeTraceData[index];
+
+        if (firstCall.trace.length) {
+            return HiddenNodeAppender.getOptimalBlockScope(firstCall.trace, 0);
+        } else {
+            return firstCall.callee;
+        }
+    }
+}

+ 3 - 0
src/interfaces/IAnalyzer.d.ts

@@ -0,0 +1,3 @@
+export interface IAnalyzer <T> {
+    analyze (): T[];
+}

+ 7 - 0
src/interfaces/IBlockScopeTraceData.d.ts

@@ -0,0 +1,7 @@
+import { TNodeWithBlockStatement } from '../types/TNodeWithBlockStatement';
+
+export interface IBlockScopeTraceData {
+    callee: TNodeWithBlockStatement;
+    name: string;
+    trace: IBlockScopeTraceData[];
+}

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

@@ -5,6 +5,7 @@ export interface IObfuscatorOptions {
     debugProtection?: boolean;
     debugProtectionInterval?: boolean;
     disableConsoleOutput?: boolean;
+    domainLock?: string[];
     encodeUnicodeLiterals?: boolean;
     reservedNames?: string[];
     rotateUnicodeArray?: boolean;
@@ -16,6 +17,5 @@ export interface IObfuscatorOptions {
     unicodeArray?: boolean;
     unicodeArrayThreshold?: number;
     wrapUnicodeArrayCalls?: boolean;
-    domainLock?: string[];
     [key: string]: any;
 }

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

@@ -5,6 +5,7 @@ export interface IOptions {
     readonly debugProtection: boolean;
     readonly debugProtectionInterval: boolean;
     readonly disableConsoleOutput: boolean;
+    readonly domainLock: string[];
     readonly encodeUnicodeLiterals: boolean;
     readonly reservedNames: string[];
     readonly rotateUnicodeArray: boolean;
@@ -16,5 +17,4 @@ export interface IOptions {
     readonly unicodeArray: boolean;
     readonly unicodeArrayThreshold: number;
     readonly wrapUnicodeArrayCalls: boolean;
-    readonly domainLock: string[];
 }

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

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

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

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

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

@@ -5,7 +5,7 @@ import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
 const assert: Chai.AssertStatic = require('chai').assert;
 
 describe('UnicodeArrayCallsWrapper', () => {
-    it('should correctly append `UnicodeArrayCallsWrapper` custom node into the obfuscated code if `wrapUnicodeArrayCalls` option is set', () => {
+    it('should correctly appendNode `UnicodeArrayCallsWrapper` custom node into the obfuscated code if `wrapUnicodeArrayCalls` option is set', () => {
         let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
             `var test = 'test';`,
             {
@@ -16,7 +16,7 @@ describe('UnicodeArrayCallsWrapper', () => {
         assert.match(obfuscationResult.getObfuscatedCode(), /return _0x([a-z0-9]){4}\[parseInt\(_0x([a-z0-9]){4,6}, *0x010\)\];/);
     });
 
-    it('should\'t append `UnicodeArrayDecodeNode` custom node into the obfuscated code if `wrapUnicodeArrayCalls` option is not set', () => {
+    it('should\'t appendNode `UnicodeArrayDecodeNode` custom node into the obfuscated code if `wrapUnicodeArrayCalls` option is not set', () => {
         let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
             `var test = 'test';`,
             {

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

@@ -5,7 +5,7 @@ import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscator';
 const assert: Chai.AssertStatic = require('chai').assert;
 
 describe('UnicodeArrayDecodeNode', () => {
-    it('should correctly append `UnicodeArrayDecodeNode` custom node into the obfuscated code if `encodeUnicodeLiterals` option is set', () => {
+    it('should correctly appendNode `UnicodeArrayDecodeNode` custom node into the obfuscated code if `encodeUnicodeLiterals` option is set', () => {
         let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
             `var test = 'test';`,
             {
@@ -16,7 +16,7 @@ describe('UnicodeArrayDecodeNode', () => {
         assert.match(obfuscationResult.getObfuscatedCode(), /decodeURI\(atob\(_0x([a-z0-9]){4}\[_0x([a-z0-9]){4,6}\]\)\)/);
     });
 
-    it('should\'t append `UnicodeArrayDecodeNode` custom node into the obfuscated code if `encodeUnicodeLiterals` option is not set', () => {
+    it('should\'t appendNode `UnicodeArrayDecodeNode` custom node into the obfuscated code if `encodeUnicodeLiterals` option is not set', () => {
         let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
             `var test = 'test';`,
             {

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

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

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

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

+ 2 - 0
test/index.spec.ts

@@ -13,6 +13,8 @@ import './unit-tests/OptionsNormalizer.spec';
 import './unit-tests/SourceMapCorrector.spec';
 import './unit-tests/Utils.spec';
 import './unit-tests/cli/CLIUtils.spec';
+import './unit-tests/hidden-node-appender/ASTTreeBlockScopeAnalyzer-spec';
+import './unit-tests/hidden-node-appender/HiddenNodeAppender-spec';
 import './unit-tests/node-obfuscators/CatchClauseObfuscator.spec';
 import './unit-tests/node-obfuscators/FunctionDeclarationObfuscator.spec';
 import './unit-tests/node-obfuscators/FunctionObfuscator.spec';

+ 1 - 1
test/unit-tests/NodeUtils.spec.ts

@@ -42,7 +42,7 @@ describe('NodeUtils', () => {
             NodeUtils.appendNode(blockStatementNode.body, expressionStatementNode);
         });
 
-        it('should append given node to a `BlockStatement` node body', () => {
+        it('should appendNode given node to a `BlockStatement` node body', () => {
             assert.deepEqual(blockStatementNode, expectedBlockStatementNode);
         });
 

+ 246 - 0
test/unit-tests/hidden-node-appender/ASTTreeBlockScopeAnalyzer-spec.ts

@@ -0,0 +1,246 @@
+import * as chai from 'chai';
+import * as estraverse from 'estraverse';
+import * as ESTree from 'estree';
+
+import { TNodeWithBlockStatement } from '../../../src/types/TNodeWithBlockStatement';
+
+import { IBlockScopeTraceData } from '../../../src/interfaces/IBlockScopeTraceData';
+
+import { ASTTreeBlockScopeAnalyzer } from '../../../src/hidden-node-appender/ASTTreeBlockScopeAnalyzer';
+import { Nodes } from '../../../src/Nodes';
+import { NodeUtils } from '../../../src/NodeUtils';
+
+const assert: any = chai.assert;
+
+/**
+ * @param astTree
+ * @param name
+ * @returns {ESTree.FunctionDeclaration|null}
+ */
+function getFunctionDeclarationByName (astTree: ESTree.Node, name: string): ESTree.FunctionDeclaration|null {
+    let functionDeclarationNode: ESTree.FunctionDeclaration|null = null;
+
+    estraverse.traverse(astTree, {
+        enter: (node: ESTree.Node): any => {
+            if (
+                Nodes.isFunctionDeclarationNode(node) &&
+                Nodes.isIdentifierNode(node.id) &&
+                node.id.name === name
+            ) {
+                functionDeclarationNode = node;
+
+                return estraverse.VisitorOption.Break;
+            }
+        }
+    });
+
+    return functionDeclarationNode;
+}
+
+/**
+ * @param astTree
+ * @param name
+ * @returns {ESTree.FunctionExpression|null}
+ */
+function getFunctionExpressionByName (astTree: ESTree.Node, name: string): ESTree.FunctionExpression|null {
+    let functionExpressionNode: ESTree.FunctionExpression|null = null;
+
+    estraverse.traverse(astTree, {
+        enter: (node: ESTree.Node): any => {
+            if (
+                Nodes.isVariableDeclaratorNode(node) &&
+                node.init &&
+                Nodes.isFunctionExpressionNode(node.init) &&
+                Nodes.isIdentifierNode(node.id) &&
+                node.id.name === name
+            ) {
+                functionExpressionNode = node.init;
+
+                return estraverse.VisitorOption.Break;
+            }
+        }
+    });
+
+    return functionExpressionNode;
+}
+
+describe('ASTTreeBlockScopeAnalyzer', () => {
+    describe('analyze (): IBlockScopeTraceData[]', () => {
+        let ASTTree: TNodeWithBlockStatement,
+            blockScopeTraceData: IBlockScopeTraceData[],
+            expectedBlockScopeTraceData: IBlockScopeTraceData[];
+
+        it('should returns correct BlockScopeTraceData - variant #1', () => {
+            ASTTree = <ESTree.Program>NodeUtils.convertCodeToStructure(`
+                function foo () {
+                
+                }
+                
+                function bar () {
+                    function inner1 () {
+                    
+                    }
+                
+                    function inner2 () {
+                        var inner3 = function () {
+                            
+                        }
+                        
+                        inner3();
+                    }
+                    
+                    inner2();
+                    inner1();
+                }
+                
+                function baz () {
+                
+                }
+                
+                baz();
+                foo();
+                bar();
+            `, false);
+
+            expectedBlockScopeTraceData = [
+                {
+                    name: 'baz',
+                    callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(ASTTree, 'baz')).body,
+                    trace: []
+                },
+                {
+                    name: 'foo',
+                    callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(ASTTree, 'foo')).body,
+                    trace: []
+                },
+                {
+                    name: 'bar',
+                    callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(ASTTree, 'bar')).body,
+                    trace: [
+                        {
+                            name: 'inner2',
+                            callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(ASTTree, 'inner2')).body,
+                            trace: [
+                                {
+                                    name: 'inner3',
+                                    callee: (<ESTree.FunctionExpression>getFunctionExpressionByName(ASTTree, 'inner3')).body,
+                                    trace: []
+                                },
+                            ]
+                        },
+                        {
+                            name: 'inner1',
+                            callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(ASTTree, 'inner1')).body,
+                            trace: []
+                        },
+                    ]
+                }
+            ];
+
+            blockScopeTraceData = new ASTTreeBlockScopeAnalyzer<IBlockScopeTraceData>(ASTTree.body).analyze();
+
+            assert.deepEqual(blockScopeTraceData, expectedBlockScopeTraceData);
+        });
+
+        it('should returns correct BlockScopeTraceData - variant #2', () => {
+            ASTTree = <ESTree.Program>NodeUtils.convertCodeToStructure(`
+                bar();
+            
+                function foo () {
+                
+                }
+                
+                function bar () {
+                    
+                }
+                
+                function baz () {
+                    function inner1 () {
+                    
+                    }
+                    
+                    inner1();
+                }
+                
+                baz();
+                foo();
+            `, false);
+
+            expectedBlockScopeTraceData = [
+                {
+                    name: 'bar',
+                    callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(ASTTree, 'bar')).body,
+                    trace: []
+                },
+                {
+                    name: 'baz',
+                    callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(ASTTree, 'baz')).body,
+                    trace: [
+                        {
+                            name: 'inner1',
+                            callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(ASTTree, 'inner1')).body,
+                            trace: []
+                        },
+                    ]
+                },
+                {
+                    name: 'foo',
+                    callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(ASTTree, 'foo')).body,
+                    trace: []
+                }
+            ];
+
+            blockScopeTraceData = new ASTTreeBlockScopeAnalyzer<IBlockScopeTraceData>(ASTTree.body).analyze();
+
+            assert.deepEqual(blockScopeTraceData, expectedBlockScopeTraceData);
+        });
+
+        it('should returns correct BlockScopeTraceData - variant #3', () => {
+            ASTTree = <ESTree.Program>NodeUtils.convertCodeToStructure(`
+                bar();
+            
+                function bar () {
+                
+                }
+            `, false);
+
+            expectedBlockScopeTraceData = [
+                {
+                    name: 'bar',
+                    callee: (<ESTree.FunctionDeclaration>getFunctionDeclarationByName(ASTTree, 'bar')).body,
+                    trace: []
+                }
+            ];
+
+            blockScopeTraceData = new ASTTreeBlockScopeAnalyzer<IBlockScopeTraceData>(ASTTree.body).analyze();
+
+            assert.deepEqual(blockScopeTraceData, expectedBlockScopeTraceData);
+        });
+
+        it('should returns correct BlockScopeTraceData - variant #4', () => {
+            ASTTree = <ESTree.Program>NodeUtils.convertCodeToStructure(`
+                function bar () {
+                
+                }
+            `, false);
+
+            expectedBlockScopeTraceData = [];
+
+            blockScopeTraceData = new ASTTreeBlockScopeAnalyzer<IBlockScopeTraceData>(ASTTree.body).analyze();
+
+            assert.deepEqual(blockScopeTraceData, expectedBlockScopeTraceData);
+        });
+
+        it('should returns correct BlockScopeTraceData - variant #5', () => {
+            ASTTree = <ESTree.Program>NodeUtils.convertCodeToStructure(`
+                bar();
+            `, false);
+
+            expectedBlockScopeTraceData = [];
+
+            blockScopeTraceData = new ASTTreeBlockScopeAnalyzer<IBlockScopeTraceData>(ASTTree.body).analyze();
+
+            assert.deepEqual(blockScopeTraceData, expectedBlockScopeTraceData);
+        });
+    });
+});

+ 302 - 0
test/unit-tests/hidden-node-appender/HiddenNodeAppender-spec.ts

@@ -0,0 +1,302 @@
+import * as chai from 'chai';
+import * as ESTree from 'estree';
+
+import { HiddenNodeAppender } from '../../../src/hidden-node-appender/HiddenNodeAppender';
+import { NodeUtils } from '../../../src/NodeUtils';
+
+const assert: any = chai.assert;
+
+describe('ASTTreeBlockScopeAnalyzer', () => {
+    describe('appendNode (blockScopeNode: TNodeWithBlockStatement, node: ESTree.Node): void', () => {
+        let astTree: ESTree.Program,
+            expectedAstTree: ESTree.Program,
+            node: ESTree.Node;
+
+        beforeEach(() => {
+            node = NodeUtils.convertCodeToStructure(`
+                var test = 1;
+            `);
+        });
+
+        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 () {
+                    
+                    }
+                
+                    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 () {
+                        }
+                        
+                        inner3();
+                    }
+                    
+                    inner2();
+                    inner1();
+                }
+                
+                function baz () {
+                    var test = 1;
+                }
+                
+                baz();
+                foo();
+                bar();
+            `, false);
+
+            HiddenNodeAppender.appendNode(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 () {
+                            
+                        }
+                        
+                        inner3();
+                    }
+                    
+                    inner2();
+                    inner1();
+                }
+                
+                function baz () {
+                
+                }
+                
+                bar();
+                baz();
+                foo();
+            `, false);
+
+            expectedAstTree = <ESTree.Program>NodeUtils.convertCodeToStructure(`
+                function foo () {
+                
+                }
+                
+                function bar () {
+                    function inner1 () {
+                    
+                    }
+                
+                    function inner2 () {
+                        var inner3 = function () {
+                            var test = 1;
+                        }
+                        
+                        inner3();
+                    }
+                    
+                    inner2();
+                    inner1();
+                }
+                
+                function baz () {
+                
+                }
+                
+                bar();
+                baz();
+                foo();
+            `, false);
+
+            HiddenNodeAppender.appendNode(astTree.body, node);
+
+            NodeUtils.parentize(astTree);
+
+            assert.deepEqual(astTree, expectedAstTree);
+        });
+
+        describe('append by specific index', () => {
+            let astTree: ESTree.Program;
+
+            beforeEach(() => {
+                astTree = <ESTree.Program>NodeUtils.convertCodeToStructure(`
+                    function foo () {
+                    
+                    }
+                    
+                    function bar () {
+                        function inner1 () {
+                        
+                        }
+                    
+                        function inner2 () {
+                            var inner3 = function () {
+                               
+                            }
+                            
+                            inner3();
+                        }
+                        
+                        inner2();
+                        inner1();
+                    }
+                    
+                    function baz () {
+                    
+                    }
+                    
+                    bar();
+                    baz();
+                    foo();
+                `, false);
+            });
+
+            it('should append node into deepest function call by specified index in calls trace - variant #1', () => {
+                expectedAstTree = <ESTree.Program>NodeUtils.convertCodeToStructure(`
+                    function foo () {
+                        var test = 1;
+                    }
+                    
+                    function bar () {
+                        function inner1 () {
+                        
+                        }
+                    
+                        function inner2 () {
+                            var inner3 = function () {
+                                
+                            }
+                            
+                            inner3();
+                        }
+                        
+                        inner2();
+                        inner1();
+                    }
+                    
+                    function baz () {
+                    
+                    }
+                    
+                    bar();
+                    baz();
+                    foo();
+                `, false);
+
+                HiddenNodeAppender.appendNode(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(`
+                    function foo () {
+                        
+                    }
+                    
+                    function bar () {
+                        function inner1 () {
+                        
+                        }
+                    
+                        function inner2 () {
+                            var inner3 = function () {
+                                
+                            }
+                            
+                            inner3();
+                        }
+                        
+                        inner2();
+                        inner1();
+                    }
+                    
+                    function baz () {
+                        var test = 1;
+                    }
+                    
+                    bar();
+                    baz();
+                    foo();
+                `, false);
+
+                HiddenNodeAppender.appendNode(astTree.body, node, 1);
+
+                NodeUtils.parentize(astTree);
+
+                assert.deepEqual(astTree, expectedAstTree);
+            });
+        });
+    });
+
+    describe('getIndexByThreshold (blockStatementBodyLength: number, threshold: number = 0.1): number', () => {
+        it('should returns random index between 0 and index based on threshold value', () => {
+            let index: number;
+
+            for (let i = 0; i < 10; i++) {
+                index = HiddenNodeAppender.getIndexByThreshold(100, 0.1);
+
+                assert.isAtLeast(index, 0);
+                assert.isAtMost(index, 10);
+            }
+
+            for (let i = 0; i < 10; i++) {
+                index = HiddenNodeAppender.getIndexByThreshold(10, 0.5);
+
+                assert.isAtLeast(index, 0);
+                assert.isAtMost(index, 5);
+            }
+
+            for (let i = 0; i < 10; i++) {
+                index = HiddenNodeAppender.getIndexByThreshold(1, 1);
+
+                assert.isAtLeast(index, 0);
+                assert.isAtMost(index, 1);
+            }
+        });
+    });
+});