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

overlookmotel / livepack / 6049514684

01 Sep 2023 12:34PM UTC coverage: 89.798%. Remained the same
6049514684

push

github

overlookmotel
Fix mangling function names [fix]

Fixes #494.

2715 of 2927 branches covered (0.0%)

Branch coverage included in aggregate %.

38 of 38 new or added lines in 6 files covered. (100.0%)

12451 of 13962 relevant lines covered (89.18%)

6507.89 hits per line

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

96.69
/lib/serialize/parseFunction.js
1
/* --------------------
31✔
2
 * livepack module
31✔
3
 * Parse function code
31✔
4
 * ------------------*/
31✔
5

31✔
6
'use strict';
31✔
7

31✔
8
// Modules
31✔
9
const pathJoin = require('path').join,
31✔
10
        {isString} = require('is-it-type'),
31✔
11
        mapValues = require('lodash/mapValues'),
31✔
12
        t = require('@babel/types');
31✔
13

31✔
14
// Imports
31✔
15
const {isJsIdentifier, isNumberKey, setAddFrom} = require('./utils.js'),
31✔
16
        {
31✔
17
                isReservedVarName, createArrayOrPush, combineArraysWithDedup,
31✔
18
                getProp, getProps, setProp, traverseAll
31✔
19
        } = require('../shared/functions.js'),
31✔
20
        {
31✔
21
                SUPER_CALL, SUPER_EXPRESSION, CONST_VIOLATION_CONST, CONST_VIOLATION_FUNCTION_SILENT
31✔
22
        } = require('../shared/constants.js'),
31✔
23
        assertBug = require('../shared/assertBug.js');
31✔
24

31✔
25
// Constants
31✔
26
const RUNTIME_DIR_PATH = pathJoin(__dirname, '../runtime/');
31✔
27

31✔
28
// Exports
31✔
29

31✔
30
/**
31✔
31
 * Assemble AST for function code, identify Identifier nodes referring to internal/external variables,
31✔
32
 * replace const violations, transpile `super`, get other info about function.
31✔
33
 * Most of this information is pre-prepared by instrumentation, just needs some late processing here.
31✔
34
 *
31✔
35
 * @this {Object} Serializer
31✔
36
 * @param {Function} fn - Function
31✔
37
 * @param {number} fnId - Function ID
31✔
38
 * @param {Function} getFunctionInfo - Function info getter function
31✔
39
 * @param {boolean} isClass - `true` if is a class
31✔
40
 * @param {boolean} isAsync - `true` if is an async function
31✔
41
 * @param {boolean} isGenerator - `true` if is a generator function
31✔
42
 * @param {string} filename - File path
31✔
43
 * @returns {Object} - Function definition object with props:
31✔
44
 *   {Object} .node - AST node for function
31✔
45
 *   {Map} .scopeDefs - Scope definitions map, keyed by block ID
31✔
46
 *   {Object} .externalVars - Object keyed by var name, values are arrays of identifier nodes
31✔
47
 *   {Object} .internalVars - Object keyed by var name, values are arrays of identifier nodes
31✔
48
 *   {Set<string>} .globalVarNames - Set of names of global vars used
31✔
49
 *   {Set<string>} .functionNames - Set of function names used
31✔
50
 *   {string} .name - `.name` of created function
31✔
51
 *   {number} .numParams - `.length` of created function
31✔
52
 *   {boolean} .isClass - `true` if is class
31✔
53
 *   {boolean} .isAsync - `true` if is async function
31✔
54
 *   {boolean} .isGenerator - `true` if is generator
31✔
55
 *   {boolean} .isArrow - `true` if is arrow function
31✔
56
 *   {boolean} .isMethod - `true` if is a method
31✔
57
 *   {boolean|null} .isStrict - `true` if is strict mode, `false` if sloppy, `null` if indeterminate
31✔
58
 *     (classes and runtime functions are indeterminate)
31✔
59
 *   {boolean} .containsEval - `true` if contains direct `eval()`
31✔
60
 *   {Array<string>|undefined} .argNames - Array of `arguments` var names
31✔
61
 * @throws {Error} - If function contains `import`
31✔
62
 */
