• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

javascript-obfuscator / javascript-obfuscator / 21613150408

27 Jan 2026 09:04PM UTC coverage: 96.658%. Remained the same
21613150408

push

github

web-flow
Fix identifiers generation (#1378)

1895 of 2050 branches covered (92.44%)

Branch coverage included in aggregate %.

35 of 36 new or added lines in 3 files covered. (97.22%)

5 existing lines in 1 file now uncovered.

5885 of 5999 relevant lines covered (98.1%)

30913015.11 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.24
/src/generators/identifier-names-generators/MangledIdentifierNamesGenerator.ts
1
import { inject, injectable } from 'inversify';
6✔
2
import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
6✔
3

4
import { TNodeWithLexicalScope } from '../../types/node/TNodeWithLexicalScope';
5

6
import { IOptions } from '../../interfaces/options/IOptions';
7
import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
8
import { ISetUtils } from '../../interfaces/utils/ISetUtils';
9

10
import { alphabetString } from '../../constants/AlphabetString';
6✔
11
import { alphabetStringUppercase } from '../../constants/AlphabetStringUppercase';
6✔
12
import { numbersString } from '../../constants/NumbersString';
6✔
13
import { reservedIdentifierNames } from '../../constants/ReservedIdentifierNames';
6✔
14

15
import { AbstractIdentifierNamesGenerator } from './AbstractIdentifierNamesGenerator';
6✔
16
import { NodeLexicalScopeUtils } from '../../node/NodeLexicalScopeUtils';
6✔
17

18
@injectable()
19
export class MangledIdentifierNamesGenerator extends AbstractIdentifierNamesGenerator {
6✔
20
    /**
21
     * @type {number}
22
     */
23
    private static readonly maxRegenerationAttempts: number = 20;
6✔
24

25
    /**
26
     * @type {string}
27
     */
28
    private static readonly initMangledNameCharacter: string = '9';
6✔
29

30
    /**
31
     * @type {string[]}
32
     */
33
    private static readonly nameSequence: string[] = [...`${numbersString}${alphabetString}${alphabetStringUppercase}`];
6✔
34

35
    /**
36
     * Reserved JS words with length of 2-4 symbols that can be possible generated with this replacer
37
     * + reserved DOM names like `Set`, `Map`, `Date`, etc
38
     *
39
     * @type {Set<string>}
40
     */
41
    private static readonly reservedNamesSet: Set<string> = new Set(reservedIdentifierNames);
6✔
42

43
    /**
44
     * @type {string}
45
     */
46
    private lastMangledName: string = MangledIdentifierNamesGenerator.initMangledNameCharacter;
50,638✔
47

48
    /**
49
     * @type {WeakMap<TNodeWithLexicalScope, string>}
50
     */
51
    private readonly lastMangledNameForScopeMap: WeakMap<TNodeWithLexicalScope, string> = new WeakMap();
50,638✔
52

53
    /**
54
     * @type {WeakMap<string, string>}
55
     */
56
    private readonly lastMangledNameForLabelMap: Map<string, string> = new Map();
50,638✔
57

58
    /**
59
     * @type {ISetUtils}
60
     */
61
    private readonly setUtils: ISetUtils;
62

63
    /**
64
     * @param {IRandomGenerator} randomGenerator
65
     * @param {IOptions} options
66
     * @param {ISetUtils} setUtils
67
     */
68
    public constructor(
69
        @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
70
        @inject(ServiceIdentifiers.IOptions) options: IOptions,
71
        @inject(ServiceIdentifiers.ISetUtils) setUtils: ISetUtils
72
    ) {
73
        super(randomGenerator, options);
50,638✔
74

75
        this.setUtils = setUtils;
50,638✔
76
    }
77

78
    /**
79
     * Generates next name based on a global previous mangled name
80
     * We can ignore nameLength parameter here, it hasn't sense with this generator
81
     *
82
     * @param {number} nameLength
83
     * @returns {string}
84
     */
85
    public generateNext(nameLength?: number): string {
86
        const identifierName: string = this.generateNewMangledName(this.lastMangledName);
13,047,852✔
87

88
        this.updatePreviousMangledName(identifierName);
13,047,852✔
89
        this.preserveName(identifierName);
13,047,852✔
90

91
        return identifierName;
13,047,852✔
92
    }
93

94
    /**
95
     * @param {number} nameLength
96
     * @returns {string}
97
     */
98
    public generateForGlobalScope(nameLength?: number): string {
99
        return this.generateForGlobalScopeInternal((name) => this.isValidIdentifierName(name));
1,397,370✔
100
    }
101

102
    /**
103
     * @param {number} nameLength
104
     * @returns {string}
105
     */
106
    public generateForGlobalScopeWithAllScopesValidation(nameLength?: number): string {
107
        return this.generateForGlobalScopeInternal((name) => this.isValidIdentifierNameInAllScopes(name));
282✔
108
    }
109

110
    /**
111
     * @param {TNodeWithLexicalScope} lexicalScopeNode
112
     * @param {number} nameLength
113
     * @returns {string}
114
     */
115
    public generateForLexicalScope(lexicalScopeNode: TNodeWithLexicalScope, nameLength?: number): string {
116
        const lexicalScopes: TNodeWithLexicalScope[] = [
7,129,300✔
117
            lexicalScopeNode,
118
            ...NodeLexicalScopeUtils.getLexicalScopes(lexicalScopeNode)
119
        ];
120

121
        const lastMangledNameForScope: string = this.getLastMangledNameForScopes(lexicalScopes);
7,129,300✔
122
        const identifierName: string = this.generateNewMangledName(
7,129,300✔
123
            lastMangledNameForScope,
124
            (newIdentifierName: string) => this.isValidIdentifierNameInLexicalScopes(newIdentifierName, lexicalScopes)
19,004,881✔
125
        );
126

127
        this.lastMangledNameForScopeMap.set(lexicalScopeNode, identifierName);
7,129,300✔
128

129
        this.updatePreviousMangledName(identifierName);
7,129,300✔
130
        this.preserveNameForLexicalScope(identifierName, lexicalScopeNode);
7,129,300✔
131

132
        return identifierName;
7,129,300✔
133
    }
134

135
    /**
136
     * @param {string} label
137
     * @param {number} nameLength
138
     * @returns {string}
139
     */
140
    public generateForLabel(label: string, nameLength?: number): string {
141
        const lastMangledNameForLabel: string = this.getLastMangledNameForLabel(label);
26,465,235✔
142

143
        const identifierName: string = this.generateNewMangledName(lastMangledNameForLabel);
26,465,235✔
144

145
        this.updatePreviousMangledNameForLabel(identifierName, label, lastMangledNameForLabel);
26,465,235✔
146

147
        return identifierName;
26,465,235✔
148
    }
149

150
    /**
151
     * @param {string} nextName
152
     * @param {string} prevName
153
     * @returns {boolean}
154
     */
155
    // eslint-disable-next-line complexity
156
    public isIncrementedMangledName(nextName: string, prevName: string): boolean {
157
        if (nextName === prevName) {
72,039,722✔
158
            return false;
23,488✔
159
        }
160

161
        const nextNameLength: number = nextName.length;
72,016,234✔
162
        const prevNameLength: number = prevName.length;
72,016,234✔
163

164
        if (nextNameLength !== prevNameLength) {
72,016,234✔
165
            return nextNameLength > prevNameLength;
6,263,965✔
166
        }
167

168
        const nameSequence: string[] = this.getNameSequence();
65,752,269✔
169

170
        for (let i: number = 0; i < nextNameLength; i++) {
65,752,269✔
171
            const nextNameCharacter: string = nextName[i];
203,928,223✔
172
            const prevNameCharacter: string = prevName[i];
203,928,223✔
173

174
            if (nextNameCharacter === prevNameCharacter) {
203,928,223✔
175
                continue;
138,175,954✔
176
            }
177

178
            const indexOfNextNameCharacter: number = nameSequence.indexOf(nextNameCharacter);
65,752,269✔
179
            const indexOfPrevNameCharacter: number = nameSequence.indexOf(prevNameCharacter);
65,752,269✔
180

181
            return indexOfNextNameCharacter > indexOfPrevNameCharacter;
65,752,269✔
182
        }
183

UNCOV
184
        throw new Error('Something goes wrong during comparison of mangled names');
×
185
    }
186

187
    /**
188
     * @param {string} mangledName
189
     * @returns {boolean}
190
     */
191
    public override isValidIdentifierName(mangledName: string): boolean {
192
        return (
1,191,825,510✔
193
            super.isValidIdentifierName(mangledName) &&
1,240,826,827✔
194
            !MangledIdentifierNamesGenerator.reservedNamesSet.has(mangledName)
195
        );
196
    }
197

198
    /**
199
     * @returns {string[]}
200
     */
201
    protected getNameSequence(): string[] {
202
        return MangledIdentifierNamesGenerator.nameSequence;
626,180,586✔
203
    }
204

205
    /**
206
     * @param {string} name
207
     */
208
    protected updatePreviousMangledName(name: string): void {
209
        if (!this.isIncrementedMangledName(name, this.lastMangledName)) {
21,574,463✔
210
            return;
6,792,172✔
211
        }
212

213
        this.lastMangledName = name;
14,782,291✔
214
    }
215

216
    /**
217
     * @param {string} name
218
     * @param {string} label
219
     * @param {string} lastMangledNameForLabel
220
     */
221
    protected updatePreviousMangledNameForLabel(name: string, label: string, lastMangledNameForLabel: string): void {
222
        if (!this.isIncrementedMangledName(name, lastMangledNameForLabel)) {
26,465,235!
UNCOV
223
            return;
×
224
        }
225

226
        this.lastMangledNameForLabelMap.set(label, name);
26,465,235✔
227
    }
228

229
    /**
230
     * @param {string} previousMangledName
231
     * @param {(newIdentifierName: string) => boolean} validationFunction
232
     * @returns {string}
233
     */
234
    protected generateNewMangledName(
235
        previousMangledName: string,
236
        validationFunction?: (newIdentifierName: string) => boolean
237
    ): string {
238
        const generateNewMangledName = (name: string, regenerationAttempt: number = 0): string => {
48,039,698✔
239
            /**
240
             * Attempt to decrease amount of regeneration tries because of large preserved names set
241
             * When we reached the limit, we're trying to generate next mangled name based on the latest
242
             * preserved name
243
             */
244
            if (regenerationAttempt > MangledIdentifierNamesGenerator.maxRegenerationAttempts) {
1,191,825,474!
UNCOV
245
                const lastPreservedName = this.setUtils.getLastElement(this.preservedNamesSet);
×
246

UNCOV
247
                if (lastPreservedName) {
×
UNCOV
248
                    return this.generateNewMangledName(lastPreservedName);
×
249
                }
250
            }
251

252
            const nameSequence: string[] = this.getNameSequence();
1,191,825,474✔
253
            const nameSequenceLength: number = nameSequence.length;
1,191,825,474✔
254
            const nameLength: number = name.length;
1,191,825,474✔
255

256
            const zeroSequence: (num: number) => string = (num: number): string => {
1,191,825,474✔
257
                return '0'.repeat(num);
1,191,825,474✔
258
            };
259

260
            let index: number = nameLength - 1;
1,191,825,474✔
261

262
            do {
1,191,825,474✔
263
                const character: string = name[index];
1,210,678,690✔
264
                const indexInSequence: number = nameSequence.indexOf(character);
1,210,678,690✔
265
                const lastNameSequenceIndex: number = nameSequenceLength - 1;
1,210,678,690✔
266

267
                if (indexInSequence !== lastNameSequenceIndex) {
1,210,678,690✔
268
                    const previousNamePart: string = name.slice(0, index);
1,191,724,073✔
269
                    const nextCharacter: string = nameSequence[indexInSequence + 1];
1,191,724,073✔
270
                    const zeroSequenceLength: number = nameLength - (index + 1);
1,191,724,073✔
271
                    const zeroSequenceCharacters: string = zeroSequence(zeroSequenceLength);
1,191,724,073✔
272

273
                    return previousNamePart + nextCharacter + zeroSequenceCharacters;
1,191,724,073✔
274
                }
275

276
                --index;
18,954,617✔
277
            } while (index >= 0);
278

279
            const firstLetterCharacter: string = nameSequence[numbersString.length];
101,401✔
280

281
            return `${firstLetterCharacter}${zeroSequence(nameLength)}`;
101,401✔
282
        };
283

284
        let identifierName: string = previousMangledName;
48,039,698✔
285
        let isValidIdentifierName: boolean;
286

287
        do {
48,039,698✔
288
            identifierName = generateNewMangledName(identifierName);
1,191,825,474✔
289
            isValidIdentifierName = validationFunction?.(identifierName) ?? this.isValidIdentifierName(identifierName);
1,191,825,474✔
290
        } while (!isValidIdentifierName);
291

292
        return identifierName;
48,039,698✔
293
    }
294

295
    /**
296
     * @param {(name: string) => boolean} validationFn
297
     * @returns {string}
298
     */
299
    private generateForGlobalScopeInternal(validationFn: (name: string) => boolean): string {
300
        const prefix: string = this.options.identifiersPrefix ? `${this.options.identifiersPrefix}` : '';
1,397,311✔
301

302
        const identifierName: string = this.generateNewMangledName(
1,397,311✔
303
            this.lastMangledName,
304
            (newIdentifierName: string) => {
305
                const identifierNameWithPrefix: string = `${prefix}${newIdentifierName}`;
1,397,652✔
306

307
                return validationFn(identifierNameWithPrefix);
1,397,652✔
308
            }
309
        );
310
        const identifierNameWithPrefix: string = `${prefix}${identifierName}`;
1,397,311✔
311

312
        this.updatePreviousMangledName(identifierName);
1,397,311✔
313
        this.preserveName(identifierNameWithPrefix);
1,397,311✔
314

315
        return identifierNameWithPrefix;
1,397,311✔
316
    }
317

318
    /**
319
     * @param {TNodeWithLexicalScope[]} lexicalScopeNodes
320
     * @returns {string}
321
     */
322
    private getLastMangledNameForScopes(lexicalScopeNodes: TNodeWithLexicalScope[]): string {
323
        for (const lexicalScope of lexicalScopeNodes) {
7,129,300✔
324
            const lastMangledName: string | null = this.lastMangledNameForScopeMap.get(lexicalScope) ?? null;
9,311,546✔
325

326
            if (!lastMangledName) {
9,311,546✔
327
                continue;
3,346,552✔
328
            }
329

330
            return lastMangledName;
5,964,994✔
331
        }
332

333
        return MangledIdentifierNamesGenerator.initMangledNameCharacter;
1,164,306✔
334
    }
335

336
    /**
337
     * @param {string} label
338
     * @returns {string}
339
     */
340
    private getLastMangledNameForLabel(label: string): string {
341
        const lastMangledName: string | null = this.lastMangledNameForLabelMap.get(label) ?? null;
26,465,235✔
342

343
        return lastMangledName ?? MangledIdentifierNamesGenerator.initMangledNameCharacter;
26,465,235✔
344
    }
345
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc