NodeAppender.ts 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. import * as ESTree from 'estree';
  2. import { TNodeWithBlockScope } from '../types/node/TNodeWithBlockScope';
  3. import { TStatement } from '../types/node/TStatement';
  4. import { IStackTraceData } from '../interfaces/analyzers/stack-trace-analyzer/IStackTraceData';
  5. import { TNodeWithScope } from '../types/node/TNodeWithScope';
  6. import { NodeGuards } from './NodeGuards';
  7. export class NodeAppender {
  8. /**
  9. * @param {TNodeWithScope} scopeNode
  10. * @param {TStatement[]} scopeStatements
  11. */
  12. public static appendNode (scopeNode: TNodeWithScope, scopeStatements: TStatement[]): void {
  13. if (!NodeAppender.validateScopeStatements(scopeStatements)) {
  14. scopeStatements = [];
  15. }
  16. scopeStatements = NodeAppender.parentizeScopeStatementsBeforeAppend(scopeNode, scopeStatements);
  17. NodeAppender.setScopeNodeStatements(scopeNode, [
  18. ...NodeAppender.getScopeNodeStatements(scopeNode),
  19. ...scopeStatements
  20. ]);
  21. }
  22. /**
  23. * Appends node into a first deepest BlockStatement in order of function calls
  24. *
  25. * For example:
  26. *
  27. * function Foo () {
  28. * var baz = function () {
  29. *
  30. * }
  31. *
  32. * baz();
  33. * }
  34. *
  35. * foo();
  36. *
  37. * Appends node into block statement of `baz` function expression
  38. *
  39. * @param {IStackTraceData[]} blockScopeStackTraceData
  40. * @param {TNodeWithBlockScope} blockScopeNode
  41. * @param {TStatement[]} nodeBodyStatements
  42. * @param {number} index
  43. */
  44. public static appendNodeToOptimalBlockScope (
  45. blockScopeStackTraceData: IStackTraceData[],
  46. blockScopeNode: TNodeWithBlockScope,
  47. nodeBodyStatements: TStatement[],
  48. index: number = 0
  49. ): void {
  50. let targetBlockScope: TNodeWithBlockScope;
  51. if (!blockScopeStackTraceData.length) {
  52. targetBlockScope = blockScopeNode;
  53. } else {
  54. targetBlockScope = NodeAppender.getOptimalBlockScope(
  55. blockScopeStackTraceData,
  56. index
  57. );
  58. }
  59. NodeAppender.prependNode(targetBlockScope, nodeBodyStatements);
  60. }
  61. /**
  62. * Returns deepest block scope node at given deep.
  63. *
  64. * @param {IStackTraceData[]} blockScopeTraceData
  65. * @param {number} index
  66. * @param {number} deep
  67. * @returns {BlockStatement}
  68. */
  69. public static getOptimalBlockScope (
  70. blockScopeTraceData: IStackTraceData[],
  71. index: number,
  72. deep: number = Infinity
  73. ): ESTree.BlockStatement {
  74. const firstCall: IStackTraceData = blockScopeTraceData[index];
  75. if (deep <= 0) {
  76. throw new Error(`Invalid \`deep\` argument value. Value should be bigger then 0.`);
  77. }
  78. if (deep > 1 && firstCall.stackTrace.length) {
  79. return NodeAppender.getOptimalBlockScope(firstCall.stackTrace, 0, --deep);
  80. } else {
  81. return firstCall.callee;
  82. }
  83. }
  84. /**
  85. * @param {TNodeWithScope} scopeNode
  86. * @param {TStatement[]} scopeStatements
  87. * @param {Node} targetStatement
  88. */
  89. public static insertNodeAfter (scopeNode: TNodeWithScope, scopeStatements: TStatement[], targetStatement: ESTree.Statement): void {
  90. const indexInScopeStatement: number = NodeAppender
  91. .getScopeNodeStatements(scopeNode)
  92. .indexOf(targetStatement);
  93. NodeAppender.insertNodeAtIndex(scopeNode, scopeStatements, indexInScopeStatement + 1);
  94. }
  95. /**
  96. * @param {TNodeWithScope} scopeNode
  97. * @param {TStatement[]} scopeStatements
  98. * @param {number} index
  99. */
  100. public static insertNodeAtIndex (scopeNode: TNodeWithScope, scopeStatements: TStatement[], index: number): void {
  101. if (!NodeAppender.validateScopeStatements(scopeStatements)) {
  102. scopeStatements = [];
  103. }
  104. scopeStatements = NodeAppender.parentizeScopeStatementsBeforeAppend(scopeNode, scopeStatements);
  105. NodeAppender.setScopeNodeStatements(scopeNode, [
  106. ...NodeAppender.getScopeNodeStatements(scopeNode).slice(0, index),
  107. ...scopeStatements,
  108. ...NodeAppender.getScopeNodeStatements(scopeNode).slice(index)
  109. ]);
  110. }
  111. /**
  112. * @param {TNodeWithScope} scopeNode
  113. * @param {TStatement[]} scopeStatements
  114. */
  115. public static prependNode (scopeNode: TNodeWithScope, scopeStatements: TStatement[]): void {
  116. if (!NodeAppender.validateScopeStatements(scopeStatements)) {
  117. scopeStatements = [];
  118. }
  119. scopeStatements = NodeAppender.parentizeScopeStatementsBeforeAppend(scopeNode, scopeStatements);
  120. NodeAppender.setScopeNodeStatements(scopeNode, [
  121. ...scopeStatements,
  122. ...NodeAppender.getScopeNodeStatements(scopeNode),
  123. ]);
  124. }
  125. /**
  126. * @param {TNodeWithScope} scopeNode
  127. * @returns {TStatement[]}
  128. */
  129. private static getScopeNodeStatements (scopeNode: TNodeWithScope): TStatement[] {
  130. if (NodeGuards.isSwitchCaseNode(scopeNode)) {
  131. return scopeNode.consequent;
  132. }
  133. return scopeNode.body;
  134. }
  135. /**
  136. * @param {TNodeWithScope} scopeNode
  137. * @param {TStatement[]} scopeStatements
  138. * @returns {TStatement[]}
  139. */
  140. private static parentizeScopeStatementsBeforeAppend (scopeNode: TNodeWithScope, scopeStatements: TStatement[]): TStatement[] {
  141. scopeStatements.forEach((statement: TStatement) => {
  142. statement.parentNode = scopeNode;
  143. });
  144. return scopeStatements;
  145. }
  146. /**
  147. * @param {TNodeWithScope} scopeNode
  148. * @param {TStatement[]} statements
  149. */
  150. private static setScopeNodeStatements (scopeNode: TNodeWithScope, statements: TStatement[]): void {
  151. if (NodeGuards.isSwitchCaseNode(scopeNode)) {
  152. scopeNode.consequent = <ESTree.Statement[]>statements;
  153. return;
  154. }
  155. scopeNode.body = statements;
  156. }
  157. /**
  158. * @param {TStatement[]} scopeStatement
  159. * @returns {boolean}
  160. */
  161. private static validateScopeStatements (scopeStatement: TStatement[]): boolean {
  162. return scopeStatement.every((statementNode: TStatement) => {
  163. return !!statementNode && statementNode.hasOwnProperty('type');
  164. });
  165. }
  166. }