mirror of
https://github.com/denoland/deno.git
synced 2024-12-22 15:24:46 -05:00
feat(Deno.inspect): Add sorted, trailingComma, compact and iterableLimit to InspectOptions (#6591)
This commit is contained in:
parent
40d081d3d9
commit
5ec41cbcc2
10 changed files with 463 additions and 185 deletions
12
cli/js/lib.deno.ns.d.ts
vendored
12
cli/js/lib.deno.ns.d.ts
vendored
|
@ -2034,8 +2034,18 @@ declare namespace Deno {
|
||||||
| PluginPermissionDescriptor
|
| PluginPermissionDescriptor
|
||||||
| HrtimePermissionDescriptor;
|
| HrtimePermissionDescriptor;
|
||||||
|
|
||||||
interface InspectOptions {
|
export interface InspectOptions {
|
||||||
|
/** Traversal depth for nested objects. Defaults to 4. */
|
||||||
depth?: number;
|
depth?: number;
|
||||||
|
/** Sort Object, Set and Map entries by key. Defaults to false. */
|
||||||
|
sorted?: boolean;
|
||||||
|
/** Add a trailing comma for multiline collections. Defaults to false. */
|
||||||
|
trailingComma?: boolean;
|
||||||
|
/** Try to fit more than one entry of a collection on the same line.
|
||||||
|
* Defaults to true. */
|
||||||
|
compact?: boolean;
|
||||||
|
/** The maximum number of iterable entries to print. Defaults to 100. */
|
||||||
|
iterableLimit?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Converts the input into a string that has the same format as printed by
|
/** Converts the input into a string that has the same format as printed by
|
||||||
|
|
|
@ -3,16 +3,16 @@
|
||||||
import { exit } from "./ops/os.ts";
|
import { exit } from "./ops/os.ts";
|
||||||
import { core } from "./core.ts";
|
import { core } from "./core.ts";
|
||||||
import { version } from "./version.ts";
|
import { version } from "./version.ts";
|
||||||
import { stringifyArgs } from "./web/console.ts";
|
import { inspectArgs } from "./web/console.ts";
|
||||||
import { startRepl, readline } from "./ops/repl.ts";
|
import { startRepl, readline } from "./ops/repl.ts";
|
||||||
import { close } from "./ops/resources.ts";
|
import { close } from "./ops/resources.ts";
|
||||||
|
|
||||||
function replLog(...args: unknown[]): void {
|
function replLog(...args: unknown[]): void {
|
||||||
core.print(stringifyArgs(args) + "\n");
|
core.print(inspectArgs(args) + "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
function replError(...args: unknown[]): void {
|
function replError(...args: unknown[]): void {
|
||||||
core.print(stringifyArgs(args) + "\n", true);
|
core.print(inspectArgs(args) + "\n", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error messages that allow users to continue input
|
// Error messages that allow users to continue input
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import { gray, green, italic, red, yellow } from "./colors.ts";
|
import { gray, green, italic, red, yellow } from "./colors.ts";
|
||||||
import { exit } from "./ops/os.ts";
|
import { exit } from "./ops/os.ts";
|
||||||
import { Console, stringifyArgs } from "./web/console.ts";
|
import { Console, inspectArgs } from "./web/console.ts";
|
||||||
import { stdout } from "./files.ts";
|
import { stdout } from "./files.ts";
|
||||||
import { exposeForTest } from "./internals.ts";
|
import { exposeForTest } from "./internals.ts";
|
||||||
import { TextEncoder } from "./web/text_encoding.ts";
|
import { TextEncoder } from "./web/text_encoding.ts";
|
||||||
|
@ -205,7 +205,7 @@ function reportToConsole(message: TestMessage): void {
|
||||||
|
|
||||||
for (const { name, error } of failures) {
|
for (const { name, error } of failures) {
|
||||||
log(name);
|
log(name);
|
||||||
log(stringifyArgs([error!]));
|
log(inspectArgs([error!]));
|
||||||
log("");
|
log("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,16 +16,28 @@ import {
|
||||||
} from "../colors.ts";
|
} from "../colors.ts";
|
||||||
|
|
||||||
type ConsoleContext = Set<unknown>;
|
type ConsoleContext = Set<unknown>;
|
||||||
type InspectOptions = Partial<{
|
|
||||||
depth: number;
|
export interface InspectOptions {
|
||||||
indentLevel: number;
|
depth?: number;
|
||||||
}>;
|
indentLevel?: number;
|
||||||
|
sorted?: boolean;
|
||||||
|
trailingComma?: boolean;
|
||||||
|
compact?: boolean;
|
||||||
|
iterableLimit?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_INSPECT_OPTIONS: Required<InspectOptions> = {
|
||||||
|
depth: 4,
|
||||||
|
indentLevel: 0,
|
||||||
|
sorted: false,
|
||||||
|
trailingComma: false,
|
||||||
|
compact: true,
|
||||||
|
iterableLimit: 100,
|
||||||
|
};
|
||||||
|
|
||||||
const DEFAULT_INDENT = " "; // Default indent string
|
const DEFAULT_INDENT = " "; // Default indent string
|
||||||
|
|
||||||
const DEFAULT_MAX_DEPTH = 4; // Default depth of logging nested objects
|
|
||||||
const LINE_BREAKING_LENGTH = 80;
|
const LINE_BREAKING_LENGTH = 80;
|
||||||
const MAX_ITERABLE_LENGTH = 100;
|
|
||||||
const MIN_GROUP_LENGTH = 6;
|
const MIN_GROUP_LENGTH = 6;
|
||||||
const STR_ABBREVIATE_SIZE = 100;
|
const STR_ABBREVIATE_SIZE = 100;
|
||||||
// Char codes
|
// Char codes
|
||||||
|
@ -63,7 +75,7 @@ function getClassInstanceName(instance: unknown): string {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function createFunctionString(value: Function, _ctx: ConsoleContext): string {
|
function inspectFunction(value: Function, _ctx: ConsoleContext): string {
|
||||||
// Might be Function/AsyncFunction/GeneratorFunction
|
// Might be Function/AsyncFunction/GeneratorFunction
|
||||||
const cstrName = Object.getPrototypeOf(value).constructor.name;
|
const cstrName = Object.getPrototypeOf(value).constructor.name;
|
||||||
if (value.name && value.name !== "anonymous") {
|
if (value.name && value.name !== "anonymous") {
|
||||||
|
@ -73,7 +85,7 @@ function createFunctionString(value: Function, _ctx: ConsoleContext): string {
|
||||||
return `[${cstrName}]`;
|
return `[${cstrName}]`;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IterablePrintConfig<T> {
|
interface InspectIterableOptions<T> {
|
||||||
typeName: string;
|
typeName: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
delims: [string, string];
|
delims: [string, string];
|
||||||
|
@ -81,23 +93,24 @@ interface IterablePrintConfig<T> {
|
||||||
entry: [unknown, T],
|
entry: [unknown, T],
|
||||||
ctx: ConsoleContext,
|
ctx: ConsoleContext,
|
||||||
level: number,
|
level: number,
|
||||||
maxLevel: number,
|
inspectOptions: Required<InspectOptions>,
|
||||||
next: () => IteratorResult<[unknown, T], unknown>
|
next: () => IteratorResult<[unknown, T], unknown>
|
||||||
) => string;
|
) => string;
|
||||||
group: boolean;
|
group: boolean;
|
||||||
|
sort: boolean;
|
||||||
}
|
}
|
||||||
type IterableEntries<T> = Iterable<T> & {
|
type IterableEntries<T> = Iterable<T> & {
|
||||||
entries(): IterableIterator<[unknown, T]>;
|
entries(): IterableIterator<[unknown, T]>;
|
||||||
};
|
};
|
||||||
function createIterableString<T>(
|
function inspectIterable<T>(
|
||||||
value: IterableEntries<T>,
|
value: IterableEntries<T>,
|
||||||
ctx: ConsoleContext,
|
ctx: ConsoleContext,
|
||||||
level: number,
|
level: number,
|
||||||
maxLevel: number,
|
options: InspectIterableOptions<T>,
|
||||||
config: IterablePrintConfig<T>
|
inspectOptions: Required<InspectOptions>
|
||||||
): string {
|
): string {
|
||||||
if (level >= maxLevel) {
|
if (level >= inspectOptions.depth) {
|
||||||
return cyan(`[${config.typeName}]`);
|
return cyan(`[${options.typeName}]`);
|
||||||
}
|
}
|
||||||
ctx.add(value);
|
ctx.add(value);
|
||||||
|
|
||||||
|
@ -109,42 +122,57 @@ function createIterableString<T>(
|
||||||
return iter.next();
|
return iter.next();
|
||||||
};
|
};
|
||||||
for (const el of iter) {
|
for (const el of iter) {
|
||||||
if (entriesLength < MAX_ITERABLE_LENGTH) {
|
if (entriesLength < inspectOptions.iterableLimit) {
|
||||||
entries.push(
|
entries.push(
|
||||||
config.entryHandler(el, ctx, level + 1, maxLevel, next.bind(iter))
|
options.entryHandler(
|
||||||
|
el,
|
||||||
|
ctx,
|
||||||
|
level + 1,
|
||||||
|
inspectOptions,
|
||||||
|
next.bind(iter)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
entriesLength++;
|
entriesLength++;
|
||||||
}
|
}
|
||||||
ctx.delete(value);
|
ctx.delete(value);
|
||||||
|
|
||||||
if (entriesLength > MAX_ITERABLE_LENGTH) {
|
if (options.sort) {
|
||||||
const nmore = entriesLength - MAX_ITERABLE_LENGTH;
|
entries.sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entriesLength > inspectOptions.iterableLimit) {
|
||||||
|
const nmore = entriesLength - inspectOptions.iterableLimit;
|
||||||
entries.push(`... ${nmore} more items`);
|
entries.push(`... ${nmore} more items`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const iPrefix = `${config.displayName ? config.displayName + " " : ""}`;
|
const iPrefix = `${options.displayName ? options.displayName + " " : ""}`;
|
||||||
|
|
||||||
const initIndentation = `\n${DEFAULT_INDENT.repeat(level + 1)}`;
|
const initIndentation = `\n${DEFAULT_INDENT.repeat(level + 1)}`;
|
||||||
const entryIndentation = `,\n${DEFAULT_INDENT.repeat(level + 1)}`;
|
const entryIndentation = `,\n${DEFAULT_INDENT.repeat(level + 1)}`;
|
||||||
const closingIndentation = `\n${DEFAULT_INDENT.repeat(level)}`;
|
const closingIndentation = `${
|
||||||
|
inspectOptions.trailingComma ? "," : ""
|
||||||
|
}\n${DEFAULT_INDENT.repeat(level)}`;
|
||||||
|
|
||||||
let iContent: string;
|
let iContent: string;
|
||||||
if (config.group && entries.length > MIN_GROUP_LENGTH) {
|
if (options.group && entries.length > MIN_GROUP_LENGTH) {
|
||||||
const groups = groupEntries(entries, level, value);
|
const groups = groupEntries(entries, level, value);
|
||||||
iContent = `${initIndentation}${groups.join(
|
iContent = `${initIndentation}${groups.join(
|
||||||
entryIndentation
|
entryIndentation
|
||||||
)}${closingIndentation}`;
|
)}${closingIndentation}`;
|
||||||
} else {
|
} else {
|
||||||
iContent = entries.length === 0 ? "" : ` ${entries.join(", ")} `;
|
iContent = entries.length === 0 ? "" : ` ${entries.join(", ")} `;
|
||||||
if (stripColor(iContent).length > LINE_BREAKING_LENGTH) {
|
if (
|
||||||
|
stripColor(iContent).length > LINE_BREAKING_LENGTH ||
|
||||||
|
!inspectOptions.compact
|
||||||
|
) {
|
||||||
iContent = `${initIndentation}${entries.join(
|
iContent = `${initIndentation}${entries.join(
|
||||||
entryIndentation
|
entryIndentation
|
||||||
)}${closingIndentation}`;
|
)}${closingIndentation}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${iPrefix}${config.delims[0]}${iContent}${config.delims[1]}`;
|
return `${iPrefix}${options.delims[0]}${iContent}${options.delims[1]}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ported from Node.js
|
// Ported from Node.js
|
||||||
|
@ -152,12 +180,13 @@ function createIterableString<T>(
|
||||||
function groupEntries<T>(
|
function groupEntries<T>(
|
||||||
entries: string[],
|
entries: string[],
|
||||||
level: number,
|
level: number,
|
||||||
value: Iterable<T>
|
value: Iterable<T>,
|
||||||
|
iterableLimit = 100
|
||||||
): string[] {
|
): string[] {
|
||||||
let totalLength = 0;
|
let totalLength = 0;
|
||||||
let maxLength = 0;
|
let maxLength = 0;
|
||||||
let entriesLength = entries.length;
|
let entriesLength = entries.length;
|
||||||
if (MAX_ITERABLE_LENGTH < entriesLength) {
|
if (iterableLimit < entriesLength) {
|
||||||
// This makes sure the "... n more items" part is not taken into account.
|
// This makes sure the "... n more items" part is not taken into account.
|
||||||
entriesLength--;
|
entriesLength--;
|
||||||
}
|
}
|
||||||
|
@ -254,7 +283,7 @@ function groupEntries<T>(
|
||||||
}
|
}
|
||||||
tmp.push(str);
|
tmp.push(str);
|
||||||
}
|
}
|
||||||
if (MAX_ITERABLE_LENGTH < entries.length) {
|
if (iterableLimit < entries.length) {
|
||||||
tmp.push(entries[entriesLength]);
|
tmp.push(entries[entriesLength]);
|
||||||
}
|
}
|
||||||
entries = tmp;
|
entries = tmp;
|
||||||
|
@ -262,11 +291,11 @@ function groupEntries<T>(
|
||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
function stringify(
|
function inspectValue(
|
||||||
value: unknown,
|
value: unknown,
|
||||||
ctx: ConsoleContext,
|
ctx: ConsoleContext,
|
||||||
level: number,
|
level: number,
|
||||||
maxLevel: number
|
inspectOptions: Required<InspectOptions>
|
||||||
): string {
|
): string {
|
||||||
switch (typeof value) {
|
switch (typeof value) {
|
||||||
case "string":
|
case "string":
|
||||||
|
@ -283,7 +312,7 @@ function stringify(
|
||||||
case "bigint": // Bigints are yellow
|
case "bigint": // Bigints are yellow
|
||||||
return yellow(`${value}n`);
|
return yellow(`${value}n`);
|
||||||
case "function": // Function string is cyan
|
case "function": // Function string is cyan
|
||||||
return cyan(createFunctionString(value as Function, ctx));
|
return cyan(inspectFunction(value as Function, ctx));
|
||||||
case "object": // null is bold
|
case "object": // null is bold
|
||||||
if (value === null) {
|
if (value === null) {
|
||||||
return bold("null");
|
return bold("null");
|
||||||
|
@ -294,7 +323,7 @@ function stringify(
|
||||||
return cyan("[Circular]");
|
return cyan("[Circular]");
|
||||||
}
|
}
|
||||||
|
|
||||||
return createObjectString(value, ctx, level, maxLevel);
|
return inspectObject(value, ctx, level, inspectOptions);
|
||||||
default:
|
default:
|
||||||
// Not implemented is red
|
// Not implemented is red
|
||||||
return red("[Not Implemented]");
|
return red("[Not Implemented]");
|
||||||
|
@ -320,11 +349,11 @@ function quoteString(string: string): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print strings when they are inside of arrays or objects with quotes
|
// Print strings when they are inside of arrays or objects with quotes
|
||||||
function stringifyWithQuotes(
|
function inspectValueWithQuotes(
|
||||||
value: unknown,
|
value: unknown,
|
||||||
ctx: ConsoleContext,
|
ctx: ConsoleContext,
|
||||||
level: number,
|
level: number,
|
||||||
maxLevel: number
|
inspectOptions: Required<InspectOptions>
|
||||||
): string {
|
): string {
|
||||||
switch (typeof value) {
|
switch (typeof value) {
|
||||||
case "string":
|
case "string":
|
||||||
|
@ -334,21 +363,21 @@ function stringifyWithQuotes(
|
||||||
: value;
|
: value;
|
||||||
return green(quoteString(trunc)); // Quoted strings are green
|
return green(quoteString(trunc)); // Quoted strings are green
|
||||||
default:
|
default:
|
||||||
return stringify(value, ctx, level, maxLevel);
|
return inspectValue(value, ctx, level, inspectOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createArrayString(
|
function inspectArray(
|
||||||
value: unknown[],
|
value: unknown[],
|
||||||
ctx: ConsoleContext,
|
ctx: ConsoleContext,
|
||||||
level: number,
|
level: number,
|
||||||
maxLevel: number
|
inspectOptions: Required<InspectOptions>
|
||||||
): string {
|
): string {
|
||||||
const printConfig: IterablePrintConfig<unknown> = {
|
const options: InspectIterableOptions<unknown> = {
|
||||||
typeName: "Array",
|
typeName: "Array",
|
||||||
displayName: "",
|
displayName: "",
|
||||||
delims: ["[", "]"],
|
delims: ["[", "]"],
|
||||||
entryHandler: (entry, ctx, level, maxLevel, next): string => {
|
entryHandler: (entry, ctx, level, inspectOptions, next): string => {
|
||||||
const [index, val] = entry as [number, unknown];
|
const [index, val] = entry as [number, unknown];
|
||||||
let i = index;
|
let i = index;
|
||||||
if (!value.hasOwnProperty(i)) {
|
if (!value.hasOwnProperty(i)) {
|
||||||
|
@ -361,117 +390,127 @@ function createArrayString(
|
||||||
const ending = emptyItems > 1 ? "s" : "";
|
const ending = emptyItems > 1 ? "s" : "";
|
||||||
return dim(`<${emptyItems} empty item${ending}>`);
|
return dim(`<${emptyItems} empty item${ending}>`);
|
||||||
} else {
|
} else {
|
||||||
return stringifyWithQuotes(val, ctx, level, maxLevel);
|
return inspectValueWithQuotes(val, ctx, level, inspectOptions);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
group: true,
|
group: inspectOptions.compact,
|
||||||
|
sort: false,
|
||||||
};
|
};
|
||||||
return createIterableString(value, ctx, level, maxLevel, printConfig);
|
return inspectIterable(value, ctx, level, options, inspectOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createTypedArrayString(
|
function inspectTypedArray(
|
||||||
typedArrayName: string,
|
typedArrayName: string,
|
||||||
value: TypedArray,
|
value: TypedArray,
|
||||||
ctx: ConsoleContext,
|
ctx: ConsoleContext,
|
||||||
level: number,
|
level: number,
|
||||||
maxLevel: number
|
inspectOptions: Required<InspectOptions>
|
||||||
): string {
|
): string {
|
||||||
const valueLength = value.length;
|
const valueLength = value.length;
|
||||||
const printConfig: IterablePrintConfig<unknown> = {
|
const options: InspectIterableOptions<unknown> = {
|
||||||
typeName: typedArrayName,
|
typeName: typedArrayName,
|
||||||
displayName: `${typedArrayName}(${valueLength})`,
|
displayName: `${typedArrayName}(${valueLength})`,
|
||||||
delims: ["[", "]"],
|
delims: ["[", "]"],
|
||||||
entryHandler: (entry, ctx, level, maxLevel): string => {
|
entryHandler: (entry, ctx, level, inspectOptions): string => {
|
||||||
const val = entry[1];
|
const val = entry[1];
|
||||||
return stringifyWithQuotes(val, ctx, level + 1, maxLevel);
|
return inspectValueWithQuotes(val, ctx, level + 1, inspectOptions);
|
||||||
},
|
},
|
||||||
group: true,
|
group: inspectOptions.compact,
|
||||||
|
sort: false,
|
||||||
};
|
};
|
||||||
return createIterableString(value, ctx, level, maxLevel, printConfig);
|
return inspectIterable(value, ctx, level, options, inspectOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createSetString(
|
function inspectSet(
|
||||||
value: Set<unknown>,
|
value: Set<unknown>,
|
||||||
ctx: ConsoleContext,
|
ctx: ConsoleContext,
|
||||||
level: number,
|
level: number,
|
||||||
maxLevel: number
|
inspectOptions: Required<InspectOptions>
|
||||||
): string {
|
): string {
|
||||||
const printConfig: IterablePrintConfig<unknown> = {
|
const options: InspectIterableOptions<unknown> = {
|
||||||
typeName: "Set",
|
typeName: "Set",
|
||||||
displayName: "Set",
|
displayName: "Set",
|
||||||
delims: ["{", "}"],
|
delims: ["{", "}"],
|
||||||
entryHandler: (entry, ctx, level, maxLevel): string => {
|
entryHandler: (entry, ctx, level, inspectOptions): string => {
|
||||||
const val = entry[1];
|
const val = entry[1];
|
||||||
return stringifyWithQuotes(val, ctx, level + 1, maxLevel);
|
return inspectValueWithQuotes(val, ctx, level + 1, inspectOptions);
|
||||||
},
|
},
|
||||||
group: false,
|
group: false,
|
||||||
|
sort: inspectOptions.sorted,
|
||||||
};
|
};
|
||||||
return createIterableString(value, ctx, level, maxLevel, printConfig);
|
return inspectIterable(value, ctx, level, options, inspectOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createMapString(
|
function inspectMap(
|
||||||
value: Map<unknown, unknown>,
|
value: Map<unknown, unknown>,
|
||||||
ctx: ConsoleContext,
|
ctx: ConsoleContext,
|
||||||
level: number,
|
level: number,
|
||||||
maxLevel: number
|
inspectOptions: Required<InspectOptions>
|
||||||
): string {
|
): string {
|
||||||
const printConfig: IterablePrintConfig<[unknown]> = {
|
const options: InspectIterableOptions<[unknown]> = {
|
||||||
typeName: "Map",
|
typeName: "Map",
|
||||||
displayName: "Map",
|
displayName: "Map",
|
||||||
delims: ["{", "}"],
|
delims: ["{", "}"],
|
||||||
entryHandler: (entry, ctx, level, maxLevel): string => {
|
entryHandler: (entry, ctx, level, inspectOptions): string => {
|
||||||
const [key, val] = entry;
|
const [key, val] = entry;
|
||||||
return `${stringifyWithQuotes(
|
return `${inspectValueWithQuotes(
|
||||||
key,
|
key,
|
||||||
ctx,
|
ctx,
|
||||||
level + 1,
|
level + 1,
|
||||||
maxLevel
|
inspectOptions
|
||||||
)} => ${stringifyWithQuotes(val, ctx, level + 1, maxLevel)}`;
|
)} => ${inspectValueWithQuotes(val, ctx, level + 1, inspectOptions)}`;
|
||||||
},
|
},
|
||||||
group: false,
|
group: false,
|
||||||
|
sort: inspectOptions.sorted,
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
return inspectIterable(
|
||||||
return createIterableString(value as any, ctx, level, maxLevel, printConfig);
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
value as any,
|
||||||
|
ctx,
|
||||||
|
level,
|
||||||
|
options,
|
||||||
|
inspectOptions
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createWeakSetString(): string {
|
function inspectWeakSet(): string {
|
||||||
return `WeakSet { ${cyan("[items unknown]")} }`; // as seen in Node, with cyan color
|
return `WeakSet { ${cyan("[items unknown]")} }`; // as seen in Node, with cyan color
|
||||||
}
|
}
|
||||||
|
|
||||||
function createWeakMapString(): string {
|
function inspectWeakMap(): string {
|
||||||
return `WeakMap { ${cyan("[items unknown]")} }`; // as seen in Node, with cyan color
|
return `WeakMap { ${cyan("[items unknown]")} }`; // as seen in Node, with cyan color
|
||||||
}
|
}
|
||||||
|
|
||||||
function createDateString(value: Date): string {
|
function inspectDate(value: Date): string {
|
||||||
// without quotes, ISO format, in magenta like before
|
// without quotes, ISO format, in magenta like before
|
||||||
return magenta(isInvalidDate(value) ? "Invalid Date" : value.toISOString());
|
return magenta(isInvalidDate(value) ? "Invalid Date" : value.toISOString());
|
||||||
}
|
}
|
||||||
|
|
||||||
function createRegExpString(value: RegExp): string {
|
function inspectRegExp(value: RegExp): string {
|
||||||
return red(value.toString()); // RegExps are red
|
return red(value.toString()); // RegExps are red
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/ban-types */
|
/* eslint-disable @typescript-eslint/ban-types */
|
||||||
|
|
||||||
function createStringWrapperString(value: String): string {
|
function inspectStringObject(value: String): string {
|
||||||
return cyan(`[String: "${value.toString()}"]`); // wrappers are in cyan
|
return cyan(`[String: "${value.toString()}"]`); // wrappers are in cyan
|
||||||
}
|
}
|
||||||
|
|
||||||
function createBooleanWrapperString(value: Boolean): string {
|
function inspectBooleanObject(value: Boolean): string {
|
||||||
return cyan(`[Boolean: ${value.toString()}]`); // wrappers are in cyan
|
return cyan(`[Boolean: ${value.toString()}]`); // wrappers are in cyan
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNumberWrapperString(value: Number): string {
|
function inspectNumberObject(value: Number): string {
|
||||||
return cyan(`[Number: ${value.toString()}]`); // wrappers are in cyan
|
return cyan(`[Number: ${value.toString()}]`); // wrappers are in cyan
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-enable @typescript-eslint/ban-types */
|
/* eslint-enable @typescript-eslint/ban-types */
|
||||||
|
|
||||||
function createPromiseString(
|
function inspectPromise(
|
||||||
value: Promise<unknown>,
|
value: Promise<unknown>,
|
||||||
ctx: ConsoleContext,
|
ctx: ConsoleContext,
|
||||||
level: number,
|
level: number,
|
||||||
maxLevel: number
|
inspectOptions: Required<InspectOptions>
|
||||||
): string {
|
): string {
|
||||||
const [state, result] = Deno.core.getPromiseDetails(value);
|
const [state, result] = Deno.core.getPromiseDetails(value);
|
||||||
|
|
||||||
|
@ -482,11 +521,11 @@ function createPromiseString(
|
||||||
const prefix =
|
const prefix =
|
||||||
state === PromiseState.Fulfilled ? "" : `${red("<rejected>")} `;
|
state === PromiseState.Fulfilled ? "" : `${red("<rejected>")} `;
|
||||||
|
|
||||||
const str = `${prefix}${stringifyWithQuotes(
|
const str = `${prefix}${inspectValueWithQuotes(
|
||||||
result,
|
result,
|
||||||
ctx,
|
ctx,
|
||||||
level + 1,
|
level + 1,
|
||||||
maxLevel
|
inspectOptions
|
||||||
)}`;
|
)}`;
|
||||||
|
|
||||||
if (str.length + PROMISE_STRING_BASE_LENGTH > LINE_BREAKING_LENGTH) {
|
if (str.length + PROMISE_STRING_BASE_LENGTH > LINE_BREAKING_LENGTH) {
|
||||||
|
@ -498,13 +537,13 @@ function createPromiseString(
|
||||||
|
|
||||||
// TODO: Proxy
|
// TODO: Proxy
|
||||||
|
|
||||||
function createRawObjectString(
|
function inspectRawObject(
|
||||||
value: Record<string, unknown>,
|
value: Record<string, unknown>,
|
||||||
ctx: ConsoleContext,
|
ctx: ConsoleContext,
|
||||||
level: number,
|
level: number,
|
||||||
maxLevel: number
|
inspectOptions: Required<InspectOptions>
|
||||||
): string {
|
): string {
|
||||||
if (level >= maxLevel) {
|
if (level >= inspectOptions.depth) {
|
||||||
return cyan("[Object]"); // wrappers are in cyan
|
return cyan("[Object]"); // wrappers are in cyan
|
||||||
}
|
}
|
||||||
ctx.add(value);
|
ctx.add(value);
|
||||||
|
@ -525,20 +564,31 @@ function createRawObjectString(
|
||||||
const entries: string[] = [];
|
const entries: string[] = [];
|
||||||
const stringKeys = Object.keys(value);
|
const stringKeys = Object.keys(value);
|
||||||
const symbolKeys = Object.getOwnPropertySymbols(value);
|
const symbolKeys = Object.getOwnPropertySymbols(value);
|
||||||
|
if (inspectOptions.sorted) {
|
||||||
|
stringKeys.sort();
|
||||||
|
symbolKeys.sort((s1, s2) =>
|
||||||
|
(s1.description ?? "").localeCompare(s2.description ?? "")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
for (const key of stringKeys) {
|
for (const key of stringKeys) {
|
||||||
entries.push(
|
entries.push(
|
||||||
`${key}: ${stringifyWithQuotes(value[key], ctx, level + 1, maxLevel)}`
|
`${key}: ${inspectValueWithQuotes(
|
||||||
|
value[key],
|
||||||
|
ctx,
|
||||||
|
level + 1,
|
||||||
|
inspectOptions
|
||||||
|
)}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
for (const key of symbolKeys) {
|
for (const key of symbolKeys) {
|
||||||
entries.push(
|
entries.push(
|
||||||
`${key.toString()}: ${stringifyWithQuotes(
|
`${key.toString()}: ${inspectValueWithQuotes(
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
value[key as any],
|
value[key as any],
|
||||||
ctx,
|
ctx,
|
||||||
level + 1,
|
level + 1,
|
||||||
maxLevel
|
inspectOptions
|
||||||
)}`
|
)}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -550,12 +600,12 @@ function createRawObjectString(
|
||||||
|
|
||||||
if (entries.length === 0) {
|
if (entries.length === 0) {
|
||||||
baseString = "{}";
|
baseString = "{}";
|
||||||
} else if (totalLength > LINE_BREAKING_LENGTH) {
|
} else if (totalLength > LINE_BREAKING_LENGTH || !inspectOptions.compact) {
|
||||||
const entryIndent = DEFAULT_INDENT.repeat(level + 1);
|
const entryIndent = DEFAULT_INDENT.repeat(level + 1);
|
||||||
const closingIndent = DEFAULT_INDENT.repeat(level);
|
const closingIndent = DEFAULT_INDENT.repeat(level);
|
||||||
baseString = `{\n${entryIndent}${entries.join(
|
baseString = `{\n${entryIndent}${entries.join(`,\n${entryIndent}`)}${
|
||||||
`,\n${entryIndent}`
|
inspectOptions.trailingComma ? "," : ""
|
||||||
)}\n${closingIndent}}`;
|
}\n${closingIndent}}`;
|
||||||
} else {
|
} else {
|
||||||
baseString = `{ ${entries.join(", ")} }`;
|
baseString = `{ ${entries.join(", ")} }`;
|
||||||
}
|
}
|
||||||
|
@ -567,9 +617,11 @@ function createRawObjectString(
|
||||||
return baseString;
|
return baseString;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createObjectString(
|
function inspectObject(
|
||||||
value: {},
|
value: {},
|
||||||
...args: [ConsoleContext, number, number]
|
consoleContext: ConsoleContext,
|
||||||
|
level: number,
|
||||||
|
inspectOptions: Required<InspectOptions>
|
||||||
): string {
|
): string {
|
||||||
if (customInspect in value && typeof value[customInspect] === "function") {
|
if (customInspect in value && typeof value[customInspect] === "function") {
|
||||||
try {
|
try {
|
||||||
|
@ -579,43 +631,46 @@ function createObjectString(
|
||||||
if (value instanceof Error) {
|
if (value instanceof Error) {
|
||||||
return String(value.stack);
|
return String(value.stack);
|
||||||
} else if (Array.isArray(value)) {
|
} else if (Array.isArray(value)) {
|
||||||
return createArrayString(value, ...args);
|
return inspectArray(value, consoleContext, level, inspectOptions);
|
||||||
} else if (value instanceof Number) {
|
} else if (value instanceof Number) {
|
||||||
return createNumberWrapperString(value);
|
return inspectNumberObject(value);
|
||||||
} else if (value instanceof Boolean) {
|
} else if (value instanceof Boolean) {
|
||||||
return createBooleanWrapperString(value);
|
return inspectBooleanObject(value);
|
||||||
} else if (value instanceof String) {
|
} else if (value instanceof String) {
|
||||||
return createStringWrapperString(value);
|
return inspectStringObject(value);
|
||||||
} else if (value instanceof Promise) {
|
} else if (value instanceof Promise) {
|
||||||
return createPromiseString(value, ...args);
|
return inspectPromise(value, consoleContext, level, inspectOptions);
|
||||||
} else if (value instanceof RegExp) {
|
} else if (value instanceof RegExp) {
|
||||||
return createRegExpString(value);
|
return inspectRegExp(value);
|
||||||
} else if (value instanceof Date) {
|
} else if (value instanceof Date) {
|
||||||
return createDateString(value);
|
return inspectDate(value);
|
||||||
} else if (value instanceof Set) {
|
} else if (value instanceof Set) {
|
||||||
return createSetString(value, ...args);
|
return inspectSet(value, consoleContext, level, inspectOptions);
|
||||||
} else if (value instanceof Map) {
|
} else if (value instanceof Map) {
|
||||||
return createMapString(value, ...args);
|
return inspectMap(value, consoleContext, level, inspectOptions);
|
||||||
} else if (value instanceof WeakSet) {
|
} else if (value instanceof WeakSet) {
|
||||||
return createWeakSetString();
|
return inspectWeakSet();
|
||||||
} else if (value instanceof WeakMap) {
|
} else if (value instanceof WeakMap) {
|
||||||
return createWeakMapString();
|
return inspectWeakMap();
|
||||||
} else if (isTypedArray(value)) {
|
} else if (isTypedArray(value)) {
|
||||||
return createTypedArrayString(
|
return inspectTypedArray(
|
||||||
Object.getPrototypeOf(value).constructor.name,
|
Object.getPrototypeOf(value).constructor.name,
|
||||||
value,
|
value,
|
||||||
...args
|
consoleContext,
|
||||||
|
level,
|
||||||
|
inspectOptions
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, default object formatting
|
// Otherwise, default object formatting
|
||||||
return createRawObjectString(value, ...args);
|
return inspectRawObject(value, consoleContext, level, inspectOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stringifyArgs(
|
export function inspectArgs(
|
||||||
args: unknown[],
|
args: unknown[],
|
||||||
{ depth = DEFAULT_MAX_DEPTH, indentLevel = 0 }: InspectOptions = {}
|
inspectOptions: InspectOptions = {}
|
||||||
): string {
|
): string {
|
||||||
|
const rInspectOptions = { ...DEFAULT_INSPECT_OPTIONS, ...inspectOptions };
|
||||||
const first = args[0];
|
const first = args[0];
|
||||||
let a = 0;
|
let a = 0;
|
||||||
let str = "";
|
let str = "";
|
||||||
|
@ -658,7 +713,12 @@ export function stringifyArgs(
|
||||||
case CHAR_LOWERCASE_O:
|
case CHAR_LOWERCASE_O:
|
||||||
case CHAR_UPPERCASE_O:
|
case CHAR_UPPERCASE_O:
|
||||||
// format as an object
|
// format as an object
|
||||||
tempStr = stringify(args[++a], new Set<unknown>(), 0, depth);
|
tempStr = inspectValue(
|
||||||
|
args[++a],
|
||||||
|
new Set<unknown>(),
|
||||||
|
0,
|
||||||
|
rInspectOptions
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case CHAR_PERCENT:
|
case CHAR_PERCENT:
|
||||||
str += first.slice(lastPos, i);
|
str += first.slice(lastPos, i);
|
||||||
|
@ -701,14 +761,14 @@ export function stringifyArgs(
|
||||||
str += value;
|
str += value;
|
||||||
} else {
|
} else {
|
||||||
// use default maximum depth for null or undefined argument
|
// use default maximum depth for null or undefined argument
|
||||||
str += stringify(value, new Set<unknown>(), 0, depth);
|
str += inspectValue(value, new Set<unknown>(), 0, rInspectOptions);
|
||||||
}
|
}
|
||||||
join = " ";
|
join = " ";
|
||||||
a++;
|
a++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (indentLevel > 0) {
|
if (rInspectOptions.indentLevel > 0) {
|
||||||
const groupIndent = DEFAULT_INDENT.repeat(indentLevel);
|
const groupIndent = DEFAULT_INDENT.repeat(rInspectOptions.indentLevel);
|
||||||
if (str.indexOf("\n") !== -1) {
|
if (str.indexOf("\n") !== -1) {
|
||||||
str = str.replace(/\n/g, `\n${groupIndent}`);
|
str = str.replace(/\n/g, `\n${groupIndent}`);
|
||||||
}
|
}
|
||||||
|
@ -745,7 +805,7 @@ export class Console {
|
||||||
|
|
||||||
log = (...args: unknown[]): void => {
|
log = (...args: unknown[]): void => {
|
||||||
this.#printFunc(
|
this.#printFunc(
|
||||||
stringifyArgs(args, {
|
inspectArgs(args, {
|
||||||
indentLevel: this.indentLevel,
|
indentLevel: this.indentLevel,
|
||||||
}) + "\n",
|
}) + "\n",
|
||||||
false
|
false
|
||||||
|
@ -756,14 +816,14 @@ export class Console {
|
||||||
info = this.log;
|
info = this.log;
|
||||||
|
|
||||||
dir = (obj: unknown, options: InspectOptions = {}): void => {
|
dir = (obj: unknown, options: InspectOptions = {}): void => {
|
||||||
this.#printFunc(stringifyArgs([obj], options) + "\n", false);
|
this.#printFunc(inspectArgs([obj], options) + "\n", false);
|
||||||
};
|
};
|
||||||
|
|
||||||
dirxml = this.dir;
|
dirxml = this.dir;
|
||||||
|
|
||||||
warn = (...args: unknown[]): void => {
|
warn = (...args: unknown[]): void => {
|
||||||
this.#printFunc(
|
this.#printFunc(
|
||||||
stringifyArgs(args, {
|
inspectArgs(args, {
|
||||||
indentLevel: this.indentLevel,
|
indentLevel: this.indentLevel,
|
||||||
}) + "\n",
|
}) + "\n",
|
||||||
true
|
true
|
||||||
|
@ -832,7 +892,10 @@ export class Console {
|
||||||
const values: string[] = [];
|
const values: string[] = [];
|
||||||
|
|
||||||
const stringifyValue = (value: unknown): string =>
|
const stringifyValue = (value: unknown): string =>
|
||||||
stringifyWithQuotes(value, new Set<unknown>(), 0, 1);
|
inspectValueWithQuotes(value, new Set<unknown>(), 0, {
|
||||||
|
...DEFAULT_INSPECT_OPTIONS,
|
||||||
|
depth: 1,
|
||||||
|
});
|
||||||
const toTable = (header: string[], body: string[][]): void =>
|
const toTable = (header: string[], body: string[][]): void =>
|
||||||
this.log(cliTable(header, body));
|
this.log(cliTable(header, body));
|
||||||
const createColumn = (value: unknown, shift?: number): string[] => [
|
const createColumn = (value: unknown, shift?: number): string[] => [
|
||||||
|
@ -966,7 +1029,7 @@ export class Console {
|
||||||
};
|
};
|
||||||
|
|
||||||
trace = (...args: unknown[]): void => {
|
trace = (...args: unknown[]): void => {
|
||||||
const message = stringifyArgs(args, { indentLevel: 0 });
|
const message = inspectArgs(args, { indentLevel: 0 });
|
||||||
const err = {
|
const err = {
|
||||||
name: "Trace",
|
name: "Trace",
|
||||||
message,
|
message,
|
||||||
|
@ -980,19 +1043,24 @@ export class Console {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const customInspect = Symbol("Deno.symbols.customInspect");
|
export const customInspect = Symbol("Deno.customInspect");
|
||||||
|
|
||||||
export function inspect(
|
export function inspect(
|
||||||
value: unknown,
|
value: unknown,
|
||||||
{ depth = DEFAULT_MAX_DEPTH }: InspectOptions = {}
|
inspectOptions: InspectOptions = {}
|
||||||
): string {
|
): string {
|
||||||
if (typeof value === "string") {
|
if (typeof value === "string") {
|
||||||
return value;
|
return value;
|
||||||
} else {
|
} else {
|
||||||
return stringify(value, new Set<unknown>(), 0, depth);
|
return inspectValue(value, new Set<unknown>(), 0, {
|
||||||
|
...DEFAULT_INSPECT_OPTIONS,
|
||||||
|
...inspectOptions,
|
||||||
|
// TODO(nayeemrmn): Indent level is not supported.
|
||||||
|
indentLevel: 0,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expose these fields to internalObject for tests.
|
// Expose these fields to internalObject for tests.
|
||||||
exposeForTest("Console", Console);
|
exposeForTest("Console", Console);
|
||||||
exposeForTest("stringifyArgs", stringifyArgs);
|
exposeForTest("inspectArgs", inspectArgs);
|
||||||
|
|
|
@ -11,22 +11,15 @@
|
||||||
import { assert, assertEquals, unitTest } from "./test_util.ts";
|
import { assert, assertEquals, unitTest } from "./test_util.ts";
|
||||||
import { stripColor } from "../../../std/fmt/colors.ts";
|
import { stripColor } from "../../../std/fmt/colors.ts";
|
||||||
|
|
||||||
// Some of these APIs aren't exposed in the types and so we have to cast to any
|
|
||||||
// in order to "trick" TypeScript.
|
|
||||||
const {
|
|
||||||
inspect,
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
} = Deno as any;
|
|
||||||
|
|
||||||
const customInspect = Deno.customInspect;
|
const customInspect = Deno.customInspect;
|
||||||
const {
|
const {
|
||||||
Console,
|
Console,
|
||||||
stringifyArgs,
|
inspectArgs,
|
||||||
// @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol
|
// @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol
|
||||||
} = Deno[Deno.internal];
|
} = Deno[Deno.internal];
|
||||||
|
|
||||||
function stringify(...args: unknown[]): string {
|
function stringify(...args: unknown[]): string {
|
||||||
return stripColor(stringifyArgs(args).replace(/\n$/, ""));
|
return stripColor(inspectArgs(args).replace(/\n$/, ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
// test cases from web-platform-tests
|
// test cases from web-platform-tests
|
||||||
|
@ -238,7 +231,7 @@ unitTest(function consoleTestStringifyCircular(): void {
|
||||||
'TAG { str: 1, Symbol(sym): 2, Symbol(Symbol.toStringTag): "TAG" }'
|
'TAG { str: 1, Symbol(sym): 2, Symbol(Symbol.toStringTag): "TAG" }'
|
||||||
);
|
);
|
||||||
// test inspect is working the same
|
// test inspect is working the same
|
||||||
assertEquals(stripColor(inspect(nestedObj)), nestedObjExpected);
|
assertEquals(stripColor(Deno.inspect(nestedObj)), nestedObjExpected);
|
||||||
});
|
});
|
||||||
/* eslint-enable @typescript-eslint/explicit-function-return-type */
|
/* eslint-enable @typescript-eslint/explicit-function-return-type */
|
||||||
|
|
||||||
|
@ -246,24 +239,21 @@ unitTest(function consoleTestStringifyWithDepth(): void {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const nestedObj: any = { a: { b: { c: { d: { e: { f: 42 } } } } } };
|
const nestedObj: any = { a: { b: { c: { d: { e: { f: 42 } } } } } };
|
||||||
assertEquals(
|
assertEquals(
|
||||||
stripColor(stringifyArgs([nestedObj], { depth: 3 })),
|
stripColor(inspectArgs([nestedObj], { depth: 3 })),
|
||||||
"{ a: { b: { c: [Object] } } }"
|
"{ a: { b: { c: [Object] } } }"
|
||||||
);
|
);
|
||||||
assertEquals(
|
assertEquals(
|
||||||
stripColor(stringifyArgs([nestedObj], { depth: 4 })),
|
stripColor(inspectArgs([nestedObj], { depth: 4 })),
|
||||||
"{ a: { b: { c: { d: [Object] } } } }"
|
"{ a: { b: { c: { d: [Object] } } } }"
|
||||||
);
|
);
|
||||||
|
assertEquals(stripColor(inspectArgs([nestedObj], { depth: 0 })), "[Object]");
|
||||||
assertEquals(
|
assertEquals(
|
||||||
stripColor(stringifyArgs([nestedObj], { depth: 0 })),
|
stripColor(inspectArgs([nestedObj])),
|
||||||
"[Object]"
|
|
||||||
);
|
|
||||||
assertEquals(
|
|
||||||
stripColor(stringifyArgs([nestedObj])),
|
|
||||||
"{ a: { b: { c: { d: [Object] } } } }"
|
"{ a: { b: { c: { d: [Object] } } } }"
|
||||||
);
|
);
|
||||||
// test inspect is working the same way
|
// test inspect is working the same way
|
||||||
assertEquals(
|
assertEquals(
|
||||||
stripColor(inspect(nestedObj, { depth: 4 })),
|
stripColor(Deno.inspect(nestedObj, { depth: 4 })),
|
||||||
"{ a: { b: { c: { d: [Object] } } } }"
|
"{ a: { b: { c: { d: [Object] } } } }"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -653,7 +643,7 @@ unitTest(function consoleTestWithCustomInspectorError(): void {
|
||||||
assertEquals(stringify(new B({ a: "a" })), "a");
|
assertEquals(stringify(new B({ a: "a" })), "a");
|
||||||
assertEquals(
|
assertEquals(
|
||||||
stringify(B.prototype),
|
stringify(B.prototype),
|
||||||
"{ Symbol(Deno.symbols.customInspect): [Function: [Deno.symbols.customInspect]] }"
|
"{ Symbol(Deno.customInspect): [Function: [Deno.customInspect]] }"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1175,3 +1165,111 @@ unitTest(function consoleTrace(): void {
|
||||||
assert(err.toString().includes("Trace: custom message"));
|
assert(err.toString().includes("Trace: custom message"));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
unitTest(function inspectSorted(): void {
|
||||||
|
assertEquals(
|
||||||
|
Deno.inspect({ b: 2, a: 1 }, { sorted: true }),
|
||||||
|
"{ a: 1, b: 2 }"
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
Deno.inspect(new Set(["b", "a"]), { sorted: true }),
|
||||||
|
`Set { "a", "b" }`
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
Deno.inspect(
|
||||||
|
new Map([
|
||||||
|
["b", 2],
|
||||||
|
["a", 1],
|
||||||
|
]),
|
||||||
|
{ sorted: true }
|
||||||
|
),
|
||||||
|
`Map { "a" => 1, "b" => 2 }`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
unitTest(function inspectTrailingComma(): void {
|
||||||
|
assertEquals(
|
||||||
|
Deno.inspect(
|
||||||
|
[
|
||||||
|
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
|
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
||||||
|
],
|
||||||
|
{ trailingComma: true }
|
||||||
|
),
|
||||||
|
`[
|
||||||
|
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
|
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
||||||
|
]`
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
Deno.inspect(
|
||||||
|
{
|
||||||
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: 1,
|
||||||
|
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb: 2,
|
||||||
|
},
|
||||||
|
{ trailingComma: true }
|
||||||
|
),
|
||||||
|
`{
|
||||||
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: 1,
|
||||||
|
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb: 2,
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
Deno.inspect(
|
||||||
|
new Set([
|
||||||
|
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
|
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
||||||
|
]),
|
||||||
|
{ trailingComma: true }
|
||||||
|
),
|
||||||
|
`Set {
|
||||||
|
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
|
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
Deno.inspect(
|
||||||
|
new Map([
|
||||||
|
["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 1],
|
||||||
|
["bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", 2],
|
||||||
|
]),
|
||||||
|
{ trailingComma: true }
|
||||||
|
),
|
||||||
|
`Map {
|
||||||
|
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" => 1,
|
||||||
|
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" => 2,
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
unitTest(function inspectCompact(): void {
|
||||||
|
assertEquals(
|
||||||
|
Deno.inspect({ a: 1, b: 2 }, { compact: false }),
|
||||||
|
`{
|
||||||
|
a: 1,
|
||||||
|
b: 2
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
unitTest(function inspectIterableLimit(): void {
|
||||||
|
assertEquals(
|
||||||
|
Deno.inspect(["a", "b", "c"], { iterableLimit: 2 }),
|
||||||
|
`[ "a", "b", ... 1 more items ]`
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
Deno.inspect(new Set(["a", "b", "c"]), { iterableLimit: 2 }),
|
||||||
|
`Set { "a", "b", ... 1 more items }`
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
Deno.inspect(
|
||||||
|
new Map([
|
||||||
|
["a", 1],
|
||||||
|
["b", 2],
|
||||||
|
["c", 3],
|
||||||
|
]),
|
||||||
|
{ iterableLimit: 2 }
|
||||||
|
),
|
||||||
|
`Map { "a" => 1, "b" => 2, ... 1 more items }`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
assertStringContains,
|
assertStringContains,
|
||||||
} from "./test_util.ts";
|
} from "./test_util.ts";
|
||||||
const {
|
const {
|
||||||
stringifyArgs,
|
inspectArgs,
|
||||||
// @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol
|
// @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol
|
||||||
} = Deno[Deno.internal];
|
} = Deno[Deno.internal];
|
||||||
|
|
||||||
|
@ -402,7 +402,7 @@ unitTest(function toStringShouldBeWebCompatibility(): void {
|
||||||
});
|
});
|
||||||
|
|
||||||
function stringify(...args: unknown[]): string {
|
function stringify(...args: unknown[]): string {
|
||||||
return stringifyArgs(args).replace(/\n$/, "");
|
return inspectArgs(args).replace(/\n$/, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
unitTest(function customInspectReturnsCorrectHeadersFormat(): void {
|
unitTest(function customInspectReturnsCorrectHeadersFormat(): void {
|
||||||
|
|
|
@ -3,8 +3,8 @@ import { unitTest, assert } from "./test_util.ts";
|
||||||
|
|
||||||
unitTest(function internalsExists(): void {
|
unitTest(function internalsExists(): void {
|
||||||
const {
|
const {
|
||||||
stringifyArgs,
|
inspectArgs,
|
||||||
// @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol
|
// @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol
|
||||||
} = Deno[Deno.internal];
|
} = Deno[Deno.internal];
|
||||||
assert(!!stringifyArgs);
|
assert(!!inspectArgs);
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,7 +21,10 @@ Don't link to / import any module whose path:
|
||||||
- Is that of a test module or test data: `test.ts`, `foo_test.ts`,
|
- Is that of a test module or test data: `test.ts`, `foo_test.ts`,
|
||||||
`testdata/bar.txt`.
|
`testdata/bar.txt`.
|
||||||
|
|
||||||
No stability is guaranteed for these files.
|
Don't import any symbol with an underscore prefix: `export function _baz() {}`.
|
||||||
|
|
||||||
|
These elements are not considered part of the public API, thus no stability is
|
||||||
|
guaranteed for them.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
|
@ -29,7 +32,7 @@ To browse documentation for modules:
|
||||||
|
|
||||||
- Go to https://deno.land/std/.
|
- Go to https://deno.land/std/.
|
||||||
- Navigate to any module of interest.
|
- Navigate to any module of interest.
|
||||||
- Click the "DOCUMENTATION" link.
|
- Click "View Documentation".
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,16 @@ export class AssertionError extends Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function format(v: unknown): string {
|
export function _format(v: unknown): string {
|
||||||
let string = globalThis.Deno ? Deno.inspect(v) : String(v);
|
let string = globalThis.Deno
|
||||||
|
? Deno.inspect(v, {
|
||||||
|
depth: Infinity,
|
||||||
|
sorted: true,
|
||||||
|
trailingComma: true,
|
||||||
|
compact: false,
|
||||||
|
iterableLimit: Infinity,
|
||||||
|
})
|
||||||
|
: String(v);
|
||||||
if (typeof v == "string") {
|
if (typeof v == "string") {
|
||||||
string = `"${string.replace(/(?=["\\])/g, "\\")}"`;
|
string = `"${string.replace(/(?=["\\])/g, "\\")}"`;
|
||||||
}
|
}
|
||||||
|
@ -167,8 +175,8 @@ export function assertEquals(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let message = "";
|
let message = "";
|
||||||
const actualString = format(actual);
|
const actualString = _format(actual);
|
||||||
const expectedString = format(expected);
|
const expectedString = _format(expected);
|
||||||
try {
|
try {
|
||||||
const diffResult = diff(
|
const diffResult = diff(
|
||||||
actualString.split("\n"),
|
actualString.split("\n"),
|
||||||
|
@ -248,13 +256,13 @@ export function assertStrictEquals<T>(
|
||||||
if (msg) {
|
if (msg) {
|
||||||
message = msg;
|
message = msg;
|
||||||
} else {
|
} else {
|
||||||
const actualString = format(actual);
|
const actualString = _format(actual);
|
||||||
const expectedString = format(expected);
|
const expectedString = _format(expected);
|
||||||
|
|
||||||
if (actualString === expectedString) {
|
if (actualString === expectedString) {
|
||||||
const withOffset = actualString
|
const withOffset = actualString
|
||||||
.split("\n")
|
.split("\n")
|
||||||
.map((l) => ` ${l}`)
|
.map((l) => ` ${l}`)
|
||||||
.join("\n");
|
.join("\n");
|
||||||
message = `Values have the same structure but are not reference-equal:\n\n${red(
|
message = `Values have the same structure but are not reference-equal:\n\n${red(
|
||||||
withOffset
|
withOffset
|
||||||
|
@ -335,9 +343,9 @@ export function assertArrayContains(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!msg) {
|
if (!msg) {
|
||||||
msg = `actual: "${format(actual)}" expected to contain: "${format(
|
msg = `actual: "${_format(actual)}" expected to contain: "${_format(
|
||||||
expected
|
expected
|
||||||
)}"\nmissing: ${format(missing)}`;
|
)}"\nmissing: ${_format(missing)}`;
|
||||||
}
|
}
|
||||||
throw new AssertionError(msg);
|
throw new AssertionError(msg);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||||
import {
|
import {
|
||||||
|
_format,
|
||||||
assert,
|
assert,
|
||||||
assertNotEquals,
|
assertNotEquals,
|
||||||
assertStringContains,
|
assertStringContains,
|
||||||
|
@ -15,7 +16,7 @@ import {
|
||||||
unimplemented,
|
unimplemented,
|
||||||
unreachable,
|
unreachable,
|
||||||
} from "./asserts.ts";
|
} from "./asserts.ts";
|
||||||
import { red, green, gray, bold, yellow } from "../fmt/colors.ts";
|
import { red, green, gray, bold, yellow, stripColor } from "../fmt/colors.ts";
|
||||||
|
|
||||||
Deno.test("testingEqual", function (): void {
|
Deno.test("testingEqual", function (): void {
|
||||||
assert(equal("world", "world"));
|
assert(equal("world", "world"));
|
||||||
|
@ -176,7 +177,23 @@ Deno.test("testingArrayContains", function (): void {
|
||||||
assertThrows(
|
assertThrows(
|
||||||
(): void => assertArrayContains(fixtureObject, [{ deno: "node" }]),
|
(): void => assertArrayContains(fixtureObject, [{ deno: "node" }]),
|
||||||
AssertionError,
|
AssertionError,
|
||||||
`actual: "[ { deno: "luv" }, { deno: "Js" } ]" expected to contain: "[ { deno: "node" } ]"\nmissing: [ { deno: "node" } ]`
|
`actual: "[
|
||||||
|
{
|
||||||
|
deno: "luv",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deno: "Js",
|
||||||
|
},
|
||||||
|
]" expected to contain: "[
|
||||||
|
{
|
||||||
|
deno: "node",
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
missing: [
|
||||||
|
{
|
||||||
|
deno: "node",
|
||||||
|
},
|
||||||
|
]`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -342,13 +359,13 @@ Deno.test({
|
||||||
assertThrows(
|
assertThrows(
|
||||||
(): void => assertEquals([1, "2", 3], ["1", "2", 3]),
|
(): void => assertEquals([1, "2", 3], ["1", "2", 3]),
|
||||||
AssertionError,
|
AssertionError,
|
||||||
[
|
`
|
||||||
"Values are not equal:",
|
[
|
||||||
...createHeader(),
|
- 1,
|
||||||
removed(`- [ ${yellow("1")}, ${green('"2"')}, ${yellow("3")} ]`),
|
+ "1",
|
||||||
added(`+ [ ${green('"1"')}, ${green('"2"')}, ${yellow("3")} ]`),
|
"2",
|
||||||
"",
|
3,
|
||||||
].join("\n")
|
]`
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -359,17 +376,16 @@ Deno.test({
|
||||||
assertThrows(
|
assertThrows(
|
||||||
(): void => assertEquals({ a: 1, b: "2", c: 3 }, { a: 1, b: 2, c: [3] }),
|
(): void => assertEquals({ a: 1, b: "2", c: 3 }, { a: 1, b: 2, c: [3] }),
|
||||||
AssertionError,
|
AssertionError,
|
||||||
[
|
`
|
||||||
"Values are not equal:",
|
{
|
||||||
...createHeader(),
|
a: 1,
|
||||||
removed(
|
+ b: 2,
|
||||||
`- { a: ${yellow("1")}, b: ${green('"2"')}, c: ${yellow("3")} }`
|
+ c: [
|
||||||
),
|
+ 3,
|
||||||
added(
|
+ ],
|
||||||
`+ { a: ${yellow("1")}, b: ${yellow("2")}, c: [ ${yellow("3")} ] }`
|
- b: "2",
|
||||||
),
|
- c: 3,
|
||||||
"",
|
}`
|
||||||
].join("\n")
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -418,13 +434,14 @@ Deno.test({
|
||||||
assertThrows(
|
assertThrows(
|
||||||
(): void => assertStrictEquals({ a: 1, b: 2 }, { a: 1, c: [3] }),
|
(): void => assertStrictEquals({ a: 1, b: 2 }, { a: 1, c: [3] }),
|
||||||
AssertionError,
|
AssertionError,
|
||||||
[
|
`
|
||||||
"Values are not strictly equal:",
|
{
|
||||||
...createHeader(),
|
a: 1,
|
||||||
removed(`- { a: ${yellow("1")}, b: ${yellow("2")} }`),
|
+ c: [
|
||||||
added(`+ { a: ${yellow("1")}, c: [ ${yellow("3")} ] }`),
|
+ 3,
|
||||||
"",
|
+ ],
|
||||||
].join("\n")
|
- b: 2,
|
||||||
|
}`
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -435,10 +452,12 @@ Deno.test({
|
||||||
assertThrows(
|
assertThrows(
|
||||||
(): void => assertStrictEquals({ a: 1, b: 2 }, { a: 1, b: 2 }),
|
(): void => assertStrictEquals({ a: 1, b: 2 }, { a: 1, b: 2 }),
|
||||||
AssertionError,
|
AssertionError,
|
||||||
[
|
`Values have the same structure but are not reference-equal:
|
||||||
"Values have the same structure but are not reference-equal:\n",
|
|
||||||
red(` { a: ${yellow("1")}, b: ${yellow("2")} }`),
|
{
|
||||||
].join("\n")
|
a: 1,
|
||||||
|
b: 2,
|
||||||
|
}`
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -535,3 +554,75 @@ Deno.test("Assert Throws Async Non-Error Fail", () => {
|
||||||
"A non-Error object was thrown or rejected."
|
"A non-Error object was thrown or rejected."
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Deno.test("assertEquals diff for differently ordered objects", () => {
|
||||||
|
assertThrows(
|
||||||
|
() => {
|
||||||
|
assertEquals(
|
||||||
|
{
|
||||||
|
aaaaaaaaaaaaaaaaaaaaaaaa: 0,
|
||||||
|
bbbbbbbbbbbbbbbbbbbbbbbb: 0,
|
||||||
|
ccccccccccccccccccccccc: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ccccccccccccccccccccccc: 1,
|
||||||
|
aaaaaaaaaaaaaaaaaaaaaaaa: 0,
|
||||||
|
bbbbbbbbbbbbbbbbbbbbbbbb: 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
AssertionError,
|
||||||
|
`
|
||||||
|
{
|
||||||
|
aaaaaaaaaaaaaaaaaaaaaaaa: 0,
|
||||||
|
bbbbbbbbbbbbbbbbbbbbbbbb: 0,
|
||||||
|
- ccccccccccccccccccccccc: 0,
|
||||||
|
+ ccccccccccccccccccccccc: 1,
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check that the diff formatter overrides some default behaviours of
|
||||||
|
// `Deno.inspect()` which are problematic for diffing.
|
||||||
|
Deno.test("assert diff formatting", () => {
|
||||||
|
// Wraps objects into multiple lines even when they are small. Prints trailing
|
||||||
|
// commas.
|
||||||
|
assertEquals(
|
||||||
|
stripColor(_format({ a: 1, b: 2 })),
|
||||||
|
`{
|
||||||
|
a: 1,
|
||||||
|
b: 2,
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Same for nested small objects.
|
||||||
|
assertEquals(
|
||||||
|
stripColor(_format([{ x: { a: 1, b: 2 }, y: ["a", "b"] }])),
|
||||||
|
`[
|
||||||
|
{
|
||||||
|
x: {
|
||||||
|
a: 1,
|
||||||
|
b: 2,
|
||||||
|
},
|
||||||
|
y: [
|
||||||
|
"a",
|
||||||
|
"b",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Grouping is disabled.
|
||||||
|
assertEquals(
|
||||||
|
stripColor(_format(["i", "i", "i", "i", "i", "i", "i"])),
|
||||||
|
`[
|
||||||
|
"i",
|
||||||
|
"i",
|
||||||
|
"i",
|
||||||
|
"i",
|
||||||
|
"i",
|
||||||
|
"i",
|
||||||
|
"i",
|
||||||
|
]`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in a new issue