Browse Source

Merge pull request #645 from javascript-obfuscator/minification-transformers

`minify` option WIP
Timofey Kachalov 4 years ago
parent
commit
90b58f2088
76 changed files with 2097 additions and 86 deletions
  1. 4 0
      CHANGELOG.md
  2. 13 1
      README.md
  3. 0 0
      dist/index.browser.js
  4. 0 0
      dist/index.cli.js
  5. 0 0
      dist/index.js
  6. 9 9
      package.json
  7. 7 0
      src/JavaScriptObfuscator.ts
  8. 4 0
      src/cli/JavaScriptObfuscatorCLI.ts
  9. 2 0
      src/container/InversifyContainerFacade.ts
  10. 20 0
      src/container/modules/node-transformers/SimplifyingTransformersModule.ts
  11. 1 1
      src/custom-code-helpers/AbstractCustomCodeHelper.ts
  12. 1 1
      src/custom-nodes/AbstractCustomNode.ts
  13. 1 0
      src/enums/node-transformers/NodeTransformationStage.ts
  14. 2 0
      src/enums/node-transformers/NodeTransformer.ts
  15. 1 0
      src/enums/node/NodeType.ts
  16. 1 1
      src/interfaces/IInitializable.ts
  17. 1 1
      src/interfaces/custom-code-helpers/ICustomCodeHelper.ts
  18. 1 1
      src/interfaces/custom-nodes/ICustomNode.ts
  19. 5 1
      src/interfaces/event-emitters/IObfuscationEventEmitter.ts
  20. 18 0
      src/interfaces/node-transformers/simplifying-transformers/IIfStatementIteratedStatementsData.ts
  21. 30 0
      src/interfaces/node-transformers/simplifying-transformers/IIfStatementSimplifyData.ts
  22. 1 0
      src/interfaces/options/IOptions.ts
  23. 1 1
      src/node-transformers/NodeTransformersRunner.ts
  24. 1 1
      src/node-transformers/rename-properties-transformers/RenamePropertiesTransformer.ts
  25. 448 0
      src/node-transformers/simplifying-transformers/IfStatementSimplifyTransformer.ts
  26. 91 0
      src/node-transformers/simplifying-transformers/VariableDeclarationsMergeTransformer.ts
  27. 38 6
      src/node/NodeFactory.ts
  28. 8 0
      src/node/NodeGuards.ts
  29. 6 0
      src/options/Options.ts
  30. 1 0
      src/options/presets/Default.ts
  31. 1 0
      src/options/presets/NoCustomNodes.ts
  32. 1 1
      src/types/container/custom-code-helpers/TCustomCodeHelperFactory.ts
  33. 1 1
      src/types/container/custom-nodes/TControlFlowCustomNodeFactory.ts
  34. 1 1
      src/types/container/custom-nodes/TDeadNodeInjectionCustomNodeFactory.ts
  35. 1 1
      src/types/container/custom-nodes/TObjectExpressionKeysTransformerCustomNodeFactory.ts
  36. 11 5
      test/dev/dev.ts
  37. 2 0
      test/functional-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts
  38. 814 0
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/IfStatementSimplifyTransformer.spec.ts
  39. 12 0
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/full-consequent-and-alternate-alternate-return-multiple-statements.js
  40. 7 0
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/full-consequent-and-alternate-alternate-return-single-statement.js
  41. 12 0
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/full-consequent-and-alternate-consequent-return-multiple-statements.js
  42. 7 0
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/full-consequent-and-alternate-consequent-return-single-statement.js
  43. 11 0
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/full-consequent-and-alternate-no-return-multiple-statements.js
  44. 7 0
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/full-consequent-and-alternate-no-return-single-statement.js
  45. 13 0
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/full-consequent-and-alternate-return-multiple-statements.js
  46. 7 0
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/full-consequent-and-alternate-return-single-statement.js
  47. 7 0
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/full-consequent-only-no-return-multiple-statements.js
  48. 5 0
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/full-consequent-only-no-return-single-statement.js
  49. 8 0
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/full-consequent-only-return-multiple-statements.js
  50. 5 0
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/full-consequent-only-return-single-statement.js
  51. 7 0
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/function-declaration-as-prohibited-single-statement.js
  52. 9 0
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/if-statement-as-prohibited-single-statement.js
  53. 15 0
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/partial-consequent-and-alternate-alternate-return-multiple-statements.js
  54. 7 0
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/partial-consequent-and-alternate-alternate-return-single-statement.js
  55. 15 0
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/partial-consequent-and-alternate-consequent-return-multiple-statements.js
  56. 7 0
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/partial-consequent-and-alternate-consequent-return-single-statement.js
  57. 10 0
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/partial-consequent-and-alternate-no-return-mixed-statements-1.js
  58. 11 0
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/partial-consequent-and-alternate-no-return-mixed-statements-2.js
  59. 14 0
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/partial-consequent-and-alternate-no-return-multiple-statements.js
  60. 7 0
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/partial-consequent-and-alternate-no-return-single-statement.js
  61. 17 0
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/partial-consequent-and-alternate-return-multiple-statements.js
  62. 8 0
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/partial-consequent-only-no-return-multiple-statements.js
  63. 5 0
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/partial-consequent-only-no-return-single-statement.js
  64. 9 0
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/partial-consequent-only-return-multiple-statements.js
  65. 7 0
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/variable-declarations-merge-transformer-integration-1.js
  66. 200 0
      test/functional-tests/node-transformers/simplifying-transformers/variable-declarations-merge-transformer/VariableDeclarationsMergeTransformer.spec.ts
  67. 6 0
      test/functional-tests/node-transformers/simplifying-transformers/variable-declarations-merge-transformer/fixtures/different-variables-kind.js
  68. 4 0
      test/functional-tests/node-transformers/simplifying-transformers/variable-declarations-merge-transformer/fixtures/multiple-declarations-with-multiple-declarators.js
  69. 3 0
      test/functional-tests/node-transformers/simplifying-transformers/variable-declarations-merge-transformer/fixtures/multiple-declarations-without-declarators.js
  70. 3 0
      test/functional-tests/node-transformers/simplifying-transformers/variable-declarations-merge-transformer/fixtures/multiple-declarations.js
  71. 3 0
      test/functional-tests/node-transformers/simplifying-transformers/variable-declarations-merge-transformer/fixtures/object-pattern-as-initializer.js
  72. 1 0
      test/functional-tests/node-transformers/simplifying-transformers/variable-declarations-merge-transformer/fixtures/single-declaration.js
  73. 5 0
      test/functional-tests/node-transformers/simplifying-transformers/variable-declarations-merge-transformer/fixtures/splitted-declarations-with-other-statement.js
  74. 2 0
      test/index.spec.ts
  75. 9 4
      test/runtime-tests/JavaScriptObfuscatorRuntime.spec.ts
  76. 64 49
      yarn.lock

+ 4 - 0
CHANGELOG.md

@@ -1,5 +1,9 @@
 Change Log
 
+v1.4.0
+---
+* **New option:** `simplify` enables additional code obfuscation through simplification.
+
 v1.3.0
 ---
 * Improvements of `stringArrayEncoding`: `base64` and `rc4`

+ 13 - 1
README.md

@@ -337,6 +337,7 @@ Following options are available for the JS Obfuscator:
     seed: 0,
     selfDefending: false,
     shuffleStringArray: true,
+    simplify: true,
     sourceMap: false,
     sourceMapBaseUrl: '',
     sourceMapFileName: '',
@@ -382,6 +383,7 @@ Following options are available for the JS Obfuscator:
     --seed <string|number>
     --self-defending <boolean>
     --shuffle-string-array <boolean>
+    --simplify <boolean>
     --source-map <boolean>
     --source-map-base-url <string>
     --source-map-file-name <string>
@@ -657,13 +659,20 @@ Use this option when you want to obfuscate multiple files. This option helps to
 ### `inputFileName`
 Type: `string` Default: `''`
 
-Allows to set name of the input file with source code. This name will used internally for source map generation.
+Allows to set name of the input file with source code. This name will be used internally for source map generation.
 
 ### `log`
 Type: `boolean` Default: `false`
 
 Enables logging of the information to the console.
 
+### `simplify`
+Type: `boolean` Default: `true`
+
+Enables additional code obfuscation through simplification.
+
+##### :warning: in the future releases obfuscation of `boolean` literals (`true` => `!![]`) will be moved under this option. 
+
 ### `renameGlobals`
 Type: `boolean` Default: `false`
 
@@ -951,6 +960,7 @@ Performance will 50-100% slower than without obfuscation
     rotateStringArray: true,
     selfDefending: true,
     shuffleStringArray: true,
+    simplify: true,
     splitStrings: true,
     splitStringsChunkLength: 5,
     stringArray: true,
@@ -981,6 +991,7 @@ Performance will 30-35% slower than without obfuscation
     rotateStringArray: true,
     selfDefending: true,
     shuffleStringArray: true,
+    simplify: true,
     splitStrings: true,
     splitStringsChunkLength: 10,
     stringArray: true,
@@ -1009,6 +1020,7 @@ Performance will slightly slower than without obfuscation
     rotateStringArray: true,
     selfDefending: true,
     shuffleStringArray: true,
+    simplify: true,
     splitStrings: false,
     stringArray: true,
     stringArrayEncoding: false,

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


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


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


+ 9 - 9
package.json

@@ -48,29 +48,29 @@
     "@types/chance": "1.1.0",
     "@types/escodegen": "0.0.6",
     "@types/eslint-scope": "3.7.0",
-    "@types/estraverse": "0.0.6",
+    "@types/estraverse": "5.1.0",
     "@types/estree": "0.0.44",
     "@types/md5": "2.2.0",
     "@types/mkdirp": "1.0.1",
     "@types/mocha": "7.0.2",
     "@types/multimatch": "4.0.0",
-    "@types/node": "14.0.14",
+    "@types/node": "14.0.19",
     "@types/rimraf": "3.0.0",
     "@types/sinon": "9.0.4",
     "@types/string-template": "1.0.2",
     "@types/webpack-env": "1.15.2",
-    "@typescript-eslint/eslint-plugin": "3.4.0",
-    "@typescript-eslint/parser": "3.4.0",
+    "@typescript-eslint/eslint-plugin": "3.6.0",
+    "@typescript-eslint/parser": "3.6.0",
     "chai": "4.2.0",
     "coveralls": "3.1.0",
-    "eslint": "7.3.1",
-    "eslint-plugin-import": "2.21.2",
-    "eslint-plugin-jsdoc": "28.5.1",
+    "eslint": "7.4.0",
+    "eslint-plugin-import": "2.22.0",
+    "eslint-plugin-jsdoc": "28.6.1",
     "eslint-plugin-no-null": "1.0.2",
     "eslint-plugin-prefer-arrow": "1.2.1",
     "eslint-plugin-unicorn": "20.1.0",
     "fork-ts-checker-notifier-webpack-plugin": "3.0.0",
-    "fork-ts-checker-webpack-plugin": "5.0.5",
+    "fork-ts-checker-webpack-plugin": "5.0.7",
     "mocha": "8.0.1",
     "nyc": "15.1.0",
     "pjson": "1.0.9",
@@ -80,7 +80,7 @@
     "threads": "1.6.3",
     "ts-loader": "7.0.5",
     "ts-node": "8.10.2",
-    "typescript": "3.9.5",
+    "typescript": "3.9.6",
     "webpack": "4.43.0",
     "webpack-cli": "3.3.12",
     "webpack-node-externals": "1.7.2"

+ 7 - 0
src/JavaScriptObfuscator.ts

@@ -68,6 +68,7 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
         NodeTransformer.DeadCodeInjectionTransformer,
         NodeTransformer.EvalCallExpressionTransformer,
         NodeTransformer.FunctionControlFlowTransformer,
+        NodeTransformer.IfStatementSimplifyTransformer,
         NodeTransformer.LabeledStatementTransformer,
         NodeTransformer.LiteralTransformer,
         NodeTransformer.RenamePropertiesTransformer,
