All files / src/node-transformers/node-control-flow-transformers FunctionControlFlowTransformer.ts

41.67% Statements 15/36
0% Branches 0/12
0% Functions 0/2
40% Lines 14/35
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 1071x 1x   1x                   1x   1x 1x 1x 1x 1x 1x 1x   1x 1x       1x                                                                                                                                                            
import { injectable, inject } from 'inversify';
import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
import * as estraverse from 'estraverse';
import * as ESTree from 'estree';
 
import { TControlFlowReplacer } from '../../types/node-transformers/TControlFlowReplacer';
import { TStatement } from '../../types/node/TStatement';
 
import { ICustomNode } from '../../interfaces/custom-nodes/ICustomNode';
import { IOptions } from '../../interfaces/options/IOptions';
import { IStorage } from '../../interfaces/storages/IStorage';
 
import { NodeType } from '../../enums/NodeType';
 
import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
import { BinaryExpressionControlFlowReplacer } from './control-flow-replacers/BinaryExpressionControlFlowReplacer';
import { ControlFlowStorage } from '../../storages/control-flow/ControlFlowStorage';
import { ControlFlowStorageNode } from '../../custom-nodes/control-flow-storage-nodes/ControlFlowStorageNode';
import { Node } from '../../node/Node';
import { NodeAppender } from '../../node/NodeAppender';
import { Utils } from '../../Utils';
 
@injectable()
export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
    /**
     * @type {Map <string, IReplacer>}
     */
    private static readonly controlFlowReplacers: Map <string, TControlFlowReplacer> = new Map <string, TControlFlowReplacer> ([
        [NodeType.BinaryExpression, BinaryExpressionControlFlowReplacer]
    ]);
 
    /**
     * @param customNodesStorage
     * @param options
     */
    constructor (
        @inject(ServiceIdentifiers['IStorage<ICustomNode>']) customNodesStorage: IStorage<ICustomNode>,
        @inject(ServiceIdentifiers.IOptions) options: IOptions
    ) {
        super(customNodesStorage, options);
    }
 
    /**
     * @param functionNode
     */
    public transformNode (functionNode: ESTree.Function): void {
        this.changeFunctionBodyControlFlow(functionNode);
    }
 
    /**
     * @param functionNode
     */
    private changeFunctionBodyControlFlow (functionNode: ESTree.Function): void {
        if (Node.isArrowFunctionExpressionNode(functionNode)) {
            return;
        }
 
        const controlFlowStorage: IStorage <ICustomNode> = new ControlFlowStorage();
        const controlFlowStorageCustomNodeName: string = Utils.getRandomVariableName(6);
 
        estraverse.replace(functionNode.body, {
            enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
                const controlFlowReplacer: TControlFlowReplacer | undefined = FunctionControlFlowTransformer
                    .controlFlowReplacers.get(node.type);
 
                if (!controlFlowReplacer) {
                    return;
                }
 
                const controlFlowStorageCallCustomNode: ICustomNode | undefined = new controlFlowReplacer(
                    this.customNodesStorage,
                    this.options
                ).replace(node, parentNode, controlFlowStorage, controlFlowStorageCustomNodeName);
 
                if (!controlFlowStorageCallCustomNode) {
                    return;
                }
 
                // controlFlowStorageCallCustomNode will always have only one TStatement node,
                // so we can get it by index `0`
                // also we need to return `expression` property of `ExpressionStatement` node because bug:
                // https://github.com/estools/escodegen/issues/289
                const statementNode: TStatement | undefined = controlFlowStorageCallCustomNode.getNode()[0];
 
                if (!statementNode || !Node.isExpressionStatementNode(statementNode)) {
                    throw new Error(`\`controlFlowStorageCallCustomNode.getNode()\` should returns array with \`ExpressionStatement\` node`);
                }
 
                return statementNode.expression;
            }
        });
 
        if (!controlFlowStorage.getLength()) {
            return;
        }
 
        const controlFlowStorageCustomNode: ICustomNode = new ControlFlowStorageNode(
            controlFlowStorage,
            controlFlowStorageCustomNodeName,
            this.options
        );
 
        NodeAppender.prependNode(functionNode.body, controlFlowStorageCustomNode.getNode());
    }
}