ObjectExpressionCalleeDataExtractor.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. import * as estraverse from 'estraverse';
  2. import * as ESTree from 'estree';
  3. import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
  4. import { ICalleeData } from '../../interfaces/stack-trace-analyzer/ICalleeData';
  5. import { ICalleeDataExtractor } from '../../interfaces/stack-trace-analyzer/ICalleeDataExtractor';
  6. import { Nodes } from '../../Nodes';
  7. import { NodeUtils } from '../../NodeUtils';
  8. export class ObjectExpressionCalleeDataExtractor implements ICalleeDataExtractor {
  9. /**
  10. * @type {ESTree.Node[]}
  11. */
  12. private blockScopeBody: ESTree.Node[];
  13. /**
  14. * @type {ESTree.MemberExpression}
  15. */
  16. private callee: ESTree.MemberExpression;
  17. /**
  18. * @type {Array}
  19. */
  20. private objectMembersCallsChain: string[] = [];
  21. /**
  22. * @param blockScopeBody
  23. * @param callee
  24. */
  25. constructor (blockScopeBody: ESTree.Node[], callee: ESTree.MemberExpression) {
  26. this.blockScopeBody = blockScopeBody;
  27. this.callee = callee;
  28. }
  29. /**
  30. * @returns {ICalleeData|null}
  31. */
  32. public extract (): ICalleeData|null {
  33. let calleeBlockStatement: TNodeWithBlockStatement|null = null,
  34. functionExpressionName: string|null = null;
  35. if (Nodes.isMemberExpressionNode(this.callee)) {
  36. this.objectMembersCallsChain = this.createObjectMembersCallsChain(this.objectMembersCallsChain, this.callee);
  37. if (!this.objectMembersCallsChain.length) {
  38. return null;
  39. }
  40. functionExpressionName = this.objectMembersCallsChain[this.objectMembersCallsChain.length - 1];
  41. calleeBlockStatement = this.getCalleeBlockStatement(
  42. NodeUtils.getBlockScopeOfNode(this.blockScopeBody[0]),
  43. this.objectMembersCallsChain
  44. );
  45. }
  46. if (!calleeBlockStatement) {
  47. return null;
  48. }
  49. return {
  50. callee: calleeBlockStatement,
  51. name: functionExpressionName
  52. };
  53. }
  54. /**
  55. * Creates array with MemberExpression calls chain.
  56. *
  57. * Example: object.foo.bar(); // ['object', 'foo', 'bar']
  58. *
  59. * @param currentChain
  60. * @param memberExpression
  61. * @returns {string[]}
  62. */
  63. private createObjectMembersCallsChain (currentChain: string[], memberExpression: ESTree.MemberExpression): string[] {
  64. if (Nodes.isIdentifierNode(memberExpression.property)) {
  65. currentChain.unshift(memberExpression.property.name);
  66. } else if (Nodes.isLiteralNode(memberExpression.property) && typeof memberExpression.property.value === 'string') {
  67. currentChain.unshift(memberExpression.property.value);
  68. } else {
  69. return currentChain;
  70. }
  71. if (Nodes.isMemberExpressionNode(memberExpression.object)) {
  72. return this.createObjectMembersCallsChain(currentChain, memberExpression.object);
  73. }
  74. if (Nodes.isIdentifierNode(memberExpression.object)) {
  75. currentChain.unshift(memberExpression.object.name);
  76. }
  77. return currentChain;
  78. }
  79. /**
  80. * @param node
  81. * @param objectMembersCallsChain
  82. * @returns {TNodeWithBlockStatement|null}
  83. */
  84. private getCalleeBlockStatement (node: ESTree.Node, objectMembersCallsChain: string[]): TNodeWithBlockStatement|null {
  85. const objectName: string = <string>objectMembersCallsChain.shift();
  86. let calleeBlockStatement: TNodeWithBlockStatement|null = null;
  87. estraverse.traverse(node, {
  88. enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
  89. if (
  90. Nodes.isVariableDeclaratorNode(node) &&
  91. Nodes.isIdentifierNode(node.id) &&
  92. node.init &&
  93. Nodes.isObjectExpressionNode(node.init) &&
  94. node.id.name === objectName
  95. ) {
  96. calleeBlockStatement = this.findCalleeBlockStatement(node.init.properties, objectMembersCallsChain);
  97. return estraverse.VisitorOption.Break;
  98. }
  99. }
  100. });
  101. return calleeBlockStatement;
  102. }
  103. private findCalleeBlockStatement (
  104. objectExpressionProperties: ESTree.Property[],
  105. objectMembersCallsChain: string[]
  106. ): TNodeWithBlockStatement|null {
  107. const nextItemInCallsChain: string|undefined = objectMembersCallsChain.shift();
  108. if (!nextItemInCallsChain) {
  109. return null;
  110. }
  111. for (let propertyNode of objectExpressionProperties) {
  112. const isTargetPropertyNodeWithIdentifierKey: boolean =
  113. Nodes.isIdentifierNode(propertyNode.key) && propertyNode.key.name === nextItemInCallsChain;
  114. const isTargetPropertyNodeWithLiteralKey: boolean =
  115. Nodes.isLiteralNode(propertyNode.key) &&
  116. Boolean(propertyNode.key.value) &&
  117. propertyNode.key.value === nextItemInCallsChain;
  118. if (!isTargetPropertyNodeWithIdentifierKey || isTargetPropertyNodeWithLiteralKey) {
  119. continue;
  120. }
  121. if (Nodes.isObjectExpressionNode(propertyNode.value)) {
  122. return this.findCalleeBlockStatement(propertyNode.value.properties, objectMembersCallsChain);
  123. }
  124. if (Nodes.isFunctionExpressionNode(propertyNode.value)) {
  125. return propertyNode.value.body;
  126. }
  127. }
  128. return null;
  129. }
  130. }