31✔
63
module.exports = function parseFunction(
31✔
64
        fn, fnId, getFunctionInfo, isClass, isAsync, isGenerator, filename
14,632✔
65
) {
14,632✔
66
        // Get function info and AST from info getter function
14,632✔
67
        const [fnInfoJson, getChildFnInfos, getSources] = getFunctionInfo();
14,632✔
68
        const fnInfo = JSON.parse(fnInfoJson);
14,632✔
69

14,632✔
70
        // Throw error if function contains `import()`
14,632✔
71
        if (fnInfo.containsImport) {
14,632!
72
                const fnName = Object.getOwnPropertyDescriptor(fn, 'name')?.value,
×
73
                        fnDesc = (isString(fnName) && fnName !== '') ? `function '${fnName}'` : 'anonymous function';
×
74
                throw new Error(`Cannot serialize function containing \`import\` (${fnDesc} in '${filename}')`);
×
75
        }
×
76

14,632✔
77
        // Assemble scope definitions
14,632✔
78
        const scopeDefs = new Map(),
14,632✔
79
                externalVars = Object.create(null);
14,632✔
80
        for (const {blockId, blockName, vars} of fnInfo.scopes) {
14,632✔
81
                scopeDefs.set(blockId, {
8,818✔
82
                        blockName,
8,818✔
83
                        vars: mapValues(vars, (varProps, varName) => {
8,818✔
84
                                externalVars[varName] = [];
11,842✔
85
                                return {isReadFrom: !!varProps.isReadFrom, isAssignedTo: !!varProps.isAssignedTo};
11,842✔
86
                        })
8,818✔
87
                });
8,818✔
88
        }
8,818✔
89

14,632✔
90
        // Add child functions into AST, get external/internal var nodes, and get amendments to be made
14,632✔
91
        // (const violations / incidences of `super` to be transpiled)
14,632✔
92
        const internalVarsWithBlockIds = Object.create(null),
14,632✔
93
                globalVarNames = new Set(),
14,632✔
94
                functionNames = new Set(),
14,632✔
95
                amendments = [];
14,632✔
96
        resolveFunctionInfo(
14,632✔
97
                fnInfo, getChildFnInfos, false, fnId, scopeDefs,
14,632✔
98
                externalVars, internalVarsWithBlockIds, globalVarNames, functionNames, amendments
14,632✔
99
        );
14,632✔
100

14,632✔
101
        const internalVars = Object.fromEntries(
14,632✔
102
                Object.entries(internalVarsWithBlockIds).map(([varName, {nodes}]) => [varName, nodes])
14,632✔
103
        );
14,632✔
104

14,632✔
105
        let node = fnInfo.ast;
14,632✔
106

14,632✔
107
        // If source maps enabled, add source files to `sourceFiles` map and set `loc.filename` for all nodes.
14,632✔
108
        // Create `copyLoc()` function to copy location info for AST nodes befing replaced.
14,632✔
109
        let copyLoc;
14,632✔
110
        if (this.options.sourceMaps) {
14,632✔
111
                const {filesHaveSourcesFor} = this;
14,590✔
112
                if (!filesHaveSourcesFor.has(filename)) {
14,590✔
113
                        Object.assign(this.sourceFiles, JSON.parse(getSources()));
9,307✔
114
                        filesHaveSourcesFor.add(filename);
9,307✔
115
                }
9,307✔
116

14,590✔
117
                traverseAll(node, ({loc}) => {
14,590✔
118
                        if (loc && !loc.filename) loc.filename = filename;
104,960✔
119
                });
14,590✔
120

14,590✔
121
                copyLoc = (destNode, srcNode) => {
14,590✔
122
                        destNode.start = srcNode.start;
4,200✔
123
                        destNode.end = srcNode.end;
4,200✔
124
                        destNode.loc = srcNode.loc;
4,200✔
125
                        return destNode;
4,200✔
126
                };
14,590✔
127
        } else {
14,632✔
128
                copyLoc = destNode => destNode;
42✔
129
        }
42✔
130

14,632✔
131
        // Get whether strict mode and remove use strict directive
14,632✔
132
        let isStrict;
14,632✔
133
        if (!isClass) {
14,632✔
134
                isStrict = !!fnInfo.isStrict;
12,712✔
135
                if (isStrict) {
12,712✔
136
                        removeUseStrictDirective(node);
10,101✔
137
                } else if (filename.startsWith(RUNTIME_DIR_PATH)) {
12,712✔
138
                        // Runtime functions are treated as indeterminate strict/sloppy mode unless explicitly strict
488✔
139
                        isStrict = null;
488✔
140
                }
488✔
141
        }
12,712✔
142

14,632✔
143
        // Create `super` var node if required
14,632✔
144
        let superVarNode;
14,632✔
145
        if (externalVars.super) {
14,632✔
146
                superVarNode = t.identifier('super');
1,312✔
147
                externalVars.super[0] = superVarNode;
1,312✔
148
                globalVarNames.add('Reflect');
1,312✔
149
                globalVarNames.add('Object');
1,312✔
150
        }
1,312✔
151

14,632✔
152
        // Conform function/class, get function name
14,632✔
153
        let isMethod, name, thisVarNode, constructorStatementNodes, firstSuperStatementIndex,
14,632✔
154
                paramNodes = node.params;
14,632✔
155
        const {type} = node,
14,632✔
156
                isArrow = type === 'ArrowFunctionExpression',
14,632✔
157
                containsEval = !!fnInfo.containsEval;
14,632✔
158
        if (isArrow) {
14,632✔
159
                // Arrow function
5,668✔
160
                name = '';
5,668✔
161
                isMethod = false;
5,668✔
162

5,668✔
163
                // If contains `super`, needs `this` too
5,668✔
164
                if (externalVars.super) {
5,668✔
165
                        thisVarNode = t.identifier('this');
128✔
166
                        externalVars.this.push(thisVarNode);
128✔
167
                }
128✔
168
        } else {
14,632✔
169
                // Class, method or function declaration/expression.
8,964✔
170
                // Get function name.
8,964✔
171
                name = (Object.getOwnPropertyDescriptor(fn, 'name') || {}).value;
8,964✔
172
                if (!isString(name)) name = '';
8,964✔
173

8,964✔
174
                // Identify if method and convert class method to object method
8,964✔
175
                if (type === 'ClassMethod') {
8,964✔
176
                        node.type = 'ObjectMethod';
1,080✔
177
                        node.static = undefined;
1,080✔
178
                        isMethod = true;
1,080✔
179
                } else if (type === 'ObjectMethod') {
8,964✔
180
                        isMethod = true;
992✔
181
                }
992✔
182

8,964✔
183
                if (isMethod) {
8,964✔
184
                        // Class/object method.
2,072✔
185
                        // Convert getter/setter to plain method, and disable computed keys.
2,072✔
186
                        node.kind = 'method';
2,072✔
187
                        node.computed = false;
2,072✔
188

2,072✔
189
                        // Wrap in object.
2,072✔
190
                        // NB Must be defined as method, to avoid it having prototype.
2,072✔
191
                        let keyNode, accessComputed;
2,072✔
192
                        if (!name) {
2,072!
193
                                name = 'a';
×
194
                                keyNode = t.identifier('a');
×
195
                                accessComputed = false;
×
196
                        } else if (isJsIdentifier(name)) {
2,072✔
197
                                keyNode = t.identifier(name);
1,576✔
198
                                accessComputed = false;
1,576✔
199
                        } else {
2,072✔
200
                                keyNode = isNumberKey(name) ? t.numericLiteral(name * 1) : t.stringLiteral(name);
496✔
201
                                accessComputed = true;
496✔
202
                        }
496✔
203

2,072✔
204
                        node.key = copyLoc(keyNode, node.key || {});
2,072!
205
                        node = t.memberExpression(t.objectExpression([node]), keyNode, accessComputed);
2,072✔
206

2,072✔
207
                        // `this` within method is actual `this`
2,072✔
208
                        thisVarNode = t.thisExpression();
2,072✔
209
                } else {
8,964✔
210
                        // Set function name
6,892✔
211
                        const idNode = node.id;
6,892✔
212
                        if (containsEval) {
6,892✔
213
                                // Function contains `eval()`.
464✔
214
                                // Cannot change name from original as would create a new var in scope of `eval()`.
464✔
215
                                // If is a named function expression, name must be retained, to maintain const violation
464✔
216
                                // error behavior (error in strict mode, silent failure in sloppy mode).
464✔
217
                                // If is a named function declaration, name must be removed to allow read/write access
464✔
218
                                // to external var holding function in upper scope.
464✔
219
                                if (idNode) {
464✔
220
                                        name = idNode.name;
80✔
221
                                        if (externalVars[name]) name = '';
80✔
222
                                } else {
464✔
223
                                        name = '';
384✔
224
                                }
384✔
225
                        } else if (
6,892✔
226
                                name && (!isJsIdentifier(name) || isReservedVarName(name) || globalVarNames.has(name))
6,428✔
227
                        ) {
6,428✔
228
                                // Name cannot be used as is illegal, or would block access to a global var
106✔
229
                                name = '';
106✔
230
                        }
106✔
231

6,892✔
232
                        if (!name) {
6,892✔
233
                                node.id = null;
3,084✔
234
                        } else {
6,892✔
235
                                if (!idNode || idNode.name !== name) {
3,808✔
236
                                        node.id = t.identifier(name);
426✔
237
                                        if (idNode) copyLoc(node.id, idNode);
426✔
238
                                }
426✔
239
                                functionNames.add(name);
3,808✔
240
                        }
3,808✔
241

6,892✔
242
                        if (isClass) {
6,892✔
243
                                // Class
1,920✔
244
                                if (type === 'ClassDeclaration') node.type = 'ClassExpression';
1,920✔
245

1,920✔
246
                                // Classes have indeterminate strict/sloppy status - never require conforming to strict/sloppy
1,920✔
247
                                // as they're automatically strict.
1,920✔
248
                                isStrict = null;
1,920✔
249

1,920✔
250
                                // Remove all members except constructor and prototype properties
1,920✔
251
                                let constructorNode;
1,920✔
252
                                const classBodyNode = node.body;
1,920✔
253
                                const memberNodes = classBodyNode.body = classBodyNode.body.filter((memberNode) => {
1,920✔
254
                                        if (!memberNode) return false;
1,800✔
255
                                        const memberType = memberNode.type;
736✔
256
                                        if (memberType === 'ClassMethod') {
736✔
257
                                                constructorNode = memberNode;
736✔
258
                                                return true;
736✔
259
                                        }
736✔
260
                                        if (memberType === 'StaticBlock') return false;
×
261
                                        return !memberNode.static;
×
262
                                });
1,920✔
263
                                paramNodes = constructorNode ? constructorNode.params : [];
1,920✔
264

1,920✔
265
                                if (fnInfo.hasSuperClass) {
1,920✔
266
                                        // Remove `extends`
672✔
267
                                        node.superClass = null;
672✔
268

672✔
269
                                        // Add constructor if none present
672✔
270
                                        if (!constructorNode) {
672✔
271
                                                // `constructor(...args) {
496✔
272
                                                //   return Reflect.construct(Object.getPrototypeOf(super$0), args, super$0);
496✔
273
                                                // }`
496✔
274
                                                const argsVarNode = t.identifier('args');
496✔
275
                                                internalVars.args = [argsVarNode];
496✔
276
                                                // TODO: Can using `.unshift()` interfere with replacing const violations further on?
496✔
277
                                                // Indexes of any class properties will be altered.
496✔
278
                                                memberNodes.unshift(t.classMethod(
496✔
279
                                                        'constructor',
496✔
280
                                                        t.identifier('constructor'),
496✔
281
                                                        [t.restElement(argsVarNode)],
496✔
282
                                                        t.blockStatement([
496✔
283
                                                                t.returnStatement(
496✔
284
                                                                        t.callExpression(
496✔
285
                                                                                t.memberExpression(t.identifier('Reflect'), t.identifier('construct')),
496✔
286
                                                                                [
496✔
287
                                                                                        t.callExpression(
496✔
288
                                                                                                t.memberExpression(t.identifier('Object'), t.identifier('getPrototypeOf')),
496✔
289
                                                                                                [superVarNode]
496✔
290
                                                                                        ),
496✔
291
                                                                                        argsVarNode,
496✔
292
                                                                                        superVarNode
496✔
293
                                                                                ]
496✔
294
                                                                        )
496✔
295
                                                                )
496✔
296
                                                        ])
496✔
297
                                                ));
496✔
298
                                        } else {
672✔
299
                                                // Class has constructor - prepare for transpiling `super()`
176✔
300
                                                const thisInternalVars = internalVars.this;
176✔
301
                                                if (thisInternalVars) {
176✔
302
                                                        // Constructor contains `this` or `super` expression (e.g. `super.foo()`).
128✔
303
                                                        // Var for `this$0` is going to be needed.
128✔
304
                                                        thisVarNode = t.identifier('this');
128✔
305
                                                        thisInternalVars.push(thisVarNode);
128✔
306
                                                }
128✔
307

176✔
308
                                                firstSuperStatementIndex = fnInfo.firstSuperStatementIndex;
176✔
309
                                                constructorStatementNodes = constructorNode.body.body;
176✔
310
                                        }
176✔
311
                                } else {
1,920✔
312
                                        // Class which doesn't extend a super class. `this` within constructor is actual `this`.
1,248✔
313
                                        thisVarNode = t.thisExpression();
1,248✔
314
                                }
1,248✔
315
                        } else {
6,892✔
316
                                // Function declaration/expression. `this` is actual `this`.
4,972✔
317
                                if (type === 'FunctionDeclaration') node.type = 'FunctionExpression';
4,972✔
318
                                thisVarNode = t.thisExpression();
4,972✔
319
                        }
4,972✔
320
                }
6,892✔
321
        }
8,964✔
322

14,632✔
323
        // Replace const violations + `super`.
14,632✔
324
        // Amendments are in reverse order:
14,632✔
325
        // - Nested functions before their parent.
14,632✔
326
        // - Within a function, deeper nested expressions/statements before their parent.
14,632✔
327
        // - Within a function, later statements before earlier statements.
14,632✔
328
        // Reverse order ensures correct handling of nested amendments
14,632✔
329
        // e.g. 2 const violations `c = c2 = 1` or const violation + super `super.foo(c = 1)`.
14,632✔
330
        const superIsProto = isClass || fnInfo.superIsProto;
14,632✔
331
        for (const amendment of amendments) {
14,632✔
332
                const {type: amendmentType, trail, trailNodes} = amendment;
1,232✔
333
                if (amendmentType === SUPER_CALL) {
1,232✔
334
                        replaceSuperCall(
208✔
335
                                trail, trailNodes, superVarNode, thisVarNode, firstSuperStatementIndex, copyLoc
208✔
336
                        );
208✔
337
                } else if (amendmentType === SUPER_EXPRESSION) {
1,232✔
338
                        replaceSuperExpression(
648✔
339
                                trail, trailNodes, superVarNode, thisVarNode, superIsProto, internalVars, copyLoc
648✔
340
                        );
648✔
341
                } else if (amendmentType === CONST_VIOLATION_FUNCTION_SILENT) {
1,024✔
342
                        replaceConstViolation(trail, trailNodes, true, internalVars, copyLoc);
8✔
343
                } else {
376✔
344
                        // amendmentType === CONST_VIOLATION_CONST || amendmentType === CONST_VIOLATION_FUNCTION_THROWING
368✔
345
                        replaceConstViolation(trail, trailNodes, false, internalVars, copyLoc);
368✔
346
                }
368✔
347
        }
1,232✔
348

14,632✔
349
        // If is class extending a super class, insert `let this$0;` and `return this$0;` statements
14,632✔
350
        // if required
14,632✔
351
        if (constructorStatementNodes && thisVarNode) {
14,632✔
352
                // Add `let this$0` at start of constructor
128✔
353
                if (firstSuperStatementIndex === undefined) {
128✔
354
                        constructorStatementNodes.unshift(
24✔
355
                                t.variableDeclaration('let', [t.variableDeclarator(thisVarNode, null)])
24✔
356
                        );
24✔
357
                }
24✔
358

128✔
359
                // Add `return this$0` to end of constructor
128✔
360
                if (!fnInfo.returnsSuper) constructorStatementNodes.push(t.returnStatement(thisVarNode));
128✔
361
        }
128✔
362

14,632✔
363
        // Determine what value of `fn.length` will be
14,632✔
364
        let numParams = 0;
14,632✔
365
        for (const {type: paramNodeType} of paramNodes) {
14,632✔
366
                if (paramNodeType === 'RestElement' || paramNodeType === 'AssignmentPattern') break;
2,775✔
367
                numParams++;
2,495✔
368
        }
2,495✔
369

14,632✔
370
        // Return function definition object
14,632✔
371
        return {
14,632✔
372
                node,
14,632✔
373
                scopeDefs,
14,632✔
374
                externalVars,
14,632✔
375
                internalVars,
14,632✔
376
                globalVarNames,
14,632✔
377
                functionNames,
14,632✔
378
                name,
14,632✔
379
                numParams,
14,632✔
380
                isClass,
14,632✔
381
                isAsync,
14,632✔
382
                isGenerator,
14,632✔
383
                isArrow,
14,632✔
384
                isMethod,
14,632✔
385
                isStrict,
14,632✔
386
                containsEval,
14,632✔
387
                argNames: fnInfo.argNames
14,632✔
388
        };
14,632✔
389
};
31✔
390

