StackTraceAnalyzer.ts 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import { injectable, inject } from 'inversify';
  2. import { ServiceIdentifiers } from '../container/ServiceIdentifiers';
  3. import * as estraverse from 'estraverse';
  4. import * as ESTree from 'estree';
  5. import { TCalleeDataExtractorsFactory } from '../types/container/TCalleeDataExtractorsFactory';
  6. import { ICalleeData } from '../interfaces/stack-trace-analyzer/ICalleeData';
  7. import { ICalleeDataExtractor } from '../interfaces/stack-trace-analyzer/ICalleeDataExtractor';
  8. import { IStackTraceAnalyzer } from '../interfaces/stack-trace-analyzer/IStackTraceAnalyzer';
  9. import { IStackTraceData } from '../interfaces/stack-trace-analyzer/IStackTraceData';
  10. import { CalleeDataExtractors } from '../enums/container/CalleeDataExtractors';
  11. import { Node } from '../node/Node';
  12. import { NodeUtils } from '../node/NodeUtils';
  13. /**
  14. * This class generates a data with code stack trace functions calls
  15. *
  16. * For example:
  17. *
  18. * function Foo () {
  19. * var baz = function () {
  20. *
  21. * }
  22. *
  23. * baz();
  24. * }
  25. *
  26. * foo();
  27. *
  28. * Will generate a structure like:
  29. *
  30. * [
  31. * {
  32. * callee: FOO_FUNCTION_NODE
  33. * name: 'Foo',
  34. * trace: [
  35. * {
  36. * callee: BAZ_FUNCTION_NODE,
  37. * name: 'baz,
  38. * trace: []
  39. * }
  40. * ]
  41. * }
  42. * ]
  43. */
  44. @injectable()
  45. export class StackTraceAnalyzer implements IStackTraceAnalyzer {
  46. /**
  47. * @type {CalleeDataExtractors[]}
  48. */
  49. private static readonly calleeDataExtractorsList: CalleeDataExtractors[] = [
  50. CalleeDataExtractors.FunctionDeclarationCalleeDataExtractor,
  51. CalleeDataExtractors.FunctionExpressionCalleeDataExtractor,
  52. CalleeDataExtractors.ObjectExpressionCalleeDataExtractor
  53. ];
  54. /**
  55. * @type {number}
  56. */
  57. private static readonly limitThresholdActivationLength: number = 25;
  58. /**
  59. * @type {number}
  60. */
  61. private static readonly limitThreshold: number = 0.002;
  62. /**
  63. * @type {(calleeDataExtractorName: CalleeDataExtractors) => ICalleeDataExtractor}
  64. */
  65. private calleeDataExtractorsFactory: (calleeDataExtractorName: CalleeDataExtractors) => ICalleeDataExtractor;
  66. constructor (
  67. @inject(ServiceIdentifiers['Factory<ICalleeDataExtractor>']) calleeDataExtractorsFactory: TCalleeDataExtractorsFactory
  68. ) {
  69. this.calleeDataExtractorsFactory = calleeDataExtractorsFactory;
  70. }
  71. /**
  72. * @param blockScopeBodyLength
  73. * @returns {number}
  74. */
  75. public static getLimitIndex (blockScopeBodyLength: number): number {
  76. const lastIndex: number = blockScopeBodyLength - 1;
  77. const limitThresholdActivationIndex: number = StackTraceAnalyzer.limitThresholdActivationLength - 1;
  78. let limitIndex: number = lastIndex;
  79. if (lastIndex > limitThresholdActivationIndex) {
  80. limitIndex = Math.round(
  81. limitThresholdActivationIndex + (lastIndex * StackTraceAnalyzer.limitThreshold)
  82. );
  83. if (limitIndex > lastIndex) {
  84. limitIndex = lastIndex;
  85. }
  86. }
  87. return limitIndex;
  88. }
  89. /**
  90. * @param blockScopeBody
  91. * @returns {IStackTraceData[]}
  92. */
  93. public analyze (blockScopeBody: ESTree.Node[]): IStackTraceData[] {
  94. return this.analyzeRecursive(blockScopeBody);
  95. }
  96. /**
  97. * @param blockScopeBody
  98. * @returns {IStackTraceData[]}
  99. */
  100. private analyzeRecursive (blockScopeBody: ESTree.Node[]): IStackTraceData[] {
  101. const limitIndex: number = StackTraceAnalyzer.getLimitIndex(blockScopeBody.length);
  102. const stackTraceData: IStackTraceData[] = [];
  103. const blockScopeBodyLength: number = blockScopeBody.length;
  104. for (let index: number = 0; index < blockScopeBodyLength; index++) {
  105. if (index > limitIndex) {
  106. break;
  107. }
  108. const blockScopeBodyNode: ESTree.Node = blockScopeBody[index];
  109. estraverse.traverse(blockScopeBodyNode, {
  110. enter: (node: ESTree.Node): void => {
  111. if (
  112. !Node.isCallExpressionNode(node) ||
  113. blockScopeBodyNode.parentNode !== NodeUtils.getBlockScopesOfNode(node)[0]
  114. ) {
  115. return;
  116. }
  117. this.analyzeCallExpressionNode(stackTraceData, blockScopeBody, node);
  118. }
  119. });
  120. }
  121. return stackTraceData;
  122. }
  123. /**
  124. * @param stackTraceData
  125. * @param blockScopeBody
  126. * @param callExpressionNode
  127. * @returns {IStackTraceData[]}
  128. */
  129. private analyzeCallExpressionNode (
  130. stackTraceData: IStackTraceData[],
  131. blockScopeBody: ESTree.Node[],
  132. callExpressionNode: ESTree.CallExpression
  133. ): void {
  134. StackTraceAnalyzer.calleeDataExtractorsList.forEach((calleeDataExtractorName: CalleeDataExtractors) => {
  135. const calleeData: ICalleeData | null = this.calleeDataExtractorsFactory(calleeDataExtractorName)
  136. .extract(blockScopeBody, callExpressionNode.callee);
  137. if (!calleeData) {
  138. return;
  139. }
  140. stackTraceData.push({
  141. ...calleeData,
  142. stackTrace: this.analyzeRecursive(calleeData.callee.body)
  143. });
  144. });
  145. }
  146. }