IfStatementSimplifyTransformer.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  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/simplifying-transformers/IIfStatementSimplifyData';
  5. import { IIfStatementIteratedStatementsData } from '../../interfaces/node-transformers/simplifying-transformers/IIfStatementIteratedStatementsData';
  6. import { IOptions } from '../../interfaces/options/IOptions';
  7. import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
  8. import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
  9. import { NodeTransformer } from '../../enums/node-transformers/NodeTransformer';
  10. import { NodeTransformationStage } from '../../enums/node-transformers/NodeTransformationStage';
  11. import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
  12. import { NodeGuards } from '../../node/NodeGuards';
  13. import { NodeFactory } from '../../node/NodeFactory';
  14. import { NodeUtils } from '../../node/NodeUtils';
  15. /**
  16. * Simplifies `IfStatement` node
  17. */
  18. @injectable()
  19. export class IfStatementSimplifyTransformer extends AbstractNodeTransformer {
  20. /**
  21. * @type {NodeTransformer[]}
  22. */
  23. public readonly runAfter: NodeTransformer[] = [
  24. NodeTransformer.VariableDeclarationsMergeTransformer
  25. ];
  26. /**
  27. * @param {IRandomGenerator} randomGenerator
  28. * @param {IOptions} options
  29. */
  30. public constructor (
  31. @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
  32. @inject(ServiceIdentifiers.IOptions) options: IOptions
  33. ) {
  34. super(randomGenerator, options);
  35. }
  36. /**
  37. * @param {NodeTransformationStage} nodeTransformationStage
  38. * @returns {IVisitor | null}
  39. */
  40. public getVisitor (nodeTransformationStage: NodeTransformationStage): IVisitor | null {
  41. switch (nodeTransformationStage) {
  42. case NodeTransformationStage.Simplifying:
  43. return {
  44. leave: (
  45. node: ESTree.Node,
  46. parentNode: ESTree.Node | null
  47. ): ESTree.Node | undefined => {
  48. if (parentNode && NodeGuards.isIfStatementNode(node)) {
  49. return this.transformNode(node, parentNode);
  50. }
  51. }
  52. };
  53. default:
  54. return null;
  55. }
  56. }
  57. /**
  58. * @param {ESTree.IfStatement} ifStatementNode
  59. * @param {ESTree.Node} parentNode
  60. * @returns {ESTree.IfStatement}
  61. */
  62. public transformNode (
  63. ifStatementNode: ESTree.IfStatement,
  64. parentNode: ESTree.Node
  65. ): ESTree.Node {
  66. const consequentSimplifyData: IIfStatementSimplifyData | null = this.getIfStatementSimplifyData(ifStatementNode.consequent);
  67. // Variant #1: no valid consequent expression data
  68. if (!consequentSimplifyData) {
  69. return ifStatementNode;
  70. }
  71. let transformedNode: ESTree.Node;
  72. if (!ifStatementNode.alternate) {
  73. // Variant #2: valid data for consequent expression only
  74. transformedNode = this.getConsequentNode(ifStatementNode, consequentSimplifyData);
  75. } else {
  76. const alternateSimplifyData: IIfStatementSimplifyData | null = this.getIfStatementSimplifyData(ifStatementNode.alternate);
  77. if (!alternateSimplifyData) {
  78. return ifStatementNode;
  79. }
  80. // Variant #3: valid data for consequent and alternate expressions
  81. transformedNode = this.getConsequentAndAlternateNode(ifStatementNode, consequentSimplifyData, alternateSimplifyData);
  82. }
  83. return NodeUtils.parentizeNode(transformedNode, parentNode);
  84. }
  85. /**
  86. * @param {ESTree.IfStatement} ifStatementNode
  87. * @param {IIfStatementSimplifyData} consequentSimplifyData
  88. * @returns {ESTree.Node}
  89. */
  90. private getConsequentNode (
  91. ifStatementNode: ESTree.IfStatement,
  92. consequentSimplifyData: IIfStatementSimplifyData
  93. ): ESTree.Node {
  94. /**
  95. * Converts:
  96. * if (true) {
  97. * const foo = 1;
  98. * console.log(1);
  99. * return 1;
  100. * }
  101. *
  102. * to:
  103. * if (true) {
  104. * const foo = 1;
  105. * return console.log(1), 1;
  106. * }
  107. */
  108. if (
  109. consequentSimplifyData.leadingStatements.length
  110. || !consequentSimplifyData.trailingStatement
  111. ) {
  112. return NodeFactory.ifStatementNode(
  113. ifStatementNode.test,
  114. this.getPartialIfStatementBranchNode(consequentSimplifyData)
  115. );
  116. }
  117. /**
  118. * Converts:
  119. * if (true) {
  120. * return 1;
  121. * }
  122. *
  123. * to:
  124. * if (true)
  125. * return 1;
  126. */
  127. if (consequentSimplifyData.hasReturnStatement) {
  128. return NodeFactory.ifStatementNode(
  129. ifStatementNode.test,
  130. consequentSimplifyData.trailingStatement.statement
  131. );
  132. }
  133. /**
  134. * Converts:
  135. * if (true) {
  136. * console.log(1);
  137. * }
  138. *
  139. * to:
  140. * true && console.log(1);
  141. */
  142. return NodeFactory.expressionStatementNode(
  143. NodeFactory.logicalExpressionNode(
  144. '&&',
  145. ifStatementNode.test,
  146. consequentSimplifyData.trailingStatement.expression
  147. )
  148. );
  149. }
  150. /**
  151. * @param {ESTree.IfStatement} ifStatementNode
  152. * @param {IIfStatementSimplifyData} consequentSimplifyData
  153. * @param {IIfStatementSimplifyData} alternateSimplifyData
  154. * @returns {ESTree.Node}
  155. */
  156. private getConsequentAndAlternateNode (
  157. ifStatementNode: ESTree.IfStatement,
  158. consequentSimplifyData: IIfStatementSimplifyData,
  159. alternateSimplifyData: IIfStatementSimplifyData
  160. ): ESTree.Node {
  161. /**
  162. * Converts:
  163. * if (true) {
  164. * const foo = 1;
  165. * console.log(1);
  166. * return 1;
  167. * }
  168. *
  169. * to:
  170. * if (true) {
  171. * const foo = 1;
  172. * return console.log(1), 1;
  173. * }
  174. */
  175. if (
  176. consequentSimplifyData.leadingStatements.length
  177. || alternateSimplifyData.leadingStatements.length
  178. || !consequentSimplifyData.trailingStatement
  179. || !alternateSimplifyData.trailingStatement
  180. ) {
  181. return NodeFactory.ifStatementNode(
  182. ifStatementNode.test,
  183. this.getPartialIfStatementBranchNode(consequentSimplifyData),
  184. this.getPartialIfStatementBranchNode(alternateSimplifyData)
  185. );
  186. }
  187. /**
  188. * Converts:
  189. * if (true) {
  190. * return 1;
  191. * } else {
  192. * return 2;
  193. * }
  194. *
  195. * to:
  196. * return true ? 1 : 2;
  197. */
  198. if (consequentSimplifyData.hasReturnStatement && alternateSimplifyData.hasReturnStatement) {
  199. return NodeFactory.returnStatementNode(
  200. NodeFactory.conditionalExpressionNode(
  201. ifStatementNode.test,
  202. consequentSimplifyData.trailingStatement.expression,
  203. alternateSimplifyData.trailingStatement.expression
  204. )
  205. );
  206. }
  207. /**
  208. * Converts:
  209. * if (true) {
  210. * return 1;
  211. * } else {
  212. * console.log(2);
  213. * }
  214. *
  215. * to:
  216. * if (true)
  217. * return 1;
  218. * else
  219. * console.log(2);
  220. */
  221. if (consequentSimplifyData.hasReturnStatement || alternateSimplifyData.hasReturnStatement) {
  222. return NodeFactory.ifStatementNode(
  223. ifStatementNode.test,
  224. consequentSimplifyData.trailingStatement.statement,
  225. alternateSimplifyData.trailingStatement.statement
  226. );
  227. }
  228. /**
  229. * Converts:
  230. * if (true) {
  231. * console.log(1);
  232. * } else {
  233. * console.log(2);
  234. * }
  235. *
  236. * to:
  237. * true ? console.log(1) : console.log(2);
  238. */
  239. return NodeFactory.expressionStatementNode(
  240. NodeFactory.conditionalExpressionNode(
  241. ifStatementNode.test,
  242. consequentSimplifyData.trailingStatement.expression,
  243. alternateSimplifyData.trailingStatement.expression
  244. )
  245. );
  246. }
  247. /**
  248. * Returns IIfStatementSimplifyData based on `IfStatement` node body
  249. *
  250. * @param {ESTree.Statement | null | undefined} statementNode
  251. * @returns {IIfStatementSimplifyData | null}
  252. */
  253. private getIfStatementSimplifyData (
  254. statementNode: ESTree.Statement | null | undefined
  255. ): IIfStatementSimplifyData | null {
  256. if (!statementNode) {
  257. return null;
  258. }
  259. if (!NodeGuards.isBlockStatementNode(statementNode)) {
  260. return {
  261. leadingStatements: [statementNode],
  262. trailingStatement: null,
  263. hasReturnStatement: false,
  264. hasSingleExpression: false
  265. };
  266. }
  267. const {
  268. startIndex,
  269. unwrappedExpressions,
  270. hasReturnStatement
  271. } = this.collectIteratedStatementsData(statementNode);
  272. const leadingStatements: ESTree.Statement[] = this.getLeadingStatements(statementNode, startIndex);
  273. if (!unwrappedExpressions.length) {
  274. return {
  275. leadingStatements,
  276. trailingStatement: null,
  277. hasReturnStatement,
  278. hasSingleExpression: false
  279. };
  280. }
  281. const hasSingleExpression: boolean = unwrappedExpressions.length === 1;
  282. const expression: ESTree.Expression = hasSingleExpression
  283. ? unwrappedExpressions[0]
  284. : NodeFactory.sequenceExpressionNode(unwrappedExpressions);
  285. const statement: ESTree.Statement = hasReturnStatement
  286. ? NodeFactory.returnStatementNode(expression)
  287. : NodeFactory.expressionStatementNode(expression);
  288. return {
  289. leadingStatements,
  290. trailingStatement: {
  291. statement,
  292. expression
  293. },
  294. hasReturnStatement,
  295. hasSingleExpression
  296. };
  297. }
  298. /**
  299. * Iterates over `IfStatement` node body and collects data
  300. *
  301. * @param {ESTree.Statement | null | undefined} statementNode
  302. * @returns {IIfStatementIteratedStatementsData}
  303. */
  304. private collectIteratedStatementsData (
  305. statementNode: ESTree.BlockStatement
  306. ): IIfStatementIteratedStatementsData {
  307. const statementNodeBodyLength: number = statementNode.body.length;
  308. const unwrappedExpressions: ESTree.Expression[] = [];
  309. let hasReturnStatement: boolean = false;
  310. let startIndex: number | null = 0;
  311. for (let i = 0; i < statementNodeBodyLength; i++) {
  312. const statementBodyStatementNode: ESTree.Statement = statementNode.body[i];
  313. if (startIndex === null) {
  314. startIndex = i;
  315. }
  316. if (NodeGuards.isExpressionStatementNode(statementBodyStatementNode)) {
  317. unwrappedExpressions.push(statementBodyStatementNode.expression);
  318. continue;
  319. }
  320. if (
  321. NodeGuards.isReturnStatementNode(statementBodyStatementNode)
  322. && statementBodyStatementNode.argument
  323. ) {
  324. unwrappedExpressions.push(statementBodyStatementNode.argument);
  325. hasReturnStatement = true;
  326. continue;
  327. }
  328. startIndex = null;
  329. unwrappedExpressions.length = 0;
  330. }
  331. return {
  332. startIndex,
  333. unwrappedExpressions,
  334. hasReturnStatement
  335. };
  336. }
  337. /**
  338. * Returns leading statements for IIfStatementSimplifyData
  339. *
  340. * @param {ESTree.BlockStatement} statementNode
  341. * @param {number | null} startIndex
  342. * @returns {ESTree.Statement[]}
  343. */
  344. private getLeadingStatements (statementNode: ESTree.BlockStatement, startIndex: number | null): ESTree.Statement[] {
  345. // variant #1: no valid statements inside `IfStatement` are found
  346. if (startIndex === null) {
  347. return statementNode.body;
  348. }
  349. return startIndex === 0
  350. // variant #2: all statements inside `IfStatement` branch are valid
  351. ? []
  352. // variant #3: only last N statements inside `IfStatement` branch are valid
  353. : statementNode.body.slice(0, startIndex);
  354. }
  355. /**
  356. * @param {IIfStatementSimplifyData} ifStatementSimplifyData
  357. * @returns {ESTree.BlockStatement}
  358. */
  359. private getPartialIfStatementBranchNode (ifStatementSimplifyData: IIfStatementSimplifyData): ESTree.Statement {
  360. // variant #1: all statements inside `IfStatement` branch are valid
  361. if (!ifStatementSimplifyData.leadingStatements.length && ifStatementSimplifyData.trailingStatement) {
  362. return ifStatementSimplifyData.trailingStatement.statement;
  363. }
  364. // variant #2: only last N statements inside `IfStatement` branch are valid
  365. const blockStatementNode: ESTree.BlockStatement = NodeFactory.blockStatementNode([
  366. ...ifStatementSimplifyData.leadingStatements.length ? ifStatementSimplifyData.leadingStatements : [],
  367. ...ifStatementSimplifyData.trailingStatement ? [ifStatementSimplifyData.trailingStatement.statement] : []
  368. ]);
  369. return blockStatementNode.body.length === 1
  370. && !this.isProhibitedSingleStatementForIfStatementBranch(blockStatementNode.body[0])
  371. ? blockStatementNode.body[0]
  372. : blockStatementNode;
  373. }
  374. /**
  375. * @param {ESTree.Statement} statement
  376. * @returns {boolean}
  377. */
  378. private isProhibitedSingleStatementForIfStatementBranch (statement: ESTree.Statement): boolean {
  379. /**
  380. * Function declaration is not allowed outside of block in `strict` mode
  381. */
  382. return NodeGuards.isFunctionDeclarationNode(statement)
  383. /**
  384. * Without ignore it can break following code:
  385. * Input:
  386. * if (condition1) {
  387. * if (condition2) {
  388. * var foo = bar();
  389. * }
  390. * } else {
  391. * var baz = bark();
  392. * }
  393. *
  394. * Invalid output:
  395. * if (condition1)
  396. * if (condition2)
  397. * var foo = bar();
  398. * else
  399. * var baz = bark();
  400. */
  401. || NodeGuards.isIfStatementNode(statement)
  402. /**
  403. * `let` and `const` variable declarations are not allowed outside of `IfStatement` block statement
  404. * Input:
  405. * if (condition1) {
  406. * const foo = 1;
  407. * }
  408. *
  409. * Invalid output with runtime error:
  410. * if (condition1)
  411. * const foo = 1;
  412. */
  413. || (NodeGuards.isVariableDeclarationNode(statement) && statement.kind !== 'var');
  414. }
  415. }