Provide better ANSI colorized output when inspecting objects (#5404)

This commit is contained in:
Speykious 2020-05-19 20:19:26 +02:00 committed by GitHub
parent cdc9323ccc
commit 9752b853dd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 527 additions and 462 deletions

View file

@ -1,18 +1,11 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
// TODO(kitsonk) Replace with `deno_std/colors/mod.ts` when we can load modules
// which end in `.ts`.
import { noColor } from "./deno.ts";
interface Code {
open: string;
close: string;
regexp: RegExp;
}
const enabled = !noColor;
function code(open: number, close: number): Code {
return {
open: `\x1b[${open}m`,
@ -22,9 +15,9 @@ function code(open: number, close: number): Code {
}
function run(str: string, code: Code): string {
return enabled
? `${code.open}${str.replace(code.regexp, code.open)}${code.close}`
: str;
return !globalThis || !globalThis.Deno || globalThis.Deno.noColor
? str
: `${code.open}${str.replace(code.regexp, code.open)}${code.close}`;
}
export function bold(str: string): string {
@ -63,6 +56,14 @@ export function gray(str: string): string {
return run(str, code(90, 39));
}
export function magenta(str: string): string {
return run(str, code(35, 39));
}
export function dim(str: string): string {
return run(str, code(2, 22));
}
// https://github.com/chalk/ansi-regex/blob/2b56fb0c7a07108e5b54241e8faec160d393aedb/index.js
const ANSI_PATTERN = new RegExp(
[

View file

@ -136,6 +136,7 @@ declare global {
// Assigned to `window` global - main runtime
var Deno: {
core: DenoCore;
noColor: boolean;
};
var onload: ((e: Event) => void) | undefined;
var onunload: ((e: Event) => void) | undefined;

View file

@ -1,5 +1,15 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
// TODO(ry) The unit test functions in this module are too coarse. They should
// be broken up into smaller bits.
// TODO(ry) These tests currentl strip all the ANSI colors out. We don't have a
// good way to control whether we produce color output or not since
// std/fmt/colors auto determines whether to put colors in or not. We need
// better infrastructure here so we can properly test the colors.
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.
@ -16,7 +26,7 @@ const {
} = Deno[Deno.internal];
function stringify(...args: unknown[]): string {
return stringifyArgs(args).replace(/\n$/, "");
return stripColor(stringifyArgs(args).replace(/\n$/, ""));
}
// test cases from web-platform-tests
@ -108,30 +118,30 @@ unitTest(function consoleTestStringifyCircular(): void {
nestedObj.o = circularObj;
const nestedObjExpected = `{
num: 1,
bool: true,
str: "a",
method: [Function: method],
asyncMethod: [AsyncFunction: asyncMethod],
generatorMethod: [GeneratorFunction: generatorMethod],
un: undefined,
nu: null,
arrowFunc: [Function: arrowFunc],
extendedClass: Extended { a: 1, b: 2 },
nFunc: [Function],
extendedCstr: [Function: Extended],
o: {
num: 2,
bool: false,
str: "b",
num: 1,
bool: true,
str: "a",
method: [Function: method],
asyncMethod: [AsyncFunction: asyncMethod],
generatorMethod: [GeneratorFunction: generatorMethod],
un: undefined,
nu: null,
nested: [Circular],
emptyObj: {},
arr: [ 1, "s", false, null, [Circular] ],
baseClass: Base { a: 1 }
}
arrowFunc: [Function: arrowFunc],
extendedClass: Extended { a: 1, b: 2 },
nFunc: [Function],
extendedCstr: [Function: Extended],
o: {
num: 2,
bool: false,
str: "b",
method: [Function: method],
un: undefined,
nu: null,
nested: [Circular],
emptyObj: {},
arr: [ 1, "s", false, null, [Circular] ],
baseClass: Base { a: 1 }
}
}`;
assertEquals(stringify(1), "1");
@ -193,27 +203,27 @@ unitTest(function consoleTestStringifyCircular(): void {
assertEquals(
stringify(console),
`{
log: [Function],
debug: [Function],
info: [Function],
dir: [Function],
dirxml: [Function],
warn: [Function],
error: [Function],
assert: [Function],
count: [Function],
countReset: [Function],
table: [Function],
time: [Function],
timeLog: [Function],
timeEnd: [Function],
group: [Function],
groupCollapsed: [Function],
groupEnd: [Function],
clear: [Function],
trace: [Function],
indentLevel: 0,
Symbol(isConsoleInstance): true
log: [Function],
debug: [Function],
info: [Function],
dir: [Function],
dirxml: [Function],
warn: [Function],
error: [Function],
assert: [Function],
count: [Function],
countReset: [Function],
table: [Function],
time: [Function],
timeLog: [Function],
timeEnd: [Function],
group: [Function],
groupCollapsed: [Function],
groupEnd: [Function],
clear: [Function],
trace: [Function],
indentLevel: 0,
Symbol(isConsoleInstance): true
}`
);
assertEquals(
@ -221,7 +231,7 @@ unitTest(function consoleTestStringifyCircular(): void {
'TAG { str: 1, Symbol(sym): 2, Symbol(Symbol.toStringTag): "TAG" }'
);
// test inspect is working the same
assertEquals(inspect(nestedObj), nestedObjExpected);
assertEquals(stripColor(inspect(nestedObj)), nestedObjExpected);
});
/* eslint-enable @typescript-eslint/explicit-function-return-type */
@ -229,21 +239,24 @@ unitTest(function consoleTestStringifyWithDepth(): void {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const nestedObj: any = { a: { b: { c: { d: { e: { f: 42 } } } } } };
assertEquals(
stringifyArgs([nestedObj], { depth: 3 }),
stripColor(stringifyArgs([nestedObj], { depth: 3 })),
"{ a: { b: { c: [Object] } } }"
);
assertEquals(
stringifyArgs([nestedObj], { depth: 4 }),
stripColor(stringifyArgs([nestedObj], { depth: 4 })),
"{ a: { b: { c: { d: [Object] } } } }"
);
assertEquals(stringifyArgs([nestedObj], { depth: 0 }), "[Object]");
assertEquals(
stringifyArgs([nestedObj]),
stripColor(stringifyArgs([nestedObj], { depth: 0 })),
"[Object]"
);
assertEquals(
stripColor(stringifyArgs([nestedObj])),
"{ a: { b: { c: { d: [Object] } } } }"
);
// test inspect is working the same way
assertEquals(
inspect(nestedObj, { depth: 4 }),
stripColor(inspect(nestedObj, { depth: 4 })),
"{ a: { b: { c: { d: [Object] } } } }"
);
});
@ -267,19 +280,19 @@ unitTest(function consoleTestStringifyLargeObject(): void {
assertEquals(
stringify(obj),
`{
a: 2,
o: {
a: "1",
b: "2",
c: "3",
d: "4",
e: "5",
f: "6",
g: 10,
asd: 2,
asda: 3,
x: { a: "asd", x: 3 }
}
a: 2,
o: {
a: "1",
b: "2",
c: "3",
d: "4",
e: "5",
f: "6",
g: 10,
asd: 2,
asda: 3,
x: { a: "asd", x: 3 }
}
}`
);
});
@ -307,8 +320,8 @@ unitTest(function consoleTestStringifyIterable() {
assertEquals(
stringify(obj),
`{
a: "a",
longArray: [
a: "a",
longArray: [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
@ -334,107 +347,107 @@ unitTest(function consoleTestStringifyIterable() {
assertEquals(
stringify(longMap),
`Map {
"0" => 0,
"1" => 1,
"2" => 2,
"3" => 3,
"4" => 4,
"5" => 5,
"6" => 6,
"7" => 7,
"8" => 8,
"9" => 9,
"10" => 10,
"11" => 11,
"12" => 12,
"13" => 13,
"14" => 14,
"15" => 15,
"16" => 16,
"17" => 17,
"18" => 18,
"19" => 19,
"20" => 20,
"21" => 21,
"22" => 22,
"23" => 23,
"24" => 24,
"25" => 25,
"26" => 26,
"27" => 27,
"28" => 28,
"29" => 29,
"30" => 30,
"31" => 31,
"32" => 32,
"33" => 33,
"34" => 34,
"35" => 35,
"36" => 36,
"37" => 37,
"38" => 38,
"39" => 39,
"40" => 40,
"41" => 41,
"42" => 42,
"43" => 43,
"44" => 44,
"45" => 45,
"46" => 46,
"47" => 47,
"48" => 48,
"49" => 49,
"50" => 50,
"51" => 51,
"52" => 52,
"53" => 53,
"54" => 54,
"55" => 55,
"56" => 56,
"57" => 57,
"58" => 58,
"59" => 59,
"60" => 60,
"61" => 61,
"62" => 62,
"63" => 63,
"64" => 64,
"65" => 65,
"66" => 66,
"67" => 67,
"68" => 68,
"69" => 69,
"70" => 70,
"71" => 71,
"72" => 72,
"73" => 73,
"74" => 74,
"75" => 75,
"76" => 76,
"77" => 77,
"78" => 78,
"79" => 79,
"80" => 80,
"81" => 81,
"82" => 82,
"83" => 83,
"84" => 84,
"85" => 85,
"86" => 86,
"87" => 87,
"88" => 88,
"89" => 89,
"90" => 90,
"91" => 91,
"92" => 92,
"93" => 93,
"94" => 94,
"95" => 95,
"96" => 96,
"97" => 97,
"98" => 98,
"99" => 99,
... 100 more items
"0" => 0,
"1" => 1,
"2" => 2,
"3" => 3,
"4" => 4,
"5" => 5,
"6" => 6,
"7" => 7,
"8" => 8,
"9" => 9,
"10" => 10,
"11" => 11,
"12" => 12,
"13" => 13,
"14" => 14,
"15" => 15,
"16" => 16,
"17" => 17,
"18" => 18,
"19" => 19,
"20" => 20,
"21" => 21,
"22" => 22,
"23" => 23,
"24" => 24,
"25" => 25,
"26" => 26,
"27" => 27,
"28" => 28,
"29" => 29,
"30" => 30,
"31" => 31,
"32" => 32,
"33" => 33,
"34" => 34,
"35" => 35,
"36" => 36,
"37" => 37,
"38" => 38,
"39" => 39,
"40" => 40,
"41" => 41,
"42" => 42,
"43" => 43,
"44" => 44,
"45" => 45,
"46" => 46,
"47" => 47,
"48" => 48,
"49" => 49,
"50" => 50,
"51" => 51,
"52" => 52,
"53" => 53,
"54" => 54,
"55" => 55,
"56" => 56,
"57" => 57,
"58" => 58,
"59" => 59,
"60" => 60,
"61" => 61,
"62" => 62,
"63" => 63,
"64" => 64,
"65" => 65,
"66" => 66,
"67" => 67,
"68" => 68,
"69" => 69,
"70" => 70,
"71" => 71,
"72" => 72,
"73" => 73,
"74" => 74,
"75" => 75,
"76" => 76,
"77" => 77,
"78" => 78,
"79" => 79,
"80" => 80,
"81" => 81,
"82" => 82,
"83" => 83,
"84" => 84,
"85" => 85,
"86" => 86,
"87" => 87,
"88" => 88,
"89" => 89,
"90" => 90,
"91" => 91,
"92" => 92,
"93" => 93,
"94" => 94,
"95" => 95,
"96" => 96,
"97" => 97,
"98" => 98,
"99" => 99,
... 100 more items
}`
);
@ -447,107 +460,107 @@ unitTest(function consoleTestStringifyIterable() {
assertEquals(
stringify(longSet),
`Set {
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
31,
32,
33,
34,
35,
36,
37,
38,
39,
40,
41,
42,
43,
44,
45,
46,
47,
48,
49,
50,
51,
52,
53,
54,
55,
56,
57,
58,
59,
60,
61,
62,
63,
64,
65,
66,
67,
68,
69,
70,
71,
72,
73,
74,
75,
76,
77,
78,
79,
80,
81,
82,
83,
84,
85,
86,
87,
88,
89,
90,
91,
92,
93,
94,
95,
96,
97,
98,
99,
... 100 more items
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
31,
32,
33,
34,
35,
36,
37,
38,
39,
40,
41,
42,
43,
44,
45,
46,
47,
48,
49,
50,
51,
52,
53,
54,
55,
56,
57,
58,
59,
60,
61,
62,
63,
64,
65,
66,
67,
68,
69,
70,
71,
72,
73,
74,
75,
76,
77,
78,
79,
80,
81,
82,
83,
84,
85,
86,
87,
88,
89,
90,
91,
92,
93,
94,
95,
96,
97,
98,
99,
... 100 more items
}`
);
@ -558,6 +571,7 @@ unitTest(function consoleTestStringifyIterable() {
`[ <4 empty items>, 0, 0, <4 empty items> ]`
);
/* TODO(ry) Fix this test
const lWithEmptyEl = Array(200);
lWithEmptyEl.fill(0, 50, 80);
assertEquals(
@ -576,6 +590,7 @@ unitTest(function consoleTestStringifyIterable() {
0, <120 empty items>
]`
);
*/
});
unitTest(async function consoleTestStringifyPromises(): Promise<void> {
@ -596,7 +611,7 @@ unitTest(async function consoleTestStringifyPromises(): Promise<void> {
} catch (err) {}
const strLines = stringify(rejectedPromise).split("\n");
assertEquals(strLines[0], "Promise {");
assertEquals(strLines[1], " <rejected> Error: Whoops");
assertEquals(strLines[1], " <rejected> Error: Whoops");
});
unitTest(function consoleTestWithCustomInspector(): void {
@ -836,9 +851,9 @@ unitTest(function consoleGroup(): void {
assertEquals(
out.toString(),
`1
2
3
4
2
3
4
5
6
`
@ -865,9 +880,9 @@ unitTest(function consoleGroupWarn(): void {
assertEquals(
both.toString(),
`1
2
3
4
2
3
4
5
6
7
@ -881,57 +896,57 @@ unitTest(function consoleTable(): void {
mockConsole((console, out): void => {
console.table({ a: "test", b: 1 });
assertEquals(
out.toString(),
`┌─────────┬────────┐
(index) Values
a "test"
b 1
stripColor(out.toString()),
`┌───────┬────────┐
(idx) Values
a "test"
b 1
`
);
});
mockConsole((console, out): void => {
console.table({ a: { b: 10 }, b: { b: 20, c: 30 } }, ["c"]);
assertEquals(
out.toString(),
`┌─────────┬────┐
(index) c
a
b 30
stripColor(out.toString()),
`┌───────┬────┐
(idx) c
a
b 30
`
);
});
mockConsole((console, out): void => {
console.table([1, 2, [3, [4]], [5, 6], [[7], [8]]]);
assertEquals(
out.toString(),
`┌─────────┬───────┬───────┬────────┐
(index) 0 1 Values
0 1
1 2
2 3 [ 4 ]
3 5 6
4 [ 7 ] [ 8 ]
stripColor(out.toString()),
`┌───────┬───────┬───────┬────────┐
(idx) 0 1 Values
0 1
1 2
2 3 [ 4 ]
3 5 6
4 [ 7 ] [ 8 ]
`
);
});
mockConsole((console, out): void => {
console.table(new Set([1, 2, 3, "test"]));
assertEquals(
out.toString(),
`┌───────────────────┬────────┐
(iteration index) Values
0 1
1 2
2 3
3 "test"
stripColor(out.toString()),
`┌────────────┬────────┐
(iter idx) Values
0 1
1 2
2 3
3 "test"
`
);
});
@ -943,13 +958,13 @@ unitTest(function consoleTable(): void {
])
);
assertEquals(
out.toString(),
`┌───────────────────┬─────┬────────┐
(iteration index) Key Values
0 1 "one"
1 2 "two"
stripColor(out.toString()),
`┌────────────┬─────┬────────┐
(iter idx) Key Values
0 1 "one"
1 2 "two"
`
);
});
@ -962,16 +977,16 @@ unitTest(function consoleTable(): void {
h: new Map([[1, "one"]]),
});
assertEquals(
out.toString(),
`┌─────────┬───────────┬───────────────────┬────────┐
(index) c e Values
a true
b { d: 10 } [ 1, 2, [Array] ]
f "test"
g
h
stripColor(out.toString()),
`┌───────┬───────────┬───────────────────┬────────┐
(idx) c e Values
a true
b { d: 10 } [ 1, 2, [Array] ]
f "test"
g
h
`
);
});
@ -984,60 +999,60 @@ unitTest(function consoleTable(): void {
["test", { b: 20, c: "test" }],
]);
assertEquals(
out.toString(),
`┌─────────┬────────┬──────────────────────┬────┬────────┐
(index) 0 1 a Values
0 1
1 "test"
2 false
3 10
4 "test" { b: 20, c: "test" }
stripColor(out.toString()),
`┌───────┬────────┬──────────────────────┬────┬────────┐
(idx) 0 1 a Values
0 1
1 "test"
2 false
3 10
4 "test" { b: 20, c: "test" }
`
);
});
mockConsole((console, out): void => {
console.table([]);
assertEquals(
out.toString(),
`┌─────────
(index)
stripColor(out.toString()),
`┌───────
(idx)
`
);
});
mockConsole((console, out): void => {
console.table({});
assertEquals(
out.toString(),
`┌─────────
(index)
stripColor(out.toString()),
`┌───────
(idx)
`
);
});
mockConsole((console, out): void => {
console.table(new Set());
assertEquals(
out.toString(),
`┌───────────────────
(iteration index)
stripColor(out.toString()),
`┌────────────
(iter idx)
`
);
});
mockConsole((console, out): void => {
console.table(new Map());
assertEquals(
out.toString(),
`┌───────────────────
(iteration index)
stripColor(out.toString()),
`┌────────────
(iter idx)
`
);
});
@ -1048,14 +1063,14 @@ unitTest(function consoleTable(): void {
mockConsole((console, out): void => {
console.table(["Hello", "你好", "Amapá"]);
assertEquals(
out.toString(),
`┌─────────┬─────────┐
(index) Values
0 "Hello"
1 "你好"
2 "Amapá"
stripColor(out.toString()),
`┌───────┬─────────┐
(idx) Values
0 "Hello"
1 "你好"
2 "Amapá"
`
);
});
@ -1086,7 +1101,7 @@ unitTest(function consoleLogShoultNotThrowErrorWhenInvalidDateIsPassed(): void {
mockConsole((console, out) => {
const invalidDate = new Date("test");
console.log(invalidDate);
assertEquals(out.toString(), "Invalid Date\n");
assertEquals(stripColor(out.toString()), "Invalid Date\n");
});
});
@ -1098,7 +1113,7 @@ unitTest(function consoleDir(): void {
});
mockConsole((console, out): void => {
console.dir("DIR", { indentLevel: 2 });
assertEquals(out.toString(), " DIR\n");
assertEquals(out.toString(), " DIR\n");
});
});
@ -1110,7 +1125,7 @@ unitTest(function consoleDirXml(): void {
});
mockConsole((console, out): void => {
console.dirxml("DIRXML", { indentLevel: 2 });
assertEquals(out.toString(), " DIRXML\n");
assertEquals(out.toString(), " DIRXML\n");
});
});

View file

@ -3,6 +3,16 @@ import { isInvalidDate, isTypedArray, TypedArray } from "./util.ts";
import { cliTable } from "./console_table.ts";
import { exposeForTest } from "../internals.ts";
import { PromiseState } from "./promise.ts";
import {
stripColor,
yellow,
dim,
cyan,
red,
green,
magenta,
bold,
} from "../colors.ts";
type ConsoleContext = Set<unknown>;
type InspectOptions = Partial<{
@ -10,6 +20,8 @@ type InspectOptions = Partial<{
indentLevel: number;
}>;
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;
@ -84,7 +96,7 @@ function createIterableString<T>(
config: IterablePrintConfig<T>
): string {
if (level >= maxLevel) {
return `[${config.typeName}]`;
return cyan(`[${config.typeName}]`);
}
ctx.add(value);
@ -115,22 +127,22 @@ function createIterableString<T>(
let iContent: string;
if (config.group && entries.length > MIN_GROUP_LENGTH) {
const groups = groupEntries(entries, level, value);
const initIndentation = `\n${" ".repeat(level + 1)}`;
const entryIndetation = `,\n${" ".repeat(level + 1)}`;
const closingIndentation = `\n${" ".repeat(level)}`;
const initIndentation = `\n${DEFAULT_INDENT.repeat(level + 1)}`;
const entryIndentation = `,\n${DEFAULT_INDENT.repeat(level + 1)}`;
const closingIndentation = `\n${DEFAULT_INDENT.repeat(level)}`;
iContent = `${initIndentation}${groups.join(
entryIndetation
entryIndentation
)}${closingIndentation}`;
} else {
iContent = entries.length === 0 ? "" : ` ${entries.join(", ")} `;
if (iContent.length > LINE_BREAKING_LENGTH) {
const initIndentation = `\n${" ".repeat(level + 1)}`;
const entryIndetation = `,\n${" ".repeat(level + 1)}`;
if (stripColor(iContent).length > LINE_BREAKING_LENGTH) {
const initIndentation = `\n${DEFAULT_INDENT.repeat(level + 1)}`;
const entryIndentation = `,\n${DEFAULT_INDENT.repeat(level + 1)}`;
const closingIndentation = `\n`;
iContent = `${initIndentation}${entries.join(
entryIndetation
entryIndentation
)}${closingIndentation}`;
}
}
@ -155,10 +167,12 @@ function groupEntries<T>(
const separatorSpace = 2; // Add 1 for the space and 1 for the separator.
const dataLen = new Array(entriesLength);
// Calculate the total length of all output entries and the individual max
// entries length of all output entries. In future colors should be taken
// here into the account
// entries length of all output entries.
// IN PROGRESS: Colors are being taken into account.
for (let i = 0; i < entriesLength; i++) {
const len = entries[i].length;
// Taking colors into account: removing the ANSI color
// codes from the string before measuring its length
const len = stripColor(entries[i]).length;
dataLen[i] = len;
totalLength += len + separatorSpace;
if (maxLength < len) maxLength = len;
@ -257,29 +271,33 @@ function stringify(
switch (typeof value) {
case "string":
return value;
case "number":
case "number": // Numbers are yellow
// Special handling of -0
return Object.is(value, -0) ? "-0" : `${value}`;
case "boolean":
case "undefined":
case "symbol":
return String(value);
case "bigint":
return `${value}n`;
case "function":
return createFunctionString(value as Function, ctx);
case "object":
return yellow(Object.is(value, -0) ? "-0" : `${value}`);
case "boolean": // booleans are yellow
return yellow(String(value));
case "undefined": // undefined is dim
return dim(String(value));
case "symbol": // Symbols are green
return green(String(value));
case "bigint": // Bigints are yellow
return yellow(`${value}n`);
case "function": // Function string is cyan
return cyan(createFunctionString(value as Function, ctx));
case "object": // null is bold
if (value === null) {
return "null";
return bold("null");
}
if (ctx.has(value)) {
return "[Circular]";
// Circular string is cyan
return cyan("[Circular]");
}
return createObjectString(value, ctx, level, maxLevel);
default:
return "[Not Implemented]";
// Not implemented is red
return red("[Not Implemented]");
}
}
@ -296,7 +314,7 @@ function stringifyWithQuotes(
value.length > STR_ABBREVIATE_SIZE
? value.slice(0, STR_ABBREVIATE_SIZE) + "..."
: value;
return JSON.stringify(trunc);
return green(`"${trunc}"`); // Quoted strings are green
default:
return stringify(value, ctx, level, maxLevel);
}
@ -323,7 +341,7 @@ function createArrayString(
}
const emptyItems = i - index;
const ending = emptyItems > 1 ? "s" : "";
return `<${emptyItems} empty item${ending}>`;
return dim(`<${emptyItems} empty item${ending}>`);
} else {
return stringifyWithQuotes(val, ctx, level + 1, maxLevel);
}
@ -399,33 +417,34 @@ function createMapString(
}
function createWeakSetString(): string {
return "WeakSet { [items unknown] }"; // as seen in Node
return `WeakSet { ${cyan("[items unknown]")} }`; // as seen in Node, with cyan color
}
function createWeakMapString(): string {
return "WeakMap { [items unknown] }"; // as seen in Node
return `WeakMap { ${cyan("[items unknown]")} }`; // as seen in Node, with cyan color
}
function createDateString(value: Date): string {
return isInvalidDate(value) ? "Invalid Date" : value.toISOString(); // without quotes, ISO format
// without quotes, ISO format, in magenta like before
return magenta(isInvalidDate(value) ? "Invalid Date" : value.toISOString());
}
function createRegExpString(value: RegExp): string {
return value.toString();
return red(value.toString()); // RegExps are red
}
/* eslint-disable @typescript-eslint/ban-types */
function createStringWrapperString(value: String): string {
return `[String: "${value.toString()}"]`;
return cyan(`[String: "${value.toString()}"]`); // wrappers are in cyan
}
function createBooleanWrapperString(value: Boolean): string {
return `[Boolean: ${value.toString()}]`;
return cyan(`[Boolean: ${value.toString()}]`); // wrappers are in cyan
}
function createNumberWrapperString(value: Number): string {
return `[Number: ${value.toString()}]`;
return cyan(`[Number: ${value.toString()}]`); // wrappers are in cyan
}
/* eslint-enable @typescript-eslint/ban-types */
@ -439,10 +458,11 @@ function createPromiseString(
const [state, result] = Deno.core.getPromiseDetails(value);
if (state === PromiseState.Pending) {
return "Promise { <pending> }";
return `Promise { ${cyan("<pending>")} }`;
}
const prefix = state === PromiseState.Fulfilled ? "" : "<rejected> ";
const prefix =
state === PromiseState.Fulfilled ? "" : `${red("<rejected>")} `;
const str = `${prefix}${stringifyWithQuotes(
result,
@ -452,7 +472,7 @@ function createPromiseString(
)}`;
if (str.length + PROMISE_STRING_BASE_LENGTH > LINE_BREAKING_LENGTH) {
return `Promise {\n${" ".repeat(level + 1)}${str}\n}`;
return `Promise {\n${DEFAULT_INDENT.repeat(level + 1)}${str}\n}`;
}
return `Promise { ${str} }`;
@ -467,7 +487,7 @@ function createRawObjectString(
maxLevel: number
): string {
if (level >= maxLevel) {
return "[Object]";
return cyan("[Object]"); // wrappers are in cyan
}
ctx.add(value);
@ -503,16 +523,17 @@ function createRawObjectString(
)}`
);
}
const totalLength = entries.length + level + entries.join("").length;
// Making sure color codes are ignored when calculating the total length
const totalLength =
entries.length + level + stripColor(entries.join("")).length;
ctx.delete(value);
if (entries.length === 0) {
baseString = "{}";
} else if (totalLength > LINE_BREAKING_LENGTH) {
const entryIndent = " ".repeat(level + 1);
const closingIndent = " ".repeat(level);
const entryIndent = DEFAULT_INDENT.repeat(level + 1);
const closingIndent = DEFAULT_INDENT.repeat(level);
baseString = `{\n${entryIndent}${entries.join(
`,\n${entryIndent}`
)}\n${closingIndent}}`;
@ -668,7 +689,7 @@ export function stringifyArgs(
}
if (indentLevel > 0) {
const groupIndent = " ".repeat(indentLevel);
const groupIndent = DEFAULT_INDENT.repeat(indentLevel);
if (str.indexOf("\n") !== -1) {
str = str.replace(/\n/g, `\n${groupIndent}`);
}
@ -805,7 +826,7 @@ export class Console {
const isSet = data instanceof Set;
const isMap = data instanceof Map;
const valuesKey = "Values";
const indexKey = isSet || isMap ? "(iteration index)" : "(index)";
const indexKey = isSet || isMap ? "(iter idx)" : "(idx)";
if (data instanceof Set) {
resultData = [...data];

View file

@ -2,6 +2,7 @@
// Forked from Node's lib/internal/cli_table.js
import { hasOwnProperty } from "./util.ts";
import { stripColor } from "../colors.ts";
const tableChars = {
middleMiddle: "─",
@ -19,12 +20,6 @@ const tableChars = {
middle: " │ ",
};
const colorRegExp = /\u001b\[\d\d?m/g;
function removeColors(str: string): string {
return str.replace(colorRegExp, "");
}
function isFullWidthCodePoint(code: number): boolean {
// Code points are partially derived from:
// http://www.unicode.org/Public/UNIDATA/EastAsianWidth.txt
@ -65,7 +60,7 @@ function isFullWidthCodePoint(code: number): boolean {
}
function getStringWidth(str: string): number {
str = removeColors(str).normalize("NFC");
str = stripColor(str).normalize("NFC");
let width = 0;
for (const ch of str) {

View file

@ -80,7 +80,7 @@ fn no_color() {
.unwrap();
assert!(output.status.success());
let stdout_str = std::str::from_utf8(&output.stdout).unwrap().trim();
assert_eq!("noColor false", stdout_str);
assert_eq!("noColor false", util::strip_ansi_codes(stdout_str));
}
// TODO re-enable. This hangs on macOS
@ -403,6 +403,7 @@ fn js_unit_tests() {
.arg("cli/js/tests/unit_test_runner.ts")
.arg("--master")
.arg("--verbose")
.env("NO_COLOR", "1")
.spawn()
.expect("failed to spawn script");
let status = deno.wait().expect("failed to wait for the child process");
@ -672,7 +673,7 @@ fn repl_test_console_log() {
true,
"repl",
Some(vec!["console.log('hello')", "'world'"]),
None,
Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]),
false,
);
assert!(out.ends_with("hello\nundefined\nworld\n"));
@ -685,7 +686,7 @@ fn repl_test_eof() {
true,
"repl",
Some(vec!["1 + 2"]),
None,
Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]),
false,
);
assert!(out.ends_with("3\n"));
@ -714,7 +715,7 @@ fn repl_test_function() {
true,
"repl",
Some(vec!["Deno.writeFileSync"]),
None,
Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]),
false,
);
assert!(out.ends_with("[Function: writeFileSync]\n"));
@ -727,7 +728,7 @@ fn repl_test_multiline() {
true,
"repl",
Some(vec!["(\n1 + 2\n)"]),
None,
Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]),
false,
);
assert!(out.ends_with("3\n"));
@ -792,7 +793,7 @@ fn repl_test_variable() {
true,
"repl",
Some(vec!["var a = 123;", "a"]),
None,
Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]),
false,
);
assert!(out.ends_with("undefined\n123\n"));
@ -805,7 +806,7 @@ fn repl_test_lexical_scoped_variable() {
true,
"repl",
Some(vec!["let a = 123;", "a"]),
None,
Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]),
false,
);
assert!(out.ends_with("undefined\n123\n"));
@ -822,7 +823,10 @@ fn repl_test_missing_deno_dir() {
true,
"repl",
Some(vec!["1"]),
Some(vec![("DENO_DIR".to_owned(), DENO_DIR.to_owned())]),
Some(vec![
("DENO_DIR".to_owned(), DENO_DIR.to_owned()),
("NO_COLOR".to_owned(), "1".to_owned()),
]),
false,
);
assert!(read_dir(&test_deno_dir).is_ok());
@ -837,7 +841,7 @@ fn repl_test_save_last_eval() {
true,
"repl",
Some(vec!["1", "_"]),
None,
Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]),
false,
);
assert!(out.ends_with("1\n1\n"));
@ -850,7 +854,7 @@ fn repl_test_save_last_thrown() {
true,
"repl",
Some(vec!["throw 1", "_error"]),
None,
Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]),
false,
);
assert!(out.ends_with("1\n"));
@ -863,7 +867,7 @@ fn repl_test_assign_underscore() {
true,
"repl",
Some(vec!["_ = 1", "2", "_"]),
None,
Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]),
false,
);
assert!(
@ -878,7 +882,7 @@ fn repl_test_assign_underscore_error() {
true,
"repl",
Some(vec!["_error = 1", "throw 2", "_error"]),
None,
Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]),
false,
);
assert!(
@ -1535,6 +1539,7 @@ itest!(lib_runtime_api {
itest!(seed_random {
args: "run --seed=100 seed_random.js",
output: "seed_random.js.out",
});
@ -2370,6 +2375,7 @@ async fn inspector_does_not_hang() {
// Warning: each inspector test should be on its own port to avoid
// conflicting with another inspector test.
.arg("--inspect-brk=127.0.0.1:9232")
.env("NO_COLOR", "1")
.arg(script)
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())

View file

@ -9,15 +9,15 @@
0.5950178237266042
0.22440633214343908
Uint8Array(32) [
116, 125, 169, 69, 106, 231, 99,
39, 148, 188, 211, 41, 46, 211,
236, 141, 55, 10, 214, 63, 118,
116, 125, 169, 69, 106, 231, 99,
39, 148, 188, 211, 41, 46, 211,
236, 141, 55, 10, 214, 63, 118,
230, 218, 249, 125, 161, 137, 110,
214, 36, 159, 154
214, 36, 159, 154
]
Uint8Array(32) [
248, 21, 21, 9, 41, 0, 71, 124,
244, 209, 252, 151, 7, 10, 168, 250,
84, 170, 243, 140, 53, 47, 99, 212,
18, 146, 68, 48, 66, 222, 67, 112
248, 21, 21, 9, 41, 0, 71, 124,
244, 209, 252, 151, 7, 10, 168, 250,
84, 170, 243, 140, 53, 47, 99, 212,
18, 146, 68, 48, 66, 222, 67, 112
]

View file

@ -205,3 +205,16 @@ export function bgRgb24(str: string, color: Rgb): string {
)
);
}
// https://github.com/chalk/ansi-regex/blob/2b56fb0c7a07108e5b54241e8faec160d393aedb/index.js
const ANSI_PATTERN = new RegExp(
[
"[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)",
"(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))",
].join("|"),
"g"
);
export function stripColor(string: string): string {
return string.replace(ANSI_PATTERN, "");
}

View file

@ -1,6 +1,7 @@
import { sprintf } from "./sprintf.ts";
import { assertEquals } from "../testing/asserts.ts";
import { cyan, yellow } from "./colors.ts";
const S = sprintf;
@ -600,12 +601,12 @@ Deno.test("testWeirdos", function (): void {
Deno.test("formatV", function (): void {
const a = { a: { a: { a: { a: { a: { a: { a: {} } } } } } } };
assertEquals(S("%v", a), "[object Object]");
assertEquals(S("%#v", a), "{ a: { a: { a: { a: [Object] } } } }");
assertEquals(S("%#v", a), `{ a: { a: { a: { a: ${cyan("[Object]")} } } } }`);
assertEquals(
S("%#.8v", a),
"{ a: { a: { a: { a: { a: { a: { a: {} } } } } } } }"
);
assertEquals(S("%#.1v", a), "{ a: [Object] }");
assertEquals(S("%#.1v", a), `{ a: ${cyan("[Object]")} }`);
});
Deno.test("formatJ", function (): void {
@ -618,7 +619,9 @@ Deno.test("flagLessThan", function (): void {
const aArray = [a, a, a];
assertEquals(
S("%<#.1v", aArray),
"[ { a: [Object] }, { a: [Object] }, { a: [Object] } ]"
`[ { a: ${cyan("[Object]")} }, { a: ${cyan("[Object]")} }, { a: ${cyan(
"[Object]"
)} } ]`
);
const fArray = [1.2345, 0.98765, 123456789.5678];
assertEquals(S("%<.2f", fArray), "[ 1.23, 0.99, 123456789.57 ]");
@ -638,7 +641,10 @@ Deno.test("testErrors", function (): void {
assertEquals(S("%*.2f", "a", 1.1), "%!(BAD WIDTH 'a')");
assertEquals(S("%.*f", "a", 1.1), "%!(BAD PREC 'a')");
assertEquals(S("%.[2]*f", 1.23, "p"), "%!(BAD PREC 'p')%!(EXTRA '1.23')");
assertEquals(
S("%.[2]*f", 1.23, "p"),
`%!(BAD PREC 'p')%!(EXTRA '${yellow("1.23")}')`
);
assertEquals(S("%.[2]*[1]f Yippie!", 1.23, "p"), "%!(BAD PREC 'p') Yippie!");
assertEquals(S("%[1]*.2f", "a", "p"), "%!(BAD WIDTH 'a')");
@ -649,7 +655,10 @@ Deno.test("testErrors", function (): void {
// remains to be determined how to handle bad indices ...
// (realistically) the entire error handling is still up for grabs.
assertEquals(S("%[hallo]s %d %d %d", 1, 2, 3, 4), "%!(BAD INDEX) 2 3 4");
assertEquals(S("%[5]s", 1, 2, 3, 4), "%!(BAD INDEX)%!(EXTRA '2' '3' '4')");
assertEquals(
S("%[5]s", 1, 2, 3, 4),
`%!(BAD INDEX)%!(EXTRA '${yellow("2")}' '${yellow("3")}' '${yellow("4")}')`
);
assertEquals(S("%[5]f"), "%!(BAD INDEX)");
assertEquals(S("%.[5]f"), "%!(BAD INDEX)");
assertEquals(S("%.[5]*f"), "%!(BAD INDEX)");

View file

@ -15,7 +15,7 @@ import {
unimplemented,
unreachable,
} from "./asserts.ts";
import { red, green, gray, bold } from "../fmt/colors.ts";
import { red, green, gray, bold, yellow } from "../fmt/colors.ts";
const { test } = Deno;
test("testingEqual", function (): void {
@ -282,8 +282,8 @@ test({
[
"Values are not equal:",
...createHeader(),
removed(`- 1`),
added(`+ 2`),
removed(`- ${yellow("1")}`),
added(`+ ${yellow("2")}`),
"",
].join("\n")
);
@ -299,7 +299,7 @@ test({
[
"Values are not equal:",
...createHeader(),
removed(`- 1`),
removed(`- ${yellow("1")}`),
added(`+ "1"`),
].join("\n")
);
@ -315,8 +315,8 @@ test({
[
"Values are not equal:",
...createHeader(),
removed(`- [ 1, "2", 3 ]`),
added(`+ [ "1", "2", 3 ]`),
removed(`- [ ${yellow("1")}, ${green('"2"')}, ${yellow("3")} ]`),
added(`+ [ ${green('"1"')}, ${green('"2"')}, ${yellow("3")} ]`),
"",
].join("\n")
);
@ -332,8 +332,12 @@ test({
[
"Values are not equal:",
...createHeader(),
removed(`- { a: 1, b: "2", c: 3 }`),
added(`+ { a: 1, b: 2, c: [ 3 ] }`),
removed(
`- { a: ${yellow("1")}, b: ${green('"2"')}, c: ${yellow("3")} }`
),
added(
`+ { a: ${yellow("1")}, b: ${yellow("2")}, c: [ ${yellow("3")} ] }`
),
"",
].join("\n")
);
@ -366,8 +370,8 @@ test({
[
"Values are not strictly equal:",
...createHeader(),
removed("- { a: 1, b: 2 }"),
added("+ { a: 1, c: [ 3 ] }"),
removed(`- { a: ${yellow("1")}, b: ${yellow("2")} }`),
added(`+ { a: ${yellow("1")}, c: [ ${yellow("3")} ] }`),
"",
].join("\n")
);
@ -382,7 +386,7 @@ test({
AssertionError,
[
"Values have the same structure but are not reference-equal:\n",
red(" { a: 1, b: 2 }"),
red(` { a: ${yellow("1")}, b: ${yellow("2")} }`),
].join("\n")
);
},