NodeAppender.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  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} scope
  10. * @param {TStatement[]} statements
  11. */
  12. public static append (scope: TNodeWithScope, statements: TStatement[]): void {
  13. statements = NodeAppender.parentizeScopeStatementsBeforeAppend(scope, statements);
  14. NodeAppender.setScopeStatements(scope, [
  15. ...NodeAppender.getScopeStatements(scope),
  16. ...statements
  17. ]);
  18. }
  19. /**
  20. * Appends node into a first deepest BlockStatement in order of function calls
  21. *
  22. * For example:
  23. *
  24. * function Foo () {
  25. * var baz = function () {
  26. *
  27. * }
  28. *
  29. * baz();
  30. * }
  31. *
  32. * foo();
  33. *
  34. * Appends node into block statement of `baz` function expression
  35. *
  36. * @param {IStackTraceData[]} stackTraceData
  37. * @param {TNodeWithBlockScope} blockScopeNode
  38. * @param {TStatement[]} bodyStatements
  39. * @param {number} index
  40. */
  41. public static appendToOptimalBlockScope (
  42. stackTraceData: IStackTraceData[],
  43. blockScopeNode: TNodeWithBlockScope,
  44. bodyStatements: TStatement[],
  45. index: number = 0
  46. ): void {
  47. const targetBlockScope: TNodeWithBlockScope = stackTraceData.length
  48. ? NodeAppender.getOptimalBlockScope(stackTraceData, index)
  49. : blockScopeNode;
  50. NodeAppender.prepend(targetBlockScope, bodyStatements);
  51. }
  52. /**
  53. * Returns deepest block scope node at given deep.
  54. *
  55. * @param {IStackTraceData[]} stackTraceData
  56. * @param {number} index
  57. * @param {number} deep
  58. * @returns {BlockStatement}
  59. */
  60. public static getOptimalBlockScope (
  61. stackTraceData: IStackTraceData[],
  62. index: number,
  63. deep: number = Infinity
  64. ): ESTree.BlockStatement {
  65. const firstCall: IStackTraceData = stackTraceData[index];
  66. if (deep <= 0) {
  67. throw new Error('Invalid `deep` argument value. Value should be bigger then 0.');
  68. }
  69. if (deep > 1 && firstCall.stackTrace.length) {
  70. return NodeAppender.getOptimalBlockScope(firstCall.stackTrace, 0, --deep);
  71. } else {
  72. return firstCall.callee;
  73. }
  74. }
  75. /**
  76. * @param {TNodeWithScope} scope
  77. * @param {TStatement[]} statements
  78. * @param {Node} target
  79. */
  80. public static insertAfter (
  81. scope: TNodeWithScope,
  82. statements: TStatement[],
  83. target: ESTree.Statement
  84. ): void {
  85. const indexInScopeStatement: number = NodeAppender
  86. .getScopeStatements(scope)
  87. .indexOf(target);
  88. NodeAppender.insertAtIndex(scope, statements, indexInScopeStatement + 1);
  89. }
  90. /**
  91. * @param {TNodeWithScope} scope
  92. * @param {TStatement[]} statements
  93. * @param {number} index
  94. */
  95. public static insertAtIndex (
  96. scope: TNodeWithScope,
  97. statements: TStatement[],
  98. index: number
  99. ): void {
  100. statements = NodeAppender.parentizeScopeStatementsBeforeAppend(scope, statements);
  101. NodeAppender.setScopeStatements(scope, [
  102. ...NodeAppender.getScopeStatements(scope).slice(0, index),
  103. ...statements,
  104. ...NodeAppender.getScopeStatements(scope).slice(index)
  105. ]);
  106. }
  107. /**
  108. * @param {TNodeWithScope} scope
  109. * @param {TStatement[]} statements
  110. */
  111. public static prepend (scope: TNodeWithScope, statements: TStatement[]): void {
  112. statements = NodeAppender.parentizeScopeStatementsBeforeAppend(scope, statements);
  113. NodeAppender.setScopeStatements(scope, [
  114. ...statements,
  115. ...NodeAppender.getScopeStatements(scope),
  116. ]);
  117. }
  118. /**
  119. * @param {TNodeWithScope} scope
  120. * @returns {TStatement[]}
  121. */
  122. private static getScopeStatements (scope: TNodeWithScope): TStatement[] {
  123. if (NodeGuards.isSwitchCaseNode(scope)) {
  124. return scope.consequent;
  125. }
  126. return scope.body;
  127. }
  128. /**
  129. * @param {TNodeWithScope} scope
  130. * @param {TStatement[]} statements
  131. * @returns {TStatement[]}
  132. */
  133. private static parentizeScopeStatementsBeforeAppend (scope: TNodeWithScope, statements: TStatement[]): TStatement[] {
  134. statements.forEach((statement: TStatement) => {
  135. statement.parentNode = scope;
  136. });
  137. return statements;
  138. }
  139. /**
  140. * @param {TNodeWithScope} scope
  141. * @param {TStatement[]} statements
  142. */
  143. private static setScopeStatements (scope: TNodeWithScope, statements: TStatement[]): void {
  144. if (NodeGuards.isSwitchCaseNode(scope)) {
  145. scope.consequent = <ESTree.Statement[]>statements;
  146. return;
  147. }
  148. scope.body = statements;
  149. }
  150. }