31✔
391
/**
31✔
392
 * Remove 'use strict' directive from function.
31✔
393
 * @param {Object} fnNode - Function AST node
31✔
394
 * @returns {undefined}
31✔
395
 */
31✔
396
function removeUseStrictDirective(fnNode) {
10,101✔
397
        const bodyNode = fnNode.body;
10,101✔
398
        if (bodyNode.type !== 'BlockStatement') return;
10,101✔
399
        const directiveNodes = bodyNode.directives;
7,124✔
400
        if (directiveNodes.length === 0) return;
9,390✔
401
        const index = directiveNodes.findIndex(directiveNode => directiveNode.value.value === 'use strict');
481✔
402
        if (index === -1) return;
864✔
403

401✔
404
        // Relocate any comments attached to directive
401✔
405
        const directiveNode = directiveNodes[index];
401✔
406
        const commentNodes = [
401✔
407
                ...(directiveNode.leadingComments || []),
10,101✔
408
                ...(directiveNode.trailingComments || [])
10,101✔
409
        ];
10,101✔
410
        if (commentNodes.length !== 0) {
10,101✔
411
                const statementNodes = bodyNode.body;
8✔
412
                if (index !== 0) {
8!
413
                        directiveNodes[index - 1].trailingComments = combineArraysWithDedup(
×
414
                                directiveNodes[index - 1].trailingComments, commentNodes
×
415
                        );
×
416
                } else if (index !== directiveNodes.length - 1) {
8!
417
                        directiveNodes[index + 1].leadingComments = combineArraysWithDedup(
×
418
                                commentNodes, directiveNodes[index + 1].leadingComments
×
419
                        );
×
420
                } else if (statementNodes.length !== 0) {
8✔
421
                        statementNodes[0].leadingComments = combineArraysWithDedup(
8✔
422
                                commentNodes, statementNodes[0].leadingComments
8✔
423
                        );
8✔
424
                } else {
8!
425
                        bodyNode.innerComments = combineArraysWithDedup(bodyNode.innerComments, commentNodes);
×
426
                }
×
427
        }
8✔
428

401✔
429
        // Remove directive
401✔
430
        directiveNodes.splice(index, 1);
401✔
431
}
10,101✔
432

