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

overlookmotel / livepack / 6963749672

22 Nov 2023 11:26PM UTC coverage: 90.247% (+0.001%) from 90.246%
6963749672

push

github

overlookmotel
WIP 1

4733 of 5094 branches covered (0.0%)

Branch coverage included in aggregate %.

97 of 128 new or added lines in 10 files covered. (75.78%)

84 existing lines in 7 files now uncovered.

12654 of 14172 relevant lines covered (89.29%)

12761.23 hits per line

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

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

62✔
6
'use strict';
62✔
7

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

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

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

62✔
28
// Exports
62✔
29

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

29,936✔
70
        // Throw error if function contains `import()`
29,936✔
71
        if (fnInfo.containsImport) {
29,936!
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

29,936✔
77
        // Assemble scope definitions
29,936✔
78
        const scopeDefs = new Map(),
29,936✔
79
                externalVars = Object.create(null);
29,936✔
80
        for (const {blockId, blockName, vars} of fnInfo.scopes) {
29,936✔
81
                scopeDefs.set(blockId, {
18,132✔
82
                        blockName,
18,132✔
83
                        vars: mapValues(vars, (varProps, varName) => {
18,132✔
84
                                externalVars[varName] = [];
24,180✔
85
                                return {
24,180✔
86
                                        isReadFrom: !!varProps.isReadFrom,
24,180✔
87
                                        isAssignedTo: !!varProps.isAssignedTo,
24,180✔
88
                                        isFrozenName: !!varProps.isFrozenName
24,180✔
89
                                };
24,180✔
90
                        })
18,132✔
91
                });
18,132✔
92
        }
18,132✔
93

29,936✔
94
        // Add child functions into AST, get external/internal var nodes, and get amendments to be made
29,936✔
95
        // (const violations / incidences of `super` to be transpiled)
29,936✔
96
        const internalVars = Object.create(null),
29,936✔
97
                globalVarNames = new Set(),
29,936✔
98
                reservedVarNames = new Set(),
29,936✔
99
                amendments = [];
29,936✔
100
        resolveFunctionInfo(
29,936✔
101
                fnInfo, getChildFnInfos, false, fnId, scopeDefs,
29,936✔
102
                externalVars, internalVars, globalVarNames, reservedVarNames, amendments
29,936✔
103
        );
29,936✔
104

29,936✔
105
        let node = fnInfo.ast;
29,936✔
106

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

29,852✔
117
                traverseAll(node, ({loc}) => {
29,852✔
118
                        if (loc && !loc.filename) loc.filename = filename;
214,656✔
119
                });
29,852✔
120

29,852✔
121
                copyLoc = (destNode, srcNode) => {
29,852✔
122
                        destNode.start = srcNode.start;
8,928✔
123
                        destNode.end = srcNode.end;
8,928✔
124
                        destNode.loc = srcNode.loc;
8,928✔
125
                        return destNode;
8,928✔
126
                };
29,852✔
127
        } else {
29,936✔
128
                copyLoc = destNode => destNode;
84✔
129
        }
84✔
130

29,936✔
131
        // Get whether strict mode and remove use strict directive
29,936✔
132
        let isStrict;
29,936✔
133
        if (!isClass) {
29,936✔
134
                isStrict = !!fnInfo.isStrict;
25,840✔
135
                if (isStrict) {
25,840✔
136
                        removeUseStrictDirective(node);
20,586✔
137
                } else if (filename.startsWith(RUNTIME_DIR_PATH)) {
25,840✔
138
                        // Runtime functions are treated as indeterminate strict/sloppy mode unless explicitly strict
976✔
139
                        isStrict = null;
976✔
140
                }
976✔
141
        }
25,840✔
142

29,936✔
143
        // Create `super` var node if required
29,936✔
144
        let superVarNode;
29,936✔
145
        if (externalVars.super) {
29,936✔
146
                superVarNode = t.identifier('super');
2,880✔
147
                externalVars.super.push(superVarNode); // NB: `externalVars.super` is always an empty array
2,880✔
148
                globalVarNames.add('Reflect');
2,880✔
149
                globalVarNames.add('Object');
2,880✔
150
        }
2,880✔
151

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

11,608✔
163
                // If contains `super`, needs `this` too
11,608✔
164
                if (externalVars.super) {
11,608✔
165
                        thisVarNode = t.identifier('this');
384✔
166
                        externalVars.this.push(thisVarNode);
384✔
167
                }
