ObjectExpressionCalleeDataExtractor.ts 5.8 KB

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