| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333 |
- import { inject, injectable } from 'inversify';
- import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
- import { TNodeWithLexicalScope } from '../../types/node/TNodeWithLexicalScope';
- import { IOptions } from '../../interfaces/options/IOptions';
- import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
- import { ISetUtils } from '../../interfaces/utils/ISetUtils';
- import { alphabetString } from '../../constants/AlphabetString';
- import { alphabetStringUppercase } from '../../constants/AlphabetStringUppercase';
- import { numbersString } from '../../constants/NumbersString';
- import { reservedIdentifierNames } from '../../constants/ReservedIdentifierNames';
- import { AbstractIdentifierNamesGenerator } from './AbstractIdentifierNamesGenerator';
- import { NodeLexicalScopeUtils } from '../../node/NodeLexicalScopeUtils';
- @injectable()
- export class MangledIdentifierNamesGenerator extends AbstractIdentifierNamesGenerator {
- /**
- * @type {number}
- */
- private static readonly maxRegenerationAttempts: number = 20;
- /**
- * @type {string}
- */
- private static readonly initMangledNameCharacter: string = '9';
- /**
- * @type {string[]}
- */
- private static readonly nameSequence: string[] = [
- ...`${numbersString}${alphabetString}${alphabetStringUppercase}`
- ];
- /**
- * Reserved JS words with length of 2-4 symbols that can be possible generated with this replacer
- * + reserved DOM names like `Set`, `Map`, `Date`, etc
- *
- * @type {Set<string>}
- */
- private static readonly reservedNamesSet: Set<string> = new Set(reservedIdentifierNames);
- /**
- * @type {string}
- */
- private lastMangledName: string = MangledIdentifierNamesGenerator.initMangledNameCharacter;
- /**
- * @type {WeakMap<TNodeWithLexicalScope, string>}
- */
- private readonly lastMangledNameForScopeMap: WeakMap <TNodeWithLexicalScope, string> = new WeakMap();
- /**
- * @type {WeakMap<string, string>}
- */
- private readonly lastMangledNameForLabelMap: Map <string, string> = new Map();
- /**
- * @type {ISetUtils}
- */
- private readonly setUtils: ISetUtils;
- /**
- * @param {IRandomGenerator} randomGenerator
- * @param {IOptions} options
- * @param {ISetUtils} setUtils
- */
- public constructor (
- @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
- @inject(ServiceIdentifiers.IOptions) options: IOptions,
- @inject(ServiceIdentifiers.ISetUtils) setUtils: ISetUtils,
- ) {
- super(randomGenerator, options);
- this.setUtils = setUtils;
- }
- /**
- * Generates next name based on a global previous mangled name
- * We can ignore nameLength parameter here, it hasn't sense with this generator
- *
- * @param {number} nameLength
- * @returns {string}
- */
- public generateNext (nameLength?: number): string {
- const identifierName: string = this.generateNewMangledName(this.lastMangledName);
- this.updatePreviousMangledName(identifierName);
- this.preserveName(identifierName);
- return identifierName;
- }
- /**
- * @param {number} nameLength
- * @returns {string}
- */
- public generateForGlobalScope (nameLength?: number): string {
- const prefix: string = this.options.identifiersPrefix ?
- `${this.options.identifiersPrefix}`
- : '';
- const identifierName: string = this.generateNewMangledName(
- this.lastMangledName,
- (newIdentifierName: string) => {
- const identifierNameWithPrefix: string = `${prefix}${newIdentifierName}`;
- return this.isValidIdentifierName(identifierNameWithPrefix);
- }
- );
- const identifierNameWithPrefix: string = `${prefix}${identifierName}`;
- this.updatePreviousMangledName(identifierName);
- this.preserveName(identifierNameWithPrefix);
- return identifierNameWithPrefix;
- }
- /**
- * @param {TNodeWithLexicalScope} lexicalScopeNode
- * @param {number} nameLength
- * @returns {string}
- */
- public generateForLexicalScope (lexicalScopeNode: TNodeWithLexicalScope, nameLength?: number): string {
- const lexicalScopes: TNodeWithLexicalScope[] = [
- lexicalScopeNode,
- ...NodeLexicalScopeUtils.getLexicalScopes(lexicalScopeNode)
- ];
- const lastMangledNameForScope: string = this.getLastMangledNameForScopes(lexicalScopes);
- const identifierName: string = this.generateNewMangledName(
- lastMangledNameForScope,
- (newIdentifierName: string) =>
- this.isValidIdentifierNameInLexicalScopes(newIdentifierName, lexicalScopes)
- );
- this.lastMangledNameForScopeMap.set(lexicalScopeNode, identifierName);
- this.updatePreviousMangledName(identifierName);
- this.preserveNameForLexicalScope(identifierName, lexicalScopeNode);
- return identifierName;
- }
- /**
- * @param {string} label
- * @param {number} nameLength
- * @returns {string}
- */
- public generateForLabel (label: string, nameLength?: number): string {
- const lastMangledNameForLabel: string = this.getLastMangledNameForLabel(label);
- const identifierName: string = this.generateNewMangledName(lastMangledNameForLabel);
- this.updatePreviousMangledNameForLabel(identifierName, label, lastMangledNameForLabel);
- return identifierName;
- }
- /**
- * @param {string} nextName
- * @param {string} prevName
- * @returns {boolean}
- */
- // eslint-disable-next-line complexity
- public isIncrementedMangledName (nextName: string, prevName: string): boolean {
- if (nextName === prevName) {
- return false;
- }
- const nextNameLength: number = nextName.length;
- const prevNameLength: number = prevName.length;
- if (nextNameLength !== prevNameLength) {
- return nextNameLength > prevNameLength;
- }
- const nameSequence: string[] = this.getNameSequence();
- for (let i: number = 0; i < nextNameLength; i++) {
- const nextNameCharacter: string = nextName[i];
- const prevNameCharacter: string = prevName[i];
- if (nextNameCharacter === prevNameCharacter) {
- continue;
- }
- const indexOfNextNameCharacter: number = nameSequence.indexOf(nextNameCharacter);
- const indexOfPrevNameCharacter: number = nameSequence.indexOf(prevNameCharacter);
- return indexOfNextNameCharacter > indexOfPrevNameCharacter;
- }
- throw new Error('Something goes wrong during comparison of mangled names');
- }
- /**
- * @param {string} mangledName
- * @returns {boolean}
- */
- public override isValidIdentifierName (mangledName: string): boolean {
- return super.isValidIdentifierName(mangledName)
- && !MangledIdentifierNamesGenerator.reservedNamesSet.has(mangledName);
- }
- /**
- * @returns {string[]}
- */
- protected getNameSequence (): string[] {
- return MangledIdentifierNamesGenerator.nameSequence;
- }
- /**
- * @param {string} name
- */
- protected updatePreviousMangledName (name: string): void {
- if (!this.isIncrementedMangledName(name, this.lastMangledName)) {
- return;
- }
- this.lastMangledName = name;
- }
- /**
- * @param {string} name
- * @param {string} label
- * @param {string} lastMangledNameForLabel
- */
- protected updatePreviousMangledNameForLabel (name: string, label: string, lastMangledNameForLabel: string): void {
- if (!this.isIncrementedMangledName(name, lastMangledNameForLabel)) {
- return;
- }
- this.lastMangledNameForLabelMap.set(label, name);
- }
- /**
- * @param {string} previousMangledName
- * @param {(newIdentifierName: string) => boolean} validationFunction
- * @returns {string}
- */
- protected generateNewMangledName (
- previousMangledName: string,
- validationFunction?: (newIdentifierName: string) => boolean
- ): string {
- const generateNewMangledName = (name: string, regenerationAttempt: number = 0): string => {
- /**
- * Attempt to decrease amount of regeneration tries because of large preserved names set
- * When we reached the limit, we're trying to generate next mangled name based on the latest
- * preserved name
- */
- if (regenerationAttempt > MangledIdentifierNamesGenerator.maxRegenerationAttempts) {
- const lastPreservedName = this.setUtils.getLastElement(this.preservedNamesSet);
- if (lastPreservedName) {
- return this.generateNewMangledName(lastPreservedName);
- }
- }
- const nameSequence: string[] = this.getNameSequence();
- const nameSequenceLength: number = nameSequence.length;
- const nameLength: number = name.length;
- const zeroSequence: (num: number) => string = (num: number): string => {
- return '0'.repeat(num);
- };
- let index: number = nameLength - 1;
- do {
- const character: string = name[index];
- const indexInSequence: number = nameSequence.indexOf(character);
- const lastNameSequenceIndex: number = nameSequenceLength - 1;
- if (indexInSequence !== lastNameSequenceIndex) {
- const previousNamePart: string = name.slice(0, index);
- const nextCharacter: string = nameSequence[indexInSequence + 1];
- const zeroSequenceLength: number = nameLength - (index + 1);
- const zeroSequenceCharacters: string = zeroSequence(zeroSequenceLength);
- return previousNamePart + nextCharacter + zeroSequenceCharacters;
- }
- --index;
- } while (index >= 0);
- const firstLetterCharacter: string = nameSequence[numbersString.length];
- return `${firstLetterCharacter}${zeroSequence(nameLength)}`;
- };
- let identifierName: string = previousMangledName;
- let isValidIdentifierName: boolean;
- do {
- identifierName = generateNewMangledName(identifierName);
- isValidIdentifierName = validationFunction?.(identifierName)
- ?? this.isValidIdentifierName(identifierName);
- } while (!isValidIdentifierName);
- return identifierName;
- }
- /**
- * @param {TNodeWithLexicalScope[]} lexicalScopeNodes
- * @returns {string}
- */
- private getLastMangledNameForScopes (lexicalScopeNodes: TNodeWithLexicalScope[]): string {
- for (const lexicalScope of lexicalScopeNodes) {
- const lastMangledName: string | null = this.lastMangledNameForScopeMap.get(lexicalScope) ?? null;
- if (!lastMangledName) {
- continue;
- }
- return lastMangledName;
- }
- return MangledIdentifierNamesGenerator.initMangledNameCharacter;
- }
- /**
- * @param {string} label
- * @returns {string}
- */
- private getLastMangledNameForLabel (label: string): string {
- const lastMangledName: string | null = this.lastMangledNameForLabelMap.get(label) ?? null;
- return lastMangledName ?? MangledIdentifierNamesGenerator.initMangledNameCharacter;
- }
- }
|