소스 검색

Fixed behaviour of `simplify` options when node with a single-statement `body` is inside simplified `IfStatement` node
Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/860

sanex 4 년 전
부모
커밋
5289dcc834
18개의 변경된 파일952개의 추가작업 그리고 5개의 파일을 삭제
  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. 2 0
      src/enums/node/NodeType.ts
  7. 4 1
      src/node-transformers/simplifying-transformers/IfStatementSimplifyTransformer.ts
  8. 97 0
      src/node/NodeFactory.ts
  9. 76 0
      src/node/NodeGuards.ts
  10. 21 0
      src/types/node/TNodeWithSingleStatementBody.ts
  11. 178 3
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/IfStatementSimplifyTransformer.spec.ts
  12. 9 0
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/single-line-do-while-statement-as-prohibited-single-statement.js
  13. 8 0
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/single-line-for-in-statement-as-prohibited-single-statement.js
  14. 8 0
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/single-line-for-of-statement-as-prohibited-single-statement.js
  15. 8 0
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/single-line-for-statement-as-prohibited-single-statement.js
  16. 8 0
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/single-line-labeled-statement-as-prohibited-single-statement.js
  17. 8 0
      test/functional-tests/node-transformers/simplifying-transformers/if-statement-simplify-transformer/fixtures/single-line-while-statement-as-prohibited-single-statement.js
  18. 520 0
      test/unit-tests/node/node-guards/NodeGuards.spec.ts

+ 4 - 0
CHANGELOG.md

@@ -1,5 +1,9 @@
 Change Log
 
+v2.10.2
+---
+* Fixed behaviour of `simplify` options when node with a single-statement `body` is inside simplified `IfStatement` node. Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/860
+
 v2.10.1
 ---
 * Removed padding characters from all base64 encoded strings. Removed RegExp that trims padding characters from `base64` encoded strings from `atob` code helper to prevent mutation of `RegExp.$1` value during calls to the `stringArray`. Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/829

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 0
dist/index.browser.js


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 0
dist/index.cli.js


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 0
dist/index.js


+ 1 - 1
package.json

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

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

