import { inject, injectable, } from 'inversify'; import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers'; import * as ESTree from 'estree'; import { TIdentifierNamesGeneratorFactory } from '../../../types/container/generators/TIdentifierNamesGeneratorFactory'; import { TNodeWithLexicalScope } from '../../../types/node/TNodeWithLexicalScope'; import { IIdentifierNamesCacheStorage } from '../../../interfaces/storages/identifier-names-cache/IIdentifierNamesCacheStorage'; import { IIdentifierNamesGenerator } from '../../../interfaces/generators/identifier-names-generators/IIdentifierNamesGenerator'; import { IIdentifierReplacer } from '../../../interfaces/node-transformers/rename-identifiers-transformers/replacer/IIdentifierReplacer'; import { IOptions } from '../../../interfaces/options/IOptions'; import { NodeFactory } from '../../../node/NodeFactory'; @injectable() export class IdentifierReplacer implements IIdentifierReplacer { /** * @type {IIdentifierNamesCacheStorage} */ private readonly identifierNamesCacheStorage: IIdentifierNamesCacheStorage; /** * @type {IIdentifierNamesGenerator} */ private readonly identifierNamesGenerator: IIdentifierNamesGenerator; /** * @type {Map>} */ private readonly blockScopesMap: Map> = new Map(); /** * @type {IOptions} */ private readonly options: IOptions; /** * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory * @param {IIdentifierNamesCacheStorage} identifierNamesCacheStorage * @param {IOptions} options */ public constructor ( @inject(ServiceIdentifiers.Factory__IIdentifierNamesGenerator) identifierNamesGeneratorFactory: TIdentifierNamesGeneratorFactory, @inject(ServiceIdentifiers.IIdentifierNamesCacheStorage) identifierNamesCacheStorage: IIdentifierNamesCacheStorage, @inject(ServiceIdentifiers.IOptions) options: IOptions ) { this.options = options; this.identifierNamesCacheStorage = identifierNamesCacheStorage; this.identifierNamesGenerator = identifierNamesGeneratorFactory(options); } /** * Store `nodeName` of global identifiers as key in map with random name as value. * Reserved name will be ignored. * * @param {Node} identifierNode * @param {TNodeWithLexicalScope} lexicalScopeNode */ public storeGlobalName (identifierNode: ESTree.Identifier, lexicalScopeNode: TNodeWithLexicalScope): void { const identifierName: string = identifierNode.name; if (this.isReservedName(identifierName)) { return; } const valueFromIdentifierNamesCache: string | null = this.identifierNamesCacheStorage.get(identifierName) ?? null; let newIdentifierName: string; if (valueFromIdentifierNamesCache) { newIdentifierName = valueFromIdentifierNamesCache; } else { newIdentifierName = this.identifierNamesGenerator.generateForGlobalScope(); } if (!this.blockScopesMap.has(lexicalScopeNode)) { this.blockScopesMap.set(lexicalScopeNode, new Map()); } const namesMap: Map = >this.blockScopesMap.get(lexicalScopeNode); namesMap.set(identifierName, newIdentifierName); if (valueFromIdentifierNamesCache !== newIdentifierName) { this.identifierNamesCacheStorage.set(identifierName, newIdentifierName); } } /** * Store `nodeName` of local identifier as key in map with random name as value. * Reserved name will be ignored. * * @param {Identifier} identifierNode * @param {TNodeWithLexicalScope} lexicalScopeNode */ public storeLocalName (identifierNode: ESTree.Identifier, lexicalScopeNode: TNodeWithLexicalScope): void { const identifierName: string = identifierNode.name; if (this.isReservedName(identifierName)) { return; } const newIdentifierName: string = this.identifierNamesGenerator.generateForLexicalScope(lexicalScopeNode); if (!this.blockScopesMap.has(lexicalScopeNode)) { this.blockScopesMap.set(lexicalScopeNode, new Map()); } const namesMap: Map = >this.blockScopesMap.get(lexicalScopeNode); namesMap.set(identifierName, newIdentifierName); } /** * @param {Identifier} identifierNode * @param {TNodeWithLexicalScope} lexicalScopeNode * @returns {Identifier} */ public replace (identifierNode: ESTree.Identifier, lexicalScopeNode: TNodeWithLexicalScope): ESTree.Identifier { let identifierName: string = identifierNode.name; if (this.blockScopesMap.has(lexicalScopeNode)) { const namesMap: Map = >this.blockScopesMap.get(lexicalScopeNode); if (namesMap.has(identifierName)) { identifierName = namesMap.get(identifierName); } } return NodeFactory.identifierNode(identifierName); } /** * Preserve `name` to protect it from further using * * @param {Identifier} identifierNode */ public preserveName (identifierNode: ESTree.Identifier): void { this.identifierNamesGenerator.preserveName(identifierNode.name); } /** * Preserve `name` to protect it from further using * * @param {Identifier} identifierNode * @param {TNodeWithLexicalScope} lexicalScopeNode */ public preserveNameForLexicalScope (identifierNode: ESTree.Identifier, lexicalScopeNode: TNodeWithLexicalScope): void { this.identifierNamesGenerator.preserveNameForLexicalScope(identifierNode.name, lexicalScopeNode); } /** * @param {string} name * @returns {boolean} */ private isReservedName (name: string): boolean { if (!this.options.reservedNames.length) { return false; } return this.options.reservedNames .some((reservedName: string) => { return new RegExp(reservedName, 'g').exec(name) !== null; }); } }