31✔
433
/**
31✔
434
 * Replace const violation expression with expression which throws.
31✔
435
 * e.g. `c = f()` -> `f(), (() => {const c = 0; c = 0;})()`
31✔
436
 * If is silent violation (function expression name assigned to in sloppy mode),
31✔
437
 * just remove the assignment.
31✔
438
 * e.g. `c += f()` -> `c + f()`
31✔
439
 *
31✔
440
 * @param {Array<string|number>} trail - Keys trail
31✔
441
 * @param {Array<Object>} trailNodes - Trail nodes
31✔
442
 * @param {boolean} isSilent - `true` if is a silent violation (i.e. doesn't throw)
31✔
443
 * @param {Object} internalVars - Map of internal vars, keyed by var name
31✔
444
 * @param {Function} copyLoc - Function to copy location from old to new AST node
31✔
445
 * @returns {undefined}
31✔
446
 */
31✔
447
function replaceConstViolation(trail, trailNodes, isSilent, internalVars, copyLoc) {
376✔
448
        const trailLen = trail.length,
376✔
449
                node = trailNodes[trailLen],
376✔
450
                parentNode = trailNodes[trailLen - 1];
376✔
451
        function replaceParent(replacementNode) {
376✔
452
                trailNodes[trailLen - 2][trail[trailLen - 2]] = copyLoc(replacementNode, parentNode);
288✔
453
        }
288✔
454

376✔
455
        const {type} = parentNode;
376✔
456
        if (type === 'AssignmentExpression') {
376✔
457
                const {operator} = parentNode;
240✔
458
                if (operator === '=') {
240✔
459
                        // `c = x` -> `x, throw()`
208✔
460
                        replaceParent(
208✔
461
                                createConstViolationThrowNode(parentNode.right, node.name, isSilent, internalVars)
208✔
462
                        );
208✔
463
                } else if (['&&=', '||=', '??='].includes(operator)) {
240✔
464
                        // `c &&= x` -> `c && (x, throw())`
16✔
465
                        replaceParent(
16✔
466
                                t.logicalExpression(
16✔
467
                                        operator.slice(0, -1),
16✔
468
                                        node,
16✔
469
                                        createConstViolationThrowNode(parentNode.right, node.name, isSilent, internalVars)
16✔
470
                                )
16✔
471
                        );
16✔
472
                } else {
16✔
473
                        // Other assignment operator e.g. `+=`, `-=`, `>>>=`
16✔
474
                        // `c += x` -> `c + x, throw()`
16✔
475
                        replaceParent(
16✔
476
                                createConstViolationThrowNode(
16✔
477
                                        t.binaryExpression(operator.slice(0, -1), node, parentNode.right),
16✔
478
                                        node.name, isSilent, internalVars
16✔
479
                                )
16✔
480
                        );
16✔
481
                }
16✔
482
        } else if (type === 'UpdateExpression') {
376✔
483
                // `c++` -> `+c, throw()`
16✔
484
                replaceParent(
16✔
485
                        createConstViolationThrowNode(t.unaryExpression('+', node), node.name, isSilent, internalVars)
16✔
486
                );
16✔
487
        } else if (type === 'AssignmentPattern') {
136✔
488
                // `[c = x] = []`
32✔
489
                // -> `[(e => ({set a(v) {v === void 0 && e(); const c = 0; c = 0;}}))(() => x).a] = []`
32✔
490
                // `{c = x} = {}`
32✔
491
                // -> `{c: (e => ({set a(v) {v === void 0 && e(); const c = 0; c = 0;}}))(() => x).a} = {}`
32✔
492
                replaceParent(
32✔
493
                        createConstViolationThrowPatternNode(node.name, parentNode.right, isSilent, internalVars)
32✔
494
                );
32✔
495
        } else {
120✔
496
                assertBug(
88✔
497
                        type === 'ForOfStatement' || type === 'ForInStatement'
88✔
498
                                || type === 'ObjectProperty' || type === 'RestElement'
88✔
499
                                || trailNodes[trailLen - 2].type === 'ArrayPattern',
88✔
500
                        `Unexpected const violation node type ${type}`
88✔
501
                );
88✔
502

88✔
503
                // `for (c of [1]) { foo(); }`
88✔
504
                // -> `for ({ set a(v) { const c = 0; c = 0; } }.a of [1]) { foo(); }`
88✔
505
                // `{x: c} = {}` -> `{x: {set a(v) { const c = 0; c = 0; }}.a} = {}`
88✔
506
                // `[...c] = []` -> `[...{set a(v) { const c = 0; c = 0; }}.a] = []`
88✔
507
                // `{...c} = {}` -> `{...{set a(v) { const c = 0; c = 0; }}.a} = {}`
88✔
508
                // `[c] = [1]` -> `[{set a(v) { const c = 0; c = 0; }}.a] = [1]`
88✔
509
                parentNode[trail[trailLen - 1]] = copyLoc(
88✔
510
                        createConstViolationThrowAssignNode(node.name, isSilent, internalVars), node
88✔
511
                );
88✔
512
        }
88✔
513
}
376✔
514

