Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Improvements to util.inspect #2360

Closed
wants to merge 15 commits into from

6 participants

@Benvie

Use all the colors available, identify constructors, make showHidden more useful by hiding useless function properties, improve quote escaping, color names to identify hidden and readonly properties, more.

@tj
tj commented

if there's and "and" in one commit msg you'll do better splitting it up

@Benvie

It can be two commits indeed, but that's pretty much the limit. The major portion is the change to inspect which is one large component.

lib/util.js
((55 lines not shown))
}
-exports.inspect = inspect;
+exports.is = is;
+
+
+// returns true for strings, numbers, booleans, null, undefined, NaN
+function isPrimitive(o){
+ return Object(o) !== o;
+}
+exports.isPrimitive = isPrimitive;
+
+
+var isUndefined = is('Undefined'),

idk if this one is right:

> Object.prototype.toString.call(undefined)
'[object global]'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@Benvie

So you don't need to compile Node just to see what this does, here's some example output on Windows:

example image

lib/util.js
((62 lines not shown))
+ return Object(o) !== o;
+}
+exports.isPrimitive = isPrimitive;
+
+
+var isUndefined = is('Undefined'),
+ isFunction = is('Function'),
+ isBoolean = is('Boolean'),
+ isString = is('String'),
+ isNumber = is('Number'),
+ isRegExp = is('RegExp'),
+ isObject = is('Object'), // inherits from Object, "plain object"
+ isArray = Array.isArray,
+ isError = is('Error'),
+ isDate = is('Date'),
+ isNull = is('Null');

Same here:

> Object.prototype.toString.call(null)
'[object global]'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@TooTallNate

So some of this is nice. I'm kinda neutral personally. But there's no way this would get merged without some test cases. And I'm guessing that some of the existing tests break with this as well...

@Benvie

The is[type] stiff I'll break out like TJ mentioned. The formatting stuff doesn't really rely on it. It basically just uses Object.prototype.toString and works from there, along with a bit of isPrimitive which is also simple.

@Benvie

And yeah test cases I'll absolutely do. This is a major core change, but I just wanted to float it up here to see if it'd even be considered before putting major effort into refining it further. If it looks like something that'd be considered upon further refinement and whatnot I'll definitely put the effort in to get there. I'm going to split the is[Type] bits out too because they're far more likely to impact actually live code. The inspect stuff is for debugging almost exclusively.

@Benvie

Also just to be super clear, what version of Node are you using with this Object.prototype.toString.call(undefined).

By the spec, and as per v8 >3.7, the result of Object.prototype.toString.call(undefined) is '[object Undefined]'. null for that returns Null, etc. Basically it's incredibly accurate with the versions of v8 I've tested.

Object.prototype.toString.call(this) returns [object global] when testing the global object which looks right to me. window is that in browsers.

@TooTallNate

@Benvie The 0.4.x branch exhibits that behavior, so I guess it doesn't matter too much. Learned something new :)
But still, perhaps more efficient tests for null and undefined are already available to us:

typeoof v === 'undefined'
v === null
@Benvie

Generally as I've seen, the null test happens after primitive tests due to the whole null is an object but not anything else useful flub.

@Benvie

Also to note, with a tiny bit more work it's possible to map stuff to something besides ansi. In that case you plreace the ansi color function with something that wraps html spans instrad of escape sequences: http://benvie.github.com/dom.js/

@Benvie

Modified to only include the inspect related stuff, as that was the primary thing I wanted to improve.

@bnoordhuis

I like the pretty colors. This won't land in v0.6 but I can see it landing in the master branch. make test should pass and it'll need tests for the new features.

Not sure if I like the resolveGetters option. Getters can have side effects, it could lead to hard to track down bugs. What about recursion? What if you have a getter that (warning: pseudo syntax) returns util.inspect(this, resolveGetters=true)?

@Benvie

You point about getters has clarified my thought process on what I'm trying to do. I'm going to remove that option and also remove any bound references to builtin functions in use for formatting and replace them with dynamic references, like Object.prototype.toString. This matches the existing functionality more closely. After I finish work on this pull I'm going to start a new one to address issues relating ECMAScript6 as well the general issue of dynamic references to modifiable builtin functions.

Here's my current thought process for this current patch, now a bit more clarified thanks to the getter comment.

It's critical to prevent unintended side-effects when doing introspection; the purpose is to observe without modifying. The API provides a hook using the inspect property on an object if a user specifically wants to cause side effects when inspecting. All other side effects should be considered bugs.

