import { inject, injectable } from 'inversify'; import { ServiceIdentifiers } from '../container/ServiceIdentifiers'; import * as estraverse from 'estraverse'; import * as ESTree from 'estree'; import { TNodeTransformerFactory } from '../types/container/node-transformers/TNodeTransformerFactory'; import { TNormalizedNodeTransformers } from '../types/node-transformers/TNormalizedNodeTransformers'; import { TTransformersRunnerData } from '../types/node-transformers/TTransformersRunnerData'; import { TVisitorDirection } from '../types/node-transformers/TVisitorDirection'; import { TVisitorFunction } from '../types/node-transformers/TVisitorFunction'; import { TVisitorResult } from '../types/node-transformers/TVisitorResult'; import { INodeTransformer } from '../interfaces/node-transformers/INodeTransformer'; import { INodeTransformerNamesGroupsBuilder } from '../interfaces/utils/INodeTransformerNamesGroupsBuilder'; import { ITransformersRunner } from '../interfaces/node-transformers/ITransformersRunner'; import { IVisitor } from '../interfaces/node-transformers/IVisitor'; import { NodeTransformer } from '../enums/node-transformers/NodeTransformer'; import { TransformationStage } from '../enums/node-transformers/TransformationStage'; import { VisitorDirection } from '../enums/node-transformers/VisitorDirection'; import { NodeGuards } from '../node/NodeGuards'; import { NodeMetadata } from '../node/NodeMetadata'; @injectable() export class TransformersRunner implements ITransformersRunner { /** * @type {Map} */ private readonly cachedNodeTransformersData: Map = new Map(); /** * @type {TNodeTransformerFactory} */ private readonly nodeTransformerFactory: TNodeTransformerFactory; /** * @type {INodeTransformerNamesGroupsBuilder} */ private readonly nodeTransformerNamesGroupsBuilder: INodeTransformerNamesGroupsBuilder; /** * @param {TNodeTransformerFactory} nodeTransformerFactory * @param {INodeTransformerNamesGroupsBuilder} nodeTransformerNamesGroupsBuilder */ public constructor ( @inject(ServiceIdentifiers.Factory__INodeTransformer) nodeTransformerFactory: TNodeTransformerFactory, @inject(ServiceIdentifiers.INodeTransformerNamesGroupsBuilder) nodeTransformerNamesGroupsBuilder: INodeTransformerNamesGroupsBuilder, ) { this.nodeTransformerFactory = nodeTransformerFactory; this.nodeTransformerNamesGroupsBuilder = nodeTransformerNamesGroupsBuilder; } /** * @param {T} astTree * @param {NodeTransformer[]} nodeTransformerNames * @param {TransformationStage} transformationStage * @returns {T} */ public transform ( astTree: T, nodeTransformerNames: NodeTransformer[], transformationStage: TransformationStage ): T { if (!nodeTransformerNames.length) { return astTree; } let normalizedNodeTransformers: TNormalizedNodeTransformers; let nodeTransformerNamesGroups: NodeTransformer[][]; if (!this.cachedNodeTransformersData.has(nodeTransformerNames)) { normalizedNodeTransformers = this.buildNormalizedNodeTransformers(nodeTransformerNames); nodeTransformerNamesGroups = this.nodeTransformerNamesGroupsBuilder.build(normalizedNodeTransformers); this.cachedNodeTransformersData.set(nodeTransformerNames, [normalizedNodeTransformers, nodeTransformerNamesGroups]); } else { [ normalizedNodeTransformers, nodeTransformerNamesGroups ] = this.cachedNodeTransformersData.get(nodeTransformerNames); } for (const nodeTransformerNamesGroup of nodeTransformerNamesGroups) { const enterVisitors: IVisitor[] = []; const leaveVisitors: IVisitor[] = []; for (const nodeTransformerName of nodeTransformerNamesGroup) { const nodeTransformer: INodeTransformer = normalizedNodeTransformers[nodeTransformerName]; const visitor: IVisitor | null = nodeTransformer.getVisitor(transformationStage); if (!visitor) { continue; } if (visitor.enter) { enterVisitors.push({ enter: visitor.enter }); } if (visitor.leave) { leaveVisitors.push({ leave: visitor.leave }); } } if (!enterVisitors.length && !leaveVisitors.length) { continue; } estraverse.replace(astTree, { enter: this.mergeVisitorsForDirection(enterVisitors, VisitorDirection.Enter), leave: this.mergeVisitorsForDirection(leaveVisitors, VisitorDirection.Leave) }); } return astTree; } /** * @param {NodeTransformer[]} nodeTransformerNames * @returns {TNormalizedNodeTransformers} */ private buildNormalizedNodeTransformers (nodeTransformerNames: NodeTransformer[]): TNormalizedNodeTransformers { return nodeTransformerNames .reduce( (acc: TNormalizedNodeTransformers, nodeTransformerName: NodeTransformer) => ({ ...acc, [nodeTransformerName]: this.nodeTransformerFactory(nodeTransformerName) }), {} ); } /** * @param {IVisitor[]} visitors * @param {TVisitorDirection} direction * @returns {TVisitorFunction} */ private mergeVisitorsForDirection (visitors: IVisitor[], direction: TVisitorDirection): TVisitorFunction { const visitorsLength: number = visitors.length; if (!visitorsLength) { return (node: ESTree.Node, parentNode: ESTree.Node | null): ESTree.Node => node; } return (node: ESTree.Node, parentNode: ESTree.Node | null): ESTree.Node | estraverse.VisitorOption => { if (NodeMetadata.isIgnoredNode(node)) { return estraverse.VisitorOption.Skip; } for (let i: number = 0; i < visitorsLength; i++) { const visitorFunction: TVisitorFunction | undefined = visitors[i][direction]; if (!visitorFunction) { continue; } const visitorResult: TVisitorResult = visitorFunction(node, parentNode); if (!visitorResult || !NodeGuards.isNode(visitorResult)) { continue; } node = visitorResult; } return node; }; } }