Browse Source

Added more tests. Updated `NodeAppender` class to work with all scope nodes.

sanex3339 7 years ago
parent
commit
3e1e1142ba

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


+ 8 - 13
src/node-transformers/converting-transformers/ObjectExpressionKeysTransformer.ts

@@ -18,9 +18,9 @@ import { NodeUtils } from '../../node/NodeUtils';
 @injectable()
 export class ObjectExpressionKeysTransformer extends AbstractNodeTransformer {
     /**
-     * @type {Map<VariableDeclarator, BlockStatement>}
+     * @type {Map<VariableDeclarator, TNodeWithScope>}
      */
-    private cachedScopeNodesMap: Map <ESTree.VariableDeclarator, ESTree.BlockStatement> = new Map();
+    private cachedScopeNodesMap: Map <ESTree.VariableDeclarator, TNodeWithScope> = new Map();
 
     /**
      * @param {IRandomGenerator} randomGenerator
@@ -34,27 +34,22 @@ export class ObjectExpressionKeysTransformer extends AbstractNodeTransformer {
     }
 
     /**
-     * @param {BlockStatement} scopeNode
+     * @param {TNodeWithScope} scopeNode
      * @param {ExpressionStatement[]} expressionStatements
      * @param {Node} variableDeclarator
      */
     private static appendExpressionStatements (
-        scopeNode: ESTree.BlockStatement,
+        scopeNode: TNodeWithScope,
         expressionStatements: ESTree.ExpressionStatement[],
         variableDeclarator: ESTree.Node
     ): void {
         const variableDeclaration: ESTree.Node | undefined = variableDeclarator.parentNode;
 
-        if (
-            !variableDeclaration
-            || !NodeGuards.isVariableDeclarationNode(variableDeclaration)
-        ) {
+        if (!variableDeclaration || !NodeGuards.isVariableDeclarationNode(variableDeclaration)) {
             throw new Error('Cannot find variable declaration for variable declarator');
         }
 
-        const indexInBlockStatement: number = scopeNode.body.indexOf(variableDeclaration);
-
-        NodeAppender.insertNodeAtIndex(scopeNode, expressionStatements, indexInBlockStatement + 1);
+        NodeAppender.insertNodeAfter(scopeNode, expressionStatements, variableDeclaration);
     }
 
     /**
@@ -108,7 +103,7 @@ export class ObjectExpressionKeysTransformer extends AbstractNodeTransformer {
 
         const scopeNode: TNodeWithScope | null = NodeUtils.getScopeOfNode(variableDeclarator);
 
-        if (!scopeNode || !NodeGuards.isBlockStatementNode(scopeNode)) {
+        if (!scopeNode || !NodeGuards.isNodeHasScope(scopeNode)) {
             return objectExpressionNode;
         }
 
@@ -216,7 +211,7 @@ export class ObjectExpressionKeysTransformer extends AbstractNodeTransformer {
             return objectExpressionNode;
         }
 
-        const scopeNode: ESTree.BlockStatement | undefined = this.cachedScopeNodesMap.get(variableDeclarator);
+        const scopeNode: TNodeWithScope | undefined = this.cachedScopeNodesMap.get(variableDeclarator);
 
         if (!scopeNode) {
             return objectExpressionNode;

+ 79 - 53
src/node/NodeAppender.ts

@@ -4,6 +4,8 @@ import { TNodeWithBlockScope } from '../types/node/TNodeWithBlockScope';
 import { TStatement } from '../types/node/TStatement';
 
 import { IStackTraceData } from '../interfaces/analyzers/stack-trace-analyzer/IStackTraceData';
+import { TNodeWithScope } from '../types/node/TNodeWithScope';
+import { NodeGuards } from './NodeGuards';
 
 /**
  * This class appends node into a first deepest BlockStatement in order of function calls
@@ -24,23 +26,20 @@ import { IStackTraceData } from '../interfaces/analyzers/stack-trace-analyzer/IS
  */
 export class NodeAppender {
     /**
-     * @param {TNodeWithBlockScope} blockScopeNode
-     * @param {TStatement[]} nodeBodyStatements
+     * @param {TNodeWithScope} scopeNode
+     * @param {TStatement[]} scopeStatements
      */
-    public static appendNode (
-        blockScopeNode: TNodeWithBlockScope,
-        nodeBodyStatements: TStatement[]
-    ): void {
-        if (!NodeAppender.validateBodyStatements(nodeBodyStatements)) {
-            nodeBodyStatements = [];
+    public static appendNode (scopeNode: TNodeWithScope, scopeStatements: TStatement[]): void {
+        if (!NodeAppender.validateScopeStatements(scopeStatements)) {
+            scopeStatements = [];
         }
 
-        nodeBodyStatements = NodeAppender.parentizeBodyStatementsBeforeAppend(blockScopeNode, nodeBodyStatements);
+        scopeStatements = NodeAppender.parentizeScopeStatementsBeforeAppend(scopeNode, scopeStatements);
 
-        blockScopeNode.body = [
-            ...blockScopeNode.body,
-            ...nodeBodyStatements
-        ];
+        NodeAppender.setScopeNodeStatements(scopeNode, [
+            ...NodeAppender.getScopeNodeStatements(scopeNode),
+            ...scopeStatements
+        ]);
     }
 
     /**
@@ -96,70 +95,97 @@ export class NodeAppender {
     }
 
     /**
-     * @param {TNodeWithBlockScope} blockScopeNode
-     * @param {TStatement[]} nodeBodyStatements
+     * @param {TNodeWithScope} scopeNode
+     * @param {TStatement[]} scopeStatements
+     * @param {Node} targetStatement
+     */
+    public static insertNodeAfter (scopeNode: TNodeWithScope, scopeStatements: TStatement[], targetStatement: ESTree.Statement): void {
+        const indexInScopeStatement: number = NodeAppender
+            .getScopeNodeStatements(scopeNode)
+            .indexOf(targetStatement);
+
+        NodeAppender.insertNodeAtIndex(scopeNode, scopeStatements, indexInScopeStatement + 1);
+    }
+
+    /**
+     * @param {TNodeWithScope} scopeNode
+     * @param {TStatement[]} scopeStatements
      * @param {number} index
      */
-    public static insertNodeAtIndex (
-        blockScopeNode: TNodeWithBlockScope,
-        nodeBodyStatements: TStatement[],
-        index: number
-    ): void {
-        if (!NodeAppender.validateBodyStatements(nodeBodyStatements)) {
-            nodeBodyStatements = [];
+    public static insertNodeAtIndex (scopeNode: TNodeWithScope, scopeStatements: TStatement[], index: number): void {
+        if (!NodeAppender.validateScopeStatements(scopeStatements)) {
+            scopeStatements = [];
         }
 
-        nodeBodyStatements = NodeAppender.parentizeBodyStatementsBeforeAppend(blockScopeNode, nodeBodyStatements);
+        scopeStatements = NodeAppender.parentizeScopeStatementsBeforeAppend(scopeNode, scopeStatements);
 
-        blockScopeNode.body = [
-            ...blockScopeNode.body.slice(0, index),
-            ...nodeBodyStatements,
-            ...blockScopeNode.body.slice(index)
-        ];
+        NodeAppender.setScopeNodeStatements(scopeNode, [
+            ...NodeAppender.getScopeNodeStatements(scopeNode).slice(0, index),
+            ...scopeStatements,
+            ...NodeAppender.getScopeNodeStatements(scopeNode).slice(index)
+        ]);
     }
 
     /**
-     * @param {TNodeWithBlockScope} blockScopeNode
-     * @param {TStatement[]} nodeBodyStatements
+     * @param {TNodeWithScope} scopeNode
+     * @param {TStatement[]} scopeStatements
      */
-    public static prependNode (
-        blockScopeNode: TNodeWithBlockScope,
-        nodeBodyStatements: TStatement[]
-    ): void {
-        if (!NodeAppender.validateBodyStatements(nodeBodyStatements)) {
-            nodeBodyStatements = [];
+    public static prependNode (scopeNode: TNodeWithScope, scopeStatements: TStatement[]): void {
+        if (!NodeAppender.validateScopeStatements(scopeStatements)) {
+            scopeStatements = [];
         }
 
-        nodeBodyStatements = NodeAppender.parentizeBodyStatementsBeforeAppend(blockScopeNode, nodeBodyStatements);
+        scopeStatements = NodeAppender.parentizeScopeStatementsBeforeAppend(scopeNode, scopeStatements);
 
-        blockScopeNode.body = [
-            ...nodeBodyStatements,
-            ...blockScopeNode.body,
-        ];
+        NodeAppender.setScopeNodeStatements(scopeNode, [
+            ...scopeStatements,
+            ...NodeAppender.getScopeNodeStatements(scopeNode),
+        ]);
     }
 
     /**
-     * @param {TNodeWithBlockScope} blockScopeNode
-     * @param {TStatement[]} nodeBodyStatements
+     * @param {TNodeWithScope} scopeNode
      * @returns {TStatement[]}
      */
-    private static parentizeBodyStatementsBeforeAppend (
-        blockScopeNode: TNodeWithBlockScope,
-        nodeBodyStatements: TStatement[]
-    ): TStatement[] {
-        nodeBodyStatements.forEach((statement: TStatement) => {
-            statement.parentNode = blockScopeNode;
+    private static getScopeNodeStatements (scopeNode: TNodeWithScope): TStatement[] {
+        if (NodeGuards.isSwitchCaseNode(scopeNode)) {
+            return scopeNode.consequent;
+        }
+
+        return scopeNode.body;
+    }
+
+    /**
+     * @param {TNodeWithScope} scopeNode
+     * @param {TStatement[]} scopeStatements
+     * @returns {TStatement[]}
+     */
+    private static parentizeScopeStatementsBeforeAppend (scopeNode: TNodeWithScope, scopeStatements: TStatement[]): TStatement[] {
+        scopeStatements.forEach((statement: TStatement) => {
+            statement.parentNode = scopeNode;
         });
 
-        return nodeBodyStatements;
+        return scopeStatements;
     }
 
     /**
-     * @param {TStatement[]} nodeBodyStatements
+     * @param {TNodeWithScope} scopeNode
+     * @param {TStatement[]} statements
+     */
+    private static setScopeNodeStatements (scopeNode: TNodeWithScope, statements: TStatement[]): void {
+        if (NodeGuards.isSwitchCaseNode(scopeNode)) {
+            scopeNode.consequent = <ESTree.Statement[]>statements;
+        } else {
+            scopeNode.body = statements;
+        }
+    }
+
+    /**
+     * @param {TStatement[]} scopeStatement
      * @returns {boolean}
      */
-    private static validateBodyStatements (nodeBodyStatements: TStatement[]): boolean {
-        return nodeBodyStatements.every((statementNode: TStatement) => {
+    private static validateScopeStatements (scopeStatement: TStatement[]): boolean {
+        return scopeStatement.every((statementNode: TStatement) => {
             return !!statementNode && statementNode.hasOwnProperty('type');
         });
     }

+ 2 - 2
src/node/NodeGuards.ts

@@ -197,7 +197,7 @@ export class NodeGuards {
         const parentNode: ESTree.Node | undefined = node.parentNode;
 
         if (!parentNode) {
-            return false;
+            throw new ReferenceError('`parentNode` property of given node is `undefined`');
         }
 
         return NodeGuards.isProgramNode(node) || (
@@ -271,7 +271,7 @@ export class NodeGuards {
             parentNode.key === node;
         const parentNodeIsMemberExpressionNode: boolean = (
             NodeGuards.isMemberExpressionNode(parentNode) &&
-            parentNode.computed === false &&
+            !parentNode.computed &&
             parentNode.property === node
         );
         const parentNodeIsMethodDefinitionNode: boolean = NodeGuards.isMethodDefinitionNode(parentNode) &&

+ 24 - 0
src/node/Nodes.ts

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

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

@@ -193,16 +193,13 @@ describe('ObjectExpressionKeysTransformer', () => {
                 assert.match(obfuscatedCode,  regExp);
             });
         });
-    });
 
-    describe('Ignore transformation', () => {
-        describe('variant #1: switch catch statement', () => {
+        describe('variant #4: switch catch statement', () => {
             const match: string = `` +
                 `switch *\\(!!\\[]\\) *{` +
                     `case *!!\\[]:` +
-                        `var *${variableMatch} *= *{` +
-                            `'foo': *'bar'` +
-                        `};` +
+                        `var *${variableMatch} *= *{};` +
+                        `${variableMatch}\\['foo'] *= *'bar';` +
                 `}` +
             ``;
             const regExp: RegExp = new RegExp(match);
@@ -222,11 +219,13 @@ describe('ObjectExpressionKeysTransformer', () => {
                 obfuscatedCode = obfuscationResult.getObfuscatedCode();
             });
 
-            it('shouldn\'t transform object keys', () => {
+            it('should transform object keys', () => {
                 assert.match(obfuscatedCode,  regExp);
             });
         });
+    });
 
+    describe('Ignore transformation', () => {
         describe('variant #2: variable declaration without initialization', () => {
             const match: string = `` +
                 `var *${variableMatch};` +

+ 1 - 0
test/index.spec.ts

@@ -20,6 +20,7 @@ import './unit-tests/generators/identifier-names-generators/MangledlIdentifierNa
 import './unit-tests/javascript-obfuscator/JavaScriptObfuscator.spec';
 import './unit-tests/logger/Logger.spec';
 import './unit-tests/node/node-appender/NodeAppender.spec';
+import './unit-tests/node/node-guards/NodeGuards.spec';
 import './unit-tests/node/node-utils/NodeUtils.spec';
 import './unit-tests/node-transformers/preparing-transformers/ObfuscatingGuardsTransformer.spec';
 import './unit-tests/obfuscation-result/ObfuscationResult.spec';

+ 25 - 0
test/unit-tests/node/node-appender/NodeAppender.spec.ts

@@ -1,3 +1,5 @@
+import 'reflect-metadata';
+
 import { ServiceIdentifiers } from '../../../../src/container/ServiceIdentifiers';
 
 import * as ESTree from 'estree';
@@ -162,6 +164,29 @@ describe('NodeAppender', () => {
         });
     });
 
+    describe('insertNodeAfter (scopeNode: TNodeWithScope, scopeStatements: TStatement[], targetStatement: ESTree.Statement): void', () => {
+        let astTree: ESTree.Program,
+            expectedAstTree: ESTree.Program,
+            node: TStatement[],
+            targetStatement: ESTree.Statement;
+
+        before(() => {
+            node = convertCodeToStructure('/fixtures/simple-input.js');
+            astTree = convertCodeToAst('/fixtures/insert-node-after.js');
+            expectedAstTree = convertCodeToAst('/fixtures/insert-node-after-expected.js');
+            targetStatement = <ESTree.Statement>astTree.body[1];
+
+            astTree = NodeUtils.parentize(astTree);
+            expectedAstTree = NodeUtils.parentize(expectedAstTree);
+
+            NodeAppender.insertNodeAfter(astTree, node, targetStatement);
+        });
+
+        it('should insert given node in `BlockStatement` node body after target statement', () => {
+            assert.deepEqual(astTree, expectedAstTree);
+        });
+    });
+
     describe('insertNodeAtIndex (blockScopeNode: TNodeWithBlockScope[], nodeBodyStatements: TStatement[], index: number): void', () => {
         let astTree: ESTree.Program,
             expectedAstTree: ESTree.Program,

+ 13 - 0
test/unit-tests/node/node-appender/fixtures/insert-node-after-expected.js

@@ -0,0 +1,13 @@
+var func1 = function () {
+    return true;
+};
+
+var func2 = function () {
+    return false;
+};
+
+var test = 1;
+
+var func3 = function () {
+    return 'string';
+};

+ 11 - 0
test/unit-tests/node/node-appender/fixtures/insert-node-after.js

@@ -0,0 +1,11 @@
+var func1 = function () {
+    return true;
+};
+
+var func2 = function () {
+    return false;
+};
+
+var func3 = function () {
+    return 'string';
+};

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

@@ -0,0 +1,453 @@
+import * as ESTree from 'estree';
+
+import { assert } from 'chai';
+
+import { Nodes } from '../../../../src/node/Nodes';
+import { NodeGuards } from '../../../../src/node/NodeGuards';
+import { NodeUtils } from '../../../../src/node/NodeUtils';
+
+describe('NodeGuards', () => {
+    describe('isNodeHasBlockScope (node: ESTree.Node): node is TNodeWithBlockScope', () => {
+        describe('truthful checks', () => {
+            describe('variant #1: block statement of function declaration', () => {
+                const expectedResult: boolean = true;
+                const node: ESTree.Node = Nodes.getBlockStatementNode();
+                const parentNode: ESTree.FunctionDeclaration = Nodes.getFunctionDeclarationNode(
+                    'foo',
+                    [],
+                    node
+                );
+
+                let result: boolean;
+
+                before(() => {
+                    NodeUtils.parentize(parentNode);
+                    result = NodeGuards.isNodeHasBlockScope(node);
+                });
+
+                it('should check if node has block scope', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+
+            describe('variant #2: block statement of function expression', () => {
+                const expectedResult: boolean = true;
+                const node: ESTree.Node = Nodes.getBlockStatementNode();
+                const parentNode: ESTree.FunctionExpression = Nodes.getFunctionExpressionNode(
+                    [],
+                    node
+                );
+
+                let result: boolean;
+
+                before(() => {
+                    NodeUtils.parentize(parentNode);
+                    result = NodeGuards.isNodeHasBlockScope(node);
+                });
+
+                it('should check if node has block scope', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+        });
+
+        describe('false checks', () => {
+            describe('variant #1: switch-case node', () => {
+                const expectedResult: boolean = false;
+                const node: ESTree.Node = Nodes.getSwitchCaseNode(
+                    Nodes.getLiteralNode(1),
+                    []
+                );
+                const parentNode: ESTree.FunctionDeclaration = Nodes.getFunctionDeclarationNode(
+                    'foo',
+                    [],
+                    Nodes.getBlockStatementNode([
+                        Nodes.getSwitchStatementNode(
+                            Nodes.getMemberExpressionNode(
+                                Nodes.getIdentifierNode('bar'),
+                                Nodes.getUpdateExpressionNode(
+                                    '++',
+                                    Nodes.getIdentifierNode('baz')
+                                ),
+                                true
+                            ),
+                            [node]
+                        )
+                    ])
+                );
+
+                let result: boolean;
+
+                before(() => {
+                    NodeUtils.parentize(parentNode);
+                    result = NodeGuards.isNodeHasBlockScope(node);
+                });
+
+                it('should check if node has block scope', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+
+            describe('variant #2: literal node', () => {
+                const expectedResult: boolean = false;
+                const node: ESTree.Node = Nodes.getLiteralNode(1);
+                const parentNode: ESTree.FunctionDeclaration = Nodes.getFunctionDeclarationNode(
+                    'foo',
+                    [],
+                    Nodes.getBlockStatementNode([
+                        Nodes.getExpressionStatementNode(
+                            Nodes.getCallExpressionNode(
+                                Nodes.getIdentifierNode('bar'),
+                                [node]
+                            )
+                        )
+                    ])
+                );
+
+                let result: boolean;
+
+                before(() => {
+                    NodeUtils.parentize(parentNode);
+                    result = NodeGuards.isNodeHasBlockScope(node);
+                });
+
+                it('should check if node has block scope', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+
+            describe('variant #3: block statement of if statement', () => {
+                const expectedResult: boolean = false;
+                const node: ESTree.Node = Nodes.getBlockStatementNode();
+                const parentNode: ESTree.IfStatement = Nodes.getIfStatementNode(
+                    Nodes.getIdentifierNode('foo'),
+                    node
+                );
+
+                let result: boolean;
+
+                before(() => {
+                    NodeUtils.parentize(parentNode);
+                    result = NodeGuards.isNodeHasBlockScope(node);
+                });
+
+                it('should check if node has block scope', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+        });
+
+        describe('exception checks', () => {
+            describe('no `parentNode` property', () => {
+                const node: ESTree.Node = Nodes.getBlockStatementNode();
+
+                let testFunc: Function;
+
+                before(() => {
+                    testFunc = () => NodeGuards.isNodeHasBlockScope(node);
+                });
+
+                it('should check if node has block scope', () => {
+                    assert.throw(testFunc, Error);
+                });
+            });
+        });
+    });
+
+    describe('isNodeHasScope (node: ESTree.Node): node is TNodeWithScope', () => {
+        describe('truthful checks', () => {
+            describe('variant #1: program node', () => {
+                const expectedResult: boolean = true;
+                const node: ESTree.Node = Nodes.getProgramNode();
+
+                let result: boolean;
+
+                before(() => {
+                    result = NodeGuards.isNodeHasScope(node);
+                });
+
+                it('should check if node has scope', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+
+            describe('variant #2: block statement node', () => {
+                const expectedResult: boolean = true;
+                const node: ESTree.Node = Nodes.getBlockStatementNode();
+
+                let result: boolean;
+
+                before(() => {
+                    result = NodeGuards.isNodeHasScope(node);
+                });
+
+                it('should check if node has scope', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+
+            describe('variant #3: switch case node', () => {
+                const expectedResult: boolean = true;
+                const node: ESTree.Node = Nodes.getSwitchCaseNode(
+                    Nodes.getLiteralNode(1),
+                    []
+                );
+
+                let result: boolean;
+
+                before(() => {
+                    result = NodeGuards.isNodeHasScope(node);
+                });
+
+                it('should check if node has scope', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+        });
+
+        describe('false checks', () => {
+            describe('variant #1: literal node', () => {
+                const expectedResult: boolean = false;
+                const node: ESTree.Node = Nodes.getLiteralNode(1);
+
+                let result: boolean;
+
+                before(() => {
+                    result = NodeGuards.isNodeHasScope(node);
+                });
+
+                it('should check if node has scope', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+
+            describe('variant #2: identifier node', () => {
+                const expectedResult: boolean = false;
+                const node: ESTree.Node = Nodes.getIdentifierNode('foo');
+
+                let result: boolean;
+
+                before(() => {
+                    result = NodeGuards.isNodeHasScope(node);
+                });
+
+                it('should check if node has scope', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+
+            describe('variant #3: if-statement node', () => {
+                const expectedResult: boolean = false;
+                const node: ESTree.Node = Nodes.getIfStatementNode(
+                    Nodes.getIdentifierNode('foo'),
+                    Nodes.getBlockStatementNode()
+                );
+
+                let result: boolean;
+
+                before(() => {
+                    result = NodeGuards.isNodeHasScope(node);
+                });
+
+                it('should check if node has scope', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+
+            describe('variant #4: switch-statement node', () => {
+                const expectedResult: boolean = false;
+                const node: ESTree.Node = Nodes.getSwitchStatementNode(
+                    Nodes.getIdentifierNode('foo'),
+                    []
+                );
+
+                let result: boolean;
+
+                before(() => {
+                    result = NodeGuards.isNodeHasScope(node);
+                });
+
+                it('should check if node has scope', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+        });
+    });
+
+    describe('isReplaceableIdentifierNode (node: ESTree.Node, parentNode: ESTree.Node): node is ESTree.Identifier', () => {
+        describe('truthful checks', () => {
+            describe('variant #1: parent node is function declaration node', () => {
+                const expectedResult: boolean = true;
+                const identifier: ESTree.Identifier = Nodes.getIdentifierNode('foo');
+                const parentNode: ESTree.Node = Nodes.getFunctionDeclarationNode(
+                    'bar',
+                    [identifier],
+                    Nodes.getBlockStatementNode()
+                );
+
+                let result: boolean;
+
+                before(() => {
+                    NodeUtils.parentize(parentNode);
+                    result = NodeGuards.isReplaceableIdentifierNode(identifier, parentNode);
+                });
+
+                it('should check if input identifier can be replaced by obfuscated one', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+
+            describe('variant #2: parent node is computed property node', () => {
+                const expectedResult: boolean = true;
+                const identifier: ESTree.Identifier = Nodes.getIdentifierNode('foo');
+                const parentNode: ESTree.Node = Nodes.getPropertyNode(
+                    identifier,
+                    Nodes.getLiteralNode('bar'),
+                    true
+                );
+
+                let result: boolean;
+
+                before(() => {
+                    NodeUtils.parentize(parentNode);
+                    result = NodeGuards.isReplaceableIdentifierNode(identifier, parentNode);
+                });
+
+                it('should check if input identifier can be replaced by obfuscated one', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+
+            describe('variant #4: parent node is computed member expression node', () => {
+                const expectedResult: boolean = true;
+                const identifier: ESTree.Identifier = Nodes.getIdentifierNode('foo');
+                const parentNode: ESTree.Node = Nodes.getMemberExpressionNode(
+                    Nodes.getIdentifierNode('bar'),
+                    identifier,
+                    true
+                );
+
+                let result: boolean;
+
+                before(() => {
+                    NodeUtils.parentize(parentNode);
+                    result = NodeGuards.isReplaceableIdentifierNode(identifier, parentNode);
+                });
+
+                it('should check if input identifier can be replaced by obfuscated one', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+
+            describe('variant #4: parent node is computed method definition node', () => {
+                const expectedResult: boolean = true;
+                const identifier: ESTree.Identifier = Nodes.getIdentifierNode('foo');
+                const parentNode: ESTree.Node = Nodes.getMethodDefinitionNode(
+                    identifier,
+                    Nodes.getFunctionExpressionNode([], Nodes.getBlockStatementNode()),
+                    'method',
+                    true
+                );
+
+                let result: boolean;
+
+                before(() => {
+                    NodeUtils.parentize(parentNode);
+                    result = NodeGuards.isReplaceableIdentifierNode(identifier, parentNode);
+                });
+
+                it('should check if input identifier can be replaced by obfuscated one', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+        });
+
+        describe('false checks', () => {
+            describe('variant #1: node isn\'t an identifier', () => {
+                const expectedResult: boolean = false;
+                const literal: ESTree.Literal = Nodes.getLiteralNode(1);
+                const parentNode: ESTree.Node = Nodes.getExpressionStatementNode(
+                    Nodes.getCallExpressionNode(
+                        Nodes.getIdentifierNode('foo'),
+                        [literal]
+                    )
+                );
+
+                let result: boolean;
+
+                before(() => {
+                    NodeUtils.parentize(parentNode);
+                    result = NodeGuards.isReplaceableIdentifierNode(literal, parentNode);
+                });
+
+                it('should check if input identifier can be replaced by obfuscated one', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+
+            describe('variant #2: parent node isn\'t computed property node', () => {
+                const expectedResult: boolean = false;
+                const identifier: ESTree.Identifier = Nodes.getIdentifierNode('foo');
+                const parentNode: ESTree.Node = Nodes.getPropertyNode(
+                    identifier,
+                    Nodes.getLiteralNode('bar'),
+                    false
+                );
+
+                let result: boolean;
+
+                before(() => {
+                    NodeUtils.parentize(parentNode);
+                    result = NodeGuards.isReplaceableIdentifierNode(identifier, parentNode);
+                });
+
+                it('should check if input identifier can be replaced by obfuscated one', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+
+            describe('variant #3: parent node isn\'t computed member expression node', () => {
+                const expectedResult: boolean = false;
+                const identifier: ESTree.Identifier = Nodes.getIdentifierNode('foo');
+                const parentNode: ESTree.Node = Nodes.getMemberExpressionNode(
+                    Nodes.getIdentifierNode('bar'),
+                    identifier,
+                    false
+                );
+
+                let result: boolean;
+
+                before(() => {
+                    NodeUtils.parentize(parentNode);
+                    result = NodeGuards.isReplaceableIdentifierNode(identifier, parentNode);
+                });
+
+                it('should check if input identifier can be replaced by obfuscated one', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+
+            describe('variant #4: parent node isn\'t computed method definition node', () => {
+                const expectedResult: boolean = false;
+                const identifier: ESTree.Identifier = Nodes.getIdentifierNode('foo');
+                const parentNode: ESTree.Node = Nodes.getMethodDefinitionNode(
+                    identifier,
+                    Nodes.getFunctionExpressionNode([], Nodes.getBlockStatementNode()),
+                    'method',
+                    false
+                );
+
+                let result: boolean;
+
+                before(() => {
+                    NodeUtils.parentize(parentNode);
+                    result = NodeGuards.isReplaceableIdentifierNode(identifier, parentNode);
+                });
+
+                it('should check if input identifier can be replaced by obfuscated one', () => {
+                    assert.equal(result, expectedResult);
+                });
+            });
+        });
+    });
+});

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