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

console: add table method #18137

Closed
wants to merge 1 commit into from
Closed

Conversation

devsnek
Copy link
Member

@devsnek devsnek commented Jan 13, 2018

Refs https://console.spec.whatwg.org/#table

new super fine console.table method

i've been using firefox as my model for how the outputs should look

some column ordering stuff:

  • indexed properties before named properties unless properties is passed
  • Values column is always last, independent from properties
  • Key column always directly before Values column, independent from properties
  • (index) and (iteration index) columns are always first, independent from properties
Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines
Affected core subsystem(s)

console

@nodejs-github-bot nodejs-github-bot added the console Issues and PRs related to the console subsystem. label Jan 13, 2018
Trott
Trott previously requested changes Jan 14, 2018
Copy link
Member

@Trott Trott left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for doing this. Can you update the documentation for console.table() in console.md?

lib/console.js Outdated
@@ -249,6 +249,75 @@ Console.prototype.groupEnd = function groupEnd() {
this[kGroupIndent].slice(0, this[kGroupIndent].length - 2);
};

const untablableTypes = [
'symbol', 'undefined', 'boolean', 'number', 'string', 'function',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

function works just fine in Chrome: console.table([function a() {}, function b() {}]).

lib/console.js Outdated
];

const untablableValues = [
undefined, null, NaN, true, false, global, process,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are global and process singled out?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

they are objectively easier to read when not run through the table method, and the spec says Fall back to just logging the argument if it can’t be parsed as tabular. which i think is fairly true for both of those.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree in this case that it is going to be easier to read. The question is if we want to have a special handling here. And there could theoretically be other things that are difficult to read as well because they have lots of properties.

In chrome window works as tableable and I think that is comparable. I guess most people will not add process or global so I would say either we should not special handle them at all or we add a limit that by checking the property number.

lib/console.js Outdated
if (isSet(O))
return Array.from({ length: O.size }, (e, i) => i);
if (isSetIterator(O))
return Array.from(O).map((i) => i - 1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will exhaust O, preventing it from further usage; use the previewMapIterator and previewSetIterator from internal/v8 instead. Make sure to set a limit in how many elements to allow. See how it's used in util.format (the limit is 100 in this case):

node/lib/util.js

Lines 821 to 840 in c84582c

function formatCollectionIterator(preview, ctx, value, recurseTimes,
visibleKeys, keys) {
var nextRecurseTimes = recurseTimes === null ? null : recurseTimes - 1;
var vals = preview(value, 100);
var output = [];
for (const o of vals) {
output.push(formatValue(ctx, o, nextRecurseTimes));
}
return output;
}
function formatMapIterator(ctx, value, recurseTimes, visibleKeys, keys) {
return formatCollectionIterator(previewMapIterator, ctx, value, recurseTimes,
visibleKeys, keys);
}
function formatSetIterator(ctx, value, recurseTimes, visibleKeys, keys) {
return formatCollectionIterator(previewSetIterator, ctx, value, recurseTimes,
visibleKeys, keys);
}

lib/console.js Outdated
if (isSetIterator(O))
return Array.from(O).map((i) => i - 1);
if (isMap(O))
O = O.keys();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean return [...O.keys()];?

lib/console.js Outdated
if (isMap(O))
O = O.keys();
if (isMapIterator(O))
return Array.from(O);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as SetIterator

lib/console.js Outdated
untablableValues.includes(value) ||
untablableTypes.includes(typeof value);

const { isSet, isMap, isSetIterator, isMapIterator } = process.binding('util');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move to the top

assert.strictEqual(queue.shift(), `(index) a b
0 1 undefined
1 undefined 2
`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs more tests:

console.table([3, 4, { a: 3 }]);
console.table([3, 4, { a: 3 }], ['nonexistent']);
console.table([3, 4, { a: 3 }], ['Value']);
console.table([3, 4, { Value: 3 }], ['Value']);
console.table([,,,,,,,,,4]);
console.table([[,,,,,,,,,4]]);
console.table([[,,,,,,,,,{}]]);

// etc.

// Check browsers for sensible behaviors.

Also needed to test are Map objects, Map Iterator objects, Set objects, Set Iterator objects.

@BridgeAR
Copy link
Member

BridgeAR commented Feb 1, 2018

Ping @devsnek

@devsnek
Copy link
Member Author

devsnek commented Feb 1, 2018

yeah sorry i've been meaning to get to this, its on my list

@devsnek
Copy link
Member Author

devsnek commented Feb 1, 2018

@Trott @TimothyGu changes have been made

@Trott Trott dismissed their stale review February 2, 2018 04:13

requested changes have been made

@TimothyGu TimothyGu self-assigned this Feb 3, 2018
```

```js
console.log([{ a: 1, b: 'Y' }, { a: 'Z', b: 2 }], ['a']);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess console.log should actually be console.table.

lib/console.js Outdated
@@ -249,6 +252,74 @@ Console.prototype.groupEnd = function groupEnd() {
this[kGroupIndent].slice(0, this[kGroupIndent].length - 2);
};

const untablableTypes = [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we maybe call it untableable?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesn't really make sense, the method below is already named that

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You misunderstood my comment: I did not mean the whole variable name but only the beginning. And that for all of these variables.

The difference is the e.

Copy link
Member Author

@devsnek devsnek Feb 17, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh. i have no idea. pretty sure tableable/tablable isn't english anyway so i have no idea where to go with that. i think following other words it would become tablible but that looks really wrong.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe @Trott has a opinion about it?

lib/console.js Outdated
];

const untablableValues = [
undefined, null, NaN, true, false, global, process,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree in this case that it is going to be easier to read. The question is if we want to have a special handling here. And there could theoretically be other things that are difficult to read as well because they have lots of properties.

In chrome window works as tableable and I think that is comparable. I guess most people will not add process or global so I would say either we should not special handle them at all or we add a limit that by checking the property number.

lib/console.js Outdated

function getTablableKeysOf(O) {
if (isSet(O))
return Array.from({ length: O.size }, (e, i) => i);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spec says it should log the properties. We could show those and add a special handling for them but e.g. Chrome does not show them.

The same applies to the entries below.

lib/console.js Outdated
else
item = item[column];
}
r += `\t${item}`;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will always print \tundefined for Map and Set but it is possible to get the value from those.

lib/console.js Outdated
for (const column of columns) {
let item = data[row];
if (deep) {
if (isMap(item))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can probably never be reached. The same applies to isSet.

2 3
`);

test([3, 4, { a: 3 }], ['nonexistent'], `(index) a
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chrome shows this as the following that I think would be great to have:

(index) Value a
0 3
1 4
2   3

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't understand the output of that table

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no undefined value for a in index 0 and 1. The value does not exist. But there is a value and we can show that with 3 and 4.

Just enter console.table([3, 4, { a: 3 }]) in chrome to see the output :-)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rn the only disparity seems to be that i choose to ignore filters of props that don't exist while the browsers use undefined. i can change that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is how it looks like for me:

image
image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BridgeAR There isn’t really a standard on what should be printed. As long as the output makes sense, which IMO it does, it’s good.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TimothyGu I am aware that there is no standard for it but I feel like the output used in chrome does make more sense and provides a relatively good user experience.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm in favour of the change requested by @BridgeAR here, in relation to missing vs undefined values. I think it would go a long way towards making this feature more useful.


test([{ a: 1 }, { b: 2 }], `(index) a b
0 1 undefined
1 undefined 2
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be best to add extra padding up to the value length that has the highest length of the column. That way it will improve the readability massively.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i was considering that but i decided that as this isn't really intended to do more than humor the spec, and in general tables in terminals have a history of being formatted with single tabs without compensation (especially in configs and stuff) so i wanted to stay consistent with that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like it improves the readability a lot, so I would really like to add that. I do not see any issues about inconsistencies here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Showing the value undefined is actually wrong here out of my perspective because there is just no value. So it should be empty instead of showing undefined. Otherwise this can not be distinguished from the value undefined.

As an extra test I would add:

test(
  [{ a: 1 }, { b: undefined }], 
  '(index) a b' +
  '0       1 ' +
  '1         undefined'
)

lib/console.js Outdated
if (isSet(O))
return Array.from({ length: O.size }, (e, i) => i);
if (isSetIterator(O))
return previewSetIterator(O, 50);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like we should handle iterators as not tableable.

@devsnek devsnek force-pushed the feature/console-table branch 2 times, most recently from 7c583c7 to 0f8b7d7 Compare February 12, 2018 20:25
@BridgeAR
Copy link
Member

Ping @devsnek

lib/console.js Outdated
'symbol', 'undefined', 'boolean', 'number', 'string',
];

const untablableValues = [undefined, null, NaN, true, false];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The booleans, undefined and NaN are actually redundant to the untablableTypes. So it would be best to remove the untablableValues and just explicitely test for null in the untableable function.

lib/console.js Outdated
@@ -249,6 +252,74 @@ Console.prototype.groupEnd = function groupEnd() {
this[kGroupIndent].slice(0, this[kGroupIndent].length - 2);
};

const untablableTypes = [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe @Trott has a opinion about it?

lib/console.js Outdated
const untablableValues = [undefined, null, NaN, true, false];

const untablable = (value) =>
Object.keys(value).length > 20 ||
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check has to be last. Otherwise it will fail for null and undefined. Please add a test for this as well.

lib/console.js Outdated
const item = data[row];
if (!item || (typeof item !== 'object' && typeof item !== 'function'))
continue;
for (const key of getTablableKeysOf(data[row])) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

data[row] should be replaced with item here.


test([{ a: 1 }, { b: 2 }], `(index) a b
0 1 undefined
1 undefined 2
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Showing the value undefined is actually wrong here out of my perspective because there is just no value. So it should be empty instead of showing undefined. Otherwise this can not be distinguished from the value undefined.

As an extra test I would add:

test(
  [{ a: 1 }, { b: undefined }], 
  '(index) a b' +
  '0       1 ' +
  '1         undefined'
)

@devsnek devsnek force-pushed the feature/console-table branch 2 times, most recently from bdb7755 to 6399953 Compare March 3, 2018 23:40
@devsnek
Copy link
Member Author

devsnek commented Mar 3, 2018

@BridgeAR new code is here, i'm planning to clean up cli_table.js a bit but the behavior is where i want it to be

@devsnek devsnek force-pushed the feature/console-table branch 2 times, most recently from 2b8769b to 3288a89 Compare March 3, 2018 23:55
@devsnek devsnek added the wip Issues and PRs that are still a work in progress. label Mar 3, 2018
@devsnek devsnek force-pushed the feature/console-table branch 2 times, most recently from be31b6a to c36dd94 Compare March 4, 2018 00:09
lib/console.js Outdated
@@ -249,6 +253,86 @@ Console.prototype.groupEnd = function groupEnd() {
this[kGroupIndent].slice(0, this[kGroupIndent].length - 2);
};

const untablable = (value) =>
value == null ||
['symbol', 'undefined', 'boolean', 'number', 'string']
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

undefined is redundant to value == null. Either the check above should be strict equal or the undefined should be removed here.

@devsnek devsnek force-pushed the feature/console-table branch 2 times, most recently from c579032 to 8382250 Compare March 4, 2018 01:14
@devsnek devsnek removed the wip Issues and PRs that are still a work in progress. label Mar 4, 2018
-->

* `tabularData` {any}
* `properties` {Array<String>} Alternate properties for constructing the table.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

string[]


test(new Map([ ['a', 1], [Symbol(), [2]] ]), `
┌───────────────────┬──────────┬────────┐
│ (iteration index) │ Key │ Values │
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Keys

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually: we might want to change Values to Value instead. Because it is a single value at the specific index position.

@devsnek devsnek force-pushed the feature/console-table branch 2 times, most recently from b50f098 to 600c45b Compare March 11, 2018 16:59
Copy link
Member

@BridgeAR BridgeAR left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not want to block this any further from landing. So LGTM. I would like to get some further adjustments in but I can open a PR for that at some point in the future.

@devsnek devsnek added the author ready PRs that have at least one approval, no pending requests for changes, and a CI started. label Mar 30, 2018
@devsnek
Copy link
Member Author

devsnek commented Mar 30, 2018

@devsnek
Copy link
Member Author

devsnek commented Mar 30, 2018

@devsnek
Copy link
Member Author

devsnek commented Mar 30, 2018

as a heads-up i will be landing this in a few hours

@devsnek
Copy link
Member Author

devsnek commented Mar 31, 2018

landed in 97ace04

@devsnek devsnek closed this Mar 31, 2018
@devsnek devsnek deleted the feature/console-table branch March 31, 2018 00:42
devsnek added a commit that referenced this pull request Mar 31, 2018
PR-URL: #18137
Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
@targos targos added semver-minor PRs that contain new features and should be released in the next minor version. and removed author ready PRs that have at least one approval, no pending requests for changes, and a CI started. labels Apr 2, 2018
@targos
Copy link
Member

targos commented Apr 2, 2018

Don't forget the semver-minor label for PRs that add new features ;)

@targos
Copy link
Member

targos commented Apr 2, 2018

Should this be backported to v9.x-staging? If yes please follow the guide and raise a backport PR, if not let me know or add the dont-land-on label.

devsnek added a commit to devsnek/node that referenced this pull request Apr 2, 2018
PR-URL: nodejs#18137
Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
@vitaly-t
Copy link
Contributor

vitaly-t commented Apr 24, 2018

Method console.table has been in Node.js for a very long time, but it was doing nothing. And now that there is an implementation, it raises a question of how to do a proper check that the current version of Node.js actually implements console.table?

@BridgeAR
Copy link
Member

@vitaly-t I can think of two ways: 1) check for the version. Everything from 10.x on does something. It will likely be backported as well. So that might not be a reliable check. 2) Hook into stdout while a module is loaded and do a feature check.

BridgeAR pushed a commit to BridgeAR/node that referenced this pull request May 1, 2018
PR-URL: nodejs#18137
Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
console Issues and PRs related to the console subsystem. semver-minor PRs that contain new features and should be released in the next minor version.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants