Ver código fonte

Merge pull request #667 from javascript-obfuscator/block-statement-simplify

Block statement simplify transformer
Timofey Kachalov 4 anos atrás
pai
commit
c8213529b1
26 arquivos alterados com 654 adições e 179 exclusões
  1. 4 0
      CHANGELOG.md
  2. 0 0
      dist/index.browser.js
  3. 0 0
      dist/index.cli.js
  4. 0 0
      dist/index.js
  5. 1 1
      package.json
  6. 1 0
      src/JavaScriptObfuscator.ts
  7. 5 0
      src/container/modules/node-transformers/SimplifyingTransformersModule.ts
  8. 1 0
      src/enums/node-transformers/NodeTransformer.ts
  9. 1 1
      src/interfaces/node-transformers/simplifying-transformers/IIteratedStatementsSimplifyData.ts
  10. 1 1
      src/interfaces/node-transformers/simplifying-transformers/IStatementSimplifyData.ts
  11. 193 0
      src/node-transformers/simplifying-transformers/AbstractStatementSimplifyTransformer.ts
  12. 87 0
      src/node-transformers/simplifying-transformers/BlockStatementSimplifyTransformer.ts
  13. 27 168
      src/node-transformers/simplifying-transformers/IfStatementSimplifyTransformer.ts
  14. 1 1
      src/node-transformers/simplifying-transformers/VariableDeclarationsMergeTransformer.ts
  15. 9 7
      test/dev/dev.ts
  16. 245 0
      test/functional-tests/node-transformers/simplifying-transformers/block-statement-simplify-transformer/BlockStatementSimplifyTransformer.spec.ts
  17. 5 0
      test/functional-tests/node-transformers/simplifying-transformers/block-statement-simplify-transformer/fixtures/full-no-return-multiple-statements.js
  18. 3 0
      test/functional-tests/node-transformers/simplifying-transformers/block-statement-simplify-transformer/fixtures/full-no-return-single-statement.js
  19. 6 0
      test/functional-tests/node-transformers/simplifying-transformers/block-statement-simplify-transformer/fixtures/full-return-multiple-statements.js
  20. 3 0
      test/functional-tests/node-transformers/simplifying-transformers/block-statement-simplify-transformer/fixtures/full-return-single-statement.js
  21. 6 0
      test/functional-tests/node-transformers/simplifying-transformers/block-statement-simplify-transformer/fixtures/partial-no-return-multiple-statements.js
  22. 3 0
      test/functional-tests/node-transformers/simplifying-transformers/block-statement-simplify-transformer/fixtures/partial-no-return-single-statement.js
  23. 7 0
      test/functional-tests/node-transformers/simplifying-transformers/block-statement-simplify-transformer/fixtures/partial-return-multiple-statements.js
  24. 5 0
      test/functional-tests/node-transformers/simplifying-transformers/block-statement-simplify-transformer/fixtures/variable-declarations-merge-transformer-integration-1.js
  25. 32 0
      test/functional-tests/node-transformers/simplifying-transformers/variable-declarations-merge-transformer/VariableDeclarationsMergeTransformer.spec.ts
  26. 8 0
      test/functional-tests/node-transformers/simplifying-transformers/variable-declarations-merge-transformer/fixtures/declarations-inside-nested-function-expressions.js

+ 4 - 0
CHANGELOG.md

@@ -1,5 +1,9 @@
 Change Log
 
+v1.7.0
+---
+* `simplify` option now affects all block statements. Improved variable declarations merging.
+
 v1.6.0
 ---
 * **New option:** `numbersToExpressions` enables numbers conversion to expressions

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
dist/index.browser.js


Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
dist/index.cli.js


Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
dist/index.js


