Skip to content

Commit 7802c66

Browse files
feat(NODE-5040): add color to BSON inspect (#635)
1 parent eb98b8c commit 7802c66

23 files changed

+213
-139
lines changed

src/binary.ts

+10-15
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isAnyArrayBuffer, isUint8Array } from './parser/utils';
1+
import { type InspectFn, defaultInspect, isAnyArrayBuffer, isUint8Array } from './parser/utils';
22
import type { EJSONOptions } from './extended_json';
33
import { BSONError } from './error';
44
import { BSON_BINARY_SUBTYPE_UUID_NEW } from './constants';
@@ -263,14 +263,12 @@ export class Binary extends BSONValue {
263263
return type === BSON_BINARY_SUBTYPE_UUID_NEW ? new UUID(data) : new Binary(data, type);
264264
}
265265

266-
/** @internal */
267-
[Symbol.for('nodejs.util.inspect.custom')](): string {
268-
return this.inspect();
269-
}
270-
271-
inspect(): string {
266+
inspect(depth?: number, options?: unknown, inspect?: InspectFn): string {
267+
inspect ??= defaultInspect;
272268
const base64 = ByteUtils.toBase64(this.buffer.subarray(0, this.position));
273-
return `Binary.createFromBase64("${base64}", ${this.sub_type})`;
269+
const base64Arg = inspect(base64, options);
270+
const subTypeArg = inspect(this.sub_type, options);
271+
return `Binary.createFromBase64(${base64Arg}, ${subTypeArg})`;
274272
}
275273
}
276274