@@ -14,6 +14,7 @@ export enum NodeType {
     ClassDeclaration = 'ClassDeclaration',
     ConditionalExpression = 'ConditionalExpression',
     ContinueStatement = 'ContinueStatement',
+    DoWhileStatement = 'DoWhileStatement',
     ExportAllDeclaration = 'ExportAllDeclaration',
     ExportNamedDeclaration = 'ExportNamedDeclaration',
     ExportSpecifier = 'ExportSpecifier',
@@ -54,6 +55,7 @@ export enum NodeType {
     UpdateExpression = 'UpdateExpression',
     VariableDeclaration = 'VariableDeclaration',
     VariableDeclarator = 'VariableDeclarator',
+    WithStatement = 'WithStatement',
     WhileStatement = 'WhileStatement',
     YieldExpression = 'YieldExpression'
 }

+ 4 - 1
src/node-transformers/simplifying-transformers/IfStatementSimplifyTransformer.ts

@@ -286,6 +286,7 @@ export class IfStatementSimplifyTransformer extends AbstractStatementSimplifyTra
          */
         return NodeGuards.isFunctionDeclarationNode(statement)
             /**
+             * Ignore any nodes with a single statement as a `body`
              * Without ignore it can break following code:
              * Input:
              * if (condition1) {
@@ -302,8 +303,10 @@ export class IfStatementSimplifyTransformer extends AbstractStatementSimplifyTra
              *         var foo = bar();
              *     else
              *         var baz = bark();
+             *
+             * See issue: https://github.com/javascript-obfuscator/javascript-obfuscator/issues/860
              */
-            || NodeGuards.isIfStatementNode(statement)
+            || NodeGuards.isNodeWithSingleStatementBody(statement)
 
             /**
              * `let` and `const` variable declarations are not allowed outside of `IfStatement` block statement

+ 97 - 0
src/node/NodeFactory.ts

@@ -167,6 +167,20 @@ export class NodeFactory {
         };
     }
 
+    /**
+     * @param {Statement} body
+     * @param {Expression} test
+     * @returns {DoWhileStatement}
+     */
+    public static doWhileStatementNode (body: ESTree.Statement, test: ESTree.Expression): ESTree.DoWhileStatement {
+        return {
+            type: NodeType.DoWhileStatement,
+            body,
+            test,
+            metadata: { ignoredNode: false }
+        };
+    }
+
     /**
      * @param {Literal} source
      * @returns {ExportAllDeclaration}
@@ -210,6 +224,72 @@ export class NodeFactory {
         };
     }
 
+    /**
+     * @param {VariableDeclaration | Expression | null} init
+     * @param {Expression | null} test
+     * @param {Expression | null} update
+     * @param {Statement} body
+     * @returns {ForStatement}
+     */
+    public static forStatementNode (
+        init: ESTree.VariableDeclaration | ESTree.Expression | null,
+        test: ESTree.Expression | null,
+        update: ESTree.Expression | null,
+        body: ESTree.Statement
+    ): ESTree.ForStatement {
+        return {
+            type: NodeType.ForStatement,
+            init,
+            test,
+            update,
+            body,
+            metadata: { ignoredNode: false }
+        };
+    }
+
+    /**
+     * @param {VariableDeclaration | Pattern} left
+     * @param {Expression} right
+     * @param {Statement} body
+     * @returns {ForInStatement}
+     */
+    public static forInStatementNode (
+        left: ESTree.VariableDeclaration | ESTree.Pattern,
+        right: ESTree.Expression,
+        body: ESTree.Statement
+    ): ESTree.ForInStatement {
+        return {
+            type: NodeType.ForInStatement,
+            left,
+            right,
+            body,
+            metadata: { ignoredNode: false }
+        };
+    }
+
+    /**
+     * @param {boolean} await
+     * @param {VariableDeclaration | Pattern} left
+     * @param {Expression} right
+     * @param {Statement} body
+     * @returns {ForOfStatement}
+     */
+    public static forOfStatementNode (
+        await: boolean,
+        left: ESTree.VariableDeclaration | ESTree.Pattern,
+        right: ESTree.Expression,
+        body: ESTree.Statement
+    ): ESTree.ForOfStatement {
+        return {
+            type: NodeType.ForOfStatement,
+            await,
+            left,
+            right,
+            body,
+            metadata: { ignoredNode: false }
+        };
+    }
+
     /**
      * @param {string} functionName
      * @param {Identifier[]} params
@@ -298,6 +378,23 @@ export class NodeFactory {
         };
     }
 
+    /**
+     * @param {Identifier} label
+     * @param {Statement} body
+     * @returns {LabeledStatement}
+     */
+    public static labeledStatementNode (
+        label: ESTree.Identifier,
+        body: ESTree.Statement
+    ): ESTree.LabeledStatement {
+        return {
+            type: NodeType.LabeledStatement,
+            label,
+            body,
+            metadata: { ignoredNode: false }
+        };
+    }
+
     /**
      * @param {boolean | number | string} value
      * @param {string} raw

+ 76 - 0
src/node/NodeGuards.ts

@@ -1,3 +1,4 @@
+/* eslint-disable max-lines */
 import * as ESTree from 'estree';
 
 import { TNodeWithLexicalScope } from '../types/node/TNodeWithLexicalScope';
@@ -5,6 +6,7 @@ import { TNodeWithLexicalScopeStatements } from '../types/node/TNodeWithLexicalS
 import { TNodeWithStatements } from '../types/node/TNodeWithStatements';
 
 import { NodeType } from '../enums/node/NodeType';
+import { TNodeWithSingleStatementBody } from '../types/node/TNodeWithSingleStatementBody';
 
 export class NodeGuards {
     /**
@@ -116,6 +118,14 @@ export class NodeGuards {
             && 'directive' in node;
     }
 
+    /**
+     * @param {Node} node
+     * @returns {boolean}
+     */
+    public static isDoWhileStatementNode (node: ESTree.Node): node is ESTree.DoWhileStatement {
+        return node.type === NodeType.DoWhileStatement;
+    }
+
     /**
      * @param {Node} node
      * @returns {boolean}
@@ -149,6 +159,22 @@ export class NodeGuards {
             && !('directive' in node);
     }
 
+    /**
+     * @param {Node} node
+     * @returns {boolean}
+     */
+    public static isForStatementNode (node: ESTree.Node): node is ESTree.ForStatement {
+        return node.type === NodeType.ForStatement;
+    }
+
+    /**
+     * @param {Node} node
+     * @returns {boolean}
+     */
+    public static isForInStatementNode (node: ESTree.Node): node is ESTree.ForInStatement {
+        return node.type === NodeType.ForInStatement;
+    }
+
     /**
      * @param {Node} node
      * @returns {boolean}
@@ -201,6 +227,19 @@ export class NodeGuards {
         return node.type === NodeType.IfStatement;
     }
 
+    /**
+     * @param {Node} node
+     * @returns {boolean}
+     */
+    public static isIfStatementNodeWithSingleStatementBody (node: ESTree.Node): node is ESTree.IfStatement {
+        if (!NodeGuards.isIfStatementNode(node)) {
+           return false;
+        }
+
+        return !NodeGuards.isBlockStatementNode(node.consequent)
+            || (!!node.alternate && !NodeGuards.isBlockStatementNode(node.alternate));
+    }
+
     /**
      * @param {Node} node
      * @returns {boolean}
@@ -287,6 +326,35 @@ export class NodeGuards {
         return NodeGuards.isNodeWithLexicalScope(node) || NodeGuards.isBlockStatementNode(node);
     }
 
+    /**
+     * Checks if a node is the node with single statement body, like:
+     * while (true)
+     *     console.log(1);
+     *
+     * or:
+     *
+     *
+     * @param {Node} node
+     * @returns {boolean}
+     */
+    public static isNodeWithSingleStatementBody (node: ESTree.Node): node is TNodeWithSingleStatementBody {
+        // Different approach for `IfStatement` node because this node hasn't `body` property
+        if (NodeGuards.isIfStatementNode(node)) {
+            return NodeGuards.isIfStatementNodeWithSingleStatementBody(node);
+        }
+
+        // All other nodes with `Statement` node as `body` property
+        return (
+            NodeGuards.isForStatementNode(node)
+            || NodeGuards.isForOfStatementNode(node)
+            || NodeGuards.isForInStatementNode(node)
+            || NodeGuards.isWhileStatementNode(node)
+            || NodeGuards.isDoWhileStatementNode(node)
+            || NodeGuards.isWithStatementNode(node)
+            || NodeGuards.isLabeledStatementNode(node)
+        ) && !NodeGuards.isBlockStatementNode(node.body);
+    }
+
     /**
      * @param {Node} node
      * @param {Node} parentNode
@@ -438,6 +506,14 @@ export class NodeGuards {
         return node.type === NodeType.VariableDeclarator;
     }
 
+    /**
+     * @param {Node} node
+     * @returns {boolean}
+     */
+    public static isWithStatementNode (node: ESTree.Node): node is ESTree.WithStatement {
+        return node.type === NodeType.WithStatement;
+    }
+
     /**
      * @param {Node} node
      * @returns {boolean}

+ 21 - 0
src/types/node/TNodeWithSingleStatementBody.ts

@@ -0,0 +1,21 @@
+import * as ESTree from 'estree';
+
+export type TNodeWithSingleStatementBody = (
+    ESTree.LabeledStatement
+    | ESTree.WithStatement
+    | ESTree.WhileStatement
+    | ESTree.DoWhileStatement
+    | ESTree.ForStatement
+    | ESTree.ForInStatement
+    | ESTree.ForOfStatement
+    & {
+        body: Exclude<ESTree.Statement, ESTree.BlockStatement>;
+    }
+)
+| (
+    ESTree.IfStatement
+    & {
+        consequent: Exclude<ESTree.Statement, ESTree.BlockStatement>;
+        alternate?: Exclude<ESTree.Statement, ESTree.BlockStatement> | null;
+    }
+);

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

@@ -781,7 +781,182 @@ describe('IfStatementSimplifyTransformer', () => {
                 });
             });
 
-            describe('Variant #2: `FunctionDeclaration` as prohibited single statement', () => {
+            describe('Variant #2: Nodes with single statement `body` property', () => {
+                describe('Variant #1: Single line `ForStatement` as prohibited single statement', () => {
+                    const regExp: RegExp = new RegExp(
+                        'if *\\(!!\\[]\\) *{ *' +
+                            'for *\\( *' +
+                                'let _0x([a-f0-9]){4,6} *= *0x0; *' +
+                                '_0x([a-f0-9]){4,6} *< *0x1; *' +
+                                '_0x([a-f0-9]){4,6}\\+\\+ *' +
+                            '\\) *' +
+                                'console\\[\'log\']\\(_0x([a-f0-9]){4,6}\\); *' +
+                        '} *else *' +
+                            'var _0x([a-f0-9]){4,6} *= *hawk\\(\\);'
+                    );
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/single-line-for-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: Single line `ForOfStatement` as prohibited single statement', () => {
+                    const regExp: RegExp = new RegExp(
+                        'if *\\(!!\\[]\\) *{ *' +
+                            'for *\\(const _0x([a-f0-9]){4,6} of *\\[\\]\\) *' +
+                                'console\\[\'log\']\\(_0x([a-f0-9]){4,6}\\); *' +
+                        '} *else *' +
+                            'var _0x([a-f0-9]){4,6} *= *hawk\\(\\);'
+                    );
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/single-line-for-of-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 #3: Single line `ForInStatement` as prohibited single statement', () => {
+                    const regExp: RegExp = new RegExp(
+                        'if *\\(!!\\[]\\) *{ *' +
+                            'for *\\(const _0x([a-f0-9]){4,6} in *\\{\\}\\) *' +
+                                'console\\[\'log\']\\(_0x([a-f0-9]){4,6}\\); *' +
+                        '} *else *' +
+                            'var _0x([a-f0-9]){4,6} *= *hawk\\(\\);'
+                    );
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/single-line-for-in-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 #4: Single line `WhileStatement` as prohibited single statement', () => {
+                    const regExp: RegExp = new RegExp(
+                        'if *\\(!!\\[]\\) *{ *' +
+                            'while *\\(!!\\[]\\) *' +
+                                'console\\[\'log\']\\(0x1\\); *' +
+                        '} *else *' +
+                            'var _0x([a-f0-9]){4,6} *= *hawk\\(\\);'
+                    );
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/single-line-while-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 #5: Single line `DoWhileStatement` as prohibited single statement', () => {
+                    const regExp: RegExp = new RegExp(
+                        'if *\\(!!\\[]\\) *{ *' +
+                            'do *' +
+                                'console\\[\'log\']\\(0x1\\); *' +
+                            'while *\\(!!\\[]\\); *' +
+                        '} *else *' +
+                            'var _0x([a-f0-9]){4,6} *= *hawk\\(\\);'
+                    );
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/single-line-do-while-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 #6: Single line `LabeledStatement` as prohibited single statement', () => {
+                    const regExp: RegExp = new RegExp(
+                        'if *\\(!!\\[]\\) *{ *' +
+                            '_0x([a-f0-9]){4,6}: *' +
+                                'console\\[\'log\']\\(0x1\\); *' +
+                        '} *else *' +
+                            'var _0x([a-f0-9]){4,6} *= *hawk\\(\\);'
+                    );
+
+                    let obfuscatedCode: string;
+
+                    before(() => {
+                        const code: string = readFileAsString(__dirname + '/fixtures/single-line-labeled-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 #3: `FunctionDeclaration` as prohibited single statement', () => {
                 const regExp: RegExp = new RegExp(
                     'if *\\(!!\\[]\\) *{ *' +
                         'function _0x([a-f0-9]){4,6} *\\(\\) *{} *' +
@@ -810,7 +985,7 @@ describe('IfStatementSimplifyTransformer', () => {
                 });
             });
 
-            describe('Variant #3: `let` `VariableDeclaration` as prohibited single statement', () => {
+            describe('Variant #4: `let` `VariableDeclaration` as prohibited single statement', () => {
                 const regExp: RegExp = new RegExp(
                     'if *\\(!!\\[]\\) *{ *' +
                         'let foo *= *0x1; *' +
@@ -837,7 +1012,7 @@ describe('IfStatementSimplifyTransformer', () => {
                 });
             });
 
-            describe('Variant #4: `const` `VariableDeclaration` as prohibited single statement', () => {
+            describe('Variant #5: `const` `VariableDeclaration` as prohibited single statement', () => {
                 const regExp: RegExp = new RegExp(
                     'if *\\(!!\\[]\\) *{ *' +
                         'const foo *= *0x1; *' +

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

@@ -0,0 +1,9 @@
+function foo() {
+    if (true) {
+        do
+            console.log(1);
+        while (true);
+    } else {
+        var bark = hawk();
+    }
+}

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

@@ -0,0 +1,8 @@
+function foo() {
+    if (true) {
+        for (const key in {})
+            console.log(key);
+    } else {
+        var bark = hawk();
+    }
+}

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

@@ -0,0 +1,8 @@
+function foo() {
+    if (true) {
+        for (const item of [])
+            console.log(item);
+    } else {
+        var bark = hawk();
+    }
+}

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

@@ -0,0 +1,8 @@
+function foo() {
+    if (true) {
+        for (let i= 0; i < 1; i++)
+            console.log(i);
+    } else {
+        var bark = hawk();
+    }
+}

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

@@ -0,0 +1,8 @@
+function foo() {
+    if (true) {
+        label:
+            console.log(1);
+    } else {
+        var bark = hawk();
+    }
+}

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

@@ -0,0 +1,8 @@
+function foo() {
+    if (true) {
+        while (true)
+            console.log(1);
+    } else {
+        var bark = hawk();
+    }
+}

+ 520 - 0
test/unit-tests/node/node-guards/NodeGuards.spec.ts

@@ -7,6 +7,107 @@ import { NodeFactory } from '../../../../src/node/NodeFactory';
 import { NodeUtils } from '../../../../src/node/NodeUtils';
 
 describe('NodeGuards', () => {
+    describe('isIfStatementNodeWithSingleStatementBody', () => {
+        describe('truthful checks', () => {
+            describe('Variant #1: single statement `consequent`', () => {
+                const expectedResult: boolean = true;
+                const node: ESTree.IfStatement = NodeFactory.ifStatementNode(
+                    NodeFactory.literalNode(true),
+                    NodeFactory.expressionStatementNode(
+                        NodeFactory.literalNode(true)
+                    )
+                );
+
+                let result: boolean;
+
+                before(() => {
+                    result = NodeGuards.isIfStatementNodeWithSingleStatementBody(node);
+                });
+
+                it('should check if `IfStatement` node has single statement `consequent`', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+
+            describe('Variant #2: single statement `alternate`', () => {
+                const expectedResult: boolean = true;
+                const node: ESTree.IfStatement = NodeFactory.ifStatementNode(
+                    NodeFactory.literalNode(true),
+                    NodeFactory.blockStatementNode([
+                        NodeFactory.expressionStatementNode(
+                            NodeFactory.literalNode(true)
+                        )
+                    ]),
+                    NodeFactory.expressionStatementNode(
+                        NodeFactory.literalNode(true)
+                    )
+                );
+
+                let result: boolean;
+
+                before(() => {
+                    result = NodeGuards.isIfStatementNodeWithSingleStatementBody(node);
+                });
+
+                it('should check if `IfStatement` node has single statement `alternate`', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+
+            describe('Variant #3: single statement `consequent` and `alternate`', () => {
+                const expectedResult: boolean = true;
+                const node: ESTree.IfStatement = NodeFactory.ifStatementNode(
+                    NodeFactory.literalNode(true),
+                    NodeFactory.expressionStatementNode(
+                        NodeFactory.literalNode(true)
+                    ),
+                    NodeFactory.expressionStatementNode(
+                        NodeFactory.literalNode(true)
+                    )
+                );
+
+                let result: boolean;
+
+                before(() => {
+                    result = NodeGuards.isIfStatementNodeWithSingleStatementBody(node);
+                });
+
+                it('should check if `IfStatement` node has single statement `consequent` and `alternate`', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+        });
+
+        describe('false checks', () => {
+            describe('Variant #1: multiple statements `consequent` and `alternate`', () => {
+                const expectedResult: boolean = false;
+                const node: ESTree.IfStatement = NodeFactory.ifStatementNode(
+                    NodeFactory.literalNode(true),
+                    NodeFactory.blockStatementNode([
+                        NodeFactory.expressionStatementNode(
+                            NodeFactory.literalNode(true)
+                        )
+                    ]),
+                    NodeFactory.blockStatementNode([
+                        NodeFactory.expressionStatementNode(
+                            NodeFactory.literalNode(true)
+                        )
+                    ])
+                );
+
+                let result: boolean;
+
+                before(() => {
+                    result = NodeGuards.isIfStatementNodeWithSingleStatementBody(node);
+                });
+
+                it('should check if `IfStatement` node has multiple statements `consequent` and `alternate`', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+        });
+    });
+
     describe('isNodeWithLexicalScopeStatements', () => {
         describe('truthful checks', () => {
             describe('Variant #1: block statement of function declaration', () => {
@@ -138,6 +239,425 @@ describe('NodeGuards', () => {
         });
     });
 
+    describe('isNodeWithSingleStatementBody', () => {
+        describe('truthful checks', () => {
+            describe('Variant #1: `IfStatement` node', () => {
+                describe('Variant #1: single statement `consequent`', () => {
+                    const expectedResult: boolean = true;
+                    const node: ESTree.IfStatement = NodeFactory.ifStatementNode(
+                        NodeFactory.literalNode(true),
+                        NodeFactory.expressionStatementNode(
+                            NodeFactory.literalNode(true)
+                        )
+                    );
+
+                    let result: boolean;
+
+                    before(() => {
+                        result = NodeGuards.isNodeWithSingleStatementBody(node);
+                    });
+
+                    it('should check if `IfStatement` node has single statement `consequent`', () => {
+                        assert.equal(result, expectedResult);
+                    });
+                });
+
+                describe('Variant #2: single statement `alternate`', () => {
+                    const expectedResult: boolean = true;
+                    const node: ESTree.IfStatement = NodeFactory.ifStatementNode(
+                        NodeFactory.literalNode(true),
+                        NodeFactory.blockStatementNode([
+                            NodeFactory.expressionStatementNode(
+                                NodeFactory.literalNode(true)
+                            )
+                        ]),
+                        NodeFactory.expressionStatementNode(
+                            NodeFactory.literalNode(true)
+                        )
+                    );
+
+                    let result: boolean;
+
+                    before(() => {
+                        result = NodeGuards.isNodeWithSingleStatementBody(node);
+                    });
+
+                    it('should check if `IfStatement` node has single statement `alternate`', () => {
+                        assert.equal(result, expectedResult);
+                    });
+                });
+
+                describe('Variant #3: single statement `consequent` and `alternate`', () => {
+                    const expectedResult: boolean = true;
+                    const node: ESTree.IfStatement = NodeFactory.ifStatementNode(
+                        NodeFactory.literalNode(true),
+                        NodeFactory.expressionStatementNode(
+                            NodeFactory.literalNode(true)
+                        ),
+                        NodeFactory.expressionStatementNode(
+                            NodeFactory.literalNode(true)
+                        )
+                    );
+
+                    let result: boolean;
+
+                    before(() => {
+                        result = NodeGuards.isNodeWithSingleStatementBody(node);
+                    });
+
+                    it('should check if `IfStatement` node has single statement `consequent` and `alternate`', () => {
+                        assert.equal(result, expectedResult);
+                    });
+                });
+            });
+
+            describe('Variant #2: `ForStatement` node', () => {
+                const expectedResult: boolean = true;
+                const node: ESTree.ForStatement = NodeFactory.forStatementNode(
+                    NodeFactory.variableDeclarationNode([
+                        NodeFactory.variableDeclaratorNode(
+                            NodeFactory.identifierNode('i'),
+                            NodeFactory.literalNode(0)
+                        )
+                    ]),
+                    NodeFactory.binaryExpressionNode(
+                        '<',
+                        NodeFactory.identifierNode('i'),
+                        NodeFactory.literalNode(10)
+                    ),
+                    NodeFactory.updateExpressionNode(
+                        '++',
+                        NodeFactory.identifierNode('i')
+                    ),
+                    NodeFactory.expressionStatementNode(
+                        NodeFactory.literalNode(true)
+                    )
+                );
+
+                let result: boolean;
+
+                before(() => {
+                    result = NodeGuards.isNodeWithSingleStatementBody(node);
+                });
+
+                it('should check if `ForStatement` node has single statement `body`', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+
+            describe('Variant #3: `ForInStatement` node', () => {
+                const expectedResult: boolean = true;
+                const node: ESTree.ForInStatement = NodeFactory.forInStatementNode(
+                    NodeFactory.variableDeclarationNode([
+                        NodeFactory.variableDeclaratorNode(
+                            NodeFactory.identifierNode('key'),
+                            null
+                        )
+                    ]),
+                    NodeFactory.objectExpressionNode(
+                        []
+                    ),
+                    NodeFactory.expressionStatementNode(
+                        NodeFactory.literalNode(true)
+                    )
+                );
+
+                let result: boolean;
+
+                before(() => {
+                    result = NodeGuards.isNodeWithSingleStatementBody(node);
+                });
+
+                it('should check if `ForInStatement` node has single statement `body`', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+
+            describe('Variant #4: `ForOfStatement` node', () => {
+                const expectedResult: boolean = true;
+                const node: ESTree.ForOfStatement = NodeFactory.forOfStatementNode(
+                    false,
+                    NodeFactory.variableDeclarationNode([
+                        NodeFactory.variableDeclaratorNode(
+                            NodeFactory.identifierNode('key'),
+                            null
+                        )
+                    ]),
+                    NodeFactory.objectExpressionNode(
+                        []
+                    ),
+                    NodeFactory.expressionStatementNode(
+                        NodeFactory.literalNode(true)
+                    )
+                );
+
+                let result: boolean;
+
+                before(() => {
+                    result = NodeGuards.isNodeWithSingleStatementBody(node);
+                });
+
+                it('should check if `ForOfStatement` node has single statement `body`', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+
+            describe('Variant #5: `WhileStatement` node', () => {
+                const expectedResult: boolean = true;
+                const node: ESTree.WhileStatement = NodeFactory.whileStatementNode(
+                    NodeFactory.literalNode(true),
+                    NodeFactory.expressionStatementNode(
+                        NodeFactory.literalNode(true)
+                    )
+                );
+
+                let result: boolean;
+
+                before(() => {
+                    result = NodeGuards.isNodeWithSingleStatementBody(node);
+                });
+
+                it('should check if `WhileStatement` node has single statement `body`', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+
+            describe('Variant #6: `DoWhileStatement` node', () => {
+                const expectedResult: boolean = true;
+                const node: ESTree.DoWhileStatement = NodeFactory.doWhileStatementNode(
+                    NodeFactory.expressionStatementNode(
+                        NodeFactory.literalNode(true)
+                    ),
+                    NodeFactory.literalNode(true)
+                );
+
+                let result: boolean;
+
+                before(() => {
+                    result = NodeGuards.isNodeWithSingleStatementBody(node);
+                });
+
+                it('should check if `DoWhileStatement` node has single statement `body`', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+
+            describe('Variant #7: `LabeledStatement` node', () => {
+                const expectedResult: boolean = true;
+                const node: ESTree.LabeledStatement = NodeFactory.labeledStatementNode(
+                    NodeFactory.identifierNode('label'),
+                    NodeFactory.expressionStatementNode(
+                        NodeFactory.literalNode(true)
+                    )
+                );
+
+                let result: boolean;
+
+                before(() => {
+                    result = NodeGuards.isNodeWithSingleStatementBody(node);
+                });
+
+                it('should check if `LabeledStatement` node has single statement `body`', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+        });
+
+        describe('false checks', () => {
+            describe('Variant #1: `IfStatement` node', () => {
+                describe('Variant #1: multiple statements `consequent` and `alternate`', () => {
+                    const expectedResult: boolean = false;
+                    const node: ESTree.IfStatement = NodeFactory.ifStatementNode(
+                        NodeFactory.literalNode(true),
+                        NodeFactory.blockStatementNode([
+                            NodeFactory.expressionStatementNode(
+                                NodeFactory.literalNode(true)
+                            )
+                        ]),
+                        NodeFactory.blockStatementNode([
+                            NodeFactory.expressionStatementNode(
+                                NodeFactory.literalNode(true)
+                            )
+                        ])
+                    );
+
+                    let result: boolean;
+
+                    before(() => {
+                        result = NodeGuards.isNodeWithSingleStatementBody(node);
+                    });
+
+                    it('should check if `IfStatement` node has multiple statements `consequent` and `alternate`', () => {
+                        assert.equal(result, expectedResult);
+                    });
+                });
+            });
+
+            describe('Variant #2: `ForStatement` node', () => {
+                const expectedResult: boolean = false;
+                const node: ESTree.ForStatement = NodeFactory.forStatementNode(
+                    NodeFactory.variableDeclarationNode([
+                        NodeFactory.variableDeclaratorNode(
+                            NodeFactory.identifierNode('i'),
+                            NodeFactory.literalNode(0)
+                        )
+                    ]),
+                    NodeFactory.binaryExpressionNode(
+                        '<',
+                        NodeFactory.identifierNode('i'),
+                        NodeFactory.literalNode(10)
+                    ),
+                    NodeFactory.updateExpressionNode(
+                        '++',
+                        NodeFactory.identifierNode('i')
+                    ),
+                    NodeFactory.blockStatementNode([
+                        NodeFactory.expressionStatementNode(
+                            NodeFactory.literalNode(true)
+                        )
+                    ])
+                );
+
+                let result: boolean;
+
+                before(() => {
+                    result = NodeGuards.isNodeWithSingleStatementBody(node);
+                });
+
+                it('should check if `ForStatement` node has multiple statements `body`', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+
+            describe('Variant #3: `ForInStatement` node', () => {
+                const expectedResult: boolean = false;
+                const node: ESTree.ForInStatement = NodeFactory.forInStatementNode(
+                    NodeFactory.variableDeclarationNode([
+                        NodeFactory.variableDeclaratorNode(
+                            NodeFactory.identifierNode('key'),
+                            null
+                        )
+                    ]),
+                    NodeFactory.objectExpressionNode(
+                        []
+                    ),
+                    NodeFactory.blockStatementNode([
+                        NodeFactory.expressionStatementNode(
+                            NodeFactory.literalNode(true)
+                        )
+                    ])
+                );
+
+                let result: boolean;
+
+                before(() => {
+                    result = NodeGuards.isNodeWithSingleStatementBody(node);
+                });
+
+                it('should check if `ForInStatement` node has multiple statements `body`', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+
+            describe('Variant #4: `ForOfStatement` node', () => {
+                const expectedResult: boolean = false;
+                const node: ESTree.ForOfStatement = NodeFactory.forOfStatementNode(
+                    false,
+                    NodeFactory.variableDeclarationNode([
+                        NodeFactory.variableDeclaratorNode(
+                            NodeFactory.identifierNode('key'),
+                            null
+                        )
+                    ]),
+                    NodeFactory.objectExpressionNode(
+                        []
+                    ),
+                    NodeFactory.blockStatementNode([
+                        NodeFactory.expressionStatementNode(
+                            NodeFactory.literalNode(true)
+                        )
+                    ])
+                );
+
+                let result: boolean;
+
+                before(() => {
+                    result = NodeGuards.isNodeWithSingleStatementBody(node);
+                });
+
+                it('should check if `ForOfStatement` node has multiple statements `body`', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+
+            describe('Variant #5: `WhileStatement` node', () => {
+                const expectedResult: boolean = false;
+                const node: ESTree.WhileStatement = NodeFactory.whileStatementNode(
+                    NodeFactory.literalNode(true),
+                    NodeFactory.blockStatementNode([
+                        NodeFactory.expressionStatementNode(
+                            NodeFactory.literalNode(true)
+                        )
+                    ])
+                );
+
+                let result: boolean;
+
+                before(() => {
+                    result = NodeGuards.isNodeWithSingleStatementBody(node);
+                });
+
+                it('should check if `WhileStatement` node has multiple statements `body`', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+
+            describe('Variant #6: `DoWhileStatement` node', () => {
+                const expectedResult: boolean = false;
+                const node: ESTree.DoWhileStatement = NodeFactory.doWhileStatementNode(
+                    NodeFactory.blockStatementNode([
+                        NodeFactory.expressionStatementNode(
+                            NodeFactory.literalNode(true)
+                        )
+                    ]),
+                    NodeFactory.literalNode(true)
+                );
+
+                let result: boolean;
+
+                before(() => {
+                    result = NodeGuards.isNodeWithSingleStatementBody(node);
+                });
+
+                it('should check if `DoWhileStatement` node has multiple statements `body`', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+
+            describe('Variant #7: `LabeledStatement` node', () => {
+                const expectedResult: boolean = false;
+                const node: ESTree.LabeledStatement = NodeFactory.labeledStatementNode(
+                    NodeFactory.identifierNode('label'),
+                    NodeFactory.blockStatementNode([
+                        NodeFactory.expressionStatementNode(
+                            NodeFactory.literalNode(true)
+                        )
+                    ])
+                );
+
+                let result: boolean;
+
+                before(() => {
+                    result = NodeGuards.isNodeWithSingleStatementBody(node);
+                });
+
+                it('should check if `LabeledStatement` node has multiple statements `body`', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+        });
+    });
+
     describe('isNodeWithStatements', () => {
         describe('truthful checks', () => {
             describe('Variant #1: program node', () => {

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.