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

How do I implement MyObject.inspect(depth, inspectOpts)? #8442

Closed
Havvy opened this issue Sep 8, 2016 · 8 comments
Closed

How do I implement MyObject.inspect(depth, inspectOpts)? #8442

Havvy opened this issue Sep 8, 2016 · 8 comments
Labels
doc Issues and PRs related to the documentations. good first issue Issues that are suitable for first-time contributors. question Issues that look for answers. util Issues and PRs related to the built-in util module.

Comments

@Havvy
Copy link
Contributor

Havvy commented Sep 8, 2016

I want to implement inspect for my own object. I want to mostly follow the semantics of inspect for non-custom implementations, but just by going by the documentation alone, I have no possible way of doing so. What conventions are there? How do I recursively call inspect while respecting depth? Do I need to handle the case of depth being null?

The example ignores the parameters passed to it completely currently. Maybe if we had an example of a Box object that inspects as "Box<InnerInspectValue>" so e.g. inspect({value: "a", inspect: ... }) returns "Box<'a'>" but the value can be anything (including over objects with custom inspect impls).

@mscdex mscdex added question Issues that look for answers. util Issues and PRs related to the built-in util module. labels Sep 8, 2016
@Havvy
Copy link
Contributor Author

Havvy commented Sep 8, 2016

cc @addaleax since you've been doing some work w.r.t. util.inspect recently.

@addaleax
Copy link
Member

addaleax commented Sep 8, 2016

Hi @Havvy – I agree, the examples don’t really go into that aspect. I probably have time to look into that later, but it would be really cool if you could start out with basically showing a full example with the output you’d like to see (and leave out the things you’re unsure about)?

@Havvy
Copy link
Contributor Author

Havvy commented Sep 9, 2016

Here's an initial attempt. Feel free to warp completely. I'm not all that good at documentation. I'm assuming that the inspect will never be called such that you need to handle not returning anything. The main goal here is to tell me the main conventions and logic the I should be thinking about when implementing things, especially recursive things. I went with an example of a Box because it's the simplest thing I could think of that embodied all of the major issues you should run into when implementing this function.


When implementing inspect on your object, to properly respect the recursion limit, you must use the depth value properly. When you want to include the inspected value of another object, call util.inspect with your inspect options but change the depth to ___ unless depth is null.

If you want to support custom colors (and no colors), you can style your text by doing ....

To support the breakLength value, if the size of your returned string is greater than the breakLength value, then you should break the return value into multiple lines. This is best done by...1. You don't need to modify breakLength when passing it to your object.

If your object is an arraylike, remember to check the maxArrayLength value.

And to combine all of these, we look at implementing inspect on a Box object. We want the box object to display its "value" property surrounded by "Box<>"2. For example, a box of an empty object would display as "Box<{}>" while a box of a longer object would show up like the following:

Box<
  {
    foo: "A long string."
  }
>

The implementation of this would look like:

function Box(value) {
    this.value = value;
}

Box.prototype[util.inspect.custom] = function (depth, inspectArgs) {
     // impl goes here with comments explaining nuances.
}

Footnotes:

  1. I can't actually think of a good general solution here.
  2. This should be colored cyan (a.k.a. special) in the docs.

@jasnell jasnell added the good first issue Issues that are suitable for first-time contributors. label Sep 9, 2016
@addaleax
Copy link
Member

addaleax commented Sep 9, 2016

Hmm. How close would this example get to what you want to show?

const util = require('util');

class Box {
  constructor(value) {
    this.value = value;
  }

  [util.inspect.custom](depth, options) {
    const newOptions = Object.assign({}, options, {
      depth: options.depth - depth
    });

    return options.stylize('Box', 'special') + '<' +
        util.inspect(this.value, newOptions) + '>';
  }
}

util.inspect.defaultOptions.colors = true;

console.log(new Box({}))
console.log(new Box({
  aaaaaaaaaaaaaaaaa: 1,
  aaaaaaaaaaaaaaaab: 2,
  aaaaaaaaaaaaaaaae: 3
}))
console.log(new Box({a:{b:{c:{d:{}}}}}));