@@ -81,6 +82,7 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
         NodeTransformer.ScopeIdentifiersTransformer,
         NodeTransformer.SplitStringTransformer,
         NodeTransformer.TemplateLiteralTransformer,
+        NodeTransformer.VariableDeclarationsMergeTransformer,
         NodeTransformer.VariablePreserveTransformer
     ];
 
@@ -215,6 +217,11 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
 
         astTree = this.runNodeTransformationStage(astTree, NodeTransformationStage.Converting);
         astTree = this.runNodeTransformationStage(astTree, NodeTransformationStage.Obfuscating);
+
+        if (this.options.simplify) {
+            astTree = this.runNodeTransformationStage(astTree, NodeTransformationStage.Simplifying);
+        }
+
         astTree = this.runNodeTransformationStage(astTree, NodeTransformationStage.Finalizing);
 
         return astTree;

+ 4 - 0
src/cli/JavaScriptObfuscatorCLI.ts

@@ -285,6 +285,10 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
                 '--shuffle-string-array <boolean>', 'Randomly shuffles string array items',
                 BooleanSanitizer
             )
+            .option(
+                '--simplify <boolean>', 'Enables additional code obfuscation through simplification',
+                BooleanSanitizer
+            )
             .option(
                 '--source-map <boolean>',
                 'Enables source map generation',

+ 2 - 0
src/container/InversifyContainerFacade.ts

@@ -16,6 +16,7 @@ import { obfuscatingTransformersModule } from './modules/node-transformers/Obfus
 import { optionsModule } from './modules/options/OptionsModule';
 import { preparingTransformersModule } from './modules/node-transformers/PreparingTransformersModule';
 import { renamePropertiesTransformersModule } from './modules/node-transformers/RenamePropertiesTransformersModule';
+import { simplifyingTransformersModule } from './modules/node-transformers/SimplifyingTransformersModule';
 import { storagesModule } from './modules/storages/StoragesModule';
 import { utilsModule } from './modules/utils/UtilsModule';
 
@@ -217,6 +218,7 @@ export class InversifyContainerFacade implements IInversifyContainerFacade {
         this.container.load(optionsModule);
         this.container.load(preparingTransformersModule);
         this.container.load(renamePropertiesTransformersModule);
+        this.container.load(simplifyingTransformersModule);
         this.container.load(storagesModule);
         this.container.load(utilsModule);
     }

+ 20 - 0
src/container/modules/node-transformers/SimplifyingTransformersModule.ts

@@ -0,0 +1,20 @@
+import { ContainerModule, interfaces } from 'inversify';
+import { ServiceIdentifiers } from '../../ServiceIdentifiers';
+
+import { INodeTransformer } from '../../../interfaces/node-transformers/INodeTransformer';
+
+import { NodeTransformer } from '../../../enums/node-transformers/NodeTransformer';
+
+import { IfStatementSimplifyTransformer } from '../../../node-transformers/simplifying-transformers/IfStatementSimplifyTransformer';
+import { VariableDeclarationsMergeTransformer } from '../../../node-transformers/simplifying-transformers/VariableDeclarationsMergeTransformer';
+
+export const simplifyingTransformersModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
+    // simplifying transformers
+    bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
+        .to(IfStatementSimplifyTransformer)
+        .whenTargetNamed(NodeTransformer.IfStatementSimplifyTransformer);
+
+    bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
+        .to(VariableDeclarationsMergeTransformer)
+        .whenTargetNamed(NodeTransformer.VariableDeclarationsMergeTransformer);
+});

+ 1 - 1
src/custom-code-helpers/AbstractCustomCodeHelper.ts

@@ -16,7 +16,7 @@ import { GlobalVariableTemplate2 } from './common/templates/GlobalVariableTempla
 
 @injectable()
 export abstract class AbstractCustomCodeHelper <
-    TInitialData extends any[] = any[]
+    TInitialData extends unknown[] = unknown[]
 > implements ICustomCodeHelper <TInitialData> {
     /**
      * @type {string[]}

+ 1 - 1
src/custom-nodes/AbstractCustomNode.ts

@@ -12,7 +12,7 @@ import { IRandomGenerator } from '../interfaces/utils/IRandomGenerator';
 
 @injectable()
 export abstract class AbstractCustomNode <
-    TInitialData extends any[] = any[]
+    TInitialData extends unknown[] = unknown[]
 > implements ICustomNode <TInitialData> {
     /**
      * @type {TStatement[] | null}

+ 1 - 0
src/enums/node-transformers/NodeTransformationStage.ts

@@ -6,5 +6,6 @@ export enum NodeTransformationStage {
     RenameProperties = 'RenameProperties',
     Converting = 'Converting',
     Obfuscating = 'Obfuscating',
+    Simplifying = 'Simplifying',
     Finalizing = 'Finalizing'
 }

+ 2 - 0
src/enums/node-transformers/NodeTransformer.ts

@@ -5,6 +5,7 @@ export enum NodeTransformer {
     DeadCodeInjectionTransformer = 'DeadCodeInjectionTransformer',
     EvalCallExpressionTransformer = 'EvalCallExpressionTransformer',
     FunctionControlFlowTransformer = 'FunctionControlFlowTransformer',
+    IfStatementSimplifyTransformer = 'IfStatementSimplifyTransformer',
     LabeledStatementTransformer = 'LabeledStatementTransformer',
     LiteralTransformer = 'LiteralTransformer',
     MemberExpressionTransformer = 'MemberExpressionTransformer',
@@ -18,5 +19,6 @@ export enum NodeTransformer {
     ScopeIdentifiersTransformer = 'ScopeIdentifiersTransformer',
     SplitStringTransformer = 'SplitStringTransformer',
     TemplateLiteralTransformer = 'TemplateLiteralTransformer',
+    VariableDeclarationsMergeTransformer = 'VariableDeclarationsMergeTransformer',
     VariablePreserveTransformer = 'VariablePreserveTransformer'
 }

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

@@ -43,6 +43,7 @@ export enum NodeType {
     SwitchStatement = 'SwitchStatement',
     TaggedTemplateExpression = 'TaggedTemplateExpression',
     TemplateLiteral = 'TemplateLiteral',
+    ThrowStatement = 'ThrowStatement',
     TryStatement = 'TryStatement',
     UnaryExpression = 'UnaryExpression',
     UpdateExpression = 'UpdateExpression',

+ 1 - 1
src/interfaces/IInitializable.ts

@@ -1,4 +1,4 @@
-export interface IInitializable <T extends any[] = never[]> {
+export interface IInitializable <T extends unknown[] = never[]> {
     [key: string]: any;
 
     /**

+ 1 - 1
src/interfaces/custom-code-helpers/ICustomCodeHelper.ts

@@ -3,7 +3,7 @@ import { TStatement } from '../../types/node/TStatement';
 import { IInitializable } from '../IInitializable';
 
 export interface ICustomCodeHelper <
-    TInitialData extends any[] = any[]
+    TInitialData extends unknown[] = unknown[]
 > extends IInitializable<TInitialData> {
     /**
      * @returns ESTree.Node[]

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

@@ -3,7 +3,7 @@ import { TStatement } from '../../types/node/TStatement';
 import { IInitializable } from '../IInitializable';
 
 export interface ICustomNode <
-    TInitialData extends any[] = any[]
+    TInitialData extends unknown[] = unknown[]
 > extends IInitializable<TInitialData> {
     /**
      * @returns ESTree.Node[]

+ 5 - 1
src/interfaces/event-emitters/IObfuscationEventEmitter.ts

@@ -1,3 +1,7 @@
+import * as ESTree from 'estree';
+
+import { ICallsGraphData } from '../analyzers/calls-graph-analyzer/ICallsGraphData';
+
 import { ObfuscationEvent } from '../../enums/event-emitters/ObfuscationEvent';
 
 export interface IObfuscationEventEmitter {
@@ -6,7 +10,7 @@ export interface IObfuscationEventEmitter {
      * @param args
      * @returns {boolean}
      */
-    emit (event: ObfuscationEvent, ...args: any[]): boolean;
+    emit (event: ObfuscationEvent, ...args: [ESTree.Program, ICallsGraphData[]]): boolean;
 
     /**
      * @param event

+ 18 - 0
src/interfaces/node-transformers/simplifying-transformers/IIfStatementIteratedStatementsData.ts

@@ -0,0 +1,18 @@
+import * as ESTree from 'estree';
+
+export interface IIfStatementIteratedStatementsData {
+    /**
+     * @type {number | null}
+     */
+    startIndex: number | null;
+
+    /**
+     * @type {ESTree.Expression[]}
+     */
+    unwrappedExpressions: ESTree.Expression[];
+
+    /**
+     * @type {boolean}
+     */
+    hasReturnStatement: boolean;
+}

+ 30 - 0
src/interfaces/node-transformers/simplifying-transformers/IIfStatementSimplifyData.ts

@@ -0,0 +1,30 @@
+import * as ESTree from 'estree';
+
+export interface IIfStatementSimplifyData {
+    /**
+     * @type {ESTree.Statement[]}
+     */
+    leadingStatements: ESTree.Statement[];
+
+    trailingStatement: {
+        /**
+         * @type {ESTree.Statement | null}
+         */
+        statement: ESTree.Statement;
+
+        /**
+         * @type {ESTree.Expression | null}
+         */
+        expression: ESTree.Expression;
+    } | null;
+
+    /**
+     * @type {boolean}
+     */
+    hasReturnStatement: boolean;
+
+    /**
+     * @type {boolean}
+     */
+    hasSingleExpression: boolean;
+}

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

