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

overlookmotel / livepack / 6972989751

23 Nov 2023 05:53PM UTC coverage: 90.245% (-0.2%) from 90.47%
6972989751

push

github

overlookmotel
Code comment [nocode]

4724 of 5086 branches covered (0.0%)

Branch coverage included in aggregate %.

2 of 2 new or added lines in 1 file covered. (100.0%)

131 existing lines in 8 files now uncovered.

12640 of 14155 relevant lines covered (89.3%)

12910.31 hits per line

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

99.34
/lib/serialize/blocks.js
1
/* --------------------
62✔
2
 * livepack module
62✔
3
 * Process blocks (holding scopes)
62✔
4
 * ------------------*/
62✔
5

62✔
6
'use strict';
62✔
7

62✔
8
// Modules
62✔
9
const assert = require('simple-invariant'),
62✔
10
        upperFirst = require('lodash/upperFirst'),
62✔
11
        t = require('@babel/types');
62✔
12

62✔
13
// Imports
62✔
14
const {
62✔
15
                createRecord, createDependency, createAssignment, createBlock, createScope
62✔
16
        } = require('./records.js'),
62✔
17
        {addStrictDirectiveToFunction} = require('./strict.js'),
62✔
18
        {
62✔
19
                replaceRecordNode, getNodeWithinWrapperParent, setAddFrom, firstMapValue, deleteFirst
62✔
20
        } = require('./utils.js');
62✔
21

62✔
22
// Exports
62✔
23