+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "javascript-obfuscator",
-  "version": "1.6.0",
+  "version": "1.7.0",
   "description": "JavaScript obfuscator",
   "keywords": [
     "obfuscator",

+ 1 - 0
src/JavaScriptObfuscator.ts

@@ -63,6 +63,7 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
      */
     private static readonly nodeTransformersList: NodeTransformer[] = [
         NodeTransformer.BlockStatementControlFlowTransformer,
+        NodeTransformer.BlockStatementSimplifyTransformer,
         NodeTransformer.CommentsTransformer,
         NodeTransformer.CustomCodeHelpersTransformer,
         NodeTransformer.DeadCodeInjectionTransformer,

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

@@ -5,11 +5,16 @@ import { INodeTransformer } from '../../../interfaces/node-transformers/INodeTra
 
 import { NodeTransformer } from '../../../enums/node-transformers/NodeTransformer';
 
+import { BlockStatementSimplifyTransformer } from '../../../node-transformers/simplifying-transformers/BlockStatementSimplifyTransformer';
 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(BlockStatementSimplifyTransformer)
+        .whenTargetNamed(NodeTransformer.BlockStatementSimplifyTransformer);
+
     bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
         .to(IfStatementSimplifyTransformer)
         .whenTargetNamed(NodeTransformer.IfStatementSimplifyTransformer);

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

@@ -1,5 +1,6 @@
 export enum NodeTransformer {
     BlockStatementControlFlowTransformer = 'BlockStatementControlFlowTransformer',
+    BlockStatementSimplifyTransformer = 'BlockStatementSimplifyTransformer',
     CommentsTransformer = 'CommentsTransformer',
     CustomCodeHelpersTransformer = 'CustomCodeHelpersTransformer',
     DeadCodeInjectionTransformer = 'DeadCodeInjectionTransformer',

+ 1 - 1
src/interfaces/node-transformers/simplifying-transformers/IIfStatementIteratedStatementsData.ts → src/interfaces/node-transformers/simplifying-transformers/IIteratedStatementsSimplifyData.ts

@@ -1,6 +1,6 @@
 import * as ESTree from 'estree';
 
-export interface IIfStatementIteratedStatementsData {
+export interface IIteratedStatementsSimplifyData {
     /**
      * @type {number | null}
      */

+ 1 - 1
src/interfaces/node-transformers/simplifying-transformers/IIfStatementSimplifyData.ts → src/interfaces/node-transformers/simplifying-transformers/IStatementSimplifyData.ts

@@ -1,6 +1,6 @@
 import * as ESTree from 'estree';
 
-export interface IIfStatementSimplifyData {
+export interface IStatementSimplifyData {
     /**
      * @type {ESTree.Statement[]}
      */

+ 193 - 0
src/node-transformers/simplifying-transformers/AbstractStatementSimplifyTransformer.ts

@@ -0,0 +1,193 @@
+import { inject, injectable, } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
+
+import * as ESTree from 'estree';
+
+import { IIteratedStatementsSimplifyData } from '../../interfaces/node-transformers/simplifying-transformers/IIteratedStatementsSimplifyData';
+import { IOptions } from '../../interfaces/options/IOptions';
+import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
+import { IStatementSimplifyData } from '../../interfaces/node-transformers/simplifying-transformers/IStatementSimplifyData';
+
+import { NodeTransformer } from '../../enums/node-transformers/NodeTransformer';
+
+import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
+import { NodeGuards } from '../../node/NodeGuards';
+import { NodeFactory } from '../../node/NodeFactory';
+
+/**
+ * Simplifies `Statement` node
+ */
+@injectable()
+export abstract class AbstractStatementSimplifyTransformer extends AbstractNodeTransformer {
+    /**
+     * @type {NodeTransformer[]}
+     */
+    public readonly runAfter: NodeTransformer[] = [
+        NodeTransformer.VariableDeclarationsMergeTransformer
+    ];
+
+    /**
+     * @param {IRandomGenerator} randomGenerator
+     * @param {IOptions} options
+     */
+    protected constructor (
+        @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        super(randomGenerator, options);
+    }
+
+    /**
+     * Returns IStatementSimplifyData based on `Statement` node
+     *
+     * @param {ESTree.Statement | null | undefined} statementNode
+     * @returns {IStatementSimplifyData | null}
+     */
+    protected getStatementSimplifyData (
+        statementNode: ESTree.Statement | null | undefined
+    ): IStatementSimplifyData | null {
+        if (!statementNode) {
+            return null;
+        }
+
+        if (!NodeGuards.isBlockStatementNode(statementNode)) {
+            return {
+                leadingStatements: [statementNode],
+                trailingStatement: null,
+                hasReturnStatement: false,
+                hasSingleExpression: false
+            };
+        }
+
+        const {
+            startIndex,
+            unwrappedExpressions,
+            hasReturnStatement
+        } = this.collectIteratedStatementsSimplifyData(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 `BlockStatement` node body and collects data
+     *
+     * @param {ESTree.Statement | null | undefined} statementNode
+     * @returns {IIteratedStatementsSimplifyData}
+     */
+    protected collectIteratedStatementsSimplifyData (
+        statementNode: ESTree.BlockStatement
+    ): IIteratedStatementsSimplifyData {
+        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
+     *
+     * @param {ESTree.BlockStatement} statementNode
+     * @param {number | null} startIndex
+     * @returns {ESTree.Statement[]}
+     */
+    protected getLeadingStatements (statementNode: ESTree.BlockStatement, startIndex: number | null): ESTree.Statement[] {
+        // variant #1: no valid statements inside `BlockStatement` are found
+        if (startIndex === null) {
+            return statementNode.body;
+        }
+
+        return startIndex === 0
+            // variant #2: all statements inside `BlockStatement` branch are valid
+            ? []
+            // variant #3: only last N statements inside `BlockStatement` branch are valid
+            : statementNode.body.slice(0, startIndex);
+    }
+
+    /**
+     * @param {IStatementSimplifyData} statementSimplifyData
+     * @returns {ESTree.Statement}
+     */
+    protected getPartialStatement (statementSimplifyData: IStatementSimplifyData): ESTree.Statement {
+        // variant #1: all statements inside `BlockStatement` branch are valid
+        if (!statementSimplifyData.leadingStatements.length && statementSimplifyData.trailingStatement) {
+            return statementSimplifyData.trailingStatement.statement;
+        }
+
+        // variant #2: only last N statements inside `BlockStatement` branch are valid
+        return NodeFactory.blockStatementNode([
+            ...statementSimplifyData.leadingStatements.length ? statementSimplifyData.leadingStatements : [],
+            ...statementSimplifyData.trailingStatement ? [statementSimplifyData.trailingStatement.statement] : []
+        ]);
+    }
+
+    /**
+     * @param {ESTree.Statement} statementNode
+     * @param {ESTree.Node} parentNode
+     * @returns {ESTree.Node}
+     */
+    public abstract transformNode (
+        statementNode: ESTree.Statement,
+        parentNode: ESTree.Node
+    ): ESTree.Node;
+}

+ 87 - 0
src/node-transformers/simplifying-transformers/BlockStatementSimplifyTransformer.ts

@@ -0,0 +1,87 @@
+import { inject, injectable, } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
+
+import * as ESTree from 'estree';
+
+import { IOptions } from '../../interfaces/options/IOptions';
+import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
+import { IStatementSimplifyData } from '../../interfaces/node-transformers/simplifying-transformers/IStatementSimplifyData';
+import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
+
+import { NodeTransformer } from '../../enums/node-transformers/NodeTransformer';
+import { NodeTransformationStage } from '../../enums/node-transformers/NodeTransformationStage';
+
+import { AbstractStatementSimplifyTransformer } from './AbstractStatementSimplifyTransformer';
+import { NodeGuards } from '../../node/NodeGuards';
+import { NodeFactory } from '../../node/NodeFactory';
+import { NodeUtils } from '../../node/NodeUtils';
+
+/**
+ * Simplifies `BlockStatement` node
+ */
+@injectable()
+export class BlockStatementSimplifyTransformer extends AbstractStatementSimplifyTransformer {
+    /**
+     * @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.isBlockStatementNode(node)) {
+                            return this.transformNode(node, parentNode);
+                        }
+                    }
+                };
+
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * @param {ESTree.Statement} statementNode
+     * @param {ESTree.Node} parentNode
+     * @returns {ESTree.Node}
+     */
+    public transformNode (
+        statementNode: ESTree.Statement,
+        parentNode: ESTree.Node
+    ): ESTree.Node {
+        const simplifyData: IStatementSimplifyData | null = this.getStatementSimplifyData(statementNode);
+
+        if (!simplifyData) {
+            return statementNode;
+        }
+
+        const partialStatementNode: ESTree.Statement = this.getPartialStatement(simplifyData);
+        const transformedNode: ESTree.Node = NodeGuards.isBlockStatementNode(partialStatementNode)
+            ? partialStatementNode
+            : NodeFactory.blockStatementNode([partialStatementNode]);
+
+        return NodeUtils.parentizeNode(transformedNode, parentNode);
+    }
+}

+ 27 - 168
src/node-transformers/simplifying-transformers/IfStatementSimplifyTransformer.ts

@@ -3,16 +3,14 @@ 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 { IStatementSimplifyData } from '../../interfaces/node-transformers/simplifying-transformers/IStatementSimplifyData';
 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 { AbstractStatementSimplifyTransformer } from './AbstractStatementSimplifyTransformer';
 import { NodeGuards } from '../../node/NodeGuards';
 import { NodeFactory } from '../../node/NodeFactory';
 import { NodeUtils } from '../../node/NodeUtils';
@@ -21,14 +19,7 @@ import { NodeUtils } from '../../node/NodeUtils';
  * Simplifies `IfStatement` node
  */
 @injectable()
-export class IfStatementSimplifyTransformer extends AbstractNodeTransformer {
-    /**
-     * @type {NodeTransformer[]}
-     */
-    public readonly runAfter: NodeTransformer[] = [
-        NodeTransformer.VariableDeclarationsMergeTransformer
-    ];
-
+export class IfStatementSimplifyTransformer extends AbstractStatementSimplifyTransformer {
     /**
      * @param {IRandomGenerator} randomGenerator
      * @param {IOptions} options
@@ -72,7 +63,7 @@ export class IfStatementSimplifyTransformer extends AbstractNodeTransformer {
         ifStatementNode: ESTree.IfStatement,
         parentNode: ESTree.Node
     ): ESTree.Node {
-        const consequentSimplifyData: IIfStatementSimplifyData | null = this.getIfStatementSimplifyData(ifStatementNode.consequent);
+        const consequentSimplifyData: IStatementSimplifyData | null = this.getStatementSimplifyData(ifStatementNode.consequent);
 
         // Variant #1: no valid consequent expression data
         if (!consequentSimplifyData) {
@@ -85,7 +76,7 @@ export class IfStatementSimplifyTransformer extends AbstractNodeTransformer {
             // Variant #2: valid data for consequent expression only
             transformedNode = this.getConsequentNode(ifStatementNode, consequentSimplifyData);
         } else {
-            const alternateSimplifyData: IIfStatementSimplifyData | null = this.getIfStatementSimplifyData(ifStatementNode.alternate);
+            const alternateSimplifyData: IStatementSimplifyData | null = this.getStatementSimplifyData(ifStatementNode.alternate);
 
             if (!alternateSimplifyData) {
                 return ifStatementNode;
@@ -100,12 +91,12 @@ export class IfStatementSimplifyTransformer extends AbstractNodeTransformer {
 
     /**
      * @param {ESTree.IfStatement} ifStatementNode
-     * @param {IIfStatementSimplifyData} consequentSimplifyData
+     * @param {IStatementSimplifyData} consequentSimplifyData
      * @returns {ESTree.Node}
      */
-    private getConsequentNode (
+    protected getConsequentNode (
         ifStatementNode: ESTree.IfStatement,
-        consequentSimplifyData: IIfStatementSimplifyData
+        consequentSimplifyData: IStatementSimplifyData
     ): ESTree.Node {
         /**
          * Converts:
@@ -127,7 +118,7 @@ export class IfStatementSimplifyTransformer extends AbstractNodeTransformer {
         ) {
             return NodeFactory.ifStatementNode(
                 ifStatementNode.test,
-                this.getPartialIfStatementBranchNode(consequentSimplifyData)
+                this.getPartialStatement(consequentSimplifyData)
             );
         }
 
@@ -168,14 +159,14 @@ export class IfStatementSimplifyTransformer extends AbstractNodeTransformer {
 
     /**
      * @param {ESTree.IfStatement} ifStatementNode
-     * @param {IIfStatementSimplifyData} consequentSimplifyData
-     * @param {IIfStatementSimplifyData} alternateSimplifyData
+     * @param {IStatementSimplifyData} consequentSimplifyData
+     * @param {IStatementSimplifyData} alternateSimplifyData
      * @returns {ESTree.Node}
      */
-    private getConsequentAndAlternateNode (
+    protected getConsequentAndAlternateNode (
         ifStatementNode: ESTree.IfStatement,
-        consequentSimplifyData: IIfStatementSimplifyData,
-        alternateSimplifyData: IIfStatementSimplifyData
+        consequentSimplifyData: IStatementSimplifyData,
+        alternateSimplifyData: IStatementSimplifyData
     ): ESTree.Node {
         /**
          * Converts:
@@ -199,8 +190,8 @@ export class IfStatementSimplifyTransformer extends AbstractNodeTransformer {
         ) {
             return NodeFactory.ifStatementNode(
                 ifStatementNode.test,
-                this.getPartialIfStatementBranchNode(consequentSimplifyData),
-                this.getPartialIfStatementBranchNode(alternateSimplifyData)
+                this.getPartialStatement(consequentSimplifyData),
+                this.getPartialStatement(alternateSimplifyData)
             );
         }
 
@@ -268,152 +259,20 @@ export class IfStatementSimplifyTransformer extends AbstractNodeTransformer {
     }
 
     /**
-     * Returns IIfStatementSimplifyData based on `IfStatement` node body
-     *
-     * @param {ESTree.Statement | null | undefined} statementNode
-     * @returns {IIfStatementSimplifyData | null}
+     * @param {IStatementSimplifyData} statementSimplifyData
+     * @returns {ESTree.Statement}
      */
-    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);
+    protected getPartialStatement (statementSimplifyData: IStatementSimplifyData): ESTree.Statement {
+        const partialStatement: ESTree.Statement = super.getPartialStatement(statementSimplifyData);
 
-        if (!unwrappedExpressions.length) {
-            return {
-                leadingStatements,
-                trailingStatement: null,
-                hasReturnStatement,
-                hasSingleExpression: false
-            };
+        if (!NodeGuards.isBlockStatementNode(partialStatement)) {
+            return partialStatement;
         }
 
-        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;
+        return partialStatement.body.length === 1
+        && !this.isProhibitedSingleStatementForIfStatementBranch(partialStatement.body[0])
+            ? partialStatement.body[0]
+            : partialStatement;
 
     }
 
@@ -421,7 +280,7 @@ export class IfStatementSimplifyTransformer extends AbstractNodeTransformer {
      * @param {ESTree.Statement} statement
      * @returns {boolean}
      */
-    private isProhibitedSingleStatementForIfStatementBranch (statement: ESTree.Statement): boolean {
+    protected isProhibitedSingleStatementForIfStatementBranch (statement: ESTree.Statement): boolean {
         /**
          * Function declaration is not allowed outside of block in `strict` mode
          */

+ 1 - 1
src/node-transformers/simplifying-transformers/VariableDeclarationsMergeTransformer.ts

@@ -46,7 +46,7 @@ export class VariableDeclarationsMergeTransformer extends AbstractNodeTransforme
         switch (nodeTransformationStage) {
             case NodeTransformationStage.Simplifying:
                 return {
-                    enter: (
+                    leave: (
                         node: ESTree.Node,
                         parentNode: ESTree.Node | null
                     ): ESTree.Node | estraverse.VisitorOption | undefined => {

+ 9 - 7
test/dev/dev.ts

@@ -7,17 +7,19 @@ import { NO_ADDITIONAL_NODES_PRESET } from '../../src/options/presets/NoCustomNo
 
     let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
         `
-            const foo = 0..toString();
-            const bar = 'abc';
-            
-            console.log(foo, bar);
+            var foo = function () {};
+            var bar = function () {
+                var baz = function () {};
+                var bark = function () {
+                    var hawk = 1;
+                    var dog = 2;
+                };
+            };
         `,
         {
             ...NO_ADDITIONAL_NODES_PRESET,
             compact: false,
-            numbersToExpressions: true,
-            stringArray: true,
-            stringArrayThreshold: 1
+            simplify: true
         }
     ).getObfuscatedCode();
 

+ 245 - 0
test/functional-tests/node-transformers/simplifying-transformers/block-statement-simplify-transformer/BlockStatementSimplifyTransformer.spec.ts

@@ -0,0 +1,245 @@
+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('BlockStatementSimplifyTransformer', () => {
+    describe('Full `BlockStatement` simplify cases', () => {
+        describe('No `ReturnStatement`', () => {
+            describe('Variant #1: single statement', () => {
+                const regExp: RegExp = new RegExp(
+                    'function foo *\\(\\) *{ *' +
+                        'bar\\(\\); *' +
+                    '}'
+                );
+
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/full-no-return-single-statement.js');
+
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            simplify: true
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should not simplify block statement', () => {
+                    assert.match(obfuscatedCode, regExp);
+                });
+            });
+
+            describe('Variant #2: multiple statements', () => {
+                const regExp: RegExp = new RegExp(
+                    'function foo *\\(\\) *{ *' +
+                        'bar\\(\\), *baz\\(\\), *bark\\(\\); *' +
+                    '}'
+                );
+
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/full-no-return-multiple-statements.js');
+
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            simplify: true
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should simplify block statement', () => {
+                    assert.match(obfuscatedCode, regExp);
+                });
+            });
+        });
+
+        describe('With `ReturnStatement`', () => {
+            describe('Variant #1: single statement', () => {
+                const regExp: RegExp = new RegExp(
+                    'function foo *\\(\\) *{ *' +
+                        'return bar\\(\\); *' +
+                    '}'
+                );
+
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/full-return-single-statement.js');
+
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            simplify: true
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should not simplify block statement', () => {
+                    assert.match(obfuscatedCode, regExp);
+                });
+            });
+
+            describe('Variant #2: multiple statements', () => {
+                const regExp: RegExp = new RegExp(
+                    'function foo *\\(\\) *{ *' +
+                        'return bar\\(\\), *baz\\(\\), *bark\\(\\); *' +
+                    '}'
+                );
+
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/full-return-multiple-statements.js');
+
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            simplify: true
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should simplify block statement', () => {
+                    assert.match(obfuscatedCode, regExp);
+                });
+            });
+        });
+    });
+
+    describe('Partial `BlockStatement` simplify cases', () => {
+        describe('No `ReturnStatement`', () => {
+            describe('Variant #1: single statement', () => {
+                const regExp: RegExp = new RegExp(
+                    'function foo *\\(\\) *{ *' +
+                        'var _0x([a-f0-9]){4,6} *= *baz\\(\\);' +
+                    '}'
+                );
+
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/partial-no-return-single-statement.js');
+
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            simplify: true
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should not simplify block statement', () => {
+                    assert.match(obfuscatedCode, regExp);
+                });
+            });
+
+            describe('Variant #2: multiple statements', () => {
+                const regExp: RegExp = new RegExp(
+                    'function foo *\\(\\) *{ *' +
+                        'const _0x([a-f0-9]){4,6} *= *baz\\(\\); *' +
+                        'bark\\(\\), *hawk\\(\\);' +
+                    '}'
+                );
+
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/partial-no-return-multiple-statements.js');
+
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            simplify: true
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should simplify block statement', () => {
+                    assert.match(obfuscatedCode, regExp);
+                });
+            });
+        });
+
+        describe('With `ReturnStatement`', () => {
+            describe('Variant #1: multiple statements', () => {
+                const regExp: RegExp = new RegExp(
+                    'function foo *\\(\\) *{ *' +
+                        'const _0x([a-f0-9]){4,6} *= *baz\\(\\); *' +
+                        'return bark\\(\\), *hawk\\(\\);' +
+                    '}'
+                );
+
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/partial-return-multiple-statements.js');
+
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            simplify: true
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should simplify block statement', () => {
+                    assert.match(obfuscatedCode, regExp);
+                });
+            });
+        });
+    });
+
+    describe('Cases', () => {
+        describe('Variable declarations merge transformer integration', () => {
+            describe('Variant #1: three statements', () => {
+                const regExp: RegExp = new RegExp(
+                    'function foo *\\(\\) *{ *' +
+                        'var _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 block statement', () => {
+                    assert.match(obfuscatedCode, regExp);
+                });
+            });
+        });
+    });
+});

+ 5 - 0
test/functional-tests/node-transformers/simplifying-transformers/block-statement-simplify-transformer/fixtures/full-no-return-multiple-statements.js

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

+ 3 - 0
test/functional-tests/node-transformers/simplifying-transformers/block-statement-simplify-transformer/fixtures/full-no-return-single-statement.js

@@ -0,0 +1,3 @@
+function foo () {
+    bar();
+}

+ 6 - 0
test/functional-tests/node-transformers/simplifying-transformers/block-statement-simplify-transformer/fixtures/full-return-multiple-statements.js

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

+ 3 - 0
test/functional-tests/node-transformers/simplifying-transformers/block-statement-simplify-transformer/fixtures/full-return-single-statement.js

@@ -0,0 +1,3 @@
+function foo () {
+    return bar();
+}

+ 6 - 0
test/functional-tests/node-transformers/simplifying-transformers/block-statement-simplify-transformer/fixtures/partial-no-return-multiple-statements.js

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

+ 3 - 0
test/functional-tests/node-transformers/simplifying-transformers/block-statement-simplify-transformer/fixtures/partial-no-return-single-statement.js

@@ -0,0 +1,3 @@
+function foo () {
+    var bar = baz();
+}

+ 7 - 0
test/functional-tests/node-transformers/simplifying-transformers/block-statement-simplify-transformer/fixtures/partial-return-multiple-statements.js

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

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

@@ -0,0 +1,5 @@
+function foo() {
+    var bar = function () {};
+    var baz = function () {};
+    var bark = function () {};
+}

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

@@ -141,6 +141,38 @@ describe('VariableDeclarationsMergeTransformer', () => {
                 assert.match(obfuscatedCode, regExp);
             });
         });
+
+        describe('Variant #6: declarations inside nested function expressions', () => {
+            const regExp: RegExp = new RegExp(
+                'var foo *= *function *\\(\\) *{ *}, *' +
+                    'bar *= *function *\\(\\) *{ *' +
+                        'var _0x([a-f0-9]){4,6} *= *function *\\(\\) *{ *}, *' +
+                            '_0x([a-f0-9]){4,6} *= *function *\\(\\) *{ *' +
+                                'var _0x([a-f0-9]){4,6} *= *0x1, *' +
+                                    '_0x([a-f0-9]){4,6} *= *0x2; *' +
+                            '}; *' +
+                    '};'
+            );
+
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/declarations-inside-nested-function-expressions.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', () => {

+ 8 - 0
test/functional-tests/node-transformers/simplifying-transformers/variable-declarations-merge-transformer/fixtures/declarations-inside-nested-function-expressions.js

@@ -0,0 +1,8 @@
+var foo = function () {};
+var bar = function () {
+    var baz = function () {};
+    var bark = function () {
+        var hawk = 1;
+        var dog = 2;
+    };
+};

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff