瀏覽代碼

Merge pull request #165 from javascript-obfuscator/object-keys-obfuscation

Object keys obfuscation
Timofey Kachalov 7 年之前
父節點
當前提交
787bdeca94
共有 100 個文件被更改,包括 2009 次插入798 次删除
  1. 2 0
      CHANGELOG.md
  2. 42 0
      README.md
  3. 0 0
      dist/index.js
  4. 16 16
      package.json
  5. 35 63
      src/JavaScriptObfuscator.ts
  6. 5 0
      src/cli/JavaScriptObfuscatorCLI.ts
  7. 5 3
      src/container/InversifyContainerFacade.ts
  8. 1 0
      src/container/ServiceIdentifiers.ts
  9. 18 1
      src/container/modules/custom-nodes/CustomNodesModule.ts
  10. 5 0
      src/container/modules/node-transformers/ConvertingTransformersModule.ts
  11. 6 0
      src/container/modules/node-transformers/FinalizingTransformersModule.ts
  12. 5 0
      src/container/modules/node-transformers/PreparingTransformersModule.ts
  13. 0 18
      src/custom-nodes/AbstractCustomNode.ts
  14. 3 3
      src/custom-nodes/AbstractCustomNodeGroup.ts
  15. 4 4
      src/custom-nodes/console-output-nodes/group/ConsoleOutputCustomNodeGroup.ts
  16. 89 0
      src/custom-nodes/dead-code-injection-nodes/BlockStatementDeadCodeInjectionNode.ts
  17. 4 4
      src/custom-nodes/debug-protection-nodes/group/DebugProtectionCustomNodeGroup.ts
  18. 4 4
      src/custom-nodes/domain-lock-nodes/group/DomainLockCustomNodeGroup.ts
  19. 2 2
      src/custom-nodes/node-calls-controller-nodes/NodeCallsControllerFunctionNode.ts
  20. 2 2
      src/custom-nodes/self-defending-nodes/SelfDefendingUnicodeNode.ts
  21. 4 4
      src/custom-nodes/self-defending-nodes/group/SelfDefendingCustomNodeGroup.ts
  22. 2 2
      src/custom-nodes/string-array-nodes/StringArrayCallsWrapper.ts
  23. 2 2
      src/custom-nodes/string-array-nodes/StringArrayRotateFunctionNode.ts
  24. 3 3
      src/custom-nodes/string-array-nodes/group/StringArrayCustomNodeGroup.ts
  25. 5 0
      src/declarations/js-string-escape.d.ts
  26. 3 3
      src/enums/analyzers/stack-trace-analyzer/CalleeDataExtractor.ts
  27. 9 9
      src/enums/custom-nodes/ControlFlowCustomNode.ts
  28. 10 10
      src/enums/custom-nodes/CustomNode.ts
  29. 5 5
      src/enums/custom-nodes/CustomNodeGroup.ts
  30. 3 0
      src/enums/custom-nodes/DeadCodeInjectionCustomNode.ts
  31. 2 5
      src/enums/logger/LoggingMessage.ts
  32. 19 17
      src/enums/node-transformers/NodeTransformer.ts
  33. 8 0
      src/enums/node-transformers/TransformationStage.ts
  34. 4 4
      src/enums/node-transformers/obfuscating-transformers/obfuscating-replacers/ControlFlowReplacer.ts
  35. 1 1
      src/enums/node-transformers/obfuscating-transformers/obfuscating-replacers/IdentifierObfuscatingReplacer.ts
  36. 3 3
      src/enums/node-transformers/obfuscating-transformers/obfuscating-replacers/LiteralObfuscatingReplacer.ts
  37. 2 2
      src/enums/node-transformers/preparing-transformers/obfuscating-guards/ObfuscatingGuard.ts
  38. 2 0
      src/enums/node/NodeType.ts
  39. 0 5
      src/interfaces/custom-nodes/ICustomNode.d.ts
  40. 2 2
      src/interfaces/custom-nodes/ICustomNodeGroup.d.ts
  41. 11 2
      src/interfaces/node-transformers/INodeTransformer.d.ts
  42. 7 1
      src/interfaces/node-transformers/ITransformersRunner.d.ts
  43. 1 0
      src/interfaces/options/IOptions.d.ts
  44. 3 3
      src/interfaces/source-map/ISourceMapCorrector.d.ts
  45. 5 2
      src/node-transformers/AbstractNodeTransformer.ts
  46. 16 5
      src/node-transformers/TransformersRunner.ts
  47. 24 10
      src/node-transformers/control-flow-transformers/BlockStatementControlFlowTransformer.ts
  48. 32 24
      src/node-transformers/control-flow-transformers/FunctionControlFlowTransformer.ts
  49. 17 9
      src/node-transformers/converting-transformers/MemberExpressionTransformer.ts
  50. 17 9
      src/node-transformers/converting-transformers/MethodDefinitionTransformer.ts
  51. 239 0
      src/node-transformers/converting-transformers/ObjectExpressionKeysTransformer.ts
  52. 18 9
      src/node-transformers/converting-transformers/TemplateLiteralTransformer.ts
  53. 139 112
      src/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.ts
  54. 17 9
      src/node-transformers/obfuscating-transformers/CatchClauseTransformer.ts
  55. 23 15
      src/node-transformers/obfuscating-transformers/ClassDeclarationTransformer.ts
  56. 23 15
      src/node-transformers/obfuscating-transformers/FunctionDeclarationTransformer.ts
  57. 23 15
      src/node-transformers/obfuscating-transformers/FunctionTransformer.ts
  58. 17 9
      src/node-transformers/obfuscating-transformers/LabeledStatementTransformer.ts
  59. 17 9
      src/node-transformers/obfuscating-transformers/LiteralTransformer.ts
  60. 17 9
      src/node-transformers/obfuscating-transformers/ObjectExpressionTransformer.ts
  61. 19 11
      src/node-transformers/obfuscating-transformers/VariableDeclarationTransformer.ts
  62. 18 9
      src/node-transformers/preparing-transformers/CommentsTransformer.ts
  63. 184 0
      src/node-transformers/preparing-transformers/EvaCallExpressionTransformer.ts
  64. 15 7
      src/node-transformers/preparing-transformers/ObfuscatingGuardsTransformer.ts
  65. 16 7
      src/node-transformers/preparing-transformers/ParentificationTransformer.ts
  66. 101 74
      src/node/NodeAppender.ts
  67. 53 4
      src/node/NodeGuards.ts
  68. 89 110
      src/node/NodeUtils.ts
  69. 24 0
      src/node/Nodes.ts
  70. 6 0
      src/options/Options.ts
  71. 1 0
      src/options/presets/Default.ts
  72. 2 1
      src/options/presets/NoCustomNodes.ts
  73. 5 0
      src/types/container/custom-nodes/TDeadNodeInjectionCustomNodeFactory.d.ts
  74. 3 0
      src/types/node/TNodeWithBlockScope.d.ts
  75. 0 3
      src/types/node/TNodeWithBlockStatement.d.ts
  76. 3 0
      src/types/node/TNodeWithScope.d.ts
  77. 7 25
      test/dev/dev.ts
  78. 11 11
      test/functional-tests/analyzers/stack-trace-analyzer/StackTraceAnalyzer.spec.ts
  79. 3 3
      test/functional-tests/custom-nodes/console-output-nodes/ConsoleOutputDisableExpressionNode.spec.ts
  80. 3 3
      test/functional-tests/custom-nodes/domain-lock-nodes/DomainLockNode.spec.ts
  81. 3 3
      test/functional-tests/custom-nodes/string-array-nodes/StringArrayCallsWrapper.spec.ts
  82. 3 3
      test/functional-tests/custom-nodes/string-array-nodes/StringArrayNode.spec.ts
  83. 3 3
      test/functional-tests/custom-nodes/string-array-nodes/StringArrayRotateFunctionNode.spec.ts
  84. 10 9
      test/functional-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts
  85. 16 16
      test/functional-tests/node-transformers/control-flow-transformers/block-statement-control-flow-transformer/BlockStatementControlFlowTransformer.spec.ts
  86. 3 3
      test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/binary-expression-control-flow-replacer/BinaryExpressionControlFlowReplacer.spec.ts
  87. 4 4
      test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/call-expression-control-flow-replacer/CallExpressionControlFlowReplacer.spec.ts
  88. 5 5
      test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/logical-expression-control-flow-replacer/LogicalExpressionControlFlowReplacer.spec.ts
  89. 2 2
      test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/string-litertal-control-flow-replacer/StringLiteralControlFlowReplacer.spec.ts
  90. 9 9
      test/functional-tests/node-transformers/control-flow-transformers/function-control-flow-transformer/FunctionControlFlowTransformer.spec.ts
  91. 5 5
      test/functional-tests/node-transformers/converting-transformers/member-expression-transformer/MemberExpressionTransformer.spec.ts
  92. 4 4
      test/functional-tests/node-transformers/converting-transformers/method-definition-transformer/MethodDefinitionTransformer.spec.ts
  93. 319 0
      test/functional-tests/node-transformers/converting-transformers/object-expression-keys-transformer/ObjectExpressionKeysTransformer.spec.ts
  94. 3 0
      test/functional-tests/node-transformers/converting-transformers/object-expression-keys-transformer/fixtures/integration-with-control-flow-flattening.js
  95. 10 0
      test/functional-tests/node-transformers/converting-transformers/object-expression-keys-transformer/fixtures/nested-objects-1.js
  96. 14 0
      test/functional-tests/node-transformers/converting-transformers/object-expression-keys-transformer/fixtures/nested-objects-2.js
  97. 8 0
      test/functional-tests/node-transformers/converting-transformers/object-expression-keys-transformer/fixtures/placement-inside-catch-clause.js
  98. 7 0
      test/functional-tests/node-transformers/converting-transformers/object-expression-keys-transformer/fixtures/placement-inside-if-statement.js
  99. 8 0
      test/functional-tests/node-transformers/converting-transformers/object-expression-keys-transformer/fixtures/placement-inside-switch-case.js
  100. 7 0
      test/functional-tests/node-transformers/converting-transformers/object-expression-keys-transformer/fixtures/placement-inside-try-statement.js

+ 2 - 0
CHANGELOG.md

@@ -2,6 +2,8 @@ Change Log
 ===
 v0.14.0
 ---
+* **New option:** `transformObjectKeys` enables object keys transformation and obfuscation.
+* **New feature:** `eval` expressions obfuscation.
 * **Breaking change:** Now CLI obfuscating directory recursively. Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/157
 * Fixed runtime errors when `deadCodeInjection` is enabled and `identifierNamesGenerator` is set to `mangled`
 * Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/171

+ 42 - 0
README.md

@@ -291,6 +291,7 @@ Following options are available for the JS Obfuscator:
     stringArrayEncoding: false,
     stringArrayThreshold: 0.75,
     target: 'browser',
+    transformObjectKeys: false,
     unicodeEscapeSequence: false
 }
 ```
@@ -327,6 +328,7 @@ Following options are available for the JS Obfuscator:
     --string-array-encoding <boolean|string> [true, false, base64, rc4]
     --string-array-threshold <number>
     --target <string> [browser, extension, node]
+    --transform-object-keys <boolean>
     --unicode-escape-sequence <boolean>
 ```
 
@@ -707,6 +709,44 @@ Available values:
 Currently output code for `browser` and `node` targets is identical.
 Output code for `extension` target is not using `eval`.
 
+### `transformObjectKeys`
+Type: `boolean` Default: `false`
+
+Enables transformation of object keys.
+
+Example:
+```ts
+// input
+(function(){
+    var object = {
+        foo: 'test1',
+        bar: {
+            baz: 'test2'
+        }
+    };
+})();
+
+// output
+var _0x5a21 = [
+    'foo',
+    'test1',
+    'bar',
+    'baz',
+    'test2'
+];
+var _0x223f = function (_0x474dc0, _0x10db96) {
+    _0x474dc0 = _0x474dc0 - 0x0;
+    var _0x4c8bf7 = _0x5a21[_0x474dc0];
+    return _0x4c8bf7;
+};
+(function () {
+    var _0x2e1a8e = {};
+    _0x2e1a8e[_0x223f('0x0')] = _0x223f('0x1');
+    _0x2e1a8e[_0x223f('0x2')] = {};
+    _0x2e1a8e[_0x223f('0x2')][_0x223f('0x3')] = _0x223f('0x4');
+}());
+```
+
 ### `unicodeEscapeSequence`
 Type: `boolean` Default: `false`
 
@@ -737,6 +777,7 @@ Performance will 50-100% slower than without obfuscation
     stringArray: true,
     stringArrayEncoding: 'rc4',
     stringArrayThreshold: 1,
+    transformObjectKeys: true,
     unicodeEscapeSequence: false
 }
 ```
@@ -763,6 +804,7 @@ Performance will 30-35% slower than without obfuscation
     stringArray: true,
     stringArrayEncoding: 'base64',
     stringArrayThreshold: 0.75,
+    transformObjectKeys: true,
     unicodeEscapeSequence: false
 }
 ```

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


+ 16 - 16
package.json

@@ -1,6 +1,6 @@
 {
   "name": "javascript-obfuscator",
-  "version": "0.14.0-beta.1",
+  "version": "0.14.0-beta.2",
   "description": "JavaScript obfuscator",
   "keywords": [
     "obfuscator",
@@ -22,34 +22,34 @@
     "chalk": "2.3.0",
     "chance": "1.0.13",
     "class-validator": "0.7.3",
-    "commander": "2.12.2",
-    "escodegen-wallaby": "1.6.15",
+    "commander": "2.13.0",
+    "escodegen-wallaby": "1.6.16",
     "esprima": "4.0.0",
     "estraverse": "4.2.0",
     "inversify": "4.9.0",
+    "js-string-escape": "1.0.1",
     "md5": "2.2.1",
     "mkdirp": "0.5.1",
     "opencollective": "1.0.3",
-    "pjson": "^1.0.9",
+    "pjson": "1.0.9",
     "reflect-metadata": "0.1.10",
     "source-map-support": "0.5.0",
     "string-template": "1.0.0",
     "tslib": "1.8.1"
   },
   "devDependencies": {
-    "@types/chai": "4.0.10",
-    "@types/chance": "0.7.35",
-    "@types/commander": "2.12.2",
+    "@types/chai": "4.1.0",
+    "@types/chance": "0.7.36",
     "@types/escodegen": "0.0.6",
     "@types/esprima": "4.0.1",
     "@types/estraverse": "0.0.6",
     "@types/estree": "0.0.38",
     "@types/md5": "2.1.32",
     "@types/mkdirp": "0.5.2",
-    "@types/mocha": "2.2.45",
-    "@types/node": "8.5.2",
-    "@types/rimraf": "^2.0.2",
-    "@types/sinon": "4.1.2",
+    "@types/mocha": "2.2.46",
+    "@types/node": "9.3.0",
+    "@types/rimraf": "2.0.2",
+    "@types/sinon": "4.1.3",
     "@types/string-template": "1.0.2",
     "@types/webpack-env": "1.13.3",
     "awesome-typescript-loader": "3.4.1",
@@ -62,13 +62,13 @@
     "istanbul": "1.1.0-alpha.1",
     "mocha": "4.1.0",
     "pre-commit": "1.2.2",
-    "rimraf": "^2.6.2",
-    "sinon": "4.1.3",
-    "threads": "^0.10.0",
+    "rimraf": "2.6.2",
+    "sinon": "4.1.4",
+    "threads": "0.10.0",
     "ts-node": "4.1.0",
-    "tslint": "5.8.0",
+    "tslint": "5.9.1",
     "tslint-eslint-rules": "4.1.1",
-    "tslint-language-service": "0.9.7",
+    "tslint-language-service": "0.9.8",
     "tslint-webpack-plugin": "1.0.0",
     "typescript": "2.6.2",
     "webpack": "3.10.0",

+ 35 - 63
src/JavaScriptObfuscator.ts

@@ -23,6 +23,7 @@ import { ITransformersRunner } from './interfaces/node-transformers/ITransformer
 import { LoggingMessage } from './enums/logger/LoggingMessage';
 import { NodeTransformer } from './enums/node-transformers/NodeTransformer';
 import { ObfuscationEvent } from './enums/event-emitters/ObfuscationEvent';
+import { TransformationStage } from './enums/node-transformers/TransformationStage';
 
 import { NodeGuards } from './node/NodeGuards';
 
@@ -40,50 +41,28 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
     /**
      * @type {NodeTransformer[]}
      */
-    private static readonly controlFlowTransformersList: NodeTransformer[] = [
+    private static readonly transformersList: NodeTransformer[] = [
         NodeTransformer.BlockStatementControlFlowTransformer,
-        NodeTransformer.FunctionControlFlowTransformer
-    ];
-
-    /**
-     * @type {NodeTransformer[]}
-     */
-    private static readonly convertingTransformersList: NodeTransformer[] = [
-        NodeTransformer.MemberExpressionTransformer,
-        NodeTransformer.MethodDefinitionTransformer,
-        NodeTransformer.TemplateLiteralTransformer
-    ];
-
-    /**
-     * @type {NodeTransformer[]}
-     */
-    private static readonly deadCodeInjectionTransformersList: NodeTransformer[] = [
-        NodeTransformer.DeadCodeInjectionTransformer
-    ];
-
-    /**
-     * @type {NodeTransformer[]}
-     */
-    private static readonly obfuscatingTransformersList: NodeTransformer[] = [
-        NodeTransformer.CatchClauseTransformer,
         NodeTransformer.ClassDeclarationTransformer,
+        NodeTransformer.CommentsTransformer,
+        NodeTransformer.DeadCodeInjectionTransformer,
+        NodeTransformer.EvalCallExpressionTransformer,
+        NodeTransformer.FunctionControlFlowTransformer,
+        NodeTransformer.CatchClauseTransformer,
         NodeTransformer.FunctionDeclarationTransformer,
         NodeTransformer.FunctionTransformer,
         NodeTransformer.LabeledStatementTransformer,
         NodeTransformer.LiteralTransformer,
+        NodeTransformer.MemberExpressionTransformer,
+        NodeTransformer.MethodDefinitionTransformer,
+        NodeTransformer.ObfuscatingGuardsTransformer,
+        NodeTransformer.ObjectExpressionKeysTransformer,
         NodeTransformer.ObjectExpressionTransformer,
+        NodeTransformer.ParentificationTransformer,
+        NodeTransformer.TemplateLiteralTransformer,
         NodeTransformer.VariableDeclarationTransformer
     ];
 
-    /**
-     * @type {NodeTransformer[]}
-     */
-    private static readonly preparingTransformersList: NodeTransformer[] = [
-        NodeTransformer.CommentsTransformer,
-        NodeTransformer.ObfuscatingGuardsTransformer,
-        NodeTransformer.ParentificationTransformer
-    ];
-
     /**
      * @type {IStorage<ICustomNodeGroup>}
      */
@@ -205,18 +184,11 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
             return astTree;
         }
 
-        // first pass: AST-tree preparation
-        this.logger.info(LoggingMessage.StagePreparingASTTree);
-        astTree = this.transformersRunner.transform(
-            astTree,
-            JavaScriptObfuscator.preparingTransformersList
-        );
+        astTree = this.runTransformationStage(astTree, TransformationStage.Preparing);
 
-        // second pass: AST-tree analyzing
-        this.logger.info(LoggingMessage.StageAnalyzingASTTree);
+        this.logger.info(LoggingMessage.AnalyzingASTTreeStage);
         const stackTraceData: IStackTraceData[] = this.stackTraceAnalyzer.analyze(astTree);
 
-        // initialize custom node groups and configure custom nodes
         this.customNodeGroupStorage
             .getStorage()
             .forEach((customNodeGroup: ICustomNodeGroup) => {
@@ -230,32 +202,17 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
 
         this.obfuscationEventEmitter.emit(ObfuscationEvent.BeforeObfuscation, astTree, stackTraceData);
 
-        // third pass: dead code injection transformer
         if (this.options.deadCodeInjection) {
-            this.logger.info(LoggingMessage.StageDeadCodeInjection);
-
-            astTree = this.transformersRunner.transform(
-                astTree,
-                JavaScriptObfuscator.deadCodeInjectionTransformersList
-            );
+            astTree = this.runTransformationStage(astTree, TransformationStage.DeadCodeInjection);
         }
 
-        // fourth pass: control flow flattening transformers
         if (this.options.controlFlowFlattening) {
-            this.logger.info(LoggingMessage.StageControlFlowFlattening);
-
-            astTree = this.transformersRunner.transform(
-                astTree,
-                JavaScriptObfuscator.controlFlowTransformersList
-            );
+            astTree = this.runTransformationStage(astTree, TransformationStage.ControlFlowFlattening);
         }
 
-        // fifth pass: converting and obfuscating transformers
-        this.logger.info(LoggingMessage.StageObfuscation);
-        astTree = this.transformersRunner.transform(astTree, [
-            ...JavaScriptObfuscator.convertingTransformersList,
-            ...JavaScriptObfuscator.obfuscatingTransformersList
-        ]);
+        astTree = this.runTransformationStage(astTree, TransformationStage.Converting);
+        astTree = this.runTransformationStage(astTree, TransformationStage.Obfuscating);
+        astTree = this.runTransformationStage(astTree, TransformationStage.Finalizing);
 
         this.obfuscationEventEmitter.emit(ObfuscationEvent.AfterObfuscation, astTree, stackTraceData);
 
@@ -299,4 +256,19 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
             generatorOutput.map
         );
     }
+
+    /**
+     * @param {Program} astTree
+     * @param {TransformationStage} transformationStage
+     * @returns {Program}
+     */
+    private runTransformationStage (astTree: ESTree.Program, transformationStage: TransformationStage): ESTree.Program {
+        this.logger.info(LoggingMessage.TransformationStage, transformationStage);
+
+        return this.transformersRunner.transform(
+            astTree,
+            JavaScriptObfuscator.transformersList,
+            transformationStage
+        );
+    }
 }

+ 5 - 0
src/cli/JavaScriptObfuscatorCLI.ts

@@ -309,6 +309,11 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
                 'Allows to set target environment for obfuscated code.',
                 ObfuscationTargetSanitizer
             )