31✔
515
/**
31✔
516
 * Create expression node which throws 'Assignment to constant variable' TypeError.
31✔
517
 * @param {Object} node - AST node to wrap
31✔
518
 * @param {string} name - Var name
31✔
519
 * @param {boolean} isSilent - `true` if violation does not throw
31✔
520
 * @param {Object} internalVars - Map of internal vars, keyed by var name
31✔
521
 * @returns {Object} - AST Node object
31✔
522
 */
31✔
523
function createConstViolationThrowNode(node, name, isSilent, internalVars) {
256✔
524
        // If silent failure, return node unchanged
256✔
525
        if (isSilent) return node;
256✔
526

248✔
527
        // `(x, (() => { const c = 0; c = 0; })())`
248✔
528
        return t.sequenceExpression([
248✔
529
                node,
248✔
530
                t.callExpression(
248✔
531
                        t.arrowFunctionExpression([], createConstViolationThrowBlockNode(name, internalVars)), []
248✔
532
                )
248✔
533
        ]);
248✔
534
}
256✔
535

31✔
536
/**
31✔
537
 * Create expression node which throws when assigned to.
31✔
538
 * @param {string} name - Var name
31✔
539
 * @param {boolean} isSilent - `true` if violation does not throw
31✔
540
 * @param {Object} internalVars - Map of internal vars, keyed by var name
31✔
541
 * @returns {Object} - AST Node object
31✔
542
 */
31✔
543
function createConstViolationThrowAssignNode(name, isSilent, internalVars) {
88✔
544
        // If silent failure, return expression which assignment to is a no-op
88✔
545
        if (isSilent) {
88!
546
                // `{}.a`
×
547
                return t.memberExpression(t.objectExpression([]), t.identifier('a'));
×
548
        }
×
549

88✔
550
        // `{ set a(v) { const c = 0; c = 0; } }.a`
88✔
551
        return t.memberExpression(
88✔
552
                t.objectExpression([
88✔
553
                        t.objectMethod(
88✔
554
                                'set',
88✔
555
                                t.identifier('a'),
88✔
556
                                [createTempVarNode(name === 'v' ? '_v' : 'v', internalVars)],
88!
557
                                createConstViolationThrowBlockNode(name, internalVars)
88✔
558
                        )
88✔
559
                ]),
88✔
560
                t.identifier('a')
88✔
561
        );
88✔
562
}
88✔
563

31✔
564
/**
31✔
565
 * Create expression node to replace an assignment pattern which:
31✔
566
 * 1. Evaluates right hand side of pattern expression only if incoming assignment value is `undefined`
31✔
567
 * 2. Throws when assigned to
31✔
568
 *
31✔
569
 * @param {string} name - Var name
31✔
570
 * @param {Object} rightNode - AST Node for right-hand side of assignment pattern
31✔
571
 * @param {boolean} isSilent - `true` if violation does not throw
31✔
572
 * @param {Object} internalVars - Map of internal vars, keyed by var name
31✔
573
 * @returns {Object} - AST Node object
31✔
574
 */
31✔
575
function createConstViolationThrowPatternNode(name, rightNode, isSilent, internalVars) {
32✔
576
        const valueVarNode = createTempVarNode(name === 'v' ? '_v' : 'v', internalVars),
32!
577
                rightGetterVarNode = createTempVarNode(name === 'e' ? '_e' : 'e', internalVars);
32!
578

32✔
579
        // `v === void 0 && e()`
32✔
580
        const statementNodes = [t.expressionStatement(t.logicalExpression(
32✔
581
                '&&',
32✔
582
                t.binaryExpression('===', valueVarNode, t.unaryExpression('void', t.numericLiteral(0))),
32✔
583
                t.callExpression(rightGetterVarNode, [])
32✔
584
        ))];
32✔
585

32✔
586
        if (!isSilent) statementNodes.push(...createConstViolationThrowStatementNodes(name, internalVars));
32✔
587

32✔
588
        // `(e => ({ set a(v) { v === void 0 && e(); const c = 0; c = 0; }}))(() => x).a`
32✔
589
        return t.memberExpression(
32✔
590
                t.callExpression(
32✔
591
                        t.arrowFunctionExpression(
32✔
592
                                [rightGetterVarNode],
32✔
593
                                t.objectExpression([
32✔
594
                                        t.objectMethod('set', t.identifier('a'), [valueVarNode], t.blockStatement(statementNodes))
32✔
595
                                ])
32✔
596
                        ),
32✔
597
                        [t.arrowFunctionExpression([], rightNode)]
32✔
598
                ),
32✔
599
                t.identifier('a')
32✔
600
        );
32✔
601
}
32✔
602

