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

overlookmotel / livepack / 6370834553

01 Oct 2023 01:08PM UTC coverage: 67.036% (-0.07%) from 67.104%
6370834553

push

github

overlookmotel
Support arguments objects (incomplete)

Can't finish this yet, as requires serializing functions.

1700 of 1963 branches covered (0.0%)

Branch coverage included in aggregate %.

91 of 91 new or added lines in 4 files covered. (100.0%)

10445 of 16154 relevant lines covered (64.66%)

2020.97 hits per line

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

61.08
/lib/serialize/trace.js
1
/* --------------------
31✔
2
 * livepack module
31✔
3
 * Trace values
31✔
4
 * ------------------*/
31✔
5

31✔
6
'use strict';
31✔
7

31✔
8
// Modules
31✔
9
const typeOf = require('native-type-of'),
31✔
10
        {isFunction, isSymbol} = require('is-it-type'),
31✔
11
        assert = require('simple-invariant'),
31✔
12
        upperFirst = require('lodash/upperFirst'),
31✔
13
        t = require('@babel/types');
31✔
14

31✔
15
// Imports
31✔
16
const {createRecord, createDependency} = require('./records.js'),
31✔
17
        {isPrimitive} = require('../shared/functions.js'),
31✔
18
        {URLContextSymbol} = require('../shared/globals.js'),
31✔
19
        {GLOBAL, MODULE, VALUE, GETTER, SETTER, PROTO, EVAL, COMMON_JS} = require('../shared/constants.js'),
31✔
20
        {createKeyNode, isNumberKey} = require('./utils.js'),
31✔
21
        {globals} = require('../shared/internal.js'),
31✔
22
        assertBug = require('../shared/assertBug.js');
31✔
23

31✔
24
// Exports
31✔
25

31✔
26
const exampleURL = new URL('http://x/'),
31✔
27
        typedArrayRegex = /^(?:Ui|I)nt(?:\d+)Array$/;
31✔
28