+            .option(
+                '--transform-object-keys <boolean>',
+                'Enables transformation of object keys',
+                BooleanSanitizer
+            )
             .option(
                 '--unicode-escape-sequence <boolean>',
                 'Allows to enable/disable string conversion to unicode escape sequence',

+ 5 - 3
src/container/InversifyContainerFacade.ts

@@ -5,6 +5,7 @@ import { analyzersModule } from './modules/analyzers/AnalyzersModule';
 import { controlFlowTransformersModule } from './modules/node-transformers/ControlFlowTransformersModule';
 import { convertingTransformersModule } from './modules/node-transformers/ConvertingTransformersModule';
 import { customNodesModule } from './modules/custom-nodes/CustomNodesModule';
+import { finalizingTransformersModule } from './modules/node-transformers/FinalizingTransformersModule';
 import { generatorsModule } from './modules/generators/GeneratorsModule';
 import { nodeTransformersModule } from './modules/node-transformers/NodeTransformersModule';
 import { obfuscatingTransformersModule } from './modules/node-transformers/ObfuscatingTransformersModule';
@@ -47,7 +48,7 @@ export class InversifyContainerFacade implements IInversifyContainerFacade {
      * @param {interfaces.ServiceIdentifier<U>} serviceIdentifier
      * @returns {U}
      */
-    public static getFactory <T extends number, U> (
+    public static getFactory <T extends string, U> (
         serviceIdentifier: interfaces.ServiceIdentifier<U>
     ): (context: interfaces.Context) => (bindingName: T) => U {
         return (context: interfaces.Context): (bindingName: T) => U => {
@@ -61,7 +62,7 @@ export class InversifyContainerFacade implements IInversifyContainerFacade {
      * @param {interfaces.ServiceIdentifier<U>} serviceIdentifier
      * @returns {U}
      */
-    public static getCacheFactory <T extends number, U> (
+    public static getCacheFactory <T extends string, U> (
         serviceIdentifier: interfaces.ServiceIdentifier<U>
     ): (context: interfaces.Context) => (bindingName: T) => U {
         return (context: interfaces.Context): (bindingName: T) => U => {
@@ -86,7 +87,7 @@ export class InversifyContainerFacade implements IInversifyContainerFacade {
      * @param {any[]} dependencies
      * @returns {U}
      */
-    public static getConstructorFactory <T extends number, U> (
+    public static getConstructorFactory <T extends string, U> (
         serviceIdentifier: interfaces.ServiceIdentifier<interfaces.Newable<U>>,
         ...dependencies: any[]
     ): (context: interfaces.Context) => (bindingName: T) => U {
@@ -198,6 +199,7 @@ export class InversifyContainerFacade implements IInversifyContainerFacade {
         this.container.load(controlFlowTransformersModule);
         this.container.load(convertingTransformersModule);
         this.container.load(customNodesModule);
+        this.container.load(finalizingTransformersModule);
         this.container.load(generatorsModule);
         this.container.load(nodeTransformersModule);
         this.container.load(obfuscatingTransformersModule);

+ 1 - 0
src/container/ServiceIdentifiers.ts

@@ -4,6 +4,7 @@ export enum ServiceIdentifiers {
     Factory__IControlFlowReplacer = 'Factory<IControlFlowReplacer>',
     Factory__ICustomNode = 'Factory<ICustomNode>',
     Factory__ICustomNodeGroup = 'Factory<ICustomNodeGroup>',
+    Factory__IDeadCodeInjectionCustomNode = 'Factory<IDeadCodeInjectionCustomNode>',
     Factory__IIdentifierNamesGenerator = 'Factory<IIdentifierNamesGenerator>',
     Factory__IIdentifierObfuscatingReplacer = 'Factory<IIdentifierObfuscatingReplacer>',
     Factory__INodeGuard = 'Factory<INodeGuard>',

+ 18 - 1
src/container/modules/custom-nodes/CustomNodesModule.ts

@@ -8,6 +8,7 @@ import { ICustomNodeGroup } from '../../../interfaces/custom-nodes/ICustomNodeGr
 import { ControlFlowCustomNode } from "../../../enums/custom-nodes/ControlFlowCustomNode";
 import { CustomNode } from '../../../enums/custom-nodes/CustomNode';
 import { CustomNodeGroup } from '../../../enums/custom-nodes/CustomNodeGroup';
+import { DeadCodeInjectionCustomNode } from '../../../enums/custom-nodes/DeadCodeInjectionCustomNode';
 
 import { ConsoleOutputCustomNodeGroup } from '../../../custom-nodes/console-output-nodes/group/ConsoleOutputCustomNodeGroup';
 import { DebugProtectionCustomNodeGroup } from '../../../custom-nodes/debug-protection-nodes/group/DebugProtectionCustomNodeGroup';
@@ -17,6 +18,7 @@ import { StringArrayCustomNodeGroup } from '../../../custom-nodes/string-array-n
 
 import { BinaryExpressionFunctionNode } from '../../../custom-nodes/control-flow-flattening-nodes/BinaryExpressionFunctionNode';
 import { BlockStatementControlFlowFlatteningNode } from '../../../custom-nodes/control-flow-flattening-nodes/BlockStatementControlFlowFlatteningNode';
+import { BlockStatementDeadCodeInjectionNode } from '../../../custom-nodes/dead-code-injection-nodes/BlockStatementDeadCodeInjectionNode';
 import { CallExpressionControlFlowStorageCallNode } from '../../../custom-nodes/control-flow-flattening-nodes/control-flow-storage-nodes/CallExpressionControlFlowStorageCallNode';
 import { CallExpressionFunctionNode } from '../../../custom-nodes/control-flow-flattening-nodes/CallExpressionFunctionNode';
 import { ControlFlowStorageNode } from '../../../custom-nodes/control-flow-flattening-nodes/control-flow-storage-nodes/ControlFlowStorageNode';
@@ -114,6 +116,11 @@ export const customNodesModule: interfaces.ContainerModule = new ContainerModule
         .toConstructor(StringLiteralControlFlowStorageCallNode)
         .whenTargetNamed(ControlFlowCustomNode.StringLiteralControlFlowStorageCallNode);
 
+    // dead code injection custom nodes
+    bind<interfaces.Newable<ICustomNode>>(ServiceIdentifiers.Newable__ICustomNode)
+        .toConstructor(BlockStatementDeadCodeInjectionNode)
+        .whenTargetNamed(DeadCodeInjectionCustomNode.BlockStatementDeadCodeInjectionNode);
+
     // node groups
     bind<ICustomNodeGroup>(ServiceIdentifiers.ICustomNodeGroup)
         .to(ConsoleOutputCustomNodeGroup)
@@ -140,7 +147,7 @@ export const customNodesModule: interfaces.ContainerModule = new ContainerModule
         .toFactory<ICustomNode>(InversifyContainerFacade
             .getFactory<CustomNode, ICustomNode>(ServiceIdentifiers.ICustomNode));
 
-    // control flow storage customNode constructor factory
+    // control flow customNode constructor factory
     bind<ICustomNode>(ServiceIdentifiers.Factory__IControlFlowCustomNode)
         .toFactory<ICustomNode>(InversifyContainerFacade
             .getConstructorFactory<ControlFlowCustomNode, ICustomNode>(
@@ -150,6 +157,16 @@ export const customNodesModule: interfaces.ContainerModule = new ContainerModule
                 ServiceIdentifiers.IOptions
             ));
 
+    // dead code injection customNode constructor factory
+    bind<ICustomNode>(ServiceIdentifiers.Factory__IDeadCodeInjectionCustomNode)
+        .toFactory<ICustomNode>(InversifyContainerFacade
+            .getConstructorFactory<DeadCodeInjectionCustomNode, ICustomNode>(
+                ServiceIdentifiers.Newable__ICustomNode,
+                ServiceIdentifiers.Factory__IIdentifierNamesGenerator,
+                ServiceIdentifiers.IRandomGenerator,
+                ServiceIdentifiers.IOptions
+            ));
+
     // customNodeGroup factory
     bind<ICustomNodeGroup>(ServiceIdentifiers.Factory__ICustomNodeGroup)
         .toFactory<ICustomNodeGroup>(InversifyContainerFacade

+ 5 - 0
src/container/modules/node-transformers/ConvertingTransformersModule.ts

@@ -7,6 +7,7 @@ import { NodeTransformer } from '../../../enums/node-transformers/NodeTransforme
 
 import { MemberExpressionTransformer } from '../../../node-transformers/converting-transformers/MemberExpressionTransformer';
 import { MethodDefinitionTransformer } from '../../../node-transformers/converting-transformers/MethodDefinitionTransformer';
+import { ObjectExpressionKeysTransformer } from '../../../node-transformers/converting-transformers/ObjectExpressionKeysTransformer';
 import { TemplateLiteralTransformer } from '../../../node-transformers/converting-transformers/TemplateLiteralTransformer';
 
 export const convertingTransformersModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
@@ -19,6 +20,10 @@ export const convertingTransformersModule: interfaces.ContainerModule = new Cont
         .to(MethodDefinitionTransformer)
         .whenTargetNamed(NodeTransformer.MethodDefinitionTransformer);
 
+    bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
+        .to(ObjectExpressionKeysTransformer)
+        .whenTargetNamed(NodeTransformer.ObjectExpressionKeysTransformer);
+
     bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
         .to(TemplateLiteralTransformer)
         .whenTargetNamed(NodeTransformer.TemplateLiteralTransformer);

+ 6 - 0
src/container/modules/node-transformers/FinalizingTransformersModule.ts

@@ -0,0 +1,6 @@
+import { ContainerModule, interfaces } from 'inversify';
+
+export const finalizingTransformersModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
+    // finalizing transformers
+
+});

+ 5 - 0
src/container/modules/node-transformers/PreparingTransformersModule.ts

@@ -11,6 +11,7 @@ import { ObfuscatingGuard } from '../../../enums/node-transformers/preparing-tra
 import { BlackListObfuscatingGuard } from '../../../node-transformers/preparing-transformers/obfuscating-guards/BlackListObfuscatingGuard';
 import { CommentsTransformer } from '../../../node-transformers/preparing-transformers/CommentsTransformer';
 import { ConditionalCommentObfuscatingGuard } from '../../../node-transformers/preparing-transformers/obfuscating-guards/ConditionalCommentObfuscatingGuard';
+import { EvalCallExpressionTransformer } from '../../../node-transformers/preparing-transformers/EvaCallExpressionTransformer';
 import { ObfuscatingGuardsTransformer } from '../../../node-transformers/preparing-transformers/ObfuscatingGuardsTransformer';
 import { ParentificationTransformer } from '../../../node-transformers/preparing-transformers/ParentificationTransformer';
 
@@ -20,6 +21,10 @@ export const preparingTransformersModule: interfaces.ContainerModule = new Conta
         .to(CommentsTransformer)
         .whenTargetNamed(NodeTransformer.CommentsTransformer);
 
+    bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
+        .to(EvalCallExpressionTransformer)
+        .whenTargetNamed(NodeTransformer.EvalCallExpressionTransformer);
+
     bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
         .to(ObfuscatingGuardsTransformer)
         .whenTargetNamed(NodeTransformer.ObfuscatingGuardsTransformer);

+ 0 - 18
src/custom-nodes/AbstractCustomNode.ts

@@ -12,8 +12,6 @@ import { IRandomGenerator } from '../interfaces/utils/IRandomGenerator';
 import { GlobalVariableTemplate1 } from '../templates/GlobalVariableTemplate1';
 import { GlobalVariableTemplate2 } from '../templates/GlobalVariableTemplate2';
 
-import { NodeUtils } from '../node/NodeUtils';
-
 @injectable()
 export abstract class AbstractCustomNode implements ICustomNode {
     /**
@@ -24,11 +22,6 @@ export abstract class AbstractCustomNode implements ICustomNode {
         GlobalVariableTemplate2()
     ];
 
-    /**
-     * @type {string}
-     */
-    protected cachedCode: string;
-
     /**
      * @type {TStatement[]}
      */
@@ -70,17 +63,6 @@ export abstract class AbstractCustomNode implements ICustomNode {
      */
     public abstract initialize (...args: any[]): void;
 
-    /**
-     * @returns {string}
-     */
-    public getCode (): string {
-        if (!this.cachedCode) {
-            this.cachedCode = NodeUtils.convertStructureToCode(this.getNode());
-        }
-
-        return this.cachedCode;
-    }
-
     /**
      * @returns {TStatement[]}
      */

+ 3 - 3
src/custom-nodes/AbstractCustomNodeGroup.ts

@@ -2,7 +2,7 @@ import { inject, injectable } from 'inversify';
 import { ServiceIdentifiers } from '../container/ServiceIdentifiers';
 
 import { TIdentifierNamesGeneratorFactory } from '../types/container/generators/TIdentifierNamesGeneratorFactory';
-import { TNodeWithBlockStatement } from '../types/node/TNodeWithBlockStatement';
+import { TNodeWithBlockScope } from '../types/node/TNodeWithBlockScope';
 
 import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
 import { ICustomNodeGroup } from '../interfaces/custom-nodes/ICustomNodeGroup';
@@ -63,10 +63,10 @@ export abstract class AbstractCustomNodeGroup implements ICustomNodeGroup {
     }
 
     /**
-     * @param {TNodeWithBlockStatement} blockScopeNode
+     * @param {TNodeWithBlockScope} blockScopeNode
      * @param {IStackTraceData[]} stackTraceData
      */
-    public abstract appendCustomNodes (blockScopeNode: TNodeWithBlockStatement, stackTraceData: IStackTraceData[]): void;
+    public abstract appendCustomNodes (blockScopeNode: TNodeWithBlockScope, stackTraceData: IStackTraceData[]): void;
 
     /**
      * @returns {ObfuscationEvent}

+ 4 - 4
src/custom-nodes/console-output-nodes/group/ConsoleOutputCustomNodeGroup.ts

@@ -3,7 +3,7 @@ import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
 
 import { TCustomNodeFactory } from '../../../types/container/custom-nodes/TCustomNodeFactory';
 import { TIdentifierNamesGeneratorFactory } from '../../../types/container/generators/TIdentifierNamesGeneratorFactory';
-import { TNodeWithBlockStatement } from '../../../types/node/TNodeWithBlockStatement';
+import { TNodeWithBlockScope } from '../../../types/node/TNodeWithBlockScope';
 
 import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
 import { IOptions } from '../../../interfaces/options/IOptions';
@@ -55,10 +55,10 @@ export class ConsoleOutputCustomNodeGroup extends AbstractCustomNodeGroup {
     }
 
     /**
-     * @param {TNodeWithBlockStatement} blockScopeNode
+     * @param {TNodeWithBlockScope} blockScopeNode
      * @param {IStackTraceData[]} stackTraceData
      */
-    public appendCustomNodes (blockScopeNode: TNodeWithBlockStatement, stackTraceData: IStackTraceData[]): void {
+    public appendCustomNodes (blockScopeNode: TNodeWithBlockScope, stackTraceData: IStackTraceData[]): void {
         const randomStackTraceIndex: number = this.getRandomStackTraceIndex(stackTraceData.length);
 
         // consoleOutputDisableExpressionNode append
@@ -73,7 +73,7 @@ export class ConsoleOutputCustomNodeGroup extends AbstractCustomNodeGroup {
 
         // nodeCallsControllerFunctionNode append
         this.appendCustomNodeIfExist(CustomNode.NodeCallsControllerFunctionNode, (customNode: ICustomNode) => {
-            let targetBlockScope: TNodeWithBlockStatement;
+            let targetBlockScope: TNodeWithBlockScope;
 
             if (stackTraceData.length) {
                 targetBlockScope = NodeAppender.getOptimalBlockScope(stackTraceData, randomStackTraceIndex, 1);

+ 89 - 0
src/custom-nodes/dead-code-injection-nodes/BlockStatementDeadCodeInjectionNode.ts

@@ -0,0 +1,89 @@
+import { inject, injectable, } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
+
+import { BinaryOperator, BlockStatement } from 'estree';
+
+import { TIdentifierNamesGeneratorFactory } from '../../types/container/generators/TIdentifierNamesGeneratorFactory';
+import { TStatement } from '../../types/node/TStatement';
+
+import { IOptions } from '../../interfaces/options/IOptions';
+import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
+
+import { initializable } from '../../decorators/Initializable';
+
+import { AbstractCustomNode } from '../AbstractCustomNode';
+import { Nodes } from '../../node/Nodes';
+import { NodeUtils } from '../../node/NodeUtils';
+
+@injectable()
+export class BlockStatementDeadCodeInjectionNode extends AbstractCustomNode {
+    /**
+     * @type {BlockStatement}
+     */
+    @initializable()
+    private blockStatementNode: BlockStatement;
+
+    /**
+     * @type {BlockStatement}
+     */
+    @initializable()
+    private deadCodeInjectionRootAstHostNode: BlockStatement;
+
+    /**
+     * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory
+     * @param {IRandomGenerator} randomGenerator
+     * @param {IOptions} options
+     */
+    constructor (
+        @inject(ServiceIdentifiers.Factory__IIdentifierNamesGenerator)
+            identifierNamesGeneratorFactory: TIdentifierNamesGeneratorFactory,
+        @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        super(identifierNamesGeneratorFactory, randomGenerator, options);
+    }
+
+    /**
+     * @param {BlockStatement} blockStatementNode
+     * @param {BlockStatement} deadCodeInjectionRootAstHostNode
+     */
+    public initialize (
+        blockStatementNode: BlockStatement,
+        deadCodeInjectionRootAstHostNode: BlockStatement
+    ): void {
+        this.blockStatementNode = blockStatementNode;
+        this.deadCodeInjectionRootAstHostNode = deadCodeInjectionRootAstHostNode;
+    }
+
+    /**
+     * @returns {TStatement[]}
+     */
+    protected getNodeStructure (): TStatement[] {
+        const random1: boolean = this.randomGenerator.getMathRandom() > 0.5;
+        const random2: boolean = this.randomGenerator.getMathRandom() > 0.5;
+
+        const operator: BinaryOperator = random1 ? '===' : '!==';
+        const leftString: string = this.randomGenerator.getRandomString(5);
+        const rightString: string = random2 ? leftString : this.randomGenerator.getRandomString(5);
+
+        const [consequent, alternate]: [BlockStatement, BlockStatement] = random1 === random2
+            ? [this.blockStatementNode, this.deadCodeInjectionRootAstHostNode]
+            : [this.deadCodeInjectionRootAstHostNode, this.blockStatementNode];
+
+        const structure: BlockStatement = Nodes.getBlockStatementNode([
+            Nodes.getIfStatementNode(
+                Nodes.getBinaryExpressionNode(
+                    operator,
+                    Nodes.getLiteralNode(leftString),
+                    Nodes.getLiteralNode(rightString)
+                ),
+                consequent,
+                alternate
+            )
+        ]);
+
+        NodeUtils.parentize(structure);
+
+        return [structure];
+    }
+}

+ 4 - 4
src/custom-nodes/debug-protection-nodes/group/DebugProtectionCustomNodeGroup.ts

@@ -3,7 +3,7 @@ import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
 
 import { TCustomNodeFactory } from '../../../types/container/custom-nodes/TCustomNodeFactory';
 import { TIdentifierNamesGeneratorFactory } from '../../../types/container/generators/TIdentifierNamesGeneratorFactory';
-import { TNodeWithBlockStatement } from '../../../types/node/TNodeWithBlockStatement';
+import { TNodeWithBlockScope } from '../../../types/node/TNodeWithBlockScope';
 
 import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
 import { IOptions } from '../../../interfaces/options/IOptions';
@@ -55,10 +55,10 @@ export class DebugProtectionCustomNodeGroup extends AbstractCustomNodeGroup {
     }
 
     /**
-     * @param {TNodeWithBlockStatement} blockScopeNode
+     * @param {TNodeWithBlockScope} blockScopeNode
      * @param {IStackTraceData[]} stackTraceData
      */
-    public appendCustomNodes (blockScopeNode: TNodeWithBlockStatement, stackTraceData: IStackTraceData[]): void {
+    public appendCustomNodes (blockScopeNode: TNodeWithBlockScope, stackTraceData: IStackTraceData[]): void {
         const randomStackTraceIndex: number = this.getRandomStackTraceIndex(stackTraceData.length);
 
         // debugProtectionFunctionCallNode append
@@ -86,7 +86,7 @@ export class DebugProtectionCustomNodeGroup extends AbstractCustomNodeGroup {
 
         // nodeCallsControllerFunctionNode append
         this.appendCustomNodeIfExist(CustomNode.NodeCallsControllerFunctionNode, (customNode: ICustomNode) => {
-            let targetBlockScope: TNodeWithBlockStatement;
+            let targetBlockScope: TNodeWithBlockScope;
 
             if (stackTraceData.length) {
                 targetBlockScope = NodeAppender.getOptimalBlockScope(stackTraceData, randomStackTraceIndex, 1);

+ 4 - 4
src/custom-nodes/domain-lock-nodes/group/DomainLockCustomNodeGroup.ts

@@ -3,7 +3,7 @@ import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
 
 import { TCustomNodeFactory } from '../../../types/container/custom-nodes/TCustomNodeFactory';
 import { TIdentifierNamesGeneratorFactory } from '../../../types/container/generators/TIdentifierNamesGeneratorFactory';
-import { TNodeWithBlockStatement } from '../../../types/node/TNodeWithBlockStatement';
+import { TNodeWithBlockScope } from '../../../types/node/TNodeWithBlockScope';
 
 import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
 import { IOptions } from '../../../interfaces/options/IOptions';
@@ -55,10 +55,10 @@ export class DomainLockCustomNodeGroup extends AbstractCustomNodeGroup {
     }
 
     /**
-     * @param {TNodeWithBlockStatement} blockScopeNode
+     * @param {TNodeWithBlockScope} blockScopeNode
      * @param {IStackTraceData[]} stackTraceData
      */
-    public appendCustomNodes (blockScopeNode: TNodeWithBlockStatement, stackTraceData: IStackTraceData[]): void {
+    public appendCustomNodes (blockScopeNode: TNodeWithBlockScope, stackTraceData: IStackTraceData[]): void {
         const randomStackTraceIndex: number = this.getRandomStackTraceIndex(stackTraceData.length);
 
         // domainLockNode append
@@ -73,7 +73,7 @@ export class DomainLockCustomNodeGroup extends AbstractCustomNodeGroup {
 
         // nodeCallsControllerFunctionNode append
         this.appendCustomNodeIfExist(CustomNode.NodeCallsControllerFunctionNode, (customNode: ICustomNode) => {
-            let targetBlockScope: TNodeWithBlockStatement;
+            let targetBlockScope: TNodeWithBlockScope;
 
             if (stackTraceData.length) {
                 targetBlockScope = NodeAppender.getOptimalBlockScope(stackTraceData, randomStackTraceIndex, 1);

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

@@ -15,7 +15,7 @@ import { initializable } from '../../decorators/Initializable';
 
 import { SingleNodeCallControllerTemplate } from '../../templates/SingleNodeCallControllerTemplate';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../options/presets/NoCustomNodes';
+import { NO_ADDITIONAL_NODES_PRESET } from '../../options/presets/NoCustomNodes';
 
 import { AbstractCustomNode } from '../AbstractCustomNode';
 import { JavaScriptObfuscator } from '../../JavaScriptObfuscatorFacade';
@@ -75,7 +75,7 @@ export class NodeCallsControllerFunctionNode extends AbstractCustomNode {
                     singleNodeCallControllerFunctionName: this.callsControllerFunctionName
                 }),
                 {
-                    ...NO_CUSTOM_NODES_PRESET,
+                    ...NO_ADDITIONAL_NODES_PRESET,
                     identifierNamesGenerator: this.options.identifierNamesGenerator,
                     seed: this.options.seed
                 }

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

@@ -12,7 +12,7 @@ import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 
 import { initializable } from '../../decorators/Initializable';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../options/presets/NoCustomNodes';
+import { NO_ADDITIONAL_NODES_PRESET } from '../../options/presets/NoCustomNodes';
 
 import { SelfDefendingTemplate } from '../../templates/self-defending-nodes/self-defending-unicode-node/SelfDefendingTemplate';
 
@@ -75,7 +75,7 @@ export class SelfDefendingUnicodeNode extends AbstractCustomNode {
                 singleNodeCallControllerFunctionName: this.callsControllerFunctionName
             }),
             {
-                ...NO_CUSTOM_NODES_PRESET,
+                ...NO_ADDITIONAL_NODES_PRESET,
                 identifierNamesGenerator: this.options.identifierNamesGenerator,
                 seed: this.options.seed,
                 unicodeEscapeSequence: true

+ 4 - 4
src/custom-nodes/self-defending-nodes/group/SelfDefendingCustomNodeGroup.ts

@@ -3,7 +3,7 @@ import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
 
 import { TCustomNodeFactory } from '../../../types/container/custom-nodes/TCustomNodeFactory';
 import { TIdentifierNamesGeneratorFactory } from '../../../types/container/generators/TIdentifierNamesGeneratorFactory';
-import { TNodeWithBlockStatement } from '../../../types/node/TNodeWithBlockStatement';
+import { TNodeWithBlockScope } from '../../../types/node/TNodeWithBlockScope';
 
 import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
 import { IOptions } from '../../../interfaces/options/IOptions';
@@ -55,10 +55,10 @@ export class SelfDefendingCustomNodeGroup extends AbstractCustomNodeGroup {
     }
 
     /**
-     * @param {TNodeWithBlockStatement} blockScopeNode
+     * @param {TNodeWithBlockScope} blockScopeNode
      * @param {IStackTraceData[]} stackTraceData
      */
-    public appendCustomNodes (blockScopeNode: TNodeWithBlockStatement, stackTraceData: IStackTraceData[]): void {
+    public appendCustomNodes (blockScopeNode: TNodeWithBlockScope, stackTraceData: IStackTraceData[]): void {
         const randomStackTraceIndex: number = this.getRandomStackTraceIndex(stackTraceData.length);
 
         // selfDefendingUnicodeNode append
@@ -73,7 +73,7 @@ export class SelfDefendingCustomNodeGroup extends AbstractCustomNodeGroup {
 
         // nodeCallsControllerFunctionNode append
         this.appendCustomNodeIfExist(CustomNode.NodeCallsControllerFunctionNode, (customNode: ICustomNode) => {
-            let targetBlockScope: TNodeWithBlockStatement;
+            let targetBlockScope: TNodeWithBlockScope;
 
             if (stackTraceData.length) {
                 targetBlockScope = NodeAppender.getOptimalBlockScope(stackTraceData, randomStackTraceIndex, 1);

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

@@ -15,7 +15,7 @@ import { StringArrayEncoding } from '../../enums/StringArrayEncoding';
 
 import { initializable } from '../../decorators/Initializable';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../options/presets/NoCustomNodes';
+import { NO_ADDITIONAL_NODES_PRESET } from '../../options/presets/NoCustomNodes';
 
 import { AtobTemplate } from '../../templates/AtobTemplate';
 import { GlobalVariableNoEvalTemplate } from '../../templates/GlobalVariableNoEvalTemplate';
@@ -98,7 +98,7 @@ export class StringArrayCallsWrapper extends AbstractCustomNode {
                 stringArrayName: this.stringArrayName
             }),
             {
-                ...NO_CUSTOM_NODES_PRESET,
+                ...NO_ADDITIONAL_NODES_PRESET,
                 identifierNamesGenerator: this.options.identifierNamesGenerator,
                 seed: this.options.seed
             }

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

@@ -12,7 +12,7 @@ import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 
 import { initializable } from '../../decorators/Initializable';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../options/presets/NoCustomNodes';
+import { NO_ADDITIONAL_NODES_PRESET } from '../../options/presets/NoCustomNodes';
 
 import { SelfDefendingTemplate } from '../../templates/string-array-nodes/string-array-rotate-function-node/SelfDefendingTemplate';
 import { StringArrayRotateFunctionTemplate } from '../../templates/string-array-nodes/string-array-rotate-function-node/StringArrayRotateFunctionTemplate';
@@ -105,7 +105,7 @@ export class StringArrayRotateFunctionNode extends AbstractCustomNode {
                 whileFunctionName
             }),
             {
-                ...NO_CUSTOM_NODES_PRESET,
+                ...NO_ADDITIONAL_NODES_PRESET,
                 identifierNamesGenerator: this.options.identifierNamesGenerator,
                 seed: this.options.seed
             }

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

@@ -3,7 +3,7 @@ import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
 
 import { TCustomNodeFactory } from '../../../types/container/custom-nodes/TCustomNodeFactory';
 import { TIdentifierNamesGeneratorFactory } from '../../../types/container/generators/TIdentifierNamesGeneratorFactory';
-import { TNodeWithBlockStatement } from '../../../types/node/TNodeWithBlockStatement';
+import { TNodeWithBlockScope } from '../../../types/node/TNodeWithBlockScope';
 
 import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
 import { IOptions } from '../../../interfaces/options/IOptions';
@@ -65,10 +65,10 @@ export class StringArrayCustomNodeGroup extends AbstractCustomNodeGroup {
     }
 
     /**
-     * @param {TNodeWithBlockStatement} blockScopeNode
+     * @param {TNodeWithBlockScope} blockScopeNode
      * @param {IStackTraceData[]} stackTraceData
      */
-    public appendCustomNodes (blockScopeNode: TNodeWithBlockStatement, stackTraceData: IStackTraceData[]): void {
+    public appendCustomNodes (blockScopeNode: TNodeWithBlockScope, stackTraceData: IStackTraceData[]): void {
         if (!this.stringArrayStorage.getLength()) {
             return;
         }

+ 5 - 0
src/declarations/js-string-escape.d.ts

@@ -0,0 +1,5 @@
+declare module 'js-string-escape' {
+    function jsStringEscape (input: string): string;
+
+    export = jsStringEscape;
+}

+ 3 - 3
src/enums/analyzers/stack-trace-analyzer/CalleeDataExtractor.ts

@@ -1,5 +1,5 @@
 export enum CalleeDataExtractor {
-    FunctionDeclarationCalleeDataExtractor,
-    FunctionExpressionCalleeDataExtractor,
-    ObjectExpressionCalleeDataExtractor,
+    FunctionDeclarationCalleeDataExtractor = 'FunctionDeclarationCalleeDataExtractor',
+    FunctionExpressionCalleeDataExtractor = 'FunctionExpressionCalleeDataExtractor',
+    ObjectExpressionCalleeDataExtractor = 'ObjectExpressionCalleeDataExtractor',
 }

+ 9 - 9
src/enums/custom-nodes/ControlFlowCustomNode.ts

@@ -1,11 +1,11 @@
 export enum ControlFlowCustomNode {
-    BinaryExpressionFunctionNode,
-    BlockStatementControlFlowFlatteningNode,
-    CallExpressionControlFlowStorageCallNode,
-    CallExpressionFunctionNode,
-    ControlFlowStorageNode,
-    ExpressionWithOperatorControlFlowStorageCallNode,
-    LogicalExpressionFunctionNode,
-    StringLiteralControlFlowStorageCallNode,
-    StringLiteralNode,
+    BinaryExpressionFunctionNode = 'BinaryExpressionFunctionNode',
+    BlockStatementControlFlowFlatteningNode = 'BlockStatementControlFlowFlatteningNode',
+    CallExpressionControlFlowStorageCallNode = 'CallExpressionControlFlowStorageCallNode',
+    CallExpressionFunctionNode = 'CallExpressionFunctionNode',
+    ControlFlowStorageNode = 'ControlFlowStorageNode',
+    ExpressionWithOperatorControlFlowStorageCallNode = 'ExpressionWithOperatorControlFlowStorageCallNode',
+    LogicalExpressionFunctionNode = 'LogicalExpressionFunctionNode',
+    StringLiteralControlFlowStorageCallNode = 'StringLiteralControlFlowStorageCallNode',
+    StringLiteralNode = 'StringLiteralNode'
 }

+ 10 - 10
src/enums/custom-nodes/CustomNode.ts

@@ -1,12 +1,12 @@
 export enum CustomNode {
-    ConsoleOutputDisableExpressionNode,
-    DebugProtectionFunctionCallNode,
-    DebugProtectionFunctionIntervalNode,
-    DebugProtectionFunctionNode,
-    DomainLockNode,
-    NodeCallsControllerFunctionNode,
-    SelfDefendingUnicodeNode,
-    StringArrayCallsWrapper,
-    StringArrayNode,
-    StringArrayRotateFunctionNode
+    ConsoleOutputDisableExpressionNode = 'ConsoleOutputDisableExpressionNode',
+    DebugProtectionFunctionCallNode = 'DebugProtectionFunctionCallNode',
+    DebugProtectionFunctionIntervalNode = 'DebugProtectionFunctionIntervalNode',
+    DebugProtectionFunctionNode = 'DebugProtectionFunctionNode',
+    DomainLockNode = 'DomainLockNode',
+    NodeCallsControllerFunctionNode = 'NodeCallsControllerFunctionNode',
+    SelfDefendingUnicodeNode = 'SelfDefendingUnicodeNode',
+    StringArrayCallsWrapper = 'StringArrayCallsWrapper',
+    StringArrayNode = 'StringArrayNode',
+    StringArrayRotateFunctionNode = 'StringArrayRotateFunctionNode'
 }

+ 5 - 5
src/enums/custom-nodes/CustomNodeGroup.ts

@@ -1,7 +1,7 @@
 export enum CustomNodeGroup {
-    ConsoleOutputCustomNodeGroup,
-    DebugProtectionCustomNodeGroup,
-    DomainLockCustomNodeGroup,
-    SelfDefendingCustomNodeGroup,
-    StringArrayCustomNodeGroup
+    ConsoleOutputCustomNodeGroup = 'ConsoleOutputCustomNodeGroup',
+    DebugProtectionCustomNodeGroup = 'DebugProtectionCustomNodeGroup',
+    DomainLockCustomNodeGroup = 'DomainLockCustomNodeGroup',
+    SelfDefendingCustomNodeGroup = 'SelfDefendingCustomNodeGroup',
+    StringArrayCustomNodeGroup = 'StringArrayCustomNodeGroup'
 }

+ 3 - 0
src/enums/custom-nodes/DeadCodeInjectionCustomNode.ts

@@ -0,0 +1,3 @@
+export enum DeadCodeInjectionCustomNode {
+    BlockStatementDeadCodeInjectionNode = 'BlockStatementDeadCodeInjectionNode'
+}

+ 2 - 5
src/enums/logger/LoggingMessage.ts

@@ -1,12 +1,9 @@
 export enum LoggingMessage {
+    AnalyzingASTTreeStage = 'Stage: analyzing AST-tree...',
     EmptySourceCode = 'Empty source code. Obfuscation canceled...',
     ObfuscationCompleted = 'Obfuscation completed. Total time: %s sec.',
     ObfuscationStarted = 'Obfuscation started...',
     RandomGeneratorSeed = 'Random generator seed: %s...',
-    StagePreparingASTTree = 'Stage: preparing AST-tree...',
-    StageAnalyzingASTTree = 'Stage: analyzing AST-tree...',
-    StageControlFlowFlattening = 'Stage: control flow flattening...',
-    StageDeadCodeInjection = 'Stage: dead code injection...',
-    StageObfuscation = 'Stage: obfuscation...',
+    TransformationStage = 'Transformation stage: %s...',
     Version = 'Version: %s'
 }

+ 19 - 17
src/enums/node-transformers/NodeTransformer.ts

@@ -1,19 +1,21 @@
 export enum NodeTransformer {
-    BlockStatementControlFlowTransformer,
-    ClassDeclarationTransformer,
-    CommentsTransformer,
-    DeadCodeInjectionTransformer,
-    FunctionControlFlowTransformer,
-    CatchClauseTransformer,
-    FunctionDeclarationTransformer,
-    FunctionTransformer,
-    LabeledStatementTransformer,
-    LiteralTransformer,
-    MemberExpressionTransformer,
-    MethodDefinitionTransformer,
-    ObfuscatingGuardsTransformer,
-    ObjectExpressionTransformer,
-    ParentificationTransformer,
-    TemplateLiteralTransformer,
-    VariableDeclarationTransformer
+    BlockStatementControlFlowTransformer = 'BlockStatementControlFlowTransformer',
+    ClassDeclarationTransformer = 'ClassDeclarationTransformer',
+    CommentsTransformer = 'CommentsTransformer',
+    DeadCodeInjectionTransformer = 'DeadCodeInjectionTransformer',
+    EvalCallExpressionTransformer = 'EvalCallExpressionTransformer',
+    FunctionControlFlowTransformer = 'FunctionControlFlowTransformer',
+    CatchClauseTransformer = 'CatchClauseTransformer',
+    FunctionDeclarationTransformer = 'FunctionDeclarationTransformer',
+    FunctionTransformer = 'FunctionTransformer',
+    LabeledStatementTransformer = 'LabeledStatementTransformer',
+    LiteralTransformer = 'LiteralTransformer',
+    MemberExpressionTransformer = 'MemberExpressionTransformer',
+    MethodDefinitionTransformer = 'MethodDefinitionTransformer',
+    ObfuscatingGuardsTransformer = 'ObfuscatingGuardsTransformer',
+    ObjectExpressionKeysTransformer = 'ObjectExpressionKeysTransformer',
+    ObjectExpressionTransformer = 'ObjectExpressionTransformer',
+    ParentificationTransformer = 'ParentificationTransformer',
+    TemplateLiteralTransformer = 'TemplateLiteralTransformer',
+    VariableDeclarationTransformer = 'VariableDeclarationTransformer'
 }

+ 8 - 0
src/enums/node-transformers/TransformationStage.ts

@@ -0,0 +1,8 @@
+export enum TransformationStage {
+    Preparing = 'Preparing',
+    DeadCodeInjection = 'DeadCodeInjection',
+    ControlFlowFlattening = 'ControlFlowFlattening',
+    Converting = 'Converting',
+    Obfuscating = 'Obfuscating',
+    Finalizing = 'Finalizing'
+}

+ 4 - 4
src/enums/node-transformers/obfuscating-transformers/obfuscating-replacers/ControlFlowReplacer.ts

@@ -1,6 +1,6 @@
 export enum ControlFlowReplacer {
-    BinaryExpressionControlFlowReplacer,
-    CallExpressionControlFlowReplacer,
-    LogicalExpressionControlFlowReplacer,
-    StringLiteralControlFlowReplacer,
+    BinaryExpressionControlFlowReplacer = 'BinaryExpressionControlFlowReplacer',
+    CallExpressionControlFlowReplacer = 'CallExpressionControlFlowReplacer',
+    LogicalExpressionControlFlowReplacer = 'LogicalExpressionControlFlowReplacer',
+    StringLiteralControlFlowReplacer = 'StringLiteralControlFlowReplacer'
 }

+ 1 - 1
src/enums/node-transformers/obfuscating-transformers/obfuscating-replacers/IdentifierObfuscatingReplacer.ts

@@ -1,3 +1,3 @@
 export enum IdentifierObfuscatingReplacer {
-    BaseIdentifierObfuscatingReplacer
+    BaseIdentifierObfuscatingReplacer = 'BaseIdentifierObfuscatingReplacer'
 }

+ 3 - 3
src/enums/node-transformers/obfuscating-transformers/obfuscating-replacers/LiteralObfuscatingReplacer.ts

@@ -1,5 +1,5 @@
 export enum LiteralObfuscatingReplacer {
-    BooleanLiteralObfuscatingReplacer,
-    NumberLiteralObfuscatingReplacer,
-    StringLiteralObfuscatingReplacer
+    BooleanLiteralObfuscatingReplacer = 'BooleanLiteralObfuscatingReplacer',
+    NumberLiteralObfuscatingReplacer = 'NumberLiteralObfuscatingReplacer',
+    StringLiteralObfuscatingReplacer = 'StringLiteralObfuscatingReplacer'
 }

+ 2 - 2
src/enums/node-transformers/preparing-transformers/obfuscating-guards/ObfuscatingGuard.ts

@@ -1,4 +1,4 @@
 export enum ObfuscatingGuard {
-    BlackListNodeGuard,
-    ConditionalCommentNodeGuard
+    BlackListNodeGuard = 'BlackListNodeGuard',
+    ConditionalCommentNodeGuard = 'ConditionalCommentNodeGuard'
 }

+ 2 - 0
src/enums/node/NodeType.ts

@@ -1,5 +1,6 @@
 export enum NodeType {
     ArrayExpression = 'ArrayExpression',
+    ArrayPattern = 'ArrayPattern',
     ArrowFunctionExpression = 'ArrowFunctionExpression',
     AssignmentExpression = 'AssignmentExpression',
     AssignmentPattern = 'AssignmentPattern',
@@ -25,6 +26,7 @@ export enum NodeType {
     ObjectPattern = 'ObjectPattern',
     Program = 'Program',
     Property = 'Property',
+    RestElement = 'RestElement',
     ReturnStatement = 'ReturnStatement',
     Super = 'Super',
     SwitchCase = 'SwitchCase',

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

@@ -3,11 +3,6 @@ import { TStatement } from '../../types/node/TStatement';
 import { IInitializable } from '../IInitializable';
 
 export interface ICustomNode extends IInitializable {
-    /**
-     * @returns {string}
-     */
-    getCode (): string;
-
     /**
      * @returns ESTree.Node[]
      */

+ 2 - 2
src/interfaces/custom-nodes/ICustomNodeGroup.d.ts

@@ -1,4 +1,4 @@
-import { TNodeWithBlockStatement } from '../../types/node/TNodeWithBlockStatement';
+import { TNodeWithBlockScope } from '../../types/node/TNodeWithBlockScope';
 
 import { ICustomNode } from './ICustomNode';
 import { IInitializable } from '../IInitializable';
@@ -12,7 +12,7 @@ export interface ICustomNodeGroup extends IInitializable {
      * @param blockScopeNode
      * @param stackTraceData
      */
-    appendCustomNodes (blockScopeNode: TNodeWithBlockStatement, stackTraceData: IStackTraceData[]): void;
+    appendCustomNodes (blockScopeNode: TNodeWithBlockScope, stackTraceData: IStackTraceData[]): void;
 
     /**
      * @returns {ObfuscationEvent}

+ 11 - 2
src/interfaces/node-transformers/INodeTransformer.d.ts

@@ -3,11 +3,14 @@ import * as ESTree from 'estree';
 
 import { IVisitor } from './IVisitor';
 
+import { TransformationStage } from '../../enums/node-transformers/TransformationStage';
+
 export interface INodeTransformer {
     /**
-     * @returns {IVisitor}
+     * @param {TransformationStage} transformationStage
+     * @returns {IVisitor | null}
      */
-    getVisitor (): IVisitor;
+    getVisitor (transformationStage: TransformationStage): IVisitor | null;
 
     /**
      * @param {Node} node
@@ -15,6 +18,12 @@ export interface INodeTransformer {
      */
     analyzeNode ? (node: ESTree.Node, parentNode: ESTree.Node | null): void;
 
+    /**
+     * @param {Node} node
+     * @param {Node | null} parentNode
+     */
+    restoreNode ? (node: ESTree.Node, parentNode: ESTree.Node | null): void;
+
     /**
      * @param {Node} node
      * @param {Node | null} parentNode

+ 7 - 1
src/interfaces/node-transformers/ITransformersRunner.d.ts

@@ -1,12 +1,18 @@
 import * as ESTree from 'estree';
 
 import { NodeTransformer } from '../../enums/node-transformers/NodeTransformer';
+import { TransformationStage } from '../../enums/node-transformers/TransformationStage';
 
 export interface ITransformersRunner {
     /**
      * @param {T} astTree
      * @param {NodeTransformer[]} nodeTransformers
+     * @param {TransformationStage} transformationStage
      * @returns {T}
      */
-    transform <T extends ESTree.Node = ESTree.Program> (astTree: T, nodeTransformers: NodeTransformer[]): T;
+    transform <T extends ESTree.Node = ESTree.Program> (
+        astTree: T,
+        nodeTransformers: NodeTransformer[],
+        transformationStage: TransformationStage
+    ): T;
 }

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

@@ -29,5 +29,6 @@ export interface IOptions {
     readonly stringArrayEncoding: TStringArrayEncoding;
     readonly stringArrayThreshold: number;
     readonly target: ObfuscationTarget;
+    readonly transformObjectKeys: boolean;
     readonly unicodeEscapeSequence: boolean;
 }

+ 3 - 3
src/interfaces/source-map/ISourceMapCorrector.d.ts

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

+ 5 - 2
src/node-transformers/AbstractNodeTransformer.ts

@@ -12,6 +12,8 @@ import { IVisitor } from '../interfaces/node-transformers/IVisitor';
 
 import { initializable } from '../decorators/Initializable';
 
+import { TransformationStage } from '../enums/node-transformers/TransformationStage';
+
 @injectable()
 export abstract class AbstractNodeTransformer implements INodeTransformer, IInitializable {
     /**
@@ -48,9 +50,10 @@ export abstract class AbstractNodeTransformer implements INodeTransformer, IInit
     }
 
     /**
-     * @returns {IVisitor}
+     * @param {TransformationStage} transformationStage
+     * @returns {IVisitor | null}
      */
-    public abstract getVisitor (): IVisitor;
+    public abstract getVisitor (transformationStage: TransformationStage): IVisitor | null;
 
     /**
      * @param {Node} node

+ 16 - 5
src/node-transformers/TransformersRunner.ts

@@ -13,6 +13,7 @@ import { ITransformersRunner } from '../interfaces/node-transformers/ITransforme
 import { IVisitor } from '../interfaces/node-transformers/IVisitor';
 
 import { NodeTransformer } from '../enums/node-transformers/NodeTransformer';
+import { TransformationStage } from '../enums/node-transformers/TransformationStage';
 import { VisitorDirection } from '../enums/node-transformers/VisitorDirection';
 
 import { NodeGuards } from '../node/NodeGuards';
@@ -36,11 +37,13 @@ export class TransformersRunner implements ITransformersRunner {
     /**
      * @param {T} astTree
      * @param {NodeTransformer[]} nodeTransformers
+     * @param {TransformationStage} transformationStage
      * @returns {T}
      */
     public transform <T extends ESTree.Node = ESTree.Program> (
         astTree: T,
-        nodeTransformers: NodeTransformer[]
+        nodeTransformers: NodeTransformer[],
+        transformationStage: TransformationStage
     ): T {
         if (!nodeTransformers.length) {
             return astTree;
@@ -50,20 +53,28 @@ export class TransformersRunner implements ITransformersRunner {
         const leaveVisitors: IVisitor[] = [];
         const nodeTransformersLength: number = nodeTransformers.length;
 
-        let visitor: IVisitor;
+        let visitor: IVisitor | null;
 
         for (let i: number = 0; i < nodeTransformersLength; i++) {
-            visitor = this.nodeTransformerFactory(nodeTransformers[i]).getVisitor();
+            visitor = this.nodeTransformerFactory(nodeTransformers[i]).getVisitor(transformationStage);
+
+            if (!visitor) {
+                continue;
+            }
 
             if (visitor.enter) {
-                enterVisitors.push(visitor);
+                enterVisitors.push({ enter: visitor.enter });
             }
 
             if (visitor.leave) {
-                leaveVisitors.push(visitor);
+                leaveVisitors.push({ leave: visitor.leave });
             }
         }
 
+        if (!enterVisitors.length && !leaveVisitors.length) {
+            return astTree;
+        }
+
         estraverse.replace(astTree, {
             enter: this.mergeVisitorsForDirection(enterVisitors, VisitorDirection.Enter),
             leave: this.mergeVisitorsForDirection(leaveVisitors, VisitorDirection.Leave)

+ 24 - 10
src/node-transformers/control-flow-transformers/BlockStatementControlFlowTransformer.ts

@@ -5,6 +5,7 @@ import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 
 import { TControlFlowCustomNodeFactory } from '../../types/container/custom-nodes/TControlFlowCustomNodeFactory';
+import { TStatement } from '../../types/node/TStatement';
 
 import { IArrayUtils } from '../../interfaces/utils/IArrayUtils';
 import { ICustomNode } from '../../interfaces/custom-nodes/ICustomNode';
@@ -13,9 +14,11 @@ import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 
 import { ControlFlowCustomNode } from '../../enums/custom-nodes/ControlFlowCustomNode';
+import { TransformationStage } from '../../enums/node-transformers/TransformationStage';
 
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { NodeGuards } from '../../node/NodeGuards';
+import { NodeUtils } from '../../node/NodeUtils';
 
 @injectable()
 export class BlockStatementControlFlowTransformer extends AbstractNodeTransformer {
@@ -97,16 +100,23 @@ export class BlockStatementControlFlowTransformer extends AbstractNodeTransforme
     }
 
     /**
-     * @return {IVisitor}
+     * @param {TransformationStage} transformationStage
+     * @returns {IVisitor | null}
      */
-    public getVisitor (): IVisitor {
-        return {
-            leave: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
-                if (parentNode && NodeGuards.isBlockStatementNode(node)) {
-                    return this.transformNode(node, parentNode);
-                }
-            }
-        };
+    public getVisitor (transformationStage: TransformationStage): IVisitor | null {
+        switch (transformationStage) {
+            case TransformationStage.ControlFlowFlattening:
+                return {
+                    leave: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
+                        if (parentNode && NodeGuards.isBlockStatementNode(node)) {
+                            return this.transformNode(node, parentNode);
+                        }
+                    }
+                };
+
+            default:
+                return null;
+        }
     }
 
     /**
@@ -136,6 +146,10 @@ export class BlockStatementControlFlowTransformer extends AbstractNodeTransforme
             originalKeysIndexesInShuffledArray
         );
 
-        return blockStatementControlFlowFlatteningCustomNode.getNode()[0];
+        const newBlockStatementNode: TStatement = blockStatementControlFlowFlatteningCustomNode.getNode()[0];
+
+        NodeUtils.parentizeNode(newBlockStatementNode, parentNode);
+
+        return newBlockStatementNode;
     }
 }

+ 32 - 24
src/node-transformers/control-flow-transformers/FunctionControlFlowTransformer.ts

@@ -7,7 +7,7 @@ import * as ESTree from 'estree';
 import { TControlFlowCustomNodeFactory } from '../../types/container/custom-nodes/TControlFlowCustomNodeFactory';
 import { TControlFlowReplacerFactory } from '../../types/container/node-transformers/TControlFlowReplacerFactory';
 import { TControlFlowStorageFactory } from '../../types/container/node-transformers/TControlFlowStorageFactory';
-import { TNodeWithBlockStatement } from '../../types/node/TNodeWithBlockStatement';
+import { TNodeWithBlockScope } from '../../types/node/TNodeWithBlockScope';
 
 import { ICustomNode } from '../../interfaces/custom-nodes/ICustomNode';
 import { IOptions } from '../../interfaces/options/IOptions';
@@ -18,6 +18,7 @@ import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 import { ControlFlowCustomNode } from '../../enums/custom-nodes/ControlFlowCustomNode';
 import { ControlFlowReplacer } from '../../enums/node-transformers/obfuscating-transformers/obfuscating-replacers/ControlFlowReplacer';
 import { NodeType } from '../../enums/node/NodeType';
+import { TransformationStage } from '../../enums/node-transformers/TransformationStage';
 
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { NodeGuards } from '../../node/NodeGuards';
@@ -57,9 +58,9 @@ export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
     private readonly visitedFunctionNodes: Set<ESTree.Function> = new Set();
 
     /**
-     * @type {Set<TNodeWithBlockStatement>}
+     * @type {Set<TNodeWithBlockScope>}
      */
-    private readonly hostNodesWithControlFlowNode: Set<TNodeWithBlockStatement> = new Set();
+    private readonly hostNodesWithControlFlowNode: Set<TNodeWithBlockScope> = new Set();
 
     /**
      * @type {TControlFlowReplacerFactory}
@@ -101,22 +102,29 @@ export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
     }
 
     /**
-     * @return {IVisitor}
+     * @param {TransformationStage} transformationStage
+     * @returns {IVisitor | null}
      */
-    public getVisitor (): IVisitor {
-        return {
-            leave: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
-                if (
-                    parentNode && (
-                        NodeGuards.isFunctionDeclarationNode(node) ||
-                        NodeGuards.isFunctionExpressionNode(node) ||
-                        NodeGuards.isArrowFunctionExpressionNode(node)
-                    )
-                ) {
-                    return this.transformNode(node, parentNode);
-                }
-            }
-        };
+    public getVisitor (transformationStage: TransformationStage): IVisitor | null {
+        switch (transformationStage) {
+            case TransformationStage.ControlFlowFlattening:
+                return {
+                    leave: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
+                        if (
+                            parentNode && (
+                                NodeGuards.isFunctionDeclarationNode(node) ||
+                                NodeGuards.isFunctionExpressionNode(node) ||
+                                NodeGuards.isArrowFunctionExpressionNode(node)
+                            )
+                        ) {
+                            return this.transformNode(node, parentNode);
+                        }
+                    }
+                };
+
+            default:
+                return null;
+        }
     }
 
     /**
@@ -131,7 +139,7 @@ export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
             return functionNode;
         }
 
-        const hostNode: TNodeWithBlockStatement = this.getHostNode(functionNode.body);
+        const hostNode: TNodeWithBlockScope = this.getHostNode(functionNode.body);
         const controlFlowStorage: IStorage<ICustomNode> = this.getControlFlowStorage(hostNode);
 
         this.controlFlowData.set(hostNode, controlFlowStorage);
@@ -153,10 +161,10 @@ export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
     }
 
     /**
-     * @param {TNodeWithBlockStatement} hostNode
+     * @param {TNodeWithBlockScope} hostNode
      * @returns {IStorage<ICustomNode>}
      */
-    private getControlFlowStorage (hostNode: TNodeWithBlockStatement): IStorage<ICustomNode> {
+    private getControlFlowStorage (hostNode: TNodeWithBlockScope): IStorage<ICustomNode> {
         const controlFlowStorage: IStorage <ICustomNode> = this.controlFlowStorageFactory();
 
         if (this.controlFlowData.has(hostNode)) {
@@ -174,10 +182,10 @@ export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
 
     /**
      * @param {BlockStatement} functionNodeBody
-     * @returns {TNodeWithBlockStatement}
+     * @returns {TNodeWithBlockScope}
      */
-    private getHostNode (functionNodeBody: ESTree.BlockStatement): TNodeWithBlockStatement {
-        const blockScopesOfNode: TNodeWithBlockStatement[] = NodeUtils.getBlockScopesOfNode(functionNodeBody);
+    private getHostNode (functionNodeBody: ESTree.BlockStatement): TNodeWithBlockScope {
+        const blockScopesOfNode: TNodeWithBlockScope[] = NodeUtils.getBlockScopesOfNode(functionNodeBody);
 
         if (blockScopesOfNode.length === 1) {
             return functionNodeBody;

+ 17 - 9
src/node-transformers/converting-transformers/MemberExpressionTransformer.ts

@@ -8,6 +8,7 @@ import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 
 import { NodeType } from '../../enums/node/NodeType';
+import { TransformationStage } from '../../enums/node-transformers/TransformationStage';
 
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { NodeGuards } from '../../node/NodeGuards';
@@ -26,16 +27,23 @@ export class MemberExpressionTransformer extends AbstractNodeTransformer {
     }
 
     /**
-     * @return {IVisitor}
+     * @param {TransformationStage} transformationStage
+     * @returns {IVisitor | null}
      */
-    public getVisitor (): IVisitor {
-        return {
-            enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
-                if (parentNode && NodeGuards.isMemberExpressionNode(node)) {
-                    return this.transformNode(node, parentNode);
-                }
-            }
-        };
+    public getVisitor (transformationStage: TransformationStage): IVisitor | null {
+        switch (transformationStage) {
+            case TransformationStage.Converting:
+                return {
+                    enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
+                        if (parentNode && NodeGuards.isMemberExpressionNode(node)) {
+                            return this.transformNode(node, parentNode);
+                        }
+                    }
+                };
+
+            default:
+                return null;
+        }
     }
 
     /**

+ 17 - 9
src/node-transformers/converting-transformers/MethodDefinitionTransformer.ts

@@ -8,6 +8,7 @@ import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 
 import { NodeType } from '../../enums/node/NodeType';
+import { TransformationStage } from '../../enums/node-transformers/TransformationStage';
 
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { NodeGuards } from '../../node/NodeGuards';
@@ -40,16 +41,23 @@ export class MethodDefinitionTransformer extends AbstractNodeTransformer {
     }
 
     /**
-     * @return {IVisitor}
+     * @param {TransformationStage} transformationStage
+     * @returns {IVisitor | null}
      */
-    public getVisitor (): IVisitor {
-        return {
-            enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
-                if (parentNode && NodeGuards.isMethodDefinitionNode(node)) {
-                    return this.transformNode(node, parentNode);
-                }
-            }
-        };
+    public getVisitor (transformationStage: TransformationStage): IVisitor | null {
+        switch (transformationStage) {
+            case TransformationStage.Converting:
+                return {
+                    enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
+                        if (parentNode && NodeGuards.isMethodDefinitionNode(node)) {
+                            return this.transformNode(node, parentNode);
+                        }
+                    }
+                };
+
+            default:
+                return null;
+        }
     }
 
     /**

+ 239 - 0
src/node-transformers/converting-transformers/ObjectExpressionKeysTransformer.ts

@@ -0,0 +1,239 @@
+import { inject, injectable } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
+
+import * as ESTree from 'estree';
+
+import { TNodeWithScope } from '../../types/node/TNodeWithScope';
+
+import { IOptions } from '../../interfaces/options/IOptions';
+import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
+import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
+
+import { TransformationStage } from '../../enums/node-transformers/TransformationStage';
+
+import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
+import { NodeAppender } from '../../node/NodeAppender';
+import { NodeGuards } from '../../node/NodeGuards';
+import { Nodes } from '../../node/Nodes';
+import { NodeUtils } from '../../node/NodeUtils';
+
+@injectable()
+export class ObjectExpressionKeysTransformer extends AbstractNodeTransformer {
+    /**
+     * @type {Map<VariableDeclarator, TNodeWithScope>}
+     */
+    private cachedScopeNodesMap: Map <ESTree.VariableDeclarator, TNodeWithScope> = new Map();
+
+    /**
+     * @param {IRandomGenerator} randomGenerator
+     * @param {IOptions} options
+     */
+    constructor (
+        @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        super(randomGenerator, options);
+    }
+
+    /**
+     * @param {TNodeWithScope} scopeNode
+     * @param {ExpressionStatement[]} expressionStatements
+     * @param {Node} variableDeclarator
+     */
+    private static appendExpressionStatements (
+        scopeNode: TNodeWithScope,
+        expressionStatements: ESTree.ExpressionStatement[],
+        variableDeclarator: ESTree.Node
+    ): void {
+        const variableDeclaration: ESTree.Node | undefined = variableDeclarator.parentNode;
+
+        if (!variableDeclaration || !NodeGuards.isVariableDeclarationNode(variableDeclaration)) {
+            throw new Error('Cannot find variable declaration for variable declarator');
+        }
+
+        NodeAppender.insertNodeAfter(scopeNode, expressionStatements, variableDeclaration);
+    }
+
+    /**
+     * @param {Property[]} properties
+     * @param {number[]} removablePropertyIds
+     * @returns {Property[]}
+     */
+    private static filterObjectExpressionProperties (properties: ESTree.Property[], removablePropertyIds: number[]): ESTree.Property[] {
+        return properties.filter((property: ESTree.Property, index: number) => !removablePropertyIds.includes(index));
+    }
+
+    /**
+     * @param {TransformationStage} transformationStage
+     * @returns {IVisitor | null}
+     */
+    public getVisitor (transformationStage: TransformationStage): IVisitor | null {
+        switch (transformationStage) {
+            case TransformationStage.Converting:
+                return {
+                    enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
+                        if (
+                            this.options.transformObjectKeys
+                            && parentNode
+                            && NodeGuards.isObjectExpressionNode(node)
+                            && NodeGuards.isVariableDeclaratorNode(parentNode)
+                        ) {
+                            return this.transformNode(node, parentNode);
+                        }
+                    }
+                };
+
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * replaces:
+     *     var object = {
+     *          foo: 1,
+     *          bar: 2
+     *     };
+     *
+     * on:
+     *     var object = {};
+     *     object['foo'] = 1;
+     *     object['bar'] = 2;
+     *
+     * @param {MemberExpression} objectExpressionNode
+     * @param {NodeGuards} variableDeclarator
+     * @returns {NodeGuards}
+     */
+    public transformNode (objectExpressionNode: ESTree.ObjectExpression, variableDeclarator: ESTree.VariableDeclarator): ESTree.Node {
+        // should pass only Expression nodes as MemberExpression.object value
+        if (!NodeGuards.isIdentifierNode(variableDeclarator.id)) {
+            return objectExpressionNode;
+        }
+
+        const scopeNode: TNodeWithScope | null = NodeUtils.getScopeOfNode(variableDeclarator);
+
+        if (!scopeNode || !NodeGuards.isNodeHasScope(scopeNode)) {
+            return objectExpressionNode;
+        }
+
+        this.cachedScopeNodesMap.set(variableDeclarator, scopeNode);
+
+        return this.transformObjectExpressionNode(
+            objectExpressionNode,
+            variableDeclarator.id,
+            variableDeclarator
+        );
+    }
+
+    /**
+     * @param {Property[]} properties
+     * @param {Expression} memberExpressionObject
+     * @param {VariableDeclarator} variableDeclarator
+     * @returns {[ExpressionStatement[] , number[]]}
+     */
+    private extractPropertiesToExpressionStatements (
+        properties: ESTree.Property[],
+        memberExpressionObject: ESTree.Expression,
+        variableDeclarator: ESTree.VariableDeclarator
+    ): [ESTree.ExpressionStatement[], number[]] {
+        const propertiesLength: number = properties.length;
+        const expressionStatements: ESTree.ExpressionStatement[] = [];
+        const removablePropertyIds: number[] = [];
+
+        for (let i: number = 0; i < propertiesLength; i++) {
+            const property: ESTree.Property = properties[i];
+            const propertyKey: ESTree.Expression = property.key;
+
+            // invalid property nodes
+            if (
+                NodeGuards.isObjectPatternNode(property.value)
+                || NodeGuards.isArrayPatternNode(property.value)
+                || NodeGuards.isAssignmentPatternNode(property.value)
+                || NodeGuards.isRestElementNode(property.value)
+            ) {
+                continue;
+            }
+
+            /**
+             * Stage 1: collecting property node key names
+             */
+            let propertyKeyName: string;
+
+            if (NodeGuards.isLiteralNode(propertyKey) && typeof propertyKey.value === 'string') {
+                propertyKeyName = propertyKey.value;
+            } else if (NodeGuards.isIdentifierNode(propertyKey)) {
+                propertyKeyName = propertyKey.name;
+            } else {
+                continue;
+            }
+
+            /**
+             * Stage 2: creating new expression statement node with member expression based on removed property
+             */
+            const shouldCreateLiteralNode: boolean = !property.computed
+                || (property.computed && NodeGuards.isLiteralNode(property.key));
+            const memberExpressionProperty: ESTree.Expression = shouldCreateLiteralNode
+                ? Nodes.getLiteralNode(propertyKeyName)
+                : Nodes.getIdentifierNode(propertyKeyName);
+            const memberExpressionNode: ESTree.MemberExpression = Nodes
+                .getMemberExpressionNode(memberExpressionObject, memberExpressionProperty, true);
+            const rightExpression: ESTree.Expression = property.value;
+            const expressionStatementNode: ESTree.ExpressionStatement = Nodes.getExpressionStatementNode(
+                Nodes.getAssignmentExpressionNode('=', memberExpressionNode, rightExpression)
+            );
+
+            /**
+             * Stage 3: recursively processing nested object expressions
+             */
+            if (NodeGuards.isObjectExpressionNode(property.value)) {
+                this.transformObjectExpressionNode(
+                    property.value,
+                    memberExpressionNode,
+                    variableDeclarator
+                );
+            }
+
+            /**
+             * Stage 4: filling arrays
+             */
+            expressionStatements.push(expressionStatementNode);
+            removablePropertyIds.push(i);
+        }
+
+        return [expressionStatements, removablePropertyIds];
+    }
+
+    /**
+     * @param {ObjectExpression} objectExpressionNode
+     * @param {Expression} memberExpressionObjectNode
+     * @param {VariableDeclarator} variableDeclarator
+     * @returns {Node}
+     */
+    private transformObjectExpressionNode (
+        objectExpressionNode: ESTree.ObjectExpression,
+        memberExpressionObjectNode: ESTree.Expression,
+        variableDeclarator: ESTree.VariableDeclarator
+    ): ESTree.Node {
+        const properties: ESTree.Property[] = objectExpressionNode.properties;
+
+        if (!properties.length) {
+            return objectExpressionNode;
+        }
+
+        const scopeNode: TNodeWithScope | undefined = this.cachedScopeNodesMap.get(variableDeclarator);
+
+        if (!scopeNode) {
+            return objectExpressionNode;
+        }
+
+        const [expressionStatements, removablePropertyIds]: [ESTree.ExpressionStatement[], number[]] = this
+            .extractPropertiesToExpressionStatements(properties, memberExpressionObjectNode, variableDeclarator);
+
+        objectExpressionNode.properties = ObjectExpressionKeysTransformer
+            .filterObjectExpressionProperties(properties, removablePropertyIds);
+        ObjectExpressionKeysTransformer
+            .appendExpressionStatements(scopeNode, expressionStatements, variableDeclarator);
+
+        return objectExpressionNode;
+    }
+}

+ 18 - 9
src/node-transformers/converting-transformers/TemplateLiteralTransformer.ts

@@ -7,6 +7,8 @@ import { IOptions } from '../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 
+import { TransformationStage } from '../../enums/node-transformers/TransformationStage';
+
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { NodeGuards } from '../../node/NodeGuards';
 import { Nodes } from '../../node/Nodes';
@@ -37,16 +39,23 @@ export class TemplateLiteralTransformer extends AbstractNodeTransformer {
     }
 
     /**
-     * @return {IVisitor}
+     * @param {TransformationStage} transformationStage
+     * @returns {IVisitor | null}
      */
-    public getVisitor (): IVisitor {
-        return {
-            enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
-                if (parentNode && NodeGuards.isTemplateLiteralNode(node)) {
-                    return this.transformNode(node, parentNode);
-                }
-            }
-        };
+    public getVisitor (transformationStage: TransformationStage): IVisitor | null {
+        switch (transformationStage) {
+            case TransformationStage.Converting:
+                return {
+                    enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
+                        if (parentNode && NodeGuards.isTemplateLiteralNode(node)) {
+                            return this.transformNode(node, parentNode);
+                        }
+                    }
+                };
+
+            default:
+                return null;
+        }
     }
 
     /**

+ 139 - 112
src/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.ts

@@ -4,15 +4,17 @@ import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 
-import { TNodeWithBlockStatement } from '../../types/node/TNodeWithBlockStatement';
+import { TDeadNodeInjectionCustomNodeFactory } from '../../types/container/custom-nodes/TDeadNodeInjectionCustomNodeFactory';
+import { TNodeWithBlockScope } from '../../types/node/TNodeWithBlockScope';
 
+import { ICustomNode } from '../../interfaces/custom-nodes/ICustomNode';
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
-import { ITransformersRunner } from '../../interfaces/node-transformers/ITransformersRunner';
 import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 
-import { NodeTransformer } from '../../enums/node-transformers/NodeTransformer';
+import { DeadCodeInjectionCustomNode } from '../../enums/custom-nodes/DeadCodeInjectionCustomNode';
 import { NodeType } from '../../enums/node/NodeType';
+import { TransformationStage } from '../../enums/node-transformers/TransformationStage';
 
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { NodeGuards } from '../../node/NodeGuards';
@@ -21,6 +23,11 @@ import { NodeUtils } from '../../node/NodeUtils';
 
 @injectable()
 export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
+    /**
+     * @type {string}
+     */
+    private static deadCodeInjectionRootAstHostNodeName: string = 'deadCodeInjectionRootAstHostNode';
+
     /**
      * @type {number}
      */
@@ -32,16 +39,9 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
     private static readonly minCollectedBlockStatementsCount: number = 5;
 
     /**
-     * @type {NodeTransformer[]}
+     * @type {Set <BlockStatement>}
      */
-    private static readonly transformersToRenameBlockScopeIdentifiers: NodeTransformer[] = [
-        NodeTransformer.CatchClauseTransformer,
-        NodeTransformer.ClassDeclarationTransformer,
-        NodeTransformer.FunctionDeclarationTransformer,
-        NodeTransformer.FunctionTransformer,
-        NodeTransformer.LabeledStatementTransformer,
-        NodeTransformer.VariableDeclarationTransformer
-    ];
+    private readonly deadCodeInjectionRootAstHostNodeSet: Set <ESTree.BlockStatement> = new Set();
 
     /**
      * @type {ESTree.BlockStatement[]}
@@ -51,57 +51,101 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
     /**
      * @type {number}
      */
-    private collectedBlockStatementsLength: number;
+    private collectedBlockStatementsTotalLength: number;
 
     /**
-     * @type {ITransformersRunner}
+     * @type {TDeadNodeInjectionCustomNodeFactory}
      */
-    private readonly transformersRunner: ITransformersRunner;
+    private readonly deadCodeInjectionCustomNodeFactory: TDeadNodeInjectionCustomNodeFactory;
 
     /**
-     * @param {ITransformersRunner} transformersRunner
+     * @param {TControlFlowCustomNodeFactory} deadCodeInjectionCustomNodeFactory
      * @param {IRandomGenerator} randomGenerator
      * @param {IOptions} options
      */
     constructor (
-        @inject(ServiceIdentifiers.ITransformersRunner) transformersRunner: ITransformersRunner,
+        @inject(ServiceIdentifiers.Factory__IDeadCodeInjectionCustomNode)
+            deadCodeInjectionCustomNodeFactory: TDeadNodeInjectionCustomNodeFactory,
         @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
         @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
         super(randomGenerator, options);
 
-        this.transformersRunner = transformersRunner;
+        this.deadCodeInjectionCustomNodeFactory = deadCodeInjectionCustomNodeFactory;
     }
 
     /**
-     * @param {Node} node
+     * @param {Node} blockStatementNode
      * @returns {boolean}
      */
-    private static isValidBlockStatementNode (node: ESTree.Node): boolean {
-        return !NodeGuards.isBreakStatementNode(node) &&
-            !NodeGuards.isContinueStatementNode(node) &&
-            !NodeGuards.isAwaitExpressionNode(node) &&
-            !NodeGuards.isSuperNode(node);
+    private static isValidBlockStatementNode (blockStatementNode: ESTree.Node): boolean {
+        const isProhibitedNode: (node: ESTree.Node) => boolean =
+            (node: ESTree.Node): boolean => NodeGuards.isBreakStatementNode(node) ||
+                NodeGuards.isContinueStatementNode(node) ||
+                NodeGuards.isAwaitExpressionNode(node) ||
+                NodeGuards.isSuperNode(node);
+
+        let nestedBlockStatementsCount: number = 0,
+            isValidBlockStatementNode: boolean = true;
+
+        estraverse.traverse(blockStatementNode, {
+            enter: (node: ESTree.Node): any => {
+                if (NodeGuards.isBlockStatementNode(node)) {
+                    nestedBlockStatementsCount++;
+                }
+
+                if (
+                    nestedBlockStatementsCount > DeadCodeInjectionTransformer.maxNestedBlockStatementsCount ||
+                    isProhibitedNode(node)
+                ) {
+                    isValidBlockStatementNode = false;
+
+                    return estraverse.VisitorOption.Break;
+                }
+            }
+        });
+
+        return isValidBlockStatementNode;
     }
 
     /**
-     * @return {IVisitor}
+     * @param {TransformationStage} transformationStage
+     * @returns {IVisitor | null}
      */
-    public getVisitor (): IVisitor {
-        return {
-            enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
-                if (parentNode && NodeGuards.isProgramNode(node)) {
-                    this.analyzeNode(node, parentNode);
-
-                    return node;
+    public getVisitor (transformationStage: TransformationStage): IVisitor | null {
+        switch (transformationStage) {
+            case TransformationStage.DeadCodeInjection:
+                return {
+                    enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
+                        if (parentNode && NodeGuards.isProgramNode(node)) {
+                            this.analyzeNode(node, parentNode);
+
+                            return node;
+                        }
+                    },
+                    leave: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
+                        if (parentNode && NodeGuards.isBlockStatementNode(node)) {
+                            return this.transformNode(node, parentNode);
+                        }
+                    }
+                };
+
+            case TransformationStage.Finalizing:
+                if (!this.deadCodeInjectionRootAstHostNodeSet.size) {
+                    return null;
                 }
-            },
-            leave: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
-                if (parentNode && NodeGuards.isBlockStatementNode(node)) {
-                    return this.transformNode(node, parentNode);
-                }
-            }
-        };
+
+                return {
+                    enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
+                        if (parentNode && this.isDeadCodeInjectionRootAstHostNode(node)) {
+                            return this.restoreNode(node, parentNode);
+                        }
+                    }
+                };
+
+            default:
+                return null;
+        }
     }
 
     /**
@@ -111,13 +155,21 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
     public analyzeNode (programNode: ESTree.Node, parentNode: ESTree.Node): void {
         estraverse.traverse(programNode, {
             enter: (node: ESTree.Node): any => {
-                if (NodeGuards.isBlockStatementNode(node)) {
-                    this.collectBlockStatementNodes(node, this.collectedBlockStatements);
+                if (!NodeGuards.isBlockStatementNode(node)) {
+                    return;
+                }
+
+                const clonedBlockStatementNode: ESTree.BlockStatement = NodeUtils.clone(node);
+
+                if (!DeadCodeInjectionTransformer.isValidBlockStatementNode(clonedBlockStatementNode)) {
+                    return;
                 }
+
+                this.collectedBlockStatements.push(clonedBlockStatementNode);
             }
         });
 
-        this.collectedBlockStatementsLength = this.collectedBlockStatements.length;
+        this.collectedBlockStatementsTotalLength = this.collectedBlockStatements.length;
     }
 
     /**
@@ -126,7 +178,7 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
      * @returns {NodeGuards | VisitorOption}
      */
     public transformNode (blockStatementNode: ESTree.BlockStatement, parentNode: ESTree.Node): ESTree.Node | estraverse.VisitorOption {
-        if (this.collectedBlockStatementsLength < DeadCodeInjectionTransformer.minCollectedBlockStatementsCount) {
+        if (this.collectedBlockStatementsTotalLength < DeadCodeInjectionTransformer.minCollectedBlockStatementsCount) {
             return estraverse.VisitorOption.Break;
         }
 
@@ -138,7 +190,7 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
             return blockStatementNode;
         }
 
-        const blockScopeOfBlockStatementNode: TNodeWithBlockStatement = NodeUtils
+        const blockScopeOfBlockStatementNode: TNodeWithBlockScope = NodeUtils
             .getBlockScopesOfNode(blockStatementNode)[0];
 
         if (blockScopeOfBlockStatementNode.type === NodeType.Program) {
@@ -154,95 +206,70 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
             return blockStatementNode;
         }
 
-        return this.replaceBlockStatementNode(blockStatementNode, randomBlockStatementNode);
+        return this.replaceBlockStatementNode(blockStatementNode, randomBlockStatementNode, parentNode);
     }
 
     /**
-     * @param {BlockStatement} blockStatementNode
-     * @param {BlockStatement[]} collectedBlockStatements
+     * @param {FunctionExpression} deadCodeInjectionRootAstHostNode
+     * @param {Node} parentNode
+     * @returns {Node}
      */
-    private collectBlockStatementNodes (
-        blockStatementNode: ESTree.BlockStatement,
-        collectedBlockStatements: ESTree.BlockStatement[]
-    ): void {
-        let clonedBlockStatementNode: ESTree.BlockStatement = NodeUtils.clone(blockStatementNode),
-            nestedBlockStatementsCount: number = 0,
-            isValidBlockStatementNode: boolean = true;
-
-        estraverse.replace(clonedBlockStatementNode, {
-            enter: (node: ESTree.Node, parentNode: ESTree.Node | null): any => {
-                /**
-                 * Count nested block statements in current block statement
-                 */
-                if (NodeGuards.isBlockStatementNode(node)) {
-                    nestedBlockStatementsCount++;
-                }
+    public restoreNode (deadCodeInjectionRootAstHostNode: ESTree.BlockStatement, parentNode: ESTree.Node): ESTree.Node {
+        const hostNodeFirstStatement: ESTree.Statement = deadCodeInjectionRootAstHostNode.body[0];
 
-                /**
-                 * If nested block statements count bigger then specified amount or current block statement
-                 * contains prohibited nodes - we will stop traversing and leave method
-                 */
-                if (
-                    nestedBlockStatementsCount > DeadCodeInjectionTransformer.maxNestedBlockStatementsCount ||
-                    !DeadCodeInjectionTransformer.isValidBlockStatementNode(node)
-                ) {
-                    isValidBlockStatementNode = false;
-
-                    return estraverse.VisitorOption.Break;
-                }
-
-                return node;
-            }
-        });
-
-        if (!isValidBlockStatementNode) {
-            return;
+        if (!NodeGuards.isFunctionDeclarationNode(hostNodeFirstStatement)) {
+            throw new Error('Wrong dead code injection root AST host node. Host node should contain `FunctionDeclaration` node');
         }
 
-        /**
-         * We should transform identifiers in the dead code block statement to avoid conflicts with original code
-         */
-        clonedBlockStatementNode = this.transformersRunner.transform(
-            clonedBlockStatementNode,
-            DeadCodeInjectionTransformer.transformersToRenameBlockScopeIdentifiers
-        );
+        return hostNodeFirstStatement.body;
+    }
 
-        collectedBlockStatements.push(clonedBlockStatementNode);
+    /**
+     * @param {Node} node
+     * @returns {boolean}
+     */
+    private isDeadCodeInjectionRootAstHostNode (node: ESTree.Node): node is ESTree.BlockStatement {
+        return NodeGuards.isBlockStatementNode(node) && this.deadCodeInjectionRootAstHostNodeSet.has(node);
     }
 
     /**
      * @param {BlockStatement} blockStatementNode
      * @param {BlockStatement} randomBlockStatementNode
+     * @param {Node} parentNode
      * @returns {BlockStatement}
      */
     private replaceBlockStatementNode (
         blockStatementNode: ESTree.BlockStatement,
-        randomBlockStatementNode: ESTree.BlockStatement
+        randomBlockStatementNode: ESTree.BlockStatement,
+        parentNode: ESTree.Node
     ): ESTree.BlockStatement {
-        const random1: boolean = this.randomGenerator.getMathRandom() > 0.5;
-        const random2: boolean = this.randomGenerator.getMathRandom() > 0.5;
-
-        const operator: ESTree.BinaryOperator = random1 ? '===' : '!==';
-        const leftString: string = this.randomGenerator.getRandomString(5);
-        const rightString: string = random2 ? leftString : this.randomGenerator.getRandomString(5);
-
-        const [consequent, alternate]: [ESTree.BlockStatement, ESTree.BlockStatement] = random1 === random2
-            ? [blockStatementNode, randomBlockStatementNode]
-            : [randomBlockStatementNode, blockStatementNode];
-
-        let newBlockStatementNode: ESTree.BlockStatement = Nodes.getBlockStatementNode([
-            Nodes.getIfStatementNode(
-                Nodes.getBinaryExpressionNode(
-                    operator,
-                    Nodes.getLiteralNode(leftString),
-                    Nodes.getLiteralNode(rightString)
-                ),
-                consequent,
-                alternate
+        /**
+         * we should wrap original random block statement node into the parent block statement node (ast root host node)
+         * with function declaration node. This function declaration node will create block scope for all identifiers
+         * inside random block statement node and this identifiers won't affect identifiers of the rest AST tree.
+         */
+        const deadCodeInjectionRootAstHostNode: ESTree.BlockStatement = Nodes.getBlockStatementNode([
+            Nodes.getFunctionDeclarationNode(
+                DeadCodeInjectionTransformer.deadCodeInjectionRootAstHostNodeName,
+                [],
+                randomBlockStatementNode
             )
         ]);
 
-        newBlockStatementNode = NodeUtils.parentize(newBlockStatementNode);
+        /**
+         * we should store that host node and then extract random block statement node on the `finalizing` stage
+         */
+        this.deadCodeInjectionRootAstHostNodeSet.add(deadCodeInjectionRootAstHostNode);
+
+        const blockStatementDeadCodeInjectionCustomNode: ICustomNode = this.deadCodeInjectionCustomNodeFactory(
+            DeadCodeInjectionCustomNode.BlockStatementDeadCodeInjectionNode
+        );
+
+        blockStatementDeadCodeInjectionCustomNode.initialize(blockStatementNode, deadCodeInjectionRootAstHostNode);
+
+        const newBlockStatementNode: ESTree.BlockStatement = <ESTree.BlockStatement>blockStatementDeadCodeInjectionCustomNode.getNode()[0];
+
+        NodeUtils.parentizeNode(newBlockStatementNode, parentNode);
 
         return newBlockStatementNode;
     }

+ 17 - 9
src/node-transformers/obfuscating-transformers/CatchClauseTransformer.ts

@@ -12,6 +12,7 @@ import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 
 import { IdentifierObfuscatingReplacer } from '../../enums/node-transformers/obfuscating-transformers/obfuscating-replacers/IdentifierObfuscatingReplacer';
+import { TransformationStage } from '../../enums/node-transformers/TransformationStage';
 
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { NodeGuards } from '../../node/NodeGuards';
@@ -50,16 +51,23 @@ export class CatchClauseTransformer extends AbstractNodeTransformer {
     }
 
     /**
-     * @return {IVisitor}
+     * @param {TransformationStage} transformationStage
+     * @returns {IVisitor | null}
      */
-    public getVisitor (): IVisitor {
-        return {
-            enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
-                if (parentNode && NodeGuards.isCatchClauseNode(node)) {
-                    return this.transformNode(node, parentNode);
-                }
-            }
-        };
+    public getVisitor (transformationStage: TransformationStage): IVisitor | null {
+        switch (transformationStage) {
+            case TransformationStage.Obfuscating:
+                return {
+                    enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
+                        if (parentNode && NodeGuards.isCatchClauseNode(node)) {
+                            return this.transformNode(node, parentNode);
+                        }
+                    }
+                };
+
+            default:
+                return null;
+        }
     }
 
     /**

+ 23 - 15
src/node-transformers/obfuscating-transformers/ClassDeclarationTransformer.ts

@@ -5,7 +5,7 @@ import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 
 import { TIdentifierObfuscatingReplacerFactory } from "../../types/container/node-transformers/TIdentifierObfuscatingReplacerFactory";
-import { TNodeWithBlockStatement } from '../../types/node/TNodeWithBlockStatement';
+import { TNodeWithBlockScope } from '../../types/node/TNodeWithBlockScope';
 
 import { IIdentifierObfuscatingReplacer } from '../../interfaces/node-transformers/obfuscating-transformers/obfuscating-replacers/IIdentifierObfuscatingReplacer';
 import { IOptions } from '../../interfaces/options/IOptions';
@@ -14,6 +14,7 @@ import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 
 import { IdentifierObfuscatingReplacer } from "../../enums/node-transformers/obfuscating-transformers/obfuscating-replacers/IdentifierObfuscatingReplacer";
 import { NodeType } from '../../enums/node/NodeType';
+import { TransformationStage } from '../../enums/node-transformers/TransformationStage';
 
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { NodeGuards } from '../../node/NodeGuards';
@@ -59,16 +60,23 @@ export class ClassDeclarationTransformer extends AbstractNodeTransformer {
     }
 
     /**
-     * @return {IVisitor}
+     * @param {TransformationStage} transformationStage
+     * @returns {IVisitor | null}
      */
-    public getVisitor (): IVisitor {
-        return {
-            enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
-                if (parentNode && NodeGuards.isClassDeclarationNode(node)) {
-                    return this.transformNode(node, parentNode);
-                }
-            }
-        };
+    public getVisitor (transformationStage: TransformationStage): IVisitor | null {
+        switch (transformationStage) {
+            case TransformationStage.Obfuscating:
+                return {
+                    enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
+                        if (parentNode && NodeGuards.isClassDeclarationNode(node)) {
+                            return this.transformNode(node, parentNode);
+                        }
+                    }
+                };
+
+            default:
+                return null;
+        }
     }
 
     /**
@@ -78,7 +86,7 @@ export class ClassDeclarationTransformer extends AbstractNodeTransformer {
      */
     public transformNode (classDeclarationNode: ESTree.ClassDeclaration, parentNode: ESTree.Node): ESTree.Node {
         const nodeIdentifier: number = this.nodeIdentifier++;
-        const blockScopeNode: TNodeWithBlockStatement = NodeUtils
+        const blockScopeNode: TNodeWithBlockScope = NodeUtils
             .getBlockScopesOfNode(classDeclarationNode)[0];
 
         if (!this.options.renameGlobals && blockScopeNode.type === NodeType.Program) {
@@ -106,10 +114,10 @@ export class ClassDeclarationTransformer extends AbstractNodeTransformer {
     }
 
     /**
-     * @param {TNodeWithBlockStatement} blockScopeNode
+     * @param {TNodeWithBlockScope} blockScopeNode
      * @param {number} nodeIdentifier
      */
-    private replaceScopeCachedIdentifiers (blockScopeNode: TNodeWithBlockStatement, nodeIdentifier: number): void {
+    private replaceScopeCachedIdentifiers (blockScopeNode: TNodeWithBlockScope, nodeIdentifier: number): void {
         const cachedReplaceableIdentifiers: ESTree.Identifier[] = <ESTree.Identifier[]>this.replaceableIdentifiers.get(blockScopeNode);
 
         cachedReplaceableIdentifiers.forEach((replaceableIdentifier: ESTree.Identifier) => {
@@ -121,10 +129,10 @@ export class ClassDeclarationTransformer extends AbstractNodeTransformer {
     }
 
     /**
-     * @param {TNodeWithBlockStatement} blockScopeNode
+     * @param {TNodeWithBlockScope} blockScopeNode
      * @param {number} nodeIdentifier
      */
-    private replaceScopeIdentifiers (blockScopeNode: TNodeWithBlockStatement, nodeIdentifier: number): void {
+    private replaceScopeIdentifiers (blockScopeNode: TNodeWithBlockScope, nodeIdentifier: number): void {
         const storedReplaceableIdentifiers: ESTree.Identifier[] = [];
 
         estraverse.replace(blockScopeNode, {

+ 23 - 15
src/node-transformers/obfuscating-transformers/FunctionDeclarationTransformer.ts

@@ -5,7 +5,7 @@ import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 
 import { TIdentifierObfuscatingReplacerFactory } from "../../types/container/node-transformers/TIdentifierObfuscatingReplacerFactory";
-import { TNodeWithBlockStatement } from '../../types/node/TNodeWithBlockStatement';
+import { TNodeWithBlockScope } from '../../types/node/TNodeWithBlockScope';
 import { TReplaceableIdentifiers } from '../../types/node-transformers/TReplaceableIdentifiers';
 import { TReplaceableIdentifiersNames } from '../../types/node-transformers/TReplaceableIdentifiersNames';
 
@@ -16,6 +16,7 @@ import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 
 import { IdentifierObfuscatingReplacer } from "../../enums/node-transformers/obfuscating-transformers/obfuscating-replacers/IdentifierObfuscatingReplacer";
 import { NodeType } from '../../enums/node/NodeType';
+import { TransformationStage } from '../../enums/node-transformers/TransformationStage';
 
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { NodeGuards } from '../../node/NodeGuards';
@@ -61,16 +62,23 @@ export class FunctionDeclarationTransformer extends AbstractNodeTransformer {
     }
 
     /**
-     * @return {IVisitor}
+     * @param {TransformationStage} transformationStage
+     * @returns {IVisitor | null}
      */
-    public getVisitor (): IVisitor {
-        return {
-            enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
-                if (parentNode && NodeGuards.isFunctionDeclarationNode(node)) {
-                    return this.transformNode(node, parentNode);
-                }
-            }
-        };
+    public getVisitor (transformationStage: TransformationStage): IVisitor | null {
+        switch (transformationStage) {
+            case TransformationStage.Obfuscating:
+                return {
+                    enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
+                        if (parentNode && NodeGuards.isFunctionDeclarationNode(node)) {
+                            return this.transformNode(node, parentNode);
+                        }
+                    }
+                };
+
+            default:
+                return null;
+        }
     }
 
     /**
@@ -80,7 +88,7 @@ export class FunctionDeclarationTransformer extends AbstractNodeTransformer {
      */
     public transformNode (functionDeclarationNode: ESTree.FunctionDeclaration, parentNode: ESTree.Node): ESTree.Node {
         const nodeIdentifier: number = this.nodeIdentifier++;
-        const blockScopeNode: TNodeWithBlockStatement = NodeUtils
+        const blockScopeNode: TNodeWithBlockScope = NodeUtils
             .getBlockScopesOfNode(functionDeclarationNode)[0];
 
         if (!this.options.renameGlobals && blockScopeNode.type === NodeType.Program) {
@@ -109,12 +117,12 @@ export class FunctionDeclarationTransformer extends AbstractNodeTransformer {
 
     /**
      * @param {FunctionDeclaration} functionDeclarationNode
-     * @param {TNodeWithBlockStatement} blockScopeNode
+     * @param {TNodeWithBlockScope} blockScopeNode
      * @param {number} nodeIdentifier
      */
     private replaceScopeCachedIdentifiers (
         functionDeclarationNode: ESTree.FunctionDeclaration,
-        blockScopeNode: TNodeWithBlockStatement,
+        blockScopeNode: TNodeWithBlockScope,
         nodeIdentifier: number
     ): void {
         const cachedReplaceableIdentifiersNamesMap: TReplaceableIdentifiersNames | undefined = this.replaceableIdentifiers.get(blockScopeNode);
@@ -142,10 +150,10 @@ export class FunctionDeclarationTransformer extends AbstractNodeTransformer {
     }
 
     /**
-     * @param {TNodeWithBlockStatement} blockScopeNode
+     * @param {TNodeWithBlockScope} blockScopeNode
      * @param {number} nodeIdentifier
      */
-    private replaceScopeIdentifiers (blockScopeNode: TNodeWithBlockStatement, nodeIdentifier: number): void {
+    private replaceScopeIdentifiers (blockScopeNode: TNodeWithBlockScope, nodeIdentifier: number): void {
         const storedReplaceableIdentifiersNamesMap: TReplaceableIdentifiersNames = new Map();
 
         estraverse.replace(blockScopeNode, {

+ 23 - 15
src/node-transformers/obfuscating-transformers/FunctionTransformer.ts

@@ -12,6 +12,7 @@ import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 
 import { IdentifierObfuscatingReplacer } from '../../enums/node-transformers/obfuscating-transformers/obfuscating-replacers/IdentifierObfuscatingReplacer';
+import { TransformationStage } from '../../enums/node-transformers/TransformationStage';
 
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { NodeGuards } from '../../node/NodeGuards';
@@ -50,22 +51,29 @@ export class FunctionTransformer extends AbstractNodeTransformer {
     }
 
     /**
-     * @return {IVisitor}
+     * @param {TransformationStage} transformationStage
+     * @returns {IVisitor | null}
      */
-    public getVisitor (): IVisitor {
-        return {
-            enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
-                if (
-                    parentNode && (
-                        NodeGuards.isFunctionDeclarationNode(node) ||
-                        NodeGuards.isFunctionExpressionNode(node) ||
-                        NodeGuards.isArrowFunctionExpressionNode(node)
-                    )
-                ) {
-                    return this.transformNode(node, parentNode);
-                }
-            }
-        };
+    public getVisitor (transformationStage: TransformationStage): IVisitor | null {
+        switch (transformationStage) {
+            case TransformationStage.Obfuscating:
+                return {
+                    enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
+                        if (
+                            parentNode && (
+                                NodeGuards.isFunctionDeclarationNode(node) ||
+                                NodeGuards.isFunctionExpressionNode(node) ||
+                                NodeGuards.isArrowFunctionExpressionNode(node)
+                            )
+                        ) {
+                            return this.transformNode(node, parentNode);
+                        }
+                    }
+                };
+
+            default:
+                return null;
+        }
     }
 
     /**

+ 17 - 9
src/node-transformers/obfuscating-transformers/LabeledStatementTransformer.ts

@@ -12,6 +12,7 @@ import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 
 import { IdentifierObfuscatingReplacer } from '../../enums/node-transformers/obfuscating-transformers/obfuscating-replacers/IdentifierObfuscatingReplacer';
+import { TransformationStage } from '../../enums/node-transformers/TransformationStage';
 
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { NodeGuards } from '../../node/NodeGuards';
@@ -58,16 +59,23 @@ export class LabeledStatementTransformer extends AbstractNodeTransformer {
     }
 
     /**
-     * @return {IVisitor}
+     * @param {TransformationStage} transformationStage
+     * @returns {IVisitor | null}
      */
-    public getVisitor (): IVisitor {
-        return {
-            enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
-                if (parentNode && NodeGuards.isLabeledStatementNode(node)) {
-                    return this.transformNode(node, parentNode);
-                }
-            }
-        };
+    public getVisitor (transformationStage: TransformationStage): IVisitor | null {
+        switch (transformationStage) {
+            case TransformationStage.Obfuscating:
+                return {
+                    enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
+                        if (parentNode && NodeGuards.isLabeledStatementNode(node)) {
+                            return this.transformNode(node, parentNode);
+                        }
+                    }
+                };
+
+            default:
+                return null;
+        }
     }
 
     /**

+ 17 - 9
src/node-transformers/obfuscating-transformers/LiteralTransformer.ts

@@ -10,6 +10,7 @@ import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 
 import { LiteralObfuscatingReplacer } from '../../enums/node-transformers/obfuscating-transformers/obfuscating-replacers/LiteralObfuscatingReplacer';
+import { TransformationStage } from '../../enums/node-transformers/TransformationStage';
 
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { NodeGuards } from '../../node/NodeGuards';
@@ -38,16 +39,23 @@ export class LiteralTransformer extends AbstractNodeTransformer {
     }
 
     /**
-     * @return {IVisitor}
+     * @param {TransformationStage} transformationStage
+     * @returns {IVisitor | null}
      */
-    public getVisitor (): IVisitor {
-        return {
-            enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
-                if (parentNode && NodeGuards.isLiteralNode(node) && !node.obfuscatedNode) {
-                    return this.transformNode(node, parentNode);
-                }
-            }
-        };
+    public getVisitor (transformationStage: TransformationStage): IVisitor | null {
+        switch (transformationStage) {
+            case TransformationStage.Obfuscating:
+                return {
+                    enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
+                        if (parentNode && NodeGuards.isLiteralNode(node) && !node.obfuscatedNode) {
+                            return this.transformNode(node, parentNode);
+                        }
+                    }
+                };
+
+            default:
+                return null;
+        }
     }
 
     /**

+ 17 - 9
src/node-transformers/obfuscating-transformers/ObjectExpressionTransformer.ts

@@ -8,6 +8,7 @@ import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 
 import { NodeType } from '../../enums/node/NodeType';
+import { TransformationStage } from '../../enums/node-transformers/TransformationStage';
 
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { NodeGuards } from '../../node/NodeGuards';
@@ -45,16 +46,23 @@ export class ObjectExpressionTransformer extends AbstractNodeTransformer {
     }
 
     /**
-     * @return {IVisitor}
+     * @param {TransformationStage} transformationStage
+     * @returns {IVisitor | null}
      */
-    public getVisitor (): IVisitor {
-        return {
-            enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
-                if (parentNode && NodeGuards.isObjectExpressionNode(node)) {
-                    return this.transformNode(node, parentNode);
-                }
-            }
-        };
+    public getVisitor (transformationStage: TransformationStage): IVisitor | null {
+        switch (transformationStage) {
+            case TransformationStage.Obfuscating:
+                return {
+                    enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
+                        if (parentNode && NodeGuards.isObjectExpressionNode(node)) {
+                            return this.transformNode(node, parentNode);
+                        }
+                    }
+                };
+
+            default:
+                return null;
+        }
     }
 
     /**

+ 19 - 11
src/node-transformers/obfuscating-transformers/VariableDeclarationTransformer.ts

@@ -5,7 +5,7 @@ import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 
 import { TIdentifierObfuscatingReplacerFactory } from '../../types/container/node-transformers/TIdentifierObfuscatingReplacerFactory';
-import { TNodeWithBlockStatement } from '../../types/node/TNodeWithBlockStatement';
+import { TNodeWithBlockScope } from '../../types/node/TNodeWithBlockScope';
 import { TReplaceableIdentifiers } from '../../types/node-transformers/TReplaceableIdentifiers';
 import { TReplaceableIdentifiersNames } from '../../types/node-transformers/TReplaceableIdentifiersNames';
 
@@ -16,6 +16,7 @@ import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 
 import { IdentifierObfuscatingReplacer } from "../../enums/node-transformers/obfuscating-transformers/obfuscating-replacers/IdentifierObfuscatingReplacer";
 import { NodeType } from '../../enums/node/NodeType';
+import { TransformationStage } from '../../enums/node-transformers/TransformationStage';
 
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { NodeGuards } from '../../node/NodeGuards';
@@ -62,16 +63,23 @@ export class VariableDeclarationTransformer extends AbstractNodeTransformer {
     }
 
     /**
-     * @return {IVisitor}
+     * @param {TransformationStage} transformationStage
+     * @returns {IVisitor | null}
      */
-    public getVisitor (): IVisitor {
-        return {
-            enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
-                if (parentNode && NodeGuards.isVariableDeclarationNode(node)) {
-                    return this.transformNode(node, parentNode);
-                }
-            }
-        };
+    public getVisitor (transformationStage: TransformationStage): IVisitor | null {
+        switch (transformationStage) {
+            case TransformationStage.Obfuscating:
+                return {
+                    enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
+                        if (parentNode && NodeGuards.isVariableDeclarationNode(node)) {
+                            return this.transformNode(node, parentNode);
+                        }
+                    }
+                };
+
+            default:
+                return null;
+        }
     }
 
     /**
@@ -80,7 +88,7 @@ export class VariableDeclarationTransformer extends AbstractNodeTransformer {
      * @returns {NodeGuards}
      */
     public transformNode (variableDeclarationNode: ESTree.VariableDeclaration, parentNode: ESTree.Node): ESTree.Node {
-        const blockScopeNode: TNodeWithBlockStatement = NodeUtils
+        const blockScopeNode: TNodeWithBlockScope = NodeUtils
             .getBlockScopesOfNode(variableDeclarationNode)[0];
 
         if (!this.options.renameGlobals && blockScopeNode.type === NodeType.Program) {

+ 18 - 9
src/node-transformers/preparing-transformers/CommentsTransformer.ts

@@ -7,6 +7,8 @@ import { IOptions } from '../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 
+import { TransformationStage } from '../../enums/node-transformers/TransformationStage';
+
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { NodeGuards } from '../../node/NodeGuards';
 
@@ -29,16 +31,23 @@ export class CommentsTransformer extends AbstractNodeTransformer {
     }
 
     /**
-     * @return {IVisitor}
+     * @param {TransformationStage} transformationStage
+     * @returns {IVisitor | null}
      */
-    public getVisitor (): IVisitor {
-        return {
-            leave: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
-                if (parentNode && NodeGuards.isNodeWithComments(node)) {
-                    return this.transformNode(node, parentNode);
-                }
-            }
-        };
+    public getVisitor (transformationStage: TransformationStage): IVisitor | null {
+        switch (transformationStage) {
+            case TransformationStage.Preparing:
+                return {
+                    leave: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
+                        if (parentNode && NodeGuards.isNodeWithComments(node)) {
+                            return this.transformNode(node, parentNode);
+                        }
+                    }
+                };
+
+            default:
+                return null;
+        }
     }
 
     /**

+ 184 - 0
src/node-transformers/preparing-transformers/EvaCallExpressionTransformer.ts

@@ -0,0 +1,184 @@
+import { inject, injectable, } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
+
+import * as ESTree from 'estree';
+import jsStringEscape = require('js-string-escape');
+
+import { TStatement } from '../../types/node/TStatement';
+
+import { IOptions } from '../../interfaces/options/IOptions';
+import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
+import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
+
+import { TransformationStage } from '../../enums/node-transformers/TransformationStage';
+
+import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
+import { NodeGuards } from '../../node/NodeGuards';
+import { Nodes } from '../../node/Nodes';
+import { NodeUtils } from '../../node/NodeUtils';
+
+@injectable()
+export class EvalCallExpressionTransformer extends AbstractNodeTransformer {
+    /**
+     * @type {Set <FunctionExpression>}
+     */
+    private readonly evalRootAstHostNodeSet: Set <ESTree.FunctionExpression> = new Set();
+
+    /**
+     * @param {IRandomGenerator} randomGenerator
+     * @param {IOptions} options
+     */
+    constructor (
+        @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        super(randomGenerator, options);
+    }
+
+    /**
+     * @param {Expression | SpreadElement} node
+     * @returns {string | null}
+     */
+    private static extractEvalStringFromCallExpressionArgument (node: ESTree.Expression | ESTree.SpreadElement): string | null {
+        if (NodeGuards.isLiteralNode(node)) {
+            return EvalCallExpressionTransformer
+                .extractEvalStringFromLiteralNode(node);
+        }
+
+        if (NodeGuards.isTemplateLiteralNode(node)) {
+            return EvalCallExpressionTransformer
+                .extractEvalStringFromTemplateLiteralNode(node);
+        }
+
+        return null;
+    }
+
+    /**
+     * @param {Literal} node
+     * @returns {string | null}
+     */
+    private static extractEvalStringFromLiteralNode (node: ESTree.Literal): string | null {
+        return typeof node.value === 'string' ? node.value : null;
+    }
+
+    /**
+     * @param {TemplateLiteral} node
+     * @returns {string | null}
+     */
+    private static extractEvalStringFromTemplateLiteralNode (node: ESTree.TemplateLiteral): string | null {
+        const quasis: ESTree.TemplateElement[] = node.quasis;
+        const allowedQuasisLength: number = 1;
+
+        if (quasis.length !== allowedQuasisLength || node.expressions.length) {
+            return null;
+        }
+
+        return quasis[0].value.cooked;
+    }
+
+    /**
+     * @param {TransformationStage} transformationStage
+     * @returns {IVisitor | null}
+     */
+    public getVisitor (transformationStage: TransformationStage): IVisitor | null {
+        switch (transformationStage) {
+            case TransformationStage.Preparing:
+                return {
+                    enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
+                        if (
+                            parentNode
+                            && NodeGuards.isCallExpressionNode(node)
+                            && NodeGuards.isIdentifierNode(node.callee)
+                            && node.callee.name === 'eval'
+                        ) {
+                            return this.transformNode(node, parentNode);
+                        }
+                    }
+                };
+
+            case TransformationStage.Finalizing:
+                if (!this.evalRootAstHostNodeSet.size) {
+                    return null;
+                }
+
+                return {
+                    leave: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
+                        if (parentNode && this.isEvalRootAstHostNode(node)) {
+                            return this.restoreNode(node, parentNode);
+                        }
+                    }
+                };
+
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * @param {CallExpression} callExpressionNode
+     * @param {Node} parentNode
+     * @returns {Node}
+     */
+    public transformNode (callExpressionNode: ESTree.CallExpression, parentNode: ESTree.Node): ESTree.Node {
+        const callExpressionFirstArgument: ESTree.Expression | ESTree.SpreadElement = callExpressionNode.arguments[0];
+
+        if (!callExpressionFirstArgument) {
+            return callExpressionNode;
+        }
+
+        const evalString: string | null = EvalCallExpressionTransformer
+            .extractEvalStringFromCallExpressionArgument(callExpressionFirstArgument);
+
+        if (!evalString) {
+            return callExpressionNode;
+        }
+
+        let ast: TStatement[];
+
+        // wrapping into try-catch to prevent parsing of incorrect `eval` string
+        try {
+            ast = NodeUtils.convertCodeToStructure(evalString);
+        } catch (e) {
+            return callExpressionNode;
+        }
+
+        /**
+         * we should wrap AST-tree into the parent function expression node (ast root host node).
+         * This function expression node will help to correctly transform AST-tree.
+         */
+        const evalRootAstHostNode: ESTree.FunctionExpression = Nodes
+            .getFunctionExpressionNode([], Nodes.getBlockStatementNode(<any>ast));
+
+        /**
+         * we should store that host node and then extract AST-tree on the `finalizing` stage
+         */
+        this.evalRootAstHostNodeSet.add(evalRootAstHostNode);
+
+        return evalRootAstHostNode;
+    }
+
+    /**
+     * @param {FunctionExpression} evalRootAstHostNode
+     * @param {Node} parentNode
+     * @returns {Node}
+     */
+    public restoreNode (evalRootAstHostNode: ESTree.FunctionExpression, parentNode: ESTree.Node): ESTree.Node {
+        const targetAst: ESTree.Statement[] = evalRootAstHostNode.body.body;
+        const obfuscatedCode: string = NodeUtils.convertStructureToCode(targetAst);
+
+        return Nodes.getCallExpressionNode(
+            Nodes.getIdentifierNode('eval'),
+            [
+                Nodes.getLiteralNode(jsStringEscape(obfuscatedCode))
+            ]
+        );
+    }
+
+    /**
+     * @param {Node} node
+     * @returns {boolean}
+     */
+    private isEvalRootAstHostNode (node: ESTree.Node): node is ESTree.FunctionExpression {
+        return NodeGuards.isFunctionExpressionNode(node) && this.evalRootAstHostNodeSet.has(node);
+    }
+}

+ 15 - 7
src/node-transformers/preparing-transformers/ObfuscatingGuardsTransformer.ts

@@ -11,6 +11,7 @@ import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 
 import { ObfuscatingGuard } from '../../enums/node-transformers/preparing-transformers/obfuscating-guards/ObfuscatingGuard';
+import { TransformationStage } from '../../enums/node-transformers/TransformationStage';
 
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 
@@ -48,14 +49,21 @@ export class ObfuscatingGuardsTransformer extends AbstractNodeTransformer {
     }
 
     /**
-     * @return {IVisitor}
+     * @param {TransformationStage} transformationStage
+     * @returns {IVisitor | null}
      */
-    public getVisitor (): IVisitor {
-        return {
-            enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
-                return this.transformNode(node, parentNode);
-            }
-        };
+    public getVisitor (transformationStage: TransformationStage): IVisitor | null {
+        switch (transformationStage) {
+            case TransformationStage.Preparing:
+                return {
+                    enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
+                        return this.transformNode(node, parentNode);
+                    }
+                };
+
+            default:
+                return null;
+        }
     }
 
     /**

+ 16 - 7
src/node-transformers/preparing-transformers/ParentificationTransformer.ts

@@ -7,6 +7,8 @@ import { IOptions } from '../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
 
+import { TransformationStage } from '../../enums/node-transformers/TransformationStage';
+
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { NodeUtils } from '../../node/NodeUtils';
 
@@ -27,14 +29,21 @@ export class ParentificationTransformer extends AbstractNodeTransformer {
     }
 
     /**
-     * @return {IVisitor}
+     * @param {TransformationStage} transformationStage
+     * @returns {IVisitor | null}
      */
-    public getVisitor (): IVisitor {
-        return {
-            enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
-                return this.transformNode(node, parentNode);
-            }
-        };
+    public getVisitor (transformationStage: TransformationStage): IVisitor | null {
+        switch (transformationStage) {
+            case TransformationStage.Preparing:
+                return {
+                    enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
+                        return this.transformNode(node, parentNode);
+                    }
+                };
+
+            default:
+                return null;
+        }
     }
 
     /**

+ 101 - 74
src/node/NodeAppender.ts

@@ -1,61 +1,59 @@
 import * as ESTree from 'estree';
 
-import { TNodeWithBlockStatement } from '../types/node/TNodeWithBlockStatement';
+import { TNodeWithBlockScope } from '../types/node/TNodeWithBlockScope';
 import { TStatement } from '../types/node/TStatement';
 
 import { IStackTraceData } from '../interfaces/analyzers/stack-trace-analyzer/IStackTraceData';
+import { TNodeWithScope } from '../types/node/TNodeWithScope';
+import { NodeGuards } from './NodeGuards';
 
-/**
- * This class appends node into a first deepest BlockStatement in order of function calls
- *
- * For example:
- *
- * function Foo () {
- *     var baz = function () {
- *
- *     }
- *
- *     baz();
- * }
- *
- * foo();
- *
- * Appends node into block statement of `baz` function expression.
- */
 export class NodeAppender {
     /**
-     * @param {TNodeWithBlockStatement} blockScopeNode
-     * @param {TStatement[]} nodeBodyStatements
+     * @param {TNodeWithScope} scopeNode
+     * @param {TStatement[]} scopeStatements
      */
-    public static appendNode (
-        blockScopeNode: TNodeWithBlockStatement,
-        nodeBodyStatements: TStatement[]
-    ): void {
-        if (!NodeAppender.validateBodyStatements(nodeBodyStatements)) {
-            nodeBodyStatements = [];
+    public static appendNode (scopeNode: TNodeWithScope, scopeStatements: TStatement[]): void {
+        if (!NodeAppender.validateScopeStatements(scopeStatements)) {
+            scopeStatements = [];
         }
 
-        nodeBodyStatements = NodeAppender.parentizeBodyStatementsBeforeAppend(blockScopeNode, nodeBodyStatements);
+        scopeStatements = NodeAppender.parentizeScopeStatementsBeforeAppend(scopeNode, scopeStatements);
 
-        blockScopeNode.body = [
-            ...blockScopeNode.body,
-            ...nodeBodyStatements
-        ];
+        NodeAppender.setScopeNodeStatements(scopeNode, [
+            ...NodeAppender.getScopeNodeStatements(scopeNode),
+            ...scopeStatements
+        ]);
     }
 
     /**
+     * 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
+     *
      * @param {IStackTraceData[]} blockScopeStackTraceData
-     * @param {TNodeWithBlockStatement} blockScopeNode
+     * @param {TNodeWithBlockScope} blockScopeNode
      * @param {TStatement[]} nodeBodyStatements
      * @param {number} index
      */
     public static appendNodeToOptimalBlockScope (
         blockScopeStackTraceData: IStackTraceData[],
-        blockScopeNode: TNodeWithBlockStatement,
+        blockScopeNode: TNodeWithBlockScope,
         nodeBodyStatements: TStatement[],
         index: number = 0
     ): void {
-        let targetBlockScope: TNodeWithBlockStatement;
+        let targetBlockScope: TNodeWithBlockScope;
 
         if (!blockScopeStackTraceData.length) {
             targetBlockScope = blockScopeNode;
@@ -96,70 +94,99 @@ export class NodeAppender {
     }
 
     /**
-     * @param {TNodeWithBlockStatement} blockScopeNode
-     * @param {TStatement[]} nodeBodyStatements
+     * @param {TNodeWithScope} scopeNode
+     * @param {TStatement[]} scopeStatements
+     * @param {Node} targetStatement
+     */
+    public static insertNodeAfter (scopeNode: TNodeWithScope, scopeStatements: TStatement[], targetStatement: ESTree.Statement): void {
+        const indexInScopeStatement: number = NodeAppender
+            .getScopeNodeStatements(scopeNode)
+            .indexOf(targetStatement);
+
+        NodeAppender.insertNodeAtIndex(scopeNode, scopeStatements, indexInScopeStatement + 1);
+    }
+
+    /**
+     * @param {TNodeWithScope} scopeNode
+     * @param {TStatement[]} scopeStatements
      * @param {number} index
      */
-    public static insertNodeAtIndex (
-        blockScopeNode: TNodeWithBlockStatement,
-        nodeBodyStatements: TStatement[],
-        index: number
-    ): void {
-        if (!NodeAppender.validateBodyStatements(nodeBodyStatements)) {
-            nodeBodyStatements = [];
+    public static insertNodeAtIndex (scopeNode: TNodeWithScope, scopeStatements: TStatement[], index: number): void {
+        if (!NodeAppender.validateScopeStatements(scopeStatements)) {
+            scopeStatements = [];
         }
 
-        nodeBodyStatements = NodeAppender.parentizeBodyStatementsBeforeAppend(blockScopeNode, nodeBodyStatements);
+        scopeStatements = NodeAppender.parentizeScopeStatementsBeforeAppend(scopeNode, scopeStatements);
 
-        blockScopeNode.body = [
-            ...blockScopeNode.body.slice(0, index),
-            ...nodeBodyStatements,
-            ...blockScopeNode.body.slice(index)
-        ];
+        NodeAppender.setScopeNodeStatements(scopeNode, [
+            ...NodeAppender.getScopeNodeStatements(scopeNode).slice(0, index),
+            ...scopeStatements,
+            ...NodeAppender.getScopeNodeStatements(scopeNode).slice(index)
+        ]);
     }
 
     /**
-     * @param {TNodeWithBlockStatement} blockScopeNode
-     * @param {TStatement[]} nodeBodyStatements
+     * @param {TNodeWithScope} scopeNode
+     * @param {TStatement[]} scopeStatements
      */
-    public static prependNode (
-        blockScopeNode: TNodeWithBlockStatement,
-        nodeBodyStatements: TStatement[]
-    ): void {
-        if (!NodeAppender.validateBodyStatements(nodeBodyStatements)) {
-            nodeBodyStatements = [];
+    public static prependNode (scopeNode: TNodeWithScope, scopeStatements: TStatement[]): void {
+        if (!NodeAppender.validateScopeStatements(scopeStatements)) {
+            scopeStatements = [];
         }
 
-        nodeBodyStatements = NodeAppender.parentizeBodyStatementsBeforeAppend(blockScopeNode, nodeBodyStatements);
+        scopeStatements = NodeAppender.parentizeScopeStatementsBeforeAppend(scopeNode, scopeStatements);
 
-        blockScopeNode.body = [
-            ...nodeBodyStatements,
-            ...blockScopeNode.body,
-        ];
+        NodeAppender.setScopeNodeStatements(scopeNode, [
+            ...scopeStatements,
+            ...NodeAppender.getScopeNodeStatements(scopeNode),
+        ]);
     }
 
     /**
-     * @param {TNodeWithBlockStatement} blockScopeNode
-     * @param {TStatement[]} nodeBodyStatements
+     * @param {TNodeWithScope} scopeNode
+     * @returns {TStatement[]}
+     */
+    private static getScopeNodeStatements (scopeNode: TNodeWithScope): TStatement[] {
+        if (NodeGuards.isSwitchCaseNode(scopeNode)) {
+            return scopeNode.consequent;
+        }
+
+        return scopeNode.body;
+    }
+
+    /**
+     * @param {TNodeWithScope} scopeNode
+     * @param {TStatement[]} scopeStatements
      * @returns {TStatement[]}
      */
-    private static parentizeBodyStatementsBeforeAppend (
-        blockScopeNode: TNodeWithBlockStatement,
-        nodeBodyStatements: TStatement[]
-    ): TStatement[] {
-        nodeBodyStatements.forEach((statement: TStatement) => {
-            statement.parentNode = blockScopeNode;
+    private static parentizeScopeStatementsBeforeAppend (scopeNode: TNodeWithScope, scopeStatements: TStatement[]): TStatement[] {
+        scopeStatements.forEach((statement: TStatement) => {
+            statement.parentNode = scopeNode;
         });
 
-        return nodeBodyStatements;
+        return scopeStatements;
     }
 
     /**
-     * @param {TStatement[]} nodeBodyStatements
+     * @param {TNodeWithScope} scopeNode
+     * @param {TStatement[]} statements
+     */
+    private static setScopeNodeStatements (scopeNode: TNodeWithScope, statements: TStatement[]): void {
+        if (NodeGuards.isSwitchCaseNode(scopeNode)) {
+            scopeNode.consequent = <ESTree.Statement[]>statements;
+
+            return;
+        }
+
+        scopeNode.body = statements;
+    }
+
+    /**
+     * @param {TStatement[]} scopeStatement
      * @returns {boolean}
      */
-    private static validateBodyStatements (nodeBodyStatements: TStatement[]): boolean {
-        return nodeBodyStatements.every((statementNode: TStatement) => {
+    private static validateScopeStatements (scopeStatement: TStatement[]): boolean {
+        return scopeStatement.every((statementNode: TStatement) => {
             return !!statementNode && statementNode.hasOwnProperty('type');
         });
     }

+ 53 - 4
src/node/NodeGuards.ts

@@ -1,10 +1,29 @@
 import * as ESTree from 'estree';
 
-import { TNodeWithBlockStatement } from '../types/node/TNodeWithBlockStatement';
+import { TNodeWithBlockScope } from '../types/node/TNodeWithBlockScope';
+import { TNodeWithScope } from '../types/node/TNodeWithScope';
 
 import { NodeType } from '../enums/node/NodeType';
 
 export class NodeGuards {
+    /**
+     * @type {string[]}
+     */
+    private static readonly nodesWithBlockScope: string[] = [
+        NodeType.ArrowFunctionExpression,
+        NodeType.FunctionDeclaration,
+        NodeType.FunctionExpression,
+        NodeType.MethodDefinition,
+    ];
+
+    /**
+     * @param {Node} node
+     * @returns {boolean}
+     */
+    public static isArrayPatternNode (node: ESTree.Node): node is ESTree.ArrayPattern {
+        return node.type === NodeType.ArrayPattern;
+    }
+
     /**
      * @param {Node} node
      * @returns {boolean}
@@ -170,12 +189,26 @@ export class NodeGuards {
         return object && !object.type !== undefined;
     }
 
+    /**
+     * @param {Node} node
+     * @param {Node} parentNode
+     * @returns {boolean}
+     */
+    public static isNodeHasBlockScope (node: ESTree.Node, parentNode: ESTree.Node): node is TNodeWithBlockScope {
+        return NodeGuards.isProgramNode(node) || (
+            NodeGuards.isBlockStatementNode(node)
+            && NodeGuards.nodesWithBlockScope.includes(parentNode.type)
+        );
+    }
+
     /**
      * @param {Node} node
      * @returns {boolean}
      */
-    public static isNodeHasBlockStatement (node: ESTree.Node): node is TNodeWithBlockStatement {
-        return Array.isArray((<TNodeWithBlockStatement>node).body);
+    public static isNodeHasScope (node: ESTree.Node): node is TNodeWithScope {
+        return NodeGuards.isProgramNode(node)
+            || NodeGuards.isBlockStatementNode(node)
+            || NodeGuards.isSwitchCaseNode(node);
     }
 
     /**
@@ -233,7 +266,7 @@ export class NodeGuards {
             parentNode.key === node;
         const parentNodeIsMemberExpressionNode: boolean = (
             NodeGuards.isMemberExpressionNode(parentNode) &&
-            parentNode.computed === false &&
+            !parentNode.computed &&
             parentNode.property === node
         );
         const parentNodeIsMethodDefinitionNode: boolean = NodeGuards.isMethodDefinitionNode(parentNode) &&
@@ -246,6 +279,14 @@ export class NodeGuards {
             !isLabelIdentifierNode;
     }
 
+    /**
+     * @param {Node} node
+     * @returns {boolean}
+     */
+    public static isRestElementNode (node: ESTree.Node): node is ESTree.RestElement {
+        return node.type === NodeType.RestElement;
+    }
+
     /**
      * @param {Node} node
      * @returns {boolean}
@@ -262,6 +303,14 @@ export class NodeGuards {
         return node.type === NodeType.Super;
     }
 
+    /**
+     * @param {Node} node
+     * @returns {boolean}
+     */
+    public static isSwitchCaseNode (node: ESTree.Node): node is ESTree.SwitchCase {
+        return node.type === NodeType.SwitchCase;
+    }
+
     /**
      * @param {Node} node
      * @returns {boolean}

+ 89 - 110
src/node/NodeUtils.ts

@@ -3,26 +3,14 @@ import * as esprima from 'esprima';
 import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 
-import { TNodeWithBlockStatement } from '../types/node/TNodeWithBlockStatement';
+import { TNodeWithBlockScope } from '../types/node/TNodeWithBlockScope';
+import { TNodeWithScope } from '../types/node/TNodeWithScope';
 import { TObject } from '../types/TObject';
 import { TStatement } from '../types/node/TStatement';
 
-import { NodeType } from '../enums/node/NodeType';
-
 import { NodeGuards } from './NodeGuards';
 
 export class NodeUtils {
-    /**
-     * @type {string[]}
-     */
-    private static readonly nodesWithBlockScope: string[] = [
-        NodeType.ArrowFunctionExpression,
-        NodeType.FunctionDeclaration,
-        NodeType.FunctionExpression,
-        NodeType.MethodDefinition,
-        NodeType.Program
-    ];
-
     /**
      * @param {T} astTree
      * @returns {T}
@@ -47,45 +35,7 @@ export class NodeUtils {
      * @returns {T}
      */
     public static clone <T extends ESTree.Node = ESTree.Node> (astTree: T): T {
-        /**
-         * @param {T} node
-         * @returns {T}
-         */
-        const cloneRecursive: (node: T) => T = (node: T) => {
-            if (node === null) {
-                return node;
-            }
-
-            const copy: TObject = {};
-
-            Object
-                .keys(node)
-                .forEach((property: string): void => {
-                    if (property === 'parentNode') {
-                        return;
-                    }
-
-                    const value: any = (<TObject>node)[property];
-
-                    let clonedValue: any | null;
-
-                    if (value === null || value instanceof RegExp) {
-                        clonedValue = value;
-                    } else if (Array.isArray(value)) {
-                        clonedValue = value.map(cloneRecursive);
-                    } else if (typeof value === 'object') {
-                        clonedValue = cloneRecursive(value);
-                    } else {
-                        clonedValue = value;
-                    }
-
-                    copy[property] = clonedValue;
-                });
-
-            return <T>copy;
-        };
-
-        return NodeUtils.parentize(cloneRecursive(astTree));
+        return NodeUtils.parentize(NodeUtils.cloneRecursive(astTree));
     }
 
     /**
@@ -114,76 +64,29 @@ export class NodeUtils {
     }
 
     /**
-     * @param {NodeGuards} node
-     * @param {number} index
-     * @returns {NodeGuards}
+     * @param {Node} targetNode
+     * @returns {TNodeWithBlockScope[]}
      */
-    public static getBlockStatementNodeByIndex (node: ESTree.Node, index: number = 0): ESTree.Node {
-        if (!NodeGuards.isNodeHasBlockStatement(node)) {
-            throw new TypeError('The specified node have no a block-statement');
-        }
-
-        if (node.body[index] === undefined) {
-            throw new ReferenceError(`Wrong index \`${index}\`. Block-statement body length is \`${node.body.length}\``);
-        }
-
-        return node.body[index];
-    }
-
-    /**
-     * @param {NodeGuards} node
-     * @param {TNodeWithBlockStatement[]} blockScopes
-     * @returns {TNodeWithBlockStatement[]}
-     */
-    public static getBlockScopesOfNode (node: ESTree.Node, blockScopes: TNodeWithBlockStatement[] = []): TNodeWithBlockStatement[] {
-        const parentNode: ESTree.Node | undefined = node.parentNode;
-
-        if (!parentNode) {
-            throw new ReferenceError('`parentNode` property of given node is `undefined`');
-        }
-
-        if (NodeGuards.isBlockStatementNode(parentNode)) {
-            if (!parentNode.parentNode) {
-                throw new ReferenceError('`parentNode` property of `parentNode` of given node is `undefined`');
-            }
-
-            if (NodeUtils.nodesWithBlockScope.includes(parentNode.parentNode.type)) {
-                blockScopes.push(parentNode);
-            }
-        }
-
-        if (node !== parentNode) {
-            return NodeUtils.getBlockScopesOfNode(parentNode, blockScopes);
-        }
-
-        if (NodeGuards.isNodeHasBlockStatement(parentNode)) {
-            blockScopes.push(parentNode);
-        }
-
-        return blockScopes;
+    public static getBlockScopesOfNode (targetNode: ESTree.Node): TNodeWithBlockScope[] {
+        return NodeUtils.getBlockScopesOfNodeRecursive(targetNode);
     }
 
     /**
      * @param {NodeGuards} node
-     * @param {number} depth
-     * @returns {number}
+     * @returns {TNodeWithScope}
      */
-    public static getNodeBlockScopeDepth (node: ESTree.Node, depth: number = 0): number {
+    public static getScopeOfNode (node: ESTree.Node): TNodeWithScope {
         const parentNode: ESTree.Node | undefined = node.parentNode;
 
         if (!parentNode) {
             throw new ReferenceError('`parentNode` property of given node is `undefined`');
         }
 
-        if (NodeGuards.isProgramNode(parentNode)) {
-            return depth;
-        }
-
-        if (NodeGuards.isBlockStatementNode(node) && NodeUtils.nodesWithBlockScope.includes(parentNode.type)) {
-            return NodeUtils.getNodeBlockScopeDepth(parentNode, ++depth);
+        if (!NodeGuards.isNodeHasScope(parentNode)) {
+            return NodeUtils.getScopeOfNode(parentNode);
         }
 
-        return NodeUtils.getNodeBlockScopeDepth(parentNode, depth);
+        return parentNode;
     }
 
     /**
@@ -203,7 +106,7 @@ export class NodeUtils {
      * @returns {T}
      */
     public static parentize <T extends ESTree.Node = ESTree.Node> (astTree: T): T {
-        estraverse.traverse(astTree, {
+        estraverse.replace(astTree, {
             enter: NodeUtils.parentizeNode
         });
 
@@ -221,4 +124,80 @@ export class NodeUtils {
 
         return node;
     }
+
+    /**
+     * @param {T} node
+     * @returns {T}
+     */
+    private static cloneRecursive <T> (node: T): T {
+        if (node === null) {
+            return node;
+        }
+
+        const copy: TObject = {};
+
+        Object
+            .keys(node)
+            .forEach((property: string): void => {
+                if (property === 'parentNode') {
+                    return;
+                }
+
+                const value: any = (<TObject>node)[property];
+
+                let clonedValue: any | null;
+
+                if (value === null || value instanceof RegExp) {
+                    clonedValue = value;
+                } else if (Array.isArray(value)) {
+                    clonedValue = value.map(NodeUtils.cloneRecursive);
+                } else if (typeof value === 'object') {
+                    clonedValue = NodeUtils.cloneRecursive(value);
+                } else {
+                    clonedValue = value;
+                }
+
+                copy[property] = clonedValue;
+            });
+
+        return <T>copy;
+    }
+
+    /**
+     * @param {Node} node
+     * @param {TNodeWithBlockScope[]} blockScopes
+     * @param {number} depth
+     * @returns {TNodeWithBlockScope[]}
+     */
+    private static getBlockScopesOfNodeRecursive (
+        node: ESTree.Node,
+        blockScopes: TNodeWithBlockScope[] = [],
+        depth: number = 0
+    ): TNodeWithBlockScope[] {
+        const parentNode: ESTree.Node | undefined = node.parentNode;
+
+        if (!parentNode) {
+            throw new ReferenceError('`parentNode` property of given node is `undefined`');
+        }
+
+        if (
+            /**
+             * we can add program node instantly
+             */
+            NodeGuards.isProgramNode(node) ||
+            /**
+             * we shouldn't add to the array input node that is node with block scope itself
+             * so, on depth 0 we will skip push to the array of block scopes
+             */
+            (depth && NodeGuards.isNodeHasBlockScope(node, parentNode))
+        ) {
+            blockScopes.push(node);
+        }
+
+        if (node !== parentNode) {
+            return NodeUtils.getBlockScopesOfNodeRecursive(parentNode, blockScopes, ++depth);
+        }
+
+        return blockScopes;
+    }
 }

+ 24 - 0
src/node/Nodes.ts

@@ -305,6 +305,30 @@ export class Nodes {
         };
     }
 
+    /**
+     * @param {Expression} key
+     * @param {FunctionExpression} value
+     * @param {"constructor" | "method" | "get" | "set"} kind
+     * @param {boolean} computed
+     * @returns {MethodDefinition}
+     */
+    public static getMethodDefinitionNode (
+        key: ESTree.Expression,
+        value: ESTree.FunctionExpression,
+        kind: 'constructor' | 'method' | 'get' | 'set',
+        computed: boolean,
+    ): ESTree.MethodDefinition {
+        return {
+            type: NodeType.MethodDefinition,
+            key,
+            value,
+            kind,
+            computed,
+            static: false,
+            obfuscatedNode: false
+        };
+    }
+
     /**
      * @param {Property[]} properties
      * @returns {ObjectExpression}

+ 6 - 0
src/options/Options.ts

@@ -206,6 +206,12 @@ export class Options implements IOptions {
     @IsIn([ObfuscationTarget.Browser, ObfuscationTarget.Extension, ObfuscationTarget.Node])
     public readonly target: ObfuscationTarget;
 
+    /**
+     * @type {boolean}
+     */
+    @IsBoolean()
+    public readonly transformObjectKeys: boolean;
+
     /**
      * @type {boolean}
      */

+ 1 - 0
src/options/presets/Default.ts

@@ -30,5 +30,6 @@ export const DEFAULT_PRESET: TInputOptions = Object.freeze({
     stringArrayEncoding: false,
     stringArrayThreshold: 0.75,
     target: ObfuscationTarget.Browser,
+    transformObjectKeys: false,
     unicodeEscapeSequence: false
 });

+ 2 - 1
src/options/presets/NoCustomNodes.ts

@@ -4,7 +4,7 @@ import { IdentifierNamesGenerator } from '../../enums/generators/identifier-name
 import { ObfuscationTarget } from '../../enums/ObfuscationTarget';
 import { SourceMapMode } from '../../enums/source-map/SourceMapMode';
 
-export const NO_CUSTOM_NODES_PRESET: TInputOptions = Object.freeze({
+export const NO_ADDITIONAL_NODES_PRESET: TInputOptions = Object.freeze({
     compact: true,
     controlFlowFlattening: false,
     controlFlowFlatteningThreshold: 0,
@@ -29,5 +29,6 @@ export const NO_CUSTOM_NODES_PRESET: TInputOptions = Object.freeze({
     stringArrayEncoding: false,
     stringArrayThreshold: 0,
     target: ObfuscationTarget.Browser,
+    transformObjectKeys: false,
     unicodeEscapeSequence: false
 });

+ 5 - 0
src/types/container/custom-nodes/TDeadNodeInjectionCustomNodeFactory.d.ts

@@ -0,0 +1,5 @@
+import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
+
+import { DeadCodeInjectionCustomNode } from '../../../enums/custom-nodes/DeadCodeInjectionCustomNode';
+
+export type TDeadNodeInjectionCustomNodeFactory = (deadCodeInjectionCustomNodeName: DeadCodeInjectionCustomNode) => ICustomNode;

+ 3 - 0
src/types/node/TNodeWithBlockScope.d.ts

@@ -0,0 +1,3 @@
+import * as ESTree from 'estree';
+
+export type TNodeWithBlockScope = ESTree.Program | ESTree.BlockStatement;

+ 0 - 3
src/types/node/TNodeWithBlockStatement.d.ts

@@ -1,3 +0,0 @@
-import * as ESTree from 'estree';
-
-export type TNodeWithBlockStatement = ESTree.BlockStatement | ESTree.Program;

+ 3 - 0
src/types/node/TNodeWithScope.d.ts

@@ -0,0 +1,3 @@
+import * as ESTree from 'estree';
+
+export type TNodeWithScope = ESTree.Program | ESTree.BlockStatement | ESTree.SwitchCase;

+ 7 - 25
test/dev/dev.ts

@@ -1,5 +1,5 @@
 'use strict';
-import { NO_CUSTOM_NODES_PRESET } from '../../src/options/presets/NoCustomNodes';
+import { NO_ADDITIONAL_NODES_PRESET } from '../../src/options/presets/NoCustomNodes';
 
 (function () {
     const JavaScriptObfuscator: any = require('../../index');
@@ -7,34 +7,16 @@ import { NO_CUSTOM_NODES_PRESET } from '../../src/options/presets/NoCustomNodes'
     let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
         `
         (function(){
-            if (true) {
-                var foo = function () {
-                    console.log('abc');
-                };
-                var bar = function () {
-                    console.log('def');
-                };
-                var baz = function () {
-                    console.log('ghi');
-                };
-                var bark = function () {
-                    console.log('jkl');
-                };
-                var hawk = function () {
-                    console.log('mno');
-                };
-            
-                foo();
-                bar();
-                baz();
-                bark();
-                hawk();
+            function foo () {
+                eval('var s = 1;');
             }
+        
+            foo();
         })();
         `,
         {
-            ...NO_CUSTOM_NODES_PRESET,
-            compact: false,
+            ...NO_ADDITIONAL_NODES_PRESET,
+            compact: false
         }
     ).getObfuscatedCode();
 

+ 11 - 11
test/functional-tests/analyzers/stack-trace-analyzer/StackTraceAnalyzer.spec.ts

@@ -7,7 +7,7 @@ import * as ESTree from 'estree';
 
 import { assert } from 'chai';
 
-import { TNodeWithBlockStatement } from '../../../../src/types/node/TNodeWithBlockStatement';
+import { TNodeWithBlockScope } from '../../../../src/types/node/TNodeWithBlockScope';
 
 import { IInversifyContainerFacade } from '../../../../src/interfaces/container/IInversifyContainerFacade';
 import { IStackTraceAnalyzer } from '../../../../src/interfaces/analyzers/stack-trace-analyzer/IStackTraceAnalyzer';
@@ -165,7 +165,7 @@ describe('StackTraceAnalyzer', () => {
         describe('variant #1: basic-1', () => {
             before(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/basic-1.js');
-                const astTree: TNodeWithBlockStatement = Nodes.getProgramNode(
+                const astTree: TNodeWithBlockScope = Nodes.getProgramNode(
                     NodeUtils.convertCodeToStructure(code)
                 );
 
@@ -215,7 +215,7 @@ describe('StackTraceAnalyzer', () => {
         describe('variant #2: basic-2', () => {
             before(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/basic-2.js');
-                const astTree: TNodeWithBlockStatement = Nodes.getProgramNode(
+                const astTree: TNodeWithBlockScope = Nodes.getProgramNode(
                     NodeUtils.convertCodeToStructure(code)
                 );
 
@@ -254,7 +254,7 @@ describe('StackTraceAnalyzer', () => {
         describe('variant #3: deep conditions nesting', () => {
             before(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/deep-conditions-nesting.js');
-                const astTree: TNodeWithBlockStatement = Nodes.getProgramNode(
+                const astTree: TNodeWithBlockScope = Nodes.getProgramNode(
                     NodeUtils.convertCodeToStructure(code)
                 );
 
@@ -293,7 +293,7 @@ describe('StackTraceAnalyzer', () => {
         describe('variant #4: call before declaration', () => {
             before(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/call-before-declaration.js');
-                const astTree: TNodeWithBlockStatement = Nodes.getProgramNode(
+                const astTree: TNodeWithBlockScope = Nodes.getProgramNode(
                     NodeUtils.convertCodeToStructure(code)
                 );
 
@@ -316,7 +316,7 @@ describe('StackTraceAnalyzer', () => {
         describe('variant #5: call expression of object member #1', () => {
             before(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/call-expression-of-object-member-1.js');
-                const astTree: TNodeWithBlockStatement = Nodes.getProgramNode(
+                const astTree: TNodeWithBlockScope = Nodes.getProgramNode(
                     NodeUtils.convertCodeToStructure(code)
                 );
 
@@ -375,7 +375,7 @@ describe('StackTraceAnalyzer', () => {
         describe('variant #5: call expression of object member #2', () => {
             before(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/call-expression-of-object-member-2.js');
-                const astTree: TNodeWithBlockStatement = Nodes.getProgramNode(
+                const astTree: TNodeWithBlockScope = Nodes.getProgramNode(
                     NodeUtils.convertCodeToStructure(code)
                 );
 
@@ -403,7 +403,7 @@ describe('StackTraceAnalyzer', () => {
         describe('variant #6: no call expressions', () => {
             before(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/no-call-expressions.js');
-                const astTree: TNodeWithBlockStatement = Nodes.getProgramNode(
+                const astTree: TNodeWithBlockScope = Nodes.getProgramNode(
                     NodeUtils.convertCodeToStructure(code)
                 );
 
@@ -420,7 +420,7 @@ describe('StackTraceAnalyzer', () => {
         describe('variant #7: only call expression', () => {
             before(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/only-call-expression.js');
-                const astTree: TNodeWithBlockStatement = Nodes.getProgramNode(
+                const astTree: TNodeWithBlockScope = Nodes.getProgramNode(
                     NodeUtils.convertCodeToStructure(code)
                 );
 
@@ -437,7 +437,7 @@ describe('StackTraceAnalyzer', () => {
         describe('variant #8: self-invoking functions', () => {
             before(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/self-invoking-functions.js');
-                const astTree: TNodeWithBlockStatement = Nodes.getProgramNode(
+                const astTree: TNodeWithBlockScope = Nodes.getProgramNode(
                     NodeUtils.convertCodeToStructure(code)
                 );
 
@@ -472,7 +472,7 @@ describe('StackTraceAnalyzer', () => {
         describe('variant #9: no recursion', () => {
             before(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/no-recursion.js');
-                const astTree: TNodeWithBlockStatement = Nodes.getProgramNode(
+                const astTree: TNodeWithBlockScope = Nodes.getProgramNode(
                     NodeUtils.convertCodeToStructure(code)
                 );
 

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

@@ -2,7 +2,7 @@ import { assert } from 'chai';
 
 import { IObfuscationResult } from '../../../../src/interfaces/IObfuscationResult';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../../../src/options/presets/NoCustomNodes';
+import { NO_ADDITIONAL_NODES_PRESET } from '../../../../src/options/presets/NoCustomNodes';
 
 import { readFileAsString } from '../../../helpers/readFileAsString';
 
@@ -21,7 +21,7 @@ describe('ConsoleOutputDisableExpressionNode', () => {
             const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                 code,
                 {
-                    ...NO_CUSTOM_NODES_PRESET,
+                    ...NO_ADDITIONAL_NODES_PRESET,
                     disableConsoleOutput: true
                 }
             );
@@ -50,7 +50,7 @@ describe('ConsoleOutputDisableExpressionNode', () => {
             const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                 code,
                 {
-                    ...NO_CUSTOM_NODES_PRESET,
+                    ...NO_ADDITIONAL_NODES_PRESET,
                     disableConsoleOutput: false
                 }
             );

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

@@ -2,7 +2,7 @@ import { assert } from 'chai';
 
 import { IObfuscationResult } from '../../../../src/interfaces/IObfuscationResult';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../../../src/options/presets/NoCustomNodes';
+import { NO_ADDITIONAL_NODES_PRESET } from '../../../../src/options/presets/NoCustomNodes';
 
 import { readFileAsString } from '../../../helpers/readFileAsString';
 
@@ -19,7 +19,7 @@ describe('DomainLockNode', () => {
             const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                 code,
                 {
-                    ...NO_CUSTOM_NODES_PRESET,
+                    ...NO_ADDITIONAL_NODES_PRESET,
                     domainLock: ['.example.com']
                 }
             );
@@ -40,7 +40,7 @@ describe('DomainLockNode', () => {
             const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                 code,
                 {
-                    ...NO_CUSTOM_NODES_PRESET,
+                    ...NO_ADDITIONAL_NODES_PRESET,
                     domainLock: []
                 }
             );

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

@@ -2,7 +2,7 @@ import { assert } from 'chai';
 
 import { IObfuscationResult } from '../../../../src/interfaces/IObfuscationResult';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../../../src/options/presets/NoCustomNodes';
+import { NO_ADDITIONAL_NODES_PRESET } from '../../../../src/options/presets/NoCustomNodes';
 
 import { readFileAsString } from '../../../helpers/readFileAsString';
 
@@ -20,7 +20,7 @@ describe('StringArrayCallsWrapper', () => {
             let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                 code,
                 {
-                    ...NO_CUSTOM_NODES_PRESET,
+                    ...NO_ADDITIONAL_NODES_PRESET,
                     stringArray: true,
                     stringArrayThreshold: 1
                 }
@@ -43,7 +43,7 @@ describe('StringArrayCallsWrapper', () => {
             let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                 code,
                 {
-                    ...NO_CUSTOM_NODES_PRESET,
+                    ...NO_ADDITIONAL_NODES_PRESET,
                     stringArray: false
                 }
             );

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

@@ -2,7 +2,7 @@ import { assert } from 'chai';
 
 import { IObfuscationResult } from '../../../../src/interfaces/IObfuscationResult';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../../../src/options/presets/NoCustomNodes';
+import { NO_ADDITIONAL_NODES_PRESET } from '../../../../src/options/presets/NoCustomNodes';
 
 import { readFileAsString } from '../../../helpers/readFileAsString';
 
@@ -20,7 +20,7 @@ describe('StringArrayNode', () => {
             let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                 code,
                 {
-                    ...NO_CUSTOM_NODES_PRESET,
+                    ...NO_ADDITIONAL_NODES_PRESET,
                     stringArray: true,
                     stringArrayThreshold: 1
                 }
@@ -43,7 +43,7 @@ describe('StringArrayNode', () => {
             let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                 code,
                 {
-                    ...NO_CUSTOM_NODES_PRESET,
+                    ...NO_ADDITIONAL_NODES_PRESET,
                     stringArray: false
                 }
             );

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

@@ -2,7 +2,7 @@ import { assert } from 'chai';
 
 import { IObfuscationResult } from '../../../../src/interfaces/IObfuscationResult';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../../../src/options/presets/NoCustomNodes';
+import { NO_ADDITIONAL_NODES_PRESET } from '../../../../src/options/presets/NoCustomNodes';
 
 import { readFileAsString } from '../../../helpers/readFileAsString';
 
@@ -20,7 +20,7 @@ describe('StringArrayRotateFunctionNode', () => {
             let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                 code,
                 {
-                    ...NO_CUSTOM_NODES_PRESET,
+                    ...NO_ADDITIONAL_NODES_PRESET,
                     rotateStringArray: true,
                     stringArray: true,
                     stringArrayThreshold: 1
@@ -44,7 +44,7 @@ describe('StringArrayRotateFunctionNode', () => {
             let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                 code,
                 {
-                    ...NO_CUSTOM_NODES_PRESET,
+                    ...NO_ADDITIONAL_NODES_PRESET,
                     rotateStringArray: false,
                     stringArray: true,
                     stringArrayThreshold: 1

+ 10 - 9
test/functional-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts

@@ -7,7 +7,7 @@ import { StringArrayEncoding } from '../../../src/enums/StringArrayEncoding';
 
 import { JavaScriptObfuscator } from '../../../src/JavaScriptObfuscatorFacade';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../../src/options/presets/NoCustomNodes';
+import { NO_ADDITIONAL_NODES_PRESET } from '../../../src/options/presets/NoCustomNodes';
 
 import { IdentifierNamesGenerator } from '../../../src/enums/generators/identifier-names-generators/IdentifierNamesGenerator';
 
@@ -26,7 +26,7 @@ describe('JavaScriptObfuscator', () => {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
-                        ...NO_CUSTOM_NODES_PRESET
+                        ...NO_ADDITIONAL_NODES_PRESET
                     }
                 );
 
@@ -91,7 +91,7 @@ describe('JavaScriptObfuscator', () => {
                     const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                         code,
                         {
-                            ...NO_CUSTOM_NODES_PRESET,
+                            ...NO_ADDITIONAL_NODES_PRESET,
                             sourceMap: true
                         }
                     );
@@ -120,7 +120,7 @@ describe('JavaScriptObfuscator', () => {
                     const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                         code,
                         {
-                            ...NO_CUSTOM_NODES_PRESET,
+                            ...NO_ADDITIONAL_NODES_PRESET,
                             sourceMap: true,
                             sourceMapMode: SourceMapMode.Inline
                         }
@@ -195,7 +195,7 @@ describe('JavaScriptObfuscator', () => {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
-                        ...NO_CUSTOM_NODES_PRESET
+                        ...NO_ADDITIONAL_NODES_PRESET
                     }
                 );
 
@@ -217,7 +217,7 @@ describe('JavaScriptObfuscator', () => {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
-                        ...NO_CUSTOM_NODES_PRESET
+                        ...NO_ADDITIONAL_NODES_PRESET
                     }
                 );
 
@@ -240,7 +240,7 @@ describe('JavaScriptObfuscator', () => {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
-                        ...NO_CUSTOM_NODES_PRESET,
+                        ...NO_ADDITIONAL_NODES_PRESET,
                         stringArray: true,
                         stringArrayThreshold: 1
                     }
@@ -269,7 +269,7 @@ describe('JavaScriptObfuscator', () => {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
-                        ...NO_CUSTOM_NODES_PRESET,
+                        ...NO_ADDITIONAL_NODES_PRESET,
                         stringArray: true,
                         stringArrayThreshold: 1
                     }
@@ -438,7 +438,7 @@ describe('JavaScriptObfuscator', () => {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
-                        ...NO_CUSTOM_NODES_PRESET
+                        ...NO_ADDITIONAL_NODES_PRESET
                     }
                 );
 
@@ -494,6 +494,7 @@ describe('JavaScriptObfuscator', () => {
                         stringArray: true,
                         stringArrayEncoding: StringArrayEncoding.Rc4,
                         stringArrayThreshold: 1,
+                        transformObjectKeys: true,
                         unicodeEscapeSequence: false
                     }
                 );

+ 16 - 16
test/functional-tests/node-transformers/control-flow-transformers/block-statement-control-flow-transformer/BlockStatementControlFlowTransformer.spec.ts

@@ -2,7 +2,7 @@ import { assert } from 'chai';
 
 import { IObfuscationResult } from '../../../../../src/interfaces/IObfuscationResult';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
+import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
 
 import { getRegExpMatch } from '../../../../helpers/getRegExpMatch';
 import { readFileAsString } from '../../../../helpers/readFileAsString';
@@ -29,7 +29,7 @@ describe('BlockStatementControlFlowTransformer', function () {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
-                        ...NO_CUSTOM_NODES_PRESET,
+                        ...NO_ADDITIONAL_NODES_PRESET,
                         controlFlowFlattening: true,
                         controlFlowFlatteningThreshold: 1
                     }
@@ -117,7 +117,7 @@ describe('BlockStatementControlFlowTransformer', function () {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
-                        ...NO_CUSTOM_NODES_PRESET,
+                        ...NO_ADDITIONAL_NODES_PRESET,
                         controlFlowFlattening: true,
                         controlFlowFlatteningThreshold: 1,
                         unicodeEscapeSequence: false
@@ -208,7 +208,7 @@ describe('BlockStatementControlFlowTransformer', function () {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
-                        ...NO_CUSTOM_NODES_PRESET,
+                        ...NO_ADDITIONAL_NODES_PRESET,
                         controlFlowFlattening: true,
                         controlFlowFlatteningThreshold: 1
                     }
@@ -232,7 +232,7 @@ describe('BlockStatementControlFlowTransformer', function () {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
-                        ...NO_CUSTOM_NODES_PRESET,
+                        ...NO_ADDITIONAL_NODES_PRESET,
                         controlFlowFlattening: true,
                         controlFlowFlatteningThreshold: 1
                     }
@@ -256,7 +256,7 @@ describe('BlockStatementControlFlowTransformer', function () {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
-                        ...NO_CUSTOM_NODES_PRESET,
+                        ...NO_ADDITIONAL_NODES_PRESET,
                         controlFlowFlattening: true,
                         controlFlowFlatteningThreshold: 1
                     }
@@ -280,7 +280,7 @@ describe('BlockStatementControlFlowTransformer', function () {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
-                        ...NO_CUSTOM_NODES_PRESET,
+                        ...NO_ADDITIONAL_NODES_PRESET,
                         controlFlowFlattening: true,
                         controlFlowFlatteningThreshold: 1
                     }
@@ -304,7 +304,7 @@ describe('BlockStatementControlFlowTransformer', function () {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
-                        ...NO_CUSTOM_NODES_PRESET,
+                        ...NO_ADDITIONAL_NODES_PRESET,
                         controlFlowFlattening: true,
                         controlFlowFlatteningThreshold: 1
                     }
@@ -331,7 +331,7 @@ describe('BlockStatementControlFlowTransformer', function () {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
-                        ...NO_CUSTOM_NODES_PRESET,
+                        ...NO_ADDITIONAL_NODES_PRESET,
                         controlFlowFlattening: true,
                         controlFlowFlatteningThreshold: 1
                     }
@@ -360,7 +360,7 @@ describe('BlockStatementControlFlowTransformer', function () {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
-                        ...NO_CUSTOM_NODES_PRESET,
+                        ...NO_ADDITIONAL_NODES_PRESET,
                         controlFlowFlattening: true,
                         controlFlowFlatteningThreshold: 1
                     }
@@ -384,7 +384,7 @@ describe('BlockStatementControlFlowTransformer', function () {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
-                        ...NO_CUSTOM_NODES_PRESET,
+                        ...NO_ADDITIONAL_NODES_PRESET,
                         controlFlowFlattening: true,
                         controlFlowFlatteningThreshold: 1
                     }
@@ -411,7 +411,7 @@ describe('BlockStatementControlFlowTransformer', function () {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
-                        ...NO_CUSTOM_NODES_PRESET,
+                        ...NO_ADDITIONAL_NODES_PRESET,
                         controlFlowFlattening: true,
                         controlFlowFlatteningThreshold: 1
                     }
@@ -440,7 +440,7 @@ describe('BlockStatementControlFlowTransformer', function () {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
-                        ...NO_CUSTOM_NODES_PRESET,
+                        ...NO_ADDITIONAL_NODES_PRESET,
                         controlFlowFlattening: true,
                         controlFlowFlatteningThreshold: 1
                     }
@@ -464,7 +464,7 @@ describe('BlockStatementControlFlowTransformer', function () {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
-                        ...NO_CUSTOM_NODES_PRESET,
+                        ...NO_ADDITIONAL_NODES_PRESET,
                         controlFlowFlattening: true,
                         controlFlowFlatteningThreshold: 1
                     }
@@ -495,7 +495,7 @@ describe('BlockStatementControlFlowTransformer', function () {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code.repeat(samples),
                     {
-                        ...NO_CUSTOM_NODES_PRESET,
+                        ...NO_ADDITIONAL_NODES_PRESET,
                         controlFlowFlattening: true,
                         controlFlowFlatteningThreshold: controlFlowFlatteningThreshold,
                     }
@@ -537,7 +537,7 @@ describe('BlockStatementControlFlowTransformer', function () {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
-                        ...NO_CUSTOM_NODES_PRESET,
+                        ...NO_ADDITIONAL_NODES_PRESET,
                         controlFlowFlattening: true,
                         controlFlowFlatteningThreshold: 1
                     }

+ 3 - 3
test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/binary-expression-control-flow-replacer/BinaryExpressionControlFlowReplacer.spec.ts

@@ -2,7 +2,7 @@ import { assert } from 'chai';
 
 import { IObfuscationResult } from '../../../../../../src/interfaces/IObfuscationResult';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../../../../../src/options/presets/NoCustomNodes';
+import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../../src/options/presets/NoCustomNodes';
 
 import { readFileAsString } from '../../../../../helpers/readFileAsString';
 
@@ -22,7 +22,7 @@ describe('BinaryExpressionControlFlowReplacer', function () {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
-                        ...NO_CUSTOM_NODES_PRESET,
+                        ...NO_ADDITIONAL_NODES_PRESET,
                         controlFlowFlattening: true,
                         controlFlowFlatteningThreshold: 1
                     }
@@ -64,7 +64,7 @@ describe('BinaryExpressionControlFlowReplacer', function () {
                     obfuscationResult = JavaScriptObfuscator.obfuscate(
                         code,
                         {
-                            ...NO_CUSTOM_NODES_PRESET,
+                            ...NO_ADDITIONAL_NODES_PRESET,
                             controlFlowFlattening: true,
                             controlFlowFlatteningThreshold: 1
                         }

+ 4 - 4
test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/call-expression-control-flow-replacer/CallExpressionControlFlowReplacer.spec.ts

@@ -2,7 +2,7 @@ import { assert } from 'chai';
 
 import { IObfuscationResult } from '../../../../../../src/interfaces/IObfuscationResult';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../../../../../src/options/presets/NoCustomNodes';
+import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../../src/options/presets/NoCustomNodes';
 
 import { readFileAsString } from '../../../../../helpers/readFileAsString';
 
@@ -22,7 +22,7 @@ describe('CallExpressionControlFlowReplacer', function () {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
-                        ...NO_CUSTOM_NODES_PRESET,
+                        ...NO_ADDITIONAL_NODES_PRESET,
                         controlFlowFlattening: true,
                         controlFlowFlatteningThreshold: 1
                     }
@@ -64,7 +64,7 @@ describe('CallExpressionControlFlowReplacer', function () {
                     obfuscationResult = JavaScriptObfuscator.obfuscate(
                         code,
                         {
-                            ...NO_CUSTOM_NODES_PRESET,
+                            ...NO_ADDITIONAL_NODES_PRESET,
                             controlFlowFlattening: true,
                             controlFlowFlatteningThreshold: 1
                         }
@@ -111,7 +111,7 @@ describe('CallExpressionControlFlowReplacer', function () {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
-                        ...NO_CUSTOM_NODES_PRESET,
+                        ...NO_ADDITIONAL_NODES_PRESET,
                         controlFlowFlattening: true,
                         controlFlowFlatteningThreshold: 1
                     }

+ 5 - 5
test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/logical-expression-control-flow-replacer/LogicalExpressionControlFlowReplacer.spec.ts

@@ -2,7 +2,7 @@ import { assert } from 'chai';
 
 import { IObfuscationResult } from '../../../../../../src/interfaces/IObfuscationResult';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../../../../../src/options/presets/NoCustomNodes';
+import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../../src/options/presets/NoCustomNodes';
 
 import { readFileAsString } from '../../../../../helpers/readFileAsString';
 
@@ -22,7 +22,7 @@ describe('LogicalExpressionControlFlowReplacer', function () {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
-                        ...NO_CUSTOM_NODES_PRESET,
+                        ...NO_ADDITIONAL_NODES_PRESET,
                         controlFlowFlattening: true,
                         controlFlowFlatteningThreshold: 1
                     }
@@ -64,7 +64,7 @@ describe('LogicalExpressionControlFlowReplacer', function () {
                     obfuscationResult = JavaScriptObfuscator.obfuscate(
                         code,
                         {
-                            ...NO_CUSTOM_NODES_PRESET,
+                            ...NO_ADDITIONAL_NODES_PRESET,
                             controlFlowFlattening: true,
                             controlFlowFlatteningThreshold: 1
                         }
@@ -111,7 +111,7 @@ describe('LogicalExpressionControlFlowReplacer', function () {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
-                        ...NO_CUSTOM_NODES_PRESET,
+                        ...NO_ADDITIONAL_NODES_PRESET,
                         controlFlowFlattening: true,
                         controlFlowFlatteningThreshold: 1
                     }
@@ -135,7 +135,7 @@ describe('LogicalExpressionControlFlowReplacer', function () {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
-                        ...NO_CUSTOM_NODES_PRESET,
+                        ...NO_ADDITIONAL_NODES_PRESET,
                         controlFlowFlattening: true,
                         controlFlowFlatteningThreshold: .1
                     }

+ 2 - 2
test/functional-tests/node-transformers/control-flow-transformers/control-flow-replacers/string-litertal-control-flow-replacer/StringLiteralControlFlowReplacer.spec.ts

@@ -2,7 +2,7 @@ import { assert } from 'chai';
 
 import { IObfuscationResult } from '../../../../../../src/interfaces/IObfuscationResult';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../../../../../src/options/presets/NoCustomNodes';
+import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../../src/options/presets/NoCustomNodes';
 
 import { readFileAsString } from '../../../../../helpers/readFileAsString';
 
@@ -20,7 +20,7 @@ describe('StringLiteralControlFlowReplacer', () => {
             const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                 code,
                 {
-                    ...NO_CUSTOM_NODES_PRESET,
+                    ...NO_ADDITIONAL_NODES_PRESET,
                     controlFlowFlattening: true,
                     controlFlowFlatteningThreshold: 1
                 }

+ 9 - 9
test/functional-tests/node-transformers/control-flow-transformers/function-control-flow-transformer/FunctionControlFlowTransformer.spec.ts

@@ -2,7 +2,7 @@ import { assert } from 'chai';
 
 import { IObfuscationResult } from '../../../../../src/interfaces/IObfuscationResult';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
+import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
 
 import { readFileAsString } from '../../../../helpers/readFileAsString';
 
@@ -38,7 +38,7 @@ describe('FunctionControlFlowTransformer', function () {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
-                        ...NO_CUSTOM_NODES_PRESET,
+                        ...NO_ADDITIONAL_NODES_PRESET,
                         controlFlowFlattening: true,
                         controlFlowFlatteningThreshold: 1
                     }
@@ -80,7 +80,7 @@ describe('FunctionControlFlowTransformer', function () {
                     obfuscationResult = JavaScriptObfuscator.obfuscate(
                         code,
                         {
-                            ...NO_CUSTOM_NODES_PRESET,
+                            ...NO_ADDITIONAL_NODES_PRESET,
                             controlFlowFlattening: true,
                             controlFlowFlatteningThreshold: 1
                         }
@@ -123,7 +123,7 @@ describe('FunctionControlFlowTransformer', function () {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
-                        ...NO_CUSTOM_NODES_PRESET,
+                        ...NO_ADDITIONAL_NODES_PRESET,
                         controlFlowFlattening: true,
                         controlFlowFlatteningThreshold: 1
                     }
@@ -147,7 +147,7 @@ describe('FunctionControlFlowTransformer', function () {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
-                        ...NO_CUSTOM_NODES_PRESET,
+                        ...NO_ADDITIONAL_NODES_PRESET,
                         controlFlowFlattening: true,
                         controlFlowFlatteningThreshold: 1
                     }
@@ -182,7 +182,7 @@ describe('FunctionControlFlowTransformer', function () {
                     const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                         code,
                         {
-                            ...NO_CUSTOM_NODES_PRESET,
+                            ...NO_ADDITIONAL_NODES_PRESET,
                             controlFlowFlattening: true,
                             controlFlowFlatteningThreshold: 1
                         }
@@ -211,7 +211,7 @@ describe('FunctionControlFlowTransformer', function () {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
-                        ...NO_CUSTOM_NODES_PRESET,
+                        ...NO_ADDITIONAL_NODES_PRESET,
                         controlFlowFlattening: true,
                         controlFlowFlatteningThreshold: 0
                     }
@@ -240,7 +240,7 @@ describe('FunctionControlFlowTransformer', function () {
                     const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                         code,
                         {
-                            ...NO_CUSTOM_NODES_PRESET,
+                            ...NO_ADDITIONAL_NODES_PRESET,
                             controlFlowFlattening: true,
                             controlFlowFlatteningThreshold: 1
                         }
@@ -264,7 +264,7 @@ describe('FunctionControlFlowTransformer', function () {
                     const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                         code,
                         {
-                            ...NO_CUSTOM_NODES_PRESET,
+                            ...NO_ADDITIONAL_NODES_PRESET,
                             controlFlowFlattening: true,
                             controlFlowFlatteningThreshold: 1
                         }

+ 5 - 5
test/functional-tests/node-transformers/converting-transformers/member-expression-transformer/MemberExpressionTransformer.spec.ts

@@ -2,7 +2,7 @@ import { assert } from 'chai';
 
 import { IObfuscationResult } from '../../../../../src/interfaces/IObfuscationResult';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
+import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
 
 import { readFileAsString } from '../../../../helpers/readFileAsString';
 
@@ -20,7 +20,7 @@ describe('MemberExpressionTransformer', () => {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
-                        ...NO_CUSTOM_NODES_PRESET
+                        ...NO_ADDITIONAL_NODES_PRESET
                     }
                 );
 
@@ -43,7 +43,7 @@ describe('MemberExpressionTransformer', () => {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
-                        ...NO_CUSTOM_NODES_PRESET,
+                        ...NO_ADDITIONAL_NODES_PRESET,
                         stringArray: true,
                         stringArrayThreshold: 1
                     }
@@ -74,7 +74,7 @@ describe('MemberExpressionTransformer', () => {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
-                        ...NO_CUSTOM_NODES_PRESET,
+                        ...NO_ADDITIONAL_NODES_PRESET,
                         stringArray: true,
                         stringArrayThreshold: 1
                     }
@@ -102,7 +102,7 @@ describe('MemberExpressionTransformer', () => {
                 const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     code,
                     {
-                        ...NO_CUSTOM_NODES_PRESET
+                        ...NO_ADDITIONAL_NODES_PRESET
                     }
                 );
 

+ 4 - 4
test/functional-tests/node-transformers/converting-transformers/method-definition-transformer/MethodDefinitionTransformer.spec.ts

@@ -2,7 +2,7 @@ import { assert } from 'chai';
 
 import { IObfuscationResult } from '../../../../../src/interfaces/IObfuscationResult';
 
-import { NO_CUSTOM_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
+import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
 
 import { readFileAsString } from '../../../../helpers/readFileAsString';
 
@@ -19,7 +19,7 @@ describe('MethodDefinitionTransformer', () => {
             const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                 code,
                 {
-                    ...NO_CUSTOM_NODES_PRESET
+                    ...NO_ADDITIONAL_NODES_PRESET
                 }
             );
 
@@ -42,7 +42,7 @@ describe('MethodDefinitionTransformer', () => {
             const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                 code,
                 {
-                    ...NO_CUSTOM_NODES_PRESET,
+                    ...NO_ADDITIONAL_NODES_PRESET,
                     stringArray: true,
                     stringArrayThreshold: 1
                 }
@@ -70,7 +70,7 @@ describe('MethodDefinitionTransformer', () => {
             const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                 code,
                 {
-                    ...NO_CUSTOM_NODES_PRESET
+                    ...NO_ADDITIONAL_NODES_PRESET
                 }
             );
 

+ 319 - 0
test/functional-tests/node-transformers/converting-transformers/object-expression-keys-transformer/ObjectExpressionKeysTransformer.spec.ts

@@ -0,0 +1,319 @@
+import { assert } from 'chai';
+
+import { IObfuscationResult } from '../../../../../src/interfaces/IObfuscationResult';
+
+import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
+
+import { readFileAsString } from '../../../../helpers/readFileAsString';
+
+import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
+
+describe('ObjectExpressionKeysTransformer', () => {
+    const variableMatch: string = '_0x([a-f0-9]){4,6}';
+
+    describe('transformation of object keys', () => {
+        describe('variant #1: simple', () => {
+            const match: string = `` +
+                `var *${variableMatch} *= *{};` +
+                `${variableMatch}\\['foo'] *= *'bar';` +
+                `${variableMatch}\\['baz'] *= *'bark';` +
+            ``;
+            const regExp: RegExp = new RegExp(match);
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/simple.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        transformObjectKeys: true
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('should correctly transform object keys', () => {
+                assert.match(obfuscatedCode,  regExp);
+            });
+        });
+
+        describe('variant #2: nested objects #1', () => {
+            const match: string = `` +
+                `var *${variableMatch} *= *{};` +
+                `${variableMatch}\\['foo'] *= *'bar';` +
+                `${variableMatch}\\['inner'] *= *{};` +
+                `${variableMatch}\\['inner']\\['inner1'] *= *{};` +
+                `${variableMatch}\\['inner']\\['inner1']\\['baz'] *= *'bark';` +
+            ``;
+            const regExp: RegExp = new RegExp(match);
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/nested-objects-1.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        transformObjectKeys: true
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('should correctly transform object keys', () => {
+                assert.match(obfuscatedCode,  regExp);
+            });
+        });
+
+        describe('variant #3: nested objects #2', () => {
+            const match: string = `` +
+                `var *${variableMatch} *= *{};` +
+                `${variableMatch}\\['foo'] *= *'bar';` +
+                `${variableMatch}\\['inner'] *= *{};` +
+                `${variableMatch}\\['ball'] *= *'door';` +
+                `${variableMatch}\\['inner']\\['baz'] *= *'bark';` +
+                `${variableMatch}\\['inner']\\['inner1'] *= *{};` +
+                `${variableMatch}\\['inner']\\['cow'] *= *'bear';` +
+                `${variableMatch}\\['inner']\\['inner1']\\['hawk'] *= *'geek';` +
+            ``;
+            const regExp: RegExp = new RegExp(match);
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/nested-objects-2.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        transformObjectKeys: true
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('should correctly transform object keys', () => {
+                assert.match(obfuscatedCode,  regExp);
+            });
+        });
+
+        describe('variant #4: correct integration with control flow flattening object', () => {
+            const match: string = `` +
+                `var *${variableMatch} *= *{};` +
+                `${variableMatch}\\['\\w{5}'] *= *function *${variableMatch} *\\(${variableMatch}, *${variableMatch}\\) *{` +
+                    `return *${variableMatch} *\\+ *${variableMatch};` +
+                `};` +
+                `var *${variableMatch} *= *${variableMatch}\\['\\w{5}']\\(0x1, *0x2\\);` +
+            ``;
+            const regExp: RegExp = new RegExp(match);
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/integration-with-control-flow-flattening.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        controlFlowFlattening: true,
+                        controlFlowFlatteningThreshold: 1,
+                        transformObjectKeys: true
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('should correctly transform object keys', () => {
+                assert.match(obfuscatedCode,  regExp);
+            });
+        });
+    });
+
+    describe('correct placement of expression statements', () => {
+        describe('variant #1: if statement', () => {
+            const match: string = `` +
+                `if *\\(!!\\[]\\) *{` +
+                    `var *${variableMatch} *= *{};` +
+                    `${variableMatch}\\['foo'] *= *'bar';` +
+                `}` +
+            ``;
+            const regExp: RegExp = new RegExp(match);
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/placement-inside-if-statement.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        transformObjectKeys: true
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('should correctly transform object keys', () => {
+                assert.match(obfuscatedCode,  regExp);
+            });
+        });
+
+        describe('variant #2: try statement', () => {
+            const match: string = `` +
+                `try *{` +
+                    `var *${variableMatch} *= *{};` +
+                    `${variableMatch}\\['foo'] *= *'bar';` +
+                `} *catch *\\(${variableMatch}\\) *{` +
+                `}` +
+            ``;
+            const regExp: RegExp = new RegExp(match);
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/placement-inside-try-statement.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        transformObjectKeys: true
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('should correctly transform object keys', () => {
+                assert.match(obfuscatedCode,  regExp);
+            });
+        });
+
+        describe('variant #3: catch clause statement', () => {
+            const match: string = `` +
+                `try *{` +
+                `} *catch *\\(${variableMatch}\\) *{` +
+                    `var *${variableMatch} *= *{};` +
+                    `${variableMatch}\\['foo'] *= *'bar';` +
+                `}` +
+            ``;
+            const regExp: RegExp = new RegExp(match);
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/placement-inside-catch-clause.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        transformObjectKeys: true
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('should correctly transform object keys', () => {
+                assert.match(obfuscatedCode,  regExp);
+            });
+        });
+
+        describe('variant #4: switch catch statement', () => {
+            const match: string = `` +
+                `switch *\\(!!\\[]\\) *{` +
+                    `case *!!\\[]:` +
+                        `var *${variableMatch} *= *{};` +
+                        `${variableMatch}\\['foo'] *= *'bar';` +
+                `}` +
+            ``;
+            const regExp: RegExp = new RegExp(match);
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/placement-inside-switch-case.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        transformObjectKeys: true
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('should transform object keys', () => {
+                assert.match(obfuscatedCode,  regExp);
+            });
+        });
+    });
+
+    describe('Ignore transformation', () => {
+        describe('variant #1: disabled option', () => {
+            const match: string = `` +
+                `var *${variableMatch} *= *{` +
+                    `'foo': *'bar',` +
+                    `'baz': *'bark'` +
+                `}` +
+            ``;
+            const regExp: RegExp = new RegExp(match);
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/simple.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('shouldn\'t transform object keys', () => {
+                assert.match(obfuscatedCode,  regExp);
+            });
+        });
+
+        describe('variant #2: variable declaration without initialization', () => {
+            const match: string = `` +
+                `var *${variableMatch};` +
+                `${variableMatch} *= *{` +
+                    `'foo': *'bar',` +
+                    `'baz': *'bark'` +
+                `}` +
+            ``;
+            const regExp: RegExp = new RegExp(match);
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/variable-declaration-without-initialization.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        transformObjectKeys: true
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            });
+
+            it('shouldn\'t transform object keys', () => {
+                assert.match(obfuscatedCode,  regExp);
+            });
+        });
+    });
+});

+ 3 - 0
test/functional-tests/node-transformers/converting-transformers/object-expression-keys-transformer/fixtures/integration-with-control-flow-flattening.js

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

+ 10 - 0
test/functional-tests/node-transformers/converting-transformers/object-expression-keys-transformer/fixtures/nested-objects-1.js

@@ -0,0 +1,10 @@
+(function(){
+    var object = {
+        foo: 'bar',
+        inner: {
+            inner1: {
+                baz: 'bark'
+            }
+        }
+    };
+})();

+ 14 - 0
test/functional-tests/node-transformers/converting-transformers/object-expression-keys-transformer/fixtures/nested-objects-2.js

@@ -0,0 +1,14 @@
+(function(){
+    var object = {
+        foo: 'bar',
+        inner: {
+            baz: 'bark',
+            inner1: {
+                hawk: 'geek'
+            },
+            cow: 'bear',
+
+        },
+        ball: 'door',
+    };
+})();

+ 8 - 0
test/functional-tests/node-transformers/converting-transformers/object-expression-keys-transformer/fixtures/placement-inside-catch-clause.js

@@ -0,0 +1,8 @@
+(function(){
+    try {
+    } catch (e) {
+        var object = {
+            foo: 'bar'
+        };
+    }
+})();

+ 7 - 0
test/functional-tests/node-transformers/converting-transformers/object-expression-keys-transformer/fixtures/placement-inside-if-statement.js

@@ -0,0 +1,7 @@
+(function(){
+    if (true) {
+        var object = {
+            foo: 'bar'
+        };
+    }
+})();

+ 8 - 0
test/functional-tests/node-transformers/converting-transformers/object-expression-keys-transformer/fixtures/placement-inside-switch-case.js

@@ -0,0 +1,8 @@
+(function(){
+    switch (true) {
+        case true:
+            var object = {
+                foo: 'bar'
+            };
+    }
+})();

+ 7 - 0
test/functional-tests/node-transformers/converting-transformers/object-expression-keys-transformer/fixtures/placement-inside-try-statement.js

@@ -0,0 +1,7 @@
+(function(){
+    try {
+        var object = {
+            foo: 'bar'
+        };
+    } catch (e) {}
+})();

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