As an illustration of this, here's an example. A user created class/prototype where the children property is a getter that does fs.readdirSync and returns an array of new instances each representing a path in the directory. Inspecting an object that represents / would result in a synchronous mapping of the whole drive, only limited by depthLimit.

There's three avenues of triggering unexpected/unwanted side-effects:

  • Accessors. o.prop and o.prop = val. Property access becomes function calls. This is the most important to check for during introspection because accessors are usually made explicitly to cause side effects during property lookup. This can be avoided using Object.getOwnPropertyDescriptor (or the deprecated Object.prototype.__lookupSetter__).

  • Type coercion. A non-primitive value should never touch touch any math or bitwise operators or any comparison operator that isn't === or !==. All of them trigger obj.valueOf or obj.toString or both.

  • Functions on builtin constructors and prototypes. With this category execution is explicit unlike the previous two, but the builtin functionality has known or no side effects (usually none). These functions can be changed and and code run in the same context with uncached references can cause unknown side effects by executing them. Benign non-side effect examples:

    • Shim Array.isArray to return true for Array-like objects.
    • Shim Object.prototype.toString to check for a _class property and return '[object '+this._class+']' if found.

Currently node prevents side-effects directly from getters, but not from using object.__lookup[GS]etter in the process. It prevents side-effects from the second as far as I've found. It does not prevent the third.

This pull fixes the issue with __lookup[GS]etter__. It only partially addressed the third, but I'm going to remove any cached bindings that don't match Node's current functionality. That change should be addressed separately and hit all the problems at once.

@tj
tj commented

you're over-thinking it IMHO, people may replace these things but they shoudn't, and if they do they're asking for it. Like I've mentioned before to others you cannot possible defend against every single thing that could be replaced in javascript, nor should you

@Benvie

It's definitely overthinking things from the perspective of ES5. But the reason I started modifying inspect to begin with was because it's largely broken for ES6 and Proxies. The question of what actions trigger what side-effects is a much, much bigger question. So I'm trying to set this up now in a way that way overthinks it for ES5 but is required for ES6.

@Benvie

I'm not going to add more to this pull, but I wanted to just write it down here for future reference because I'm going to make further pulls regarding those issues.

@Benvie

I think it's looking pretty good now. Amongst other improvements it now passes all existing tests and about doubled the number of tests for util.inspect.

@bnoordhuis

@Benvie: Is this patch against master or v0.6?

@Benvie

It's against master. Looking at the history there's only one commit difference between master and 0.6 (change date formatting from Date.prototype.toUTCString to Date.prototype.toString which is included in this) so it likely could go in either without issue. It has no external dependencies.

Benvie added some commits
@Benvie Benvie Significant modifications to util.js. Changes includes new type detec…
…tion functions and new inspect formatting.
1402bd5
@Benvie Benvie Revert changes to the various isType functions. Make non-configurable…
… properties always display as constants instead of just capslock ones. Reorganize a bit to more closely match original organization of util.js.
16049ea
@Benvie Benvie Change objectToString back to unbound function. Change quoting to be …
…more friendly to copy/pasting back into javascript. Show prototypes for constructors when using showHidden. Change check for labeling a color as constant when non-writable or getter with no setter.
ff213b9
@Benvie Benvie Change colors to identify hidden constants from enumerable constants,…
… rearrange the type colors a bit to work better.
be86f47
@Benvie Benvie Fix quoting b381db9
@Benvie Benvie Fix so sparse arrays aren't abbreviated. Fix quoting from adding an e…
…xtra slash sometimes. Add special arrows to non-color formatter. Match error formatting from prior version of inspect with brackets and all properties. Match prior version of formatting hidden names with brackets.
8804b36
@Benvie Benvie Add Proxy, WeakMap, Set, and Map to list of known globals so testing …
…with --harmony is possible.
bae19ce
@Benvie Benvie Add tests for util.inspect
* Constructor detection and formatting
* Quote formatting
* Property name quoting
* RegExp formatting
* Accessor formatting
* Circular references
* Recurse limit
48c4b8c
@Benvie Benvie Add tests for util.inspect
* Constructor detection and formatting
* Quote formatting
* Property name quoting
* RegExp formatting
* Accessor formatting
* Circular references
* Recurse limit
cc9881a
@Benvie Benvie Improve constructor detection. Make V8 c++ accessors no always show u…
…p as read only.
9e9ab8c
@Benvie Benvie Break out `isConstructor` into its own function and also export it. 29aaf64
@Benvie Benvie Add in background colors ansi escape sequence list. Get branch up to …
…latest version. Add in a few fixes I've found along the way in the version I'm using outside of node core
4297318
@Benvie Benvie Missed different names in the merge, fixes the build 66fd77b
@Benvie Benvie Fix some merge issues, add in showing of cistom [[protos]], fix spaci…
…ng a bit, set name variable separate from key so the raw key can be checked.
243b640
@Benvie Benvie Fix another issue with merge, looks good now. c0b82b5
@nyxtom

