import * as ESTree from 'estree';
import { TNodeWithBlockStatement } from '../types/node/TNodeWithBlockStatement';
import { TStatement } from '../types/node/TStatement';
import { IStackTraceData } from '../interfaces/stack-trace-analyzer/IStackTraceData';
import { RandomGeneratorUtils } from '../utils/RandomGeneratorUtils';
/**
* This class appends node into a first deepest BlockStatement in order of function calls
*
* For example:
*
* function Foo () {
* var baz = function () {
*
* }
*
* baz();
* }
*
* foo();
*
* Appends node into block statement of `baz` function expression.
*/
export class NodeAppender {
/**
* @param blockScopeNode
* @param nodeBodyStatements
*/
public static appendNode (
blockScopeNode: TNodeWithBlockStatement,
nodeBodyStatements: TStatement[]
): void {
Iif (!NodeAppender.validateBodyStatements(nodeBodyStatements)) {
nodeBodyStatements = [];
}
nodeBodyStatements = NodeAppender.parentizeBodyStatementsBeforeAppend(blockScopeNode, nodeBodyStatements);
blockScopeNode.body = [
...blockScopeNode.body,
...nodeBodyStatements
];
}
/**
* @param blockScopeStackTraceData
* @param blockScopeNode
* @param nodeBodyStatements
* @param index
*/
public static appendNodeToOptimalBlockScope (
blockScopeStackTraceData: IStackTraceData[],
blockScopeNode: TNodeWithBlockStatement,
nodeBodyStatements: TStatement[],
index: number = 0
): void {
let targetBlockScope: TNodeWithBlockStatement;
if (!blockScopeStackTraceData.length) {
targetBlockScope = blockScopeNode;
} else {
targetBlockScope = NodeAppender.getOptimalBlockScope(
blockScopeStackTraceData,
index
);
}
NodeAppender.prependNode(targetBlockScope, nodeBodyStatements);
}
/**
* Returns deepest block scope node at given deep.
*
* @param blockScopeTraceData
* @param index
* @param deep
* @returns {ESTree.BlockStatement}
*/
public static getOptimalBlockScope (
blockScopeTraceData: IStackTraceData[],
index: number,
deep: number = Infinity
): ESTree.BlockStatement {
const firstCall: IStackTraceData = blockScopeTraceData[index];
Iif (deep <= 0) {
throw new Error(`Invalid \`deep\` argument value. Value should be bigger then 0.`);
}
if (deep > 1 && firstCall.stackTrace.length) {
return NodeAppender.getOptimalBlockScope(firstCall.stackTrace, 0, --deep);
} else {
return firstCall.callee;
}
}
/**
* @param stackTraceRootLength
*/
public static getRandomStackTraceIndex (stackTraceRootLength: number): number {
return RandomGeneratorUtils.getRandomInteger(0, Math.max(0, Math.round(stackTraceRootLength - 1)));
}
/**
* @param blockScopeNode
* @param nodeBodyStatements
* @param index
*/
public static insertNodeAtIndex (
blockScopeNode: TNodeWithBlockStatement,
nodeBodyStatements: TStatement[],
index: number
): void {
Iif (!NodeAppender.validateBodyStatements(nodeBodyStatements)) {
nodeBodyStatements = [];
}
nodeBodyStatements = NodeAppender.parentizeBodyStatementsBeforeAppend(blockScopeNode, nodeBodyStatements);
blockScopeNode.body = [
...blockScopeNode.body.slice(0, index),
...nodeBodyStatements,
...blockScopeNode.body.slice(index)
];
}
/**
* @param blockScopeNode
* @param nodeBodyStatements
*/
public static prependNode (
blockScopeNode: TNodeWithBlockStatement,
nodeBodyStatements: TStatement[]
): void {
Iif (!NodeAppender.validateBodyStatements(nodeBodyStatements)) {
nodeBodyStatements = [];
}
nodeBodyStatements = NodeAppender.parentizeBodyStatementsBeforeAppend(blockScopeNode, nodeBodyStatements);
blockScopeNode.body = [
...nodeBodyStatements,
...blockScopeNode.body,
];
}
/**
* @param blockScopeNode
* @param nodeBodyStatements
*/
private static parentizeBodyStatementsBeforeAppend (
blockScopeNode: TNodeWithBlockStatement,
nodeBodyStatements: TStatement[]
): TStatement[] {
for (const statement of nodeBodyStatements) {
statement.parentNode = blockScopeNode;
}
return nodeBodyStatements;
}
/**
* @param nodeBodyStatements
* @returns {boolean}
*/
private static validateBodyStatements (nodeBodyStatements: TStatement[]): boolean {
return nodeBodyStatements.every((statementNode: TStatement) => {
return !!statementNode && statementNode.hasOwnProperty('type');
});
}
}
|