import { inject, injectable, } from 'inversify'; import { ServiceIdentifiers } from '../../container/ServiceIdentifiers'; import * as estraverse from 'estraverse'; import * as ESTree from 'estree'; import { TControlFlowCustomNodeFactory } from '../../types/container/custom-nodes/TControlFlowCustomNodeFactory'; import { TStatement } from '../../types/node/TStatement'; import { IArrayUtils } from '../../interfaces/utils/IArrayUtils'; import { ICustomNode } from '../../interfaces/custom-nodes/ICustomNode'; import { IOptions } from '../../interfaces/options/IOptions'; import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator'; import { IVisitor } from '../../interfaces/node-transformers/IVisitor'; import { ControlFlowCustomNode } from '../../enums/custom-nodes/ControlFlowCustomNode'; import { AbstractNodeTransformer } from '../AbstractNodeTransformer'; import { NodeGuards } from '../../node/NodeGuards'; import { NodeUtils } from '../../node/NodeUtils'; @injectable() export class BlockStatementControlFlowTransformer extends AbstractNodeTransformer { /** * @type {IArrayUtils} */ private readonly arrayUtils: IArrayUtils; /** * @type {TControlFlowCustomNodeFactory} */ private readonly controlFlowCustomNodeFactory: TControlFlowCustomNodeFactory; /** * @param {TControlFlowCustomNodeFactory} controlFlowCustomNodeFactory * @param {IArrayUtils} arrayUtils * @param {IRandomGenerator} randomGenerator * @param {IOptions} options */ constructor ( @inject(ServiceIdentifiers.Factory__IControlFlowCustomNode) controlFlowCustomNodeFactory: TControlFlowCustomNodeFactory, @inject(ServiceIdentifiers.IArrayUtils) arrayUtils: IArrayUtils, @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator, @inject(ServiceIdentifiers.IOptions) options: IOptions ) { super(randomGenerator, options); this.controlFlowCustomNodeFactory = controlFlowCustomNodeFactory; this.arrayUtils = arrayUtils; } /** * @param {BlockStatement} blockStatementNode * @returns {boolean} */ private static blockStatementHasProhibitedStatements (blockStatementNode: ESTree.BlockStatement): boolean { return blockStatementNode.body.some((statement: ESTree.Statement) => { const isBreakOrContinueStatement: boolean = NodeGuards.isBreakStatementNode(statement) || NodeGuards.isContinueStatementNode(statement); const isVariableDeclarationWithLetOrConstKind: boolean = NodeGuards.isVariableDeclarationNode(statement) && (statement.kind === 'const' || statement.kind === 'let'); return NodeGuards.isFunctionDeclarationNode(statement) || isBreakOrContinueStatement || isVariableDeclarationWithLetOrConstKind; }); } /** * @param {BlockStatement} blockStatementNode * @returns {boolean} */ private static canTransformBlockStatementNode (blockStatementNode: ESTree.BlockStatement): boolean { let canTransform: boolean = true; estraverse.traverse(blockStatementNode, { enter: (node: ESTree.Node): any => { if (NodeGuards.isWhileStatementNode(node)) { return estraverse.VisitorOption.Skip; } if ( NodeGuards.isBlockStatementNode(node) && BlockStatementControlFlowTransformer.blockStatementHasProhibitedStatements(node) ) { canTransform = false; } } }); if (blockStatementNode.body.length <= 4) { canTransform = false; } return canTransform; } /** * @return {IVisitor} */ public getVisitor (): IVisitor { return { leave: (node: ESTree.Node, parentNode: ESTree.Node | null) => { if (parentNode && NodeGuards.isBlockStatementNode(node)) { return this.transformNode(node, parentNode); } } }; } /** * @param {BlockStatement} blockStatementNode * @param {NodeGuards} parentNode * @returns {NodeGuards} */ public transformNode (blockStatementNode: ESTree.BlockStatement, parentNode: ESTree.Node): ESTree.Node { if ( this.randomGenerator.getMathRandom() > this.options.controlFlowFlatteningThreshold || !BlockStatementControlFlowTransformer.canTransformBlockStatementNode(blockStatementNode) ) { return blockStatementNode; } const blockStatementBody: ESTree.Statement[] = blockStatementNode.body; const originalKeys: number[] = this.arrayUtils.arrayRange(blockStatementBody.length); const shuffledKeys: number[] = this.arrayUtils.arrayShuffle(originalKeys); const originalKeysIndexesInShuffledArray: number[] = originalKeys.map((key: number) => shuffledKeys.indexOf(key)); const blockStatementControlFlowFlatteningCustomNode: ICustomNode = this.controlFlowCustomNodeFactory( ControlFlowCustomNode.BlockStatementControlFlowFlatteningNode ); blockStatementControlFlowFlatteningCustomNode.initialize( blockStatementBody, shuffledKeys, originalKeysIndexesInShuffledArray ); const newBlockStatementNode: TStatement = blockStatementControlFlowFlatteningCustomNode.getNode()[0]; NodeUtils.parentizeNode(newBlockStatementNode, parentNode); return newBlockStatementNode; } }