384✔
168
        } else {
29,936✔
169
                // Class, method or function declaration/expression.
18,328✔
170
                // Get function name.
18,328✔
171
                name = (Object.getOwnPropertyDescriptor(fn, 'name') || {}).value;
18,328✔
172
                if (!isString(name)) name = '';
18,328✔
173

18,328✔
174
                // Identify if method and convert class method to object method
18,328✔
175
                if (type === 'ClassMethod') {
18,328✔
176
                        node.type = 'ObjectMethod';
2,256✔
177
                        node.static = undefined;
2,256✔
178
                        isMethod = true;
2,256✔
179
                } else if (type === 'ObjectMethod') {
18,328✔
180
                        isMethod = true;
2,032✔
181
                }
2,032✔
182

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

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

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

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

14,040✔
232
                        if (!name) {
14,040✔
233
                                node.id = null;
6,168✔
234
                        } else {
14,040✔
235
                                if (!idNode || idNode.name !== name) {
7,872✔
236
                                        node.id = t.identifier(name);
852✔
237
                                        if (idNode) copyLoc(node.id, idNode);
852✔
238
                                }
852✔
239
                                reservedVarNames.add(name);
7,872✔
240
                        }
7,872✔
241

14,040✔
242
                        if (isClass) {
14,040✔
243
                                // Class
4,096✔
244
                                if (type === 'ClassDeclaration') node.type = 'ClassExpression';
4,096✔
245

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

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

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

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

416✔
308
                                                firstSuperStatementIndex = fnInfo.firstSuperStatementIndex;
416✔
309
                                                constructorStatementNodes = constructorNode.body.body;
416✔
310
                                        }
416✔
311
                                } else {
4,096✔
312
                                        // Class which doesn't extend a super class. `this` within constructor is actual `this`.
2,624✔
313
                                        thisVarNode = t.thisExpression();
2,624✔
314
                                }
2,624✔
315
                        } else {
14,040✔
316
                                // Function declaration/expression. `this` is actual `this`.
9,944✔
317
                                if (type === 'FunctionDeclaration') node.type = 'FunctionExpression';
9,944✔
318
                                thisVarNode = t.thisExpression();
9,944✔
319
                        }
9,944✔
320
                }
14,040✔
321
        }
18,328✔
322

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

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

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

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

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

62✔
391
/**
62✔
392
 * Remove 'use strict' directive from function.
62✔
393
 * @param {Object} fnNode - Function AST node
62✔
394
 * @returns {undefined}
62✔
395
 */
62✔
396
function removeUseStrictDirective(fnNode) {
20,586✔
397
        const bodyNode = fnNode.body;
20,586✔
398
        if (bodyNode.type !== 'BlockStatement') return;
20,586✔
399
        const directiveNodes = bodyNode.directives;
14,440✔
400
        if (directiveNodes.length === 0) return;
18,972✔
401
        const index = directiveNodes.findIndex(directiveNode => directiveNode.value.value === 'use strict');
962✔
402
        if (index === -1) return;
1,728✔
403

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

802✔
429
        // Remove directive
802✔
430
        directiveNodes.splice(index, 1);
802✔
431
}
20,586✔
432

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

752✔
455
        const {type} = parentNode;
