NodeUtils.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. import * as escodegen from 'escodegen';
  2. import * as esprima from 'esprima';
  3. import * as estraverse from 'estraverse';
  4. import * as ESTree from 'estree';
  5. import { TNodeWithBlockStatement } from '../types/TNodeWithBlockStatement';
  6. import { TStatement } from '../types/TStatement';
  7. import { NodeType } from '../enums/NodeType';
  8. import { Node } from './Node';
  9. import { Utils } from '../Utils';
  10. export class NodeUtils {
  11. /**
  12. * @type {string[]}
  13. */
  14. private static readonly nodesWithBlockScope: string[] = [
  15. NodeType.ArrowFunctionExpression,
  16. NodeType.FunctionDeclaration,
  17. NodeType.FunctionExpression,
  18. NodeType.MethodDefinition,
  19. NodeType.Program
  20. ];
  21. /**
  22. * @param node
  23. */
  24. public static addXVerbatimPropertyToLiterals (node: ESTree.Node): void {
  25. NodeUtils.typedReplace(node, NodeType.Literal, {
  26. leave: (node: ESTree.Literal) => {
  27. node['x-verbatim-property'] = {
  28. content : node.raw,
  29. precedence: escodegen.Precedence.Primary
  30. };
  31. }
  32. });
  33. }
  34. /**
  35. * @param code
  36. * @returns {TStatement[]}
  37. */
  38. public static convertCodeToStructure (code: string): TStatement[] {
  39. const structure: ESTree.Program = esprima.parse(code);
  40. NodeUtils.addXVerbatimPropertyToLiterals(structure);
  41. NodeUtils.parentize(structure);
  42. return <TStatement[]>structure.body;
  43. }
  44. /**
  45. * @param node
  46. * @param index
  47. * @returns {ESTree.Node}
  48. */
  49. public static getBlockStatementNodeByIndex (node: ESTree.Node, index: number = 0): ESTree.Node {
  50. if (Node.isNodeHasBlockStatement(node)) {
  51. if (node.body[index] === undefined) {
  52. throw new ReferenceError(`Wrong index \`${index}\`. Block-statement body length is \`${node.body.length}\``);
  53. }
  54. return node.body[index];
  55. }
  56. throw new TypeError('The specified node have no a block-statement');
  57. }
  58. /**
  59. * @param node
  60. * @param depth
  61. * @returns {ESTree.Node}
  62. */
  63. public static getBlockScopeOfNode (node: ESTree.Node, depth: number = 0): TNodeWithBlockStatement {
  64. const parentNode: ESTree.Node | undefined = node.parentNode;
  65. if (!parentNode) {
  66. throw new ReferenceError('`parentNode` property of given node is `undefined`');
  67. }
  68. if (Node.isBlockStatementNode(parentNode)) {
  69. if (!parentNode.parentNode) {
  70. throw new ReferenceError('`parentNode` property of `parentNode` of given node is `undefined`');
  71. }
  72. if (!Utils.arrayContains(NodeUtils.nodesWithBlockScope, parentNode.parentNode.type)) {
  73. return NodeUtils.getBlockScopeOfNode(parentNode, depth);
  74. } else if (depth > 0) {
  75. return NodeUtils.getBlockScopeOfNode(parentNode, --depth);
  76. }
  77. return parentNode;
  78. }
  79. if (Node.isProgramNode(parentNode)) {
  80. return parentNode;
  81. }
  82. return NodeUtils.getBlockScopeOfNode(parentNode);
  83. }
  84. /**
  85. * @param node
  86. * @param depth
  87. * @returns {number}
  88. */
  89. public static getNodeBlockScopeDepth (node: ESTree.Node, depth: number = 0): number {
  90. const parentNode: ESTree.Node | undefined = node.parentNode;
  91. if (!parentNode) {
  92. throw new ReferenceError('`parentNode` property of given node is `undefined`');
  93. }
  94. if (Node.isProgramNode(parentNode)) {
  95. return depth;
  96. }
  97. if (Node.isBlockStatementNode(node)) {
  98. return NodeUtils.getNodeBlockScopeDepth(parentNode, ++depth);
  99. }
  100. return NodeUtils.getNodeBlockScopeDepth(parentNode, depth);
  101. }
  102. /**
  103. * @param node
  104. */
  105. public static parentize (node: ESTree.Node): void {
  106. let isRootNode: boolean = true;
  107. estraverse.replace(node, {
  108. enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
  109. let value: ESTree.Node;
  110. if (isRootNode) {
  111. if (node.type === NodeType.Program) {
  112. value = node;
  113. } else {
  114. value = Node.getProgramNode(<TStatement[]>[node]);
  115. value.parentNode = value;
  116. }
  117. isRootNode = false;
  118. } else {
  119. value = parentNode || node;
  120. }
  121. node.parentNode = value;
  122. node.obfuscated = false;
  123. }
  124. });
  125. }
  126. /**
  127. * @param node
  128. * @param nodeType
  129. * @param visitor
  130. */
  131. public static typedReplace (
  132. node: ESTree.Node,
  133. nodeType: string,
  134. visitor: {enter?: (node: ESTree.Node) => void, leave?: (node: ESTree.Node) => void},
  135. ): void {
  136. NodeUtils.typedTraverse(node, nodeType, visitor, 'replace');
  137. }
  138. /**
  139. * @param node
  140. * @param nodeType
  141. * @param visitor
  142. * @param traverseType
  143. */
  144. public static typedTraverse (
  145. node: ESTree.Node,
  146. nodeType: string,
  147. visitor: estraverse.Visitor,
  148. traverseType: string = 'traverse'
  149. ): void {
  150. (<any>estraverse)[traverseType](node, {
  151. enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
  152. if (node.type === nodeType && visitor.enter) {
  153. visitor.enter(node, parentNode);
  154. }
  155. },
  156. leave: (node: ESTree.Node, parentNode: ESTree.Node): any => {
  157. if (node.type === nodeType && visitor.leave) {
  158. visitor.leave(node, parentNode);
  159. }
  160. }
  161. });
  162. }
  163. }