@@ -29,6 +29,7 @@ export interface IOptions {
     readonly seed: string | number;
     readonly selfDefending: boolean;
     readonly shuffleStringArray: boolean;
+    readonly simplify: boolean;
     readonly sourceMap: boolean;
     readonly sourceMapBaseUrl: string;
     readonly sourceMapFileName: string;

+ 1 - 1
src/node-transformers/NodeTransformersRunner.ts

@@ -127,7 +127,7 @@ export class NodeTransformersRunner implements INodeTransformersRunner {
                         return acc;
                     }
 
-                    return {
+                    return <TDictionary<INodeTransformer>>{
                         ...acc,
                         [nodeTransformerName]: nodeTransformer
                     };

+ 1 - 1
src/node-transformers/rename-properties-transformers/RenamePropertiesTransformer.ts

@@ -42,7 +42,7 @@ export class RenamePropertiesTransformer extends AbstractNodeTransformer {
      */
     private static isValidPropertyNode<
         TNode extends ESTree.Property | ESTree.MemberExpression | ESTree.MethodDefinition
-    >(
+    > (
         propertyNode: TNode,
         propertyKeyNode: ESTree.Expression
     ): propertyKeyNode is ESTree.Identifier | ESTree.Literal {

+ 448 - 0
src/node-transformers/simplifying-transformers/IfStatementSimplifyTransformer.ts

@@ -0,0 +1,448 @@
+import { inject, injectable, } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
+
+import * as ESTree from 'estree';
+
+import { IIfStatementSimplifyData } from '../../interfaces/node-transformers/simplifying-transformers/IIfStatementSimplifyData';
+import { IIfStatementIteratedStatementsData } from '../../interfaces/node-transformers/simplifying-transformers/IIfStatementIteratedStatementsData';
+import { IOptions } from '../../interfaces/options/IOptions';
+import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
+import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
+
+import { NodeTransformer } from '../../enums/node-transformers/NodeTransformer';
+import { NodeTransformationStage } from '../../enums/node-transformers/NodeTransformationStage';
+
+import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
+import { NodeGuards } from '../../node/NodeGuards';
+import { NodeFactory } from '../../node/NodeFactory';
+import { NodeUtils } from '../../node/NodeUtils';
+
+/**
+ * Simplifies `IfStatement` node
+ */
+@injectable()
+export class IfStatementSimplifyTransformer extends AbstractNodeTransformer {
+    /**
+     * @type {NodeTransformer[]}
+     */
+    public readonly runAfter: NodeTransformer[] = [
+        NodeTransformer.VariableDeclarationsMergeTransformer
+    ];
+
+    /**
+     * @param {IRandomGenerator} randomGenerator
+     * @param {IOptions} options
+     */
+    public constructor (
+        @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        super(randomGenerator, options);
+    }
+
+    /**
+     * @param {NodeTransformationStage} nodeTransformationStage
+     * @returns {IVisitor | null}
+     */
+    public getVisitor (nodeTransformationStage: NodeTransformationStage): IVisitor | null {
+        switch (nodeTransformationStage) {
+            case NodeTransformationStage.Simplifying:
+                return {
+                    leave: (
+                        node: ESTree.Node,
+                        parentNode: ESTree.Node | null
+                    ): ESTree.Node | undefined => {
+                        if (parentNode && NodeGuards.isIfStatementNode(node)) {
+                            return this.transformNode(node, parentNode);
+                        }
+                    }
+                };
+
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * @param {ESTree.IfStatement} ifStatementNode
+     * @param {ESTree.Node} parentNode
+     * @returns {ESTree.IfStatement}
+     */
+    public transformNode (
+        ifStatementNode: ESTree.IfStatement,
+        parentNode: ESTree.Node
+    ): ESTree.Node {
+        const consequentSimplifyData: IIfStatementSimplifyData | null = this.getIfStatementSimplifyData(ifStatementNode.consequent);
+
+        // Variant #1: no valid consequent expression data
+        if (!consequentSimplifyData) {
+            return ifStatementNode;
+        }
+
+        let transformedNode: ESTree.Node;
+
+        if (!ifStatementNode.alternate) {
+            // Variant #2: valid data for consequent expression only
+            transformedNode = this.getConsequentNode(ifStatementNode, consequentSimplifyData);
+        } else {
+            const alternateSimplifyData: IIfStatementSimplifyData | null = this.getIfStatementSimplifyData(ifStatementNode.alternate);
+
+            if (!alternateSimplifyData) {
+                return ifStatementNode;
+            }
+
+            // Variant #3: valid data for consequent and alternate expressions
+            transformedNode = this.getConsequentAndAlternateNode(ifStatementNode, consequentSimplifyData, alternateSimplifyData);
+        }
+
+        return NodeUtils.parentizeNode(transformedNode, parentNode);
+    }
+
+    /**
+     * @param {ESTree.IfStatement} ifStatementNode
+     * @param {IIfStatementSimplifyData} consequentSimplifyData
+     * @returns {ESTree.Node}
+     */
+    private getConsequentNode (
+        ifStatementNode: ESTree.IfStatement,
+        consequentSimplifyData: IIfStatementSimplifyData
+    ): ESTree.Node {
+        /**
+         * Converts:
+         * if (true) {
+         *     const foo = 1;
+         *     console.log(1);
+         *     return 1;
+         * }
+         *
+         * to:
+         * if (true) {
+         *     const foo = 1;
+         *     return console.log(1), 1;
+         * }
+         */
+        if (
+            consequentSimplifyData.leadingStatements.length
+            || !consequentSimplifyData.trailingStatement
+        ) {
+            return NodeFactory.ifStatementNode(
+                ifStatementNode.test,
+                this.getPartialIfStatementBranchNode(consequentSimplifyData)
+            );
+        }
+
+        /**
+         * Converts:
+         * if (true) {
+         *     return 1;
+         * }
+         *
+         * to:
+         * if (true)
+         *     return 1;
+         */
+        if (consequentSimplifyData.hasReturnStatement) {
+            return NodeFactory.ifStatementNode(
+                ifStatementNode.test,
+                consequentSimplifyData.trailingStatement.statement
+            );
+        }
+
+        /**
+         * Converts:
+         * if (true) {
+         *     console.log(1);
+         * }
+         *
+         * to:
+         * true && console.log(1);
+         */
+        return NodeFactory.expressionStatementNode(
+            NodeFactory.logicalExpressionNode(
+                '&&',
+                ifStatementNode.test,
+                consequentSimplifyData.trailingStatement.expression
+            )
+        );
+    }
+
+    /**
+     * @param {ESTree.IfStatement} ifStatementNode
+     * @param {IIfStatementSimplifyData} consequentSimplifyData
+     * @param {IIfStatementSimplifyData} alternateSimplifyData
+     * @returns {ESTree.Node}
+     */
+    private getConsequentAndAlternateNode (
+        ifStatementNode: ESTree.IfStatement,
+        consequentSimplifyData: IIfStatementSimplifyData,
+        alternateSimplifyData: IIfStatementSimplifyData
+    ): ESTree.Node {
+        /**
+         * Converts:
+         * if (true) {
+         *     const foo = 1;
+         *     console.log(1);
+         *     return 1;
+         * }
+         *
+         * to:
+         * if (true) {
+         *     const foo = 1;
+         *     return console.log(1), 1;
+         * }
+         */
+        if (
+            consequentSimplifyData.leadingStatements.length
+            || alternateSimplifyData.leadingStatements.length
+            || !consequentSimplifyData.trailingStatement
+            || !alternateSimplifyData.trailingStatement
+        ) {
+            return NodeFactory.ifStatementNode(
+                ifStatementNode.test,
+                this.getPartialIfStatementBranchNode(consequentSimplifyData),
+                this.getPartialIfStatementBranchNode(alternateSimplifyData)
+            );
+        }
+
+        /**
+         * Converts:
+         * if (true) {
+         *     return 1;
+         * } else {
+         *     return 2;
+         * }
+         *
+         * to:
+         * return true ? 1 : 2;
+         */
+        if (consequentSimplifyData.hasReturnStatement && alternateSimplifyData.hasReturnStatement) {
+            return NodeFactory.returnStatementNode(
+                NodeFactory.conditionalExpressionNode(
+                    ifStatementNode.test,
+                    consequentSimplifyData.trailingStatement.expression,
+                    alternateSimplifyData.trailingStatement.expression
+                )
+            );
+        }
+
+        /**
+         * Converts:
+         * if (true) {
+         *     return 1;
+         * } else {
+         *     console.log(2);
+         * }
+         *
+         * to:
+         * if (true)
+         *     return 1;
+         * else
+         *     console.log(2);
+         */
+        if (consequentSimplifyData.hasReturnStatement || alternateSimplifyData.hasReturnStatement) {
+            return NodeFactory.ifStatementNode(
+                ifStatementNode.test,
+                consequentSimplifyData.trailingStatement.statement,
+                alternateSimplifyData.trailingStatement.statement
+            );
+        }
+
+        /**
+         * Converts:
+         * if (true) {
+         *     console.log(1);
+         * } else {
+         *     console.log(2);
+         * }
+         *
+         * to:
+         * true ? console.log(1) : console.log(2);
+         */
+        return NodeFactory.expressionStatementNode(
+            NodeFactory.conditionalExpressionNode(
+                ifStatementNode.test,
+                consequentSimplifyData.trailingStatement.expression,
+                alternateSimplifyData.trailingStatement.expression
+            )
+        );
+    }
+
+    /**
+     * Returns IIfStatementSimplifyData based on `IfStatement` node body
+     *
+     * @param {ESTree.Statement | null | undefined} statementNode
+     * @returns {IIfStatementSimplifyData | null}
+     */
+    private getIfStatementSimplifyData (
+        statementNode: ESTree.Statement | null | undefined
+    ): IIfStatementSimplifyData | null {
+        if (!statementNode) {
+            return null;
+        }
+
+        if (!NodeGuards.isBlockStatementNode(statementNode)) {
+            return {
+                leadingStatements: [statementNode],
+                trailingStatement: null,
+                hasReturnStatement: false,
+                hasSingleExpression: false
+            };
+        }
+
+        const {
+            startIndex,
+            unwrappedExpressions,
+            hasReturnStatement
+        } = this.collectIteratedStatementsData(statementNode);
+
+        const leadingStatements: ESTree.Statement[] = this.getLeadingStatements(statementNode, startIndex);
+
+        if (!unwrappedExpressions.length) {
+            return {
+                leadingStatements,
+                trailingStatement: null,
+                hasReturnStatement,
+                hasSingleExpression: false
+            };
+        }
+
+        const hasSingleExpression: boolean = unwrappedExpressions.length === 1;
+
+        const expression: ESTree.Expression = hasSingleExpression
+            ? unwrappedExpressions[0]
+            : NodeFactory.sequenceExpressionNode(unwrappedExpressions);
+
+        const statement: ESTree.Statement = hasReturnStatement
+            ? NodeFactory.returnStatementNode(expression)
+            : NodeFactory.expressionStatementNode(expression);
+
+        return {
+            leadingStatements,
+            trailingStatement: {
+                statement,
+                expression
+            },
+            hasReturnStatement,
+            hasSingleExpression
+        };
+    }
+
+    /**
+     * Iterates over `IfStatement` node body and collects data
+     *
+     * @param {ESTree.Statement | null | undefined} statementNode
+     * @returns {IIfStatementIteratedStatementsData}
+     */
+    private collectIteratedStatementsData (
+        statementNode: ESTree.BlockStatement
+    ): IIfStatementIteratedStatementsData {
+        const statementNodeBodyLength: number = statementNode.body.length;
+        const unwrappedExpressions: ESTree.Expression[] = [];
+
+        let hasReturnStatement: boolean = false;
+        let startIndex: number | null = 0;
+
+        for (let i = 0; i < statementNodeBodyLength; i++) {
+            const statementBodyStatementNode: ESTree.Statement = statementNode.body[i];
+
+            if (startIndex === null) {
+                startIndex = i;
+            }
+
+            if (NodeGuards.isExpressionStatementNode(statementBodyStatementNode)) {
+                unwrappedExpressions.push(statementBodyStatementNode.expression);
+                continue;
+            }
+
+            if (
+                NodeGuards.isReturnStatementNode(statementBodyStatementNode)
+                && statementBodyStatementNode.argument
+            ) {
+                unwrappedExpressions.push(statementBodyStatementNode.argument);
+                hasReturnStatement = true;
+                continue;
+            }
+
+            startIndex = null;
+            unwrappedExpressions.length = 0;
+        }
+
+        return {
+            startIndex,
+            unwrappedExpressions,
+            hasReturnStatement
+        };
+    }
+
+    /**
+     * Returns leading statements for IIfStatementSimplifyData
+     *
+     * @param {ESTree.BlockStatement} statementNode
+     * @param {number | null} startIndex
+     * @returns {ESTree.Statement[]}
+     */
+    private getLeadingStatements (statementNode: ESTree.BlockStatement, startIndex: number | null): ESTree.Statement[] {
+        // variant #1: no valid statements inside `IfStatement` are found
+        if (startIndex === null) {
+            return statementNode.body;
+        }
+
+        return startIndex === 0
+            // variant #2: all statements inside `IfStatement` branch are valid
+            ? []
+            // variant #3: only last N statements inside `IfStatement` branch are valid
+            : statementNode.body.slice(0, startIndex);
+    }
+
+    /**
+     * @param {IIfStatementSimplifyData} ifStatementSimplifyData
+     * @returns {ESTree.BlockStatement}
+     */
+    private getPartialIfStatementBranchNode (ifStatementSimplifyData: IIfStatementSimplifyData): ESTree.Statement {
+        // variant #1: all statements inside `IfStatement` branch are valid
+        if (!ifStatementSimplifyData.leadingStatements.length && ifStatementSimplifyData.trailingStatement) {
+            return ifStatementSimplifyData.trailingStatement.statement;
+        }
+
+        // variant #2: only last N statements inside `IfStatement` branch are valid
+        const blockStatementNode: ESTree.BlockStatement = NodeFactory.blockStatementNode([
+            ...ifStatementSimplifyData.leadingStatements.length ? ifStatementSimplifyData.leadingStatements : [],
+            ...ifStatementSimplifyData.trailingStatement ? [ifStatementSimplifyData.trailingStatement.statement] : []
+        ]);
+
+        return blockStatementNode.body.length === 1
+            && !this.isProhibitedSingleStatementForIfStatementBranch(blockStatementNode.body[0])
+            ? blockStatementNode.body[0]
+            : blockStatementNode;
+
+    }
+
+    /**
+     * @param {ESTree.Statement} statement
+     * @returns {boolean}
+     */
+    private isProhibitedSingleStatementForIfStatementBranch (statement: ESTree.Statement): boolean {
+        // TODO: write tests
+        // function declaration is not allowed outside of block in `strict` mode
+        return NodeGuards.isFunctionDeclarationNode(statement)
+            /**
+             * Without ignore it can break following code:
+             * Input:
+             * if (condition1) {
+             *     if (condition2) {
+             *         var foo = bar();
+             *     }
+             * } else {
+             *     var baz = bark();
+             * }
+             *
+             * Invalid output:
+             * if (condition1)
+             *     if (condition2)
+             *         var foo = bar();
+             *     else
+             *         var baz = bark();
+             */
+            || NodeGuards.isIfStatementNode(statement);
+    }
+}

+ 91 - 0
src/node-transformers/simplifying-transformers/VariableDeclarationsMergeTransformer.ts

@@ -0,0 +1,91 @@
+import { inject, injectable, } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
+
+import * as estraverse from 'estraverse';
+import * as ESTree from 'estree';
+
+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 { NodeTransformationStage } from '../../enums/node-transformers/NodeTransformationStage';
+
+import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
+import { NodeGuards } from '../../node/NodeGuards';
+import { NodeStatementUtils } from '../../node/NodeStatementUtils';
+
+/**
+ * replaces:
+ *     var foo = 1;
+ *     var bar = 2;
+ *
+ * on:
+ *     var foo = 1,
+ *         bar = 2;
+ */
+@injectable()
+export class VariableDeclarationsMergeTransformer extends AbstractNodeTransformer {
+    /**
+     * @param {IRandomGenerator} randomGenerator
+     * @param {IOptions} options
+     */
+    public constructor (
+        @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        super(randomGenerator, options);
+    }
+
+    /**
+     * @param {NodeTransformationStage} nodeTransformationStage
+     * @returns {IVisitor | null}
+     */
+    public getVisitor (nodeTransformationStage: NodeTransformationStage): IVisitor | null {
+        switch (nodeTransformationStage) {
+            case NodeTransformationStage.Simplifying:
+                return {
+                    enter: (
+                        node: ESTree.Node,
+                        parentNode: ESTree.Node | null
+                    ): ESTree.Node | estraverse.VisitorOption | undefined => {
+                        if (parentNode && NodeGuards.isVariableDeclarationNode(node)) {
+                            return this.transformNode(node, parentNode);
+                        }
+                    }
+                };
+
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * @param {ESTree.VariableDeclaration} variableDeclarationNode
+     * @param {ESTree.Node} parentNode
+     * @returns {ESTree.VariableDeclaration | estraverse.VisitorOption}
+     */
+    public transformNode (
+        variableDeclarationNode: ESTree.VariableDeclaration,
+        parentNode: ESTree.Node
+    ): ESTree.VariableDeclaration | estraverse.VisitorOption {
+        if (!NodeGuards.isNodeWithStatements(parentNode)) {
+            return variableDeclarationNode;
+        }
+
+        const prevStatement: TStatement | null = NodeStatementUtils.getPreviousSiblingStatement(variableDeclarationNode);
+
+        if (!prevStatement || !NodeGuards.isVariableDeclarationNode(prevStatement)) {
+            return variableDeclarationNode;
+        }
+
+        if (variableDeclarationNode.kind !== prevStatement.kind) {
+            return variableDeclarationNode;
+        }
+
+        prevStatement.declarations.push(...variableDeclarationNode.declarations);
+
+        return estraverse.VisitorOption.Remove;
+    }
+}

+ 38 - 6
src/node/NodeFactory.ts

@@ -114,6 +114,26 @@ export class NodeFactory {
         };
     }
 
+    /**
+     * @param {ESTree.Expression} test
+     * @param {ESTree.Expression} consequent
+     * @param {ESTree.Expression} alternate
+     * @returns {ESTree.ConditionalExpression}
+     */
+    public static conditionalExpressionNode (
+        test: ESTree.Expression,
+        consequent: ESTree.Expression,
+        alternate: ESTree.Expression
+    ): ESTree.ConditionalExpression {
+        return {
+            type: NodeType.ConditionalExpression,
+            test,
+            consequent,
+            alternate,
+            metadata: { ignoredNode: false }
+        };
+    }
+
     /**
      * @param {Identifier} label
      * @returns {ContinueStatement}
@@ -195,15 +215,15 @@ export class NodeFactory {
     }
 
     /**
-     * @param {Expression} test
-     * @param {BlockStatement} consequent
-     * @param {BlockStatement} alternate
-     * @returns {IfStatement}
+     * @param {ESTree.Expression} test
+     * @param {ESTree.Statement} consequent
+     * @param {ESTree.Statement | null} alternate
+     * @returns {ESTree.IfStatement}
      */
     public static ifStatementNode (
         test: ESTree.Expression,
-        consequent: ESTree.BlockStatement,
-        alternate?: ESTree.BlockStatement
+        consequent: ESTree.Statement,
+        alternate?: ESTree.Statement | null
     ): ESTree.IfStatement {
         return {
             type: NodeType.IfStatement,
@@ -350,6 +370,18 @@ export class NodeFactory {
         };
     }
 
+    /**
+     * @param {ESTree.Expression[]} expressions
+     * @returns {ESTree.SequenceExpression}
+     */
+    public static sequenceExpressionNode (expressions: ESTree.Expression[]): ESTree.SequenceExpression {
+        return {
+            type: NodeType.SequenceExpression,
+            expressions,
+            metadata: { ignoredNode: false }
+        };
+    }
+
     /**
      * @param {Expression} discriminant
      * @param {SwitchCase[]} cases

+ 8 - 0
src/node/NodeGuards.ts

@@ -168,6 +168,14 @@ export class NodeGuards {
         return node.type === NodeType.Identifier;
     }
 
+    /**
+     * @param {Node} node
+     * @returns {boolean}
+     */
+    public static isIfStatementNode (node: ESTree.Node): node is ESTree.IfStatement {
+        return node.type === NodeType.IfStatement;
+    }
+
     /**
      * @param {Node} node
      * @returns {boolean}

+ 6 - 0
src/options/Options.ts

@@ -200,6 +200,12 @@ export class Options implements IOptions {
     @IsBoolean()
     public readonly shuffleStringArray!: boolean;
 
+    /**
+     * @type {boolean}
+     */
+    @IsBoolean()
+    public readonly simplify!: boolean;
+
     /**
      * @type {boolean}
      */

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

@@ -29,6 +29,7 @@ export const DEFAULT_PRESET: TInputOptions = Object.freeze({
     seed: 0,
     selfDefending: false,
     shuffleStringArray: true,
+    simplify: true,
     sourceMap: false,
     sourceMapBaseUrl: '',
     sourceMapFileName: '',

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

@@ -28,6 +28,7 @@ export const NO_ADDITIONAL_NODES_PRESET: TInputOptions = Object.freeze({
     seed: 0,
     selfDefending: false,
     shuffleStringArray: false,
+    simplify: false,
     sourceMap: false,
     sourceMapBaseUrl: '',
     sourceMapFileName: '',

+ 1 - 1
src/types/container/custom-code-helpers/TCustomCodeHelperFactory.ts

@@ -3,5 +3,5 @@ import { ICustomCodeHelper } from '../../../interfaces/custom-code-helpers/ICust
 import { CustomCodeHelper } from '../../../enums/custom-code-helpers/CustomCodeHelper';
 
 export type TCustomCodeHelperFactory = <
-    TInitialData extends any[] = any[]
+    TInitialData extends unknown[] = unknown[]
 > (customCodeHelperName: CustomCodeHelper) => ICustomCodeHelper<TInitialData>;

+ 1 - 1
src/types/container/custom-nodes/TControlFlowCustomNodeFactory.ts

@@ -3,5 +3,5 @@ import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
 import { ControlFlowCustomNode } from '../../../enums/custom-nodes/ControlFlowCustomNode';
 
 export type TControlFlowCustomNodeFactory = <
-    TInitialData extends any[] = any[]
+    TInitialData extends unknown[] = unknown[]
 > (controlFlowCustomNodeName: ControlFlowCustomNode) => ICustomNode<TInitialData>;

+ 1 - 1
src/types/container/custom-nodes/TDeadNodeInjectionCustomNodeFactory.ts

@@ -3,5 +3,5 @@ import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
 import { DeadCodeInjectionCustomNode } from '../../../enums/custom-nodes/DeadCodeInjectionCustomNode';
 
 export type TDeadNodeInjectionCustomNodeFactory = <
-    TInitialData extends any[] = any[]
+    TInitialData extends unknown[] = unknown[]
 > (deadCodeInjectionCustomNodeName: DeadCodeInjectionCustomNode) => ICustomNode <TInitialData>;

+ 1 - 1
src/types/container/custom-nodes/TObjectExpressionKeysTransformerCustomNodeFactory.ts

@@ -3,5 +3,5 @@ import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
 import { ObjectExpressionKeysTransformerCustomNode } from '../../../enums/custom-nodes/ObjectExpressionKeysTransformerCustomNode';
 
 export type TObjectExpressionKeysTransformerCustomNodeFactory = <
-    TInitialData extends any[] = any[]
+    TInitialData extends unknown[] = unknown[]
 > (objectExpressionKeysTransformerNodeName: ObjectExpressionKeysTransformerCustomNode) => ICustomNode <TInitialData>;

+ 11 - 5
test/dev/dev.ts

@@ -7,14 +7,20 @@ import { NO_ADDITIONAL_NODES_PRESET } from '../../src/options/presets/NoCustomNo
 
     let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
         `
-            const foo = 'abc';
-            console.log(foo);
+            function foo() {
+                if (true) {
+                    if (false) {
+                        var bar = baz();
+                    }
+                } else {
+                    var bark = hawk();
+                }
+            }
         `,
         {
             ...NO_ADDITIONAL_NODES_PRESET,
-            stringArray: true,
-            stringArrayThreshold: 1,
-            stringArrayEncoding: 'base64',
+            compact: false,
+            simplify: true
         }
     ).getObfuscatedCode();
 

+ 2 - 0
test/functional-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts

@@ -789,6 +789,8 @@ describe('JavaScriptObfuscator', () => {
                         deadCodeInjection: true,
                         deadCodeInjectionThreshold: 1,
                         disableConsoleOutput: false,
+                        simplify: true,
+                        renameProperties: true,
                         rotateStringArray: true,
                         stringArray: true,
                         stringArrayEncoding: StringArrayEncoding.Rc4,

+ 814 - 0
test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/IfStatementSimplifyTransformer.spec.ts

@@ -0,0 +1,814 @@
+import { assert } from 'chai';
+
+import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
+
+import { readFileAsString } from '../../../../helpers/readFileAsString';
+
+import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
+
+describe('IfStatementSimplifyTransformer', () => {
+    describe('Full `IfStatement` simplify cases', () => {
+        describe('Consequent only', () => {
+            describe('No `ReturnStatement`', () => {
+                describe('Variant #1: single statement', () => {
+                    const regExp: RegExp = new RegExp(
+                        '!!\\[] *&& *bar\\(\\);'
+                    );
+
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/full-consequent-only-no-return-single-statement.js');
+
+                        obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                ...NO_ADDITIONAL_NODES_PRESET,
+                                simplify: true
+                            }
+                        ).getObfuscatedCode();
+                    });
+
+                    it('should simplify if statement', () => {
+                        assert.match(obfuscatedCode, regExp);
+                    });
+                });
+
+                describe('Variant #2: multiple statements', () => {
+                    const regExp: RegExp = new RegExp(
+                        '!!\\[] *&& *\\(bar\\(\\) *, *baz\\(\\) *, *bark\\(\\)\\);'
+                    );
+
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/full-consequent-only-no-return-multiple-statements.js');
+
+                        obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                ...NO_ADDITIONAL_NODES_PRESET,
+                                simplify: true
+                            }
+                        ).getObfuscatedCode();
+                    });
+
+                    it('should simplify if statement', () => {
+                        assert.match(obfuscatedCode, regExp);
+                    });
+                });
+            });
+
+            describe('With `ReturnStatement`', () => {
+                describe('Variant #1: single statement', () => {
+                    const regExp: RegExp = new RegExp(
+                        'if *\\(!!\\[]\\) *' +
+                            'return *bar\\(\\);'
+                    );
+
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/full-consequent-only-return-single-statement.js');
+
+                        obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                ...NO_ADDITIONAL_NODES_PRESET,
+                                simplify: true
+                            }
+                        ).getObfuscatedCode();
+                    });
+
+                    it('should simplify if statement', () => {
+                        assert.match(obfuscatedCode, regExp);
+                    });
+                });
+
+                describe('Variant #2: multiple statements', () => {
+                    const regExp: RegExp = new RegExp(
+                        'if *\\(!!\\[]\\) *' +
+                            'return *bar\\(\\) *, *baz\\(\\) *, *bark\\(\\);'
+                    );
+
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/full-consequent-only-return-multiple-statements.js');
+
+                        obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                ...NO_ADDITIONAL_NODES_PRESET,
+                                simplify: true
+                            }
+                        ).getObfuscatedCode();
+                    });
+
+                    it('should simplify if statement', () => {
+                        assert.match(obfuscatedCode, regExp);
+                    });
+                });
+            });
+        });
+
+        describe('Consequent and alternate', () => {
+            describe('No `ReturnStatement`', () => {
+                describe('Variant #1: single statement', () => {
+                    const regExp: RegExp = new RegExp(
+                        '!!\\[] *' +
+                            '\\? *bar\\(\\) *' +
+                            ': *baz\\(\\);'
+                    );
+
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/full-consequent-and-alternate-no-return-single-statement.js');
+
+                        obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                ...NO_ADDITIONAL_NODES_PRESET,
+                                simplify: true
+                            }
+                        ).getObfuscatedCode();
+                    });
+
+                    it('should simplify if statement', () => {
+                        assert.match(obfuscatedCode, regExp);
+                    });
+                });
+
+                describe('Variant #2: multiple statements', () => {
+                    const regExp: RegExp = new RegExp(
+                        '!!\\[] *' +
+                            '\\? *\\(bar\\(\\) *, *baz\\(\\) *, *bark\\(\\)\\) *' +
+                            ': *\\(hawk\\(\\) *, *pork\\(\\) *, *eagle\\(\\)\\);'
+                    );
+
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/full-consequent-and-alternate-no-return-multiple-statements.js');
+
+                        obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                ...NO_ADDITIONAL_NODES_PRESET,
+                                simplify: true
+                            }
+                        ).getObfuscatedCode();
+                    });
+
+                    it('should simplify if statement', () => {
+                        assert.match(obfuscatedCode, regExp);
+                    });
+                });
+            });
+
+            describe('With consequent `ReturnStatement`', () => {
+                describe('Variant #1: single statement', () => {
+                    const regExp: RegExp = new RegExp(
+                        'if *\\(!!\\[]\\) *' +
+                            'return *bar\\(\\); *' +
+                        'else *' +
+                            'baz\\(\\);'
+                    );
+
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/full-consequent-and-alternate-consequent-return-single-statement.js');
+
+                        obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                ...NO_ADDITIONAL_NODES_PRESET,
+                                simplify: true
+                            }
+                        ).getObfuscatedCode();
+                    });
+
+                    it('should simplify if statement', () => {
+                        assert.match(obfuscatedCode, regExp);
+                    });
+                });
+
+                describe('Variant #2: multiple statements', () => {
+                    const regExp: RegExp = new RegExp(
+                        'if *\\(!!\\[]\\) *' +
+                            'return *bar\\(\\) *, *baz\\(\\) *, *bark\\(\\); *' +
+                        'else *' +
+                            'hawk\\(\\) *, *pork\\(\\) *, *eagle\\(\\);'
+                    );
+
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/full-consequent-and-alternate-consequent-return-multiple-statements.js');
+
+                        obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                ...NO_ADDITIONAL_NODES_PRESET,
+                                simplify: true
+                            }
+                        ).getObfuscatedCode();
+                    });
+
+                    it('should simplify if statement', () => {
+                        assert.match(obfuscatedCode, regExp);
+                    });
+                });
+            });
+
+            describe('With alternate `ReturnStatement`', () => {
+                describe('Variant #1: single statement', () => {
+                    const regExp: RegExp = new RegExp(
+                        'if *\\(!!\\[]\\) *' +
+                            'bar\\(\\); *' +
+                        'else *' +
+                            'return *baz\\(\\);'
+                    );
+
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/full-consequent-and-alternate-alternate-return-single-statement.js');
+
+                        obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                ...NO_ADDITIONAL_NODES_PRESET,
+                                simplify: true
+                            }
+                        ).getObfuscatedCode();
+                    });
+
+                    it('should simplify if statement', () => {
+                        assert.match(obfuscatedCode, regExp);
+                    });
+                });
+
+                describe('Variant #2: multiple statements', () => {
+                    const regExp: RegExp = new RegExp(
+                        'if *\\(!!\\[]\\) *' +
+                            'bar\\(\\) *, *baz\\(\\) *, *bark\\(\\); *' +
+                        'else *' +
+                            'return *hawk\\(\\) *, *pork\\(\\) *, *eagle\\(\\);'
+                    );
+
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/full-consequent-and-alternate-alternate-return-multiple-statements.js');
+
+                        obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                ...NO_ADDITIONAL_NODES_PRESET,
+                                simplify: true
+                            }
+                        ).getObfuscatedCode();
+                    });
+
+                    it('should simplify if statement', () => {
+                        assert.match(obfuscatedCode, regExp);
+                    });
+                });
+            });
+
+            describe('With consequent and alternate `ReturnStatement`', () => {
+                describe('Variant #1: single statement', () => {
+                    const regExp: RegExp = new RegExp(
+                        'return *!!\\[] *' +
+                            '\\? *bar\\(\\) *' +
+                            ': *baz\\(\\);'
+                    );
+
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/full-consequent-and-alternate-return-single-statement.js');
+
+                        obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                ...NO_ADDITIONAL_NODES_PRESET,
+                                simplify: true
+                            }
+                        ).getObfuscatedCode();
+                    });
+
+                    it('should simplify if statement', () => {
+                        assert.match(obfuscatedCode, regExp);
+                    });
+                });
+
+                describe('Variant #2: multiple statements', () => {
+                    const regExp: RegExp = new RegExp(
+                        'return *!!\\[] *' +
+                            '\\? *\\(bar\\(\\) *, *baz\\(\\) *, *bark\\(\\)\\) *' +
+                            ': *\\(hawk\\(\\) *, *pork\\(\\) *, *eagle\\(\\)\\);'
+                    );
+
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/full-consequent-and-alternate-return-multiple-statements.js');
+
+                        obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                ...NO_ADDITIONAL_NODES_PRESET,
+                                simplify: true
+                            }
+                        ).getObfuscatedCode();
+                    });
+
+                    it('should simplify if statement', () => {
+                        assert.match(obfuscatedCode, regExp);
+                    });
+                });
+            });
+        });
+    });
+
+    describe('Partial `IfStatement` simplify cases', () => {
+        describe('Consequent only', () => {
+            describe('No `ReturnStatement`', () => {
+                describe('Variant #1: single statement', () => {
+                    const regExp: RegExp = new RegExp(
+                        'if *\\(!!\\[]\\) *' +
+                            'const _0x([a-f0-9]){4,6} *= *baz\\(\\);'
+                    );
+
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/partial-consequent-only-no-return-single-statement.js');
+
+                        obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                ...NO_ADDITIONAL_NODES_PRESET,
+                                simplify: true
+                            }
+                        ).getObfuscatedCode();
+                    });
+
+                    it('should not simplify if statement', () => {
+                        assert.match(obfuscatedCode, regExp);
+                    });
+                });
+
+                describe('Variant #2: multiple statements', () => {
+                    const regExp: RegExp = new RegExp(
+                        'if *\\(!!\\[]\\) *{ *' +
+                            'const _0x([a-f0-9]){4,6} *= *baz\\(\\); *' +
+                            'bark\\(\\), *hawk\\(\\);' +
+                        '}'
+                    );
+
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/partial-consequent-only-no-return-multiple-statements.js');
+
+                        obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                ...NO_ADDITIONAL_NODES_PRESET,
+                                simplify: true
+                            }
+                        ).getObfuscatedCode();
+                    });
+
+                    it('should simplify if statement', () => {
+                        assert.match(obfuscatedCode, regExp);
+                    });
+                });
+            });
+
+            describe('With `ReturnStatement`', () => {
+                describe('Variant #1: multiple statements', () => {
+                    const regExp: RegExp = new RegExp(
+                        'if *\\(!!\\[]\\) *{ *' +
+                            'const _0x([a-f0-9]){4,6} *= *baz\\(\\); *' +
+                            'return bark\\(\\), *hawk\\(\\);' +
+                        '}'
+                    );
+
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/partial-consequent-only-return-multiple-statements.js');
+
+                        obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                ...NO_ADDITIONAL_NODES_PRESET,
+                                simplify: true
+                            }
+                        ).getObfuscatedCode();
+                    });
+
+                    it('should simplify if statement', () => {
+                        assert.match(obfuscatedCode, regExp);
+                    });
+                });
+            });
+        });
+
+        describe('Consequent and alternate', () => {
+            describe('No `ReturnStatement`', () => {
+                describe('Variant #1: single statement', () => {
+                    const regExp: RegExp = new RegExp(
+                        'if *\\(!!\\[]\\) *' +
+                            'const *_0x([a-f0-9]){4,6} *= *baz\\(\\); *' +
+                        'else *' +
+                            'const *_0x([a-f0-9]){4,6} *= *hawk\\(\\);'
+                    );
+
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/partial-consequent-and-alternate-no-return-single-statement.js');
+
+                        obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                ...NO_ADDITIONAL_NODES_PRESET,
+                                simplify: true
+                            }
+                        ).getObfuscatedCode();
+                    });
+
+                    it('should not simplify if statement', () => {
+                        assert.match(obfuscatedCode, regExp);
+                    });
+                });
+
+                describe('Variant #2: multiple statements', () => {
+                    const regExp: RegExp = new RegExp(
+                        'if *\\(!!\\[]\\) *{ *' +
+                            'const *_0x([a-f0-9]){4,6} *= *baz\\(\\), *' +
+                                '_0x([a-f0-9]){4,6} *= *hawk\\(\\); *' +
+                            'eagle\\(\\), *pork\\(\\);' +
+                        '} *else *{ *' +
+                            'const *_0x([a-f0-9]){4,6} *= *cow\\(\\); *' +
+                            'lion\\(\\), *pig\\(\\);' +
+                        '}'
+                    );
+
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/partial-consequent-and-alternate-no-return-multiple-statements.js');
+
+                        obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                ...NO_ADDITIONAL_NODES_PRESET,
+                                simplify: true
+                            }
+                        ).getObfuscatedCode();
+                    });
+
+                    it('should simplify if statement', () => {
+                        assert.match(obfuscatedCode, regExp);
+                    });
+                });
+
+                describe('Variant #3: mixed statements #1', () => {
+                    const regExp: RegExp = new RegExp(
+                        'if *\\(!!\\[]\\) *' +
+                            'const *_0x([a-f0-9]){4,6} *= *baz\\(\\); *' +
+                        'else *{ *' +
+                            'const *_0x([a-f0-9]){4,6} *= *hawk\\(\\); *' +
+                            'eagle\\(\\), *dog\\(\\);' +
+                        '}'
+                    );
+
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/partial-consequent-and-alternate-no-return-mixed-statements-1.js');
+
+                        obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                ...NO_ADDITIONAL_NODES_PRESET,
+                                simplify: true
+                            }
+                        ).getObfuscatedCode();
+                    });
+
+                    it('should simplify if statement', () => {
+                        assert.match(obfuscatedCode, regExp);
+                    });
+                });
+
+                describe('Variant #4: mixed statements #2', () => {
+                    const regExp: RegExp = new RegExp(
+                        'if *\\(!!\\[]\\) *{ *' +
+                            'const *_0x([a-f0-9]){4,6} *= *baz\\(\\), *' +
+                                '_0x([a-f0-9]){4,6} *= *hawk\\(\\); *' +
+                            'eagle\\(\\), *pork\\(\\);' +
+                        '} *else *' +
+                            'const *_0x([a-f0-9]){4,6} *= *cow\\(\\);'
+                    );
+
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/partial-consequent-and-alternate-no-return-mixed-statements-2.js');
+
+                        obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                ...NO_ADDITIONAL_NODES_PRESET,
+                                simplify: true
+                            }
+                        ).getObfuscatedCode();
+                    });
+
+                    it('should simplify if statement', () => {
+                        assert.match(obfuscatedCode, regExp);
+                    });
+                });
+            });
+
+            describe('With consequent `ReturnStatement`', () => {
+                describe('Variant #1: single statement', () => {
+                    const regExp: RegExp = new RegExp(
+                        'if *\\(!!\\[]\\) *' +
+                            'return *bar\\(\\); *' +
+                        'else *' +
+                            'const *_0x([a-f0-9]){4,6} *= *bark\\(\\);'
+                    );
+
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/partial-consequent-and-alternate-consequent-return-single-statement.js');
+
+                        obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                ...NO_ADDITIONAL_NODES_PRESET,
+                                simplify: true
+                            }
+                        ).getObfuscatedCode();
+                    });
+
+                    it('should simplify if statement', () => {
+                        assert.match(obfuscatedCode, regExp);
+                    });
+                });
+
+                describe('Variant #2: multiple statements', () => {
+                    const regExp: RegExp = new RegExp(
+                        'if *\\(!!\\[]\\) *{ *' +
+                            'const *_0x([a-f0-9]){4,6} *= *baz\\(\\), *' +
+                                '_0x([a-f0-9]){4,6} *= *hawk\\(\\); *' +
+                            'return *eagle\\(\\), *cat\\(\\);' +
+                        '} *else *{ *' +
+                            'const *_0x([a-f0-9]){4,6} *= *pig\\(\\); *' +
+                            'lion\\(\\), *dog\\(\\);' +
+                        '}'
+                    );
+
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/partial-consequent-and-alternate-consequent-return-multiple-statements.js');
+
+                        obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                ...NO_ADDITIONAL_NODES_PRESET,
+                                simplify: true
+                            }
+                        ).getObfuscatedCode();
+                    });
+
+                    it('should simplify if statement', () => {
+                        assert.match(obfuscatedCode, regExp);
+                    });
+                });
+            });
+
+            describe('With alternate `ReturnStatement`', () => {
+                describe('Variant #1: single statement', () => {
+                    const regExp: RegExp = new RegExp(
+                        'if *\\(!!\\[]\\) *' +
+                            'const *_0x([a-f0-9]){4,6} *= *baz\\(\\); *' +
+                        'else *' +
+                            'return *bark\\(\\);'
+                    );
+
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/partial-consequent-and-alternate-alternate-return-single-statement.js');
+
+                        obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                ...NO_ADDITIONAL_NODES_PRESET,
+                                simplify: true
+                            }
+                        ).getObfuscatedCode();
+                    });
+
+                    it('should simplify if statement', () => {
+                        assert.match(obfuscatedCode, regExp);
+                    });
+                });
+
+                describe('Variant #2: multiple statements', () => {
+                    const regExp: RegExp = new RegExp(
+                        'if *\\(!!\\[]\\) *{ *' +
+                            'const *_0x([a-f0-9]){4,6} *= *baz\\(\\); *' +
+                            'bark\\(\\), *hawk\\(\\);' +
+                        '} *else *{ *' +
+                            'const *_0x([a-f0-9]){4,6} *= *pork\\(\\), *' +
+                                '_0x([a-f0-9]){4,6} *= *dog\\(\\); *' +
+                            'return *pig\\(\\), *lion\\(\\);' +
+                        '}'
+                    );
+
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/partial-consequent-and-alternate-alternate-return-multiple-statements.js');
+
+                        obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                ...NO_ADDITIONAL_NODES_PRESET,
+                                simplify: true
+                            }
+                        ).getObfuscatedCode();
+                    });
+
+                    it('should simplify if statement', () => {
+                        assert.match(obfuscatedCode, regExp);
+                    });
+                });
+            });
+
+            describe('With consequent and alternate `ReturnStatement`', () => {
+                describe('Variant #1: multiple statements', () => {
+                    const regExp: RegExp = new RegExp(
+                        'if *\\(!!\\[]\\) *{ *' +
+                            'const *_0x([a-f0-9]){4,6} *= *baz\\(\\), *' +
+                                '_0x([a-f0-9]){4,6} *= *eagle\\(\\); *' +
+                            'return *hawk\\(\\), *lion\\(\\);' +
+                        '} *else *{ *' +
+                            'const *_0x([a-f0-9]){4,6} *= *dog\\(\\), *' +
+                                '_0x([a-f0-9]){4,6} *= *hamster\\(\\); *' +
+                            'return *parrot\\(\\), *bull\\(\\);' +
+                        '}'
+                    );
+
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/partial-consequent-and-alternate-return-multiple-statements.js');
+
+                        obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                            code,
+                            {
+                                ...NO_ADDITIONAL_NODES_PRESET,
+                                simplify: true
+                            }
+                        ).getObfuscatedCode();
+                    });
+
+                    it('should simplify if statement', () => {
+                        assert.match(obfuscatedCode, regExp);
+                    });
+                });
+            });
+        });
+    });
+
+    describe('Cases', () => {
+        describe('Variable declarations merge transformer integration', () => {
+            describe('Variant #1: three statements', () => {
+                const regExp: RegExp = new RegExp(
+                    'if *\\(!!\\[]\\) *' +
+                        'const _0x([a-f0-9]){4,6} *= *function *\\(\\) *{}, *' +
+                            '_0x([a-f0-9]){4,6} *= *function *\\(\\) *{}, *' +
+                            '_0x([a-f0-9]){4,6} *= *function *\\(\\) *{};'
+                );
+
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/variable-declarations-merge-transformer-integration-1.js');
+
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            simplify: true
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should simplify if statement', () => {
+                    assert.match(obfuscatedCode, regExp);
+                });
+            });
+        });
+
+        describe('Prohibited single statement', () => {
+            describe('Variant #1: `IfStatement` as prohibited single statement', () => {
+                const regExp: RegExp = new RegExp(
+                    'if *\\(!!\\[]\\) *{ *' +
+                        'if *\\(!\\[]\\) *' +
+                            'var _0x([a-f0-9]){4,6} *= *baz\\(\\); *' +
+                    '} *else *' +
+                        'var _0x([a-f0-9]){4,6} *= *hawk\\(\\);'
+                );
+
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/if-statement-as-prohibited-single-statement.js');
+
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            simplify: true
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should not simplify if statement', () => {
+                    assert.match(obfuscatedCode, regExp);
+                });
+            });
+
+            describe('Variant #2: `FunctionDeclaration` as prohibited single statement', () => {
+                const regExp: RegExp = new RegExp(
+                    'if *\\(!!\\[]\\) *{ *' +
+                        'function _0x([a-f0-9]){4,6} *\\(\\) *{} *' +
+                    '} *else *{ *' +
+                        'function _0x([a-f0-9]){4,6} *\\(\\) *{} *' +
+                    '}'
+                );
+
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/function-declaration-as-prohibited-single-statement.js');
+
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            simplify: true
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should not simplify if statement', () => {
+                    assert.match(obfuscatedCode, regExp);
+                });
+            });
+        });
+    });
+});