752✔
456
        if (type === 'AssignmentExpression') {
752✔
457
                const {operator} = parentNode;
480✔
458
                if (operator === '=') {
480✔
459
                        // `c = x` -> `x, throw()`
416✔
460
                        replaceParent(
416✔
461
                                createConstViolationThrowNode(parentNode.right, node.name, isSilent, internalVars)
416✔
462
                        );
416✔
463
                } else if (['&&=', '||=', '??='].includes(operator)) {
480✔
464
                        // `c &&= x` -> `c && (x, throw())`
32✔
465
                        replaceParent(
32✔
466
                                t.logicalExpression(
32✔
467
                                        operator.slice(0, -1),
32✔
468
                                        node,
32✔
469
                                        createConstViolationThrowNode(parentNode.right, node.name, isSilent, internalVars)
32✔
470
                                )
32✔
471
                        );
32✔
472
                } else {
32✔
473
                        // Other assignment operator e.g. `+=`, `-=`, `>>>=`
32✔
474
                        // `c += x` -> `c + x, throw()`
32✔
475
                        replaceParent(
32✔
476
                                createConstViolationThrowNode(
32✔
477
                                        t.binaryExpression(operator.slice(0, -1), node, parentNode.right),
32✔
478
                                        node.name, isSilent, internalVars
32✔
479
                                )
32✔
480
                        );
32✔
481
                }
32✔
482
        } else if (type === 'UpdateExpression') {
752✔
483
                // `c++` -> `(n => n++)(c), throw()`
32✔
484
                // `--c` -> `(n => --n)(c), throw()`
32✔
485
                // See https://github.com/overlookmotel/livepack/issues/528#issuecomment-1773242897
32✔
486
                const varNode = createTempVarNode(node.name, internalVars),
32✔
487
                        replacementNode = t.callExpression(
32✔
488
                                t.arrowFunctionExpression(
32✔
489
                                        [varNode], t.updateExpression(parentNode.operator, varNode, parentNode.prefix)
32✔
490
                                ),
32✔
491
                                [node]
32✔
492
                        );
32✔
493
                replaceParent(createConstViolationThrowNode(replacementNode, node.name, isSilent, internalVars));
32✔
494
        } else if (type === 'AssignmentPattern') {
272✔
495
                // `[c = x] = []`
64✔
496
                // -> `[(e => ({set a(v) {v === void 0 && e(); const c = 0; c = 0;}}))(() => x).a] = []`
64✔
497
                // `{c = x} = {}`
64✔
498
                // -> `{c: (e => ({set a(v) {v === void 0 && e(); const c = 0; c = 0;}}))(() => x).a} = {}`
64✔
499
                replaceParent(
64✔
500
                        createConstViolationThrowPatternNode(node.name, parentNode.right, isSilent, internalVars)
64✔
501
                );
64✔
502
        } else if (
240✔
503
                type === 'ForOfStatement' || type === 'ForInStatement'
176✔
504
                || type === 'ObjectProperty' || type === 'RestElement'
176✔
505
                || trailNodes[trailLen - 2].type === 'ArrayPattern'
176✔
506
        ) {
176✔
507
                // `for (c of [1]) { foo(); }`
176✔
508
                // -> `for ({ set a(v) { const c = 0; c = 0; } }.a of [1]) { foo(); }`
176✔
509
                // `{x: c} = {}` -> `{x: {set a(v) { const c = 0; c = 0; }}.a} = {}`
176✔
510
                // `[...c] = []` -> `[...{set a(v) { const c = 0; c = 0; }}.a] = []`
176✔
511
                // `{...c} = {}` -> `{...{set a(v) { const c = 0; c = 0; }}.a} = {}`
176✔
512
                // `[c] = [1]` -> `[{set a(v) { const c = 0; c = 0; }}.a] = [1]`
176✔
513
                parentNode[trail[trailLen - 1]] = copyLoc(
176✔
514
                        createConstViolationThrowAssignNode(node.name, isSilent, internalVars), node
176✔
515
                );
176✔
516
        } else {
176!
UNCOV
517
                assertBug(false, `Unexpected const violation node type ${type}`);
×
UNCOV
518
        }
×
519
}
752✔
520

62✔
521
/**
62✔
522
 * Create expression node which throws 'Assignment to constant variable' TypeError.
62✔
523
 * @param {Object} node - AST node to wrap
62✔
524
 * @param {string} name - Var name
62✔
525
 * @param {boolean} isSilent - `true` if violation does not throw
62✔
526
 * @param {Object} internalVars - Map of internal vars, keyed by var name
62✔
527
 * @returns {Object} - AST Node object
62✔
528
 */
62✔
529
function createConstViolationThrowNode(node, name, isSilent, internalVars) {
512✔
530
        // If silent failure, return node unchanged
512✔
531
        if (isSilent) return node;
512✔
532

496✔
533
        // `(x, (() => { const c = 0; c = 0; })())`
496✔
534
        return t.sequenceExpression([
496✔
535
                node,
496✔
536
                t.callExpression(
496✔
537
                        t.arrowFunctionExpression([], createConstViolationThrowBlockNode(name, internalVars)), []
496✔
538
                )
496✔
539
        ]);
496✔
540
}
512✔
541

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

176✔
556
        // `{ set a(v) { const c = 0; c = 0; } }.a`
176✔
557
        return t.memberExpression(
176✔
558
                t.objectExpression([
176✔
559
                        t.objectMethod(
176✔
560
                                'set',
176✔
561
                                t.identifier('a'),
176✔
562
                                [createTempVarNode(name === 'v' ? '_v' : 'v', internalVars)],
176!
563
                                createConstViolationThrowBlockNode(name, internalVars)
176✔
564
                        )
176✔
565
                ]),
176✔
566
                t.identifier('a')
176✔
567
        );