Output:

Box<{}>
Box<{ aaaaaaaaaaaaaaaaa: 1,
  aaaaaaaaaaaaaaaab: 2,
  aaaaaaaaaaaaaaaae: 3 }>
Box<{ a: [Object] }>

I agree, there are some parts, like checking maxArrayLength and friends, that are not that easy to implement, but your suggestions sounds pretty solid to me.

@Havvy
Copy link
Contributor Author

Havvy commented Sep 10, 2016

It's close.

It doesn't seem to be respecting depth properly.

const box = new Box({x: 5});
console.log(util.inspect(box));
console.log(util.inspect({wrap: box}));
console.log(util.inspect({wrap: {wrap: box} });
console.log(util.inspect({wrap: {wrap: {wrap: box} } }));
console.log(util.inspect({wrap: {wrap: {wrap: {wrap: box} } } }));

// Expected Outputs
// Box<{ x: 5 }>
// { wrap: Box<{ x: 5 }> }
// { wrap: { wrap: { Box<[Object]> } }
// { wrap: { wrap: { wrap: [Box] } } }
// { wrap: { wrap: { wrap: [Object] } } }

// Actual Outputs
// Box<{ x: 5 }>
// { wrap: Box<{ x: 5 }> }
// { wrap: { wrap: Box<{ x: 5 }> } }
// { wrap: { wrap: { wrap: Box<{ x: 5 }> } } }
// { wrap: { wrap: { wrap: [Object] } } }

Experimenting with util.inspect, it shows the short form (e.g. "[Object]") if the depth is less than zero.

This line depth: options.depth - depth actually sets the depth to 0, and of course, you're not checking depth to see if you should not recurse downwards or not. As such, let box = new Box(5); for (var ix = 0; ix < 10; ix += 1) { box = new Box(box); } util.inspect(box) will show all 10 or so levels of boxing instead of stopping earlier. Also, null - 1 evaluates to -1, so we need to specifically check for that. (Why was null chosen as the value instead of Infinity?)

So, taking those into consideration, here's a better version:

class Box {
  constructor(value) {
    this.value = value;
  }

  inspect(depth, options) {
    if (depth < 0) {
      return options.stylize('[Box]', 'special');
    }

    const newOptions = Object.assign({}, options, {
      depth: options.depth === null ? null : options.depth - 1
    });

    return options.stylize('Box', 'special') + '<' +
        util.inspect(this.value, newOptions) + '>';
  }
}

@Havvy
Copy link
Contributor Author

Havvy commented Sep 28, 2016

And here's code dealing with whitespace. (I haven't actually tested this code yet)

class Box {
  constructor(value) {
    this.value = value;
  }

  inspect(depth, options) {
    if (depth < 0) {
      return options.stylize('[Box]', 'special');
    }

    const newOptions = Object.assign({}, options, {
      depth: options.depth === null ? null : options.depth - 1
    });

    // Four space padding because that's the size of "Box<".
    const padding = "    ";
    const inner = util.inspect(this.value, newOptions).replace(/\n/g, "\n" + padding);
    return options.stylize('Box', 'special') + '<' + inner + '>';
  }
}

@addaleax
Copy link
Member

@Havvy I guess that looks pretty okay, and trying it out seems to work just fine – do you want to open a PR against doc/api/util.md with this example?

Havvy added a commit to Havvy/node that referenced this issue Oct 1, 2016
Changes the custom inspect example to a more complex object that
actually uses the parameters of the custom inspect function. I
specifically chose a wrapper of a value called a "Box" that inspects
to "Box < inner_inspect_value >".

I also want there to be documentation explaining what the code is
actually doing. E.g., the padding replacement part is to make the
inspected value line up properly when multi-line inputs are given.

I also went with having a space between the Box's brackets and the inner
value because it matches how objects work, and that should definitely be
listed as a convention somewhere in here.

Also, the convention to shorten only when depth is less than 0, not e.g.
at 0.

