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

javascript-obfuscator / javascript-obfuscator / 21501110621

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

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%)

30447958.87 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,770✔
47

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

53
    /**
54
     * @type {WeakMap<string, string>}
55
     */
56
    private readonly lastMangledNameForLabelMap: Map<string, string> = new Map();
50,770✔
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,770✔
74

75
        this.setUtils = setUtils;
50,770✔
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,048,189✔
87

88
        this.updatePreviousMangledName(identifierName);
13,048,189✔
89
        this.preserveName(identifierName);
13,048,189✔
90

91
        return identifierName;
13,048,189✔
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,399,229✔
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,135,512✔
117
            lexicalScopeNode,
118
            ...NodeLexicalScopeUtils.getLexicalScopes(lexicalScopeNode)
119
        ];
120

121
        const lastMangledNameForScope: string = this.getLastMangledNameForScopes(lexicalScopes);
7,135,512✔
122
        const identifierName: string = this.generateNewMangledName(
7,135,512✔
123
            lastMangledNameForScope,
124
            (newIdentifierName: string) => this.isValidIdentifierNameInLexicalScopes(newIdentifierName, lexicalScopes)
19,051,872✔
125
        );
126

127
        this.lastMangledNameForScopeMap.set(lexicalScopeNode, identifierName);
7,135,512✔
128

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

132
        return identifierName;
7,135,512✔
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,482,947✔
142

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

145
        this.updatePreviousMangledNameForLabel(identifierName, label, lastMangledNameForLabel);
26,482,947✔
146

147
        return identifierName;
26,482,947✔
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,065,677✔
158
            return false;
23,858✔
159
        }
160

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

164
        if (nextNameLength !== prevNameLength) {
72,041,819✔
165
            return nextNameLength > prevNameLength;
6,254,680✔
166
        }
167

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

170
        for (let i: number = 0; i < nextNameLength; i++) {
65,787,139✔
171
            const nextNameCharacter: string = nextName[i];
203,998,100✔
172
            const prevNameCharacter: string = prevName[i];
203,998,100✔
173

174
            if (nextNameCharacter === prevNameCharacter) {
203,998,100✔
175
                continue;
138,210,961✔
176
            }
177

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

181
            return indexOfNextNameCharacter > indexOfPrevNameCharacter;
65,787,139✔
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,198,554,463✔
193
            super.isValidIdentifierName(mangledName) &&
1,247,603,333✔
194
            !MangledIdentifierNamesGenerator.reservedNamesSet.has(mangledName)
195
        );
196
    }
197

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

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

213
        this.lastMangledName = name;
14,785,309✔
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,482,947!
UNCOV
223
            return;
×
224
        }
225

226
        this.lastMangledNameForLabelMap.set(label, name);
26,482,947✔
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,065,653✔
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,198,554,427!
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,198,554,427✔
253
            const nameSequenceLength: number = nameSequence.length;
1,198,554,427✔
254
            const nameLength: number = name.length;
1,198,554,427✔
255

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

260
            let index: number = nameLength - 1;
1,198,554,427✔
261

262
            do {
1,198,554,427✔
263
                const character: string = name[index];
1,217,516,706✔
264
                const indexInSequence: number = nameSequence.indexOf(character);
1,217,516,706✔
265
                const lastNameSequenceIndex: number = nameSequenceLength - 1;
1,217,516,706✔
266

267
                if (indexInSequence !== lastNameSequenceIndex) {
1,217,516,706✔
268
                    const previousNamePart: string = name.slice(0, index);
1,198,452,728✔
269
                    const nextCharacter: string = nameSequence[indexInSequence + 1];
1,198,452,728✔
270
                    const zeroSequenceLength: number = nameLength - (index + 1);
1,198,452,728✔
271
                    const zeroSequenceCharacters: string = zeroSequence(zeroSequenceLength);
1,198,452,728✔
272

273
                    return previousNamePart + nextCharacter + zeroSequenceCharacters;
1,198,452,728✔
274
                }
275

276
                --index;
19,063,978✔
277
            } while (index >= 0);
278

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

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

284
        let identifierName: string = previousMangledName;
48,065,653✔
285
        let isValidIdentifierName: boolean;
286

287
        do {
48,065,653✔
288
            identifierName = generateNewMangledName(identifierName);
1,198,554,427✔
289
            isValidIdentifierName = validationFunction?.(identifierName) ?? this.isValidIdentifierName(identifierName);
1,198,554,427✔
290
        } while (!isValidIdentifierName);
291

292
        return identifierName;
48,065,653✔
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,399,005✔
301

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

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

312
        this.updatePreviousMangledName(identifierName);
1,399,005✔
313
        this.preserveName(identifierNameWithPrefix);
1,399,005✔
314

315
        return identifierNameWithPrefix;
1,399,005✔
316
    }
317

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

326
            if (!lastMangledName) {
9,322,390✔
327
                continue;
3,351,871✔
328
            }
329

330
            return lastMangledName;
5,970,519✔
331
        }
332

333
        return MangledIdentifierNamesGenerator.initMangledNameCharacter;
1,164,993✔
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,482,947✔
342

343
        return lastMangledName ?? MangledIdentifierNamesGenerator.initMangledNameCharacter;
26,482,947✔
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