176✔
568
}
176✔
569

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

64✔
585
        // `v === void 0 && e()`
64✔
586
        const statementNodes = [t.expressionStatement(t.logicalExpression(
64✔
587
                '&&',
64✔
588
                t.binaryExpression('===', valueVarNode, t.unaryExpression('void', t.numericLiteral(0))),
64✔
589
                t.callExpression(rightGetterVarNode, [])
64✔
590
        ))];
64✔
591

64✔
592
        if (!isSilent) statementNodes.push(...createConstViolationThrowStatementNodes(name, internalVars));
64✔
593

64✔
594
        // `(e => ({ set a(v) { v === void 0 && e(); const c = 0; c = 0; }}))(() => x).a`
64✔
595
        return t.memberExpression(
64✔
596
                t.callExpression(
64✔
597
                        t.arrowFunctionExpression(
64✔
598
                                [rightGetterVarNode],
64✔
599
                                t.objectExpression([
64✔
600
                                        t.objectMethod('set', t.identifier('a'), [valueVarNode], t.blockStatement(statementNodes))
64✔
601
                                ])
64✔
602
                        ),
64✔
603
                        [t.arrowFunctionExpression([], rightNode)]
64✔
604
                ),
64✔
605
                t.identifier('a')
64✔
606
        );
64✔
607
}
64✔
608

62✔
609
/**
62✔
610
 * Create block statement which throws 'Assignment to constant variable' TypeError.
62✔
611
 * @param {string} name - Var name
62✔
612
 * @param {Object} internalVars - Map of internal vars, keyed by var name
62✔
613
 * @returns {Object} - AST Node object
62✔
614
 */
62✔
615
function createConstViolationThrowBlockNode(name, internalVars) {
672✔
616
        // `{ const c = 0; c = 0; }`
672✔
617
        return t.blockStatement(createConstViolationThrowStatementNodes(name, internalVars));
672✔
618
}
672✔
619

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

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

480✔
651
        // Convert to `super(x, y)` -> `Reflect.construct(Object.getPrototypeOf(super$0), [x, y], super$0)`.
480✔
652
        // NB: Cannot optimize `super(...x)` to `Reflect.construct(Object.getPrototypeOf(super$0), x, super$0)`
480✔
653
        // in case `x` is an iterator not an array.
480✔
654
        const argumentsNodes = callNode.arguments;
480✔
655
        callNode.callee = copyLoc(
480✔
656
                t.memberExpression(t.identifier('Reflect'), t.identifier('construct')),
480✔
657
                callNode.callee
480✔
658
        );
480✔
659
        callNode.arguments = [
480✔
660
                t.callExpression(
480✔
661
                        t.memberExpression(t.identifier('Object'), t.identifier('getPrototypeOf')),
480✔
662
                        [superVarNode]
480✔
663
                ),
480✔
664
                t.arrayExpression(argumentsNodes),
480✔
665
                superVarNode
480✔
666
        ];
480✔
667

480✔
668
        // If is `return super()`, just substitute transpiled super
480✔
669
        // `return super(x)` -> `return Reflect.construct(Object.getPrototypeOf(super$0), [x], super$0)`
480✔
670
        const grandParentNode = trailNodes[trailLen - 2],
480✔
671
                grandParentType = grandParentNode.type;
480✔
672
        if (grandParentType === 'ReturnStatement') {
480✔
673
                if (thisVarNode) {
16!
UNCOV
674
                        grandParentNode[trail[trailLen - 2]] = copyLoc(
×
UNCOV
675
                                t.assignmentExpression('=', thisVarNode, callNode),
×
UNCOV
676
                                callNode
×
UNCOV
677
                        );
×
678
                }
×
679
                return;
16✔
680
        }
16✔
681

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

304✔
704
                        if (statementIndex === firstSuperStatementIndex) {
384✔
705
                                // First `super()` statement - replace with `const this$0 = Reflect.construct(...);`
272✔
706
                                statementNodes[statementIndex] = copyLoc(
272✔
707
                                        t.variableDeclaration('const', [t.variableDeclarator(thisVarNode, callNode)]),
272✔
708
                                        grandParentNode
272✔
709
                                );
272✔
710
                                return;
272✔
711
                        }
272✔
712
                }
400✔
713
        }
448✔
714

96✔
715
        // Replace with `this$0 = Reflect.construct(...)`
