StackTraceAnalyzer.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. import * as estraverse from 'estraverse';
  2. import * as ESTree from 'estree';
  3. import { TNodeWithBlockStatement } from './types/TNodeWithBlockStatement';
  4. import { IStackTraceData } from './interfaces/IStackTraceData';
  5. import { IStackTraceAnalyzer } from './interfaces/IAnalyzer';
  6. import { Nodes } from './Nodes';
  7. import { NodeUtils } from './NodeUtils';
  8. /**
  9. * This class generates a data with code stack trace functions calls
  10. *
  11. * For example:
  12. *
  13. * function Foo () {
  14. * var baz = function () {
  15. *
  16. * }
  17. *
  18. * baz();
  19. * }
  20. *
  21. * foo();
  22. *
  23. * Will generate a structure like:
  24. *
  25. * [
  26. * {
  27. * callee: FOO_FUNCTION_NODE
  28. * name: 'Foo',
  29. * trace: [
  30. * {
  31. * callee: BAZ_FUNCTION_NODE,
  32. * name: 'baz,
  33. * trace: []
  34. * }
  35. * ]
  36. * }
  37. * ]
  38. */
  39. export class StackTraceAnalyzer implements IStackTraceAnalyzer {
  40. /**
  41. * @type {ESTree.Node[]}
  42. */
  43. private blockScopeBody: ESTree.Node[];
  44. /**
  45. * @type {IStackTraceData[]}
  46. */
  47. private stackTraceData: IStackTraceData[] = [];
  48. /**
  49. * @param blockScopeBody
  50. */
  51. constructor (blockScopeBody: ESTree.Node[]) {
  52. this.blockScopeBody = blockScopeBody;
  53. }
  54. /**
  55. * @returns {T}
  56. */
  57. public analyze (): IStackTraceData[] {
  58. if (this.blockScopeBody.length === 1) {
  59. estraverse.traverse(this.blockScopeBody[0], {
  60. enter: (node: ESTree.Node): any => {
  61. if (Nodes.isBlockStatementNode(node)) {
  62. this.analyzeRecursive(node.body, this.stackTraceData);
  63. return estraverse.VisitorOption.Skip;
  64. }
  65. }
  66. });
  67. } else {
  68. this.analyzeRecursive(this.blockScopeBody, this.stackTraceData);
  69. }
  70. return this.stackTraceData;
  71. }
  72. /**
  73. * @param blockScopeBody
  74. * @param stackTraceData
  75. */
  76. private analyzeRecursive (blockScopeBody: ESTree.Node[], stackTraceData: IStackTraceData[]): void {
  77. for (let rootNode of blockScopeBody) {
  78. estraverse.traverse(rootNode, {
  79. enter: (node: ESTree.Node): any => {
  80. if (
  81. Nodes.isCallExpressionNode(node) &&
  82. Nodes.isIdentifierNode(node.callee) &&
  83. rootNode.parentNode === NodeUtils.getBlockScopeOfNode(node)
  84. ) {
  85. const calleeNode: TNodeWithBlockStatement|null = this.getCalleeBlockStatement(
  86. NodeUtils.getBlockScopeOfNode(blockScopeBody[0]),
  87. node.callee.name
  88. );
  89. if (!calleeNode) {
  90. return estraverse.VisitorOption.Break;
  91. }
  92. const data: IStackTraceData = {
  93. callee: calleeNode,
  94. name: node.callee.name,
  95. stackTrace: []
  96. };
  97. stackTraceData.push(data);
  98. this.analyzeRecursive(calleeNode.body, data.stackTrace);
  99. }
  100. }
  101. });
  102. }
  103. }
  104. private getCalleeBlockStatement (node: ESTree.Node, name: string): TNodeWithBlockStatement|null {
  105. let calleeBlockStatement: TNodeWithBlockStatement|null = null;
  106. estraverse.traverse(node, {
  107. enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
  108. if (Nodes.isFunctionDeclarationNode(node) && node.id.name === name) {
  109. calleeBlockStatement = node.body;
  110. return estraverse.VisitorOption.Break;
  111. }
  112. if (
  113. Nodes.isFunctionExpressionNode(node) &&
  114. Nodes.isVariableDeclaratorNode(parentNode) &&
  115. Nodes.isIdentifierNode(parentNode.id) &&
  116. parentNode.id.name === name
  117. ) {
  118. calleeBlockStatement = node.body;
  119. return estraverse.VisitorOption.Break;
  120. }
  121. }
  122. });
  123. return calleeBlockStatement;
  124. }
  125. }