NodeUtils.ts 6.0 KB

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