What's the status of this? Are there still outstanding issues with this pull or is it just backlogged?

@isaacs

There are three problems.

  1. It does not apply cleanly against master. It should be rebased.
  2. It does quite a bit more stuff. I'd want to make sure it doesn't cause any performance regressions for standard non-colored uses of console.log and the like.
  3. I'm not really seeing many people begging for this. (Could be just other things have been more important, I don't know).

@TooTallNate You are the committer who's been the most active in util.inspect and console in recent history. I'll leave it up to you. If you think this is a good idea, please re-open, and consider it yours to review/land. I have no strong opinions. There's no urgency, so feel free to re-open and leave it untouched for another year. Just closing it so that if no one cares, it will go uncared-for.

@isaacs isaacs closed this
@TooTallNate

I think the majority of this has been moved into @benvie's "ultra repl".

The functionality in the current core util.inspect() probably won't be changing this drastically.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 1, 2012
  1. @Benvie

    Significant modifications to util.js. Changes includes new type detec…

    Benvie authored
    …tion functions and new inspect formatting.
  2. @Benvie

    Revert changes to the various isType functions. Make non-configurable…

    Benvie authored
    … properties always display as constants instead of just capslock ones. Reorganize a bit to more closely match original organization of util.js.
  3. @Benvie

    Change objectToString back to unbound function. Change quoting to be …

    Benvie authored
    …more friendly to copy/pasting back into javascript. Show prototypes for constructors when using showHidden. Change check for labeling a color as constant when non-writable or getter with no setter.
  4. @Benvie

    Change colors to identify hidden constants from enumerable constants,…

    Benvie authored
    … rearrange the type colors a bit to work better.
  5. @Benvie

    Fix quoting

    Benvie authored
  6. @Benvie

    Fix so sparse arrays aren't abbreviated. Fix quoting from adding an e…

    Benvie authored
    …xtra slash sometimes. Add special arrows to non-color formatter. Match error formatting from prior version of inspect with brackets and all properties. Match prior version of formatting hidden names with brackets.
  7. @Benvie

    Add Proxy, WeakMap, Set, and Map to list of known globals so testing …

    Benvie authored
    …with --harmony is possible.
  8. @Benvie

    Add tests for util.inspect

    Benvie authored
    * Constructor detection and formatting
    * Quote formatting
    * Property name quoting
    * RegExp formatting
    * Accessor formatting
    * Circular references
    * Recurse limit
  9. @Benvie

    Add tests for util.inspect

    Benvie authored
    * Constructor detection and formatting
    * Quote formatting
    * Property name quoting
    * RegExp formatting
    * Accessor formatting
    * Circular references
    * Recurse limit
  10. @Benvie
  11. @Benvie
  12. @Benvie

    Add in background colors ansi escape sequence list. Get branch up to …

    Benvie authored
    …latest version. Add in a few fixes I've found along the way in the version I'm using outside of node core
Commits on Feb 3, 2012
  1. @Benvie
  2. @Benvie

    Fix some merge issues, add in showing of cistom [[protos]], fix spaci…

    Benvie authored
    …ng a bit, set name variable separate from key so the raw key can be checked.
Commits on Feb 4, 2012
  1. @Benvie
This page is out of date. Refresh to see the latest.
Showing with 422 additions and 200 deletions.
  1. +349 −200 lib/util.js
  2. +14 −0 test/common.js
  3. +59 −0 test/simple/test-util-inspect.js
