diff --git a/lib/serialize/arguments.js b/lib/serialize/arguments.js index e9454a5b..678d1611 100644 --- a/lib/serialize/arguments.js +++ b/lib/serialize/arguments.js @@ -6,65 +6,108 @@ 'use strict'; // Modules -const t = require('@babel/types'); +const t = require('@babel/types'), + {isNumber} = require('is-it-type'); // Imports -const {createDependency} = require('./records.js'), - {recordIsCircular, isIntegerKey} = require('./utils.js'); +const {ARGUMENTS_TYPE, registerSerializer} = require('./types.js'); // Exports -module.exports = function serializeArguments(args, record) { - const varName = record.varNode.name, - undefinedRecord = this.serializeValue(undefined), - argumentNodes = [], - defaultProps = []; - for (const key of Object.getOwnPropertyNames(args)) { - if (!isIntegerKey(key)) break; +module.exports = traceArguments; - // Fill gaps (deleted elements) with undefined - const index = key * 1; - let nextIndex = argumentNodes.length; +/** + * Trace arguments object. + * @this {Object} Serializer + * @param {Object} args - Arguments object + * @param {Object} record - Record + * @returns {number} - Type ID + */ +function traceArguments(args, record) { + this.traceProperties(args, record, argumentsShouldSkipKey); + return ARGUMENTS_TYPE; +} + +/** + * Serialize arguments object. + * Use runtime function `createArguments()` to generate the arguments object. + * @this {Object} Serializer + * @param {Object} record - Record + * @returns {Object} - AST node + */ +function serializeArguments(record) { + const argumentNodes = [], + existingProps = []; + const pushUndefinedArg = (index) => { + argumentNodes.push(this.serializeValue(this.undefinedRecord)); + existingProps.push({ + key: index, + valRecord: this.undefinedRecord, + getRecord: undefined, + setRecord: undefined, + writable: true, + enumerable: true, + configurable: true + }); + }; + + // Get all arguments which can be passed to `createArguments()` + let nextIndex = 0; + for (const prop of record.props) { + // Skip non-integer keys + const index = prop.key; + if (!isNumber(index)) break; + + // Fill gaps (deleted properties) with `undefined` while (index !== nextIndex) { - argumentNodes[nextIndex] = undefinedRecord.varNode; - createDependency(record, undefinedRecord, argumentNodes, nextIndex); - defaultProps[nextIndex] = { - name: `${nextIndex}`, value: undefined, writable: true, enumerable: true, configurable: true - }; + pushUndefinedArg(nextIndex); nextIndex++; } - // Add value - const descriptor = Object.getOwnPropertyDescriptor(args, key); - let val = descriptor.value, - valRecord = this.serializeValue(val, `${varName}_${key}`, `[${key}]`); - if (recordIsCircular(valRecord)) { - // Circular reference - leave it to `wrapWithProperties()` to assign value - val = undefined; - valRecord = undefinedRecord; + const {valRecord} = prop; + if (valRecord && !valRecord.isCircular) { + // Value is present (not a getter/setter) and is not circular - add to arguments + argumentNodes.push(this.serializeValue(valRecord)); + existingProps.push({ + key: index, + valRecord, + getRecord: undefined, + setRecord: undefined, + writable: true, + enumerable: true, + configurable: true + }); + } else { + // Add `undefined` to arguments + pushUndefinedArg(index); } - argumentNodes[index] = valRecord.varNode; - createDependency(record, valRecord, argumentNodes, index); - defaultProps[index] = { - name: key, value: val, writable: true, enumerable: true, configurable: true - }; + nextIndex++; } - // Create call to `createArguments` function - const createArgumentsRecord = this.serializeRuntime('createArguments'); - const node = t.callExpression(createArgumentsRecord.varNode, argumentNodes); - createDependency(record, createArgumentsRecord, node, 'callee'); - - // Wrap in properties - defaultProps.push({ - name: 'length', value: argumentNodes.length, writable: true, enumerable: false, configurable: true + // Add `length` to existing props + existingProps.push({ + key: 'length', + valRecord: this.traceAndSerializeGlobal(nextIndex), + getRecord: undefined, + setRecord: undefined, + writable: true, + enumerable: false, + configurable: true }); - return this.wrapWithProperties( - args, record, node, Object.prototype, defaultProps, argumentsShouldSkipKey - ); -}; + // Use runtime function `createArguments` to create arguments object. + // `createArguments(x, y, z)` + // TODO: Write `traceAndSerializeRuntime()`. + // Will require an extra pass to figure out what output the runtime function goes in, + // and add it to that output file (or create a new output file), after globals. + const createArgumentsNode = this.traceAndSerializeRuntime('createArguments', record); // TODO + const node = t.callExpression(createArgumentsNode, argumentNodes); + + // Wrap will additional properties + delete deleted properties + return this.wrapWithProperties(node, record, this.objectPrototypeRecord, existingProps); +} +registerSerializer(ARGUMENTS_TYPE, serializeArguments); function argumentsShouldSkipKey(key) { // TODO: Would be better to include `Symbol.iterator` in default props diff --git a/lib/serialize/serializer.js b/lib/serialize/serializer.js index 54b17efc..0e57b30d 100644 --- a/lib/serialize/serializer.js +++ b/lib/serialize/serializer.js @@ -22,7 +22,7 @@ const serializeMethods = require('./serialize.js'), otherMethods = require('./other.js'), blockMethods = require('./blocks.js'), splitMethods = require('./split.js').methods, - serializeArguments = require('./arguments.js'), + traceArguments = require('./arguments.js'), parseFunction = require('./parseFunction.js'), serializeRuntime = require('./runtime.js'), {createMangledVarNameTransform, createUnmangledVarNameTransform} = require('./varNames.js'), @@ -75,7 +75,7 @@ Object.assign( otherMethods, blockMethods, splitMethods, - {serializeArguments, parseFunction, serializeRuntime} + {traceArguments, parseFunction, serializeRuntime} ); module.exports = Serializer; diff --git a/lib/serialize/trace.js b/lib/serialize/trace.js index 6e5edf1e..fa0b4e82 100644 --- a/lib/serialize/trace.js +++ b/lib/serialize/trace.js @@ -167,7 +167,7 @@ module.exports = { if (objType === 'Number') return this.traceBoxedNumber(val, record); if (objType === 'BigInt') return this.traceBoxedBigInt(val, record); if (objType === 'Symbol') return this.traceBoxedSymbol(val, record); - // if (objType === 'Arguments') return this.traceArguments(val, record); + if (objType === 'Arguments') return this.traceArguments(val, record); throw new Error(`Cannot serialize ${objType}s`); }, diff --git a/lib/serialize/types.js b/lib/serialize/types.js index 2e2958a6..d1c703d7 100644 --- a/lib/serialize/types.js +++ b/lib/serialize/types.js @@ -35,6 +35,7 @@ const NO_TYPE = 0, BOXED_NUMBER_TYPE = OBJECT_TYPE | 12, BOXED_BIGINT_TYPE = OBJECT_TYPE | 13, BOXED_SYMBOL_TYPE = OBJECT_TYPE | 14, + ARGUMENTS_TYPE = OBJECT_TYPE | 15, FUNCTION_TYPE = 64, METHOD_TYPE = FUNCTION_TYPE | 1, GLOBAL_TYPE = 128, @@ -80,6 +81,7 @@ module.exports = { BOXED_NUMBER_TYPE, BOXED_BIGINT_TYPE, BOXED_SYMBOL_TYPE, + ARGUMENTS_TYPE, FUNCTION_TYPE, METHOD_TYPE, GLOBAL_TYPE,