Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v13.5.0 proposal #31010

Merged
merged 70 commits into from
Dec 18, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
f371562
build,win: support building MSI with VS2019
joaocgreis Dec 11, 2019
453be95
util: fix built-in detection
BridgeAR Nov 30, 2019
8dec909
util: inspect (user defined) prototype properties
BridgeAR Nov 30, 2019
0d2172f
lib: update Symbol.toStringTag by SymbolToStringTag primordial
Sebastien-Ahkrin Dec 11, 2019
b53e2a8
doc: improve doc writable streams: 'finish' event
dev-script Dec 10, 2019
a326309
lib: replace Symbol.toPrimitive to SymbolToPrimitive primordials
Sebastien-Ahkrin Dec 11, 2019
e11acc5
repl: fix autocomplete when useGlobal is false
targos Dec 10, 2019
60225c1
build: fix missing x64 arch suffix in binary tar name
legendecas Dec 10, 2019
30d3249
lib: refactor NativeModule
joyeecheung Dec 8, 2019
00cbf5b
build: auto-load ICU data from --with-icu-default-data-dir
sgallagher Dec 6, 2019
4f3eca5
console: unregister temporary error listener
ronag Dec 8, 2019
2430dd8
lib: use strict equality comparison
PW486 Dec 11, 2019
1722280
http,https: increase server headers timeout
timcosta Oct 22, 2019
7a756cb
http: remove unnecessary bind
apapirovski Jun 7, 2019
4e67d38
perf_hooks: remove unnecessary bind
apapirovski Jun 7, 2019
ad5b715
fs: remove unnecessary bind
apapirovski Jun 7, 2019
52aab47
http2: remove unnecessary bind from setImmediate
apapirovski Jun 7, 2019
30e2d28
cluster: remove unnecessary bind
apapirovski Jun 8, 2019
c43461a
src: make debug_options getters public
codebytere Nov 15, 2019
663a6b4
stream: make all streams error in a pipeline
mcollina Dec 9, 2019
a221017
crypto: cast oaepLabel to unsigned char*
codebytere Dec 12, 2019
7e6510b
test: delay loading 'os' in test/common module
Trott Dec 12, 2019
b7a0574
test: make test-os-checked-function work without test harness
Trott Dec 12, 2019
7a25c2c
test: improve assertion error message in test-debug-usage
Trott Dec 12, 2019
cbe29ce
lib: change var to let/const
Dec 12, 2019
df5ae1a
doc: fix description of N-API exception handlers
tniessen Dec 11, 2019
0c18c49
stream: do not chunk strings and Buffer in Readable.from
mcollina Dec 12, 2019
d456aa0
src: unregister Isolate with platform before disposing
addaleax Dec 12, 2019
60485dc
test: add test for validation for wasi.start() argument
Trott Dec 12, 2019
51d1a91
test: add missing test flags
cjihrig Dec 15, 2019
b20ddde
tools: enable Markdown linter's usage information
DerekNonGeneric Nov 2, 2019
10a77d3
build,win: fix goto exit in vcbuild
joaocgreis Dec 13, 2019
b6b917d
test: avoid leftover report file
Flarna Dec 12, 2019
f830a7d
util: refactor inspect code for constistency
BridgeAR Nov 2, 2019
f62a767
util: add Set and map size to inspect output
BridgeAR Nov 2, 2019
eb6443d
doc: clarify expectations for PR commit messages
DerekNonGeneric Dec 12, 2019
69aaab0
test: improve dns lookup coverage
KeeReal Dec 3, 2019
d549dae
repl: remove dead code
BridgeAR Dec 10, 2019
f7eeb8c
repl: simplify repl autocompletion
BridgeAR Dec 10, 2019
8b92223
repl: simplify code
BridgeAR Dec 10, 2019
424c37b
readline: update ansi-regex
BridgeAR Dec 11, 2019
f6f298e
repl,readline: refactor common code
BridgeAR Dec 11, 2019
3906e14
repl,readline: refactor for simplicity
BridgeAR Dec 11, 2019
1a8f828
repl: improve completion
BridgeAR Dec 11, 2019
6a3e79f
repl: add completion preview
BridgeAR Dec 11, 2019
02f3fe4
repl: fix preview bug in case of long lines
BridgeAR Dec 11, 2019
f30b771
test: add multiple repl preview tests
BridgeAR Dec 13, 2019
559284b
doc: add "Be direct." to the style guide
Trott Dec 13, 2019
698e0a2
lib: add TypedArray constructors to primordials
Sebastien-Ahkrin Nov 30, 2019
2b0e2c2
v8: use of TypedArray constructors from primordials
Sebastien-Ahkrin Nov 30, 2019
19f05ca
lib: enforce use of Promise from primordials
targos Dec 13, 2019
92475e9
lib: replace Symbol.asyncIterator by SymbolAsyncIterator
Sebastien-Ahkrin Dec 13, 2019
f51b5bd
lib: replace Symbol.hasInstance by SymbolHasInstance
Sebastien-Ahkrin Dec 13, 2019
88731ad
lib: replace Symbol.species by SymbolSpecies
Sebastien-Ahkrin Dec 13, 2019
954793f
process: fix promise catching
pd4d10 Dec 14, 2019
b6ddbc1
benchmark: use let/const instead of var in buffers
dnlup Dec 13, 2019
13b5ace
doc: explain napi_run_script
tniessen Dec 9, 2019
5b49ded
readline: promote _getCursorPos to public api
Js-Brecht Nov 27, 2019
510edea
process: refs --unhandled-rejections documentation in warning message
aduh95 Nov 20, 2019
d8ce9a0
cli: add --trace-exit cli option
legendecas Nov 17, 2019
5ca29d8
stream: use for...of
trivikr Dec 14, 2019
54d51db
wasi: require CLI flag to require() wasi module
cjihrig Dec 14, 2019
72b4aee
test: improve test coverage in child_process
juanarbol Feb 24, 2019
c2d9552
test: improve WASI start() coverage
cjihrig Dec 15, 2019
5e268b8
test: simplify test-wasi-start-validation.js
cjihrig Dec 15, 2019
3abcb69
doc: add note about fs.close() about undefined behavior
ronag Dec 14, 2019
e10917f
async_hooks: ensure proper handling in runInAsyncScope
apapirovski Dec 14, 2019
956dec8
tls: for...of in _tls_common.js
trivikr Dec 14, 2019
3bc9b09
http: use for...of in http library code
trivikr Dec 14, 2019
2e222e6
2019-12-18, Version 13.5.0 (Current)
MylesBorins Dec 17, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
util: inspect (user defined) prototype properties
This is only active if the `showHidden` option is truthy.