+ 12 - 0
test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/full-consequent-and-alternate-alternate-return-multiple-statements.js

@@ -0,0 +1,12 @@
+function foo () {
+    if (true) {
+        bar();
+        baz();
+        bark();
+    } else {
+        hawk();
+        pork();
+
+        return eagle();
+    }
+}

+ 7 - 0
test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/full-consequent-and-alternate-alternate-return-single-statement.js

@@ -0,0 +1,7 @@
+function foo () {
+    if (true) {
+        bar();
+    } else {
+        return baz();
+    }
+}

+ 12 - 0
test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/full-consequent-and-alternate-consequent-return-multiple-statements.js

@@ -0,0 +1,12 @@
+function foo () {
+    if (true) {
+        bar();
+        baz();
+
+        return bark();
+    } else {
+        hawk();
+        pork();
+        eagle();
+    }
+}

+ 7 - 0
test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/full-consequent-and-alternate-consequent-return-single-statement.js

@@ -0,0 +1,7 @@
+function foo () {
+    if (true) {
+        return bar();
+    } else {
+        baz();
+    }
+}

+ 11 - 0
test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/full-consequent-and-alternate-no-return-multiple-statements.js

@@ -0,0 +1,11 @@
+function foo () {
+    if (true) {
+        bar();
+        baz();
+        bark();
+    } else {
+        hawk();
+        pork();
+        eagle();
+    }
+}