62✔
24
module.exports = {
62✔
25
        initBlocks() {
62✔
26
                const rootBlock = createBlock(0, 'root', null);
27,758✔
27
                this.rootBlock = rootBlock;
27,758✔
28
                this.rootScope = createScope(null, rootBlock, null);
27,758✔
29
                this.rootStrictFnRecords = [];
27,758✔
30
                this.rootSloppyFnRecords = [];
27,758✔
31
        },
62✔
32

62✔
33
        processBlocks() {
62✔
34
                // Create scopes and functions
27,758✔
35
                const {rootBlock, rootScope} = this;
27,758✔
36
                createMissingScopes(rootBlock, null);
27,758✔
37

27,758✔
38
                const createScopeRecord = createRecord('createScopeRoot');
27,758✔
39
                const {node: blockNode, globalVarNames: functionsGlobalVarNames} = this.processBlock(
27,758✔
40
                        rootBlock, () => createScopeRecord, Object.create(null), new Set(), true
27,758✔
41
                );
27,758✔
42
                const {elements} = blockNode.body;
27,758✔
43

27,758✔
44
                this.globalVarNames.push(...functionsGlobalVarNames);
27,758✔
45

27,758✔
46
                // Flatten out contents of root scope
27,758✔
47
                const rootScopeRecord = rootScope.record;
27,758✔
48
                for (const {record: dependentRecord} of rootScopeRecord.dependents) {
27,758✔
49
                        // Substitute root scope calls for functions
25,518✔
50
                        replaceRecordNode(dependentRecord, elements.shift());
25,518✔
51

25,518✔
52
                        dependentRecord.dependencies = dependentRecord.dependencies.filter(
25,518✔
53
                                dependency => dependency.record !== rootScopeRecord
25,518✔
54
                        );
25,518✔
55
                }
25,518✔
56
        },
62✔
57

62✔
58
        processBlock(block, getParentCreateScopeRecord, inheritedVars, frozenNames, isRoot) {
62✔
59
                const returnNodes = [];
40,464✔
60

40,464✔
61
                // Init param objects and add frozen param names to `frozenNames`
40,464✔
62
                const {containsEval} = block,
40,464✔
63
                        paramsByName = Object.create(null);
40,464✔
64
                let frozenNamesIsCloned = false;
40,464✔
65
                const params = [...block.params.keys()].map((name) => {
40,464✔
66
                        const param = {
17,362✔
67
                                name,
17,362✔
68
                                // Identifier nodes referring to this param
17,362✔
69
                                localVarNodes: [],
17,362✔
70
                                // If param always contains another function defined in this block,
17,362✔
71
                                // function definition and array of function records
17,362✔
72
                                fnDef: null,
17,362✔
73
                                fnRecords: null,
17,362✔
74
                                // Set to `true` if param does not always contain another function defined in this block
17,362✔
75
                                isNotLocalFunction: false,
17,362✔
76
                                // Count of scopes which define a value for this param
17,362✔
77
                                definedCount: 0,
17,362✔
78
                                // Index of function to inject value for param in returned functions
17,362✔
79
                                injectionIndex: null,
17,362✔
80
                                // Node for parameter to injection function
17,362✔
81
                                injectionVarNode: null
17,362✔
82
                        };
17,362✔
83
                        paramsByName[name] = param;
17,362✔
84

17,362✔
85
                        // `super` and `new.target` are not actually frozen
17,362✔
86
                        if (containsEval && !['super', 'new.target'].includes(name)) {
17,362✔
87
                                if (!frozenNamesIsCloned) {
4,800✔
88
                                        frozenNames = new Set(frozenNames);
2,400✔
89
                                        frozenNamesIsCloned = true;
2,400✔
90
                                }
2,400✔
91
                                frozenNames.add(name);
4,800✔
92
                        }
4,800✔
93

17,362✔
94
                        return param;
17,362✔
95
                });
40,464✔
96

40,464✔
97
                // Identify scope vars which refer to functions within this scope.
40,464✔
98
                // If value of a scope var is consistently a function created within this block
40,464✔
99
                // (consistent across all scopes for this block), scope var can be set inline within scope function.
40,464✔
100
                // i.e. `(x, getX) => [getX = () => x, () => getX()]` (note `getX = `).
40,464✔
101
                // If function is not used elsewhere in code except by other functions in this block, it doesn't
40,464✔
102
                // need to be returned from the scope function at all.
40,464✔
103
                // i.e. `(x, getX) => (getX = () => x, () => getX())` (only 2nd function is returned).
40,464✔
104
                // Any other reference to a function defined in this block or blocks above is flagged as circular.
40,464✔
105
                // Also count which params are most used in calls to create scope function, so params
40,464✔
106
                // which are most often undefined (or circular, and so undefined in initial call to scope function)
40,464✔
107
                // go last.
40,464✔
108
                const {functions: blockFunctions, scopes: blockScopes, children: childBlocks} = block,
40,464✔
109
                        localFunctions = new Map();
40,464✔
110
                for (const fnDef of blockFunctions) {
40,464✔
111
                        for (const [scope, fnRecord] of fnDef.scopes) {
30,288✔
112
                                localFunctions.set(fnRecord, {scope, fnDef});
32,338✔
113
                        }
32,338✔
114
                }
30,288✔
115

40,464✔
116
                const undefinedRecord = this.serializeValue(undefined);
40,464✔
117
                let numLocalFunctions = 0;
40,464✔
118
                for (const scope of blockScopes.values()) {
40,464✔
119
                        const {values} = scope;
42,642✔
120
                        for (const param of params) {
42,642✔
121
                                const valProps = values[param.name];
19,876✔
122
                                if (!valProps) continue;
19,876✔
123

19,620✔
124
                                // Flag references to functions within this block as circular and increment count where not
19,620✔
125
                                const valRecord = valProps.record;
19,620✔
126
                                let localFunction;
19,620✔
127
                                if (valRecord === undefinedRecord) {
19,876✔
128
                                        // Value is undefined - treat same as unused
256✔
129
                                        values[param.name] = undefined;
256✔
130
                                } else {
19,876✔
131
                                        localFunction = localFunctions.get(valRecord);
19,364✔
132
                                        if (localFunction) {
19,364✔
133
                                                valProps.isCircular = true;
3,584✔
134
                                        } else if (!valProps.isCircular) {
19,364✔
135
                                                // Check if value or its dependencies are functions in this block or above
14,932✔
136
                                                const isCircular = (function referencesSameScopeOrDeeper(depRecord) {
14,932✔
137
                                                        // If value is a function, and it's in this scope or one nested within it,
22,224✔
138
                                                        // needs to be injected later. Flag as circular if so.
22,224✔
139
                                                        // NB: `.scope` property is only set for functions.
22,224✔
140
                                                        let fnScope = depRecord.scope;
22,224✔
141
                                                        if (fnScope) {
22,224✔
142
                                                                do {
2,070✔
143
                                                                        if (fnScope.block === block) return true;
2,694✔
144
                                                                        fnScope = fnScope.parent;
2,502✔
145
                                                                } while (fnScope);
2,070✔
146
                                                        }
2,070✔
147

22,032✔
148
                                                        // Iterate through value's dependencies too
22,032✔
149
                                                        const {dependencies} = depRecord;
22,032✔
150
                                                        if (dependencies) {
22,224✔
151
                                                                for (const dependency of dependencies) {
15,778✔
152
                                                                        if (referencesSameScopeOrDeeper(dependency.record)) return true;
7,292✔
153
                                                                }
7,292✔
154
                                                        }
15,650✔
155

21,904✔
156
                                                        return false;
21,904✔
157
                                                }(valRecord));
14,932✔
158

14,932✔
159
                                                if (isCircular) {
14,932✔
160
                                                        valProps.isCircular = true;
192✔
161
                                                } else {
14,932✔
162
                                                        param.definedCount++;
14,740✔
163
                                                }
14,740✔
164
                                        }
14,932✔
165
                                }
19,364✔
166

19,620✔
167
                                // Determine if param value refers to a function in this scope
19,620✔
168
                                if (param.isNotLocalFunction) continue; // Already discounted
19,876✔
169

17,714✔
170
                                const isLocalFunction = !!localFunction && localFunction.scope === scope;
19,876✔
171
                                if (!param.fnDef) {
19,876✔
172
                                        if (isLocalFunction) {
17,250✔
173
                                                param.fnDef = localFunction.fnDef;
3,104✔
174
                                                param.fnRecords = [valRecord];
3,104✔
175
                                                numLocalFunctions++;
3,104✔
176
                                        } else {
17,250✔
177
                                                param.isNotLocalFunction = true;
14,146✔
178
                                        }
14,146✔
179
                                } else if (isLocalFunction && param.fnDef === localFunction.fnDef) {
19,876✔
180
                                        param.fnRecords.push(valRecord);
432✔
181
                                } else {
464✔
182
                                        param.isNotLocalFunction = true;
32✔
183
                                        param.fnDef = null;
32✔
184
                                        param.fnRecords = null;
32✔
185
                                        numLocalFunctions--;
32✔
186
                                }
32✔
187
                        }
19,876✔
188
                }
42,642✔
189

40,464✔
190
                let numInternalOnlyFunctions = 0;
40,464✔
191
                if (numLocalFunctions > 0) {
40,464✔
192
                        const internalFunctions = new Set();
2,976✔
193
                        for (const {name: paramName, fnDef, fnRecords} of params) {
2,976✔
194
                                if (!fnDef) continue;
4,992✔
195

3,072✔
196
                                // Record param name containing this function in function def object
3,072✔
197
                                (fnDef.internalScopeParamNames || (fnDef.internalScopeParamNames = [])).push(paramName);
4,992✔
198

4,992✔
199
                                // Delete values for this param from all scopes.
4,992✔
200
                                // Delete the dependencies created between scope record and value record for all scopes.
4,992✔
201
                                for (const fnRecord of fnRecords) {
4,992✔
202
                                        const {scope} = fnRecord;
3,504✔
203
                                        scope.values[paramName] = undefined;
3,504✔
204

3,504✔
205
                                        const scopeRecord = scope.record;
3,504✔
206
                                        deleteFirst(scopeRecord.dependencies, dependency => dependency.record === fnRecord);
3,504✔
207
                                        deleteFirst(
3,504✔
208
                                                fnRecord.dependents,
3,504✔
209
                                                dependent => dependent.record === scopeRecord && dependent.node === undefined
3,504✔
210
                                        );
3,504✔
211
                                }
3,504✔
212

3,072✔
213
                                internalFunctions.add(fnDef);
3,072✔
214
                        }
3,072✔
215

2,976✔
216
                        // Determine if functions are only referenced by other functions within the scope
2,976✔
217
                        // they are defined in. If so, they don't need to be returned from the scope function.
2,976✔
218
                        for (const fnDef of internalFunctions) {
2,976✔
219
                                if (![...fnDef.scopes.values()].some(fnRecord => (
3,008✔
220
                                        fnRecord.assignments || fnRecord.dependents.length > 0 || t.isCallExpression(fnRecord.node)
3,152✔
221
                                ))) {
3,008✔
222
                                        fnDef.isScopeInternalOnly = true;
224✔
223
                                        numInternalOnlyFunctions++;
224✔
224
                                }
224✔
225
                        }
3,008✔
226
                }
2,976✔
227

40,464✔
228
                // Re-order params with least often defined last
40,464✔
229
                params.sort(({definedCount: count1}, {definedCount: count2}) => (
40,464✔
230
                        count1 > count2 ? -1 : count2 > count1 ? 1 : 0
6,688✔
231
                ));
40,464✔
232

40,464✔
233
                // Function to create functions to inject values into scope
40,464✔
234
                function createInjection(param) {
40,464✔
235
                        const inputParamNode = t.identifier(`_${param.name}`),
1,008✔
236
                                outputParamNode = t.identifier(param.name);
1,008✔
237
                        param.injectionVarNode = inputParamNode;
1,008✔
238
                        param.localVarNodes.push(outputParamNode);
1,008✔
239

1,008✔
240
                        const injectNode = t.arrowFunctionExpression(
1,008✔
241
                                [inputParamNode],
1,008✔
242
                                t.assignmentExpression('=', outputParamNode, inputParamNode)
1,008✔
243
                        );
1,008✔
244

1,008✔
245
                        const index = returnNodes.length;
1,008✔
246
                        param.injectionIndex = index;
1,008✔
247
                        returnNodes.push(injectNode);
1,008✔
248
                        return index;
1,008✔
249
                }
1,008✔
250

40,464✔
251
                // Create scopes
40,464✔
252
                for (const scope of blockScopes.values()) {
40,464✔
253
                        // Create node for calling createScope function to create scope object
42,642✔
254
                        const createScopeRecord = getParentCreateScopeRecord(scope.parent);
42,642✔
255

42,642✔
256
                        const argumentNodes = [],
42,642✔
257
                                callNode = t.callExpression(createScopeRecord.varNode, argumentNodes),
42,642✔
258
                                scopeRecord = scope.record;
42,642✔
259
                        scopeRecord.node = callNode;
42,642✔
260

42,642✔
261
                        // Get parameters
42,642✔
262
                        const {values} = scope,
42,642✔
263
                                undefinedIndexes = [];
42,642✔
264
                        let numTrailingUndefined = 0,
42,642✔
265
                                paramIndex = 0;
42,642✔
266
                        for (const param of params) {
42,642✔
267
                                const valProps = values[param.name];
19,876✔
268
                                let node;
19,876✔
269
                                if (!valProps) {
19,876✔
270
                                        // Value not required - insert `undefined` as empty value
4,016✔
271
                                        node = undefinedRecord.varNode;
4,016✔
272
                                        undefinedIndexes.push(paramIndex);
4,016✔
273
                                        numTrailingUndefined++;
4,016✔
274
                                } else {
19,876✔
275
                                        const valRecord = valProps.record,
15,860✔
276
                                                valDependents = valRecord.dependents,
15,860✔
277
                                                valDependent = valDependents
15,860✔
278
                                                        ? valDependents.find(dependent => dependent.record === scopeRecord && !dependent.node)
15,860✔
279
                                                        : undefined;
15,860✔
280
                                        node = valRecord.varNode;
15,860✔
281
                                        if (valProps.isCircular) {
15,860✔
282
                                                // Circular reference - inject into scope later
1,120✔
283
                                                const injectionIndex = param.injectionIndex ?? createInjection(param);
1,120✔
284

1,120✔
285
                                                // Create var for inject function for this scope.
1,120✔
286
                                                // Each var will only be injected once into each scope, so no need to guard against
1,120✔
287
                                                // duplicate inject function vars being created.
1,120✔
288
                                                const injectRecord = createRecord(
1,120✔
289
                                                        `inject${upperFirst(param.name)}Into${upperFirst(scopeRecord.varNode.name)}`
1,120✔
290
                                                );
1,120✔
291

1,120✔
292
                                                const injectFnNode = t.memberExpression(
1,120✔
293
                                                        scopeRecord.varNode,
1,120✔
294
                                                        t.numericLiteral(injectionIndex),
1,120✔
295
                                                        true
1,120✔
296
                                                );
1,120✔
297
                                                injectRecord.node = injectFnNode;
1,120✔
298

1,120✔
299
                                                createDependency(injectRecord, scopeRecord, injectFnNode, 'object');
1,120✔
300

1,120✔
301
                                                // Create assignment
1,120✔
302
                                                const argumentsNode = [node],
1,120✔
303
                                                        assignmentNode = t.callExpression(injectRecord.varNode, argumentsNode),
1,120✔
304
                                                        assignment = createAssignment(scopeRecord, assignmentNode);
1,120✔
305
                                                createDependency(assignment, injectRecord, assignmentNode, 'callee');
1,120✔
306

1,120✔
307
                                                // Move dependency on value from scope record to assignment
1,120✔
308
                                                valDependent.record = assignment;
1,120✔
309
                                                valDependent.node = argumentsNode;
1,120✔
310
                                                valDependent.key = 0;
1,120✔
311
                                                assignment.dependencies.push(
1,120✔
312
                                                        deleteFirst(scopeRecord.dependencies, dep => dep.record === valRecord)
1,120✔
313
                                                );
1,120✔
314

1,120✔
315
                                                // Insert `undefined` as empty value
1,120✔
316
                                                node = undefinedRecord.varNode;
1,120✔
317
                                                undefinedIndexes.push(paramIndex);
1,120✔
318
                                                numTrailingUndefined++;
1,120✔
319
                                        } else {
15,844✔
320
                                                // Complete dependent object with `.node` and `.key` properties
14,740✔
321
                                                if (valDependent) {
14,740✔
322
                                                        valDependent.node = argumentNodes;
8,486✔
323
                                                        valDependent.key = paramIndex;
8,486✔
324
                                                }
8,486✔
325
                                                numTrailingUndefined = 0;
14,740✔
326
                                        }
14,740✔
327
                                }
15,860✔
328

19,876✔
329
                                argumentNodes[paramIndex] = node;
19,876✔
330
                                paramIndex++;
19,876✔
331
                        }
19,876✔
332

42,642✔
333
                        // Trim off any trailing undefined params
42,642✔
334
                        if (numTrailingUndefined > 0) {
42,642✔
335
                                argumentNodes.length -= numTrailingUndefined;
4,752✔
336
                                undefinedIndexes.length -= numTrailingUndefined;
4,752✔
337
                        }
4,752✔
338

42,642✔
339
                        // Create dependencies for `undefined`
42,642✔
340
                        for (const paramIndex of undefinedIndexes) { // eslint-disable-line no-shadow
42,642!
UNCOV
341
                                createDependency(scopeRecord, undefinedRecord, argumentNodes, paramIndex);
×
UNCOV
342
                        }
×
343

42,642✔
344
                        // Link scope object to createScope function (as 1st dependency)
42,642✔
345
                        createDependency(scopeRecord, createScopeRecord, callNode, 'callee');
42,642✔
346
                        const scopeDependencies = scopeRecord.dependencies;
42,642✔
347
                        scopeDependencies.unshift(scopeDependencies.pop());
42,642✔
348
                }
42,642✔
349

40,464✔
350
                // Determine if can output a singular value rather than an array.
40,464✔
351
                // NB: Root function always returns an array - it will be destructured in `processBlocks()`.
40,464✔
352
                let returnNodeIndex = returnNodes.length;
40,464✔
353

40,464✔
354
                const isSingular = !isRoot
40,464✔
355
                        && returnNodeIndex + blockFunctions.length - numInternalOnlyFunctions + childBlocks.length === 1;
40,464✔
356

40,464✔
357
                // Init vars to track strict/sloppy children
40,464✔
358
                const strictFns = [];
40,464✔
359
                let someSloppy = false;
40,464✔
360
                function recordStrict(node, isStrict) {
40,464✔
361
                        if (isStrict) {
17,476✔
362
                                strictFns.push({node, index: returnNodeIndex});
11,890✔
363
                        } else if (isStrict === false) {
17,476✔
364
                                someSloppy = true;
3,826✔
365
                        }
3,826✔
366
                }
17,476✔
367

40,464✔
368
                const {rootStrictFnRecords, rootSloppyFnRecords} = this;
40,464✔
369
                function recordRootStrict(fnRecord, isStrict) {
40,464✔
370
                        if (isStrict) {
24,062✔
371
                                rootStrictFnRecords.push(fnRecord);
17,176✔
372
                        } else if (isStrict === false) {
24,062✔
373
                                rootSloppyFnRecords.push(fnRecord);
2,646✔
374
                        }
2,646✔
375
                }
24,062✔
376

40,464✔
377
                // Create functions
40,464✔
378
                const globalVarNames = new Set(),
40,464✔
379
                        reservedVarNames = new Set(),
40,464✔
380
                        internalFunctionNodes = [];
40,464✔
381
                for (const blockFunction of blockFunctions) {
40,464✔
382
                        const {
30,288✔
383
                                scopes: fnScopes, externalVars, internalVars, globalVarNames: fnGlobalVarNames,
30,288✔
384
                                containsEval: fnContainsEval, isStrict, internalScopeParamNames, isScopeInternalOnly
30,288✔
385
                        } = blockFunction;
30,288✔
386

30,288✔
387
                        if (!isScopeInternalOnly) {
30,288✔
388
                                for (const [scope, fnRecord] of fnScopes) {
30,064✔
389
                                        let scopeRecord = scope.record;
31,970✔
390

31,970✔
391
                                        let fnNode;
31,970✔
392
                                        if (isSingular) {
31,970✔
393
                                                // Will output singular function rather than array.
7,266✔
394
                                                // The scope object is defunct - can go direct to createScope function.
7,266✔
395

7,266✔
396
                                                // Copy over `createScope(...)` node created above
7,266✔
397
                                                fnNode = scopeRecord.node;
7,266✔
398
                                                scopeRecord.node = null;
7,266✔
399

7,266✔
400
                                                // Transfer dependencies from scope object to function instance
7,266✔
401
                                                const {dependencies} = fnRecord,
7,266✔
402
                                                        scopeDependencies = scopeRecord.dependencies;
7,266✔
403
                                                for (const dependency of scopeDependencies) {
7,266✔
404
                                                        dependencies.push(dependency);
10,486✔
405
                                                        for (const dependent of dependency.record.dependents) {
10,486✔
406
                                                                if (dependent.record === scopeRecord) dependent.record = fnRecord;
13,758✔
407
                                                        }
13,758✔
408
                                                }
10,486✔
409
                                                scopeRecord = scopeDependencies[0].record;
7,266✔
410
                                                scopeDependencies.length = 0;
7,266✔
411
                                        } else {
31,970✔
412
                                                // Will output array - link function instance to scope object
24,704✔
413
                                                fnNode = t.memberExpression(
24,704✔
414
                                                        scopeRecord.varNode,
24,704✔
415
                                                        t.numericLiteral(returnNodeIndex),
24,704✔
416
                                                        true
24,704✔
417
                                                );
24,704✔
418

24,704✔
419
                                                createDependency(fnRecord, scopeRecord, fnNode, 'object');
24,704✔
420

24,704✔
421
                                                // Record strict/sloppy for top level functions
24,704✔
422
                                                if (isRoot && !fnContainsEval) recordRootStrict(fnRecord, isStrict);
24,704✔
423
                                        }
24,704✔
424

31,970✔
425
                                        // Replace placeholder var with reference to scope var
31,970✔
426
                                        const fnNodeWithWrapper = fnRecord.node,
31,970✔
427
                                                wrapperParentNode = getNodeWithinWrapperParent(fnNodeWithWrapper);
31,970✔
428
                                        if (!internalScopeParamNames) {
31,970✔
429
                                                // Replace placeholder var with reference to scope var
28,962✔
430
                                                if (wrapperParentNode) {
28,962✔
431
                                                        wrapperParentNode[0] = fnNode;
3,044✔
432
                                                } else {
28,962✔
433
                                                        fnRecord.node = fnNode;
25,918✔
434
                                                }
25,918✔
435
                                        } else {
31,970✔
436
                                                if (wrapperParentNode) {
3,008✔
437
                                                        wrapperParentNode[0] = fnRecord.varNode;
2,288✔
438
                                                        const assignment = createAssignment(scopeRecord, fnNodeWithWrapper);
2,288✔
439
                                                        createDependency(assignment, fnRecord, wrapperParentNode, 0);
2,288✔
440

2,288✔
441
                                                        // Re-assign function record's dependencies to scope record
2,288✔
442
                                                        const assignmentDependencies = assignment.dependencies;
2,288✔
443
                                                        fnRecord.dependencies = fnRecord.dependencies.filter((dependency) => {
2,288✔
444
                                                                const dependencyRecord = dependency.record;
6,720✔
445
                                                                if (dependencyRecord === scopeRecord) return true;
6,720✔
446

4,432✔
447
                                                                assignmentDependencies.push(dependency);
4,432✔
448
                                                                for (const dependent of dependencyRecord.dependents) {
6,720✔
449
                                                                        if (dependent.record === fnRecord) dependent.record = scopeRecord;
8,896✔
450
                                                                }
8,896✔
451
                                                                return false;
4,432✔
452
                                                        });
2,288✔
453
                                                }
2,288✔
454

3,008✔
455
                                                fnRecord.node = fnNode;
3,008✔
456

3,008✔
457
                                                // Re-assign function record's assignments to scope record
3,008✔
458
                                                const fnAssignments = fnRecord.assignments;
3,008✔
459
                                                if (fnAssignments) {
3,008✔
460
                                                        let {assignments} = scopeRecord;
1,712✔
461
                                                        if (!assignments) assignments = scopeRecord.assignments = [];
1,712✔
462
                                                        for (const assignment of fnAssignments) {
1,712✔
463
                                                                assignments.push(assignment);
1,744✔
464

1,744✔
465
                                                                if (!assignment.node) {
1,744✔
466
                                                                        // Re-assign prototype to depend on scope record.
1,584✔
467
                                                                        // Required to maintain circular dependency between function and prototype
1,584✔
468
                                                                        // when code-splitting.
1,584✔
469
                                                                        const protoRecord = assignment.dependencies[0].record;
1,584✔
470
                                                                        protoRecord.prototypeOf = scopeRecord;
1,584✔
471
                                                                }
1,584✔
472
                                                        }
1,744✔
473

1,712✔
474
                                                        fnRecord.assignments = undefined;
1,712✔
475
                                                }
1,712✔
476
                                        }
3,008✔
477
                                }
31,970✔
478
                        }
30,064✔
479

30,288✔
480
                        // Add var nodes to globals/locals
30,288✔
481
                        const fnReservedVarNames = new Set();
30,288✔
482
                        for (const varName in externalVars) {
30,288✔
483
                                const param = paramsByName[varName];
24,180✔
484
                                if (param) {
24,180✔
485
                                        // Local var
16,228✔
486
                                        param.localVarNodes.push(...externalVars[varName]);
16,228✔
487
                                } else {
24,180✔
488
                                        // Var referencing upper scope
7,952✔
489
                                        inheritedVars[varName].push(...externalVars[varName]);
7,952✔
490
                                }
7,952✔
491

24,180✔
492
                                // If var name is frozen, prevent an internal var taking same name
24,180✔
493
                                if (frozenNames.has(varName)) fnReservedVarNames.add(varName);
24,180✔
494
                        }
24,180✔
495

30,288✔
496
                        // Add global vars to globals
30,288✔
497
                        for (const varName of fnGlobalVarNames) {
30,288✔
498
                                if (!isRoot || !fnContainsEval) globalVarNames.add(varName);
11,240✔
499
                                fnReservedVarNames.add(varName);
11,240✔
500
                        }
11,240✔
501

30,288✔
502
                        // Add var names which are frozen internal to function to reserved names
30,288✔
503
                        // to prevent other vars clashing with them
30,288✔
504
                        for (const varName of blockFunction.reservedVarNames) {
30,288✔
505
                                reservedVarNames.add(varName);
11,522✔
506
                                fnReservedVarNames.add(varName);
11,522✔
507
                        }
11,522✔
508

30,288✔
509
                        // Rename internal vars.
30,288✔
510
                        // Names avoid clashing with internal and globals vars used within this function.
30,288✔
511
                        // Function names and vars which are frozen by `eval()` aren't included in `internalVars`.
30,288✔
512
                        const transformVarName = this.createVarNameTransform(fnReservedVarNames);
30,288✔
513
                        for (const varName in internalVars) {
30,288✔
514
                                // `this` is only included in `internalVars` if it's the faux `this` created for transpiled
11,298✔
515
                                // `super()`. If function contains `eval()`, make sure it's prefixed with "_".
11,298✔
516
                                const newName = transformVarName(varName, fnContainsEval && varName === 'this');
11,298✔
517
                                if (newName !== varName) {
11,298✔
518
                                        for (const varNode of internalVars[varName]) {
6,046✔
519
                                                varNode.name = newName;
10,780✔
520
                                        }
10,780✔
521
                                }
6,046✔
522
                                reservedVarNames.add(newName);
11,298✔
523
                        }
11,298✔
524

30,288✔
525
                        // Record if strict or sloppy
30,288✔
526
                        let {node} = blockFunction;
30,288✔
527
                        if (!isRoot) {
30,288✔
528
                                recordStrict(node, isStrict);
13,460✔
529
                        } else if (fnContainsEval) {
30,224✔
530
                                // Function in root scope containing eval - wrap in `(0, eval)(...)`
624✔
531
                                // to prevent access to outer vars
624✔
532
                                node = this.wrapInIndirectEval(node, isStrict);
624✔
533
                        }
624✔
534

30,288✔
535
                        // If param is a locally-defined function, add `x = ...` to node
30,288✔
536
                        if (internalScopeParamNames) {
30,288✔
537
                                // If unnamed function, wrap in `(0, ...)` to prevent implicit naming
3,008✔
538
                                if (!node.id) node = t.sequenceExpression([t.numericLiteral(0), node]);
3,008✔
539

3,008✔
540
                                for (const paramName of internalScopeParamNames) {
3,008✔
541
                                        const varNode = t.identifier(paramName);
3,072✔
542
                                        paramsByName[paramName].localVarNodes.push(varNode);
3,072✔
543
                                        node = t.assignmentExpression('=', varNode, node);
3,072✔
544
                                }
3,072✔
545
                        }
3,008✔
546

30,288✔
547
                        // Return function definition (as function/class expression)
30,288✔
548
                        if (isScopeInternalOnly) {
30,288✔
549
                                internalFunctionNodes.push(node);
224✔
550
                        } else {
30,288✔
551
                                returnNodes[returnNodeIndex++] = node;
30,064✔
552
                        }
30,064✔
553
                }
30,288✔
554

40,464✔
555
                const nextInheritedVars = Object.assign(Object.create(null), inheritedVars);
40,464✔
556
                for (const param of params) {
40,464✔
557
                        nextInheritedVars[param.name] = param.localVarNodes;
17,362✔
558
                }
17,362✔
559

40,464✔
560
                // Process child blocks
40,464✔
561
                for (const childBlock of childBlocks) {
40,464✔
562
                        const createScopes = new Map(),
12,706✔
563
                                createScopeName = `createScope${upperFirst(childBlock.name)}`;
12,706✔
564
                        let createScopeRecord;
12,706✔
565
                        // eslint-disable-next-line no-inner-declarations, no-loop-func
12,706✔
566
                        function getCreateScopeRecord(scope) {
12,706✔
567
                                // If already created, return it
14,884✔
568
                                createScopeRecord = createScopes.get(scope);
14,884✔
569
                                if (createScopeRecord) return createScopeRecord;
14,884✔
570

13,202✔
571
                                const scopeRecord = scope.record;
13,202✔
572
                                if (isSingular) {
14,884✔
573
                                        // Will output singular createScope function.
3,088✔
574
                                        // Repurpose the scope object created above as the createScope function.
3,088✔
575
                                        // NB: No need to do dependency linking - already done above.
3,088✔
576
                                        scopeRecord.varNode.name = createScopeName;
3,088✔
577
                                        createScopeRecord = scopeRecord;
3,088✔
578
                                } else {
14,884✔
579
                                        // Will output array of functions - createScope function will be an element of scope array
10,114✔
580
                                        createScopeRecord = createRecord(createScopeName);
10,114✔
581

10,114✔
582
                                        const createScopeNode = t.memberExpression(
10,114✔
583
                                                scopeRecord.varNode,
10,114✔
584
                                                t.numericLiteral(returnNodeIndex),
10,114✔
585
                                                true
10,114✔
586
                                        );
10,114✔
587
                                        createScopeRecord.node = createScopeNode;
10,114✔
588

10,114✔
589
                                        createDependency(createScopeRecord, scopeRecord, createScopeNode, 'object');
10,114✔
590
                                }
10,114✔
591

13,202✔
592
                                createScopes.set(scope, createScopeRecord);
13,202✔
593
                                return createScopeRecord;
13,202✔
594
                        }
14,884✔
595

12,706✔
596
                        let {
12,706✔
597
                                node: childNode, // eslint-disable-next-line prefer-const
12,706✔
598
                                globalVarNames: childGlobalVarNames, reservedVarNames: childReservedVarNames, isStrict
12,706✔
599
                        } = this.processBlock(childBlock, getCreateScopeRecord, nextInheritedVars, frozenNames, false);
12,706✔
600

12,706✔
601
                        if (isRoot && childBlock.containsEval) {
12,706✔
602
                                // Top-level block containing eval - wrap in `(0, eval)(...)`
832✔
603
                                // to prevent access to outer vars
832✔
604
                                childNode = this.wrapInIndirectEval(childNode, isStrict);
832✔
605
                        } else {
12,706✔
606
                                setAddFrom(globalVarNames, childGlobalVarNames);
11,874✔
607
                                setAddFrom(reservedVarNames, childReservedVarNames);
11,874✔
608

11,874✔
609
                                if (isRoot) {
11,874✔
610
                                        recordRootStrict(createScopeRecord, isStrict);
7,858✔
611
                                } else {
11,874✔
612
                                        recordStrict(childNode, isStrict);
4,016✔
613
                                }
4,016✔
614
                        }
11,874✔
615

12,706✔
616
                        returnNodes[returnNodeIndex++] = childNode;
12,706✔
617
                }
12,706✔
618

40,464✔
619
                // Rename params
40,464✔
620
                const transformVarName = this.createVarNameTransform(
40,464✔
621
                        new Set([...globalVarNames, ...reservedVarNames, ...frozenNames])
40,464✔
622
                );
40,464✔
623

40,464✔
624
                const paramNodes = [],
40,464✔
625
                        {mangle} = this.options;
40,464✔
626
                let hasArgumentsOrEvalParam = false,
40,464✔
627
                        frozenThisVarName, frozenArgumentsVarName;
40,464✔
628
                for (const param of params) {
40,464✔
629
                        const paramName = param.name;
17,362✔
630
                        let newName;
17,362✔
631
                        const renameInjectionVarNode = () => {
17,362✔
632
                                if (!param.injectionVarNode) return;
10,546✔
633
                                param.injectionVarNode.name = mangle
720✔
634
                                        ? (newName === 'a' ? 'b' : 'a')
6,700✔
635
                                        : `_${newName}`;
10,546✔
636
                        };
17,362✔
637

17,362✔
638
                        if (paramName === 'new.target') {
17,362✔
639
                                // `new.target` is always renamed as it can't be provided for `eval()` anyway
368✔
640
                                newName = transformVarName('newTarget', containsEval);
368✔
641

368✔
642
                                // Convert `MetaProperty` nodes into `Identifier`s with new name
368✔
643
                                for (const varNode of param.localVarNodes) {
368✔
644
                                        varNode.type = 'Identifier';
368✔
645
                                        varNode.name = newName;
368✔
646
                                        varNode.meta = undefined;
368✔
647
                                        varNode.property = undefined;
368✔
648
                                }
368✔
649

368✔
650
                                // Rename injection node
368✔
651
                                renameInjectionVarNode();
368✔
652
                        } else if (!containsEval || paramName === 'super') {
17,362✔
653
                                // Param can be renamed.
12,194✔
654
                                // NB: `super` param is always renamed.
12,194✔
655
                                newName = transformVarName(paramName, containsEval);
12,194✔
656
                                if (newName !== paramName) {
12,194✔
657
                                        // Rename all nodes
8,178✔
658
                                        for (const varNode of param.localVarNodes) {
8,178✔
659
                                                varNode.name = newName;
13,708✔
660
                                        }
13,708✔
661

8,178✔
662
                                        // Rename injection node
8,178✔
663
                                        renameInjectionVarNode();
8,178✔
664
                                }
8,178✔
665
                        } else {
16,994✔
666
                                // Frozen var name (potentially used in `eval()`)
4,800✔
667
                                // eslint-disable-next-line no-lonely-if
4,800✔
668
                                if (paramName === 'this' || (paramName === 'arguments' && paramsByName.this)) {
4,800✔
669
                                        // `this` or `arguments` captured from function.
800✔
670
                                        // `arguments` is only injected with a function wrapper if `this` is frozen too.
800✔
671
                                        // Otherwise, can just make `arguments` a normal param.
800✔
672
                                        // This can be the case if `arguments` is a user-defined variable,
800✔
673
                                        // not `arguments` created by a function.
800✔
674
                                        newName = transformVarName(paramName, true);
800✔
675
                                        if (paramName === 'this') {
800✔
676
                                                frozenThisVarName = newName;
400✔
677
                                        } else {
400✔
678
                                                frozenArgumentsVarName = newName;
400✔
679
                                        }
400✔
680

800✔
681
                                        assert(
800✔
682
                                                !param.injectionVarNode,
800✔
683
                                                'Cannot handle circular `this` or `arguments` where `this` cannot be renamed due to use of `eval()`'
800✔
684
                                        );
800✔
685
                                } else {
4,800✔
686
                                        newName = paramName;
4,000✔
687

4,000✔
688
                                        if (paramName === 'arguments' || paramName === 'eval') hasArgumentsOrEvalParam = true;
4,000✔
689

4,000✔
690
                                        // Rename injection node
4,000✔
691
                                        if (mangle) renameInjectionVarNode();
4,000✔
692
                                }
4,000✔
693
                        }
4,800✔
694

17,362✔
695
                        reservedVarNames.add(newName);
17,362✔
696

17,362✔
697
                        paramNodes.push(t.identifier(newName));
17,362✔
698
                }
17,362✔
699

40,464✔
700
                // Handle strict/sloppy mode
40,464✔
701
                let isStrict;
40,464✔
702
                if (!isRoot) {
40,464✔
703
                        if (hasArgumentsOrEvalParam) {
12,706✔
704
                                // If param named `arguments` or `eval`, scope function must be sloppy mode
32✔
705
                                // or it's a syntax error.
32✔
706
                                // NB: Only way param will be called `arguments` or `eval` is if it's frozen by an `eval()`.
32✔
707
                                isStrict = false;
32✔
708
                        } else if (strictFns.length === 0) {
12,706✔
709
                                // No strict child functions or child blocks. Block is sloppy if any sloppy children,
3,874✔
710
                                // or indeterminate (`null`) if all children are indeterminate.
3,874✔
711
                                isStrict = someSloppy ? false : null;
3,874✔
712
                        } else if (someSloppy) {
12,674✔
713
                                // Some sloppy and some strict children (and maybe some indeterminate). Block is sloppy.
176✔
714
                                isStrict = false;
176✔
715
                        } else {
8,800✔
716
                                // At least one strict child and no sloppy children. Block is strict.
8,624✔
717
                                isStrict = true;
8,624✔
718
                        }
8,624✔
719

12,706✔
720
                        // If block is sloppy, add 'use strict' directives to strict functions
12,706✔
721
                        if (isStrict === false) {
12,706✔
722
                                for (const strictFnProps of strictFns) {
3,058✔
723
                                        returnNodes[strictFnProps.index] = addStrictDirectiveToFunction(strictFnProps.node);
272✔
724
                                }
272✔
725
                        }
3,058✔
726
                }
12,706✔
727

40,464✔
728
                // Create block function - will return either array of functions or single function
40,464✔
729
                let returnNode = isSingular ? returnNodes[0] : t.arrayExpression(returnNodes);
40,464✔
730
                if (numInternalOnlyFunctions > 0) {
40,464✔
731
                        internalFunctionNodes.push(returnNode);
224✔
732
                        returnNode = t.sequenceExpression(internalFunctionNodes);
224✔
733
                }
224✔
734

40,464✔
735
                // If uses frozen `this`, wrap return value in an IIFE to inject actual `this`
40,464✔
736
                // (and `arguments`, if it's used too).
40,464✔
737
                // `() => eval(x)` -> `(function() { return () => eval(x); }).apply(this$0, arguments$0)`
40,464✔
738
                // TODO: In sloppy mode, it's possible for `arguments` to be re-defined as a non-iterable object
40,464✔
739
                // which would cause an error when this function is called.
40,464✔
740
                // A better solution when outputting sloppy mode code would be to just use a var called `arguments`,
40,464✔
741
                // rather than injecting. Of course this isn't possible in ESM.
40,464✔
742
                // TODO: Ensure scope function using `this` is strict mode if value of `this` is not an object.
40,464✔
743
                // In sloppy mode, literals passed as `this` get boxed.
40,464✔
744
                // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode#securing_javascript
40,464✔
745
                // TODO: Also doesn't work where `this` or `arguments` is circular and is injected late.
40,464✔
746
                if (frozenThisVarName) {
40,464✔
747
                        returnNode = t.callExpression(
400✔
748
                                t.memberExpression(
400✔
749
                                        t.functionExpression(null, [], t.blockStatement([t.returnStatement(returnNode)])),
400✔
750
                                        t.identifier(frozenArgumentsVarName ? 'apply' : 'call')
400!
751
                                ),
400✔
752
                                [
400✔
753
                                        t.identifier(frozenThisVarName),
400✔
754
                                        ...(frozenArgumentsVarName ? [t.identifier(frozenArgumentsVarName)] : [])
400!
755
                                ]
400✔
756
                        );
400✔
757
                }
400✔
758

40,464✔
759
                const node = t.arrowFunctionExpression(paramNodes, returnNode);
40,464✔
760

40,464✔
761
                // Return block function node, global var names, reserved var names + strict/sloppy mode flag
40,464✔
762
                return {node, globalVarNames, reservedVarNames, isStrict};
40,464✔
763
        }
40,464✔
764
};
62✔
765

