mirror of
https://github.com/bendtherules/completion-viz.git
synced 2026-08-18 21:52:36 +00:00
331 lines
9.7 KiB
JavaScript
331 lines
9.7 KiB
JavaScript
'use strict';
|
|
|
|
const engine262 = require('..');
|
|
|
|
const contexts = [];
|
|
|
|
class InspectorContext {
|
|
constructor(realm) {
|
|
this.realm = realm;
|
|
this.idToObject = new Map();
|
|
this.objectToId = new Map();
|
|
this.objectCounter = 0;
|
|
this.previewStack = [];
|
|
}
|
|
|
|
internObject(object, group = 'default') {
|
|
if (this.objectToId.has(object)) {
|
|
return this.objectToId.get(object);
|
|
}
|
|
const id = `${group}:${this.objectCounter}`;
|
|
this.objectCounter += 1;
|
|
this.idToObject.set(id, object);
|
|
this.objectToId.set(object, id);
|
|
return id;
|
|
}
|
|
|
|
releaseObjectGroup(group) {
|
|
for (const [id, object] of this.idToObject.entries()) {
|
|
if (id.startsWith(group)) {
|
|
this.idToObject.delete(id);
|
|
this.objectToId.delete(object);
|
|
}
|
|
}
|
|
}
|
|
|
|
getObject(objectId) {
|
|
return this.idToObject.get(objectId);
|
|
}
|
|
|
|
toRemoteObject(object, options) {
|
|
const result = {};
|
|
switch (engine262.Type(object)) {
|
|
case 'Object':
|
|
result.objectId = this.internObject(object, options.objectGroup);
|
|
if ('Call' in object) {
|
|
result.type = 'function';
|
|
} else {
|
|
result.type = 'object';
|
|
if ('PromiseState' in object) {
|
|
result.subtype = 'promise';
|
|
} else if ('MapData' in object) {
|
|
result.subtype = 'map';
|
|
} else if ('SetData' in object) {
|
|
result.subtype = 'set';
|
|
} else if ('ErrorData' in object) {
|
|
result.subtype = 'error';
|
|
} else if ('TypedArrayName' in object) {
|
|
result.subtype = 'typedarray';
|
|
} else if ('DataView' in object) {
|
|
result.subtype = 'dataview';
|
|
} else if ('ProxyTarget' in object) {
|
|
result.subtype = 'proxy';
|
|
} else if ('DateValue' in object) {
|
|
result.subtype = 'date';
|
|
} else if ('GeneratorState' in object) {
|
|
result.subtype = 'generator';
|
|
} else if (engine262.IsArray(object) === engine262.Value.true) {
|
|
result.subtype = 'array';
|
|
}
|
|
}
|
|
break;
|
|
case 'Null':
|
|
result.type = 'object';
|
|
result.subtype = 'null';
|
|
result.value = null;
|
|
break;
|
|
case 'Undefined':
|
|
result.type = 'undefined';
|
|
break;
|
|
case 'String':
|
|
result.type = 'string';
|
|
result.value = object.stringValue();
|
|
break;
|
|
case 'Number': {
|
|
result.type = 'number';
|
|
const v = object.numberValue();
|
|
if (!Number.isFinite(v)) {
|
|
result.unserializableValue = v.toString();
|
|
} else {
|
|
result.value = v;
|
|
}
|
|
break;
|
|
}
|
|
case 'Boolean':
|
|
result.type = 'boolean';
|
|
result.value = object.booleanValue();
|
|
break;
|
|
case 'BigInt':
|
|
result.type = 'bigint';
|
|
result.unserializableValue = `${object.bigintValue().toString()}n`;
|
|
break;
|
|
case 'Symbol':
|
|
result.type = 'symbol';
|
|
result.description = object.Description === engine262.Value.undefined
|
|
? undefined
|
|
: object.Description.stringValue();
|
|
break;
|
|
default:
|
|
throw new RangeError();
|
|
}
|
|
if (options.generatePreview
|
|
&& result.type === 'object'
|
|
&& result.subtype !== 'null'
|
|
&& !this.previewStack.includes(object)) {
|
|
this.previewStack.push(object);
|
|
const properties = this.getPropertyPreview(object, options);
|
|
let entries;
|
|
if ('MapData' in object) {
|
|
entries = object.MapData.map((d) => ({
|
|
key: this.toRemoteObject(d.Key, options).preview,
|
|
value: this.toRemoteObject(d.Value, options).preview,
|
|
}));
|
|
}
|
|
this.previewStack.pop();
|
|
result.preview = {
|
|
type: result.type,
|
|
subtype: result.subtype,
|
|
overflow: properties.length > 5,
|
|
properties: properties.slice(0, 5),
|
|
entries,
|
|
};
|
|
}
|
|
return result;
|
|
}
|
|
|
|
getProperties(object, options) {
|
|
const wrap = (v) => this.toRemoteObject(v, options);
|
|
|
|
const properties = [];
|
|
const internalProperties = [];
|
|
|
|
let p = object;
|
|
while (p !== engine262.Value.null) {
|
|
const keys = p.OwnPropertyKeys();
|
|
if (keys instanceof engine262.AbruptCompletion) {
|
|
return keys;
|
|
}
|
|
|
|
for (const key of keys) {
|
|
const desc = p.GetOwnProperty(key);
|
|
if (desc instanceof engine262.AbruptCompletion) {
|
|
return desc;
|
|
}
|
|
if (options.accessorPropertiesOnly && desc.Value) {
|
|
continue;
|
|
}
|
|
const descriptor = {
|
|
name: key.stringValue
|
|
? key.stringValue()
|
|
: undefined,
|
|
value: desc.Value ? wrap(desc.Value) : undefined,
|
|
writable: desc.Writable === engine262.Value.true,
|
|
get: desc.Get ? wrap(desc.Get) : undefined,
|
|
set: desc.Set ? wrap(desc.Set) : undefined,
|
|
configurable: desc.Configurable === engine262.Value.true,
|
|
enumerable: desc.Enumerable === engine262.Value.true,
|
|
wasThrown: false,
|
|
isOwn: p === object,
|
|
symbol: key.stringValue ? undefined : wrap(key),
|
|
};
|
|
properties.push(descriptor);
|
|
}
|
|
|
|
if (options.ownProperties) {
|
|
break;
|
|
}
|
|
p = p.GetPrototypeOf();
|
|
if (p instanceof engine262.AbruptCompletion) {
|
|
return p;
|
|
}
|
|
}
|
|
|
|
if ('PromiseState' in object) {
|
|
internalProperties.push({
|
|
name: '[[PromiseState]]',
|
|
value: {
|
|
type: 'string',
|
|
value: object.PromiseState,
|
|
},
|
|
});
|
|
internalProperties.push({
|
|
name: '[[PromiseResult]]',
|
|
value: wrap(object.PromiseResult),
|
|
});
|
|
}
|
|
|
|
return { properties, internalProperties };
|
|
}
|
|
|
|
getPropertyPreview(object, options) {
|
|
const wrap = (v) => this.toRemoteObject(v, options);
|
|
|
|
const keys = object.OwnPropertyKeys();
|
|
if (keys instanceof engine262.AbruptCompletion) {
|
|
return keys;
|
|
}
|
|
|
|
const properties = [];
|
|
for (const key of keys) {
|
|
const desc = object.GetOwnProperty(key);
|
|
if (desc instanceof engine262.AbruptCompletion) {
|
|
return desc;
|
|
}
|
|
const descriptor = {
|
|
name: key.stringValue
|
|
? key.stringValue()
|
|
: `Symbol(${key.Description.stringValue ? key.Description.stringValue() : ''})`,
|
|
};
|
|
if (desc.Value) {
|
|
desc.valuePreview = wrap(desc.Value).preview;
|
|
switch (engine262.Type(desc.Value)) {
|
|
case 'Object':
|
|
if ('Call' in desc.Value) {
|
|
descriptor.type = 'function';
|
|
} else {
|
|
descriptor.type = 'object';
|
|
if ('PromiseState' in desc.Value) {
|
|
descriptor.subtype = 'promise';
|
|
} else if ('MapData' in desc.Value) {
|
|
descriptor.subtype = 'map';
|
|
} else if ('SetData' in desc.Value) {
|
|
descriptor.subtype = 'set';
|
|
} else if ('ErrorData' in desc.Value) {
|
|
descriptor.subtype = 'error';
|
|
} else if ('TypedArrayName' in desc.Value) {
|
|
descriptor.subtype = 'typedarray';
|
|
} else if ('DataView' in desc.Value) {
|
|
descriptor.subtype = 'dataview';
|
|
} else if ('ProxyTarget' in desc.Value) {
|
|
descriptor.subtype = 'proxy';
|
|
} else if ('DateValue' in desc.Value) {
|
|
descriptor.subtype = 'date';
|
|
} else if ('GeneratorState' in desc.Value) {
|
|
descriptor.subtype = 'generator';
|
|
} else if (engine262.IsArray(desc.Value) === engine262.Value.true) {
|
|
descriptor.subtype = 'array';
|
|
}
|
|
}
|
|
break;
|
|
case 'Null':
|
|
descriptor.type = 'object';
|
|
descriptor.subtype = 'null';
|
|
descriptor.value = 'null';
|
|
break;
|
|
case 'Undefined':
|
|
descriptor.type = 'undefined';
|
|
descriptor.value = 'undefined';
|
|
break;
|
|
case 'String':
|
|
descriptor.type = 'string';
|
|
descriptor.value = desc.Value.stringValue();
|
|
break;
|
|
case 'Number': {
|
|
descriptor.type = 'number';
|
|
descriptor.value = desc.Value.numberValue().toString();
|
|
break;
|
|
}
|
|
case 'Boolean':
|
|
descriptor.type = 'boolean';
|
|
descriptor.value = desc.Value.booleanValue().toString();
|
|
break;
|
|
case 'BigInt':
|
|
descriptor.type = 'bigint';
|
|
descriptor.value = `${desc.Value.bigintValue().toString()}n`;
|
|
break;
|
|
case 'Symbol': {
|
|
descriptor.type = 'symbol';
|
|
const description = desc.Value.Description === engine262.Value.undefined
|
|
? ''
|
|
: desc.Value.Description.stringValue();
|
|
descriptor.value = `Symbol(${description})`;
|
|
break;
|
|
}
|
|
default:
|
|
throw new RangeError();
|
|
}
|
|
} else {
|
|
desc.type = 'accessor';
|
|
}
|
|
properties.push(descriptor);
|
|
}
|
|
|
|
if ('PromiseState' in object) {
|
|
properties.push({
|
|
name: '[[PromiseState]]',
|
|
type: 'string',
|
|
value: object.PromiseState,
|
|
});
|
|
}
|
|
|
|
return properties;
|
|
}
|
|
|
|
createEvaluationResult(completion, options) {
|
|
if (completion instanceof engine262.AbruptCompletion) {
|
|
return {
|
|
exceptionDetails: {
|
|
text: 'uh oh',
|
|
lineNumber: 0,
|
|
columnNumber: 0,
|
|
exception: this.toRemoteObject(completion.Value, options),
|
|
},
|
|
};
|
|
} else {
|
|
return {
|
|
result: this.toRemoteObject(completion.Value, options),
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
function attachRealm(realm) {
|
|
contexts.push(new InspectorContext(realm));
|
|
}
|
|
|
|
function getContext(id) {
|
|
return contexts[id] || contexts[0];
|
|
}
|
|
|
|
module.exports = { attachRealm, getContext };
|