NodeUtils.ts 6.7 KB

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