Skip to content

Commit

Permalink
Support Maps and WeakMaps
Browse files Browse the repository at this point in the history
  • Loading branch information
overlookmotel committed Sep 28, 2023
1 parent 8dd5f00 commit b355e50
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 47 deletions.
138 changes: 96 additions & 42 deletions lib/serialize/setsMaps.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,8 @@
const t = require('@babel/types');

// Imports
const {createDependency, createAssignment} = require('./records.js'),
{weakSets, weakMaps} = require('../shared/internal.js'),
{SET_TYPE, WEAK_SET_TYPE, registerSerializer} = require('./types.js'),
{recordIsCircular} = require('./utils.js');
const {SET_TYPE, WEAK_SET_TYPE, MAP_TYPE, WEAK_MAP_TYPE, registerSerializer} = require('./types.js'),
{weakSets, weakMaps} = require('../shared/internal.js');

// Exports

Expand Down Expand Up @@ -49,53 +47,33 @@ module.exports = {
return WEAK_SET_TYPE;
},

serializeMap(map, record) {
return this.serializeMapLike(map, [...mapEntries.call(map)], Map, record);
/**
* Trace Map.
* @param {Map} map - Map object
* @param {Object} record - Record
* @returns {number} - Type ID
*/
traceMap(map, record) {
traceMapOrWeakMap.call(this, map, [...mapEntries.call(map)], record);
return MAP_TYPE;
},

serializeWeakMap(map, record) {
/**
* Trace Map.
* @param {Map} map - Map object
* @param {Object} record - Record
* @returns {number} - Type ID
*/
traceWeakMap(map, record) {
const {refs, mapping} = weakMaps.get(map);
const entries = [];
for (const ref of refs) {
const key = ref.deref();
if (key) entries.push([key, mapping.get(key).value]);
}

return this.serializeMapLike(map, entries, WeakMap, record);
},

serializeMapLike(map, entries, ctor, record) {
const {varNode} = record,
varName = varNode.name;
let isCircular = false;
const entryNodes = [];
entries.forEach(([key, val], index) => {
const keyRecord = this.serializeValue(key, `${varName}Keys_${index}`, `<Map key ${index}>`);
const valRecord = this.serializeValue(val, `${varName}Values_${index}`, `<Map value ${index}>`);
if (!isCircular && (recordIsCircular(keyRecord) || recordIsCircular(valRecord))) isCircular = true;

const pair = [keyRecord.varNode, valRecord.varNode];
let dependent;
if (isCircular) {
const memberNode = t.memberExpression(varNode, t.identifier('set'));
const assignmentNode = t.callExpression(memberNode, pair);
dependent = createAssignment(record, assignmentNode, memberNode, 'object');
} else {
entryNodes[index] = t.arrayExpression(pair);
dependent = record;
}

createDependency(dependent, keyRecord, pair, 0);
createDependency(dependent, valRecord, pair, 1);
});

const mapCtorRecord = this.serializeValue(ctor);
const node = t.newExpression(
mapCtorRecord.varNode,
entryNodes.length > 0 ? [t.arrayExpression(entryNodes)] : []
);
createDependency(record, mapCtorRecord, node, 'callee');
return this.wrapWithProperties(map, record, node, ctor.prototype);
traceMapOrWeakMap.call(this, map, entries, record);
return WEAK_MAP_TYPE;
}
};

Expand Down Expand Up @@ -172,3 +150,79 @@ function serializeSetOrWeakSet(record, ctor, protoRecord, isOrdered) {
node = t.newExpression(ctorNode, entryNodes.length > 0 ? [t.arrayExpression(entryNodes)] : []);
return this.wrapWithProperties(node, record, protoRecord, null);
}

/**
* Trace Map or WeakMap.
* @this {Object} Serializer
* @param {Map|WeakMap} map - Map/WeakMap object
* @param {Array<*>} entries - Map/WeakMap entries
* @param {Object} record - Record
* @returns {undefined}
*/
function traceMapOrWeakMap(map, entries, record) {
const entryRecords = entries.map(([key, val], index) => ({
keyRecord: this.traceDependency(key, `${record.name}Keys_${index}`, `<Map key ${index}>`, record),
valRecord: this.traceDependency(val, `${record.name}Values_${index}`, `<Map value ${index}>`, record)
}));
record.extra = {entryRecords};
this.traceProperties(map, record, undefined);
}

/**
* Serialize Map.
* @this {Object} Serializer
* @param {Object} record - Record
* @returns {Object} - AST node
*/
function serializeMap(record) {
return serializeMapOrWeakMap.call(this, record, Map, this.mapPrototypeRecord, true);
}
registerSerializer(MAP_TYPE, serializeMap);

/**
* Serialize WeakMap.
* @this {Object} Serializer
* @param {Object} record - Record
* @returns {Object} - AST node
*/
function serializeWeakMap(record) {
return serializeMapOrWeakMap.call(this, record, WeakMap, this.weakMapPrototypeRecord, false);
}
registerSerializer(WEAK_MAP_TYPE, serializeWeakMap);

/**
* Serialize Map or WeakMap.
* @this {Object} Serializer
* @param {Object} record - Record
* @param {Function} ctor - Constructor - `Map` or `WeakMap`
* @param {Object} protoRecord - Record for prototype
* @param {boolean} isOrdered - `true` if entries are ordered (`true` for Maps, `false` for WeakMaps)
* @returns {undefined}
*/
function serializeMapOrWeakMap(record, ctor, protoRecord, isOrdered) {
const {varNode} = record,
entryNodes = [];
let isCircular = false;
for (const {keyRecord, valRecord} of record.extra.entryRecords) {
if (!isOrdered) {
isCircular = keyRecord.isCircular || valRecord.isCircular;
} else if (!isCircular && (keyRecord.isCircular || valRecord.isCircular)) {
isCircular = true;
}

const keyNode = this.serializeValue(keyRecord),
valNode = this.serializeValue(valRecord);
if (isCircular) {
// `map.set(..., ...)`
this.assignmentNodes.push(t.expressionStatement(
t.callExpression(t.memberExpression(varNode, t.identifier('set')), [keyNode, valNode])
));
} else {
entryNodes.push(t.arrayExpression([keyNode, valNode]));
}
}

const ctorNode = this.traceAndSerializeGlobal(ctor),
node = t.newExpression(ctorNode, entryNodes.length > 0 ? [t.arrayExpression(entryNodes)] : []);
return this.wrapWithProperties(node, record, protoRecord, null);
}
6 changes: 4 additions & 2 deletions lib/serialize/trace.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ module.exports = {
this.regexpPrototypeRecord = this.traceValue(RegExp.prototype, null, null);
this.datePrototypeRecord = this.traceValue(Date.prototype, null, null);
this.setPrototypeRecord = this.traceValue(Set.prototype, null, null);
this.mapPrototypeRecord = this.traceValue(Map.prototype, null, null);
this.weakSetPrototypeRecord = this.traceValue(WeakSet.prototype, null, null);
this.weakMapPrototypeRecord = this.traceValue(WeakMap.prototype, null, null);
this.urlPrototypeRecord = this.traceValue(URL.prototype, null, null);
this.urlSearchParamsPrototypeRecord = this.traceValue(URLSearchParams.prototype, null, null);

Expand Down Expand Up @@ -147,9 +149,9 @@ module.exports = {
if (objType === 'RegExp') return this.traceRegexp(val, record);
if (objType === 'Date') return this.traceDate(val, record);
if (objType === 'Set') return this.traceSet(val, record);
// if (objType === 'Map') return this.traceMap(val, record);
if (objType === 'Map') return this.traceMap(val, record);
if (objType === 'WeakSet') return this.traceWeakSet(val, record);
// if (objType === 'WeakMap') return this.traceWeakMap(val, record);
if (objType === 'WeakMap') return this.traceWeakMap(val, record);
if (objType === 'WeakRef') return this.traceWeakRef(val, record);
if (objType === 'FinalizationRegistry') return this.traceFinalizationRegistry(val, record);
// if (typedArrayRegex.test(objType)) return this.traceBuffer(val, objType, record);
Expand Down
6 changes: 3 additions & 3 deletions test/maps.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const {itSerializes, itSerializesEqual} = require('./support/index.js');

// Tests

describe.skip('Maps', () => {
describe('Maps', () => {
itSerializesEqual('no entries', {
in: () => new Map(),
out: 'new Map',
Expand Down Expand Up @@ -93,7 +93,7 @@ describe.skip('Maps', () => {
});
});

describe('map subclass', () => {
describe.skip('map subclass', () => {
itSerializes('no entries', {
in() {
class M extends Map {}
Expand Down Expand Up @@ -196,7 +196,7 @@ describe.skip('Maps', () => {
});
});

describe.skip('WeakMaps', () => {
describe('WeakMaps', () => {
it('calling `WeakMap()` without `new` throws error', () => {
expect(() => WeakMap()).toThrowWithMessage(
TypeError, "Class constructor WeakMap cannot be invoked without 'new'"
Expand Down

0 comments on commit b355e50

Please sign in to comment.