96✔
716
        grandParentNode[trail[trailLen - 2]] = copyLoc(
96✔
717
                t.assignmentExpression('=', thisVarNode, callNode), callNode
96✔
718
        );
96✔
719
}
480✔
720

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

1,424✔
741
        // `super.prop` -> `Reflect.get(Object.getPrototypeOf(super$0.prototype), 'prop', this$0)`
1,424✔
742
        // or in static class method `Reflect.get(Object.getPrototypeOf(super$0), 'prop', this$0)`
1,424✔
743
        let propNode = expressionNode.property;
1,424✔
744
        if (!expressionNode.computed) propNode = copyLoc(t.stringLiteral(propNode.name), propNode);
1,424✔
745

1,424✔
746
        let replacementNode = t.callExpression(
1,424✔
747
                t.memberExpression(t.identifier('Reflect'), t.identifier('get')),
1,424✔
748
                [
1,424✔
749
                        t.callExpression(
1,424✔
750
                                t.memberExpression(t.identifier('Object'), t.identifier('getPrototypeOf')),
1,424✔
751
                                [
1,424✔
752
                                        superIsProto
1,424✔
753
                                                ? t.memberExpression(superVarNode, t.identifier('prototype'))
1,424✔
754
                                                : superVarNode
1,424✔
755
                                ]
1,424✔
756
                        ),
1,424✔
757
                        propNode,
1,424✔
758
                        thisVarNode
1,424✔
759
                ]
1,424✔
760
        );
1,424✔
761

1,424✔
762
        // `super.prop = ...` -> `Reflect.set(Object.getPrototypeOf(super$0.prototype), 'prop', value, this$0)`
1,424✔
763
        const parentNode = trailNodes[trailLen - 2],
1,424✔
764
                parentKey = trail[trailLen - 2],
1,424✔
765
                parentType = parentNode.type;
1,424✔
766
        if (parentType === 'AssignmentExpression' && parentKey === 'left') {
1,424✔
767
                // Convert `Reflect.get(...)` to `Reflect.set(...)`
224✔
768
                replacementNode.callee.property.name = 'set';
224✔
769

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

224✔
795
                grandParentNode[trail[trailLen - 3]] = copyLoc(replacementNode, parentNode);
224✔
796
                return;
224✔
797
        }
224✔
798

1,200✔
799
        // `super.prop(x, y)` -> `Reflect.get(...).call(this$0, x, y)`
1,200✔
800
        if (parentType === 'CallExpression') {
1,424✔
801
                replacementNode = t.memberExpression(replacementNode, t.identifier('call'));
912✔
802
                parentNode.arguments.unshift(thisVarNode);
912✔
803
        }
912✔
804
        copyLoc(replacementNode, expressionNode);
1,200✔
805

1,200✔
806
        parentNode[parentKey] = replacementNode;
1,200✔
807
}
1,424✔
808

62✔
809
/**
62✔
810
 * Create temp var node and add to internal vars.
62✔
811
 * @param {string} name - Var name
62✔
812
 * @param {Object} internalVars - Map of internal vars, keyed by var name
62✔
813
 * @returns {Object} - Identifier AST node
62✔
814
 */
62✔
815
function createTempVarNode(name, internalVars) {
1,104✔
816
        const node = t.identifier(name);
1,104✔
817
        createArrayOrPush(internalVars, name, node);
1,104✔
818
        return node;
1,104✔
819
}
1,104✔
820

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

35,268✔
855
        // Process child functions
35,268✔
856
        const fnNode = fnInfo.ast;
35,268✔
857
        fnInfo.childFns.forEach((trail, index) => {
35,268✔
858
                const [childJson, childGetInfos] = getInfos[index](),
5,332✔
859
                        childInfo = JSON.parse(childJson);
5,332✔
860
                resolveFunctionInfo(
5,332✔
861
                        childInfo, childGetInfos, true, fnId, scopeDefs,
5,332✔
862
                        externalVars, internalVars, globalVarNames, reservedVarNames, amendments
5,332✔
863
                );
5,332✔
864

5,332✔
865
                // Insert child function's AST into this function's AST
5,332✔
866
                setProp(fnNode, trail, childInfo.ast);
5,332✔
867
        });
35,268✔
868

35,268✔
869
        // Get external var nodes