31✔
603
/**
31✔
604
 * Create block statement which throws 'Assignment to constant variable' TypeError.
31✔
605
 * @param {string} name - Var name
31✔
606
 * @param {Object} internalVars - Map of internal vars, keyed by var name
31✔
607
 * @returns {Object} - AST Node object
31✔
608
 */
31✔
609
function createConstViolationThrowBlockNode(name, internalVars) {
336✔
610
        // `{ const c = 0; c = 0; }`
336✔
611
        return t.blockStatement(createConstViolationThrowStatementNodes(name, internalVars));
336✔
612
}
336✔
613

31✔
614
/**
31✔
615
 * Create statements which throw 'Assignment to constant variable' TypeError.
31✔
616
 * @param {string} name - Var name
31✔
617
 * @param {Object} internalVars - Map of internal vars, keyed by var name
31✔
618
 * @returns {Array<Object>} - Array of AST Node objects
31✔
619
 */
31✔
620
function createConstViolationThrowStatementNodes(name, internalVars) {
368✔
621
        // `const c = 0; c = 0;`
368✔
622
        const varNode = createTempVarNode(name, internalVars);
368✔
623
        return [
368✔
624
                t.variableDeclaration('const', [t.variableDeclarator(varNode, t.numericLiteral(0))]),
368✔
625
                t.expressionStatement(t.assignmentExpression('=', varNode, t.numericLiteral(0)))
368✔
626
        ];
368✔
627
}
368✔
628

31✔
629
/**
31✔
630
 * Replace `super()` call with transpiled version.
31✔
631
 * @param {Array<string|number>} trail - Keys trail
31✔
632
 * @param {Array<Object>} trailNodes - Trail nodes
31✔
633
 * @param {Object} superVarNode - Var node for home object for `super`
31✔
634
 * @param {Object} [thisVarNode] - Var node for `this` (Identifier)
31✔
635
 * @param {number} [firstSuperStatementIndex] - Index of first top-level `super()` statement
31✔
636
 * @param {Function} copyLoc - Function to copy location from old to new AST node
31✔
637
 * @returns {undefined}
31✔
638
 */
31✔
639
function replaceSuperCall(
208✔
640
        trail, trailNodes, superVarNode, thisVarNode, firstSuperStatementIndex, copyLoc
208✔
641
) {
208✔
642
        const trailLen = trail.length,
208✔
643
                callNode = trailNodes[trailLen - 1];
208✔
644

208✔
645
        // Convert to `super(x, y)` -> `Reflect.construct(Object.getPrototypeOf(super$0), [x, y], super$0)`.
208✔
646
        // NB Cannot optimize `super(...x)` to `Reflect.construct(Object.getPrototypeOf(super$0), x, super$0)`
208✔
647
        // in case `x` is an iterator not an array.
208✔
648
        const argumentsNodes = callNode.arguments;
208✔
649
        callNode.callee = copyLoc(
208✔
650
                t.memberExpression(t.identifier('Reflect'), t.identifier('construct')),
208✔
651
                callNode.callee
208✔
652
        );
208✔
653
        callNode.arguments = [
208✔
654
                t.callExpression(
208✔
655
                        t.memberExpression(t.identifier('Object'), t.identifier('getPrototypeOf')),
208✔
656
                        [superVarNode]
208✔
657
                ),
208✔
658
                t.arrayExpression(argumentsNodes),
208✔
659
                superVarNode
208✔
660
        ];
208✔
661

208✔
662
        // If is `return super()`, just substitute transpiled super
208✔
663
        // `return super(x)` -> `return Reflect.construct(Object.getPrototypeOf(super$0), [x], super$0)`
208✔
664
        const grandParentNode = trailNodes[trailLen - 2],
208✔
665
                grandParentType = grandParentNode.type;
208✔
666
        if (grandParentType === 'ReturnStatement') {
208✔
667
                if (thisVarNode) {
8!
668
                        grandParentNode[trail[trailLen - 2]] = copyLoc(
×
669
                                t.assignmentExpression('=', thisVarNode, callNode),
×
670
                                callNode
×
671
                        );
×
672
                }
×
673
                return;
8✔
674
        }
8✔
675

200✔
676
        // If is a top-level statement in constructor
200✔
677
        // (i.e. is statement which is purely `super()`, directly in constructor body block):
200✔
678
        // - Last statement: `return Reflect.construct(...)`
200✔
679
        //   or if `this` used in constructor: `return this$0 = Reflect.construct(...)`
200✔
680
        // - First statement: `const this$0 = Reflect.construct(...)`
200✔
681
        // - Any other statement: `this$0 = Reflect.construct(...)`
200✔
682
        if (grandParentType === 'ExpressionStatement') {
200✔
683
                const fnType = trailNodes[0].type;
192✔
684
                if ((fnType === 'ClassDeclaration' || fnType === 'ClassExpression') && trail.length === 8) {
192✔
685
                        // Top-level statement in constructor
168✔
686
                        const statementNodes = trailNodes[5],
168✔
687
                                statementIndex = trail[5];
168✔
688
                        if (statementIndex === statementNodes.length - 1) {
168✔
689
                                // Last statement in constructor
48✔
690
                                // `return Reflect.construct(...);` or `return this$0 = Reflect.construct(...);`
48✔
691
                                statementNodes[statementIndex] = copyLoc(
48✔
692
                                        t.returnStatement(thisVarNode ? t.assignmentExpression('=', thisVarNode, callNode) : callNode),
48✔
693
                                        callNode
48✔
694
                                );
48✔
695
                                return;
48✔
696
                        }
48✔
697

120✔
698
                        if (statementIndex === firstSuperStatementIndex) {
160✔
699
                                // First `super()` statement - replace with `const this$0 = Reflect.construct(...);`
104✔
700
                                statementNodes[statementIndex] = copyLoc(
104✔
701
                                        t.variableDeclaration('const', [t.variableDeclarator(thisVarNode, callNode)]),
104✔
702
                                        grandParentNode
104✔
703
                                );
104✔
704
                                return;
104✔
705
                        }
104✔
706
                }
168✔
707
        }
192✔
708

48✔
709
        // Replace with `this$0 = Reflect.construct(...)`
48✔
710
        grandParentNode[trail[trailLen - 2]] = copyLoc(
48✔
711
                t.assignmentExpression('=', thisVarNode, callNode), callNode
48✔
712
        );
48✔
713
}
208✔
714