+ 7 - 0
test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/full-consequent-and-alternate-no-return-single-statement.js

@@ -0,0 +1,7 @@
+function foo () {
+    if (true) {
+        bar();
+    } else {
+        baz();
+    }
+}

+ 13 - 0
test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/full-consequent-and-alternate-return-multiple-statements.js

@@ -0,0 +1,13 @@
+function foo () {
+    if (true) {
+        bar();
+        baz();
+
+        return bark();
+    } else {
+        hawk();
+        pork();
+
+        return eagle();
+    }
+}

+ 7 - 0
test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/full-consequent-and-alternate-return-single-statement.js

@@ -0,0 +1,7 @@
+function foo () {
+    if (true) {
+        return bar();
+    } else {
+        return baz();
+    }
+}

+ 7 - 0
test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/full-consequent-only-no-return-multiple-statements.js

@@ -0,0 +1,7 @@
+function foo () {
+    if (true) {
+        bar();
+        baz();
+        bark();
+    }
+}

+ 5 - 0
test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/full-consequent-only-no-return-single-statement.js

@@ -0,0 +1,5 @@
+function foo () {
+    if (true) {
+        bar();
+    }
+}

+ 8 - 0
test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/full-consequent-only-return-multiple-statements.js

@@ -0,0 +1,8 @@
+function foo () {
+    if (true) {
+        bar();
+        baz();
+
+        return bark();
+    }
+}

