IfStatementSimplifyTransformer.ts 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. import { inject, injectable, } from 'inversify';
  2. import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
  3. import * as ESTree from 'estree';
  4. import { IIfStatementSimplifyData } from '../../interfaces/node-transformers/minification-transformers/IIfStatementSimplifyData';
  5. import { IOptions } from '../../interfaces/options/IOptions';
  6. import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
  7. import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
  8. import { NodeTransformationStage } from '../../enums/node-transformers/NodeTransformationStage';
  9. import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
  10. import { NodeGuards } from '../../node/NodeGuards';
  11. import { NodeFactory } from '../../node/NodeFactory';
  12. /**
  13. * Simplifies `IfStatement` node
  14. */
  15. @injectable()
  16. export class IfStatementSimplifyTransformer extends AbstractNodeTransformer {
  17. /**
  18. * @param {IRandomGenerator} randomGenerator
  19. * @param {IOptions} options
  20. */
  21. public constructor (
  22. @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
  23. @inject(ServiceIdentifiers.IOptions) options: IOptions
  24. ) {
  25. super(randomGenerator, options);
  26. }
  27. /**
  28. * @param {NodeTransformationStage} nodeTransformationStage
  29. * @returns {IVisitor | null}
  30. */
  31. public getVisitor (nodeTransformationStage: NodeTransformationStage): IVisitor | null {
  32. switch (nodeTransformationStage) {
  33. case NodeTransformationStage.Minification:
  34. return {
  35. leave: (
  36. node: ESTree.Node,
  37. parentNode: ESTree.Node | null
  38. ): ESTree.Node | undefined => {
  39. if (parentNode && NodeGuards.isIfStatementNode(node)) {
  40. return this.transformNode(node, parentNode);
  41. }
  42. }
  43. };
  44. default:
  45. return null;
  46. }
  47. }
  48. /**
  49. * @param {ESTree.IfStatement} ifStatementNode
  50. * @param {ESTree.Node} parentNode
  51. * @returns {ESTree.IfStatement}
  52. */
  53. public transformNode (
  54. ifStatementNode: ESTree.IfStatement,
  55. parentNode: ESTree.Node
  56. ): ESTree.Node {
  57. const consequentSimplifyData: IIfStatementSimplifyData | null = this.getIfStatementSimplifyData(ifStatementNode.consequent);
  58. // Variant #1: no valid consequent expression
  59. if (!consequentSimplifyData) {
  60. return ifStatementNode;
  61. }
  62. // Variant #2: valid consequent expression only
  63. if (!ifStatementNode.alternate) {
  64. return this.getConsequentNode(ifStatementNode, consequentSimplifyData);
  65. }
  66. const alternateSimplifyData: IIfStatementSimplifyData | null = this.getIfStatementSimplifyData(ifStatementNode.alternate);
  67. if (!alternateSimplifyData) {
  68. return ifStatementNode;
  69. }
  70. // Variant #3: valid consequent and alternate expressions
  71. return this.getConsequentAndAlternateNode(ifStatementNode, consequentSimplifyData, alternateSimplifyData);
  72. }
  73. /**
  74. * @param {ESTree.IfStatement} ifStatementNode
  75. * @param {IIfStatementSimplifyData} consequentSimplifyData
  76. * @returns {ESTree.Node}
  77. */
  78. private getConsequentNode (
  79. ifStatementNode: ESTree.IfStatement,
  80. consequentSimplifyData: IIfStatementSimplifyData
  81. ): ESTree.Node {
  82. /**
  83. * Converts:
  84. * if (true) {
  85. * const foo = 1;
  86. * console.log(1);
  87. * return 1;
  88. * }
  89. *
  90. * to:
  91. * if (true) {
  92. * const foo = 1;
  93. * return console.log(1), 1;
  94. * }
  95. */
  96. if (consequentSimplifyData.leadingStatements.length) {
  97. return NodeFactory.ifStatementNode(
  98. ifStatementNode.test,
  99. NodeFactory.blockStatementNode([
  100. ...consequentSimplifyData.leadingStatements,
  101. consequentSimplifyData.statement
  102. ])
  103. );
  104. }
  105. /**
  106. * Converts:
  107. * if (true) {
  108. * return 1;
  109. * }
  110. *
  111. * to:
  112. * if (true)
  113. * return 1;
  114. */
  115. if (consequentSimplifyData.hasReturnStatement) {
  116. return NodeFactory.ifStatementNode(
  117. ifStatementNode.test,
  118. consequentSimplifyData.statement
  119. );
  120. }
  121. /**
  122. * Converts:
  123. * if (true) {
  124. * console.log(1);
  125. * }
  126. *
  127. * to:
  128. * true && console.log(1);
  129. */
  130. return NodeFactory.expressionStatementNode(
  131. NodeFactory.logicalExpressionNode(
  132. '&&',
  133. ifStatementNode.test,
  134. consequentSimplifyData.expression,
  135. )
  136. );
  137. }
  138. /**
  139. * @param {ESTree.IfStatement} ifStatementNode
  140. * @param {IIfStatementSimplifyData} consequentSimplifyData
  141. * @param {IIfStatementSimplifyData} alternateSimplifyData
  142. * @returns {ESTree.Node}
  143. */
  144. private getConsequentAndAlternateNode (
  145. ifStatementNode: ESTree.IfStatement,
  146. consequentSimplifyData: IIfStatementSimplifyData,
  147. alternateSimplifyData: IIfStatementSimplifyData
  148. ): ESTree.Node {
  149. /**
  150. * Converts:
  151. * if (true) {
  152. * return 1;
  153. * } else {
  154. * return 2;
  155. * }
  156. *
  157. * to:
  158. * return true ? 1 : 2;
  159. */
  160. if (consequentSimplifyData.hasReturnStatement && alternateSimplifyData.hasReturnStatement) {
  161. return NodeFactory.returnStatementNode(
  162. NodeFactory.conditionalExpressionNode(
  163. ifStatementNode.test,
  164. consequentSimplifyData.expression,
  165. alternateSimplifyData.expression
  166. )
  167. );
  168. }
  169. /**
  170. * Converts:
  171. * if (true) {
  172. * return 1;
  173. * } else {
  174. * console.log(2);
  175. * }
  176. *
  177. * to:
  178. * if (true)
  179. * return 1;
  180. * else
  181. * console.log(2);
  182. */
  183. if (consequentSimplifyData.hasReturnStatement || alternateSimplifyData.hasReturnStatement) {
  184. return NodeFactory.ifStatementNode(
  185. ifStatementNode.test,
  186. consequentSimplifyData.statement,
  187. alternateSimplifyData.statement
  188. );
  189. }
  190. /**
  191. * Converts:
  192. * if (true) {
  193. * console.log(1);
  194. * } else {
  195. * console.log(2);
  196. * }
  197. *
  198. * to:
  199. * true ? console.log(1) : console.log(2);
  200. */
  201. return NodeFactory.expressionStatementNode(
  202. NodeFactory.conditionalExpressionNode(
  203. ifStatementNode.test,
  204. consequentSimplifyData.expression,
  205. alternateSimplifyData.expression
  206. )
  207. );
  208. }
  209. /**
  210. * @param {ESTree.Statement | null | undefined} statementNode
  211. * @returns {IIfStatementSimplifyData | null}
  212. */
  213. // eslint-disable-next-line complexity
  214. private getIfStatementSimplifyData (
  215. statementNode: ESTree.Statement | null | undefined
  216. ): IIfStatementSimplifyData | null {
  217. if (!statementNode || !NodeGuards.isBlockStatementNode(statementNode)) {
  218. return null;
  219. }
  220. const statementNodeBodyLength: number = statementNode.body.length;
  221. const unwrappedExpressions: ESTree.Expression[] = [];
  222. let hasReturnStatement: boolean = false;
  223. let startIndex: number | null = 0;
  224. for (let i = 0; i < statementNodeBodyLength; i++) {
  225. const statementBodyStatementNode: ESTree.Statement = statementNode.body[i];
  226. if (startIndex === null) {
  227. startIndex = i;
  228. }
  229. if (NodeGuards.isExpressionStatementNode(statementBodyStatementNode)) {
  230. unwrappedExpressions.push(statementBodyStatementNode.expression);
  231. continue;
  232. }
  233. if (
  234. NodeGuards.isReturnStatementNode(statementBodyStatementNode)
  235. && statementBodyStatementNode.argument
  236. ) {
  237. unwrappedExpressions.push(statementBodyStatementNode.argument);
  238. hasReturnStatement = true;
  239. continue;
  240. }
  241. startIndex = null;
  242. unwrappedExpressions.length = 0;
  243. }
  244. if (startIndex === null || !unwrappedExpressions.length) {
  245. return null;
  246. }
  247. const hasSingleExpression: boolean = unwrappedExpressions.length === 1;
  248. const expression: ESTree.Expression = hasSingleExpression
  249. ? unwrappedExpressions[0]
  250. : NodeFactory.sequenceExpressionNode(unwrappedExpressions);
  251. const leadingStatements: ESTree.Statement[] = startIndex > 0
  252. ? statementNode.body.slice(0, startIndex)
  253. : [];
  254. const statement: ESTree.Statement = hasReturnStatement
  255. ? NodeFactory.returnStatementNode(expression)
  256. : NodeFactory.expressionStatementNode(expression);
  257. return {
  258. leadingStatements,
  259. statement,
  260. expression,
  261. hasReturnStatement,
  262. hasSingleExpression
  263. };
  264. }
  265. }