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

javascript-obfuscator / javascript-obfuscator / 21408147530

27 Jan 2026 05:53PM UTC coverage: 96.664% (-0.01%) from 96.677%
21408147530

Pull #1375

github

web-flow
Merge d4ca39512 into 3848bca79
Pull Request #1375: Fixed `transformObjectKeys` incorrectly hoisting object literal outsie of loop

1892 of 2047 branches covered (92.43%)

Branch coverage included in aggregate %.

12 of 13 new or added lines in 2 files covered. (92.31%)

1 existing line in 1 file now uncovered.

5874 of 5987 relevant lines covered (98.11%)

33138563.09 hits per line

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

98.45
/src/node-transformers/converting-transformers/ObjectExpressionKeysTransformer.ts
1
import { inject, injectable } from 'inversify';
6✔
2
import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
6✔
3

4
import * as estraverse from '@javascript-obfuscator/estraverse';
6✔
5
import * as ESTree from 'estree';
6

7
import { TObjectExpressionExtractorFactory } from '../../types/container/node-transformers/TObjectExpressionExtractorFactory';
8

9
import { IOptions } from '../../interfaces/options/IOptions';
10
import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
11
import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
12

13
import { NodeTransformationStage } from '../../enums/node-transformers/NodeTransformationStage';
6✔
14
import { ObjectExpressionExtractor } from '../../enums/node-transformers/converting-transformers/properties-extractors/ObjectExpressionExtractor';
6✔
15

16
import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
6✔
17
import { NodeGuards } from '../../node/NodeGuards';
6✔
18
import { NodeStatementUtils } from '../../node/NodeStatementUtils';
6✔
19

