NodeUtils.ts 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  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/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 {T} astTree
  23. * @returns {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 {T} astTree
  38. * @returns {T}
  39. */
  40. public static clone <T extends ESTree.Node> (astTree: T): T {
  41. /**
  42. * @param {T} node
  43. * @returns {T}
  44. */
  45. const cloneRecursive: (node: T) => T = (node: T) => {
  46. if (node === null) {
  47. return node;
  48. }
  49. const copy: {[key: string]: any} = {};
  50. Object
  51. .keys(node)
  52. .filter((property: string) => property !== 'parentNode')
  53. .forEach((property: string): void => {
  54. const value: any = (<{[key: string]: any}>node)[property];
  55. let clonedValue: any | null;
  56. if (value === null || value instanceof RegExp) {
  57. clonedValue = value;
  58. } else if (Array.isArray(value)) {
  59. clonedValue = value.map(cloneRecursive);
  60. } else if (typeof value === 'object') {
  61. clonedValue = cloneRecursive(value);
  62. } else {
  63. clonedValue = value;
  64. }
  65. copy[property] = clonedValue;
  66. });
  67. return <T>copy;
  68. };
  69. return NodeUtils.parentize(cloneRecursive(astTree));
  70. }
  71. /**
  72. * @param {string} code
  73. * @returns {TStatement[]}
  74. */
  75. public static convertCodeToStructure (code: string): TStatement[] {
  76. let structure: ESTree.Program = esprima.parse(code);
  77. structure = NodeUtils.addXVerbatimPropertyToLiterals(structure);
  78. structure = NodeUtils.parentize(structure);
  79. return structure.body;
  80. }
  81. /**
  82. * @param {Node[]} structure
  83. * @returns {string}
  84. */
  85. public static convertStructureToCode (structure: ESTree.Node[]): string {
  86. let code: string = '';
  87. structure.forEach((node: ESTree.Node) => {
  88. code += escodegen.generate(node, {
  89. sourceMapWithCode: true
  90. }).code;
  91. });
  92. return code;
  93. }
  94. /**
  95. * @param {Node} node
  96. * @param {number} index
  97. * @returns {Node}
  98. */
  99. public static getBlockStatementNodeByIndex (node: ESTree.Node, index: number = 0): ESTree.Node {
  100. if (Node.isNodeHasBlockStatement(node)) {
  101. if (node.body[index] === undefined) {
  102. throw new ReferenceError(`Wrong index \`${index}\`. Block-statement body length is \`${node.body.length}\``);
  103. }
  104. return node.body[index];
  105. }
  106. throw new TypeError('The specified node have no a block-statement');
  107. }
  108. /**
  109. * @param {Node} node
  110. * @param {TNodeWithBlockStatement[]} blockScopes
  111. * @returns {TNodeWithBlockStatement[]}
  112. */
  113. public static getBlockScopesOfNode (node: ESTree.Node, blockScopes: TNodeWithBlockStatement[] = []): TNodeWithBlockStatement[] {
  114. const parentNode: ESTree.Node | undefined = node.parentNode;
  115. if (!parentNode) {
  116. throw new ReferenceError('`parentNode` property of given node is `undefined`');
  117. }
  118. if (Node.isBlockStatementNode(parentNode)) {
  119. if (!parentNode.parentNode) {
  120. throw new ReferenceError('`parentNode` property of `parentNode` of given node is `undefined`');
  121. }
  122. if (NodeUtils.nodesWithBlockScope.includes(parentNode.parentNode.type)) {
  123. blockScopes.push(parentNode);
  124. }
  125. }
  126. if (!Node.isProgramNode(parentNode)) {
  127. return NodeUtils.getBlockScopesOfNode(parentNode, blockScopes);
  128. }
  129. blockScopes.push(parentNode);
  130. return blockScopes;
  131. }
  132. /**
  133. * @param {Node} node
  134. * @param {number} depth
  135. * @returns {number}
  136. */
  137. public static getNodeBlockScopeDepth (node: ESTree.Node, depth: number = 0): number {
  138. const parentNode: ESTree.Node | undefined = node.parentNode;
  139. if (!parentNode) {
  140. throw new ReferenceError('`parentNode` property of given node is `undefined`');
  141. }
  142. if (Node.isProgramNode(parentNode)) {
  143. return depth;
  144. }
  145. if (Node.isBlockStatementNode(node) && NodeUtils.nodesWithBlockScope.includes(parentNode.type)) {
  146. return NodeUtils.getNodeBlockScopeDepth(parentNode, ++depth);
  147. }
  148. return NodeUtils.getNodeBlockScopeDepth(parentNode, depth);
  149. }
  150. /**
  151. * @param {UnaryExpression} unaryExpressionNode
  152. * @returns {Node}
  153. */
  154. public static getUnaryExpressionArgumentNode (unaryExpressionNode: ESTree.UnaryExpression): ESTree.Node {
  155. if (Node.isUnaryExpressionNode(unaryExpressionNode.argument)) {
  156. return NodeUtils.getUnaryExpressionArgumentNode(unaryExpressionNode.argument);
  157. }
  158. return unaryExpressionNode.argument;
  159. }
  160. /**
  161. * @param {T} astTree
  162. * @returns {T}
  163. */
  164. public static parentize <T extends ESTree.Node> (astTree: T): T {
  165. let isRootNode: boolean = true;
  166. estraverse.traverse(astTree, {
  167. enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
  168. let value: ESTree.Node;
  169. if (isRootNode) {
  170. if (node.type === NodeType.Program) {
  171. value = node;
  172. } else {
  173. value = Nodes.getProgramNode(<TStatement[]>[node]);
  174. value.parentNode = value;
  175. }
  176. isRootNode = false;
  177. } else {
  178. value = parentNode || node;
  179. }
  180. node.parentNode = value;
  181. node.obfuscatedNode = false;
  182. }
  183. });
  184. return astTree;
  185. }
  186. /**
  187. * @param {Node} astTree
  188. * @param {string} nodeType
  189. * @param {visitor} visitor
  190. */
  191. public static typedReplace (
  192. astTree: ESTree.Node,
  193. nodeType: string,
  194. visitor: {enter?: (node: ESTree.Node) => void, leave?: (node: ESTree.Node) => void},
  195. ): void {
  196. NodeUtils.typedTraverse(astTree, nodeType, visitor, 'replace');
  197. }
  198. /**
  199. * @param {Node} astTree
  200. * @param {string} nodeType
  201. * @param {Visitor} visitor
  202. * @param {string} traverseType
  203. */
  204. public static typedTraverse (
  205. astTree: ESTree.Node,
  206. nodeType: string,
  207. visitor: estraverse.Visitor,
  208. traverseType: string = 'traverse'
  209. ): void {
  210. (<any>estraverse)[traverseType](astTree, {
  211. enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
  212. if (node.type === nodeType && visitor.enter) {
  213. return visitor.enter(node, parentNode);
  214. }
  215. },
  216. leave: (node: ESTree.Node, parentNode: ESTree.Node): any => {
  217. if (node.type === nodeType && visitor.leave) {
  218. return visitor.leave(node, parentNode);
  219. }
  220. }
  221. });
  222. }
  223. }