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

javascript-obfuscator / javascript-obfuscator / 19907815758

03 Dec 2025 08:27PM UTC coverage: 97.319%. Remained the same
19907815758

push

github

sanex3339
Adjust precommit hook

1770 of 1891 branches covered (93.6%)

Branch coverage included in aggregate %.

5671 of 5755 relevant lines covered (98.54%)

34102965.58 hits per line

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

94.16
/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,562✔
47

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

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

75
        this.setUtils = setUtils;
50,562✔
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,106,026✔
87

88
        this.updatePreviousMangledName(identifierName);
13,106,026✔
89
        this.preserveName(identifierName);
13,106,026✔
90

91
        return identifierName;
13,106,026✔
92
    }
93

94
    /**
95
     * @param {number} nameLength
96
     * @returns {string}
97
     */
98
    public generateForGlobalScope(nameLength?: number): string {
99
        const prefix: string = this.options.identifiersPrefix ? `${this.options.identifiersPrefix}` : '';
1,507,058✔
100

101
        const identifierName: string = this.generateNewMangledName(
1,507,058✔
102
            this.lastMangledName,
103
            (newIdentifierName: string) => {
104
                const identifierNameWithPrefix: string = `${prefix}${newIdentifierName}`;
1,507,671✔
105

106
                return this.isValidIdentifierName(identifierNameWithPrefix);
1,507,671✔
107
            }
108
        );
109
        const identifierNameWithPrefix: string = `${prefix}${identifierName}`;
1,507,058✔
110

111
        this.updatePreviousMangledName(identifierName);
1,507,058✔
112
        this.preserveName(identifierNameWithPrefix);
1,507,058✔
113

114
        return identifierNameWithPrefix;
1,507,058✔
115
    }
116

117
    /**
118
     * @param {TNodeWithLexicalScope} lexicalScopeNode
119
     * @param {number} nameLength
120
     * @returns {string}
121
     */
122
    public generateForLexicalScope(lexicalScopeNode: TNodeWithLexicalScope, nameLength?: number): string {
123
        const lexicalScopes: TNodeWithLexicalScope[] = [
7,847,863✔
124
            lexicalScopeNode,
125
            ...NodeLexicalScopeUtils.getLexicalScopes(lexicalScopeNode)
126
        ];
127

128
        const lastMangledNameForScope: string = this.getLastMangledNameForScopes(lexicalScopes);
7,847,863✔
129
        const identifierName: string = this.generateNewMangledName(
7,847,863✔
130
            lastMangledNameForScope,
131
            (newIdentifierName: string) => this.isValidIdentifierNameInLexicalScopes(newIdentifierName, lexicalScopes)
20,503,427✔
132
        );
133

134
        this.lastMangledNameForScopeMap.set(lexicalScopeNode, identifierName);
7,847,863✔
135

136
        this.updatePreviousMangledName(identifierName);
7,847,863✔
137
        this.preserveNameForLexicalScope(identifierName, lexicalScopeNode);
7,847,863✔
138

139
        return identifierName;
7,847,863✔
140
    }
141

142
    /**
143
     * @param {string} label
144
     * @param {number} nameLength
145
     * @returns {string}
146
     */
147
    public generateForLabel(label: string, nameLength?: number): string {
148
        const lastMangledNameForLabel: string = this.getLastMangledNameForLabel(label);
27,507,249✔
149

150
        const identifierName: string = this.generateNewMangledName(lastMangledNameForLabel);
27,507,249✔
151

152
        this.updatePreviousMangledNameForLabel(identifierName, label, lastMangledNameForLabel);
27,507,249✔
153

154
        return identifierName;
27,507,249✔
155
    }
156

157
    /**
158
     * @param {string} nextName
159
     * @param {string} prevName
160
     * @returns {boolean}
161
     */
162
    // eslint-disable-next-line complexity
163
    public isIncrementedMangledName(nextName: string, prevName: string): boolean {
164
        if (nextName === prevName) {
73,968,220✔
165
            return false;
25,316✔
166
        }
167

168
        const nextNameLength: number = nextName.length;
73,942,904✔
169
        const prevNameLength: number = prevName.length;
73,942,904✔
170

171
        if (nextNameLength !== prevNameLength) {
73,942,904✔
172
            return nextNameLength > prevNameLength;
6,785,981✔
173
        }
174

175
        const nameSequence: string[] = this.getNameSequence();
67,156,923✔
176

177
        for (let i: number = 0; i < nextNameLength; i++) {
67,156,923✔
178
            const nextNameCharacter: string = nextName[i];
206,705,872✔
179
            const prevNameCharacter: string = prevName[i];
206,705,872✔
180

181
            if (nextNameCharacter === prevNameCharacter) {
206,705,872✔
182
                continue;
139,548,949✔
183
            }
184

185
            const indexOfNextNameCharacter: number = nameSequence.indexOf(nextNameCharacter);
67,156,923✔
186
            const indexOfPrevNameCharacter: number = nameSequence.indexOf(prevNameCharacter);
67,156,923✔
187

188
            return indexOfNextNameCharacter > indexOfPrevNameCharacter;
67,156,923✔
189
        }
190

191
        throw new Error('Something goes wrong during comparison of mangled names');
×
192
    }
193

194
    /**
195
     * @param {string} mangledName
196
     * @returns {boolean}
197
     */
198
    public override isValidIdentifierName(mangledName: string): boolean {
199
        return (
1,391,802,734✔
200
            super.isValidIdentifierName(mangledName) &&
1,442,864,403✔
201
            !MangledIdentifierNamesGenerator.reservedNamesSet.has(mangledName)
202
        );
203
    }