20
@injectable()
21
export class ObjectExpressionKeysTransformer extends AbstractNodeTransformer {
6✔
22
    /**
23
     * @type {string}
24
     */
25
    private static readonly thisIdentifierName: string = 'this';
6✔
26

27
    /**
28
     * @type {ObjectExpressionExtractor[]}
29
     */
30
    private static readonly objectExpressionExtractorNames: ObjectExpressionExtractor[] = [
6✔
31
        ObjectExpressionExtractor.ObjectExpressionToVariableDeclarationExtractor,
32
        ObjectExpressionExtractor.BasePropertiesExtractor
33
    ];
34

35
    /**
36
     * @type {TObjectExpressionExtractorFactory}
37
     */
38
    private readonly objectExpressionExtractorFactory: TObjectExpressionExtractorFactory;
39

40
    /**
41
     * @param {TObjectExpressionExtractorFactory} objectExpressionExtractorFactory
42
     * @param {IRandomGenerator} randomGenerator
43
     * @param {IOptions} options
44
     */
45
    public constructor(
46
        @inject(ServiceIdentifiers.Factory__IObjectExpressionExtractor)
47
        objectExpressionExtractorFactory: TObjectExpressionExtractorFactory,
48
        @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
49
        @inject(ServiceIdentifiers.IOptions) options: IOptions
50
    ) {
51
        super(randomGenerator, options);
198,294✔
52

53
        this.objectExpressionExtractorFactory = objectExpressionExtractorFactory;
198,294✔
54
    }
55

56
    /**
57
     * Combined prohibition check result
58
     */
59
    private static checkProhibitedPatterns(
60
        objectExpressionNode: ESTree.ObjectExpression,
61
        objectExpressionHostNode: ESTree.Node
62
    ): { hasReferencedIdentifier: boolean; hasCallExpression: boolean } {
63
        const identifierNamesSet: Set<string> = new Set();
98,966✔
64

65
        let hasReferencedIdentifier: boolean = false;
98,966✔
66
        let hasCallExpression: boolean = false;
98,966✔
67
        let isInsideObjectExpression: boolean = false;
98,966✔
68

69
        estraverse.traverse(objectExpressionHostNode, {
98,966✔
70
            // eslint-disable-next-line complexity
71
            enter: (node: ESTree.Node): void | estraverse.VisitorOption => {
72
                if (node === objectExpressionNode) {
28,293,429✔
73
                    isInsideObjectExpression = true;
98,966✔
74
                }
75

76
                if (isInsideObjectExpression && !hasCallExpression) {
28,293,429✔
77
                    if (NodeGuards.isCallExpressionNode(node) || NodeGuards.isNewExpressionNode(node)) {
1,572,014✔
78
                        hasCallExpression = true;
51,423✔
79
                    }
80
                }
81

82
                if (NodeGuards.isIdentifierNode(node) || NodeGuards.isThisExpressionNode(node)) {
28,293,429✔
83
                    const identifierName: string = NodeGuards.isIdentifierNode(node)
8,305,678✔
84
                        ? node.name
8,305,678✔
85
                        : ObjectExpressionKeysTransformer.thisIdentifierName;
86

87
                    if (!isInsideObjectExpression) {
8,305,678✔
88
                        identifierNamesSet.add(identifierName);
1,144,855✔
89
                    } else if (identifierNamesSet.has(identifierName)) {
7,160,823✔
90
                        hasReferencedIdentifier = true;
13,185✔
91
                    }
92
                }
93

94
                if (hasReferencedIdentifier && hasCallExpression) {
28,293,429✔
95
                    return estraverse.VisitorOption.Break;
4,286✔
96
                }
97
            },
98
            leave: (node: ESTree.Node): void | estraverse.VisitorOption => {
99
                if (node === objectExpressionNode) {
28,132,676✔
100
                    isInsideObjectExpression = false;
94,680✔
101
                    if (hasReferencedIdentifier || hasCallExpression) {
94,680✔
102
                        return estraverse.VisitorOption.Break;
53,249✔
103
                    }
104
                }
105
            }
106
        });
107

108
        return { hasReferencedIdentifier, hasCallExpression };
98,966✔
109
    }
110

111
    /**
112
     * @param {ObjectExpression} objectExpressionNode
113
     * @param {Node} objectExpressionParentNode
114
     * @param {Statement} objectExpressionHostStatement
115
     * @returns {boolean}
116
     */
117
    private static isProhibitedObjectExpressionNode(
118
        objectExpressionNode: ESTree.ObjectExpression,
119
        objectExpressionParentNode: ESTree.Node,
120
        objectExpressionHostStatement: ESTree.Statement
121
    ): boolean {
122
        if (
99,290✔
123
            ObjectExpressionKeysTransformer.isProhibitedArrowFunctionExpression(
297,816✔
124
                objectExpressionNode,
125
                objectExpressionParentNode
126
            ) ||
127
            ObjectExpressionKeysTransformer.isProhibitedSequenceExpression(
128
                objectExpressionNode,
129
                objectExpressionHostStatement
130
            ) ||
131
            ObjectExpressionKeysTransformer.isProhibitedLoopBody(objectExpressionNode)
132
        ) {
133
            return true;
324✔
134
        }
135

136
        const { hasReferencedIdentifier, hasCallExpression } = ObjectExpressionKeysTransformer.checkProhibitedPatterns(
98,966✔
137
            objectExpressionNode,
138
            objectExpressionHostStatement
139
        );
140

141
        return hasReferencedIdentifier || hasCallExpression;
98,966✔
142
    }
143

144
    /**
145
     * @param {ObjectExpression} objectExpressionNode
146
     * @returns {boolean}
147
     */
148
    private static isProhibitedLoopBody(objectExpressionNode: ESTree.ObjectExpression): boolean {
149
        let currentNode: ESTree.Node | undefined = objectExpressionNode;
99,242✔
150

151
        while (currentNode) {
99,242✔
152
            const parentNode: ESTree.Node | undefined = currentNode.parentNode;
477,820✔
153

154
            if (!parentNode || parentNode === currentNode) {
477,820!
NEW
155
                break;
×
156
            }
157

158
            const isNonBlockLoopBody: boolean =
159
                NodeGuards.isLoopStatementNode(parentNode) &&
477,820✔
160
                parentNode.body === currentNode &&
161
                !NodeGuards.isBlockStatementNode(currentNode);
162

163
            if (isNonBlockLoopBody) {
477,820✔
164
                return true;
276✔
165
            }
166

167
            if (NodeGuards.isFunctionNode(parentNode) || NodeGuards.isProgramNode(parentNode)) {
477,544✔
168
                break;
98,966✔
169
            }
170

171
            currentNode = parentNode;
378,578✔
172
        }
173

174
        return false;
98,966✔
175
    }
176

177
    /**
178
     * @param {ObjectExpression} objectExpressionNode
179
     * @param {Node} objectExpressionNodeParentNode
180
     * @returns {boolean}
181
     */
182
    private static isProhibitedArrowFunctionExpression(
183
        objectExpressionNode: ESTree.ObjectExpression,
184
        objectExpressionNodeParentNode: ESTree.Node
185
    ): boolean {
186
        return (
99,290✔
187
            NodeGuards.isArrowFunctionExpressionNode(objectExpressionNodeParentNode) &&
99,296✔
188
            objectExpressionNodeParentNode.body === objectExpressionNode
189
        );
190
    }
191

192
    /**
193
     * @param {ObjectExpression} objectExpressionNode
194
     * @param {Node} objectExpressionHostNode
195
     * @returns {boolean}
196
     */
197
    private static isProhibitedSequenceExpression(
198
        objectExpressionNode: ESTree.ObjectExpression,
199
        objectExpressionHostNode: ESTree.Node
200
    ): boolean {
201
        return (
99,284✔
202
            NodeGuards.isExpressionStatementNode(objectExpressionHostNode) &&
145,177✔
203
            NodeGuards.isSequenceExpressionNode(objectExpressionHostNode.expression) &&
204
            objectExpressionHostNode.expression.expressions.some(
205
                (expressionNode: ESTree.Expression) =>
206
                    NodeGuards.isCallExpressionNode(expressionNode) && NodeGuards.isSuperNode(expressionNode.callee)
55,446✔
207
            )
208
        );
209
    }
210

211
    /**
212
     * @param {NodeTransformationStage} nodeTransformationStage
213
     * @returns {IVisitor | null}
214
     */
215
    public getVisitor(nodeTransformationStage: NodeTransformationStage): IVisitor | null {
216
        if (!this.options.transformObjectKeys) {
1,420,575✔
217
            return null;
1,405,989✔
218
        }
219

220
        switch (nodeTransformationStage) {
14,586✔
221
            case NodeTransformationStage.Converting:
14,586✔
222
                return {
3,192✔
223
                    leave: (node: ESTree.Node, parentNode: ESTree.Node | null): ESTree.Node | undefined => {
224
                        if (parentNode && NodeGuards.isObjectExpressionNode(node)) {
20,389,625✔
225
                            return this.transformNode(node, parentNode);
105,288✔
226
                        }
227
                    }
228
                };
229

230
            default:
231
                return null;
11,394✔
232
        }
233
    }
234

235
    /**
236
     * replaces:
237
     *     var object = {
238
     *          foo: 1,
239
     *          bar: 2
240
     *     };
241
     *
242
     * on:
243
     *     var _0xabc123 = {};
244
     *     _0xabc123['foo'] = 1;
245
     *     _0xabc123['bar'] = 2;
246
     *     var object = _0xabc123;
247
     *
248
     * @param {ObjectExpression} objectExpressionNode
249
     * @param {Node} parentNode
250
     * @returns {NodeGuards}
251
     */
252
    public transformNode(objectExpressionNode: ESTree.ObjectExpression, parentNode: ESTree.Node): ESTree.Node {
253
        if (!objectExpressionNode.properties.length) {
105,288✔
254
            return objectExpressionNode;
5,998✔
255
        }
256

257
        const hostStatement: ESTree.Statement = NodeStatementUtils.getRootStatementOfNode(objectExpressionNode);
99,290✔
258

259
        if (
99,290✔
260
            ObjectExpressionKeysTransformer.isProhibitedObjectExpressionNode(
261
                objectExpressionNode,
262
                parentNode,
263
                hostStatement
264
            )
265
        ) {
266
            return objectExpressionNode;
57,859✔
267
        }
268

269
        return this.applyObjectExpressionKeysExtractorsRecursive(objectExpressionNode, hostStatement, 0);
41,431✔
270
    }
271

272
    /**
273
     * @param {ObjectExpression} objectExpressionNode
274
     * @param {Statement} hostStatement
275
     * @param {number} extractorIndex
276
     * @returns {Node}
277
     */
278
    private applyObjectExpressionKeysExtractorsRecursive(
279
        objectExpressionNode: ESTree.ObjectExpression,
280
        hostStatement: ESTree.Statement,
281
        extractorIndex: number
282
    ): ESTree.Node {
283
        const objectExpressionExtractorNames = ObjectExpressionKeysTransformer.objectExpressionExtractorNames;
124,293✔
284

285
        if (extractorIndex >= objectExpressionExtractorNames.length) {
124,293✔
286
            return objectExpressionNode;
41,431✔
287
        }
288

289
        const objectExpressionExtractor: ObjectExpressionExtractor = objectExpressionExtractorNames[extractorIndex];
82,862✔
290

291
        const {
292
            nodeToReplace,
293
            objectExpressionHostStatement: newObjectExpressionHostStatement,
294
            objectExpressionNode: newObjectExpressionNode
295
        } = this.objectExpressionExtractorFactory(objectExpressionExtractor).extract(
82,862✔
296
            objectExpressionNode,
297
            hostStatement
298
        );
299

300
        this.applyObjectExpressionKeysExtractorsRecursive(
82,862✔
301
            newObjectExpressionNode,
302
            newObjectExpressionHostStatement,
303
            extractorIndex + 1
304
        );
305

306
        return nodeToReplace;
82,862✔
307
    }
308
}
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