"use strict";
// Format is the base class for all other comparators, and used
// directly by comparators for their "simplePrint" methods.
// It doesn't do comparison, just formatting.
Object.defineProperty(exports, "__esModule", { value: true });
exports.Format = void 0;
const styles_js_1 = require("./styles.js");
const arrayFrom = (obj) => {
try {
return Array.from(obj);
}
catch (_) {
return null;
}
};
const { toString } = Object.prototype;
const objToString = (obj) => toString.call(obj);
class Format {
constructor(obj, options = {}) {
this.options = options;
this.parent = options.parent || null;
this.memo = null;
this.sort = !!options.sort;
if (typeof options.seen === 'function') {
this.seen = options.seen;
}
this.id = null;
this.idCounter = 0;
this.idMap = this.parent ? this.parent.idMap : new Map();
const style = this.parent
? this.parent.style
: styles_js_1.styles[options.style || 'pretty'];
if (!style) {
throw new TypeError(`unknown style: ${options.style}`);
}
this.style = style;
this.bufferChunkSize =
this.style.bufferChunkSize === Infinity
? Infinity
: options.bufferChunkSize ||
this.style.bufferChunkSize;
// for printing child values of pojos and maps
this.key = options.key;
// for printing Map keys
this.isKey = !!options.isKey;
if (this.isKey &&
!(this.parent && this.parent.isMap())) {
throw new Error('isKey should only be set for Map keys');
}
this.level = this.parent ? this.parent.level + 1 : 0;
this.indent = this.parent
? this.parent.indent
: typeof options.indent === 'string'
? options.indent
: ' ';
this.match = true;
this.object = obj;
this.expect = obj;
}
incId() {
return this.parent
? this.parent.incId()
: (this.idCounter += 1);
}
getId() {
if (this.id) {
return this.id;
}
const fromMap = this.idMap.get(this.object);
if (fromMap) {
return (this.id = fromMap);
}
const id = this.incId();
this.idMap.set(this.object, id);
return (this.id = id);
}
seen(_) {
if (!this.object || typeof this.object !== 'object') {
return false;
}
for (let p = this.parent; p; p = p.parent) {
if (p.object === this.object) {
p.id = p.id || p.getId();
return p;
}
}
return false;
}
child(obj, options, cls) {
// This raises an error because ts thinks 'typeof Class' is
// a normal function, not an instantiable class. Ignore.
//@ts-expect-error
return new (cls || this.constructor)(obj, {
...this.options,
isKey: false,
provisional: false,
...options,
parent: this,
});
}
// type testing methods
isError() {
return this.object instanceof Error;
}
isArguments() {
return objToString(this.object) === '[object Arguments]';
}
isArray() {
return (Array.isArray(this.object) ||
this.isArguments() ||
this.isIterable());
}
// technically this means "is an iterable we don't have another fit for"
// sets, arrays, maps, and streams all handled specially.
isIterable() {
return (this.object &&
typeof this.object === 'object' &&
!this.isSet() &&
!this.isMap() &&
!this.isStream() &&
typeof this.object[Symbol.iterator] === 'function');
}
isKeyless() {
return (!this.parent ||
this.parent.isSet() ||
this.parent.isArray() ||
this.parent.isString() ||
this.isKey);
}
isStream() {
const s = this.object;
return (s &&
typeof s === 'object' &&
(typeof s.pipe === 'function' || // readable
typeof s.pipeTo === 'function' || // whatwg readable
(typeof s.write === 'function' &&
typeof s.end === 'function')) // writable
);
}
isMap() {
return this.object instanceof Map;
}
isSet() {
return this.object instanceof Set;
}
isBuffer() {
return Buffer.isBuffer(this.object);
}
isString() {
return typeof this.object === 'string';
}
// end type checking functions
getClass() {
const ts = objToString(this.object).slice(8, -1);
return this.object.constructor !== Object &&
this.object.constructor &&
this.object.constructor.name &&
this.object.constructor.name !== ts
? this.object.constructor.name
: !Object.getPrototypeOf(this.object)
? 'Null Object'
: ts;
}
get objectAsArray() {
// return the object as an actual array, if we can
const value = Array.isArray(this.object)
? this.object
: this.isArray()
? arrayFrom(this.object)
: null;
if (value === null) {
this.isArray = () => false;
}
Object.defineProperty(this, 'objectAsArray', {
value,
configurable: true,
});
return value;
}
// printing methods
// Change from v5: ONLY the print() method returns a string
// everything else mutates this.memo, so that child classes
// can track both this.memo AND this.expectMemo, and then calculate
// a diff at the end.
print() {
if (this.memo !== null) {
return this.memo;
}
this.memo = '';
const seen = this.seen(this.object);
if (seen) {
this.printCircular(seen);
}
else {
this.printValue();
}
this.printStart();
this.printEnd();
// this should be impossible
/* c8 ignore start */
if (typeof this.memo !== 'string') {
throw new Error('failed to build memo string in print() method');
}
/* c8 ignore stop */
return this.memo;
}
printValue() {
switch (typeof this.object) {
case 'undefined':
this.printUndefined();
break;
case 'object':
if (!this.object) {
this.printNull();
}
else if (this.object instanceof RegExp) {
this.printRegExp();
}
else if (this.object instanceof Date) {
this.printDate();
}
else {
this.printCollection();
}
break;
case 'symbol':
this.printSymbol();
break;
case 'bigint':
this.printBigInt();
break;
case 'string':
this.printString();
break;
case 'boolean':
this.printBoolean();
break;
case 'number':
this.printNumber();
break;
case 'function':
this.printFn();
break;
}
}
printDate() {
this.memo += this.object.toISOString();
}
printRegExp() {
this.memo += this.object.toString();
}
printUndefined() {
this.memo += 'undefined';
}
printNull() {
this.memo += 'null';
}
printSymbol() {
this.memo += this.object.toString();
}
printBigInt() {
this.memo += this.object.toString() + 'n';
}
printBoolean() {
this.memo += JSON.stringify(this.object);
}
printNumber() {
this.memo += JSON.stringify(this.object);
}
printStart() {
if (!this.parent) {
this.memo = this.nodeId() + this.memo;
return;
}
const indent = this.isKey ? '' : this.indentLevel();
const key = this.isKeyless() ? '' : this.getKey();
const sep = !key
? ''
: this.parent && this.parent.isMap()
? this.style.mapKeyValSep()
: this.style.pojoKeyValSep();
this.memo =
this.style.start(indent, key, sep) +
this.nodeId() +
this.memo;
}
printEnd() {
if (!this.parent) {
return;
}
this.memo +=
this.isKey || !this.parent
? ''
: this.parent.isMap()
? this.style.mapEntrySep()
: this.parent.isBuffer()
? ''
: this.parent.isArray()
? this.style.arrayEntrySep()
: this.parent.isSet()
? this.style.setEntrySep()
: this.parent.isString()
? ''
: this.style.pojoEntrySep();
}
getKey() {
return this.parent && this.parent.isMap()
? this.style.mapKeyStart() +
this.parent
.child(this.key, { isKey: true }, Format)
.print()
: JSON.stringify(this.key);
}
printCircular(seen) {
this.memo += this.style.circular(seen);
}
indentLevel(n = 0) {
return this.indent.repeat(this.level + n);
}
printCollection() {
return this.isError()
? this.printError()
: this.isSet()
? this.printSet()
: this.isMap()
? this.printMap()
: this.isBuffer()
? this.printBuffer()
: this.isArray() && this.objectAsArray
? this.printArray()
: // TODO streams, JSX
this.printPojo();
}
nodeId() {
return this.id ? this.style.nodeId(this.id) : '';
}
printBuffer() {
if (this.parent && this.parent.isBuffer()) {
this.memo +=
this.style.bufferKey(this.key) +
this.style.bufferKeySep() +
this.style.bufferLine(this.object, this.bufferChunkSize);
}
else if (this.object.length === 0) {
this.memo += this.style.bufferEmpty();
}
else if (this.bufferIsShort()) {
this.memo +=
this.style.bufferStart() +
this.style.bufferBody(this.object) +
this.style.bufferEnd(this.object);
}
else {
this.printBufferHead();
this.printBufferBody();
this.printBufferTail();
}
}
bufferIsShort() {
return this.object.length < this.bufferChunkSize + 5;
}
printBufferHead() {
this.memo += this.style.bufferHead();
}
printBufferBody() {
const c = this.bufferChunkSize;
let i;
for (i = 0; i < this.object.length - c; i += c) {
this.printBufferLine(i, this.object.slice(i, i + c));
}
this.printBufferLastLine(i, this.object.slice(i, i + c));
}
printBufferLine(key, val) {
this.printBufferLastLine(key, val);
this.memo += this.style.bufferLineSep();
}
printBufferLastLine(key, val) {
const child = this.child(val, { key });
child.print();
this.memo += child.memo;
}
printBufferTail() {
this.memo += this.style.bufferTail(this.indentLevel());
}
printSet() {
if (this.setIsEmpty()) {
this.printSetEmpty();
}
else {
this.printSetHead();
this.printSetBody();
this.printSetTail();
}
}
setIsEmpty() {
return this.object.size === 0;
}
printSetEmpty() {
this.memo += this.style.setEmpty(this.getClass());
}
printSetHead() {
this.memo += this.style.setHead(this.getClass());
}
printSetBody() {
for (const val of this.object) {
this.printSetEntry(val);
}
}
printSetTail() {
this.memo += this.style.setTail(this.indentLevel());
}
printSetEntry(val) {
const child = this.child(val, { key: val });
child.print();
this.memo += child.memo;
}
printMap() {
if (this.mapIsEmpty()) {
this.printMapEmpty();
}
else {
this.printMapHead();
this.printMapBody();
this.printMapTail();
}
}
mapIsEmpty() {
return this.object.size === 0;
}
printMapEmpty() {
this.memo += this.style.mapEmpty(this.getClass());
}
printMapHead() {
this.memo += this.style.mapHead(this.getClass());
}
getMapEntries(obj = this.object) {
// can never get here unless obj is already a map
/* c8 ignore start */
if (!(obj instanceof Map)) {
throw new TypeError('cannot get map entries for non-Map object');
}
/* c8 ignore stop */
return [...obj.entries()];
}
printMapBody() {
for (const [key, val] of this.getMapEntries()) {
this.printMapEntry(key, val);
}
}
printMapTail() {
this.memo += this.style.mapTail(this.indentLevel());
}
printMapEntry(key, val) {
const child = this.child(val, { key });
child.print();
this.memo += child.memo;
}
printFn() {
this.memo += this.style.fn(this.object, this.getClass());
}
printString() {
if (this.parent && this.parent.isString()) {
this.memo = this.style.stringLine(this.object);
}
else if (this.stringIsEmpty()) {
this.printStringEmpty();
}
else if (this.stringIsOneLine()) {
return this.printStringOneLine();
}
else {
this.printStringHead();
this.printStringBody();
this.printStringTail();
}
}
stringIsEmpty() {
return this.object.length === 0;
}
printStringEmpty() {
this.memo += this.style.stringEmpty();
}
stringIsOneLine() {
return /^[^\n]*\n?$/.test(this.object);
}
printStringOneLine() {
this.memo += this.style.stringOneLine(this.object);
}
printStringHead() {
this.memo += this.style.stringHead();
}
printStringBody() {
const lines = this.object.split('\n');
const lastLine = lines.pop();
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
this.printStringLine(i, line + '\n');
}
this.printStringLastLine(lines.length, lastLine + '\n');
}
printStringLine(key, val) {
this.printStringLastLine(key, val);
this.memo += this.style.stringLineSep();
}
printStringLastLine(key, val) {
const child = this.child(val, { key });
child.print();
this.memo += child.memo;
}
printStringTail() {
this.memo += this.style.stringTail(this.indentLevel());
}
printArray() {
if (this.arrayIsEmpty()) {
this.printArrayEmpty();
}
else {
this.printArrayHead();
this.printArrayBody();
this.printArrayTail();
}
}
arrayIsEmpty() {
const a = this.objectAsArray;
return !!a && a.length === 0;
}
printArrayEmpty() {
this.memo += this.style.arrayEmpty(this.getClass());
}
printArrayHead() {
this.memo += this.style.arrayHead(this.getClass());
}
printArrayBody() {
if (this.objectAsArray) {
this.objectAsArray.forEach((val, key) => this.printArrayEntry(key, val));
}
}
printArrayTail() {
this.memo += this.style.arrayTail(this.indentLevel());
}
printArrayEntry(key, val) {
const child = this.child(val, { key });
child.print();
this.memo += child.memo;
}
printError() {
if (this.errorIsEmpty()) {
this.printErrorEmpty();
}
else {
this.printErrorHead();
this.printErrorBody();
this.printErrorTail();
}
}
errorIsEmpty() {
return this.pojoIsEmpty();
}
printErrorEmpty() {
this.memo += this.style.errorEmpty(this.object, this.getClass());
}
printErrorHead() {
this.memo += this.style.errorHead(this.object, this.getClass());
}
printErrorTail() {
this.memo += this.style.errorTail(this.indentLevel());
}
printErrorBody() {
this.printPojoBody();
}
getPojoKeys(obj = this.object) {
if (this.options.includeEnumerable) {
const keys = [];
for (const i in obj) {
keys.push(i);
}
return keys;
}
else if (this.options.includeGetters) {
const own = new Set(Object.keys(obj));
const proto = Object.getPrototypeOf(obj);
if (proto) {
const desc = Object.getOwnPropertyDescriptors(proto);
for (const [name, prop] of Object.entries(desc)) {
if (prop.enumerable &&
typeof prop.get === 'function') {
// public wrappers around internal things are worth showing
own.add(name);
}
}
}
return Array.from(own);
}
else {
return Object.keys(obj);
}
}
printPojo() {
if (this.pojoIsEmpty()) {
this.printPojoEmpty();
}
else {
this.printPojoHead();
this.printPojoBody();
this.printPojoTail();
}
}
pojoIsEmpty(obj = this.object) {
return this.getPojoKeys(obj).length === 0;
}
printPojoEmpty() {
this.memo += this.style.pojoEmpty(this.getClass());
}
printPojoHead() {
// impossible
/* c8 ignore start */
if (this.memo === null) {
throw new Error('pojo head while memo is null');
}
/* c8 ignore stop */
this.memo += this.style.pojoHead(this.getClass());
}
printPojoBody() {
const ent = this.getPojoEntries(this.object);
for (const [key, val] of ent) {
this.printPojoEntry(key, val);
}
}
getPojoEntries(obj) {
const ent = this.getPojoKeys(obj).map(k => [k, obj[k]]);
return this.sort
? ent.sort((a, b) => a[0].localeCompare(b[0], 'en'))
: ent;
}
printPojoTail() {
this.memo += this.style.pojoTail(this.indentLevel());
}
printPojoEntry(key, val) {
const child = this.child(val, { key });
child.print();
this.memo += child.memo;
}
}
exports.Format = Format;
//# sourceMappingURL=format.js.map |