35,268✔
870
        for (const scope of fnInfo.scopes) {
35,268✔
871
                const {blockId} = scope;
22,838✔
872
                if (blockId < fnId) {
22,838✔
873
                        // External var
19,796✔
874
                        for (
19,796✔
875
                                const [varName, {isReadFrom, isAssignedTo, isFrozenName, trails}]
19,796✔
876
                                of Object.entries(scope.vars)
19,796✔
877
                        ) {
19,796✔
878
                                if (isNestedFunction) {
27,604✔
879
                                        const scopeDefVar = scopeDefs.get(blockId).vars[varName];
3,424✔
880
                                        if (isReadFrom) scopeDefVar.isReadFrom = true;
3,424✔
881
                                        if (isAssignedTo) scopeDefVar.isAssignedTo = true;
3,424✔
882
                                        if (isFrozenName) scopeDefVar.isFrozenName = true;
3,424✔
883
                                }
3,424✔
884

27,604✔
885
                                if (!isFrozenName) externalVars[varName].push(...trailsToNodes(fnNode, trails, varName));
27,604✔
886
                        }
27,604✔
887
                } else {
22,838✔
888
                        // Var which is external to current function, but internal to function being serialized
3,042✔
889
                        for (const [varName, {isFrozenInternalName, trails}] of Object.entries(scope.vars)) {
3,042✔
890
                                if (!isFrozenInternalName) {
3,364✔
891
                                        internalVars[varName].push(...trailsToNodes(fnNode, trails, varName));
1,252✔
892
                                }
1,252✔
893
                        }
3,364✔
894
                }
3,042✔
895
        }
22,838✔
896

35,268✔
897
        // Get internal var nodes
35,268✔
898
        for (const {varName, trails} of internalVarTrails) {
35,268✔
899
                internalVars[varName].push(...trailsToNodes(fnNode, trails, varName));
8,834✔
900
        }
8,834✔
901

35,268✔
902
        // Get reserved internal var names + global var names
35,268✔
903
        if (fnInfo.reservedVarNames) setAddFrom(reservedVarNames, fnInfo.reservedVarNames);
35,268✔
904
        if (fnInfo.globalVarNames) setAddFrom(globalVarNames, fnInfo.globalVarNames);
35,268✔
905

35,268✔
906
        // Get amendments (const violations and `super`).
35,268✔
907
        // Ignore amendments which refer to internal vars.
35,268✔
908
        const thisAmendments = fnInfo.amendments;
35,268✔
909
        if (thisAmendments) {
35,268✔
910
                for (const [type, blockId, ...trail] of thisAmendments) {
2,624✔
911
                        if (blockId < fnId) {
2,832✔
912
                                amendments.push({type, trail, trailNodes: getProps(fnNode, trail)});
2,656✔
913
                        } else if (type === CONST_VIOLATION_NEEDS_VAR) {
2,832✔
914
                                // Const violation where var is internal to function. Add to internal vars instead.
32✔
915
                                // If var is frozen, no internal var is needed. In these cases, type will be
32✔
916
                                // `CONST_VIOLATION_SILENT` or `CONST_VIOLATION_NEEDS_NO_VAR`.
32✔
917
                                // If violation is read-write (e.g. `x += 1`), then it would also appear in `externalVars`
32✔
918
                                // so will have been converted to an internal var already above. Type will be
32✔
919
                                // `CONST_VIOLATION_NEEDS_NO_VAR`.
32✔
920
                                const node = getProp(fnNode, trail);
32✔
921
                                internalVars[node.name].push(node);
32✔
922
                        }
32✔
923
                }
2,832✔
924
        }
2,624✔
925
}
35,268✔
926

62✔
927
/**
62✔
928
 * Get nodes for trails.
62✔
929
 * Convert `ThisExpression`s to `Identifier`s.
62✔
930
 * @param {Object} fnNode - AST node for function
62✔
931
 * @param {Array<Array>} trails - Trails
62✔
932
 * @param {string} varName - Var name
62✔
933
 * @returns {Array<Object>} - AST nodes specified by trails
62✔
934
 */
62✔
935
function trailsToNodes(fnNode, trails, varName) {
29,834✔
936
        return varName === 'this'
29,834✔
937
                ? trails.map((trail) => {
29,834✔
938
                        const node = getProp(fnNode, trail);
544✔
939
                        node.type = 'Identifier';
544✔
940
                        node.name = 'this';
544✔
941
                        return node;
544✔
942
                })
976✔
943
                : trails.map(trail => getProp(fnNode, trail));
29,834✔
944
}
29,834✔
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

© 2025 Coveralls, Inc