NodeUtils.ts 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. import * as escodegen from 'escodegen-wallaby';
  2. import * as espree from 'espree';
  3. import * as estraverse from 'estraverse';
  4. import * as ESTree from 'estree';
  5. import { TNodeWithBlockScope } from '../types/node/TNodeWithBlockScope';
  6. import { TNodeWithScope } from '../types/node/TNodeWithScope';
  7. import { TObject } from '../types/TObject';
  8. import { TStatement } from '../types/node/TStatement';
  9. import { NodeGuards } from './NodeGuards';
  10. import { NodeMetadata } from './NodeMetadata';
  11. export class NodeUtils {
  12. /**
  13. * @param {T} literalNode
  14. * @returns {T}
  15. */
  16. public static addXVerbatimPropertyTo (literalNode: ESTree.Literal): ESTree.Literal {
  17. literalNode['x-verbatim-property'] = {
  18. content: literalNode.raw,
  19. precedence: escodegen.Precedence.Primary
  20. };
  21. return literalNode;
  22. }
  23. /**
  24. * @param {T} astTree
  25. * @returns {T}
  26. */
  27. public static clone <T extends ESTree.Node = ESTree.Node> (astTree: T): T {
  28. return NodeUtils.parentizeAst(NodeUtils.cloneRecursive(astTree));
  29. }
  30. /**
  31. * @param {string} code
  32. * @returns {Statement[]}
  33. */
  34. public static convertCodeToStructure (code: string): ESTree.Statement[] {
  35. const structure: ESTree.Program = espree.parse(code, { sourceType: 'script' });
  36. estraverse.replace(structure, {
  37. enter: (node: ESTree.Node, parentNode: ESTree.Node | null): ESTree.Node => {
  38. NodeUtils.parentizeNode(node, parentNode);
  39. if (NodeGuards.isLiteralNode(node)) {
  40. NodeUtils.addXVerbatimPropertyTo(node);
  41. }
  42. NodeMetadata.set(node, { ignoredNode: false });
  43. return node;
  44. }
  45. });
  46. return <ESTree.Statement[]>structure.body;
  47. }
  48. /**
  49. * @param {NodeGuards[]} structure
  50. * @returns {string}
  51. */
  52. public static convertStructureToCode (structure: ESTree.Node[]): string {
  53. return structure.reduce((code: string, node: ESTree.Node) => {
  54. return code + escodegen.generate(node, {
  55. sourceMapWithCode: true
  56. }).code;
  57. }, '');
  58. }
  59. /**
  60. * @param {Node} node
  61. * @returns {TNodeWithBlockScope[]}
  62. */
  63. public static getBlockScopesOfNode (node: ESTree.Node): TNodeWithBlockScope[] {
  64. return NodeUtils.getBlockScopesOfNodeRecursive(node);
  65. }
  66. /**
  67. * @param {Statement} statement
  68. * @returns {TStatement | null}
  69. */
  70. public static getNextSiblingStatement (statement: ESTree.Statement): TStatement | null {
  71. return NodeUtils.getSiblingStatementByOffset(statement, 1);
  72. }
  73. /**
  74. * @param {Statement} statement
  75. * @returns {TStatement | null}
  76. */
  77. public static getPreviousSiblingStatement (statement: ESTree.Statement): TStatement | null {
  78. return NodeUtils.getSiblingStatementByOffset(statement, -1);
  79. }
  80. /**
  81. * @param {Node} node
  82. * @returns {Statement}
  83. */
  84. public static getRootStatementOfNode (node: ESTree.Node): ESTree.Statement {
  85. if (NodeGuards.isProgramNode(node)) {
  86. throw new Error('Unable to find root statement for `Program` node');
  87. }
  88. const parentNode: ESTree.Node | undefined = node.parentNode;
  89. if (!parentNode) {
  90. throw new ReferenceError('`parentNode` property of given node is `undefined`');
  91. }
  92. if (!NodeGuards.isNodeHasScope(parentNode)) {
  93. return NodeUtils.getRootStatementOfNode(parentNode);
  94. }
  95. return <ESTree.Statement>node;
  96. }
  97. /**
  98. * @param {NodeGuards} node
  99. * @returns {TNodeWithScope}
  100. */
  101. public static getScopeOfNode (node: ESTree.Node): TNodeWithScope {
  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 (!NodeGuards.isNodeHasScope(parentNode)) {
  107. return NodeUtils.getScopeOfNode(parentNode);
  108. }
  109. return parentNode;
  110. }
  111. /**
  112. * @param {UnaryExpression} unaryExpressionNode
  113. * @returns {NodeGuards}
  114. */
  115. public static getUnaryExpressionArgumentNode (unaryExpressionNode: ESTree.UnaryExpression): ESTree.Node {
  116. if (NodeGuards.isUnaryExpressionNode(unaryExpressionNode.argument)) {
  117. return NodeUtils.getUnaryExpressionArgumentNode(unaryExpressionNode.argument);
  118. }
  119. return unaryExpressionNode.argument;
  120. }
  121. /**
  122. * @param {T} astTree
  123. * @returns {T}
  124. */
  125. public static parentizeAst <T extends ESTree.Node = ESTree.Node> (astTree: T): T {
  126. estraverse.replace(astTree, {
  127. enter: NodeUtils.parentizeNode
  128. });
  129. return astTree;
  130. }
  131. /**
  132. * @param {T} node
  133. * @param {Node} parentNode
  134. * @returns {T}
  135. */
  136. public static parentizeNode <T extends ESTree.Node = ESTree.Node> (node: T, parentNode: ESTree.Node | null): T {
  137. node.parentNode = parentNode || node;
  138. return node;
  139. }
  140. /**
  141. * @param {T} node
  142. * @returns {T}
  143. */
  144. private static cloneRecursive <T> (node: T): T {
  145. if (node === null) {
  146. return node;
  147. }
  148. const copy: TObject = {};
  149. Object
  150. .keys(node)
  151. .forEach((property: string) => {
  152. if (property === 'parentNode') {
  153. return;
  154. }
  155. const value: T[keyof T] = node[<keyof T>property];
  156. let clonedValue: T[keyof T] | T[keyof T][] | null;
  157. if (value === null || value instanceof RegExp) {
  158. clonedValue = value;
  159. } else if (Array.isArray(value)) {
  160. clonedValue = value.map(NodeUtils.cloneRecursive);
  161. } else if (typeof value === 'object') {
  162. clonedValue = NodeUtils.cloneRecursive(value);
  163. } else {
  164. clonedValue = value;
  165. }
  166. copy[property] = clonedValue;
  167. });
  168. return <T>copy;
  169. }
  170. /**
  171. * @param {Node} node
  172. * @param {TNodeWithBlockScope[]} blockScopes
  173. * @param {number} depth
  174. * @returns {TNodeWithBlockScope[]}
  175. */
  176. private static getBlockScopesOfNodeRecursive (
  177. node: ESTree.Node,
  178. blockScopes: TNodeWithBlockScope[] = [],
  179. depth: number = 0
  180. ): TNodeWithBlockScope[] {
  181. const parentNode: ESTree.Node | undefined = node.parentNode;
  182. if (!parentNode) {
  183. throw new ReferenceError('`parentNode` property of given node is `undefined`');
  184. }
  185. /**
  186. * Stage 1: process root block statement node of the slice of AST-tree
  187. */
  188. if (NodeGuards.isBlockStatementNode(node) && parentNode === node) {
  189. blockScopes.push(node);
  190. }
  191. /**
  192. * Stage 2: process any other nodes
  193. */
  194. if (
  195. /**
  196. * we can add program node instantly
  197. */
  198. NodeGuards.isProgramNode(node) ||
  199. /**
  200. * we shouldn't add to the array input node that is node with block scope itself
  201. * so, on depth 0 we will skip push to the array of block scopes
  202. */
  203. (depth && NodeGuards.isNodeHasBlockScope(node, parentNode))
  204. ) {
  205. blockScopes.push(node);
  206. }
  207. if (node !== parentNode) {
  208. return NodeUtils.getBlockScopesOfNodeRecursive(parentNode, blockScopes, ++depth);
  209. }
  210. return blockScopes;
  211. }
  212. /**
  213. * @param {Statement} statement
  214. * @param {number} offset
  215. * @returns {TStatement | null}
  216. */
  217. private static getSiblingStatementByOffset (statement: ESTree.Statement, offset: number): TStatement | null {
  218. const scopeNode: TNodeWithScope = NodeUtils.getScopeOfNode(statement);
  219. const scopeBody: TStatement[] = !NodeGuards.isSwitchCaseNode(scopeNode)
  220. ? scopeNode.body
  221. : scopeNode.consequent;
  222. const indexInScope: number = scopeBody.indexOf(statement);
  223. return scopeBody[indexInScope + offset] || null;
  224. }
  225. }