@@ -463,13 +461,10 @@ export class UUID extends Binary {
463461
* Converts to a string representation of this Id.
464462
*
465463
* @returns return the 36 character hex string representation.
466-
* @internal
464+
*
467465
*/
468-
[Symbol.for('nodejs.util.inspect.custom')](): string {
469-
return this.inspect();
470-
}
471-
472-
inspect(): string {
473-
return `new UUID("${this.toHexString()}")`;
466+
inspect(depth?: number, options?: unknown, inspect?: InspectFn): string {
467+
inspect ??= defaultInspect;
468+
return `new UUID(${inspect(this.toHexString(), options)})`;
474469
}
475470
}

src/bson_value.ts

+15-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { BSON_MAJOR_VERSION } from './constants';
2+
import { type InspectFn } from './parser/utils';
23

34
/** @public */
45
export abstract class BSONValue {
@@ -10,8 +11,20 @@ export abstract class BSONValue {
1011
return BSON_MAJOR_VERSION;
1112
}
1213

13-
/** @public */
14-
public abstract inspect(): string;
14+
[Symbol.for('nodejs.util.inspect.custom')](
15+
depth?: number,
16+
options?: unknown,
17+
inspect?: InspectFn
18+
): string {
19+
return this.inspect(depth, options, inspect);
20+
}
21+
22+
/**
23+
* @public
24+
* Prints a human-readable string of BSON value information
25+
* If invoked manually without node.js.inspect function, this will default to a modified JSON.stringify
26+
*/
27+
public abstract inspect(depth?: number, options?: unknown, inspect?: InspectFn): string;
1528

1629
/** @internal */
1730
abstract toExtendedJSON(): unknown;

src/code.ts

+10-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Document } from './bson';
22
import { BSONValue } from './bson_value';
3+
import { type InspectFn, defaultInspect } from './parser/utils';
34

45
/** @public */
56
export interface CodeExtended {
@@ -55,15 +56,14 @@ export class Code extends BSONValue {
5556
return new Code(doc.$code, doc.$scope);
5657
}
5758

58-
/** @internal */
59-
[Symbol.for('nodejs.util.inspect.custom')](): string {
60-
return this.inspect();
61-
}
62-
63-
inspect(): string {
64-
const codeJson = this.toJSON();
65-
return `new Code(${JSON.stringify(String(codeJson.code))}${
66-
codeJson.scope != null ? `, ${JSON.stringify(codeJson.scope)}` : ''
67-
})`;
59+
inspect(depth?: number, options?: unknown, inspect?: InspectFn): string {
60+
inspect ??= defaultInspect;
61+
let parametersString = inspect(this.code, options);
62+
const multiLineFn = parametersString.includes('\n');
63+
if (this.scope != null) {
64+
parametersString += `,${multiLineFn ? '\n' : ' '}${inspect(this.scope, options)}`;
65+
}
66+
const endingNewline = multiLineFn && this.scope === null;
67+
return `new Code(${multiLineFn ? '\n' : ''}${parametersString}${endingNewline ? '\n' : ''})`;
6868
}
6969
}

src/db_ref.ts

+13-11
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { Document } from './bson';
22
import { BSONValue } from './bson_value';
33
import type { EJSONOptions } from './extended_json';
44
import type { ObjectId } from './objectid';
5+
import { type InspectFn, defaultInspect } from './parser/utils';
56

67
/** @public */
78
export interface DBRefLike {
@@ -111,17 +112,18 @@ export class DBRef extends BSONValue {
111112
return new DBRef(doc.$ref, doc.$id, doc.$db, copy);
112113
}
113114

114-
/** @internal */
115-
[Symbol.for('nodejs.util.inspect.custom')](): string {
116-
return this.inspect();
117-
}
115+
inspect(depth?: number, options?: unknown, inspect?: InspectFn): string {
116+
inspect ??= defaultInspect;
117+
118+
const args = [
119+
inspect(this.namespace, options),
120+
inspect(this.oid, options),
121+
...(this.db ? [inspect(this.db, options)] : []),
122+
...(Object.keys(this.fields).length > 0 ? [inspect(this.fields, options)] : [])
123+
];
124+
125+
args[1] = inspect === defaultInspect ? `new ObjectId(${args[1]})` : args[1];
118126

119-
inspect(): string {
120-
// NOTE: if OID is an ObjectId class it will just print the oid string.
121-
const oid =
122-
this.oid === undefined || this.oid.toString === undefined ? this.oid : this.oid.toString();
123-
return `new DBRef("${this.namespace}", new ObjectId("${String(oid)}")${
124-
this.db ? `, "${this.db}"` : ''
125-
})`;
127+
return `new DBRef(${args.join(', ')})`;
126128
}
127129
}

src/decimal128.ts

+5-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { BSONValue } from './bson_value';
22
import { BSONError } from './error';
33
import { Long } from './long';
4-
import { isUint8Array } from './parser/utils';
4+
import { type InspectFn, defaultInspect, isUint8Array } from './parser/utils';
55
import { ByteUtils } from './utils/byte_utils';
66

77
const PARSE_STRING_REGEXP = /^(\+|-)?(\d+|(\d*\.\d*))?(E|e)?([-+])?(\d+)?$/;
@@ -847,12 +847,9 @@ export class Decimal128 extends BSONValue {
847847
return Decimal128.fromString(doc.$numberDecimal);
848848
}
849849

850-
/** @internal */
851-
[Symbol.for('nodejs.util.inspect.custom')](): string {
852-
return this.inspect();
853-
}
854-
855-
inspect(): string {
856-
return `new Decimal128("${this.toString()}")`;
850+
inspect(depth?: number, options?: unknown, inspect?: InspectFn): string {
851+
inspect ??= defaultInspect;
852+
const d128string = inspect(this.toString(), options);
853+
return `new Decimal128(${d128string})`;
857854
}
858855
}

src/double.ts

+4-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { BSONValue } from './bson_value';
22
import type { EJSONOptions } from './extended_json';
3+
import { type InspectFn, defaultInspect } from './parser/utils';
34

45
/** @public */
56
export interface DoubleExtended {
@@ -71,13 +72,8 @@ export class Double extends BSONValue {
7172
return options && options.relaxed ? doubleValue : new Double(doubleValue);
7273
}
7374

74-
/** @internal */
75-
[Symbol.for('nodejs.util.inspect.custom')](): string {
76-
return this.inspect();
77-
}
78-
79-
inspect(): string {
80-
const eJSON = this.toExtendedJSON() as DoubleExtended;
81-
return `new Double(${eJSON.$numberDouble})`;
75+
inspect(depth?: number, options?: unknown, inspect?: InspectFn): string {
76+
inspect ??= defaultInspect;
77+
return `new Double(${inspect(this.value, options)})`;
8278
}
8379
}

src/int_32.ts

+4-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { BSONValue } from './bson_value';
22
import type { EJSONOptions } from './extended_json';
3+
import { type InspectFn, defaultInspect } from './parser/utils';
34

45
/** @public */
56
export interface Int32Extended {
@@ -59,12 +60,8 @@ export class Int32 extends BSONValue {
5960
return options && options.relaxed ? parseInt(doc.$numberInt, 10) : new Int32(doc.$numberInt);
6061
}
6162

62-
/** @internal */
63-
[Symbol.for('nodejs.util.inspect.custom')](): string {
64-
return this.inspect();
65-
}
66-
67-
inspect(): string {
68-
return `new Int32(${this.valueOf()})`;
63+
inspect(depth?: number, options?: unknown, inspect?: InspectFn): string {
64+
inspect ??= defaultInspect;
65+
return `new Int32(${inspect(this.value, options)})`;
6966
}
7067
}

src/long.ts

+6-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { BSONValue } from './bson_value';
22
import { BSONError } from './error';
33
import type { EJSONOptions } from './extended_json';
4+
import { type InspectFn, defaultInspect } from './parser/utils';
45
import type { Timestamp } from './timestamp';
56

67
interface LongWASMHelpers {
@@ -1056,12 +1057,10 @@ export class Long extends BSONValue {
10561057
return longResult;
10571058
}
10581059

1059-
/** @internal */
1060-
[Symbol.for('nodejs.util.inspect.custom')](): string {
1061-
return this.inspect();
1062-
}
1063-
1064-
inspect(): string {
1065-
return `new Long("${this.toString()}"${this.unsigned ? ', true' : ''})`;
1060+
inspect(depth?: number, options?: unknown, inspect?: InspectFn): string {
1061+
inspect ??= defaultInspect;
1062+
const longVal = inspect(this.toString(), options);
1063+
const unsignedVal = this.unsigned ? `, ${inspect(this.unsigned, options)}` : '';
1064+
return `new Long(${longVal}${unsignedVal})`;
10661065
}
10671066
}

src/max_key.ts

-5
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,6 @@ export class MaxKey extends BSONValue {
2525
return new MaxKey();
2626
}
2727

28-
/** @internal */
29-
[Symbol.for('nodejs.util.inspect.custom')](): string {
30-
return this.inspect();
31-
}
32-
3328
inspect(): string {
3429
return 'new MaxKey()';
3530
}

src/min_key.ts

-5
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,6 @@ export class MinKey extends BSONValue {
2525
return new MinKey();
2626
}
2727

28-
/** @internal */
29-
[Symbol.for('nodejs.util.inspect.custom')](): string {
30-
return this.inspect();
31-
}
32-
3328
inspect(): string {
3429
return 'new MinKey()';
3530
}

src/objectid.ts

+4-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { BSONValue } from './bson_value';
22
import { BSONError } from './error';
3+
import { type InspectFn, defaultInspect } from './parser/utils';
34
import { BSONDataView, ByteUtils } from './utils/byte_utils';
45

56
// Regular expression that checks for hex value
@@ -294,13 +295,9 @@ export class ObjectId extends BSONValue {
294295
* Converts to a string representation of this Id.
295296
*
296297
* @returns return the 24 character hex string representation.
297-
* @internal
298298
*/
299-
[Symbol.for('nodejs.util.inspect.custom')](): string {
300-
return this.inspect();
301-
}
302-
303-
inspect(): string {
304-
return `new ObjectId("${this.toHexString()}")`;
299+
inspect(depth?: number, options?: unknown, inspect?: InspectFn): string {
300+
inspect ??= defaultInspect;
301+
return `new ObjectId(${inspect(this.toHexString(), options)})`;
305302
}
306303
}

src/parser/utils.ts

+27
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,30 @@ export function isMap(d: unknown): d is Map<unknown, unknown> {
2727
export function isDate(d: unknown): d is Date {
2828
return Object.prototype.toString.call(d) === '[object Date]';
2929
}
30+
31+
export type InspectFn = (x: unknown, options?: unknown) => string;
32+
export function defaultInspect(x: unknown, _options?: unknown): string {
33+
return JSON.stringify(x, (k: string, v: unknown) => {
34+
if (typeof v === 'bigint') {
35+
return { $numberLong: `${v}` };
36+
} else if (isMap(v)) {
37+
return Object.fromEntries(v);
38+
}
39+
return v;
40+
});
41+
}
42+
43+
/** @internal */
44+
type StylizeFunction = (x: string, style: string) => string;
45+
/** @internal */
46+
export function getStylizeFunction(options?: unknown): StylizeFunction | undefined {
47+
const stylizeExists =
48+
options != null &&
49+
typeof options === 'object' &&
50+
'stylize' in options &&
51+
typeof options.stylize === 'function';
52+
53+
if (stylizeExists) {
54+
return options.stylize as StylizeFunction;
55+
}
56+
}

src/regexp.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { BSONValue } from './bson_value';
22
import { BSONError } from './error';
33
import type { EJSONOptions } from './extended_json';
4+
import { type InspectFn, defaultInspect, getStylizeFunction } from './parser/utils';
45

56
function alphabetize(str: string): string {
67
return str.split('').sort().join('');
@@ -103,12 +104,11 @@ export class BSONRegExp extends BSONValue {
103104
throw new BSONError(`Unexpected BSONRegExp EJSON object form: ${JSON.stringify(doc)}`);
104105
}
105106

106-
/** @internal */
107-
[Symbol.for('nodejs.util.inspect.custom')](): string {
108-
return this.inspect();
109-
}
110-
111-
inspect(): string {
112-
return `new BSONRegExp(${JSON.stringify(this.pattern)}, ${JSON.stringify(this.options)})`;
107+
inspect(depth?: number, options?: unknown, inspect?: InspectFn): string {
108+
const stylize = getStylizeFunction(options) ?? (v => v);
109+
inspect ??= defaultInspect;
110+
const pattern = stylize(inspect(this.pattern), 'regexp');
111+
const flags = stylize(inspect(this.options), 'regexp');
112+
return `new BSONRegExp(${pattern}, ${flags})`;
113113
}
114114
}

src/symbol.ts

+4-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { BSONValue } from './bson_value';
2+
import { type InspectFn, defaultInspect } from './parser/utils';
23

34
/** @public */
45
export interface BSONSymbolExtended {
@@ -33,10 +34,6 @@ export class BSONSymbol extends BSONValue {
3334
return this.value;
3435
}
3536

36-
inspect(): string {
37-
return `new BSONSymbol(${JSON.stringify(this.value)})`;
38-
}
39-
4037
toJSON(): string {
4138
return this.value;
4239
}
@@ -51,8 +48,8 @@ export class BSONSymbol extends BSONValue {
5148
return new BSONSymbol(doc.$symbol);
5249
}
5350

54-
/** @internal */
55-
[Symbol.for('nodejs.util.inspect.custom')](): string {
56-
return this.inspect();
51+
inspect(depth?: number, options?: unknown, inspect?: InspectFn): string {
52+
inspect ??= defaultInspect;
53+
return `new BSONSymbol(${inspect(this.value, options)})`;
5754
}
5855
}

0 commit comments

Comments
 (0)