fix(console): inspect for {Set,Map}Iterator and Weak{Set,Map} (#21554)

This commit is contained in:
Kenta Moriuchi 2023-12-19 15:05:49 +09:00 committed by GitHub
parent 476f5d3deb
commit 68241234fa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 204 additions and 200 deletions

View file

@ -268,6 +268,14 @@ Deno.test(function consoleTestStringifyCircular() {
"2018-12-10T02:26:59.002Z",
);
assertEquals(stringify(new Set([1, 2, 3])), "Set(3) { 1, 2, 3 }");
assertEquals(
stringify(new Set([1, 2, 3]).values()),
"[Set Iterator] { 1, 2, 3 }",
);
assertEquals(
stringify(new Set([1, 2, 3]).entries()),
"[Set Entries] { [ 1, 1 ], [ 2, 2 ], [ 3, 3 ] }",
);
assertEquals(
stringify(
new Map([
@ -277,6 +285,14 @@ Deno.test(function consoleTestStringifyCircular() {
),
`Map(2) { 1 => "one", 2 => "two" }`,
);
assertEquals(
stringify(new Map([[1, "one"], [2, "two"]]).values()),
`[Map Iterator] { "one", "two" }`,
);
assertEquals(
stringify(new Map([[1, "one"], [2, "two"]]).entries()),
`[Map Entries] { [ 1, "one" ], [ 2, "two" ] }`,
);
assertEquals(stringify(new WeakSet()), "WeakSet { <items unknown> }");
assertEquals(stringify(new WeakMap()), "WeakMap { <items unknown> }");
assertEquals(stringify(Symbol(1)), `Symbol("1")`);
@ -378,13 +394,16 @@ Deno.test(function consoleTestStringifyFunctionWithPrototypeRemoved() {
assertEquals(stringify(f), "[Function (null prototype): f]");
const af = async function af() {};
Reflect.setPrototypeOf(af, null);
assertEquals(stringify(af), "[Function (null prototype): af]");
assertEquals(stringify(af), "[AsyncFunction (null prototype): af]");
const gf = function* gf() {};
Reflect.setPrototypeOf(gf, null);
assertEquals(stringify(gf), "[Function (null prototype): gf]");
assertEquals(stringify(gf), "[GeneratorFunction (null prototype): gf]");
const agf = async function* agf() {};
Reflect.setPrototypeOf(agf, null);
assertEquals(stringify(agf), "[Function (null prototype): agf]");
assertEquals(
stringify(agf),
"[AsyncGeneratorFunction (null prototype): agf]",
);
});
Deno.test(function consoleTestStringifyFunctionWithProperties() {
@ -1002,6 +1021,26 @@ Deno.test(function consoleTestStringifyIterableWhenGrouped() {
);
});
Deno.test(function consoleTestIteratorValueAreNotConsumed() {
const setIterator = new Set([1, 2, 3]).values();
assertEquals(
stringify(setIterator),
"[Set Iterator] { 1, 2, 3 }",
);
assertEquals([...setIterator], [1, 2, 3]);
});
Deno.test(function consoleTestWeakSetAndWeakMapWithShowHidden() {
assertEquals(
stripColor(Deno.inspect(new WeakSet([{}]), { showHidden: true })),
"WeakSet { {} }",
);
assertEquals(
stripColor(Deno.inspect(new WeakMap([[{}, "foo"]]), { showHidden: true })),
'WeakMap { {} => "foo" }',
);
});
Deno.test(async function consoleTestStringifyPromises() {
const pendingPromise = new Promise((_res, _rej) => {});
assertEquals(stringify(pendingPromise), "Promise { <pending> }");
@ -2279,7 +2318,7 @@ Deno.test(function inspectWithPrototypePollution() {
Deno.test(function inspectPromiseLike() {
assertEquals(
Deno.inspect(Object.create(Promise.prototype)),
"Promise { <unknown> }",
"Promise {}",
);
});

View file

@ -10,7 +10,6 @@ const {
ArrayBufferPrototypeGetByteLength,
ArrayIsArray,
ArrayPrototypeFill,
ArrayPrototypeConcat,
ArrayPrototypeFilter,
ArrayPrototypeFind,
ArrayPrototypeForEach,
@ -75,7 +74,6 @@ const {
ObjectPrototype,
ObjectPrototypeIsPrototypeOf,
ObjectPrototypePropertyIsEnumerable,
ObjectPrototypeToString,
ObjectSetPrototypeOf,
ObjectValues,
Proxy,
@ -135,22 +133,7 @@ const {
WeakMapPrototypeHas,
WeakSetPrototypeHas,
} = primordials;
// supposed to be in node/internal_binding/util.ts
export function previewEntries(iter, isKeyValue) {
if (isKeyValue) {
// deno-lint-ignore prefer-primordials
const arr = [...iter];
if (ArrayIsArray(arr[0]) && arr[0].length === 2) {
// deno-lint-ignore prefer-primordials
return [ArrayPrototypeConcat([], ...arr), true];
}
return [arr, false];
} else {
// deno-lint-ignore prefer-primordials
return [...iter];
}
}
const ops = core.ops;
let noColor = () => false;
@ -284,19 +267,15 @@ function isObjectLike(value) {
return value !== null && typeof value === "object";
}
export function isAnyArrayBuffer(value) {
return isArrayBuffer(value) || isSharedArrayBuffer(value);
function isAnyArrayBuffer(value) {
return ops.op_is_any_arraybuffer(value);
}
export function isArgumentsObject(value) {
return (
isObjectLike(value) &&
value[SymbolToStringTag] === undefined &&
ObjectPrototypeToString(value) === "[object Arguments]"
);
function isArgumentsObject(value) {
return ops.op_is_arguments_object(value);
}
export function isArrayBuffer(value) {
function isArrayBuffer(value) {
try {
ArrayBufferPrototypeGetByteLength(value);
return true;
@ -305,21 +284,11 @@ export function isArrayBuffer(value) {
}
}
export function isAsyncFunction(value) {
return (
typeof value === "function" &&
(value[SymbolToStringTag] === "AsyncFunction")
);
function isAsyncFunction(value) {
return ops.op_is_async_function(value);
}
export function isAsyncGeneratorFunction(value) {
return (
typeof value === "function" &&
(value[SymbolToStringTag] === "AsyncGeneratorFunction")
);
}
export function isBooleanObject(value) {
function isBooleanObject(value) {
if (!isObjectLike(value)) {
return false;
}
@ -332,7 +301,7 @@ export function isBooleanObject(value) {
}
}
export function isBoxedPrimitive(
function isBoxedPrimitive(
value,
) {
return (
@ -344,27 +313,22 @@ export function isBoxedPrimitive(
);
}
export function isDataView(value) {
function isDataView(value) {
return (
ArrayBufferIsView(value) &&
TypedArrayPrototypeGetSymbolToStringTag(value) === undefined
);
}
export function isTypedArray(value) {
function isTypedArray(value) {
return TypedArrayPrototypeGetSymbolToStringTag(value) !== undefined;
}
export function isGeneratorFunction(
value,
) {
return (
typeof value === "function" &&
value[SymbolToStringTag] === "GeneratorFunction"
);
function isGeneratorFunction(value) {
return ops.op_is_generator_function(value);
}
export function isMap(value) {
function isMap(value) {
try {
MapPrototypeGetSize(value);
return true;
@ -373,33 +337,19 @@ export function isMap(value) {
}
}
export function isMapIterator(
value,
) {
return (
isObjectLike(value) &&
value[SymbolToStringTag] === "Map Iterator"
);
function isMapIterator(value) {
return ops.op_is_map_iterator(value);
}
export function isModuleNamespaceObject(
value,
) {
return (
isObjectLike(value) &&
value[SymbolToStringTag] === "Module"
);
function isModuleNamespaceObject(value) {
return ops.op_is_module_namespace_object(value);
}
export function isNativeError(value) {
return (
isObjectLike(value) &&
value[SymbolToStringTag] === undefined &&
ObjectPrototypeToString(value) === "[object Error]"
);
function isNativeError(value) {
return ops.op_is_native_error(value);
}
export function isNumberObject(value) {
function isNumberObject(value) {
if (!isObjectLike(value)) {
return false;
}
@ -412,7 +362,7 @@ export function isNumberObject(value) {
}
}
export function isBigIntObject(value) {
function isBigIntObject(value) {
if (!isObjectLike(value)) {
return false;
}
@ -425,21 +375,15 @@ export function isBigIntObject(value) {
}
}
export function isPromise(value) {
return (
isObjectLike(value) &&
value[SymbolToStringTag] === "Promise"
);
}
export function isRegExp(value) {
return (
isObjectLike(value) &&
value[SymbolToStringTag] === undefined &&
ObjectPrototypeToString(value) === "[object RegExp]"
);
function isPromise(value) {
return ops.op_is_promise(value);
}
export function isSet(value) {
function isRegExp(value) {
return ops.op_is_reg_exp(value);
}
function isSet(value) {
try {
SetPrototypeGetSize(value);
return true;
@ -448,27 +392,11 @@ export function isSet(value) {
}
}
export function isSetIterator(
value,
) {
return (
isObjectLike(value) &&
value[SymbolToStringTag] === "Set Iterator"
);
function isSetIterator(value) {
return ops.op_is_set_iterator(value);
}
export function isSharedArrayBuffer(
value,
) {
try {
getSharedArrayBufferByteLength(value);
return true;
} catch {
return false;
}
}
export function isStringObject(value) {
function isStringObject(value) {
if (!isObjectLike(value)) {
return false;
}
@ -481,7 +409,7 @@ export function isStringObject(value) {
}
}
export function isSymbolObject(value) {
function isSymbolObject(value) {
if (!isObjectLike(value)) {
return false;
}
@ -494,7 +422,7 @@ export function isSymbolObject(value) {
}
}
export function isWeakMap(
function isWeakMap(
value,
) {
try {
@ -505,7 +433,7 @@ export function isWeakMap(
}
}
export function isWeakSet(
function isWeakSet(
value,
) {
try {
@ -793,9 +721,6 @@ function getFunctionBase(value, constructor, tag) {
if (isAsyncFunction(value)) {
type = `Async${type}`;
}
if (isAsyncGeneratorFunction(value)) {
type = `AsyncGenerator${type}`;
}
let base = `[${type}`;
if (constructor === null) {
base += " (null prototype)";
@ -866,7 +791,7 @@ function formatRaw(ctx, value, recurseTimes, typedArray, proxyDetails) {
const prefix = (constructor !== "Array" || tag !== "")
? getPrefix(constructor, tag, "Array", `(${value.length})`)
: "";
keys = core.ops.op_get_non_index_property_names(value, filter);
keys = ops.op_get_non_index_property_names(value, filter);
braces = [`${prefix}[`, "]"];
if (
value.length === 0 && keys.length === 0 && protoProps === undefined
@ -1465,7 +1390,7 @@ function formatTypedArray(
) {
const maxLength = MathMin(MathMax(0, ctx.maxArrayLength), length);
const remaining = value.length - maxLength;
const output = new Array(maxLength);
const output = [];
const elementFormatter = value.length > 0 && typeof value[0] === "number"
? formatNumber
: formatBigInt;
@ -1508,7 +1433,7 @@ function getIteratorBraces(type, tag) {
const iteratorRegExp = new SafeRegExp(" Iterator] {$");
function formatIterator(braces, ctx, value, recurseTimes) {
const { 0: entries, 1: isKeyValue } = previewEntries(value, true);
const { 0: entries, 1: isKeyValue } = ops.op_preview_entries(value, true);
if (isKeyValue) {
// Mark entry iterators as such.
braces[0] = StringPrototypeReplace(
@ -1625,7 +1550,7 @@ function inspectError(value, ctx) {
const hexSliceLookupTable = function () {
const alphabet = "0123456789abcdef";
const table = new Array(256);
const table = [];
for (let i = 0; i < 16; ++i) {
const i16 = i * 16;
for (let j = 0; j < 16; ++j) {
@ -1689,14 +1614,7 @@ const PromiseState = {
function formatPromise(ctx, value, recurseTimes) {
let output;
let opResult;
// This op will fail for non-promises, but we get here for some promise-likes.
try {
opResult = core.getPromiseDetails(value);
} catch {
return [ctx.stylize("<unknown>", "special")];
}
const { 0: state, 1: result } = opResult;
const { 0: state, 1: result } = core.getPromiseDetails(value);
if (state === PromiseState.Pending) {
output = [ctx.stylize("<pending>", "special")];
} else {
@ -1717,12 +1635,12 @@ function formatWeakCollection(ctx) {
}
function formatWeakSet(ctx, value, recurseTimes) {
const entries = previewEntries(value);
const entries = ops.op_preview_entries(value, false);
return formatSetIterInner(ctx, recurseTimes, entries, kWeak);
}
function formatWeakMap(ctx, value, recurseTimes) {
const entries = previewEntries(value);
const entries = ops.op_preview_entries(value, false);
return formatMapIterInner(ctx, recurseTimes, entries, kWeak);
}
@ -1843,7 +1761,7 @@ function formatNamespaceObject(
value,
recurseTimes,
) {
const output = new Array(keys.length);
const output = [];
for (let i = 0; i < keys.length; i++) {
try {
output[i] = formatProperty(
@ -2062,7 +1980,7 @@ function groupArrayElements(ctx, output, value) {
outputLength--;
}
const separatorSpace = 2; // Add 1 for the space and 1 for the separator.
const dataLen = new Array(outputLength);
const dataLen = [];
// Calculate the total length of all output entries and the individual max
// entries length of all output entries. We have to remove colors first,
// otherwise the length would not be calculated properly.
@ -2176,7 +2094,7 @@ function formatMapIterInner(
const len = entries.length / 2;
const remaining = len - maxArrayLength;
const maxLength = MathMin(maxArrayLength, len);
const output = new Array(maxLength);
const output = [];
let i = 0;
ctx.indentationLvl += 2;
if (state === kWeak) {
@ -2227,7 +2145,7 @@ function formatSetIterInner(
) {
const maxArrayLength = MathMax(ctx.maxArrayLength, 0);
const maxLength = MathMin(maxArrayLength, entries.length);
const output = new Array(maxLength);
const output = [];
ctx.indentationLvl += 2;
for (let i = 0; i < maxLength; i++) {
output[i] = formatValue(ctx, entries[i], recurseTimes);

View file

@ -1,8 +1,103 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use deno_core::op2;
use deno_core::v8;
use std::path::PathBuf;
deno_core::extension!(deno_console, esm = ["01_console.js"],);
deno_core::extension!(
deno_console,
ops = [
op_is_any_arraybuffer,
op_is_arguments_object,
op_is_async_function,
op_is_generator_function,
op_is_generator_object,
op_is_map_iterator,
op_is_module_namespace_object,
op_is_native_error,
op_is_promise,
op_is_reg_exp,
op_is_set_iterator,
op_preview_entries,
],
esm = ["01_console.js"],
);
pub fn get_declaration() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("lib.deno_console.d.ts")
}
#[op2(fast)]
fn op_is_any_arraybuffer(value: &v8::Value) -> bool {
value.is_array_buffer() || value.is_shared_array_buffer()
}
#[op2(fast)]
pub fn op_is_arguments_object(value: &v8::Value) -> bool {
value.is_arguments_object()
}
#[op2(fast)]
pub fn op_is_async_function(value: &v8::Value) -> bool {
value.is_async_function()
}
#[op2(fast)]
pub fn op_is_generator_function(value: &v8::Value) -> bool {
value.is_generator_function()
}
#[op2(fast)]
pub fn op_is_generator_object(value: &v8::Value) -> bool {
value.is_generator_object()
}
#[op2(fast)]
pub fn op_is_map_iterator(value: &v8::Value) -> bool {
value.is_map_iterator()
}
#[op2(fast)]
pub fn op_is_module_namespace_object(value: &v8::Value) -> bool {
value.is_module_namespace_object()
}
#[op2(fast)]
pub fn op_is_native_error(value: &v8::Value) -> bool {
value.is_native_error()
}
#[op2(fast)]
pub fn op_is_promise(value: &v8::Value) -> bool {
value.is_promise()
}
#[op2(fast)]
pub fn op_is_reg_exp(value: &v8::Value) -> bool {
value.is_reg_exp()
}
#[op2(fast)]
pub fn op_is_set_iterator(value: &v8::Value) -> bool {
value.is_set_iterator()
}
#[op2]
pub fn op_preview_entries<'s>(
scope: &mut v8::HandleScope<'s>,
object: &v8::Object,
slow_path: bool,
) -> v8::Local<'s, v8::Value> {
let (entries, is_key_value) = object.preview_entries(scope);
match entries {
None => v8::undefined(scope).into(),
Some(entries) => {
if !slow_path {
return entries.into();
}
let ret: [v8::Local<v8::Value>; 2] =
[entries.into(), v8::Boolean::new(scope, is_key_value).into()];
v8::Array::new_with_elements(scope, &ret).into()
}
}
}

View file

@ -150,11 +150,6 @@ fn op_node_build_os() -> String {
env!("TARGET").split('-').nth(2).unwrap().to_string()
}
#[op2(fast)]
fn op_is_any_arraybuffer(value: &v8::Value) -> bool {
value.is_array_buffer() || value.is_shared_array_buffer()
}
#[op2(fast)]
fn op_node_is_promise_rejected(value: v8::Local<v8::Value>) -> bool {
let Ok(promise) = v8::Local::<v8::Promise>::try_from(value) else {
@ -286,7 +281,6 @@ deno_core::extension!(deno_node,
ops::os::op_node_os_username<P>,
ops::os::op_geteuid<P>,
op_node_build_os,
op_is_any_arraybuffer,
op_node_is_promise_rejected,
op_npm_process_state,
ops::require::op_require_init_paths,

View file

@ -4,6 +4,9 @@
// TODO(petamoriken): enable prefer-primordials for node polyfills
// deno-lint-ignore-file prefer-primordials
import { core } from "ext:core/mod.js";
const ops = core.ops;
// Mock trace for now
const trace = () => {};
import {
@ -17,7 +20,6 @@ import {
validateInteger,
validateObject,
} from "ext:deno_node/internal/validators.mjs";
import { previewEntries } from "ext:deno_node/internal_binding/util.ts";
import { Buffer } from "node:buffer";
const { isBuffer } = Buffer;
import {
@ -500,7 +502,7 @@ const consoleMethods = {
let isKeyValue = false;
let i = 0;
if (mapIter) {
const res = previewEntries(tabularData, true);
const res = ops.op_preview_entries(tabularData, true);
tabularData = res[0];
isKeyValue = res[1];
}
@ -535,7 +537,7 @@ const consoleMethods = {
const setIter = isSetIterator(tabularData);
if (setIter) {
tabularData = previewEntries(tabularData);
tabularData = ops.op_preview_entries(tabularData, false);
}
const setlike = setIter || mapIter || isSet(tabularData);

View file

@ -27,9 +27,6 @@
const { core } = globalThis.__bootstrap;
const { ops } = core;
// https://tc39.es/ecma262/#sec-object.prototype.tostring
const _toString = Object.prototype.toString;
// https://tc39.es/ecma262/#sec-bigint.prototype.valueof
const _bigIntValueOf = BigInt.prototype.valueOf;
@ -94,11 +91,7 @@ export function isAnyArrayBuffer(
}
export function isArgumentsObject(value: unknown): value is IArguments {
return (
isObjectLike(value) &&
value[Symbol.toStringTag] === undefined &&
_toString.call(value) === "[object Arguments]"
);
return ops.op_is_arguments_object(value);
}
export function isArrayBuffer(value: unknown): value is ArrayBuffer {
@ -113,11 +106,7 @@ export function isArrayBuffer(value: unknown): value is ArrayBuffer {
export function isAsyncFunction(
value: unknown,
): value is (...args: unknown[]) => Promise<unknown> {
return (
typeof value === "function" &&
// @ts-ignore: function is a kind of object
value[Symbol.toStringTag] === "AsyncFunction"
);
return ops.op_is_async_function(value);
}
// deno-lint-ignore ban-types
@ -166,18 +155,11 @@ export function isDate(value: unknown): value is Date {
export function isGeneratorFunction(
value: unknown,
): value is GeneratorFunction {
return (
typeof value === "function" &&
// @ts-ignore: function is a kind of object
value[Symbol.toStringTag] === "GeneratorFunction"
);
return ops.op_is_generator_function(value);
}
export function isGeneratorObject(value: unknown): value is Generator {
return (
isObjectLike(value) &&
value[Symbol.toStringTag] === "Generator"
);
return ops.op_is_generator_object(value);
}
export function isMap(value: unknown): value is Map<unknown, unknown> {
@ -192,27 +174,17 @@ export function isMap(value: unknown): value is Map<unknown, unknown> {
export function isMapIterator(
value: unknown,
): value is IterableIterator<[unknown, unknown]> {
return (
isObjectLike(value) &&
value[Symbol.toStringTag] === "Map Iterator"
);
return ops.op_is_map_iterator(value);
}
export function isModuleNamespaceObject(
value: unknown,
): value is Record<string | number | symbol, unknown> {
return (
isObjectLike(value) &&
value[Symbol.toStringTag] === "Module"
);
return ops.op_is_module_namespace_object(value);
}
export function isNativeError(value: unknown): value is Error {
return (
isObjectLike(value) &&
value[Symbol.toStringTag] === undefined &&
_toString.call(value) === "[object Error]"
);
return ops.op_is_native_error(value);
}
// deno-lint-ignore ban-types
@ -243,10 +215,7 @@ export function isBigIntObject(value: unknown): value is bigint {
}
export function isPromise(value: unknown): value is Promise<unknown> {
return (
isObjectLike(value) &&
value[Symbol.toStringTag] === "Promise"
);
return ops.op_is_promise(value);
}
export function isProxy(
@ -256,11 +225,7 @@ export function isProxy(
}
export function isRegExp(value: unknown): value is RegExp {
return (
isObjectLike(value) &&
value[Symbol.toStringTag] === undefined &&
_toString.call(value) === "[object RegExp]"
);
return ops.op_is_reg_exp(value);
}
export function isSet(value: unknown): value is Set<unknown> {
@ -275,10 +240,7 @@ export function isSet(value: unknown): value is Set<unknown> {
export function isSetIterator(
value: unknown,
): value is IterableIterator<unknown> {
return (
isObjectLike(value) &&
value[Symbol.toStringTag] === "Set Iterator"
);
return ops.op_is_set_iterator(value);
}
export function isSharedArrayBuffer(

View file

@ -129,5 +129,3 @@ export function getOwnNonIndexProperties(
}
return result;
}
export { previewEntries } from "ext:deno_console/01_console.js";

View file

@ -109,15 +109,11 @@ export function isFunction(value: unknown): boolean {
return typeof value === "function";
}
/** @deprecated Use util.types.RegExp() instead. */
export function isRegExp(value: unknown): boolean {
return types.isRegExp(value);
}
/** @deprecated Use util.types.isRegExp() instead. */
export const isRegExp = types.isRegExp;
/** @deprecated Use util.types.isDate() instead. */
export function isDate(value: unknown): boolean {
return types.isDate(value);
}
export const isDate = types.isDate;
/** @deprecated - use `value === null || (typeof value !== "object" && typeof value !== "function")` instead. */
export function isPrimitive(value: unknown): boolean {