All files / src StackTraceAnalyzer.ts

100% Statements 37/37
100% Branches 22/22
100% Functions 4/4
100% Lines 35/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 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 1491x               1x 1x                                                                 1x                 41x           41x             41x 20x   79x 7x   7x         21x     41x               248x 248x   3367x 3367x   3367x 142x 56x         56x       3367x 3311x     56x           56x   56x             56x   56x   1324x 44x   44x     1280x           12x   12x         56x      
import * as estraverse from 'estraverse';
import * as ESTree from 'estree';
 
import { TNodeWithBlockStatement } from './types/TNodeWithBlockStatement';
 
import { IStackTraceData } from './interfaces/IStackTraceData';
import { IStackTraceAnalyzer } from './interfaces/IAnalyzer';
 
import { Nodes } from './Nodes';
import { NodeUtils } from './NodeUtils';
 
/**
 * This class generates a data with code stack trace functions calls
 *
 * For example:
 *
 * function Foo () {
 *     var baz = function () {
 *
 *     }
 *
 *     baz();
 * }
 *
 * foo();
 *
 * Will generate a structure like:
 *
 * [
 *      {
 *          callee: FOO_FUNCTION_NODE
 *          name: 'Foo',
 *          trace: [
 *              {
 *                  callee: BAZ_FUNCTION_NODE,
 *                  name: 'baz,
 *                  trace: []
 *              }
 *          ]
 *      }
 * ]
 */
export class StackTraceAnalyzer implements IStackTraceAnalyzer {
    /**
     * @type {ESTree.Node[]}
     */
    private blockScopeBody: ESTree.Node[];
 
    /**
     * @type {IStackTraceData[]}
     */
    private stackTraceData: IStackTraceData[] = [];
 
    /**
     * @param blockScopeBody
     */
    constructor (blockScopeBody: ESTree.Node[]) {
        this.blockScopeBody = blockScopeBody;
    }
 
    /**
     * @returns {T}
     */
    public analyze (): IStackTraceData[] {
        if (this.blockScopeBody.length === 1) {
            estraverse.traverse(this.blockScopeBody[0], {
                enter: (node: ESTree.Node): any => {
                    if (Nodes.isBlockStatementNode(node)) {
                        this.analyzeRecursive(node.body, this.stackTraceData);
 
                        return estraverse.VisitorOption.Skip;
                    }
                }
            });
        } else {
            this.analyzeRecursive(this.blockScopeBody, this.stackTraceData);
        }
 
        return this.stackTraceData;
    }
 
    /**
     * @param blockScopeBody
     * @param stackTraceData
     */
    private analyzeRecursive (blockScopeBody: ESTree.Node[], stackTraceData: IStackTraceData[]): void {
        for (let rootNode of blockScopeBody) {
            estraverse.traverse(rootNode, {
                enter: (node: ESTree.Node): any => {
                    let calleeNode: TNodeWithBlockStatement|null = null,
                        name: string = '';
 
                    if (Nodes.isCallExpressionNode(node) && rootNode.parentNode === NodeUtils.getBlockScopeOfNode(node)) {
                        if (Nodes.isIdentifierNode(node.callee)) {
                            calleeNode = this.getCalleeBlockStatement(
                                NodeUtils.getBlockScopeOfNode(blockScopeBody[0]),
                                node.callee.name
                            );
 
                            name = node.callee.name;
                        }
                    }
 
                    if (!calleeNode) {
                        return;
                    }
 
                    const data: IStackTraceData = {
                        callee: calleeNode,
                        name: name,
                        stackTrace: []
                    };
 
                    stackTraceData.push(data);
 
                    this.analyzeRecursive(calleeNode.body, data.stackTrace);
                }
            });
        }
    }
 
    private getCalleeBlockStatement (node: ESTree.Node, name: string): TNodeWithBlockStatement|null {
        let calleeBlockStatement: TNodeWithBlockStatement|null = null;
 
        estraverse.traverse(node, {
            enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
                if (Nodes.isFunctionDeclarationNode(node) && node.id.name === name) {
                    calleeBlockStatement = node.body;
 
                    return estraverse.VisitorOption.Break;
                }
 
                if (
                    Nodes.isFunctionExpressionNode(node) &&
                    Nodes.isVariableDeclaratorNode(parentNode) &&
                    Nodes.isIdentifierNode(parentNode.id) &&
                    parentNode.id.name === name
                ) {
                    calleeBlockStatement = node.body;
 
                    return estraverse.VisitorOption.Break;
                }
            }
        });
 
        return calleeBlockStatement;
    }
}