62✔
766
function createMissingScopes(block, parentBlock) {
40,464✔
767
        for (const scope of block.scopes.values()) {
40,464✔
768
                // If scope's parent is not in block's parent, select a scope for it
42,594✔
769
                // (any which is nested in scope's current parent will work)
42,594✔
770
                let parentScope = scope.parent;
42,594✔
771
                const currentParentBlock = parentScope && parentScope.block;
42,594✔
772
                if (currentParentBlock === parentBlock) continue;
42,594✔
773

112✔
774
                // Get missing blocks
112✔
775
                const missingBlocks = [parentBlock];
112✔
776
                let thisBlock = parentBlock;
112✔
777
                while (true) { // eslint-disable-line no-constant-condition
15,586✔
778
                        thisBlock = thisBlock.parent;
144✔
779
                        if (thisBlock === currentParentBlock) break;
144✔
780
                        missingBlocks.unshift(thisBlock);
32✔
781
                }
32✔
782

112✔
783
                let possibleParentScopes = new Set([parentScope]),
112✔
784
                        index;
112✔
785
                for (index = 0; index < missingBlocks.length; index++) {
15,586✔
786
                        const interveningBlock = missingBlocks[index],
128✔
787
                                possibleScopes = new Set();
128✔
788
                        for (const possibleScope of interveningBlock.scopes.values()) {
128✔
789
                                if (possibleParentScopes.has(possibleScope.parent)) possibleScopes.add(possibleScope);
128✔
790
                        }
128✔
791
                        if (possibleScopes.size === 0) break;
128✔
792
                        possibleParentScopes = possibleScopes;
96✔
793
                }
96✔
794

112✔
795
                parentScope = firstMapValue(possibleParentScopes);
112✔
796

112✔
797
                // If some missing scopes don't exist, create them
112✔
798
                for (; index < missingBlocks.length; index++) {
15,586✔
799
                        parentScope = createScope(null, missingBlocks[index], parentScope);
48✔
800
                }
48✔
801

112✔
802
                // Set new parent
112✔
803
                scope.parent = parentScope;
112✔
804
        }
112✔
805

40,464✔
806
        // Traverse through child blocks
40,464✔
807
        for (const childBlock of block.children) {
40,464✔
808
                createMissingScopes(childBlock, block);
12,706✔
809

12,706✔
810
                // If a child block contains eval, this block does too
12,706✔
811
                if (childBlock.containsEval) block.containsEval = true;
12,706✔
812
        }
12,706✔
813
}
40,464✔
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