31✔
715
/**
31✔
716
 * Replace `super` expression with transpiled version.
31✔
717
 * `super.prop` / `super[prop]` / `super.prop(...)` / `super[prop](...)` /
31✔
718
 * `super.prop = ...` / `super[prop] = ...`
31✔
719
 *
31✔
720
 * @param {Array<string|number>} trail - Keys trail
31✔
721
 * @param {Array<Object>} trailNodes - Trail nodes
31✔
722
 * @param {Object} superVarNode - Var node for home object for `super`
31✔
723
 * @param {Object} [thisVarNode] - Var node for `this` (may be Identifier or ThisExpression)
31✔
724
 * @param {boolean} superIsProto - `true` if `super` is within context of class prototype method
31✔
725
 * @param {Object} internalVars - Map of internal vars, keyed by var name
31✔
726
 * @param {Function} copyLoc - Function to copy location from old to new AST node
31✔
727
 * @returns {undefined}
31✔
728
 */
31✔
729
function replaceSuperExpression(
648✔
730
        trail, trailNodes, superVarNode, thisVarNode, superIsProto, internalVars, copyLoc
648✔
731
) {
648✔
732
        const trailLen = trail.length,
648✔
733
                expressionNode = trailNodes[trailLen - 1];
648✔
734

648✔
735
        // `super.prop` -> `Reflect.get(Object.getPrototypeOf(super$0.prototype), 'prop', this$0)`
648✔
736
        // or in static class method `Reflect.get(Object.getPrototypeOf(super$0), 'prop', this$0)`
648✔
737
        let propNode = expressionNode.property;
648✔
738
        if (!expressionNode.computed) propNode = copyLoc(t.stringLiteral(propNode.name), propNode);
648✔
739

648✔
740
        let replacementNode = t.callExpression(
648✔
741
                t.memberExpression(t.identifier('Reflect'), t.identifier('get')),
648✔
742
                [
648✔
743
                        t.callExpression(
648✔
744
                                t.memberExpression(t.identifier('Object'), t.identifier('getPrototypeOf')),
648✔
745
                                [
648✔
746
                                        superIsProto
648✔
747
                                                ? t.memberExpression(superVarNode, t.identifier('prototype'))
648✔
748
                                                : superVarNode
648✔
749
                                ]
648✔
750
                        ),
648✔
751
                        propNode,
648✔
752
                        thisVarNode
648✔
753
                ]
648✔
754
        );
648✔
755

648✔
756
        // `super.prop = ...` -> `Reflect.set(Object.getPrototypeOf(super$0.prototype), 'prop', value, this$0)`
648✔
757
        const parentNode = trailNodes[trailLen - 2],
648✔
758
                parentKey = trail[trailLen - 2],
648✔
759
                parentType = parentNode.type;
648✔
760
        if (parentType === 'AssignmentExpression' && parentKey === 'left') {
648✔
761
                // Convert `Reflect.get(...)` to `Reflect.set(...)`
112✔
762
                replacementNode.callee.property.name = 'set';
112✔
763

112✔
764
                const valueNode = parentNode.right,
112✔
765
                        grandParentNode = trailNodes[trailLen - 3];
112✔
766
                if (grandParentNode.type === 'ExpressionStatement') {
112✔
767
                        // Standalone expression - add value to `Reflect.set()` arguments
104✔
768
                        replacementNode.arguments.splice(2, 0, valueNode);
104✔
769
                } else {
104✔
770
                        // Not standalone expression - wrap in function which return values.
8✔
771
                        // This avoids evaluating the value expression twice.
8✔
772
                        // `x = super.foo = y();` =>
8✔
773
                        // `x = ((key, value) => (Reflect.set(..., key, value, this$0), value))('foo', y());`
8✔
774
                        // NB Key is passed to function as argument too as it could be a complex expression
8✔
775
                        // referencing many vars. Evaluating it outside the temp closure ensures no var name clashes.
8✔
776
                        const keyVarNode = createTempVarNode('key', internalVars),
8✔
777
                                valueVarNode = createTempVarNode('value', internalVars);
8✔
778
                        replacementNode.arguments[1] = keyVarNode;
8✔
779
                        replacementNode.arguments.splice(2, 0, valueVarNode);
8✔
780
                        replacementNode = t.callExpression(
8✔
781
                                t.arrowFunctionExpression(
8✔
782
                                        [keyVarNode, valueVarNode],
8✔
783
                                        t.sequenceExpression([replacementNode, valueVarNode])
8✔
784
                                ),
8✔
785
                                [propNode, valueNode]
8✔
786
                        );
8✔
787
                }
8✔
788

112✔
789
                grandParentNode[trail[trailLen - 3]] = copyLoc(replacementNode, parentNode);
112✔
790
                return;
112✔
791
        }
112✔
792

536✔
793
        // `super.prop(x, y)` -> `Reflect.get(...).call(this$0, x, y)`
536✔
794
        if (parentType === 'CallExpression') {
648✔
795
                replacementNode = t.memberExpression(replacementNode, t.identifier('call'));
392✔
796
                parentNode.arguments.unshift(thisVarNode);
392✔
797
        }
392✔
798
        copyLoc(replacementNode, expressionNode);
536✔
799

536✔
800
        parentNode[parentKey] = replacementNode;
536✔
801
}
648✔
802

31✔
803
/**
31✔
804
 * Create temp var node and add to internal vars.
31✔
805
 * @param {string} name - Var name
31✔
806
 * @param {Object} internalVars - Map of internal vars, keyed by var name
31✔
807
 * @returns {Object} - Identifier AST node
31✔
808
 */
31✔
809
function createTempVarNode(name, internalVars) {
536✔
810
        const node = t.identifier(name);
536✔
811
        createArrayOrPush(internalVars, name, node);
536✔
812
        return node;
536✔
813
}
536✔
814

31✔
815
/**
31✔
816
 * Add ASTs of nested functions to AST.
31✔
817
 * A function which has other functions nested within it will have left those nodes defined as `null`
31✔
818
 * in JSON-serialized AST. Get those child functions' ASTs from their own `getFnInfo()` functions
31✔
819
 * and insert them into the parent AST.
31✔
820
 * @param {Object} fnInfo - Function info object which was encoded as JSON to fn info getter function
31✔
821
 * @param {Array<Function>} getInfos - Function info getter functions for child functions
31✔
822
 * @param {boolean} isNestedFunction - `true` if is nested function
31✔
823
 * @param {number} fnId - Block ID of function being serialized
31✔
824
 * @param {Map} scopeDefs - Scope definitions map, keyed by block ID
31✔
825
 * @param {Object} externalVars - Map of var name to array of var nodes
31✔
826
 * @param {Object} internalVars - Map of var name to array of var nodes
31✔
827
 * @param {Set} globalVarNames - Set of global var names
31✔
828
 * @param {Set} functionNames - Set of function names
31✔
829
 * @param {Array<Object>} amendments - Array of amendments (const violations or `super`)
31✔
830
 * @returns {undefined}
31✔
831
 */
