ObjectExpressionKeysTransformer.ts 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. import { inject, injectable } from 'inversify';
  2. import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
  3. import * as ESTree from 'estree';
  4. import { TNodeWithScope } from '../../types/node/TNodeWithScope';
  5. import { IOptions } from '../../interfaces/options/IOptions';
  6. import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
  7. import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
  8. import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
  9. import { NodeAppender } from '../../node/NodeAppender';
  10. import { NodeGuards } from '../../node/NodeGuards';
  11. import { Nodes } from '../../node/Nodes';
  12. import { NodeUtils } from '../../node/NodeUtils';
  13. @injectable()
  14. export class ObjectExpressionKeysTransformer extends AbstractNodeTransformer {
  15. /**
  16. * @type {Map<VariableDeclarator, TNodeWithScope>}
  17. */
  18. private cachedScopeNodesMap: Map <ESTree.VariableDeclarator, TNodeWithScope> = new Map();
  19. /**
  20. * @param {IRandomGenerator} randomGenerator
  21. * @param {IOptions} options
  22. */
  23. constructor (
  24. @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
  25. @inject(ServiceIdentifiers.IOptions) options: IOptions
  26. ) {
  27. super(randomGenerator, options);
  28. }
  29. /**
  30. * @param {TNodeWithScope} scopeNode
  31. * @param {ExpressionStatement[]} expressionStatements
  32. * @param {Node} variableDeclarator
  33. */
  34. private static appendExpressionStatements (
  35. scopeNode: TNodeWithScope,
  36. expressionStatements: ESTree.ExpressionStatement[],
  37. variableDeclarator: ESTree.Node
  38. ): void {
  39. const variableDeclaration: ESTree.Node | undefined = variableDeclarator.parentNode;
  40. if (!variableDeclaration || !NodeGuards.isVariableDeclarationNode(variableDeclaration)) {
  41. throw new Error('Cannot find variable declaration for variable declarator');
  42. }
  43. NodeAppender.insertNodeAfter(scopeNode, expressionStatements, variableDeclaration);
  44. }
  45. /**
  46. * @param {Property[]} properties
  47. * @param {number[]} removablePropertyIds
  48. * @returns {Property[]}
  49. */
  50. private static filterObjectExpressionProperties (properties: ESTree.Property[], removablePropertyIds: number[]): ESTree.Property[] {
  51. return properties.filter((property: ESTree.Property, index: number) => !removablePropertyIds.includes(index));
  52. }
  53. /**
  54. * @return {IVisitor}
  55. */
  56. public getVisitor (): IVisitor {
  57. return {
  58. enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
  59. if (
  60. this.options.transformObjectKeys
  61. && parentNode
  62. && NodeGuards.isObjectExpressionNode(node)
  63. && NodeGuards.isVariableDeclaratorNode(parentNode)
  64. ) {
  65. return this.transformNode(node, parentNode);
  66. }
  67. }
  68. };
  69. }
  70. /**
  71. * replaces:
  72. * var object = {
  73. * foo: 1,
  74. * bar: 2
  75. * };
  76. *
  77. * on:
  78. * var object = {};
  79. * object['foo'] = 1;
  80. * object['bar'] = 2;
  81. *
  82. * @param {MemberExpression} objectExpressionNode
  83. * @param {NodeGuards} variableDeclarator
  84. * @returns {NodeGuards}
  85. */
  86. public transformNode (objectExpressionNode: ESTree.ObjectExpression, variableDeclarator: ESTree.VariableDeclarator): ESTree.Node {
  87. // should pass only Expression nodes as MemberExpression.object value
  88. if (!NodeGuards.isIdentifierNode(variableDeclarator.id)) {
  89. return objectExpressionNode;
  90. }
  91. const scopeNode: TNodeWithScope | null = NodeUtils.getScopeOfNode(variableDeclarator);
  92. if (!scopeNode || !NodeGuards.isNodeHasScope(scopeNode)) {
  93. return objectExpressionNode;
  94. }
  95. this.cachedScopeNodesMap.set(variableDeclarator, scopeNode);
  96. return this.transformObjectExpressionNode(
  97. objectExpressionNode,
  98. variableDeclarator.id,
  99. variableDeclarator
  100. );
  101. }
  102. /**
  103. * @param {Property[]} properties
  104. * @param {Expression} memberExpressionObject
  105. * @param {VariableDeclarator} variableDeclarator
  106. * @returns {[ExpressionStatement[] , number[]]}
  107. */
  108. private extractPropertiesToExpressionStatements (
  109. properties: ESTree.Property[],
  110. memberExpressionObject: ESTree.Expression,
  111. variableDeclarator: ESTree.VariableDeclarator
  112. ): [ESTree.ExpressionStatement[], number[]] {
  113. const propertiesLength: number = properties.length;
  114. const expressionStatements: ESTree.ExpressionStatement[] = [];
  115. const removablePropertyIds: number[] = [];
  116. for (let i: number = 0; i < propertiesLength; i++) {
  117. const property: ESTree.Property = properties[i];
  118. const propertyKey: ESTree.Expression = property.key;
  119. // invalid property nodes
  120. if (
  121. NodeGuards.isObjectPatternNode(property.value)
  122. || NodeGuards.isArrayPatternNode(property.value)
  123. || NodeGuards.isAssignmentPatternNode(property.value)
  124. || NodeGuards.isRestElementNode(property.value)
  125. ) {
  126. continue;
  127. }
  128. /**
  129. * Stage 1: collecting property node key names
  130. */
  131. let propertyKeyName: string;
  132. if (NodeGuards.isLiteralNode(propertyKey) && typeof propertyKey.value === 'string') {
  133. propertyKeyName = propertyKey.value;
  134. } else if (NodeGuards.isIdentifierNode(propertyKey)) {
  135. propertyKeyName = propertyKey.name;
  136. } else {
  137. continue;
  138. }
  139. /**
  140. * Stage 2: creating new expression statement node with member expression based on removed property
  141. */
  142. const shouldCreateLiteralNode: boolean = !property.computed
  143. || (property.computed && NodeGuards.isLiteralNode(property.key));
  144. const memberExpressionProperty: ESTree.Expression = shouldCreateLiteralNode
  145. ? Nodes.getLiteralNode(propertyKeyName)
  146. : Nodes.getIdentifierNode(propertyKeyName);
  147. const memberExpressionNode: ESTree.MemberExpression = Nodes
  148. .getMemberExpressionNode(memberExpressionObject, memberExpressionProperty, true);
  149. const rightExpression: ESTree.Expression = property.value;
  150. const expressionStatementNode: ESTree.ExpressionStatement = Nodes.getExpressionStatementNode(
  151. Nodes.getAssignmentExpressionNode('=', memberExpressionNode, rightExpression)
  152. );
  153. /**
  154. * Stage 3: recursively processing nested object expressions
  155. */
  156. if (NodeGuards.isObjectExpressionNode(property.value)) {
  157. this.transformObjectExpressionNode(
  158. property.value,
  159. memberExpressionNode,
  160. variableDeclarator
  161. );
  162. }
  163. /**
  164. * Stage 4: filling arrays
  165. */
  166. expressionStatements.push(expressionStatementNode);
  167. removablePropertyIds.push(i);
  168. }
  169. return [expressionStatements, removablePropertyIds];
  170. }
  171. /**
  172. * @param {ObjectExpression} objectExpressionNode
  173. * @param {Expression} memberExpressionObjectNode
  174. * @param {VariableDeclarator} variableDeclarator
  175. * @returns {Node}
  176. */
  177. private transformObjectExpressionNode (
  178. objectExpressionNode: ESTree.ObjectExpression,
  179. memberExpressionObjectNode: ESTree.Expression,
  180. variableDeclarator: ESTree.VariableDeclarator
  181. ): ESTree.Node {
  182. const properties: ESTree.Property[] = objectExpressionNode.properties;
  183. if (!properties.length) {
  184. return objectExpressionNode;
  185. }
  186. const scopeNode: TNodeWithScope | undefined = this.cachedScopeNodesMap.get(variableDeclarator);
  187. if (!scopeNode) {
  188. return objectExpressionNode;
  189. }
  190. const [expressionStatements, removablePropertyIds]: [ESTree.ExpressionStatement[], number[]] = this
  191. .extractPropertiesToExpressionStatements(properties, memberExpressionObjectNode, variableDeclarator);
  192. objectExpressionNode.properties = ObjectExpressionKeysTransformer
  193. .filterObjectExpressionProperties(properties, removablePropertyIds);
  194. ObjectExpressionKeysTransformer
  195. .appendExpressionStatements(scopeNode, expressionStatements, variableDeclarator);
  196. return objectExpressionNode;
  197. }
  198. }