View
549 lib/util.js
@@ -92,65 +92,167 @@ var error = exports.error = function(x) {
* output. Default is false (no coloring).
*/
function inspect(obj, showHidden, depth, colors) {
- var ctx = {
- showHidden: showHidden,
- seen: [],
- stylize: colors ? stylizeWithColor : stylizeNoColor
+ var settings = {
+ showHidden: showHidden, // show non-enumerables
+ style: colors ? color : noColor,
+ seen: []
};
- return formatValue(ctx, obj, (typeof depth === 'undefined' ? 2 : depth));
+
+ // cache formatted brackets
+ settings.square = [
+ settings.style('[', 'Square'),
+ settings.style(']', 'Square')
+ ];
+ settings.curly = [
+ settings.style('{', 'Curly'),
+ settings.style('}', 'Curly')
+ ];
+
+ return formatValue(obj, '', depth || 2, settings);
}
exports.inspect = inspect;
// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
-var colors = {
- 'bold' : [1, 22],
- 'italic' : [3, 23],
- 'underline' : [4, 24],
- 'inverse' : [7, 27],
- 'white' : [37, 39],
- 'grey' : [90, 39],
- 'black' : [30, 39],
- 'blue' : [34, 39],
- 'cyan' : [36, 39],
- 'green' : [32, 39],
- 'magenta' : [35, 39],
- 'red' : [31, 39],
- 'yellow' : [33, 39]
+var ansi = {
+ black : [ '30', '39'],
+ red : [ '31', '39'],
+ green : [ '32', '39'],
+ yellow : [ '33', '39'],
+ blue : [ '34', '39'],
+ magenta : [ '35', '39'],
+ cyan : [ '36', '39'],
+ white : [ '37', '39'],
+ bblack : ['1;30', '22;39'],
+ bred : ['1;31', '22;39'],
+ bgreen : ['1;32', '22;39'],
+ byellow : ['1;33', '22;39'],
+ bblue : ['1;34', '22;39'],
+ bmagenta : ['1;35', '22;39'],
+ bcyan : ['1;36', '22;39'],
+ bwhite : ['1;37', '22;39'],
+ bgblack : [ '40', '49'],
+ bgred : [ '41', '49'],
+ bggreen : [ '42', '49'],
+ bgyellow : [ '43', '49'],
+ bgblue : [ '44', '49'],
+ bgmagenta : [ '45', '49'],
+ bgcyan : [ '46', '49'],
+ bgwhite : [ '47', '49'],
+ bgbblack : [ '100', '25;49'],
+ bgbred : [ '101', '25;49'],
+ bgbgreen : [ '102', '25;49'],
+ bgbyellow : [ '103', '25;49'],
+ bgbblue : [ '104', '25;49'],
+ bgbmagenta : [ '105', '25;49'],
+ bgbcyan : [ '106', '25;49'],
+ bgbwhite : [ '107', '25;49']
};
-// Don't use 'blue' not visible on cmd.exe
+
+// map types to a color
var styles = {
- 'special': 'cyan',
- 'number': 'yellow',
- 'boolean': 'yellow',
- 'undefined': 'grey',
- 'null': 'bold',
- 'string': 'green',
- 'date': 'magenta',
- // "name": intentionally not styling
- 'regexp': 'red'
+ // falsey
+ Undefined : 'bblack',
+ Null : 'bblack',
+ // constructor functions
+ Constructor : 'bcyan',
+ // normal types
+ Function : 'cyan',
+ Boolean : 'magenta',
+ Date : 'magenta',
+ Error : 'bred',
+ Number : 'bmagenta',
+ RegExp : 'bmagenta',
+ // proprty names and strings
+ HString : 'green',
+ String : 'bgreen',
+ HConstant : 'yellow',
+ Constant : 'byellow',
+ HName : 'bblack',
+ Name : 'bwhite',
+ // meta-labels
+ More : 'red',
+ Accessor : 'magenta',
+ Circular : 'red',
+ Proto : 'bred',
+ ObjState : 'bred',
+ // brackets
+ Square : 'white',
+ Curly : 'white'
};
-function stylizeWithColor(str, styleType) {
- var style = styles[styleType];
- if (style) {
- return '\033[' + colors[style][0] + 'm' + str +
- '\033[' + colors[style][1] + 'm';
- } else {
- return str;
+// most formatting determined by internal [[class]]
+var formatters = {
+ Boolean: String,
+ Date: function(d){
+ return '[' + Date.prototype.toString.call(d) + ']'
+ },
+ Constructor: function(f){
+ var nativeLabel = isNative(f) ? 'Native ' : '';
+ var name = f.name.length ? ': ' + f.name: '';
+ return '[' + nativeLabel + 'Constructor' + name + ']';
+ },
+ Error: function(e){
+ return '[' + Error.prototype.toString.call(e) + ']';
+ },
+ Function: function(f){
+ var nativeLabel = isNative(f) ? 'Native ' : '';
+ var name = f.name.length ? ': ' + f.name: '';
+ return '[' + nativeLabel + 'Function' + name + ']';
+ },
+ Null: String,
+ Number: String,
+ RegExp: function(r){
+ return RegExp.prototype.toString.call(r);
+ },
+ String: quotes,
+ Undefined: String,
+ Proto : function(f){
+ if (Object(f) === f && 'constructor' in f && f.constructor.name.length) {
+ var name = ': ' + f.constructor.name;
+ } else {
+ var name = '';
+ }
+ return '[[Proto' + name + ']]';
+ }
+};
+
+
+// wrap a string with ansi escapes for coloring
+function color(str, style, special) {
+ var out = special ? '\u00AB' + str + '\u00BB' : str;
+ if (styles[style]) {
+ out = '\033[' + ansi[styles[style]][0] + 'm' + out +
+ '\033[' + ansi[styles[style]][1] + 'm';
}
+ return out;
}
-function stylizeNoColor(str, styleType) {
- return str;
+// return without ansi colors
+function noColor(str, style, special) {
+ return special ? '\u00AB' + str + '\u00BB' : str;
}
-function formatValue(ctx, value, recurseTimes) {
+var numeric = /^\d+$/;
+var q = ['"', "'"];
+var qMatch = [/(')/g, /(")/g];
+
+// quote string preferably with quote type not found in the string
+// then escape slashes and opposite quotes if string had both types
+function quotes(s) {
+ s = String(s).replace(/\\/g, '\\\\');
+ var qWith = +(s.match(qMatch[0]) === null);
+ return q[qWith] + s.replace(qMatch[1-qWith], '\\$1') + q[qWith];
+}
+
+
+
+function formatValue(value, key, depth, settings) {
// Provide a hook for user-specified inspect functions.
// Check that value is an object with an inspect function on it
if (value && typeof value.inspect === 'function' &&
@@ -158,220 +260,235 @@ function formatValue(ctx, value, recurseTimes) {
value.inspect !== exports.inspect &&
// Also filter out any prototype objects using the circular check.
!(value.constructor && value.constructor.prototype === value)) {
- return value.inspect(recurseTimes);
+ return value.inspect(depth, settings.showHidden, settings.colors.name === 'color');
}
- // Primitive types cannot have properties
- var primitive = formatPrimitive(ctx, value);
- if (primitive) {
- return primitive;
- }
+ var base = '';
+ var type = isConstructor(value) ? 'Constructor' : getClass(value);
+ var array = isArray(value);
+ var braces = array ? settings.square : settings.curly;
- // Look up the keys of the object.
- var visibleKeys = Object.keys(value);
- var keys = ctx.showHidden ? Object.getOwnPropertyNames(value) : visibleKeys;
+ if (type in formatters) {
+ // types can be formatted by matching their internal class
+ base = settings.style(formatters[type](value), type);
+ }
- // Some type of object without properties can be shortcutted.
- if (keys.length === 0) {
- if (typeof value === 'function') {
- var name = value.name ? ': ' + value.name : '';
- return ctx.stylize('[Function' + name + ']', 'special');
- }
- if (isRegExp(value)) {
- return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
- }
- if (isDate(value)) {
- return ctx.stylize(Date.prototype.toString.call(value), 'date');
- }
- if (isError(value)) {
- return formatError(value);
- }
+ // prevent deeper inspection for primitives and regexps
+ if (isPrimitive(value) ||
+ !settings.showHidden && type === 'RegExp') {
+ return base;
}
- var base = '', array = false, braces = ['{', '}'];
- // Make Array say that they are Array
- if (isArray(value)) {
- array = true;
- braces = ['[', ']'];
+ if (settings.showHidden) {
+ var properties = Object.getOwnPropertyNames(value);
+ } else {
+ var properties = Object.keys(value);
}
- // Make functions say that they are functions
+
if (typeof value === 'function') {
- var n = value.name ? ': ' + value.name : '';
- base = ' [Function' + n + ']';
+ properties = properties.filter(function(key) {
+ // hide useless properties every function has
+ return !(key in Function);
+ });
}
- // Make RegExps say that they are RegExps
- if (isRegExp(value)) {
- base = ' ' + RegExp.prototype.toString.call(value);
+ // show prototype last for constructors
+ if (type === 'Constructor') {
+ var desc = Object.getOwnPropertyDescriptor(value, 'prototype');
+ if (desc && (settings.showHidden || desc.enumerable)) {
+ properties.push('prototype');
+ }
}
- // Make dates with properties first say the date
- if (isDate(value)) {
- base = ' ' + Date.prototype.toUTCString.call(value);
+ if (value !== global) {
+ var proto = Object.getPrototypeOf(value);
+ var ctor = proto && proto.constructor && proto.constructor.name;
+ // don't list protos for built-ins, even ones from other vm contexts
+ if (proto && !isNative(proto.constructor) || ctor === 'Object' &&
+ !Object.prototype.hasOwnProperty.call(proto, 'hasOwnProperty')) {
+ properties.push('__proto__');
+ }
}
- // Make error with message first say the error
- if (isError(value)) {
- base = ' ' + formatError(value);
+ if (properties.length === 0) {
+ if (base) return base;
+ if (!array || value.length === 0) return braces.join('');
}
-
- if (keys.length === 0 && (!array || value.length == 0)) {
- return braces[0] + base + braces[1];
+ if (depth < 0) {
+ return (base ? base+' ' : '') + settings.style('More', 'More', true);
}
- if (recurseTimes < 0) {
- if (isRegExp(value)) {
- return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
- } else {
- return ctx.stylize('[Object]', 'special');
+
+ try {
+ if (Object.isFrozen(value)) {
+ output.push(color('Frozen', 'ObjState', true));
+ } else if (Object.isSealed(value)) {
+ output.push(color('Sealed', 'ObjState', true));
+ } else if (!Object.isExtensible(value)) {
+ output.push(color('Non-Extensible', 'ObjState', true));
}
- }
+ } catch (e) {}
- ctx.seen.push(value);
- var output;
- if (array) {
- output = formatArray(ctx, value, recurseTimes, visibleKeys, keys);
- } else {
- output = keys.map(function(key) {
- return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array);
- });
+ settings.seen.push(value);
+ var output = [];
+
+ // iterate array indexes first
+ if (array && value.length) {
+ for (var i = 0, len = value.length; i < len; i++) {
+ if (typeof value[i] === 'undefined') {
+ output.push('');
+ } else {
+ output.push(formatProperty(value, i, depth, settings, array));
+ }
+ }
}
- ctx.seen.pop();
+ // properties on objects and named array properties
+ properties.forEach(function(key) {
+ if (!array || !numeric.test(key)) {
+ var prop = formatProperty(value, key, depth, settings, array);
+ if (prop.length) {
+ output.push(prop);
+ }
+ }
+ });
- return reduceToSingleString(output, base, braces);
+ return combine(output, base, braces);
}
+function formatProperty(value, key, depth, settings, array) {
+ // str starts as an array, val is a property descriptor
+ var str = [];
+ var val = Object.getOwnPropertyDescriptor(value, key);
+
+ // V8 c++ accessors like process.env that don't correctly
+ // work with Object.getOwnPropertyDescriptor
+ if (typeof val === 'undefined') {
+ val = {
+ value: value[key],
+ enumerable: true,
+ writable: true
+ };
+ }
-function formatPrimitive(ctx, value) {
- switch (typeof value) {
- case 'undefined':
- return ctx.stylize('undefined', 'undefined');
+ var name = key;
+ var nameFormat;
- case 'string':
- var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '')
- .replace(/'/g, "\\'")
- .replace(/\\"/g, '"') + '\'';
- return ctx.stylize(simple, 'string');
+ if (array && numeric.test(key)) {
+ name = '';
+ } else {
- case 'number':
- return ctx.stylize('' + value, 'number');
+ if (/^[a-zA-Z_\$][a-zA-Z0-9_\$]*$/.test(key)) {
+ // valid JavaScript name not requiring quotes
- case 'boolean':
- return ctx.stylize('' + value, 'boolean');
- }
- // For some reason typeof null is "object", so special case here.
- if (value === null) {
- return ctx.stylize('null', 'null');
- }
-}
+ if (val.value && !val.writable) {
+ // color non-writable differently
+ nameFormat = 'Constant';
+ } else {
+ // regular name
+ nameFormat = 'Name';
+ }
+ } else {
+ // name requires quoting
+ nameFormat = 'String';
+ name = quotes(name);
+ }
+ if (!val.enumerable) {
+ if (settings.style.name !== 'color') {
+ // add brackets if colors are disabled
+ name = '[' + name + ']';
+ } else {
+ // use different coloring otherwise
+ nameFormat = 'H' + nameFormat;
+ }
+ }
-function formatError(value) {
- return '[' + Error.prototype.toString.call(value) + ']';
-}
+ if (key === '__proto__') {
+ name = formatters.Proto(val.value);
+ nameFormat = 'Proto';
+ }
-function formatArray(ctx, value, recurseTimes, visibleKeys, keys) {
- var output = [];
- for (var i = 0, l = value.length; i < l; ++i) {
- if (Object.prototype.hasOwnProperty.call(value, String(i))) {
- output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
- String(i), true));
- } else {
- output.push('');
- }
+ name = settings.style(name, nameFormat) + ': ';
+ array = false;
}
- keys.forEach(function(key) {
- if (!key.match(/^\d+$/)) {
- output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
- key, true));
- }
- });
- return output;
-}
-function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) {
- var name, str, desc;
- desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] };
- if (desc.get) {
- if (desc.set) {
- str = ctx.stylize('[Getter/Setter]', 'special');
- } else {
- str = ctx.stylize('[Getter]', 'special');
- }
+ // check for accessors
+ val.get && str.push('Getter');
+ val.set && str.push('Setter');
+
+ // combine Getter/Setter, or evaluate to empty for data descriptors
+ str = str.join('/');
+ if (str) {
+ // accessor descriptor
+ str = settings.style(str, 'Accessor', true);
} else {
- if (desc.set) {
- str = ctx.stylize('[Setter]', 'special');
- }
- }
- if (visibleKeys.indexOf(key) < 0) {
- name = '[' + key + ']';
- }
- if (!str) {
- if (ctx.seen.indexOf(desc.value) < 0) {
- if (recurseTimes === null) {
- str = formatValue(ctx, desc.value, null);
+ // data descriptor
+ if (~settings.seen.indexOf(val.value)) {
+ // already seen
+ if (key !== 'constructor') {
+ str = settings.style('Circular', 'Circular', true);
} else {
- str = formatValue(ctx, desc.value, recurseTimes - 1);
- }
- if (str.indexOf('\n') > -1) {
- if (array) {
- str = str.split('\n').map(function(line) {
- return ' ' + line;
- }).join('\n').substr(2);
- } else {
- str = '\n' + str.split('\n').map(function(line) {
- return ' ' + line;
- }).join('\n');
- }
+ // hide redundent constructor reference
+ return '';
}
+
} else {
- str = ctx.stylize('[Circular]', 'special');
- }
- }
- if (typeof name === 'undefined') {
- if (array && key.match(/^\d+$/)) {
- return str;
- }
- name = JSON.stringify('' + key);
- if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) {
- name = name.substr(1, name.length - 2);
- name = ctx.stylize(name, 'name');
- } else {
- name = name.replace(/'/g, "\\'")
- .replace(/\\"/g, '"')
- .replace(/(^"|"$)/g, "'");
- name = ctx.stylize(name, 'string');
+ // recurse to subproperties
+ depth = depth === null ? null : depth - 1;
+ str = formatValue(val.value, key, depth, settings);
+
+ // prepend indentation for multiple lines
+ if (~str.indexOf('\n')) {
+ str = indent(str);
+ // trim the edges
+ str = array ? str.substring(str, 2) : '\n' + str;
+ }
}
}
- return name + ': ' + str;
+ return name + str;
}
+function indent(str){
+ return str.split('\n')
+ .map(function(line) { return ' ' + line; })
+ .join('\n');
+}
-function reduceToSingleString(output, base, braces) {
- var numLinesEst = 0;
+function combine(output, base, braces) {
+ var lines = 0;
+ // last line's length
var length = output.reduce(function(prev, cur) {
- numLinesEst++;
- if (cur.indexOf('\n') >= 0) numLinesEst++;
+ // number of lines
+ lines += ~cur.indexOf('\n') ? cur.match(/\n/).length : 0;
return prev + cur.length + 1;
}, 0);
- if (length > 60) {
- return braces[0] +
- (base === '' ? '' : base + '\n ') +
- ' ' +
- output.join(',\n ') +
- ' ' +
- braces[1];
+ if (base.length) {
+ // if given base make it so that it's not too long
+ length += base.length
+ if (length > 60 || lines > 1) {
+ base = ' ' + base;
+ output.unshift(lines > 1 ? '' : ' ');
+ } else {
+ base = ' ' + base + ' ';
+ }
+ } else {
+ base = ' ';
}
- return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1];
+ // combine lines with commas and pad as needed
+ var separator = (lines > 1 || length > 60) ? '\n ' : ' ';
+ base += output.join(',' + separator) + ' ';
+
+ // wrap in appropriate braces
+ return braces[0] + base + braces[1];
}
@@ -406,6 +523,38 @@ function objectToString(o) {
return Object.prototype.toString.call(o);
}
+// slice '[object Class]' to 'Class' for use in dict lookups
+function getClass(o) {
+ return objectToString(o).slice(8, -1);
+}
+
+
+// returns true for strings, numbers, booleans, null, undefined, NaN
+function isPrimitive(o) {
+ return Object(o) !== o;
+}
+exports.isPrimitive = isPrimitive;
+
+
+// returns true if a function has properties besides `constructor` in its prototype
+// and gracefully handles any input including undefined and undefined prototypes
+function isConstructor(o){
+ return typeof o === 'function' &&
+ Object(o.prototype) === o.prototype &&
+ Object.getOwnPropertyNames(o.prototype).length >
+ ('constructor' in o.prototype);
+}
+exports.isConstructor = isConstructor;
+
+
+// Test code code indicating [native code]
+function isNative(o){
+ return typeof o === 'function' &&
+ Function.prototype.toString.call(o).slice(-17) === '{ [native code] }';
+}
+exports.isConstructor = isConstructor;
+
+
exports.p = function() {
for (var i = 0, len = arguments.length; i < len; ++i) {
@@ -505,4 +654,4 @@ exports.inherits = function(ctor, superCtor) {
configurable: true
}
});
-};
+};
View
14 test/common.js
@@ -119,6 +119,20 @@ process.on('exit', function() {
knownGlobals.push(DataView);
}
+ if (global.Proxy) {
+ knownGlobals.push(Proxy);
+ }
+
+ if (global.WeakMap) {
+ knownGlobals.push(WeakMap);
+ }
+
+ if (global.Map) {
+ knownGlobals.push(Map);
+ knownGlobals.push(Set);
+ }
+
+
for (var x in global) {
var found = false;
View
59 test/simple/test-util-inspect.js
@@ -100,3 +100,62 @@ assert.doesNotThrow(function() {
// GH-2225
var x = { inspect: util.inspect };
assert.ok(util.inspect(x).indexOf('inspect') != -1);
+
+
+// Constructor detection and formatting
+function ctor(){}
+assert.equal(util.inspect(ctor), '[Function: ctor]');
+assert.equal(util.inspect(ctor.prototype, true), '{ [constructor]: [Function: ctor] }');
+ctor.prototype.p = 1;
+assert.equal(util.inspect(ctor), '[Constructor: ctor]');
+// check redundent constructor is hidden
+assert.equal(util.inspect(ctor, true), '{ [Constructor: ctor] [prototype]: { p: 1 } }');
+
+
+// Complex quoting
+assert.equal(util.inspect('""'), '\'""\'');
+assert.equal(util.inspect("''"), "\"''\"");
+assert.equal(util.inspect('""\''), '"\\"\\"\'"');
+assert.equal(util.inspect("''\""), '"\'\'\\""');
+assert.equal(util.inspect('\\'), "'\\\\'");
+
+// Property name quoting
+assert.equal(util.inspect({ $: 1 }), '{ $: 1 }');
+assert.equal(util.inspect({ '$^': 1 }), "{ '$^': 1 }");
+assert.equal(util.inspect({ '0': 1 }), "{ '0': 1 }");
+assert.equal(util.inspect({ "'q'": 1 }), "{ \"'q'\": 1 }");
+assert.equal(util.inspect({ '"q"': 1 }), "{ '\"q\"': 1 }");
+
+
+// RegExp formatting
+assert.equal(util.inspect(new RegExp), '/(?:)/');
+var regexp = util.inspect(new RegExp, true);
+assert.ok(regexp.indexOf('/(?:)/') != -1);
+assert.ok(regexp.indexOf('[lastIndex]') != -1);
+assert.ok(regexp.indexOf('[multiline]') != -1);
+assert.ok(regexp.indexOf('[global]') != -1);
+assert.ok(regexp.indexOf('[source]') != -1);
+assert.ok(regexp.indexOf('[ignoreCase]') != -1);
+
+
+// Accessor formatting
+var getter = { get p(){} }
+assert.equal(util.inspect(getter), '{ p: \u00ABGetter\u00BB }');
+var setter = { set p(){} }
+assert.equal(util.inspect(setter), '{ p: \u00ABSetter\u00BB }');
+var accessor = { get p(){}, set p(v){} };
+assert.equal(util.inspect(accessor), '{ p: \u00ABGetter/Setter\u00BB }');
+
+
+// Circular references
+var circular = {};
+circular.p = circular;
+assert.equal(util.inspect(circular), '{ p: \u00ABCircular\u00BB }');
+
+
+// Recurse limit
+var depth = { p1: { p2: { p3: { p4: {} } } } };
+
+assert.equal(util.inspect(depth), '{ p1: { p2: { p3: \u00ABMore\u00BB } } }');
+assert.equal(util.inspect(depth, null, 3), '{ p1: { p2: { p3: { p4: \u00ABMore\u00BB } } } }');
+assert.equal(util.inspect(depth, null, 4), '{ p1: { p2: { p3: { p4: {} } } } }');
Something went wrong with that request. Please try again.