summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNayeem Rahman <nayeemrmn99@gmail.com>2020-07-11 05:52:18 +0100
committerGitHub <noreply@github.com>2020-07-11 00:52:18 -0400
commit5ec41cbcc2778a80b6ee91f0c391fc2edec0a8e0 (patch)
tree71d44638e72871624aef63deca19eb271ffa8604
parent40d081d3d9f64bcd2524da86fb78808ac1d7b888 (diff)
feat(Deno.inspect): Add sorted, trailingComma, compact and iterableLimit to InspectOptions (#6591)
-rw-r--r--cli/js/lib.deno.ns.d.ts12
-rw-r--r--cli/js/repl.ts6
-rw-r--r--cli/js/testing.ts4
-rw-r--r--cli/js/web/console.ts296
-rw-r--r--cli/tests/unit/console_test.ts136
-rw-r--r--cli/tests/unit/headers_test.ts4
-rw-r--r--cli/tests/unit/internals_test.ts4
-rw-r--r--std/README.md7
-rw-r--r--std/testing/asserts.ts26
-rw-r--r--std/testing/asserts_test.ts153
10 files changed, 463 insertions, 185 deletions
diff --git a/cli/js/lib.deno.ns.d.ts b/cli/js/lib.deno.ns.d.ts
index 41243d0ca..32b9bb39b 100644
--- a/cli/js/lib.deno.ns.d.ts
+++ b/cli/js/lib.deno.ns.d.ts
@@ -2034,8 +2034,18 @@ declare namespace Deno {
| PluginPermissionDescriptor
| HrtimePermissionDescriptor;
- interface InspectOptions {
+ export interface InspectOptions {
+ /** Traversal depth for nested objects. Defaults to 4. */
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
diff --git a/cli/js/repl.ts b/cli/js/repl.ts
index daa112e1e..8a37d991f 100644
--- a/cli/js/repl.ts
+++ b/cli/js/repl.ts
@@ -3,16 +3,16 @@
import { exit } from "./ops/os.ts";
import { core } from "./core.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 { close } from "./ops/resources.ts";
function replLog(...args: unknown[]): void {
- core.print(stringifyArgs(args) + "\n");
+ core.print(inspectArgs(args) + "\n");
}
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
diff --git a/cli/js/testing.ts b/cli/js/testing.ts
index 0648683a4..d38c5427d 100644
--- a/cli/js/testing.ts
+++ b/cli/js/testing.ts
@@ -2,7 +2,7 @@
import { gray, green, italic, red, yellow } from "./colors.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 { exposeForTest } from "./internals.ts";
import { TextEncoder } from "./web/text_encoding.ts";
@@ -205,7 +205,7 @@ function reportToConsole(message: TestMessage): void {
for (const { name, error } of failures) {
log(name);
- log(stringifyArgs([error!]));
+ log(inspectArgs([error!]));
log("");
}
diff --git a/cli/js/web/console.ts b/cli/js/web/console.ts
index 1f96bfe9d..5ddf50114 100644
--- a/cli/js/web/console.ts
+++ b/cli/js/web/console.ts
@@ -16,16 +16,28 @@ import {
} from "../colors.ts";
type ConsoleContext = Set<unknown>;
-type InspectOptions = Partial<{
- depth: number;
- indentLevel: number;
-}>;
+
+export interface InspectOptions {
+ 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_MAX_DEPTH = 4; // Default depth of logging nested objects
const LINE_BREAKING_LENGTH = 80;
-const MAX_ITERABLE_LENGTH = 100;
const MIN_GROUP_LENGTH = 6;
const STR_ABBREVIATE_SIZE = 100;
// Char codes
@@ -63,7 +75,7 @@ function getClassInstanceName(instance: unknown): string {
return "";
}
-function createFunctionString(value: Function, _ctx: ConsoleContext): string {
+function inspectFunction(value: Function, _ctx: ConsoleContext): string {
// Might be Function/AsyncFunction/GeneratorFunction
const cstrName = Object.getPrototypeOf(value).constructor.name;
if (value.name && value.name !== "anonymous") {
@@ -73,7 +85,7 @@ function createFunctionString(value: Function, _ctx: ConsoleContext): string {
return `[${cstrName}]`;
}
-interface IterablePrintConfig<T> {
+interface InspectIterableOptions<T> {
typeName: string;
displayName: string;
delims: [string, string];
@@ -81,23 +93,24 @@ interface IterablePrintConfig<T> {
entry: [unknown, T],
ctx: ConsoleContext,
level: number,
- maxLevel: number,
+ inspectOptions: Required<InspectOptions>,
next: () => IteratorResult<[unknown, T], unknown>
) => string;
group: boolean;
+ sort: boolean;
}
type IterableEntries<T> = Iterable<T> & {
entries(): IterableIterator<[unknown, T]>;
};
-function createIterableString<T>(
+function inspectIterable<T>(
value: IterableEntries<T>,
ctx: ConsoleContext,
level: number,
- maxLevel: number,
- config: IterablePrintConfig<T>
+ options: InspectIterableOptions<T>,
+ inspectOptions: Required<InspectOptions>
): string {
- if (level >= maxLevel) {
- return cyan(`[${config.typeName}]`);
+ if (level >= inspectOptions.depth) {
+ return cyan(`[${options.typeName}]`);
}
ctx.add(value);
@@ -109,42 +122,57 @@ function createIterableString<T>(
return iter.next();
};
for (const el of iter) {
- if (entriesLength < MAX_ITERABLE_LENGTH) {
+ if (entriesLength < inspectOptions.iterableLimit) {
entries.push(
- config.entryHandler(el, ctx, level + 1, maxLevel, next.bind(iter))
+ options.entryHandler(
+ el,
+ ctx,
+ level + 1,
+ inspectOptions,
+ next.bind(iter)
+ )
);
}
entriesLength++;
}
ctx.delete(value);
- if (entriesLength > MAX_ITERABLE_LENGTH) {
- const nmore = entriesLength - MAX_ITERABLE_LENGTH;
+ if (options.sort) {
+ entries.sort();
+ }
+
+ if (entriesLength > inspectOptions.iterableLimit) {
+ const nmore = entriesLength - inspectOptions.iterableLimit;
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 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;
- if (config.group && entries.length > MIN_GROUP_LENGTH) {
+ if (options.group && entries.length > MIN_GROUP_LENGTH) {
const groups = groupEntries(entries, level, value);
iContent = `${initIndentation}${groups.join(
entryIndentation
)}${closingIndentation}`;
} else {
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(
entryIndentation
)}${closingIndentation}`;
}
}
- return `${iPrefix}${config.delims[0]}${iContent}${config.delims[1]}`;
+ return `${iPrefix}${options.delims[0]}${iContent}${options.delims[1]}`;
}
// Ported from Node.js
@@ -152,12 +180,13 @@ function createIterableString<T>(
function groupEntries<T>(
entries: string[],
level: number,
- value: Iterable<T>
+ value: Iterable<T>,
+ iterableLimit = 100
): string[] {
let totalLength = 0;
let maxLength = 0;
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.
entriesLength--;
}
@@ -254,7 +283,7 @@ function groupEntries<T>(
}
tmp.push(str);
}
- if (MAX_ITERABLE_LENGTH < entries.length) {
+ if (iterableLimit < entries.length) {
tmp.push(entries[entriesLength]);
}
entries = tmp;
@@ -262,11 +291,11 @@ function groupEntries<T>(
return entries;
}
-function stringify(
+function inspectValue(
value: unknown,
ctx: ConsoleContext,
level: number,
- maxLevel: number
+ inspectOptions: Required<InspectOptions>
): string {
switch (typeof value) {
case "string":
@@ -283,7 +312,7 @@ function stringify(
case "bigint": // Bigints are yellow
return yellow(`${value}n`);
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
if (value === null) {
return bold("null");
@@ -294,7 +323,7 @@ function stringify(
return cyan("[Circular]");
}
- return createObjectString(value, ctx, level, maxLevel);
+ return inspectObject(value, ctx, level, inspectOptions);
default:
// Not implemented is red
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
-function stringifyWithQuotes(
+function inspectValueWithQuotes(
value: unknown,
ctx: ConsoleContext,
level: number,
- maxLevel: number
+ inspectOptions: Required<InspectOptions>
): string {
switch (typeof value) {
case "string":
@@ -334,21 +363,21 @@ function stringifyWithQuotes(
: value;
return green(quoteString(trunc)); // Quoted strings are green
default:
- return stringify(value, ctx, level, maxLevel);
+ return inspectValue(value, ctx, level, inspectOptions);
}
}
-function createArrayString(
+function inspectArray(
value: unknown[],
ctx: ConsoleContext,
level: number,
- maxLevel: number
+ inspectOptions: Required<InspectOptions>
): string {
- const printConfig: IterablePrintConfig<unknown> = {
+ const options: InspectIterableOptions<unknown> = {
typeName: "Array",
displayName: "",
delims: ["[", "]"],
- entryHandler: (entry, ctx, level, maxLevel, next): string => {
+ entryHandler: (entry, ctx, level, inspectOptions, next): string => {
const [index, val] = entry as [number, unknown];
let i = index;
if (!value.hasOwnProperty(i)) {
@@ -361,117 +390,127 @@ function createArrayString(
const ending = emptyItems > 1 ? "s" : "";
return dim(`<${emptyItems} empty item${ending}>`);
} 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,
value: TypedArray,
ctx: ConsoleContext,
level: number,
- maxLevel: number
+ inspectOptions: Required<InspectOptions>
): string {
const valueLength = value.length;
- const printConfig: IterablePrintConfig<unknown> = {
+ const options: InspectIterableOptions<unknown> = {
typeName: typedArrayName,
displayName: `${typedArrayName}(${valueLength})`,
delims: ["[", "]"],
- entryHandler: (entry, ctx, level, maxLevel): string => {
+ entryHandler: (entry, ctx, level, inspectOptions): string => {
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>,
ctx: ConsoleContext,
level: number,
- maxLevel: number
+ inspectOptions: Required<InspectOptions>
): string {
- const printConfig: IterablePrintConfig<unknown> = {
+ const options: InspectIterableOptions<unknown> = {
typeName: "Set",
displayName: "Set",
delims: ["{", "}"],
- entryHandler: (entry, ctx, level, maxLevel): string => {
+ entryHandler: (entry, ctx, level, inspectOptions): string => {
const val = entry[1];
- return stringifyWithQuotes(val, ctx, level + 1, maxLevel);
+ return inspectValueWithQuotes(val, ctx, level + 1, inspectOptions);
},
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>,
ctx: ConsoleContext,
level: number,
- maxLevel: number
+ inspectOptions: Required<InspectOptions>
): string {
- const printConfig: IterablePrintConfig<[unknown]> = {
+ const options: InspectIterableOptions<[unknown]> = {
typeName: "Map",
displayName: "Map",
delims: ["{", "}"],
- entryHandler: (entry, ctx, level, maxLevel): string => {
+ entryHandler: (entry, ctx, level, inspectOptions): string => {
const [key, val] = entry;
- return `${stringifyWithQuotes(
+ return `${inspectValueWithQuotes(
key,
ctx,
level + 1,
- maxLevel
- )} => ${stringifyWithQuotes(val, ctx, level + 1, maxLevel)}`;
+ inspectOptions
+ )} => ${inspectValueWithQuotes(val, ctx, level + 1, inspectOptions)}`;
},
group: false,
+ sort: inspectOptions.sorted,
};
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- return createIterableString(value as any, ctx, level, maxLevel, printConfig);
+ return inspectIterable(
+ // 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
}
-function createWeakMapString(): string {
+function inspectWeakMap(): string {
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
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
}
/* 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
}
-function createBooleanWrapperString(value: Boolean): string {
+function inspectBooleanObject(value: Boolean): string {
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
}
/* eslint-enable @typescript-eslint/ban-types */
-function createPromiseString(
+function inspectPromise(
value: Promise<unknown>,
ctx: ConsoleContext,
level: number,
- maxLevel: number
+ inspectOptions: Required<InspectOptions>
): string {
const [state, result] = Deno.core.getPromiseDetails(value);
@@ -482,11 +521,11 @@ function createPromiseString(
const prefix =
state === PromiseState.Fulfilled ? "" : `${red("<rejected>")} `;
- const str = `${prefix}${stringifyWithQuotes(
+ const str = `${prefix}${inspectValueWithQuotes(
result,
ctx,
level + 1,
- maxLevel
+ inspectOptions
)}`;
if (str.length + PROMISE_STRING_BASE_LENGTH > LINE_BREAKING_LENGTH) {
@@ -498,13 +537,13 @@ function createPromiseString(
// TODO: Proxy
-function createRawObjectString(
+function inspectRawObject(
value: Record<string, unknown>,
ctx: ConsoleContext,
level: number,
- maxLevel: number
+ inspectOptions: Required<InspectOptions>
): string {
- if (level >= maxLevel) {
+ if (level >= inspectOptions.depth) {
return cyan("[Object]"); // wrappers are in cyan
}
ctx.add(value);
@@ -525,20 +564,31 @@ function createRawObjectString(
const entries: string[] = [];
const stringKeys = Object.keys(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) {
entries.push(
- `${key}: ${stringifyWithQuotes(value[key], ctx, level + 1, maxLevel)}`
+ `${key}: ${inspectValueWithQuotes(
+ value[key],
+ ctx,
+ level + 1,
+ inspectOptions
+ )}`
);
}
for (const key of symbolKeys) {
entries.push(
- `${key.toString()}: ${stringifyWithQuotes(
+ `${key.toString()}: ${inspectValueWithQuotes(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
value[key as any],
ctx,
level + 1,
- maxLevel
+ inspectOptions
)}`
);
}
@@ -550,12 +600,12 @@ function createRawObjectString(
if (entries.length === 0) {
baseString = "{}";
- } else if (totalLength > LINE_BREAKING_LENGTH) {
+ } else if (totalLength > LINE_BREAKING_LENGTH || !inspectOptions.compact) {
const entryIndent = DEFAULT_INDENT.repeat(level + 1);
const closingIndent = DEFAULT_INDENT.repeat(level);
- baseString = `{\n${entryIndent}${entries.join(
- `,\n${entryIndent}`
- )}\n${closingIndent}}`;
+ baseString = `{\n${entryIndent}${entries.join(`,\n${entryIndent}`)}${
+ inspectOptions.trailingComma ? "," : ""
+ }\n${closingIndent}}`;
} else {
baseString = `{ ${entries.join(", ")} }`;
}
@@ -567,9 +617,11 @@ function createRawObjectString(
return baseString;
}
-function createObjectString(
+function inspectObject(
value: {},
- ...args: [ConsoleContext, number, number]
+ consoleContext: ConsoleContext,
+ level: number,
+ inspectOptions: Required<InspectOptions>
): string {
if (customInspect in value && typeof value[customInspect] === "function") {
try {
@@ -579,43 +631,46 @@ function createObjectString(
if (value instanceof Error) {
return String(value.stack);
} else if (Array.isArray(value)) {
- return createArrayString(value, ...args);
+ return inspectArray(value, consoleContext, level, inspectOptions);
} else if (value instanceof Number) {
- return createNumberWrapperString(value);
+ return inspectNumberObject(value);
} else if (value instanceof Boolean) {
- return createBooleanWrapperString(value);
+ return inspectBooleanObject(value);
} else if (value instanceof String) {
- return createStringWrapperString(value);
+ return inspectStringObject(value);
} else if (value instanceof Promise) {
- return createPromiseString(value, ...args);
+ return inspectPromise(value, consoleContext, level, inspectOptions);
} else if (value instanceof RegExp) {
- return createRegExpString(value);
+ return inspectRegExp(value);
} else if (value instanceof Date) {
- return createDateString(value);
+ return inspectDate(value);
} else if (value instanceof Set) {
- return createSetString(value, ...args);
+ return inspectSet(value, consoleContext, level, inspectOptions);
} else if (value instanceof Map) {
- return createMapString(value, ...args);
+ return inspectMap(value, consoleContext, level, inspectOptions);
} else if (value instanceof WeakSet) {
- return createWeakSetString();
+ return inspectWeakSet();
} else if (value instanceof WeakMap) {
- return createWeakMapString();
+ return inspectWeakMap();
} else if (isTypedArray(value)) {
- return createTypedArrayString(
+ return inspectTypedArray(
Object.getPrototypeOf(value).constructor.name,
value,
- ...args
+ consoleContext,
+ level,
+ inspectOptions
);
} else {
// Otherwise, default object formatting
- return createRawObjectString(value, ...args);
+ return inspectRawObject(value, consoleContext, level, inspectOptions);
}
}
-export function stringifyArgs(
+export function inspectArgs(
args: unknown[],
- { depth = DEFAULT_MAX_DEPTH, indentLevel = 0 }: InspectOptions = {}
+ inspectOptions: InspectOptions = {}
): string {
+ const rInspectOptions = { ...DEFAULT_INSPECT_OPTIONS, ...inspectOptions };
const first = args[0];
let a = 0;
let str = "";
@@ -658,7 +713,12 @@ export function stringifyArgs(
case CHAR_LOWERCASE_O:
case CHAR_UPPERCASE_O:
// format as an object
- tempStr = stringify(args[++a], new Set<unknown>(), 0, depth);
+ tempStr = inspectValue(
+ args[++a],
+ new Set<unknown>(),
+ 0,
+ rInspectOptions
+ );
break;
case CHAR_PERCENT:
str += first.slice(lastPos, i);
@@ -701,14 +761,14 @@ export function stringifyArgs(
str += value;
} else {
// 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 = " ";
a++;
}
- if (indentLevel > 0) {
- const groupIndent = DEFAULT_INDENT.repeat(indentLevel);
+ if (rInspectOptions.indentLevel > 0) {
+ const groupIndent = DEFAULT_INDENT.repeat(rInspectOptions.indentLevel);
if (str.indexOf("\n") !== -1) {
str = str.replace(/\n/g, `\n${groupIndent}`);
}
@@ -745,7 +805,7 @@ export class Console {
log = (...args: unknown[]): void => {
this.#printFunc(
- stringifyArgs(args, {
+ inspectArgs(args, {
indentLevel: this.indentLevel,
}) + "\n",
false
@@ -756,14 +816,14 @@ export class Console {
info = this.log;
dir = (obj: unknown, options: InspectOptions = {}): void => {
- this.#printFunc(stringifyArgs([obj], options) + "\n", false);
+ this.#printFunc(inspectArgs([obj], options) + "\n", false);
};
dirxml = this.dir;
warn = (...args: unknown[]): void => {
this.#printFunc(
- stringifyArgs(args, {
+ inspectArgs(args, {
indentLevel: this.indentLevel,
}) + "\n",
true
@@ -832,7 +892,10 @@ export class Console {
const values: 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 =>
this.log(cliTable(header, body));
const createColumn = (value: unknown, shift?: number): string[] => [
@@ -966,7 +1029,7 @@ export class Console {
};
trace = (...args: unknown[]): void => {
- const message = stringifyArgs(args, { indentLevel: 0 });
+ const message = inspectArgs(args, { indentLevel: 0 });
const err = {
name: "Trace",
message,
@@ -980,19 +1043,24 @@ export class Console {
}
}
-export const customInspect = Symbol("Deno.symbols.customInspect");
+export const customInspect = Symbol("Deno.customInspect");
export function inspect(
value: unknown,
- { depth = DEFAULT_MAX_DEPTH }: InspectOptions = {}
+ inspectOptions: InspectOptions = {}
): string {
if (typeof value === "string") {
return value;
} 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.
exposeForTest("Console", Console);
-exposeForTest("stringifyArgs", stringifyArgs);
+exposeForTest("inspectArgs", inspectArgs);
diff --git a/cli/tests/unit/console_test.ts b/cli/tests/unit/console_test.ts
index d7281fbb2..7a03cd6b6 100644
--- a/cli/tests/unit/console_test.ts
+++ b/cli/tests/unit/console_test.ts
@@ -11,22 +11,15 @@
import { assert, assertEquals, unitTest } from "./test_util.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 {
Console,
- stringifyArgs,
+ inspectArgs,
// @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol
} = Deno[Deno.internal];
function stringify(...args: unknown[]): string {
- return stripColor(stringifyArgs(args).replace(/\n$/, ""));
+ return stripColor(inspectArgs(args).replace(/\n$/, ""));
}
// test cases from web-platform-tests
@@ -238,7 +231,7 @@ unitTest(function consoleTestStringifyCircular(): void {
'TAG { str: 1, Symbol(sym): 2, Symbol(Symbol.toStringTag): "TAG" }'
);
// 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 */
@@ -246,24 +239,21 @@ unitTest(function consoleTestStringifyWithDepth(): void {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const nestedObj: any = { a: { b: { c: { d: { e: { f: 42 } } } } } };
assertEquals(
- stripColor(stringifyArgs([nestedObj], { depth: 3 })),
+ stripColor(inspectArgs([nestedObj], { depth: 3 })),
"{ a: { b: { c: [Object] } } }"
);
assertEquals(
- stripColor(stringifyArgs([nestedObj], { depth: 4 })),
+ stripColor(inspectArgs([nestedObj], { depth: 4 })),
"{ a: { b: { c: { d: [Object] } } } }"
);
+ assertEquals(stripColor(inspectArgs([nestedObj], { depth: 0 })), "[Object]");
assertEquals(
- stripColor(stringifyArgs([nestedObj], { depth: 0 })),
- "[Object]"
- );
- assertEquals(
- stripColor(stringifyArgs([nestedObj])),
+ stripColor(inspectArgs([nestedObj])),
"{ a: { b: { c: { d: [Object] } } } }"
);
// test inspect is working the same way
assertEquals(
- stripColor(inspect(nestedObj, { depth: 4 })),
+ stripColor(Deno.inspect(nestedObj, { depth: 4 })),
"{ a: { b: { c: { d: [Object] } } } }"
);
});
@@ -653,7 +643,7 @@ unitTest(function consoleTestWithCustomInspectorError(): void {
assertEquals(stringify(new B({ a: "a" })), "a");
assertEquals(
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"));
});
});
+
+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 }`
+ );
+});
diff --git a/cli/tests/unit/headers_test.ts b/cli/tests/unit/headers_test.ts
index 8fbf1d4e4..2156fb56b 100644
--- a/cli/tests/unit/headers_test.ts
+++ b/cli/tests/unit/headers_test.ts
@@ -6,7 +6,7 @@ import {
assertStringContains,
} from "./test_util.ts";
const {
- stringifyArgs,
+ inspectArgs,
// @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol
} = Deno[Deno.internal];
@@ -402,7 +402,7 @@ unitTest(function toStringShouldBeWebCompatibility(): void {
});
function stringify(...args: unknown[]): string {
- return stringifyArgs(args).replace(/\n$/, "");
+ return inspectArgs(args).replace(/\n$/, "");
}
unitTest(function customInspectReturnsCorrectHeadersFormat(): void {
diff --git a/cli/tests/unit/internals_test.ts b/cli/tests/unit/internals_test.ts
index 3f4bdae79..e59783e54 100644
--- a/cli/tests/unit/internals_test.ts
+++ b/cli/tests/unit/internals_test.ts
@@ -3,8 +3,8 @@ import { unitTest, assert } from "./test_util.ts";
unitTest(function internalsExists(): void {
const {
- stringifyArgs,
+ inspectArgs,
// @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol
} = Deno[Deno.internal];
- assert(!!stringifyArgs);
+ assert(!!inspectArgs);
});
diff --git a/std/README.md b/std/README.md
index bf1d6940b..12380412f 100644
--- a/std/README.md
+++ b/std/README.md
@@ -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`,
`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
@@ -29,7 +32,7 @@ To browse documentation for modules:
- Go to https://deno.land/std/.
- Navigate to any module of interest.
-- Click the "DOCUMENTATION" link.
+- Click "View Documentation".
## Contributing
diff --git a/std/testing/asserts.ts b/std/testing/asserts.ts
index 10e2b9c97..b1164090d 100644
--- a/std/testing/asserts.ts
+++ b/std/testing/asserts.ts
@@ -19,8 +19,16 @@ export class AssertionError extends Error {
}
}
-function format(v: unknown): string {
- let string = globalThis.Deno ? Deno.inspect(v) : String(v);
+export function _format(v: unknown): string {
+ let string = globalThis.Deno
+ ? Deno.inspect(v, {
+ depth: Infinity,
+ sorted: true,
+ trailingComma: true,
+ compact: false,
+ iterableLimit: Infinity,
+ })
+ : String(v);
if (typeof v == "string") {
string = `"${string.replace(/(?=["\\])/g, "\\")}"`;
}
@@ -167,8 +175,8 @@ export function assertEquals(
return;
}
let message = "";
- const actualString = format(actual);
- const expectedString = format(expected);
+ const actualString = _format(actual);
+ const expectedString = _format(expected);
try {
const diffResult = diff(
actualString.split("\n"),
@@ -248,13 +256,13 @@ export function assertStrictEquals<T>(
if (msg) {
message = msg;
} else {
- const actualString = format(actual);
- const expectedString = format(expected);
+ const actualString = _format(actual);
+ const expectedString = _format(expected);
if (actualString === expectedString) {
const withOffset = actualString
.split("\n")
- .map((l) => ` ${l}`)
+ .map((l) => ` ${l}`)
.join("\n");
message = `Values have the same structure but are not reference-equal:\n\n${red(
withOffset
@@ -335,9 +343,9 @@ export function assertArrayContains(
return;
}
if (!msg) {
- msg = `actual: "${format(actual)}" expected to contain: "${format(
+ msg = `actual: "${_format(actual)}" expected to contain: "${_format(
expected
- )}"\nmissing: ${format(missing)}`;
+ )}"\nmissing: ${_format(missing)}`;
}
throw new AssertionError(msg);
}
diff --git a/std/testing/asserts_test.ts b/std/testing/asserts_test.ts
index 011e98590..c7fa5a737 100644
--- a/std/testing/asserts_test.ts
+++ b/std/testing/asserts_test.ts
@@ -1,5 +1,6 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import {
+ _format,
assert,
assertNotEquals,
assertStringContains,
@@ -15,7 +16,7 @@ import {
unimplemented,
unreachable,
} 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 {
assert(equal("world", "world"));
@@ -176,7 +177,23 @@ Deno.test("testingArrayContains", function (): void {
assertThrows(
(): void => assertArrayContains(fixtureObject, [{ deno: "node" }]),
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(
(): void => assertEquals([1, "2", 3], ["1", "2", 3]),
AssertionError,
- [
- "Values are not equal:",
- ...createHeader(),
- removed(`- [ ${yellow("1")}, ${green('"2"')}, ${yellow("3")} ]`),
- added(`+ [ ${green('"1"')}, ${green('"2"')}, ${yellow("3")} ]`),
- "",
- ].join("\n")
+ `
+ [
+- 1,
++ "1",
+ "2",
+ 3,
+ ]`
);
},
});
@@ -359,17 +376,16 @@ Deno.test({
assertThrows(
(): void => assertEquals({ a: 1, b: "2", c: 3 }, { a: 1, b: 2, c: [3] }),
AssertionError,
- [
- "Values are not equal:",
- ...createHeader(),
- removed(
- `- { a: ${yellow("1")}, b: ${green('"2"')}, c: ${yellow("3")} }`
- ),
- added(
- `+ { a: ${yellow("1")}, b: ${yellow("2")}, c: [ ${yellow("3")} ] }`
- ),
- "",
- ].join("\n")
+ `
+ {
+ a: 1,
++ b: 2,
++ c: [
++ 3,
++ ],
+- b: "2",
+- c: 3,
+ }`
);
},
});
@@ -418,13 +434,14 @@ Deno.test({
assertThrows(
(): void => assertStrictEquals({ a: 1, b: 2 }, { a: 1, c: [3] }),
AssertionError,
- [
- "Values are not strictly equal:",
- ...createHeader(),
- removed(`- { a: ${yellow("1")}, b: ${yellow("2")} }`),
- added(`+ { a: ${yellow("1")}, c: [ ${yellow("3")} ] }`),
- "",
- ].join("\n")
+ `
+ {
+ a: 1,
++ c: [
++ 3,
++ ],
+- b: 2,
+ }`
);
},
});
@@ -435,10 +452,12 @@ Deno.test({
assertThrows(
(): void => assertStrictEquals({ a: 1, b: 2 }, { a: 1, b: 2 }),
AssertionError,
- [
- "Values have the same structure but are not reference-equal:\n",
- red(` { a: ${yellow("1")}, b: ${yellow("2")} }`),
- ].join("\n")
+ `Values have the same structure but are not reference-equal:
+
+ {
+ a: 1,
+ b: 2,
+ }`
);
},
});
@@ -535,3 +554,75 @@ Deno.test("Assert Throws Async Non-Error Fail", () => {
"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",
+]`
+ );
+});