204

205
    /**
206
     * @returns {string[]}
207
     */
208
    protected getNameSequence(): string[] {
209
        return MangledIdentifierNamesGenerator.nameSequence;
725,315,643✔
210
    }
211

212
    /**
213
     * @param {string} name
214
     */
215
    protected updatePreviousMangledName(name: string): void {
216
        if (!this.isIncrementedMangledName(name, this.lastMangledName)) {
22,460,947✔
217
            return;
7,508,106✔
218
        }
219

220
        this.lastMangledName = name;
14,952,841✔
221
    }
222

223
    /**
224
     * @param {string} name
225
     * @param {string} label
226
     * @param {string} lastMangledNameForLabel
227
     */
228
    protected updatePreviousMangledNameForLabel(name: string, label: string, lastMangledNameForLabel: string): void {
229
        if (!this.isIncrementedMangledName(name, lastMangledNameForLabel)) {
27,507,249!
230
            return;
×
231
        }
232

233
        this.lastMangledNameForLabelMap.set(label, name);
27,507,249✔
234
    }
235

236
    /**
237
     * @param {string} previousMangledName
238
     * @param {(newIdentifierName: string) => boolean} validationFunction
239
     * @returns {string}
240
     */
241
    protected generateNewMangledName(
242
        previousMangledName: string,
243
        validationFunction?: (newIdentifierName: string) => boolean
244
    ): string {
245
        const generateNewMangledName = (name: string, regenerationAttempt: number = 0): string => {
49,968,196✔
246
            /**
247
             * Attempt to decrease amount of regeneration tries because of large preserved names set
248
             * When we reached the limit, we're trying to generate next mangled name based on the latest
249
             * preserved name
250
             */
251
            if (regenerationAttempt > MangledIdentifierNamesGenerator.maxRegenerationAttempts) {
1,391,802,698!
252
                const lastPreservedName = this.setUtils.getLastElement(this.preservedNamesSet);
×
253

254
                if (lastPreservedName) {
×
255
                    return this.generateNewMangledName(lastPreservedName);
×
256
                }
257
            }
258

259
            const nameSequence: string[] = this.getNameSequence();
1,391,802,698✔
260
            const nameSequenceLength: number = nameSequence.length;
1,391,802,698✔
261
            const nameLength: number = name.length;
1,391,802,698✔
262

263
            const zeroSequence: (num: number) => string = (num: number): string => {
1,391,802,698✔
264
                return '0'.repeat(num);
1,391,802,698✔
265
            };
266

267
            let index: number = nameLength - 1;
1,391,802,698✔
268

269
            do {
1,391,802,698✔
270
                const character: string = name[index];
1,413,882,152✔
271
                const indexInSequence: number = nameSequence.indexOf(character);
1,413,882,152✔
272
                const lastNameSequenceIndex: number = nameSequenceLength - 1;
1,413,882,152✔
273

274
                if (indexInSequence !== lastNameSequenceIndex) {
1,413,882,152✔
275
                    const previousNamePart: string = name.slice(0, index);
1,391,691,495✔
276
                    const nextCharacter: string = nameSequence[indexInSequence + 1];
1,391,691,495✔
277
                    const zeroSequenceLength: number = nameLength - (index + 1);
1,391,691,495✔
278
                    const zeroSequenceCharacters: string = zeroSequence(zeroSequenceLength);
1,391,691,495✔
279

280
                    return previousNamePart + nextCharacter + zeroSequenceCharacters;
1,391,691,495✔
281
                }
282

283
                --index;
22,190,657✔
284
            } while (index >= 0);
285

286
            const firstLetterCharacter: string = nameSequence[numbersString.length];
111,203✔
287

288
            return `${firstLetterCharacter}${zeroSequence(nameLength)}`;
111,203✔
289
        };
290

291
        let identifierName: string = previousMangledName;
49,968,196✔
292
        let isValidIdentifierName: boolean;
293

294
        do {
49,968,196✔
295
            identifierName = generateNewMangledName(identifierName);
1,391,802,698✔
296
            isValidIdentifierName = validationFunction?.(identifierName) ?? this.isValidIdentifierName(identifierName);
1,391,802,698✔
297
        } while (!isValidIdentifierName);
298

299
        return identifierName;
49,968,196✔
300
    }
301

302
    /**
303
     * @param {TNodeWithLexicalScope[]} lexicalScopeNodes
304
     * @returns {string}
305
     */
306
    private getLastMangledNameForScopes(lexicalScopeNodes: TNodeWithLexicalScope[]): string {
307
        for (const lexicalScope of lexicalScopeNodes) {
7,847,863✔
308
            const lastMangledName: string | null = this.lastMangledNameForScopeMap.get(lexicalScope) ?? null;
10,260,223✔
309

310
            if (!lastMangledName) {
10,260,223✔
311
                continue;
3,638,924✔
312
            }
313

314
            return lastMangledName;
6,621,299✔
315
        }
316

317
        return MangledIdentifierNamesGenerator.initMangledNameCharacter;
1,226,564✔
318
    }
319

320
    /**
321
     * @param {string} label
322
     * @returns {string}
323
     */
324
    private getLastMangledNameForLabel(label: string): string {
325
        const lastMangledName: string | null = this.lastMangledNameForLabelMap.get(label) ?? null;
27,507,249✔
326

327
        return lastMangledName ?? MangledIdentifierNamesGenerator.initMangledNameCharacter;
27,507,249✔
328
    }
329
}
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