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