+ 5 - 0
test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/full-consequent-only-return-single-statement.js

@@ -0,0 +1,5 @@
+function foo () {
+    if (true) {
+        return bar();
+    }
+}

+ 7 - 0
test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/function-declaration-as-prohibited-single-statement.js

@@ -0,0 +1,7 @@
+function foo() {
+    if (true) {
+        function foo() {}
+    } else {
+        function bar() {}
+    }
+}

+ 9 - 0
test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/if-statement-as-prohibited-single-statement.js

@@ -0,0 +1,9 @@
+function foo() {
+    if (true) {
+        if (false) {
+            var bar = baz();
+        }
+    } else {
+        var bark = hawk();
+    }
+}

+ 15 - 0
test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/partial-consequent-and-alternate-alternate-return-multiple-statements.js

@@ -0,0 +1,15 @@
+function foo () {
+    if (true) {
+        const bar = baz();
+
+        bark();
+        hawk();
+    } else {
+        const eagle = pork();
+        const cat = dog()
+
+        pig();
+
+        return lion();
+    }
+}

+ 7 - 0
test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/partial-consequent-and-alternate-alternate-return-single-statement.js

@@ -0,0 +1,7 @@
+function foo () {
+    if (true) {
+        const bar = baz();
+    } else {
+        return bark();
+    }
+}

+ 15 - 0
test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/partial-consequent-and-alternate-consequent-return-multiple-statements.js