But I don't know how to write the documentation for that, so I'm leaving
that to somebody who reads this message.

Partially fixes: nodejs#8442
addaleax pushed a commit that referenced this issue Oct 26, 2016
Changes the custom inspect example to a more complex object that
actually uses the parameters of the custom inspect function. I
specifically chose a wrapper of a value called a "Box" that inspects
to "Box < inner_inspect_value >".

I also want there to be documentation explaining what the code is
actually doing. E.g., the padding replacement part is to make the
inspected value line up properly when multi-line inputs are given.

I also went with having a space between the Box's brackets and the inner
value because it matches how objects work, and that should definitely be
listed as a convention somewhere in here.

Also, the convention to shorten only when depth is less than 0, not e.g.
at 0.

But I don't know how to write the documentation for that, so I'm leaving
that to somebody who reads this message.

Ref: #8442
PR-URL: #8875
Reviewed-By: Evan Lucas <evanlucas@me.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
evanlucas pushed a commit that referenced this issue Nov 3, 2016
Changes the custom inspect example to a more complex object that
actually uses the parameters of the custom inspect function. I
specifically chose a wrapper of a value called a "Box" that inspects
to "Box < inner_inspect_value >".

I also want there to be documentation explaining what the code is
actually doing. E.g., the padding replacement part is to make the
inspected value line up properly when multi-line inputs are given.

I also went with having a space between the Box's brackets and the inner
value because it matches how objects work, and that should definitely be
listed as a convention somewhere in here.

Also, the convention to shorten only when depth is less than 0, not e.g.
at 0.

But I don't know how to write the documentation for that, so I'm leaving
that to somebody who reads this message.

Ref: #8442
PR-URL: #8875
Reviewed-By: Evan Lucas <evanlucas@me.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
MylesBorins pushed a commit that referenced this issue Nov 17, 2016
Changes the custom inspect example to a more complex object that
actually uses the parameters of the custom inspect function. I
specifically chose a wrapper of a value called a "Box" that inspects
to "Box < inner_inspect_value >".

I also want there to be documentation explaining what the code is
actually doing. E.g., the padding replacement part is to make the
inspected value line up properly when multi-line inputs are given.

I also went with having a space between the Box's brackets and the inner
value because it matches how objects work, and that should definitely be
listed as a convention somewhere in here.

Also, the convention to shorten only when depth is less than 0, not e.g.
at 0.

But I don't know how to write the documentation for that, so I'm leaving
that to somebody who reads this message.

Ref: #8442
PR-URL: #8875
Reviewed-By: Evan Lucas <evanlucas@me.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
MylesBorins pushed a commit that referenced this issue Nov 19, 2016
Changes the custom inspect example to a more complex object that
actually uses the parameters of the custom inspect function. I
specifically chose a wrapper of a value called a "Box" that inspects
to "Box < inner_inspect_value >".

I also want there to be documentation explaining what the code is
actually doing. E.g., the padding replacement part is to make the
inspected value line up properly when multi-line inputs are given.

I also went with having a space between the Box's brackets and the inner
value because it matches how objects work, and that should definitely be
listed as a convention somewhere in here.

Also, the convention to shorten only when depth is less than 0, not e.g.
at 0.

But I don't know how to write the documentation for that, so I'm leaving
that to somebody who reads this message.

Ref: #8442
PR-URL: #8875
Reviewed-By: Evan Lucas <evanlucas@me.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
@sam-github sam-github added doc Issues and PRs related to the documentations. and removed doc Issues and PRs related to the documentations. docs-requested labels Dec 1, 2016
@addaleax
Copy link
Member

I’m going to close this as #8875 has landed and there hasn’t been any new discussion since then. If you feel like there’s more to this issue, feel free to re-open, or open a new issue or pull request. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
doc Issues and PRs related to the documentations. good first issue Issues that are suitable for first-time contributors. question Issues that look for answers. util Issues and PRs related to the built-in util module.
Projects
None yet
Development

No branches or pull requests

5 participants