diff --git a/lib/serialize/other.js b/lib/serialize/other.js index 2916e3d4..b2f978ff 100644 --- a/lib/serialize/other.js +++ b/lib/serialize/other.js @@ -11,6 +11,7 @@ const t = require('@babel/types'); // Imports const {createDependency} = require('./records.js'), {URLContextSymbol, URLQuerySymbol} = require('../shared/globals.js'), + {REGEXP_TYPE, DATE_TYPE, registerSerializer} = require('./types.js'), {firstMapKey} = require('./utils.js'); // Exports @@ -37,33 +38,7 @@ module.exports = { traceRegexp(regexp, record) { this.traceProperties(regexp, record, undefined); record.extra = {regexp: regexSourceGetter.call(regexp), flags: regexFlagsGetter.call(regexp)}; - return this.serializeRegexp; - }, - - /** - * Serialize RegExp. - * @param {Object} record - Record - * @param {Object} record.extra - Extra props object - * @param {string} record.extra.regexp - Regex source string - * @param {string} record.extra.flags - Flags - * @returns {Object} - AST node - */ - serializeRegexp(record) { - const {extra} = record; - const node = t.regExpLiteral(extra.regexp, extra.flags); - /* - // TODO: Remove `lastIndex` property if matches default - const defaultProps = [{ - key: 'lastIndex', - val: 0, - get: undefined, - set: undefined, - writable: true, - enumerable: false, - configurable: false - }]; - */ - return this.wrapWithProperties(node, record, regexpProtoSetters); + return REGEXP_TYPE; }, /** @@ -74,28 +49,9 @@ module.exports = { * @returns {Function} - Serializer function */ traceDate(date, record) { - const dateCtorRecord = this.traceValue(Date); - createDependency(record, dateCtorRecord); - this.traceProperties(date, record, undefined); record.extra = {time: dateGetTime.call(date)}; - return this.serializeDate; - }, - - /** - * Serialize Date. - * `Date` constructor will be 1st dependency. - * @param {Object} record - Record - * @param {Object} record.extra - Extra props object - * @param {number} record.extra.time - Time (from `date.getTime()`) - * @returns {Object} - AST node - */ - serializeDate(record) { - // `new Date(...)` - // TODO: Handle invalid dates (`record.extra.time` is `NaN`) - const dateCtorNode = this.serializeValue(firstMapKey(record.dependencies)); - const node = t.newExpression(dateCtorNode, [t.numericLiteral(record.extra.time)]); - return this.wrapWithProperties(node, record, dateProtoSetters); + return DATE_TYPE; }, /** @@ -200,3 +156,46 @@ module.exports = { throw new Error('Cannot serialize FinalizationRegistrys'); } }; + +/** + * Serialize RegExp. + * @this {Object} Serializer + * @param {Object} record - Record + * @param {Object} record.extra - Extra props object + * @param {string} record.extra.regexp - Regex source string + * @param {string} record.extra.flags - Flags + * @returns {Object} - AST node + */ +function serializeRegexp(record) { + const node = t.regExpLiteral(record.extra.regexp, record.extra.flags); + + const existingProps = [{ + key: 'lastIndex', + valRecord: this.traceValue(0, null, null), + getRecord: undefined, + setRecord: undefined, + writable: true, + enumerable: false, + configurable: false + }]; + + return this.wrapWithProperties(node, record, this.regexpPrototypeRecord, existingProps); +} +registerSerializer(REGEXP_TYPE, serializeRegexp); + +/** + * Serialize Date. + * @this {Object} Serializer + * @param {Object} record - Record + * @param {Object} record.extra - Extra props object + * @param {number} record.extra.time - Time (from `date.getTime()`) + * @returns {Object} - AST node + */ +function serializeDate(record) { + // `new Date(...)` + const {time} = record.extra, + timeNode = Number.isNaN(time) ? this.traceAndSerializeGlobal(NaN) : t.numericLiteral(time); + const node = t.newExpression(this.traceAndSerializeGlobal(Date), [timeNode]); + return this.wrapWithProperties(node, record, this.datePrototypeRecord, null); +} +registerSerializer(DATE_TYPE, serializeDate); diff --git a/lib/serialize/trace.js b/lib/serialize/trace.js index 6ef17654..21f80150 100644 --- a/lib/serialize/trace.js +++ b/lib/serialize/trace.js @@ -36,9 +36,12 @@ module.exports = { // TODO: Next properties should be defined elsewhere this.nullRecord = this.traceValue(null, null, null); this.nullRecord.setters = new Set(); + this.undefinedRecord = this.traceValue(undefined, null, null); this.objectPrototypeRecord = this.traceValue(Object.prototype, null, null); this.arrayPrototypeRecord = this.traceValue(Array.prototype, null, null); + this.regexpPrototypeRecord = this.traceValue(RegExp.prototype, null, null); + this.datePrototypeRecord = this.traceValue(Date.prototype, null, null); this.minusZeroRecord = null; }, diff --git a/lib/serialize/types.js b/lib/serialize/types.js index 89d25ab9..1bef2ec9 100644 --- a/lib/serialize/types.js +++ b/lib/serialize/types.js @@ -19,6 +19,8 @@ const NO_TYPE = 0, NEGATIVE_TYPE = PRIMITIVE_TYPE | 6, // TODO: Should this be a primitive? OBJECT_TYPE = 16, ARRAY_TYPE = OBJECT_TYPE | 1, + REGEXP_TYPE = OBJECT_TYPE | 2, + DATE_TYPE = OBJECT_TYPE | 3, FUNCTION_TYPE = 32, METHOD_TYPE = FUNCTION_TYPE | 1, GLOBAL_TYPE = 64, @@ -51,6 +53,8 @@ module.exports = { NEGATIVE_TYPE, OBJECT_TYPE, ARRAY_TYPE, + REGEXP_TYPE, + DATE_TYPE, FUNCTION_TYPE, METHOD_TYPE, GLOBAL_TYPE, diff --git a/test/other.test.js b/test/other.test.js index 7e8e4391..6e4fa9de 100644 --- a/test/other.test.js +++ b/test/other.test.js @@ -17,7 +17,7 @@ const {itSerializes, itSerializesEqual} = require('./support/index.js'); const urlsHaveContext = parseNodeVersion(process.version).major < 20, itSerializesEqualIfUrlsHaveContext = urlsHaveContext ? itSerializesEqual : itSerializesEqual.skip; -describe.skip('RegExps', () => { +describe('RegExps', () => { itSerializesEqual('with no flags', { in: () => /^foo$/, out: '/^foo$/', @@ -49,7 +49,7 @@ describe.skip('RegExps', () => { } }); - itSerializesEqual('RegExp subclass', { + itSerializesEqual.skip('RegExp subclass', { in() { class R extends RegExp {} return new R('^foo$', 'gu'); @@ -77,7 +77,7 @@ describe.skip('RegExps', () => { }); }); -describe.skip('Dates', () => { +describe('Dates', () => { itSerializesEqual('without extra props', { in: () => new Date('01/01/2020 12:00:00'), out: 'new Date(1577880000000)', @@ -101,7 +101,17 @@ describe.skip('Dates', () => { } }); - itSerializesEqual('Date subclass', { + itSerializes('invalid date', { + in: () => new Date(Number.MAX_SAFE_INTEGER), + out: 'new Date(NaN)', + validate(date) { + expect(date).toHavePrototype(Date.prototype); + expect(date).not.toBeValidDate(); + expect(date.getTime()).toBeNaN(); + } + }); + + itSerializesEqual.skip('Date subclass', { in() { class D extends Date {} return new D('01/01/2020 12:00:00');