@@ -0,0 +1,15 @@
+function foo () {
+    if (true) {
+        const bar = baz();
+        const bark = hawk();
+
+        eagle();
+
+        return cat();
+    } else {
+        const pork = pig();
+
+        lion();
+        dog();
+    }
+}

+ 7 - 0
test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/partial-consequent-and-alternate-consequent-return-single-statement.js

@@ -0,0 +1,7 @@
+function foo () {
+    if (true) {
+        return bar();
+    } else {
+        const baz = bark();
+    }
+}

+ 10 - 0
test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/partial-consequent-and-alternate-no-return-mixed-statements-1.js

@@ -0,0 +1,10 @@
+function foo () {
+    if (true) {
+        const bar = baz();
+    } else {
+        const bark = hawk();
+
+        eagle();
+        dog();
+    }
+}

+ 11 - 0
test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/partial-consequent-and-alternate-no-return-mixed-statements-2.js

@@ -0,0 +1,11 @@
+function foo () {
+    if (true) {
+        const bar = baz();
+        const bark = hawk();
+
+        eagle()
+        pork();
+    } else {
+        const horse = cow();
+    }
+}

+ 14 - 0
test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/partial-consequent-and-alternate-no-return-multiple-statements.js

@@ -0,0 +1,14 @@
+function foo () {
+    if (true) {
+        const bar = baz();
+        const bark = hawk();
+
+        eagle()
+        pork();
+    } else {
+        const horse = cow();
+
+        lion();
+        pig();
+    }
+}

+ 7 - 0
test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/partial-consequent-and-alternate-no-return-single-statement.js

@@ -0,0 +1,7 @@
+function foo () {
+    if (true) {
+        const bar = baz();
+    } else {
+        const bark = hawk();
+    }
+}

+ 17 - 0
test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/partial-consequent-and-alternate-return-multiple-statements.js

@@ -0,0 +1,17 @@
+function foo () {
+    if (true) {
+        const bar = baz();
+        const bark = eagle();
+
+        hawk();
+
+        return lion();
+    } else {
+        const cat = dog();
+        const elephant = hamster();
+
+        parrot();
+
+        return bull();
+    }
+}

+ 8 - 0
test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/partial-consequent-only-no-return-multiple-statements.js

@@ -0,0 +1,8 @@
+function foo () {
+    if (true) {
+        const bar = baz();
+
+        bark();
+        hawk();
+    }
+}

+ 5 - 0
test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/partial-consequent-only-no-return-single-statement.js

@@ -0,0 +1,5 @@
+function foo () {
+    if (true) {
+        const bar = baz();
+    }
+}

+ 9 - 0
test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/partial-consequent-only-return-multiple-statements.js

@@ -0,0 +1,9 @@
+function foo () {
+    if (true) {
+        const bar = baz();
+
+        bark();
+
+        return hawk();
+    }
+}

+ 7 - 0
test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/variable-declarations-merge-transformer-integration-1.js

@@ -0,0 +1,7 @@
+function foo() {
+    if (true) {
+        const bar = function () {};
+        const baz = function () {};
+        const bark = function () {};
+    }
+}

+ 200 - 0
test/functional-tests/node-transformers/simplifying-transformers/variable-declarations-merge-transformer/VariableDeclarationsMergeTransformer.spec.ts

@@ -0,0 +1,200 @@
+import { assert } from 'chai';
+
+import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
+
+import { readFileAsString } from '../../../../helpers/readFileAsString';
+
+import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
+
+describe('VariableDeclarationsMergeTransformer', () => {
+    describe('base behaviour', () => {
+        describe('Variant #1: single variable declaration', () => {
+            const regExp: RegExp = new RegExp(
+                'var foo *= *0x1;'
+            );
+
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/single-declaration.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        simplify: true
+                    }
+                ).getObfuscatedCode();
+            });
+
+            it('should keep single declaration', () => {
+                assert.match(obfuscatedCode, regExp);
+            });
+        });
+
+        describe('Variant #2: multiple variable declarations', () => {
+            const regExp: RegExp = new RegExp(
+                'var foo *= *0x1, *' +
+                    'bar *= *0x2, *' +
+                    'baz *= *0x3;'
+            );
+
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/multiple-declarations.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        simplify: true
+                    }
+                ).getObfuscatedCode();
+            });
+
+            it('should merge variable declarations', () => {
+                assert.match(obfuscatedCode, regExp);
+            });
+        });
+
+        describe('Variant #3: multiple variable declarations with multiple declarators', () => {
+            const regExp: RegExp = new RegExp(
+                'var foo *= *0x1, *' +
+                    'bar *= *0x2, *' +
+                    'baz *= *0x3, *' +
+                    'bark *= *0x4;'
+            );
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/multiple-declarations-with-multiple-declarators.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        simplify: true
+                    }
+                ).getObfuscatedCode();
+            });
+
+            it('should merge variable declarations', () => {
+                assert.match(obfuscatedCode, regExp);
+            });
+        });
+
+        describe('Variant #4: Splitted declarations with other statement', () => {
+            const regExp: RegExp = new RegExp(
+                'var foo *= *0x1, *' +
+                    'bar *= *0x2; *' +
+                'console\\[\'log\']\\(\'123\'\\); *' +
+                'var baz *= *0x3, *' +
+                    'bark *= *0x4;'
+            );
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/splitted-declarations-with-other-statement.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        simplify: true
+                    }
+                ).getObfuscatedCode();
+            });
+
+            it('should merge variable declarations', () => {
+                assert.match(obfuscatedCode, regExp);
+            });
+        });
+
+        describe('Variant #5: multiple variable declarations without declarators', () => {
+            const regExp: RegExp = new RegExp(
+                'var foo, *' +
+                    'bar, *' +
+                    'baz;'
+            );
+
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/multiple-declarations-without-declarators.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        simplify: true
+                    }
+                ).getObfuscatedCode();
+            });
+
+            it('should merge variable declarations', () => {
+                assert.match(obfuscatedCode, regExp);
+            });
+        });
+    });
+
+    describe('object pattern as initializer', () => {
+        const regExp: RegExp = new RegExp(
+            'var foo *= *0x1, *' +
+            '{bar} *= *{\'bar\': *0x2}, *' +
+            'baz *= *0x3;'
+        );
+
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/object-pattern-as-initializer.js');
+
+            obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_ADDITIONAL_NODES_PRESET,
+                    simplify: true
+                }
+            ).getObfuscatedCode();
+        });
+
+        it('should merge variable declarations with object pattern', () => {
+            assert.match(obfuscatedCode, regExp);
+        });
+    });
+
+    describe('variables kind', () => {
+        const regExp: RegExp = new RegExp(
+            'var foo *= *0x1, *' +
+                'bar *= *0x2; *' +
+            'let baz *= *0x3, *' +
+                'bark *= *0x4;' +
+            'const hawk *= *0x5, *' +
+                'pork *= *0x6;'
+        );
+
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/different-variables-kind.js');
+
+            obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_ADDITIONAL_NODES_PRESET,
+                    simplify: true
+                }
+            ).getObfuscatedCode();
+        });
+
+        it('should keep unmerged variable declarations with different variable kinds', () => {
+            assert.match(obfuscatedCode, regExp);
+        });
+    });
+});

+ 6 - 0
test/functional-tests/node-transformers/simplifying-transformers/variable-declarations-merge-transformer/fixtures/different-variables-kind.js

@@ -0,0 +1,6 @@
+var foo = 1;
+var bar = 2;
+let baz = 3;
+let bark = 4;
+const hawk = 5;
+const pork = 6;

+ 4 - 0
test/functional-tests/node-transformers/simplifying-transformers/variable-declarations-merge-transformer/fixtures/multiple-declarations-with-multiple-declarators.js

@@ -0,0 +1,4 @@
+var foo = 1,
+    bar = 2;
+var baz = 3,
+    bark = 4;

+ 3 - 0
test/functional-tests/node-transformers/simplifying-transformers/variable-declarations-merge-transformer/fixtures/multiple-declarations-without-declarators.js

@@ -0,0 +1,3 @@
+var foo;
+var bar;
+var baz;

+ 3 - 0
test/functional-tests/node-transformers/simplifying-transformers/variable-declarations-merge-transformer/fixtures/multiple-declarations.js

@@ -0,0 +1,3 @@
+var foo = 1;
+var bar = 2;
+var baz = 3;

+ 3 - 0
test/functional-tests/node-transformers/simplifying-transformers/variable-declarations-merge-transformer/fixtures/object-pattern-as-initializer.js

@@ -0,0 +1,3 @@
+var foo = 1;
+var {bar} = {bar: 2};
+var baz = 3;

+ 1 - 0
test/functional-tests/node-transformers/simplifying-transformers/variable-declarations-merge-transformer/fixtures/single-declaration.js

@@ -0,0 +1 @@
+var foo = 1;

+ 5 - 0
test/functional-tests/node-transformers/simplifying-transformers/variable-declarations-merge-transformer/fixtures/splitted-declarations-with-other-statement.js

@@ -0,0 +1,5 @@
+var foo = 1;
+var bar = 2;
+console.log('123');
+var baz = 3;
+var bark = 4;

+ 2 - 0
test/index.spec.ts

@@ -90,6 +90,8 @@ import './functional-tests/node-transformers/converting-transformers/split-strin
 import './functional-tests/node-transformers/converting-transformers/template-literal-transformer/TemplateLiteralTransformer.spec';
 import './functional-tests/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.spec';
 import './functional-tests/node-transformers/initializing-transformers/comments-transformer/CommentsTransformer.spec';
+import './functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/IfStatementSimplifyTransformer.spec';
+import './functional-tests/node-transformers/simplifying-transformers/variable-declarations-merge-transformer/VariableDeclarationsMergeTransformer.spec';
 import './functional-tests/node-transformers/obfuscating-transformers/labeled-statement-transformer/LabeledStatementTransformer.spec';
 import './functional-tests/node-transformers/obfuscating-transformers/literal-transformer/LiteralTransformer.spec';
 import './functional-tests/node-transformers/obfuscating-transformers/scope-identifiers-transformer/catch-clause/CatchClause.spec';

+ 9 - 4
test/runtime-tests/JavaScriptObfuscatorRuntime.spec.ts

