NodeUtils.ts 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  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/node/TNodeWithBlockStatement';
  6. import { TStatement } from '../types/node/TStatement';
  7. import { NodeType } from '../enums/NodeType';
  8. import { Node } from './Node';
  9. import { Nodes } from './Nodes';
  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 astTree
  23. * @return {T}
  24. */
  25. public static addXVerbatimPropertyToLiterals <T extends ESTree.Node> (astTree: T): T {
  26. NodeUtils.typedReplace(astTree, NodeType.Literal, {
  27. leave: (literalNode: ESTree.Literal) => {
  28. literalNode['x-verbatim-property'] = {
  29. content : literalNode.raw,
  30. precedence: escodegen.Precedence.Primary
  31. };
  32. }
  33. });
  34. return astTree;
  35. }
  36. /**
  37. * @param code
  38. * @returns {TStatement[]}
  39. */
  40. public static convertCodeToStructure (code: string): TStatement[] {
  41. let structure: ESTree.Program = esprima.parse(code);
  42. structure = NodeUtils.addXVerbatimPropertyToLiterals(structure);
  43. structure = NodeUtils.parentize(structure);
  44. return <TStatement[]>structure.body;
  45. }
  46. /**
  47. * @param structure
  48. * @returns {string}
  49. */
  50. public static convertStructureToCode (structure: ESTree.Node[]): string {
  51. let code: string = '';
  52. structure.forEach((node: ESTree.Node) => {
  53. code += escodegen.generate(node, {
  54. sourceMapWithCode: true
  55. }).code;
  56. });
  57. return code;
  58. }
  59. /**
  60. * @param node
  61. * @param index
  62. * @returns {ESTree.Node}
  63. */
  64. public static getBlockStatementNodeByIndex (node: ESTree.Node, index: number = 0): ESTree.Node {
  65. if (Node.isNodeHasBlockStatement(node)) {
  66. if (node.body[index] === undefined) {
  67. throw new ReferenceError(`Wrong index \`${index}\`. Block-statement body length is \`${node.body.length}\``);
  68. }
  69. return node.body[index];
  70. }
  71. throw new TypeError('The specified node have no a block-statement');
  72. }
  73. /**
  74. * @param node
  75. * @param blockScopes
  76. * @returns {ESTree.Node}
  77. */
  78. public static getBlockScopesOfNode (node: ESTree.Node, blockScopes: TNodeWithBlockStatement[] = []): TNodeWithBlockStatement[] {
  79. const parentNode: ESTree.Node | undefined = node.parentNode;
  80. if (!parentNode) {
  81. throw new ReferenceError('`parentNode` property of given node is `undefined`');
  82. }
  83. if (Node.isBlockStatementNode(parentNode)) {
  84. if (!parentNode.parentNode) {
  85. throw new ReferenceError('`parentNode` property of `parentNode` of given node is `undefined`');
  86. }
  87. if (NodeUtils.nodesWithBlockScope.includes(parentNode.parentNode.type)) {
  88. blockScopes.push(parentNode);
  89. }
  90. }
  91. if (!Node.isProgramNode(parentNode)) {
  92. return NodeUtils.getBlockScopesOfNode(parentNode, blockScopes);
  93. }
  94. blockScopes.push(parentNode);
  95. return blockScopes;
  96. }
  97. /**
  98. * @param node
  99. * @param depth
  100. * @returns {number}
  101. */
  102. public static getNodeBlockScopeDepth (node: ESTree.Node, depth: number = 0): number {
  103. const parentNode: ESTree.Node | undefined = node.parentNode;
  104. if (!parentNode) {
  105. throw new ReferenceError('`parentNode` property of given node is `undefined`');
  106. }
  107. if (Node.isProgramNode(parentNode)) {
  108. return depth;
  109. }
  110. if (Node.isBlockStatementNode(node) && NodeUtils.nodesWithBlockScope.includes(parentNode.type)) {
  111. return NodeUtils.getNodeBlockScopeDepth(parentNode, ++depth);
  112. }
  113. return NodeUtils.getNodeBlockScopeDepth(parentNode, depth);
  114. }
  115. /**
  116. * @param unaryExpressionNode
  117. * @returns {ESTree.Node}
  118. */
  119. public static getUnaryExpressionArgumentNode (unaryExpressionNode: ESTree.UnaryExpression): ESTree.Node {
  120. if (Node.isUnaryExpressionNode(unaryExpressionNode.argument)) {
  121. return NodeUtils.getUnaryExpressionArgumentNode(unaryExpressionNode.argument);
  122. }
  123. return unaryExpressionNode.argument;
  124. }
  125. /**
  126. * @param astTree
  127. * @return {T}
  128. */
  129. public static parentize <T extends ESTree.Node> (astTree: T): T {
  130. let isRootNode: boolean = true;
  131. estraverse.traverse(astTree, {
  132. enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
  133. let value: ESTree.Node;
  134. if (isRootNode) {
  135. if (node.type === NodeType.Program) {
  136. value = node;
  137. } else {
  138. value = Nodes.getProgramNode(<TStatement[]>[node]);
  139. value.parentNode = value;
  140. }
  141. isRootNode = false;
  142. } else {
  143. value = parentNode || node;
  144. }
  145. node.parentNode = value;
  146. node.obfuscatedNode = false;
  147. }
  148. });
  149. return astTree;
  150. }
  151. /**
  152. * @param astTree
  153. * @param nodeType
  154. * @param visitor
  155. */
  156. public static typedReplace (
  157. astTree: ESTree.Node,
  158. nodeType: string,
  159. visitor: {enter?: (node: ESTree.Node) => void, leave?: (node: ESTree.Node) => void},
  160. ): void {
  161. NodeUtils.typedTraverse(astTree, nodeType, visitor, 'replace');
  162. }
  163. /**
  164. * @param astTree
  165. * @param nodeType
  166. * @param visitor
  167. * @param traverseType
  168. */
  169. public static typedTraverse (
  170. astTree: ESTree.Node,
  171. nodeType: string,
  172. visitor: estraverse.Visitor,
  173. traverseType: string = 'traverse'
  174. ): void {
  175. (<any>estraverse)[traverseType](astTree, {
  176. enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
  177. if (node.type === nodeType && visitor.enter) {
  178. return visitor.enter(node, parentNode);
  179. }
  180. },
  181. leave: (node: ESTree.Node, parentNode: ESTree.Node): any => {
  182. if (node.type === nodeType && visitor.leave) {
  183. return visitor.leave(node, parentNode);
  184. }
  185. }
  186. });
  187. }
  188. }