The implementation is a trade-off between accuracy and performance.
This will miss properties such as properties added to built-in data
types.

The goal is mainly to visualize prototype getters and setters such as:

class Foo {
  ownProperty = true
  get bar() {
    return 'Hello world!'
  }
}

const a = new Foo()

The `bar` property is a non-enumerable property on the prototype while
`ownProperty` will be set directly on the created instance.

The output is similar to the one of Chromium when inspecting objects
closer. The output from Firefox is difficult to compare, since it's
always a structured interactive output and was therefore not taken
into account.

PR-URL: #30768
Fixes: #30183
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
  • Loading branch information
BridgeAR authored and MylesBorins committed Dec 17, 2019
commit 8dec909aa75fee206186c57f443403671c8c35b9
7 changes: 6 additions & 1 deletion doc/api/util.md
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,10 @@ stream.write('With ES6');
<!-- YAML
added: v0.3.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/30768
description: User defined prototype properties are inspected in case
`showHidden` is `true`.
- version: v13.0.0
pr-url: https://github.com/nodejs/node/pull/27685
description: Circular references now include a marker to the reference.
Expand Down Expand Up @@ -461,7 +465,8 @@ changes:
* `options` {Object}
* `showHidden` {boolean} If `true`, `object`'s non-enumerable symbols and
properties are included in the formatted result. [`WeakMap`][] and
[`WeakSet`][] entries are also included. **Default:** `false`.
[`WeakSet`][] entries are also included as well as user defined prototype
properties (excluding method properties). **Default:** `false`.
* `depth` {number} Specifies the number of times to recurse while formatting
`object`. This is useful for inspecting large objects. To recurse up to
the maximum call stack size pass `Infinity` or `null`.
Expand Down
116 changes: 100 additions & 16 deletions lib/internal/util/inspect.js
Original file line number Diff line number Diff line change
Expand Up @@ -448,14 +448,20 @@ function getEmptyFormatArray() {
return [];
}