31✔
29
module.exports = {
31✔
30
        /**
31✔
31
         * Init trace stack.
31✔
32
         * @returns {undefined}
31✔
33
         */
31✔
34
        initTrace() {
31✔
35
                this.traceStack = [];
1,803✔
36
                // TODO: Next properties should be defined elsewhere
1,803✔
37
                this.nullRecord = this.traceValue(null, null, null);
1,803✔
38
                this.nullRecord.setters = new Set();
1,803✔
39

1,803✔
40
                this.undefinedRecord = this.traceValue(undefined, null, null);
1,803✔
41
                this.objectPrototypeRecord = this.traceValue(Object.prototype, null, null);
1,803✔
42
                this.arrayPrototypeRecord = this.traceValue(Array.prototype, null, null);
1,803✔
43
                this.regexpPrototypeRecord = this.traceValue(RegExp.prototype, null, null);
1,803✔
44
                this.datePrototypeRecord = this.traceValue(Date.prototype, null, null);
1,803✔
45
                this.setPrototypeRecord = this.traceValue(Set.prototype, null, null);
1,803✔
46
                this.mapPrototypeRecord = this.traceValue(Map.prototype, null, null);
1,803✔
47
                this.weakSetPrototypeRecord = this.traceValue(WeakSet.prototype, null, null);
1,803✔
48
                this.weakMapPrototypeRecord = this.traceValue(WeakMap.prototype, null, null);
1,803✔
49
                this.urlPrototypeRecord = this.traceValue(URL.prototype, null, null);
1,803✔
50
                this.urlSearchParamsPrototypeRecord = this.traceValue(URLSearchParams.prototype, null, null);
1,803✔
51
                this.stringPrototypeRecord = this.traceValue(String.prototype, null, null);
1,803✔
52
                this.booleanPrototypeRecord = this.traceValue(Boolean.prototype, null, null);
1,803✔
53
                this.numberPrototypeRecord = this.traceValue(Number.prototype, null, null);
1,803✔
54
                this.bigIntPrototypeRecord = this.traceValue(BigInt.prototype, null, null);
1,803✔
55
                this.symbolPrototypeRecord = this.traceValue(Symbol.prototype, null, null);
1,803✔
56

1,803✔
57
                this.minusZeroRecord = null;
1,803✔
58

1,803✔
59
                this.maybeSoloURLSearchParams = [];
1,803✔
60
        },
31✔
61

31✔
62
        /**
31✔
63
         * Trace value.
31✔
64
         * @param {*} val - Value
31✔
65
         * @param {string} [name] - Name for var. Does not need to be valid JS identifier.
31✔
66
         *   Can be omitted for globals.
31✔
67
         * @param {string} [traceStackEntry] - Trace stack entry for debugging. Can be omitted for globals.
31✔
68
         * @returns {Object} - Record
31✔
69
         */
31✔
70
        traceValue(val, name, traceStackEntry) {
31✔
71
                const {traceStack} = this;
73,873✔
72
                traceStack.push(traceStackEntry);
73,873✔
73
                const record = this.traceValueInner(val, name);
73,873✔
74
                traceStack.pop();
73,873✔
75
                return record;
73,873✔
76
        },
31✔
77

31✔
78
        traceValueInner(val, name) {
31✔
79
                // Special case -0 because 0 and -0 are treated as equal as Map keys,
73,873✔
80
                // so `records.get()` conflates the two
73,873✔
81
                if (Object.is(val, -0)) {
73,873✔
82
                        if (this.minusZeroRecord) return this.minusZeroRecord;
16!
83
                        const record = createRecord(name);
16✔
84
                        record.type = this.traceMinusZero(record);
16✔
85
                        this.minusZeroRecord = record;
16✔
86
                        return record;
16✔
87
                }
16✔
88

73,857✔
89
                // Use existing record
73,857✔
90
                const {records} = this;
73,857✔
91
                let record = records.get(val);
73,857✔
92
                if (record) return record;
73,873✔
93

64,969✔
94
                // Create new record
64,969✔
95
                record = createRecord(name);
64,969✔
96
                records.set(val, record);
64,969✔
97

64,969✔
98
                // Handle primitives
64,969✔
99
                if (val == null) {
73,873✔
100
                        record.type = this.tracePrimitive(val, val === null ? 'null' : 'undefined', record);
3,606✔
101
                        return record;
3,606✔
102
                }
3,606✔
103
                const type = typeof val;
61,363✔
104
                if (
61,363✔
105
                        type === 'number'
61,363✔
106
                                ? val !== Infinity && val !== -Infinity && !Number.isNaN(val)
73,873✔
107
                                : type === 'string' || type === 'boolean' || type === 'bigint'
73,873✔
108
                ) {
73,873✔
109
                        record.type = this.tracePrimitive(val, type, record);
2,505✔
110
                        return record;
2,505✔
111
                }
2,505✔
112

58,858✔
113
                // Handle globals + special values
58,858✔
114
                const globalProps = globals.get(val);
58,858✔
115
                if (globalProps) {
73,873✔
116
                        record.type = this.traceGlobal(val, globalProps, record);
56,446✔
117
                        return record;
56,446✔
118
                }
56,446✔
119

2,412✔
120
                // Trace non-primitive value
2,412✔
121
                record.type = this.traceNonPrimitive(val, type, record);
2,412✔
122
                return record;
2,412✔
123
        },
31✔
124

31✔
125
        /**
31✔
126
         * Trace a non-primitive value (not including globals).
31✔
127
         * @param {Object|Function|symbol|undefined} val - Value
31✔
128
         * @param {string} type - `typeof value`
31✔
129
         * @param {Object} record - Record
31✔
130
         * @returns {Function} - Serializer function
31✔
131
         * @throws {Error} - If unsupported type
31✔
132
         */
31✔
133
        traceNonPrimitive(val, type, record) {
31✔
134
                if (type === 'symbol') return this.traceSymbol(val, record);
2,412✔
135
                if (type === 'function') return this.traceFunction(val, record);
2,412!
136

2,195✔
137
                // Check if is a prototype + define as `fn.prototype` if so
2,195✔
138
                // TODO
2,195✔
139

2,195✔
140
                // Trace object
2,195✔
141
                assertBug(typeof val === 'object');
2,195✔
142

2,195✔
143
                // TODO: Add support for remaining types commented out below
2,195✔
144
                const objType = typeOf(val);
2,195✔
145
                if (objType === 'Object') {
2,316✔
146
                        // `URL` and `URLSearchParams` are implemented in Javascript in Node internals
1,362✔
147
                        // and so cannot be detected with a type check
1,362✔
148
                        // TODO: I think they can now be identified with a type check in NodeJS v18+
1,362✔
149
                        if (val instanceof URL) return this.traceURL(val, record);
1,362✔
150
                        if (val instanceof URLSearchParams) return this.traceURLSearchParams(val, record);
1,362✔
151
                        return this.traceObject(val, record);
1,306✔
152
                }
1,306✔
153
                if (objType === 'Array') return this.traceArray(val, record);
1,145✔
154
                if (objType === 'RegExp') return this.traceRegexp(val, record);
728✔
155
                if (objType === 'Date') return this.traceDate(val, record);
592✔
156
                if (objType === 'Set') return this.traceSet(val, record);
568✔
157
                if (objType === 'Map') return this.traceMap(val, record);
568✔
158
                if (objType === 'WeakSet') return this.traceWeakSet(val, record);
472✔
159
                if (objType === 'WeakMap') return this.traceWeakMap(val, record);
304✔
160
                if (objType === 'WeakRef') return this.traceWeakRef(val, record);
272!
161
                if (objType === 'FinalizationRegistry') return this.traceFinalizationRegistry(val, record);
272!
162
                // if (typedArrayRegex.test(objType)) return this.traceBuffer(val, objType, record);
208✔
163
                // if (objType === 'ArrayBuffer') return this.traceArrayBuffer(val, record);
208✔
164
                // if (objType === 'SharedArrayBuffer') return this.traceSharedArrayBuffer(val, record);
208✔
165
                if (objType === 'String') return this.traceBoxedString(val, record);
272✔
166
                if (objType === 'Boolean') return this.traceBoxedBoolean(val, record);
272✔
167
                if (objType === 'Number') return this.traceBoxedNumber(val, record);
272✔
168
                if (objType === 'BigInt') return this.traceBoxedBigInt(val, record);
272✔
169
                if (objType === 'Symbol') return this.traceBoxedSymbol(val, record);
56✔
170
                if (objType === 'Arguments') return this.traceArguments(val, record);
×
171
                throw new Error(`Cannot serialize ${objType}s`);
×
172
        },
31✔
173

31✔
174
        /**
31✔
175
         * Get trace stack for debug message.
31✔
176
         * @returns {string} - Trace stack
31✔
177
         */
31✔
178
        getTraceStack() {
31✔
179
                return `  ${this.traceStack.join('\n  ')}`;
×
180
        },
31✔
181

31✔
182
        /**
31✔
183
         * Trace dependency of record.
31✔
184
         * @param {*} val - Value which is dependency
31✔
185
         * @param {string} [name] - Name for var. Does not need to be valid JS identifier.
31✔
186
         *   Can be omitted for globals.
31✔
187
         * @param {string} [traceStackEntry] - Trace stack entry for debugging. Can be omitted for globals.
31✔
188
         * @param {Object} dependent - Record for dependent
31✔
189
         * @returns {Object} - Record
31✔
190
         */
31✔
191
        traceDependency(val, name, traceStackEntry, dependent) {
31✔
192
                const record = this.traceValue(val, name, traceStackEntry);
6,874✔
193
                createDependency(dependent, record);
6,874✔
194
                return record;
6,874✔
195
        },
31✔
196

31✔
197
        /**
31✔
198
         * Trace value which is a global, and serialize it.
31✔
199
         * Used for e.g. `Object.defineProperty` where is required during serialization phase.
31✔
200
         * @param {*} val - Global value
31✔
201
         * @returns {Object} - AST node
31✔
202
         */
31✔
203
        traceAndSerializeGlobal(val) {
31✔
204
                return this.serializeValue(this.traceValue(val, null, null));
1,697✔
205
        },
31✔
206

31✔
207
        /**
31✔
208
         * Finalize trace.
31✔
209
         * Tidy up any orphans etc.
31✔
210
         * @returns {undefined}
31✔
211
         */
31✔
212
        finalizeTrace() {
31✔
213
                this.finalizeURLSearchParamses();
1,803✔
214
        }
1,803✔
215
};
31✔
216