31✔
832
function resolveFunctionInfo(
17,250✔
833
        fnInfo, getInfos, isNestedFunction, fnId, scopeDefs,
17,250✔
834
        externalVars, internalVars, globalVarNames, functionNames, amendments
17,250✔
835
) {
17,250✔
836
        // Init internal vars.
17,250✔
837
        // Converting trails to nodes needs to be done after child functions added into AST
17,250✔
838
        // because some vars recorded as internal to this function may be within the ASTs of child functions.
17,250✔
839
        // These are class `extends` clauses and computed method keys.
17,250✔
840
        // Initializing properties of `internalVars` needs to happen before processing child functions,
17,250✔
841
        // as external vars in child functions can become internal vars in this function.
17,250✔
842
        const internalVarTrails = [];
17,250✔
843
        for (const [varName, {blockIds, trails}] of Object.entries(fnInfo.internalVars)) {
17,250✔
844
                let internalVar = internalVars[varName];
5,233✔
845
                if (!internalVar) internalVar = internalVars[varName] = {blockIds: new Set(), nodes: []};
5,233✔
846
                blockIds.forEach(blockId => internalVar.blockIds.add(blockId));
5,233✔
847

5,233✔
848
                internalVarTrails.push({varName, trails});
5,233✔
849
        }
5,233✔
850

17,250✔
851
        // Process child functions
17,250✔
852
        const fnNode = fnInfo.ast;
17,250✔
853
        fnInfo.childFns.forEach((trail, index) => {
17,250✔
854
                const [childJson, childGetInfos] = getInfos[index](),
2,618✔
855
                        childInfo = JSON.parse(childJson);
2,618✔
856
                resolveFunctionInfo(
2,618✔
857
                        childInfo, childGetInfos, true, fnId, scopeDefs,
2,618✔
858
                        externalVars, internalVars, globalVarNames, functionNames, amendments
2,618✔
859
                );
2,618✔
860

2,618✔
861
                // Insert child function's AST into this function's AST
2,618✔
862
                setProp(fnNode, trail, childInfo.ast);
2,618✔
863
        });
17,250✔
864

17,250✔
865
        // Record function name
17,250✔
866
        if (isNestedFunction) {
17,250✔
867
                const idNode = fnNode.id;
2,618✔
868
                if (idNode) functionNames.add(idNode.name);
2,618✔
869
        }
2,618✔
870

17,250✔
871
        // Get external var nodes
17,250✔
872
        for (const scope of fnInfo.scopes) {
17,250✔
873
                const {blockId} = scope;
11,163✔
874
                if (blockId < fnId) {
11,163✔
875
                        // External var
9,650✔
876
                        for (const [varName, {isReadFrom, isAssignedTo, trails}] of Object.entries(scope.vars)) {
9,650✔
877
                                if (isNestedFunction) {
13,554✔
878
                                        const scopeDefVar = scopeDefs.get(blockId).vars[varName];
1,712✔
879
                                        if (isReadFrom) scopeDefVar.isReadFrom = true;
1,712✔
880
                                        if (isAssignedTo) scopeDefVar.isAssignedTo = true;
1,712✔
881
                                }
1,712✔
882

13,554✔
883
                                externalVars[varName].push(...trailsToNodes(fnNode, trails, varName));
13,554✔
884
                        }
13,554✔
885
                } else {
11,163✔
886
                        // Var which is external to current function, but internal to function being serialized
1,513✔
887
                        for (const [varName, {trails}] of Object.entries(scope.vars)) {
1,513✔
888
                                const internalVar = internalVars[varName];
1,674✔
889
                                if (internalVar && internalVar.blockIds.has(blockId)) {
1,674✔
890
                                        internalVar.nodes.push(...trailsToNodes(fnNode, trails, varName));
842✔
891
                                }
842✔
892
                        }
1,674✔
893
                }
1,513✔
894
        }
11,163✔
895

17,250✔
896
        // Get internal var nodes
17,250✔
897
        for (const {varName, trails} of internalVarTrails) {
17,250✔
898
                internalVars[varName].nodes.push(...trailsToNodes(fnNode, trails, varName));
5,233✔
899
        }
5,233✔
900

17,250✔
901
        // Get global var names
17,250✔
902
        const thisGlobalVarNames = fnInfo.globalVarNames;
17,250✔
903
        if (thisGlobalVarNames) setAddFrom(globalVarNames, thisGlobalVarNames);
17,250✔
904

17,250✔
905
        // Get amendments (const violations and `super`).
17,250✔
906
        // Ignore amendments which refer to internal vars.
17,250✔
907
        const thisAmendments = fnInfo.amendments;
17,250✔
908
        if (thisAmendments) {
17,250✔
909
                for (const [type, blockId, ...trail] of thisAmendments) {
1,216✔
910
                        if (blockId < fnId) {
1,320✔
911
                                amendments.push({type, trail, trailNodes: getProps(fnNode, trail)});
1,232✔
912
                        } else if (type === CONST_VIOLATION_CONST) {
1,320✔
913
                                // Const violation where var is internal to function. Add to internal vars instead.
16✔
914
                                // Ignore CONST_VIOLATION_FUNCTION_THROWING and CONST_VIOLATION_FUNCTION_SILENT violation types
16✔
915
                                // because they refer to function names, which are not treated as internal vars.
16✔
916
                                const node = getProp(fnNode, trail);
16✔
917
                                internalVars[node.name].nodes.push(node);
16✔
918
                        }
16✔
919
                }
1,320✔
920
        }
1,216✔
921
}
17,250✔
922

31✔
923
/**
31✔
924
 * Get nodes for trails.
31✔
925
 * Convert `ThisExpression`s to `Identifier`s.
31✔
926
 * @param {Object} fnNode - AST node for function
31✔
927
 * @param {Array<Array>} trails - Trails
31✔
928
 * @param {string} varName - Var name
31✔
929
 * @returns {Array<Object>} - AST nodes specified by trails
31✔
930
 */
31✔
931
function trailsToNodes(fnNode, trails, varName) {
19,629✔
932
        return varName === 'this'
19,629✔
933
                ? trails.map((trail) => {
19,629✔
934
                        const node = getProp(fnNode, trail);
240✔
935
                        node.type = 'Identifier';
240✔
936
                        node.name = 'this';
240✔
937
                        return node;
240✔
938
                })
592✔
939
                : trails.map(trail => getProp(fnNode, trail));
19,629✔
940
}
19,629✔
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