function getConstructorName(obj, ctx, recurseTimes) {
function getConstructorName(obj, ctx, recurseTimes, protoProps) {
let firstProto;
const tmp = obj;
while (obj) {
const descriptor = ObjectGetOwnPropertyDescriptor(obj, 'constructor');
if (descriptor !== undefined &&
typeof descriptor.value === 'function' &&
descriptor.value.name !== '') {
if (protoProps !== undefined &&
!builtInObjects.has(descriptor.value.name)) {
const isProto = firstProto !== undefined;
addPrototypeProperties(
ctx, tmp, obj, recurseTimes, isProto, protoProps);
}
return descriptor.value.name;
}

Expand All @@ -475,7 +481,8 @@ function getConstructorName(obj, ctx, recurseTimes) {
return `${res} <Complex prototype>`;
}

const protoConstr = getConstructorName(firstProto, ctx, recurseTimes + 1);
const protoConstr = getConstructorName(
firstProto, ctx, recurseTimes + 1, protoProps);

if (protoConstr === null) {
return `${res} <${inspect(firstProto, {
Expand All @@ -488,6 +495,68 @@ function getConstructorName(obj, ctx, recurseTimes) {
return `${res} <${protoConstr}>`;
}

// This function has the side effect of adding prototype properties to the
// `output` argument (which is an array). This is intended to highlight user
// defined prototype properties.
function addPrototypeProperties(ctx, main, obj, recurseTimes, isProto, output) {
let depth = 0;
let keys;
let keySet;
do {
if (!isProto) {
obj = ObjectGetPrototypeOf(obj);
// Stop as soon as a null prototype is encountered.
if (obj === null) {
return;
}
// Stop as soon as a built-in object type is detected.
const descriptor = ObjectGetOwnPropertyDescriptor(obj, 'constructor');
if (descriptor !== undefined &&
typeof descriptor.value === 'function' &&
builtInObjects.has(descriptor.value.name)) {
return;
}
} else {
isProto = false;
}

if (depth === 0) {
keySet = new Set();
} else {
keys.forEach((key) => keySet.add(key));
}
// Get all own property names and symbols.
keys = ObjectGetOwnPropertyNames(obj);
const symbols = ObjectGetOwnPropertySymbols(obj);
if (symbols.length !== 0) {
keys.push(...symbols);
}
for (const key of keys) {
// Ignore the `constructor` property and keys that exist on layers above.
if (key === 'constructor' ||
ObjectPrototypeHasOwnProperty(main, key) ||
(depth !== 0 && keySet.has(key))) {
continue;
}
const desc = ObjectGetOwnPropertyDescriptor(obj, key);
if (typeof desc.value === 'function') {
continue;
}
const value = formatProperty(
ctx, obj, recurseTimes, key, kObjectType, desc);
if (ctx.colors) {
// Faint!
output.push(`\u001b[2m${value}\u001b[22m`);
} else {
output.push(value);
}
}
// Limit the inspection to up to three prototype layers. Using `recurseTimes`
// is not a good choice here, because it's as if the properties are declared
// on the current object from the users perspective.
} while (++depth !== 3);
}

function getPrefix(constructor, tag, fallback) {
if (constructor === null) {
if (tag !== '') {
Expand Down Expand Up @@ -691,8 +760,17 @@ function formatValue(ctx, value, recurseTimes, typedArray) {

function formatRaw(ctx, value, recurseTimes, typedArray) {
let keys;
let protoProps;
if (ctx.showHidden && (recurseTimes <= ctx.depth || ctx.depth === null)) {
protoProps = [];
}

const constructor = getConstructorName(value, ctx, recurseTimes, protoProps);
// Reset the variable to check for this later on.
if (protoProps !== undefined && protoProps.length === 0) {
protoProps = undefined;
}

const constructor = getConstructorName(value, ctx, recurseTimes);
let tag = value[SymbolToStringTag];
// Only list the tag in case it's non-enumerable / not an own property.
// Otherwise we'd print this twice.
Expand Down Expand Up @@ -722,21 +800,21 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
// Only set the constructor for non ordinary ("Array [...]") arrays.
const prefix = getPrefix(constructor, tag, 'Array');
braces = [`${prefix === 'Array ' ? '' : prefix}[`, ']'];
if (value.length === 0 && keys.length === 0)
if (value.length === 0 && keys.length === 0 && protoProps === undefined)
return `${braces[0]}]`;
extrasType = kArrayExtrasType;
formatter = formatArray;
} else if (isSet(value)) {
keys = getKeys(value, ctx.showHidden);
const prefix = getPrefix(constructor, tag, 'Set');
if (value.size === 0 && keys.length === 0)
if (value.size === 0 && keys.length === 0 && protoProps === undefined)
return `${prefix}{}`;
braces = [`${prefix}{`, '}'];
formatter = formatSet;
} else if (isMap(value)) {
keys = getKeys(value, ctx.showHidden);
const prefix = getPrefix(constructor, tag, 'Map');
if (value.size === 0 && keys.length === 0)
if (value.size === 0 && keys.length === 0 && protoProps === undefined)
return `${prefix}{}`;
braces = [`${prefix}{`, '}'];
formatter = formatMap;
Expand Down Expand Up @@ -771,12 +849,12 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
} else if (tag !== '') {
braces[0] = `${getPrefix(constructor, tag, 'Object')}{`;
}
if (keys.length === 0) {
if (keys.length === 0 && protoProps === undefined) {
return `${braces[0]}}`;
}
} else if (typeof value === 'function') {
base = getFunctionBase(value, constructor, tag);
if (keys.length === 0)
if (keys.length === 0 && protoProps === undefined)
return ctx.stylize(base, 'special');
} else if (isRegExp(value)) {
// Make RegExps say that they are RegExps
Expand All @@ -786,8 +864,10 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
const prefix = getPrefix(constructor, tag, 'RegExp');
if (prefix !== 'RegExp ')
base = `${prefix}${base}`;
if (keys.length === 0 || (recurseTimes > ctx.depth && ctx.depth !== null))
if ((keys.length === 0 && protoProps === undefined) ||
(recurseTimes > ctx.depth && ctx.depth !== null)) {
return ctx.stylize(base, 'regexp');
}
} else if (isDate(value)) {
// Make dates with properties first say the date
base = NumberIsNaN(DatePrototypeGetTime(value)) ?
Expand All @@ -796,12 +876,12 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
const prefix = getPrefix(constructor, tag, 'Date');
if (prefix !== 'Date ')
base = `${prefix}${base}`;
if (keys.length === 0) {
if (keys.length === 0 && protoProps === undefined) {
return ctx.stylize(base, 'date');
}
} else if (isError(value)) {
base = formatError(value, constructor, tag, ctx);
if (keys.length === 0)
if (keys.length === 0 && protoProps === undefined)
return base;
} else if (isAnyArrayBuffer(value)) {
// Fast path for ArrayBuffer and SharedArrayBuffer.
Expand All @@ -812,7 +892,7 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
const prefix = getPrefix(constructor, tag, arrayType);
if (typedArray === undefined) {
formatter = formatArrayBuffer;
} else if (keys.length === 0) {
} else if (keys.length === 0 && protoProps === undefined) {
return prefix +
`{ byteLength: ${formatNumber(ctx.stylize, value.byteLength)} }`;
}
Expand All @@ -836,7 +916,7 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
formatter = formatNamespaceObject;
} else if (isBoxedPrimitive(value)) {
base = getBoxedBase(value, ctx, keys, constructor, tag);
if (keys.length === 0) {
if (keys.length === 0 && protoProps === undefined) {
return base;
}
} else {
Expand All @@ -856,7 +936,7 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
formatter = formatIterator;
// Handle other regular objects again.
} else {
if (keys.length === 0) {
if (keys.length === 0 && protoProps === undefined) {
if (isExternal(value))
return ctx.stylize('[External]', 'special');
return `${getCtxStyle(value, constructor, tag)}{}`;
Expand Down Expand Up @@ -884,6 +964,9 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
output.push(
formatProperty(ctx, value, recurseTimes, keys[i], extrasType));
}
if (protoProps !== undefined) {
output.push(...protoProps);
}
} catch (err) {
const constructorName = getCtxStyle(value, constructor, tag).slice(0, -1);
return handleMaxCallStackSize(ctx, err, constructorName, indentationLvl);
Expand Down Expand Up @@ -1350,6 +1433,7 @@ function formatTypedArray(ctx, value, recurseTimes) {
}
if (ctx.showHidden) {
// .buffer goes last, it's not a primitive like the others.
// All besides `BYTES_PER_ELEMENT` are actually getters.
ctx.indentationLvl += 2;
for (const key of [
'BYTES_PER_ELEMENT',
Expand Down Expand Up @@ -1498,10 +1582,10 @@ function formatPromise(ctx, value, recurseTimes) {
return output;
}

function formatProperty(ctx, value, recurseTimes, key, type) {
function formatProperty(ctx, value, recurseTimes, key, type, desc) {
let name, str;
let extra = ' ';
const desc = ObjectGetOwnPropertyDescriptor(value, key) ||
desc = desc || ObjectGetOwnPropertyDescriptor(value, key) ||
{ value: value[key], enumerable: true };
if (desc.value !== undefined) {
const diff = (type !== kObjectType || ctx.compact !== true) ? 2 : 3;
Expand Down
80 changes: 79 additions & 1 deletion test/parallel/test-util-inspect.js
Original file line number Diff line number Diff line change
Expand Up @@ -391,8 +391,24 @@ assert.strictEqual(
{
class CustomArray extends Array {}
CustomArray.prototype[5] = 'foo';
CustomArray.prototype[49] = 'bar';
CustomArray.prototype.foo = true;
const arr = new CustomArray(50);
assert.strictEqual(util.inspect(arr), 'CustomArray [ <50 empty items> ]');
arr[49] = 'I win';
assert.strictEqual(
util.inspect(arr),
"CustomArray [ <49 empty items>, 'I win' ]"
);
assert.strictEqual(
util.inspect(arr, { showHidden: true }),
'CustomArray [\n' +
' <49 empty items>,\n' +
" 'I win',\n" +
' [length]: 50,\n' +
" '5': 'foo',\n" +
' foo: true\n' +
']'
);
}

// Array with extra properties.
Expand Down Expand Up @@ -2588,3 +2604,65 @@ assert.strictEqual(
throw err;
}
}

// Inspect prototype properties.
{
class Foo extends Map {
prop = false;
prop2 = true;
get abc() {
return true;
}
get def() {
return false;
}
set def(v) {}
get xyz() {
return 'Should be ignored';
}
func(a) {}
[util.inspect.custom]() {
return this;
}
}

class Bar extends Foo {
abc = true;
prop = true;
get xyz() {
return 'YES!';
}
[util.inspect.custom]() {
return this;
}
}

const bar = new Bar();

assert.strictEqual(
inspect(bar),
'Bar [Map] { prop: true, prop2: true, abc: true }'
);
assert.strictEqual(
inspect(bar, { showHidden: true, getters: true, colors: false }),
'Bar [Map] {\n' +
' [size]: 0,\n' +
' prop: true,\n' +
' prop2: true,\n' +
' abc: true,\n' +
" [xyz]: [Getter: 'YES!'],\n" +
' [def]: [Getter/Setter: false]\n' +
'}'
);
assert.strictEqual(
inspect(bar, { showHidden: true, getters: false, colors: true }),
'Bar [Map] {\n' +
' [size]: \x1B[33m0\x1B[39m,\n' +
' prop: \x1B[33mtrue\x1B[39m,\n' +
' prop2: \x1B[33mtrue\x1B[39m,\n' +
' abc: \x1B[33mtrue\x1B[39m,\n' +
' \x1B[2m[xyz]: \x1B[36m[Getter]\x1B[39m\x1B[22m,\n' +
' \x1B[2m[def]: \x1B[36m[Getter/Setter]\x1B[39m\x1B[22m\n' +
'}'
);
}
17 changes: 13 additions & 4 deletions test/parallel/test-whatwg-encoding-custom-textdecoder.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,19 @@ if (common.hasIntl) {
} else {
assert.strictEqual(
util.inspect(dec, { showHidden: true }),
"TextDecoder {\n encoding: 'utf-8',\n fatal: false,\n " +
'ignoreBOM: true,\n [Symbol(flags)]: 4,\n [Symbol(handle)]: ' +
"StringDecoder {\n encoding: 'utf8',\n " +
'[Symbol(kNativeDecoder)]: <Buffer 00 00 00 00 00 00 01>\n }\n}'
'TextDecoder {\n' +
" encoding: 'utf-8',\n" +
' fatal: false,\n' +
' ignoreBOM: true,\n' +
' [Symbol(flags)]: 4,\n' +
' [Symbol(handle)]: StringDecoder {\n' +
" encoding: 'utf8',\n" +
' [Symbol(kNativeDecoder)]: <Buffer 00 00 00 00 00 00 01>,\n' +
' lastChar: [Getter],\n' +
' lastNeed: [Getter],\n' +
' lastTotal: [Getter]\n' +
' }\n' +
'}'
);
}
}
Expand Down