@@ -25,6 +25,7 @@ describe('JavaScriptObfuscator runtime eval', function () {
         debugProtection: true,
         disableConsoleOutput: true,
         domainLock: ['obfuscator.io'],
+        simplify: true,
         renameProperties: true,
         reservedNames: ['generate', 'sha256'],
         rotateStringArray: true,
@@ -256,10 +257,14 @@ describe('JavaScriptObfuscator runtime eval', function () {
                         }
                     ).getObfuscatedCode();
 
-                    evaluationResult = eval(`
-                        ${getEnvironmentCode()}
-                        ${obfuscatedCode}
-                    `);
+                    try {
+                        evaluationResult = eval(`
+                            ${getEnvironmentCode()}
+                            ${obfuscatedCode}
+                        `);
+                    } catch (e) {
+                        throw new Error(`Evaluation error: ${e.message}. Code: ${obfuscatedCode}`);
+                    }
                 });
 
                 it('should obfuscate code without any runtime errors after obfuscation: Variant #4 webpack bootstrap', () => {

+ 64 - 49
yarn.lock

@@ -297,10 +297,10 @@
     "@types/estree" "*"
     "@types/json-schema" "*"
 
-"@types/estraverse@0.0.6":
-  version "0.0.6"
-  resolved "https://registry.yarnpkg.com/@types/estraverse/-/estraverse-0.0.6.tgz#669f7cdf72ab797e6125f8d00fed33d4cf30c221"
-  integrity sha1-Zp9833KreX5hJfjQD+0z1M8wwiE=
+"@types/estraverse@5.1.0":
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/@types/estraverse/-/estraverse-5.1.0.tgz#7a16cf8a8ee7764c05ccef3b2701bbcde6d5d0ae"
+  integrity sha512-vH2ItsZq47KprWHdv8OMjlfpygPHp1P7X4zuJuTghXldyezatpaotNSujld/HNsxh9TUS7+JRB0HEldkv67qaw==
   dependencies:
     "@types/estree" "*"
 
@@ -369,10 +369,10 @@
   resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.3.tgz#6356df2647de9eac569f9a52eda3480fa9e70b4d"
   integrity sha512-01s+ac4qerwd6RHD+mVbOEsraDHSgUaefQlEdBbUolnQFjKwCr7luvAlEwW1RFojh67u0z4OUTjPn9LEl4zIkA==
 
-"@types/[email protected]4":
-  version "14.0.14"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.14.tgz#24a0b5959f16ac141aeb0c5b3cd7a15b7c64cbce"
-  integrity sha512-syUgf67ZQpaJj01/tRTknkMNoBBLWJOBODF0Zm4NrXmiSuxjymFrxnTu1QVYRubhVkRcZLYZG8STTwJRdVm/WQ==
+"@types/[email protected]9":
+  version "14.0.19"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.19.tgz#994d99708822bca643a2364f8aeed04a16e0f5a1"
+  integrity sha512-yf3BP/NIXF37BjrK5klu//asUWitOEoUP5xE1mhSUjazotwJ/eJDgEmMQNlOeWOVv72j24QQ+3bqXHE++CFGag==
 
 "@types/normalize-package-data@^2.4.0":
   version "2.4.0"
@@ -419,51 +419,66 @@
   resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.15.2.tgz#927997342bb9f4a5185a86e6579a0a18afc33b0a"
   integrity sha512-67ZgZpAlhIICIdfQrB5fnDvaKFcDxpKibxznfYRVAT4mQE41Dido/3Ty+E3xGBmTogc5+0Qb8tWhna+5B8z1iQ==
 
-"@typescript-eslint/eslint-plugin@3.4.0":
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.4.0.tgz#8378062e6be8a1d049259bdbcf27ce5dfbeee62b"
-  integrity sha512-wfkpiqaEVhZIuQRmudDszc01jC/YR7gMSxa6ulhggAe/Hs0KVIuo9wzvFiDbG3JD5pRFQoqnf4m7REDsUvBnMQ==
+"@typescript-eslint/eslint-plugin@3.6.0":
+  version "3.6.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.6.0.tgz#ba2b6cae478b8fca3f2e58ff1313e4198eea2d8a"
+  integrity sha512-ubHlHVt1lsPQB/CZdEov9XuOFhNG9YRC//kuiS1cMQI6Bs1SsqKrEmZnpgRwthGR09/kEDtr9MywlqXyyYd8GA==
   dependencies:
-    "@typescript-eslint/experimental-utils" "3.4.0"
+    "@typescript-eslint/experimental-utils" "3.6.0"
     debug "^4.1.1"
     functional-red-black-tree "^1.0.1"
     regexpp "^3.0.0"
     semver "^7.3.2"
     tsutils "^3.17.1"
 
-"@typescript-eslint/experimental-utils@3.4.0":
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.4.0.tgz#8a44dfc6fb7f1d071937b390fe27608ebda122b8"
-  integrity sha512-rHPOjL43lOH1Opte4+dhC0a/+ks+8gOBwxXnyrZ/K4OTAChpSjP76fbI8Cglj7V5GouwVAGaK+xVwzqTyE/TPw==
+"@typescript-eslint/experimental-utils@3.6.0":
+  version "3.6.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.6.0.tgz#0138152d66e3e53a6340f606793fb257bf2d76a1"
+  integrity sha512-4Vdf2hvYMUnTdkCNZu+yYlFtL2v+N2R7JOynIOkFbPjf9o9wQvRwRkzUdWlFd2YiiUwJLbuuLnl5civNg5ykOQ==
   dependencies:
     "@types/json-schema" "^7.0.3"
-    "@typescript-eslint/typescript-estree" "3.4.0"
+    "@typescript-eslint/types" "3.6.0"
+    "@typescript-eslint/typescript-estree" "3.6.0"
     eslint-scope "^5.0.0"
     eslint-utils "^2.0.0"
 
-"@typescript-eslint/parser@3.4.0":
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-3.4.0.tgz#fe52b68c5cb3bba3f5d875bd17adb70420d49d8d"
-  integrity sha512-ZUGI/de44L5x87uX5zM14UYcbn79HSXUR+kzcqU42gH0AgpdB/TjuJy3m4ezI7Q/jk3wTQd755mxSDLhQP79KA==
+"@typescript-eslint/parser@3.6.0":
+  version "3.6.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-3.6.0.tgz#79b5232e1a2d06f1fc745942b690cd87aca7b60e"
+  integrity sha512-taghDxuLhbDAD1U5Fk8vF+MnR0yiFE9Z3v2/bYScFb0N1I9SK8eKHkdJl1DAD48OGFDMFTeOTX0z7g0W6SYUXw==
   dependencies:
     "@types/eslint-visitor-keys" "^1.0.0"
-    "@typescript-eslint/experimental-utils" "3.4.0"
-    "@typescript-eslint/typescript-estree" "3.4.0"
+    "@typescript-eslint/experimental-utils" "3.6.0"
+    "@typescript-eslint/types" "3.6.0"
+    "@typescript-eslint/typescript-estree" "3.6.0"
     eslint-visitor-keys "^1.1.0"
 
-"@typescript-eslint/[email protected]":
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.4.0.tgz#6a787eb70b48969e4cd1ea67b057083f96dfee29"
-  integrity sha512-zKwLiybtt4uJb4mkG5q2t6+W7BuYx2IISiDNV+IY68VfoGwErDx/RfVI7SWL4gnZ2t1A1ytQQwZ+YOJbHHJ2rw==
+"@typescript-eslint/[email protected]":
+  version "3.6.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.6.0.tgz#4bd6eee55d2f9d35a4b36c4804be1880bf68f7bc"
+  integrity sha512-JwVj74ohUSt0ZPG+LZ7hb95fW8DFOqBuR6gE7qzq55KDI3BepqsCtHfBIoa0+Xi1AI7fq5nCu2VQL8z4eYftqg==
+
+"@typescript-eslint/[email protected]":
+  version "3.6.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.6.0.tgz#9b4cab43f1192b64ff51530815b8919f166ce177"
+  integrity sha512-G57NDSABHjvob7zVV09ehWyD1K6/YUKjz5+AufObFyjNO4DVmKejj47MHjVHHlZZKgmpJD2yyH9lfCXHrPITFg==
   dependencies:
+    "@typescript-eslint/types" "3.6.0"
+    "@typescript-eslint/visitor-keys" "3.6.0"
     debug "^4.1.1"
-    eslint-visitor-keys "^1.1.0"
     glob "^7.1.6"
     is-glob "^4.0.1"
     lodash "^4.17.15"
     semver "^7.3.2"
     tsutils "^3.17.1"
 
+"@typescript-eslint/[email protected]":
+  version "3.6.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-3.6.0.tgz#44185eb0cc47651034faa95c5e2e8b314ecebb26"
+  integrity sha512-p1izllL2Ubwunite0ITjubuMQRBGgjdVYwyG7lXPX8GbrA6qF0uwSRz9MnXZaHMxID4948gX0Ez8v9tUDi/KfQ==
+  dependencies:
+    eslint-visitor-keys "^1.1.0"
+
 "@webassemblyjs/[email protected]":
   version "1.9.0"
   resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964"
@@ -1889,10 +1904,10 @@ eslint-module-utils@^2.6.0:
     debug "^2.6.9"
     pkg-dir "^2.0.0"
 
[email protected]1.2:
-  version "2.21.2"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.21.2.tgz#8fef77475cc5510801bedc95f84b932f7f334a7c"
-  integrity sha512-FEmxeGI6yaz+SnEB6YgNHlQK1Bs2DKLM+YF+vuTk5H8J9CLbJLtlPvRFgZZ2+sXiKAlN5dpdlrWOjK8ZoZJpQA==
[email protected].0:
+  version "2.22.0"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz#92f7736fe1fde3e2de77623c838dd992ff5ffb7e"
+  integrity sha512-66Fpf1Ln6aIS5Gr/55ts19eUuoDhAbZgnr6UxK5hbDx6l/QgQgx61AePq+BV4PP2uXQFClgMVzep5zZ94qqsxg==
   dependencies:
     array-includes "^3.1.1"
     array.prototype.flat "^1.2.3"
@@ -1908,10 +1923,10 @@ [email protected]:
     resolve "^1.17.0"
     tsconfig-paths "^3.9.0"
 
-eslint-plugin-jsdoc@28.5.1:
-  version "28.5.1"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-28.5.1.tgz#5a95ee8705af389ba2062a1036be6077b5e62f3f"
-  integrity sha512-1XSWu8UnGwqO8mX3XKGofffL83VRt00ptq0m5OrTLFDN3At4x+/pJ8YHJONKhGC35TtjskcS9/RR6F9pjQ8c+w==
+eslint-plugin-jsdoc@28.6.1:
+  version "28.6.1"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-28.6.1.tgz#c9e9da59d0d3cef4fb45ffb91c0acde43af4e418"
+  integrity sha512-Z3y7hcNPDuhL339D1KOf9SY8pMAxYxhaG4QLtu3KVn20k/hNF1u6WQv44wvuSCb6OfPJ4say37RUlSNqIjR+mw==
   dependencies:
     comment-parser "^0.7.5"
     debug "^4.1.1"
@@ -2000,10 +2015,10 @@ eslint-visitor-keys@^1.2.0:
   resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.2.0.tgz#74415ac884874495f78ec2a97349525344c981fa"
   integrity sha512-WFb4ihckKil6hu3Dp798xdzSfddwKKU3+nGniKF6HfeW6OLd2OUDEPP7TcHtB5+QXOKg2s6B2DaMPE1Nn/kxKQ==
 
-eslint@7.3.1:
-  version "7.3.1"
-  resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.3.1.tgz#76392bd7e44468d046149ba128d1566c59acbe19"
-  integrity sha512-cQC/xj9bhWUcyi/RuMbRtC3I0eW8MH0jhRELSvpKYkWep3C6YZ2OkvcvJVUeO6gcunABmzptbXBuDoXsjHmfTA==
+eslint@7.4.0:
+  version "7.4.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.4.0.tgz#4e35a2697e6c1972f9d6ef2b690ad319f80f206f"
+  integrity sha512-gU+lxhlPHu45H3JkEGgYhWhkR9wLHHEXC9FbWFnTlEkbKyZKWgWRLgf61E8zWmBuI6g5xKBph9ltg3NtZMVF8g==
   dependencies:
     "@babel/code-frame" "^7.0.0"
     ajv "^6.10.0"
@@ -2339,10 +2354,10 @@ [email protected]:
   dependencies:
     node-notifier "^6.0.0"
 
[email protected].5:
-  version "5.0.5"
-  resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.0.5.tgz#4cf77f925008464db4670750a6e9266a4d25a988"
-  integrity sha512-2vpP+irspLOpZ1pcH3K3yR76MWXVSOYKTGwhwFQGnWjOs5MxzPN+dyDh1tNoaD5DUFP5kr4lGQURE9Qk+DpiUQ==
[email protected].7:
+  version "5.0.7"
+  resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.0.7.tgz#a462bd863bbd654bad14a25518006ab99b7b13a0"
+  integrity sha512-X7zHKTx4is5YSKa9bDiXGzv8v5K3bsUrZlZgr3F8DeCVsb3oik/+0Mo+K138Sdjh4mpzoHcuUgsrIgZeLIXovw==
   dependencies:
     "@babel/code-frame" "^7.8.3"
     chalk "^2.4.1"
@@ -5258,10 +5273,10 @@ typedarray@^0.0.6:
   resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
   integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
 
[email protected].5:
-  version "3.9.5"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.5.tgz#586f0dba300cde8be52dd1ac4f7e1009c1b13f36"
-  integrity sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ==
[email protected].6:
+  version "3.9.6"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.6.tgz#8f3e0198a34c3ae17091b35571d3afd31999365a"
+  integrity sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw==
 
 union-value@^1.0.0:
   version "1.0.1"

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