31✔
217
/*
31✔
218
 * Everything below is old.
31✔
219
 * TODO Delete it all.
31✔
220
 */
31✔
221

31✔
222
const oldMethods = { // eslint-disable-line no-unused-vars
31✔
223
        serializeValueOld(val, name, trace) {
31✔
224
                return this.withTrace(() => this.serializeValueInner(val, name), trace);
×
225
        },
31✔
226

31✔
227
        serializeValueInner(val, name) {
31✔
228
                if (isPrimitive(val)) {
×
229
                        return {
×
230
                                varNode: serializePrimitive(val), // eslint-disable-line no-undef
×
231
                                node: null,
×
232
                                dependencies: undefined,
×
233
                                dependents: undefined
×
234
                        };
×
235
                }
×
236

×
237
                const {records} = this;
×
238
                let record = records.get(val);
×
239
                if (record) return record;
×
240

×
241
                // Handle globals
×
242
                const globalProps = globals.get(val);
×
243
                if (globalProps) return this.serializeGlobal(val, globalProps);
×
244

×
245
                // Create record and serialize value
×
246
                record = createRecord(name, val);
×
247
                records.set(val, record);
×
248
                record.node = this.serializeThing(val, record);
×
249
                return record;
×
250
        },
31✔
251

31✔
252
        serializeThing(val, record) {
31✔
253
                if (isSymbol(val)) return this.serializeSymbol(val, record);
×
254
                if (isFunction(val)) return this.serializeFunction(val, record);
×
255

×
256
                // Check if is a prototype + define as `fn.prototype` if so
×
257
                const {prototypes} = this;
×
258
                let ctorRecord = prototypes.get(val);
×
259
                if (ctorRecord === undefined) {
×
260
                        // If this is a new prototype, serialize function
×
261
                        const ctor = getConstructor(val);
×
262
                        if (ctor) {
×
263
                                this.serializeValue(ctor, 'constructor', '.constructor');
×
264
                                ctorRecord = prototypes.get(val);
×
265
                        }
×
266
                }
×
267

×
268
                if (ctorRecord !== undefined) {
×
269
                        prototypes.delete(val);
×
270
                        if (ctorRecord) return this.serializePrototype(record, ctorRecord);
×
271
                }
×
272

×
273
                // Serialize
×
274
                assertBug(typeof val === 'object');
×
275

×
276
                const type = typeOf(val);
×
277
                if (type === 'Object') {
×
278
                        // `URL` and `URLSearchParams` are implemented in Javascript in Node internals
×
279
                        // and so cannot be detected with a type check
×
280
                        if (val instanceof URL) return this.serializeURL(val, record);
×
281
                        if (val instanceof URLSearchParams) return this.serializeURLSearchParams(val, record);
×
282
                        return this.serializeObject(val, record);
×
283
                }
×
284
                if (type === 'Array') return this.serializeArray(val, record);
×
285
                if (type === 'RegExp') return this.serializeRegex(val, record);
×
286
                if (type === 'Date') return this.serializeDate(val, record);
×
287
                if (type === 'Set') return this.serializeSet(val, record);
×
288
                if (type === 'Map') return this.serializeMap(val, record);
×
289
                if (type === 'WeakSet') return this.serializeWeakSet(val, record);
×
290
                if (type === 'WeakMap') return this.serializeWeakMap(val, record);
×
291
                if (type === 'WeakRef') return this.serializeWeakRef(val, record);
×
292
                if (type === 'FinalizationRegistry') return this.serializeFinalizationRegistry(val, record);
×
293
                if (typedArrayRegex.test(type)) return this.serializeBuffer(val, type, record);
×
294
                if (type === 'ArrayBuffer') return this.serializeArrayBuffer(val, record);
×
295
                if (type === 'SharedArrayBuffer') return this.serializeSharedArrayBuffer(val, record);
×
296
                if (type === 'String') return this.serializeBoxedString(val, record);
×
297
                if (type === 'Boolean') return this.serializeBoxedBoolean(val, record);
×
298
                if (type === 'Number') return this.serializeBoxedNumber(val, record);
×
299
                if (type === 'BigInt') return this.serializeBoxedBigInt(val, record);
×
300
                if (type === 'Arguments') return this.serializeArguments(val, record);
×
301
                throw new Error(`Cannot serialize ${type}s`);
×
302
        },
31✔
303

31✔
304
        serializeGlobal(val, {type, parent, key}) {
31✔
305
                // Replace `eval` shim created by Babel plugin with global `eval`
×
306
                if (type === EVAL) return this.serializeValueInner(eval, 'eval'); // eslint-disable-line no-eval
×
307

×
308
                const record = createRecord(key || 'x'),
×
309
                        {varNode} = record;
×
310
                this.records.set(val, record);
×
311
                let node, dependentRecord, dependentKey;
×
312
                if (type === VALUE) {
×
313
                        // Lower level global e.g. `Object.assign`
×
314
                        dependentRecord = this.serializeValue(parent, null, `<parent global ${key}>`);
×
315
                        const parentVarNode = dependentRecord.varNode;
×
316
                        const keyNode = createKeyNode(key);
×
317
                        node = t.memberExpression(parentVarNode, keyNode, !t.isIdentifier(keyNode));
×
318
                        varNode.name = `${parentVarNode.name}${upperFirst(key)}`;
×
319
                        dependentKey = 'object';
×
320
                } else if (type === PROTO) {
×
321
                        // PrototypeOf
×
322
                        const parentRecord = this.serializeValue(parent, null, '<parent global prototypeOf>');
×
323
                        const parentVarNode = parentRecord.varNode;
×
324
                        dependentRecord = this.serializeValue(Object.getPrototypeOf);
×
325
                        node = t.callExpression(dependentRecord.varNode, [parentVarNode]);
×
326
                        createDependency(record, parentRecord, node.arguments, 0);
×
327
                        varNode.name = `${parentVarNode.name}Proto`;
×
328
                        dependentKey = 'callee';
×
329
                } else if (type === GETTER || type === SETTER) {
×
330
                        // Getter/setter
×
331
                        const isGetter = type === GETTER;
×
332
                        const typeName = isGetter ? 'getter' : 'setter';
×
333
                        const parentRecord = this.serializeValue(parent, null, `<parent global ${typeName} ${key}>`);
×
334
                        const parentVarNode = parentRecord.varNode;
×
335
                        const keyNode = isNumberKey(key) ? t.numericLiteral(key * 1) : t.stringLiteral(key);
×
336
                        const getDescriptorRecord = this.serializeValue(Object.getOwnPropertyDescriptor);
×
337
                        node = t.memberExpression(
×
338
                                t.callExpression(
×
339
                                        getDescriptorRecord.varNode,
×
340
                                        [parentVarNode, keyNode]
×
341
                                ),
×
342
                                t.identifier(isGetter ? 'get' : 'set')
×
343
                        );
×
344
                        createDependency(record, getDescriptorRecord, node.object, 'callee');
×
345
                        createDependency(record, parentRecord, node.object.arguments, 0);
×
346
                        varNode.name = `${parentVarNode.name}${upperFirst(key)}${upperFirst(typeName)}`;
×
347
                } else if (type === GLOBAL) {
×
348
                        // Top level global e.g. `Object`
×
349
                        node = t.identifier(key);
×
350
                        this.globalVarNames.push(key);
×
351
                } else if (type === MODULE) {
×
352
                        // Built-in module e.g. `require('path')`
×
353
                        node = this.createImportOrRequireNode(key, varNode);
×
354
                } else if (type === COMMON_JS) {
×
355
                        node = this.serializeCommonJsVar(val, key, parent, record);
×
356
                } else if (key === 'undefined') {
×
357
                        node = t.unaryExpression('void', t.numericLiteral(0));
×
358
                } else if (['generatorFunction', 'asyncFunction', 'asyncGeneratorFunction'].includes(key)) {
×
359
                        // `function*() {}` / `async function() {}` / `async function*() {}`
×
360
                        // These are used to access their prototypes
×
361
                        const isGenerator = key !== 'asyncFunction',
×
362
                                isAsync = key !== 'generatorFunction';
×
363
                        node = t.functionExpression(null, [], t.blockStatement([]), isGenerator, isAsync);
×
364
                } else if (key === 'minusInfinity') {
×
365
                        // `-Infinity`
×
366
                        dependentRecord = this.serializeValue(Infinity);
×
367
                        dependentKey = 'argument';
×
368
                        node = t.unaryExpression('-', dependentRecord.varNode);
×
369
                } else if (key === 'URLSymbols') {
×
370
                        // URLSymbols = Object.getOwnPropertySymbols(exampleURL)
×
371
                        dependentRecord = this.serializeValue(Object.getOwnPropertySymbols);
×
372
                        dependentKey = 'callee';
×
373
                        const exampleURLRecord = this.serializeValue(exampleURL, 'url', '<example URL>');
×
374
                        node = t.callExpression(dependentRecord.varNode, [exampleURLRecord.varNode]);
×
375
                        createDependency(record, exampleURLRecord, node.arguments, 0);
×
376
                } else if (key === 'URLContext') {
×
377
                        // URLContext = exampleURL[URLContextSymbol].constructor
×
378
                        const exampleURLRecord = this.serializeValue(exampleURL, 'url', '<example URL>');
×
379
                        const URLContextSymbolRecord = this.serializeValue(URLContextSymbol);
×
380
                        node = t.memberExpression(
×
381
                                t.memberExpression(exampleURLRecord.varNode, URLContextSymbolRecord.varNode, true),
×
382
                                t.identifier('constructor')
×
383
                        );
×
384
                        createDependency(record, exampleURLRecord, node.object, 'object');
×
385
                        createDependency(record, URLContextSymbolRecord, node.object, 'property');
×
386
                } else if (key === 'CallSite') {
×
387
                        dependentRecord = this.serializeRuntime('getCallSite');
×
388
                        node = t.callExpression(dependentRecord.varNode, []);
×
389
                        dependentKey = 'callee';
×
390
                } else {
×
391
                        assertBug(false, `Unexpected global: '${key}' type ${type} - ${val}`);
×
392
                }
×
393

×
394
                record.node = node;
×
395
                if (dependentRecord) createDependency(record, dependentRecord, node, dependentKey);
×
396
                return record;
×
397
        },
31✔
398

31✔
399
        serializeCommonJsVar(val, name, path, record) {
31✔
400
                assert(name === 'module', `Cannot serialize \`require\` or \`import\` (in ${path})`);
×
401

×
402
                // Reference to `module` - substitute `{exports}`
×
403
                return this.serializeObject({exports: val.exports}, record);
×
404
        },
31✔
405

31✔
406
        createImportOrRequireNode(filePath, varNode) {
31✔
407
                if (this.options.format === 'esm') {
×
408
                        return t.importDeclaration([t.importDefaultSpecifier(varNode)], t.stringLiteral(filePath));
×
409
                }
×
410
                return t.callExpression(t.identifier('require'), [t.stringLiteral(filePath)]);
×
411
        }
×
412
};
31✔
413

31✔
414
function getConstructor(obj) {
×
415
        const ctorDescriptor = Object.getOwnPropertyDescriptor(obj, 'constructor');
×
416
        if (!ctorDescriptor) return undefined;
×
417

×
418
        const ctor = ctorDescriptor.value;
×
419
        if (!isFunction(ctor)) return undefined;
×
420

×
421
        const protoDescriptor = Object.getOwnPropertyDescriptor(ctor, 'prototype');
×
422
        if (!protoDescriptor) return undefined;
×
423

×
424
        if (protoDescriptor.value !== obj) return undefined;
×
425

×
426
        return ctor;
×
427
}
×
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